diff --git a/src/client-library/deriv-api-client.ts b/src/client-library/deriv-api-client.ts index 9282be5..f90f2c5 100644 --- a/src/client-library/deriv-api-client.ts +++ b/src/client-library/deriv-api-client.ts @@ -8,10 +8,10 @@ import { TSocketSubscribeResponseData, } from '../types/api.types'; -export type DerivAPIClientOptions = { +export interface DerivAPIClientOptions { onOpen?: (e: Event) => void; onClose?: (e: CloseEvent) => void; -}; +} type DataHandler = (data: TSocketResponseData) => void; @@ -57,6 +57,8 @@ export type UnsubscribeHandlerArgs = { }; export class DerivAPIClient { + options?: DerivAPIClientOptions; + endpoint: string; websocket: WebSocket; requestHandler: RequestMap; subscribeHandler: SubscriptionMap; @@ -70,20 +72,25 @@ export class DerivAPIClient { keepAliveIntervalId: NodeJS.Timeout | null = null; constructor(endpoint: string, options?: DerivAPIClientOptions) { - this.websocket = new WebSocket(endpoint); + this.options = options; + this.endpoint = endpoint; + this.websocket = new WebSocket(this.endpoint); this.req_id = 0; this.requestHandler = new Map(); this.subscribeHandler = new Map(); this.waitForWebSocketOpen = PromiseUtils.createPromise(); + this.init(); + } + async init() { this.websocket.addEventListener('open', e => { - if (typeof options?.onOpen === 'function') options.onOpen(e); + if (typeof this.options?.onOpen === 'function') this.options.onOpen(e); const { resolve } = this.waitForWebSocketOpen; resolve({}); }); this.websocket.addEventListener('close', e => { - if (typeof options?.onClose === 'function') options.onClose(e); + if (typeof this.options?.onClose === 'function') this.options.onClose(e); }); this.websocket.addEventListener('message', async response => { @@ -258,6 +265,13 @@ export class DerivAPIClient { } } + async reconnect() { + this.websocket = new WebSocket(this.endpoint); + this.waitForWebSocketOpen = PromiseUtils.createPromise(); + this.init(); + this.reinitializeSubscriptions(this.subscribeHandler, this.authorizePayload); + } + isSocketClosingOrClosed() { return ![2, 3].includes(this.websocket.readyState); } diff --git a/src/client-library/deriv-api-manager.ts b/src/client-library/deriv-api-manager.ts index ddfee1c..bf8b736 100644 --- a/src/client-library/deriv-api-manager.ts +++ b/src/client-library/deriv-api-manager.ts @@ -1,12 +1,33 @@ import { DerivAPIClient, DerivAPIClientOptions } from './deriv-api-client'; +export interface DerivAPIManagerOptions extends DerivAPIClientOptions { + reconnect?: boolean; +} + export class DerivAPIManager { + is_reconnecting: boolean; options?: DerivAPIClientOptions; activeClient: DerivAPIClient; clientList: Map = new Map(); - constructor(endpoint: string, options?: DerivAPIClientOptions) { - const client = new DerivAPIClient(endpoint, options); + constructor(endpoint: string, options?: DerivAPIClientOptions & DerivAPIManagerOptions) { + const { reconnect = true, ...api_options } = options || {}; + + const client = new DerivAPIClient(endpoint, { + onClose: async () => { + api_options?.onClose; + // Only reconnect if the reconnect option is true and is not already reconnecting. + if (reconnect && !this.is_reconnecting) { + this.is_reconnecting = true; + await this.handleReconnect(); + // Wait before WebSocket is open before setting is_reconnecting to false + await this.activeClient.waitForWebSocketOpen; + this.is_reconnecting = false; + } + }, + onOpen: api_options?.onOpen, + }); + this.is_reconnecting = false; this.clientList.set(endpoint, client); this.activeClient = client; this.options = options; @@ -16,6 +37,10 @@ export class DerivAPIManager { return this.activeClient; } + async handleReconnect() { + await this.activeClient.reconnect(); + } + /** * Creates a new connection and swap out the current active connection with it. * Brings authorize and subscription context to the new connection. diff --git a/src/context/api-context.tsx b/src/context/api-context.tsx index 648a925..047db65 100644 --- a/src/context/api-context.tsx +++ b/src/context/api-context.tsx @@ -2,14 +2,14 @@ import { createContext, PropsWithChildren } from 'react'; import { URLUtils } from '@deriv-com/utils'; import { DerivAPIManager } from '../client-library/deriv-api-manager'; -export const derivAPIClient = new DerivAPIManager(URLUtils.getWebsocketURL()); - type APIData = { derivAPIClient: DerivAPIManager; }; export const APIDataContext = createContext(null); +const derivApiManager = new DerivAPIManager(URLUtils.getWebsocketURL()); + /** * Provides a React Query client and API data context to its child components. * @@ -20,5 +20,5 @@ export const APIDataContext = createContext(null); * @returns {JSX.Element} The provider component wrapping its children with API data context and React Query client. */ export const APIProvider = ({ children }: PropsWithChildren) => { - return {children}; + return {children}; };