import { DocumentNode , GraphQLFormattedError } from 'graphql' ; import gql from 'graphql-tag' ; import { FinchCache , Listener } from './cache' ; import { FinchDefaultPortName , FinchQueryOptions , GenericVariables , } from '@finch-graphql/types' ; import { isDocumentNode } from '../utils' ; import { messageCreator , queryApi } from './client' ; import { connectPort } from '@finch-graphql/browser-polyfill' ; import { v4 } from 'uuid' ; interface FinchClientOptions { cache ? : FinchCache ; id ? : string ; messageKey ? : string ; portName ? : string ; useMessages ? : boolean ; messageTimeout ? : number ; autoStart ? : boolean ; } enum FinchClientStatus { Disconnected = 'disconnected' , Connected = 'connected' , Connecting = 'connecting' , Idle = 'idle' } /**
 * FinchClient is a class that constructs a client that is able to query
 * a FinchApi. A client is able to hold configuration on how the API calls
 * are made and also any query caching.
 */ export class FinchClient { private cache : FinchCache | undefined ; private id : string | undefined ; private messageKey : string | undefined ; private port : browser . runtime . Port | chrome . runtime . Port | null ; private portName = FinchDefaultPortName ; private portReconnectTimeout = 1000 ; private useMessages : boolean ; private messageTimeout = 5000 ; public status = FinchClientStatus . Idle ; param options param options . cache param options . id param options . messageKey param options . disablePort param options . messageTimeout constructor ( { cache , id , messageKey , portName , useMessages , messageTimeout , autoStart = true , } = { } ) { this . cache = cache ; this . id = id ; this . messageKey = messageKey ; this . portName = portName || this . portName ; this . useMessages = useMessages ?? false ; this . messageTimeout = messageTimeout ?? this . messageTimeout ; if ( autoStart ) { this . start ( ) ; } } start ( ) { if ( this . status !== FinchClientStatus . Idle ) { return ; } if ( this . useMessages ) { this . status = FinchClientStatus . Connected ; } else { this . status = FinchClientStatus . Connecting ; this . connectPort ( ) ; } } stop ( ) { this . port . disconnect ( ) ; setTimeout ( ( ) => { this . status = FinchClientStatus . Idle ; clearTimeout ( this . portReconnectTimeout ) ; } , 0 ) ; } connectPort ( ) { const port = connectPort ( { extensionId : this . id , connectInfo : { name : this . portName } } ) ; this . port = port ; this . status = FinchClientStatus . Connected ; port . onDisconnect . addListener ( ( ) => { this . status = FinchClientStatus . Disconnected ; this . portReconnectTimeout = window . setTimeout ( ( ) => { if ( this . status === FinchClientStatus . Idle ) { return ; } console . warn ( `Reattempting reconnect to background script` ) ; this . connectPort ( ) ; } , this . portReconnectTimeout ) ; this . port = null ; } ) ; } private queryApiViaPort < Query extends { } = { } , Variables extends GenericVariables = { } > ( query , variables ? , options = { } ) { return new Promise ( resolve => { const messageId = v4 ( ) ; const decoratedMessage = messageCreator ( query , variables , options . messageKey , ! ! this . id ) ; this . port ?. postMessage ( { id : messageId , ... decoratedMessage } ) ; const requestTimeout = setTimeout ( ( ) => { resolve ( { data : null , errors : [ { message : `Timed out waiting for response from background script` } , ] } ) ; } , this . messageTimeout ) ; const onMessage = ( { id , external , ... response } ) => { if ( id === messageId ) { clearTimeout ( requestTimeout ) ; this . port ?. onMessage . removeListener ( onMessage ) ; resolve ( response ) ; } } ; this . port ?. onMessage . addListener ( onMessage ) ; } ) ; } async queryApi < Query extends { } = { } , Variables extends GenericVariables = { } > ( query , variables ? , options = { } ) { if ( this . useMessages ) { return queryApi < Query , Variables > ( query , variables , options ) ; } return this . queryApiViaPort < Query , Variables > ( query , variables , options ) ; } param query param variables param options returns async query < Query extends { } = { } , Variables extends GenericVariables = { } > ( query , variables ? , options = { } ) { const documentNode = isDocumentNode ( query ) ? query : gql ( query ) ; const result = await this . queryApi < Query , Variables > ( documentNode , variables , { id : this . id , messageKey : this . messageKey , ... options } ) ; if ( this . cache && result ?. data ) { this . cache . setCache ( documentNode , variables , result . data ) ; } return result ; } param mutation param variables param options returns async mutate < Query extends { } = { } , Variables extends GenericVariables = { } > ( mutation , variables ? , options = { } ) { const documentNode = isDocumentNode ( mutation ) ? mutation : gql ( mutation ) ; const result = await this . queryApi < Query , Variables > ( documentNode , variables , { id : this . id , messageKey : this . messageKey , ... options } ) ; return result ; } param query param variables param listener returns subscribe < Query extends unknown > ( query , variables , listener ) { if ( ! this . cache ) { return ( ) => { } ; } const documentNode = isDocumentNode ( query ) ? query : gql ( query ) ; return this . cache . subscribe < Query > ( documentNode , variables , listener ) ; } }