Skip to content

Commit 1ebece6

Browse files
authored
Jim/wall 5227/switch ws connection when clients switch between real and demo wallets 1 (#17949)
* chore: update generic error message component for cashier and wallets * feat: add endpoint switching * revert: revert unrelated changes * style: rename variables to more meaningful names * style: sort imports * style: rename variable and remove unnecessary type attribute
1 parent 2ac55e6 commit 1ebece6

File tree

7 files changed

+75
-25
lines changed

7 files changed

+75
-25
lines changed

packages/api-v2/src/APIProvider.tsx

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import React, { PropsWithChildren, createContext, useContext, useEffect, useRef, useState, useCallback } from 'react';
1+
import React, { createContext, PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'react';
2+
23
import { getAppId, getSocketURL } from '@deriv/shared';
34
import { getInitialLanguage } from '@deriv-com/translations';
45
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6+
57
import { TSocketRequestPayload, TSocketResponseData, TSocketSubscribableEndpointNames } from '../types';
6-
import { hashObject } from './utils';
8+
79
import WSClient from './ws-client/ws-client';
10+
import { PLATFORMS } from './constants';
11+
import { hashObject } from './utils';
812

913
type TSubscribeFunction = <T extends TSocketSubscribableEndpointNames>(
1014
name: T,
@@ -27,14 +31,14 @@ type APIContextData = {
2731
setOnConnected: (onConnected: () => void) => void;
2832
connection: WebSocket;
2933
wsClient: WSClient;
34+
createNewWSConnection: () => void;
3035
};
3136

3237
/**
3338
* Retrieves the WebSocket URL based on the current environment.
3439
* @returns {string} The WebSocket URL.
3540
*/
36-
const getWebSocketURL = () => {
37-
const endpoint = getSocketURL();
41+
const getWebSocketURL = (endpoint: string) => {
3842
const app_id = getAppId();
3943
const language = getInitialLanguage();
4044
return `wss://${endpoint}/websockets/v3?app_id=${app_id}&l=${language}&brand=deriv`;
@@ -45,8 +49,8 @@ const APIContext = createContext<APIContextData | null>(null);
4549
/**
4650
* @returns {WebSocket} The initialized WebSocket instance.
4751
*/
48-
const initializeConnection = (onWSClose: () => void, onOpen?: () => void): WebSocket => {
49-
const wss_url = getWebSocketURL();
52+
const initializeConnection = (endpoint: string, onWSClose: () => void, onOpen?: () => void): WebSocket => {
53+
const wss_url = getWebSocketURL(endpoint);
5054

5155
const connection = new WebSocket(wss_url);
5256
connection.addEventListener('close', () => {
@@ -67,12 +71,13 @@ const initializeConnection = (onWSClose: () => void, onOpen?: () => void): WebSo
6771
type TAPIProviderProps = {
6872
/** If set to true, the APIProvider will instantiate it's own socket connection. */
6973
standalone?: boolean;
74+
platform?: string;
7075
};
7176

7277
type SubscribeReturnType = ReturnType<TSubscribeFunction>; // This captures the entire return type of TSubscribeFunction
7378
type UnwrappedSubscription = Awaited<SubscribeReturnType>;
7479

75-
const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
80+
const APIProvider = ({ children, platform }: PropsWithChildren<TAPIProviderProps>) => {
7681
const [reconnect, setReconnect] = useState(false);
7782
const connectionRef = useRef<WebSocket>();
7883
const subscriptionsRef = useRef<Record<string, UnwrappedSubscription['subscription']>>();
@@ -87,6 +92,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
8792

8893
const language = getInitialLanguage();
8994
const [prevLanguage, setPrevLanguage] = useState<string>(language);
95+
const endpoint = getSocketURL(platform === PLATFORMS.WALLETS);
9096

9197
useEffect(() => {
9298
isMounted.current = true;
@@ -109,6 +115,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
109115
// have to be here and not inside useEffect as there are places in code expecting this to be available
110116
if (!connectionRef.current) {
111117
connectionRef.current = initializeConnection(
118+
endpoint,
112119
() => {
113120
if (isMounted.current) setReconnect(true);
114121
},
@@ -118,6 +125,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
118125
}
119126

120127
wsClientRef.current.setWs(connectionRef.current);
128+
wsClientRef.current.setEndpoint(endpoint);
121129
if (isMounted.current) {
122130
isOpenRef.current = true;
123131
if (onConnectedRef.current) {
@@ -197,6 +205,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
197205
let reconnectTimerId: NodeJS.Timeout;
198206
if (reconnect) {
199207
connectionRef.current = initializeConnection(
208+
endpoint,
200209
() => {
201210
reconnectTimerId = setTimeout(() => {
202211
if (isMounted.current) {
@@ -209,6 +218,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
209218
throw new Error('Connection is not set');
210219
}
211220
wsClientRef.current.setWs(connectionRef.current);
221+
wsClientRef.current.setEndpoint(endpoint);
212222
if (onReconnectedRef.current) {
213223
onReconnectedRef.current();
214224
}
@@ -218,7 +228,7 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
218228
}
219229

220230
return () => clearTimeout(reconnectTimerId);
221-
}, [reconnect]);
231+
}, [endpoint, reconnect]);
222232

223233
// reconnects to latest WS url for new language only when language changes
224234
useEffect(() => {
@@ -229,10 +239,15 @@ const APIProvider = ({ children }: PropsWithChildren<TAPIProviderProps>) => {
229239
// eslint-disable-next-line react-hooks/exhaustive-deps
230240
}, [language]);
231241

242+
const createNewWSConnection = useCallback(() => {
243+
setReconnect(true);
244+
}, []);
245+
232246
return (
233247
<APIContext.Provider
234248
value={{
235249
subscribe,
250+
createNewWSConnection,
236251
unsubscribe,
237252
queryClient: reactQueryRef.current,
238253
setOnReconnected,

packages/api-v2/src/AuthProvider.tsx

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import React, { createContext, useState, useContext, useCallback, useEffect, useMemo } from 'react';
2-
import { useAPIContext } from './APIProvider';
1+
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
32

43
import { getAccountsFromLocalStorage, getActiveLoginIDFromLocalStorage, getToken } from '@deriv/utils';
5-
import useMutation from './useMutation';
6-
import { TSocketSubscribableEndpointNames, TSocketResponseData, TSocketRequestPayload } from '../types';
7-
import useAPI from './useAPI';
4+
import { AppIDConstants } from '@deriv-com/utils';
5+
6+
import { TSocketRequestPayload, TSocketResponseData, TSocketSubscribableEndpointNames } from '../types';
7+
8+
import { useAPIContext } from './APIProvider';
89
import { API_ERROR_CODES } from './constants';
10+
import useAPI from './useAPI';
11+
import useMutation from './useMutation';
912

1013
// Define the type for the context state
1114
type AuthContextType = {
@@ -25,6 +28,7 @@ type AuthContextType = {
2528
name: T,
2629
payload?: TSocketRequestPayload<T>
2730
) => {
31+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2832
subscribe: (onData: (response: any) => void) => Promise<{ unsubscribe: () => Promise<void> }>;
2933
};
3034
};
@@ -108,7 +112,7 @@ const AuthProvider = ({ loginIDKey, children, cookieTimeout, selectDefaultAccoun
108112

109113
const { mutateAsync } = useMutation('authorize');
110114

111-
const { queryClient, setOnReconnected, setOnConnected, wsClient } = useAPIContext();
115+
const { queryClient, setOnReconnected, setOnConnected, wsClient, createNewWSConnection } = useAPIContext();
112116

113117
const [isLoading, setIsLoading] = useState(true);
114118
const [isSwitching, setIsSwitching] = useState(false);
@@ -125,6 +129,7 @@ const AuthProvider = ({ loginIDKey, children, cookieTimeout, selectDefaultAccoun
125129
const subscribe = useCallback(
126130
<T extends TSocketSubscribableEndpointNames>(name: T, payload?: TSocketRequestPayload<T>) => {
127131
return {
132+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
128133
subscribe: (onData: (response: any) => void) => {
129134
return wsClient?.subscribe(name, payload, onData);
130135
},
@@ -149,8 +154,15 @@ const AuthProvider = ({ loginIDKey, children, cookieTimeout, selectDefaultAccoun
149154
if (!activeAccount) return;
150155

151156
localStorage.setItem(loginIDKey ?? 'active_loginid', activeLoginID);
157+
const isDemo = activeAccount.is_virtual;
158+
const shouldCreateNewWSConnection =
159+
(isDemo && wsClient?.endpoint === AppIDConstants.environments.real) ||
160+
(!isDemo && wsClient?.endpoint === AppIDConstants.environments.demo);
161+
if (shouldCreateNewWSConnection) {
162+
createNewWSConnection();
163+
}
152164
},
153-
[loginIDKey]
165+
[loginIDKey, wsClient?.endpoint, createNewWSConnection]
154166
);
155167

156168
useEffect(() => {
@@ -267,8 +279,21 @@ const AuthProvider = ({ loginIDKey, children, cookieTimeout, selectDefaultAccoun
267279
isInitializing,
268280
subscribe,
269281
logout,
282+
createNewWSConnection,
270283
};
271-
}, [data, switchAccount, refetch, isLoading, isError, isFetching, isSuccess, loginid, logout, subscribe]);
284+
}, [
285+
data,
286+
switchAccount,
287+
refetch,
288+
isLoading,
289+
isError,
290+
isFetching,
291+
isSuccess,
292+
loginid,
293+
logout,
294+
createNewWSConnection,
295+
subscribe,
296+
]);
272297

273298
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
274299
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './countries';
22
export * from './onfido';
33
export * from './errorCodes';
4+
export * from './platforms';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const PLATFORMS = { WALLETS: 'wallets' } as const;

packages/api-v2/src/ws-client/ws-client.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import SubscriptionsManager from './subscriptions-manager';
2-
import request from './request';
31
import {
4-
TSocketResponse,
5-
TSocketRequestPayload,
62
TSocketEndpointNames,
3+
TSocketRequestPayload,
4+
TSocketResponse,
75
TSocketSubscribableEndpointNames,
86
} from '../../types';
97

8+
import request from './request';
9+
import SubscriptionsManager from './subscriptions-manager';
10+
1011
/**
1112
* WSClient as main instance
1213
*/
@@ -15,6 +16,7 @@ export default class WSClient {
1516
subscriptionManager: SubscriptionsManager;
1617
isAuthorized = false;
1718
onAuthorized?: () => void;
19+
endpoint?: string;
1820

1921
constructor(onAuthorized?: () => void) {
2022
this.onAuthorized = onAuthorized;
@@ -28,6 +30,10 @@ export default class WSClient {
2830
}
2931
}
3032

33+
setEndpoint(endpoint: string) {
34+
this.endpoint = endpoint;
35+
}
36+
3137
private onWebsocketAuthorized() {
3238
if (!this.ws) {
3339
return;
@@ -47,7 +53,7 @@ export default class WSClient {
4753
return Promise.reject(new Error('WS is not set'));
4854
}
4955
return request(this.ws, name, payload).then((response: TSocketResponse<TSocketEndpointNames>) => {
50-
if ((response as unknown as any).msg_type === 'authorize') {
56+
if ('msg_type' in response && response.msg_type === 'authorize') {
5157
this.onWebsocketAuthorized();
5258
}
5359

packages/shared/src/utils/config/config.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const getAppId = () => {
7777
return app_id;
7878
};
7979

80-
export const getSocketURL = () => {
80+
export const getSocketURL = (is_wallets = false) => {
8181
const local_storage_server_url = window.localStorage.getItem('config.server_url');
8282
if (local_storage_server_url) return local_storage_server_url;
8383

@@ -87,8 +87,10 @@ export const getSocketURL = () => {
8787
const params = new URLSearchParams(document.location.search.substring(1));
8888
active_loginid_from_url = params.get('acct1');
8989
}
90-
91-
const loginid = window.localStorage.getItem('active_loginid') || active_loginid_from_url;
90+
const local_storage_loginid = is_wallets
91+
? window.localStorage.getItem('active_wallet_loginid')
92+
: window.localStorage.getItem('active_loginid');
93+
const loginid = local_storage_loginid || active_loginid_from_url;
9294
const is_real = loginid && !/^(VRT|VRW)/.test(loginid);
9395

9496
const server = is_real ? 'green' : 'blue';

packages/wallets/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const App: React.FC<TProps> = ({
4444
const defaultLanguage = preferredLanguage ?? language;
4545

4646
return (
47-
<APIProvider standalone>
47+
<APIProvider platform='wallets' standalone>
4848
<WalletsAuthProvider logout={logout}>
4949
<TranslationProvider defaultLang={defaultLanguage} i18nInstance={i18nInstance}>
5050
<React.Suspense fallback={<Loader />}>

0 commit comments

Comments
 (0)