Repository: deriv-com/bot
Pull request #454: Rupato/fix: implementation of TMB
Author: login: rupato-deriv
Created: 2025-05-26T09:35:13Z | Merged: 2025-05-28T03:33:37Z

This pull request introduces significant changes related to the implementation of a new feature flag, `is_tmb_enabled`, which impacts authentication flows and session management. It includes updates to various components, hooks, and utilities to support this feature. Additionally, some cleanup and refactoring have been performed to remove unnecessary logs and improve code readability.

### Feature Implementation: `is_tmb_enabled`

* **New Hook for TMB Support**: Added a new `useTMB` hook to manage TMB-related logic, including session checks, token processing, and logout handling. This hook centralizes the logic for determining if TMB is enabled and provides utility functions like `onRenderTMBCheck`. (`src/hooks/useTMB.ts`)
* **Integration Across Components**:
  * Updated `AppHeader`, `Layout`, and `AppWrapper` components to conditionally handle TMB-specific logic, such as invoking `onRenderTMBCheck` when `is_tmb_enabled` is true. (`src/components/layout/header/header.tsx` - [[1]](diffhunk://#diff-369c35ed105770696d1e076b245f598bf8b44660ac3fefe85ee26b5f31e5b7c3R40-R41) [[2]](diffhunk://#diff-369c35ed105770696d1e076b245f598bf8b44660ac3fefe85ee26b5f31e5b7c3R135-R137) [[3]](diffhunk://#diff-369c35ed105770696d1e076b245f598bf8b44660ac3fefe85ee26b5f31e5b7c3R151); `src/components/layout/index.tsx` - [[4]](diffhunk://#diff-297411bbdb20a2ca96207a6e5284cd0598f90d22b776fa55232a04fae431835aR19) [[5]](diffhunk://#diff-297411bbdb20a2ca96207a6e5284cd0598f90d22b776fa55232a04fae431835aR138-R140) [[6]](diffhunk://#diff-297411bbdb20a2ca96207a6e5284cd0598f90d22b776fa55232a04fae431835aR154); `src/pages/main/main.tsx` - [[7]](diffhunk://#diff-e315c9587afa8674eab883837b77bd5ad552be5a25c2b29ea062b071ba4aff36R83-R84) [[8]](diffhunk://#diff-e315c9587afa8674eab883837b77bd5ad552be5a25c2b29ea062b071ba4aff36R238-R240) [[9]](diffhunk://#diff-e315c9587afa8674eab883837b77bd5ad552be5a25c2b29ea062b071ba4aff36R254)
  * Modified `AuthWrapper` and `CallbackPage` to prevent certain authentication events when TMB is enabled. (`src/app/AuthWrapper.tsx` - [[1]](diffhunk://#diff-af63e738f04e9a6ee6aa4a007406f027572f6c9ba0d97b9dadc0f2e36d13521aR44-R46); `src/pages/callback/callback-page.tsx` - [[2]](diffhunk://#diff-48442a2313eb74f85495b7177c68f5054c9518faf6198893217e053cb9a7b836L74-R76)
  * Updated `AppStore` to account for the `is_tmb_enabled` flag in login state validation. (`src/stores/app-store.ts` - [src/stores/app-store.tsR90-R94](diffhunk://#diff-2b2c12521ce90c2e7b4bd63888105bcd4112e69447589cd65eb23b4506000d57R90-R94))

### Dependency Update

* **Auth Client Version Update**: Upgraded `@deriv-com/auth-client` from version `1.4.6` to `1.5.1` to support new authentication features required for TMB. (`package.json`)

### Code Cleanup and Refactoring

* **Removed Debug Logs**: Cleaned up unnecessary `console.log` statements in the `useMobileMenuConfig` function to improve code cleanliness. (`src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx`)
* **Miscellaneous Imports**: Added `useTMB` imports across various files to integrate the new hook. (`src/components/layout/header/header.tsx` - [[1]](diffhunk://#diff-369c35ed105770696d1e076b245f598bf8b44660ac3fefe85ee26b5f31e5b7c3R11); `src/components/layout/index.tsx` - [[2]](diffhunk://#diff-297411bbdb20a2ca96207a6e5284cd0598f90d22b776fa55232a04fae431835aR6); `src/pages/callback/callback-page.tsx` - [[3]](diffhunk://#diff-48442a2313eb74f85495b7177c68f5054c9518faf6198893217e053cb9a7b836R5); `src/pages/main/main.tsx` - [[4]](diffhunk://#diff-e315c9587afa8674eab883837b77bd5ad552be5a25c2b29ea062b071ba4aff36R19)


Diff:
diff --git a/package-lock.json b/package-lock.json
index 85605a7dd..3be48fccb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,11 +10,11 @@
             "dependencies": {
                 "@datadog/browser-rum": "^5.31.1",
                 "@deriv-com/analytics": "^1.5.3",
-                "@deriv-com/auth-client": "^1.4.6",
+                "@deriv-com/auth-client": "^1.5.1",
                 "@deriv-com/quill-ui": "1.18.1",
                 "@deriv-com/translations": "^1.3.9",
                 "@deriv-com/ui": "^1.37.1",
-                "@deriv-com/utils": "*",
+                "@deriv-com/utils": "latest",
                 "@deriv/deriv-api": "^1.0.15",
                 "@deriv/deriv-charts": "^2.5.1",
                 "@deriv/js-interpreter": "^3.0.0",
@@ -2618,9 +2618,9 @@
             }
         },
         "node_modules/@deriv-com/auth-client": {
-            "version": "1.4.6",
-            "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.4.6.tgz",
-            "integrity": "sha512-oRrrdT9UVeFrATm0sNr8cgMF0dIlxrymjRMyZ/kbR5f+Ofxm9VGalNzO6R2ZjcazoMvdbP0PRkQZpRH7IJMPfA==",
+            "version": "1.5.1",
+            "resolved": "https://registry.npmjs.org/@deriv-com/auth-client/-/auth-client-1.5.1.tgz",
+            "integrity": "sha512-zeRwiSRPxjJyF74+Oq//E93VTrGL1OpWzo4RMCeKNoqrwm/Mz077I2eQMG6nujvqBY8ygXzz5BowZumxmQaH9g==",
             "dependencies": {
                 "@deriv-com/utils": "^0.0.42",
                 "js-cookie": "3.0.5",
diff --git a/package.json b/package.json
index f5bb88687..69f982878 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
     "dependencies": {
         "@datadog/browser-rum": "^5.31.1",
         "@deriv-com/analytics": "^1.5.3",
-        "@deriv-com/auth-client": "^1.4.6",
+        "@deriv-com/auth-client": "^1.5.1",
         "@deriv-com/quill-ui": "1.18.1",
         "@deriv-com/translations": "^1.3.9",
         "@deriv-com/ui": "^1.37.1",
diff --git a/src/app/AuthWrapper.tsx b/src/app/AuthWrapper.tsx
index e2399d9e7..d5d4b9d93 100644
--- a/src/app/AuthWrapper.tsx
+++ b/src/app/AuthWrapper.tsx
@@ -41,8 +41,9 @@ const setLocalStorageToken = async (
                         // Set isAuthComplete to true to prevent the app from getting stuck in loading state
                         setIsAuthComplete(true);
 
+                        const is_tmb_enabled = localStorage.getItem('is_tmb_enabled') === 'true';
                         // Only emit the InvalidToken event if logged_state is true
-                        if (Cookies.get('logged_state') === 'true') {
+                        if (Cookies.get('logged_state') === 'true' && !is_tmb_enabled) {
                             // Emit an event that can be caught by the application to retrigger OIDC authentication
                             globalObserver.emit('InvalidToken', { error });
                         }
diff --git a/src/components/layout/header/header.tsx b/src/components/layout/header/header.tsx
index 2df18dfe5..0fece5639 100644
--- a/src/components/layout/header/header.tsx
+++ b/src/components/layout/header/header.tsx
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
 import clsx from 'clsx';
 import { observer } from 'mobx-react-lite';
 import { standalone_routes } from '@/components/shared';
@@ -7,6 +8,7 @@ import { useOauth2 } from '@/hooks/auth/useOauth2';
 import useGrowthbookGetFeatureValue from '@/hooks/growthbook/useGrowthbookGetFeatureValue';
 import { useApiBase } from '@/hooks/useApiBase';
 import { useStore } from '@/hooks/useStore';
+import useTMB from '@/hooks/useTMB';
 import { StandaloneCircleUserRegularIcon } from '@deriv/quill-icons/Standalone';
 import { requestOidcAuthentication } from '@deriv-com/auth-client';
 import { Localize, useTranslations } from '@deriv-com/translations';
@@ -35,6 +37,8 @@ const AppHeader = observer(() => {
     const { isSingleLoggingIn } = useOauth2();
 
     const { featureFlagValue } = useGrowthbookGetFeatureValue<any>({ featureFlag: 'hub_enabled_country_list' });
+    const { onRenderTMBCheck } = useTMB();
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
 
     const renderAccountSection = () => {
         if (isAuthorizing || isSingleLoggingIn) {
@@ -128,19 +132,23 @@ const AppHeader = observer(() => {
                             const query_param_currency =
                                 sessionStorage.getItem('query_param_currency') || currency || 'USD';
                             try {
-                                await requestOidcAuthentication({
-                                    redirectCallbackUri: `${window.location.origin}/callback`,
-                                    ...(query_param_currency
-                                        ? {
-                                              state: {
-                                                  account: query_param_currency,
-                                              },
-                                          }
-                                        : {}),
-                                }).catch(err => {
-                                    // eslint-disable-next-line no-console
-                                    console.error(err);
-                                });
+                                if (is_tmb_enabled) {
+                                    onRenderTMBCheck();
+                                } else {
+                                    await requestOidcAuthentication({
+                                        redirectCallbackUri: `${window.location.origin}/callback`,
+                                        ...(query_param_currency
+                                            ? {
+                                                  state: {
+                                                      account: query_param_currency,
+                                                  },
+                                              }
+                                            : {}),
+                                    }).catch(err => {
+                                        // eslint-disable-next-line no-console
+                                        console.error(err);
+                                    });
+                                }
                             } catch (error) {
                                 // eslint-disable-next-line no-console
                                 console.error(error);
diff --git a/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx b/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx
index 2d479ef9f..8b05e7c2a 100644
--- a/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx
+++ b/src/components/layout/header/mobile-menu/use-mobile-menu-config.tsx
@@ -93,10 +93,6 @@ const useMobileMenuConfig = (client?: RootStore['client']) => {
     // Determine the appropriate redirect URL based on user's country
     const getRedirectUrl = () => {
         // Check if the user's country is in the hub-enabled country list
-        console.log('test is_virtual', featureFlagValue?.hub_enabled_country_list);
-        console.log('test residence', client?.residence);
-        console.log('test is_hub_enabled_country', is_hub_enabled_country);
-        console.log('test has_wallet', has_wallet);
         if (has_wallet && is_hub_enabled_country) {
             return getAccountUrl(standalone_routes.account_settings);
         }
diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx
index 086b5ba1f..95db242c8 100644
--- a/src/components/layout/index.tsx
+++ b/src/components/layout/index.tsx
@@ -3,6 +3,7 @@ import clsx from 'clsx';
 import Cookies from 'js-cookie';
 import { Outlet } from 'react-router-dom';
 import { api_base } from '@/external/bot-skeleton';
+import useTMB from '@/hooks/useTMB';
 import { requestOidcAuthentication } from '@deriv-com/auth-client';
 import { useDevice } from '@deriv-com/ui';
 import { crypto_currencies_display_order, fiat_currencies_display_order } from '../shared';
@@ -15,6 +16,7 @@ const Layout = () => {
     const { isDesktop } = useDevice();
 
     const isCallbackPage = window.location.pathname === '/callback';
+    const { is_tmb_enabled = false, onRenderTMBCheck } = useTMB();
     const isLoggedInCookie = Cookies.get('logged_state') === 'true';
     const isEndpointPage = window.location.pathname.includes('endpoint');
     const checkClientAccount = JSON.parse(localStorage.getItem('clientAccounts') ?? '{}');
@@ -133,19 +135,23 @@ const Layout = () => {
                 sessionStorage.setItem('query_param_currency', query_param_currency);
             }
             try {
-                requestOidcAuthentication({
-                    redirectCallbackUri: `${window.location.origin}/callback`,
-                    ...(query_param_currency
-                        ? {
-                              state: {
-                                  account: query_param_currency,
-                              },
-                          }
-                        : {}),
-                }).catch(err => {
-                    // eslint-disable-next-line no-console
-                    console.error(err);
-                });
+                if (is_tmb_enabled) {
+                    onRenderTMBCheck();
+                } else {
+                    requestOidcAuthentication({
+                        redirectCallbackUri: `${window.location.origin}/callback`,
+                        ...(query_param_currency
+                            ? {
+                                  state: {
+                                      account: query_param_currency,
+                                  },
+                              }
+                            : {}),
+                    }).catch(err => {
+                        // eslint-disable-next-line no-console
+                        console.error(err);
+                    });
+                }
             } catch (err) {
                 // eslint-disable-next-line no-console
                 console.error(err);
diff --git a/src/external/bot-skeleton/services/api/api-base.ts b/src/external/bot-skeleton/services/api/api-base.ts
index dd087c8ca..566c01552 100644
--- a/src/external/bot-skeleton/services/api/api-base.ts
+++ b/src/external/bot-skeleton/services/api/api-base.ts
@@ -167,7 +167,8 @@ class APIBase {
             const { authorize, error } = await this.api.authorize(this.token);
             if (error) {
                 if (error.code === 'InvalidToken') {
-                    if (Cookies.get('logged_state') === 'true') {
+                    const is_tmb_enabled = JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false');
+                    if (Cookies.get('logged_state') === 'true' && !is_tmb_enabled) {
                         globalObserver.emit('InvalidToken', { error });
                     } else {
                         clearAuthData();
diff --git a/src/hooks/useTMB.ts b/src/hooks/useTMB.ts
new file mode 100644
index 000000000..3c952a7c0
--- /dev/null
+++ b/src/hooks/useTMB.ts
@@ -0,0 +1,193 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+const TMBState = {
+    isInitialized: false,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+    }, [logout, domains, currentDomain]);
+
+    const getActiveSessions = useCallback(async () => {
+        try {
+            return await requestSessionActive();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        TMBState.checkInProgress = true;
+
+        try {
+            const activeSessions = await getActiveSessions();
+
+            if (!activeSessions?.active && !isEndpointPage) {
+                console.error('Failed to get active sessions: No data returned');
+                TMBState.checkInProgress = false;
+                return handleLogout();
+            } else if (activeSessions?.active) {
+                if (Array.isArray(activeSessions.tokens) && activeSessions.tokens.length > 0) {
+                    const { accountsList, clientAccounts } = processTokens(activeSessions.tokens);
+
+                    localStorage.setItem('accountsList', JSON.stringify(accountsList));
+                    localStorage.setItem('clientAccounts', JSON.stringify(clientAccounts));
+
+                    const accountParam = getAccountFromURL();
+
+                    let selectedToken = activeSessions.tokens[0];
+                    if (accountParam) {
+                        const matchingToken = activeSessions.tokens.find(
+                            (token: TokenItem) => token.cur === accountParam
+                        );
+                        if (matchingToken) {
+                            selectedToken = matchingToken;
+                        }
+                    }
+
+                    if (selectedToken.loginid && selectedToken.token) {
+                        localStorage.setItem('authToken', selectedToken.token);
+                        localStorage.setItem('active_loginid', selectedToken.loginid);
+
+                        authTokenRef.current = selectedToken.token;
+
+                        if (api_base) {
+                            api_base.init(true).then(() => {
+                                if (selectedToken.loginid) {
+                                    setAuthData({
+                                        loginid: selectedToken.loginid,
+                                        currency: selectedToken.cur || '',
+                                        token: selectedToken.token, // Keeping token property, despite TypeScript error Need to check this later
+                                    });
+                                }
+                            });
+                        }
+                    }
+                }
+
+                if (domains.includes(currentDomain)) {
+                    Cookies.set('logged_state', 'true', {
+                        domain: currentDomain,
+                        expires: 30,
+                        path: '/',
+                        secure: true,
+                    });
+                }
+            }
+        } finally {
+            TMBState.checkInProgress = false;
+        }
+    }, [isCallbackPage, getActiveSessions, isEndpointPage, handleLogout, processTokens, domains, currentDomain]);
+
+    return useMemo(
+        () => ({
+            handleLogout,
+            isOAuth2Enabled,
+            is_tmb_enabled,
+            onRenderTMBCheck,
+        }),
+        [handleLogout, isOAuth2Enabled, is_tmb_enabled, onRenderTMBCheck]
+    );
+};
+
+export default useTMB;
diff --git a/src/pages/callback/callback-page.tsx b/src/pages/callback/callback-page.tsx
index 0dc6d3b68..99e6fd275 100644
--- a/src/pages/callback/callback-page.tsx
+++ b/src/pages/callback/callback-page.tsx
@@ -2,6 +2,7 @@ import Cookies from 'js-cookie';
 import { crypto_currencies_display_order, fiat_currencies_display_order } from '@/components/shared';
 import { generateDerivApiInstance } from '@/external/bot-skeleton/services/api/appId';
 import { observer as globalObserver } from '@/external/bot-skeleton/utils/observer';
+import useTMB from '@/hooks/useTMB';
 import { clearAuthData } from '@/utils/auth-utils';
 import { Callback } from '@deriv-com/auth-client';
 import { Button } from '@deriv-com/ui';
@@ -71,7 +72,8 @@ const CallbackPage = () => {
                             is_token_set = true;
 
                             // Only emit the InvalidToken event if logged_state is true
-                            if (Cookies.get('logged_state') === 'true') {
+                            const { is_tmb_enabled = false } = useTMB();
+                            if (Cookies.get('logged_state') === 'true' && !is_tmb_enabled) {
                                 // Emit an event that can be caught by the application to retrigger OIDC authentication
                                 globalObserver.emit('InvalidToken', { error });
                             }
diff --git a/src/pages/main/main.tsx b/src/pages/main/main.tsx
index cb5096310..c1ca26ed3 100644
--- a/src/pages/main/main.tsx
+++ b/src/pages/main/main.tsx
@@ -16,6 +16,7 @@ import { isDbotRTL } from '@/external/bot-skeleton/utils/workspace';
 import { useOauth2 } from '@/hooks/auth/useOauth2';
 import { useApiBase } from '@/hooks/useApiBase';
 import { useStore } from '@/hooks/useStore';
+import useTMB from '@/hooks/useTMB';
 import {
     LabelPairedChartLineCaptionRegularIcon,
     LabelPairedObjectsColumnCaptionRegularIcon,
@@ -79,6 +80,8 @@ const AppWrapper = observer(() => {
     };
     const active_hash_tab = GetHashedValue(active_tab);
 
+    const { is_tmb_enabled, onRenderTMBCheck } = useTMB();
+
     React.useEffect(() => {
         const el_dashboard = document.getElementById('id-dbot-dashboard');
         const el_tutorial = document.getElementById('id-tutorials');
@@ -232,19 +235,23 @@ const AppWrapper = observer(() => {
             const currency = getQueryParams.get('account') ?? '';
             const query_param_currency = sessionStorage.getItem('query_param_currency') || currency || 'USD';
             try {
-                await requestOidcAuthentication({
-                    redirectCallbackUri: `${window.location.origin}/callback`,
-                    ...(query_param_currency
-                        ? {
-                              state: {
-                                  account: query_param_currency,
-                              },
-                          }
-                        : {}),
-                }).catch(err => {
-                    // eslint-disable-next-line no-console
-                    console.error(err);
-                });
+                if (is_tmb_enabled) {
+                    onRenderTMBCheck();
+                } else {
+                    await requestOidcAuthentication({
+                        redirectCallbackUri: `${window.location.origin}/callback`,
+                        ...(query_param_currency
+                            ? {
+                                  state: {
+                                      account: query_param_currency,
+                                  },
+                              }
+                            : {}),
+                    }).catch(err => {
+                        // eslint-disable-next-line no-console
+                        console.error(err);
+                    });
+                }
             } catch (error) {
                 // eslint-disable-next-line no-console
                 console.error(error);
diff --git a/src/stores/app-store.ts b/src/stores/app-store.ts
index d13e8fc27..6a83a53a5 100644
--- a/src/stores/app-store.ts
+++ b/src/stores/app-store.ts
@@ -87,9 +87,11 @@ export default class AppStore {
 
         // Check if we're in the process of logging in
         // When isSingleLoggingIn is true, we don't want to show the EU error message
+        const is_tmb_enabled = JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false');
         const isSingleLoggingIn =
             window.location.pathname === '/callback' ||
             (Cookies.get('logged_state') === 'true' &&
+                !is_tmb_enabled &&
                 Object.keys(JSON.parse(localStorage.getItem('accountsList') || '{}')).length === 0);
 
         if (isSingleLoggingIn) {


Reviews:
user: login: amam-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T09:57:33Z
user: login: amam-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T09:59:43Z
user: login: amam-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T10:00:59Z
user: login: rupato-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T10:13:44Z
user: login: rupato-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T10:15:05Z
user: login: rupato-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T10:15:17Z
user: login: amam-deriv
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T10:34:54Z
user: login: mayuran-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T15:55:50Z
user: login: mayuran-deriv
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-27T15:57:28Z
user: login: akmal-deriv
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-28T02:53:10Z
user: login: rupato-deriv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T02:58:59Z

Review comments:
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
path: src/hooks/useTMB.ts
user: login: amam-deriv
body: let's standardise to remove caching on our side to lessen complication
created_at: 2025-05-26T09:57:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
+            return TMBState.sessionsCache.data;
+        }
+
+        try {
+            const data = await requestSessionActive();
+            // Cache the result
+            TMBState.sessionsCache = {
+                data,
+                timestamp: now,
+            };
+            return data;
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        const now = Date.now();
+        if (now - TMBState.lastCheckTimestamp < 2000) return;
path: src/hooks/useTMB.ts
user: login: amam-deriv
body: let's find the underlying cause why it's rendering unexpectedly and please remove this timestamp/caching logic
created_at: 2025-05-26T09:59:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
+            return TMBState.sessionsCache.data;
+        }
+
+        try {
+            const data = await requestSessionActive();
+            // Cache the result
+            TMBState.sessionsCache = {
+                data,
+                timestamp: now,
+            };
+            return data;
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        const now = Date.now();
+        if (now - TMBState.lastCheckTimestamp < 2000) return;
+
+        TMBState.checkInProgress = true;
+        TMBState.lastCheckTimestamp = now;
+
+        try {
+            const activeSessions = await getActiveSessions();
+
+            if (!activeSessions?.active && !isEndpointPage) {
+                TMBState.checkInProgress = false;
+                return handleLogout();
+            }
+
+            if (activeSessions?.active) {
+                if (Array.isArray(activeSessions.tokens) && activeSessions.tokens.length > 0) {
path: src/hooks/useTMB.ts
user: login: amam-deriv
body: console error on the else function
created_at: 2025-05-26T10:00:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
path: src/hooks/useTMB.ts
user: login: rupato-deriv
body: done ✅ 
created_at: 2025-05-26T10:13:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
+            return TMBState.sessionsCache.data;
+        }
+
+        try {
+            const data = await requestSessionActive();
+            // Cache the result
+            TMBState.sessionsCache = {
+                data,
+                timestamp: now,
+            };
+            return data;
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        const now = Date.now();
+        if (now - TMBState.lastCheckTimestamp < 2000) return;
+
+        TMBState.checkInProgress = true;
+        TMBState.lastCheckTimestamp = now;
+
+        try {
+            const activeSessions = await getActiveSessions();
+
+            if (!activeSessions?.active && !isEndpointPage) {
+                TMBState.checkInProgress = false;
+                return handleLogout();
+            }
+
+            if (activeSessions?.active) {
+                if (Array.isArray(activeSessions.tokens) && activeSessions.tokens.length > 0) {
path: src/hooks/useTMB.ts
user: login: rupato-deriv
body: done ✅ 
created_at: 2025-05-26T10:15:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,224 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+interface SessionCache {
+    data: any;
+    timestamp: number;
+}
+
+const TMBState = {
+    isInitialized: false,
+    sessionsCache: null as SessionCache | null,
+    lastCheckTimestamp: 0,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        // Check if we have cached data and it's less than 30 seconds old
+        const now = Date.now();
+        if (TMBState.sessionsCache && now - TMBState.sessionsCache.timestamp < 30000) {
+            return TMBState.sessionsCache.data;
+        }
+
+        try {
+            const data = await requestSessionActive();
+            // Cache the result
+            TMBState.sessionsCache = {
+                data,
+                timestamp: now,
+            };
+            return data;
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        const now = Date.now();
+        if (now - TMBState.lastCheckTimestamp < 2000) return;
path: src/hooks/useTMB.ts
user: login: rupato-deriv
body: ✅ done removed this
created_at: 2025-05-26T10:15:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,200 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+const TMBState = {
+    isInitialized: false,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        try {
+            return await requestSessionActive();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        TMBState.checkInProgress = true;
+
+        try {
+            const activeSessions = await getActiveSessions();
+
+            if (!activeSessions?.active && !isEndpointPage) {
+                console.error('Failed to get active sessions: No data returned');
+                TMBState.checkInProgress = false;
+                return handleLogout();
path: src/hooks/useTMB.ts
user: login: mayuran-deriv
body: @rupato-deriv We're checking for no active session here, and if it's not the endpoint page, we're logging out and redirecting to OAuth.
Since we also have a logged-out version available in dbots, the user should be able to see it, right?
But instead, they're being redirected to the OAuth page.
Just wanted to clarify — please correct me if I’m wrong.

line number 87 **redirectToAuth();**
created_at: 2025-05-27T15:55:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,200 @@
+import { useCallback, useEffect, useMemo, useRef } from 'react';
+import Cookies from 'js-cookie';
+import { generateOAuthURL } from '@/components/shared/utils/config/config';
+import { removeCookies } from '@/components/shared/utils/storage/storage';
+import { api_base } from '@/external/bot-skeleton';
+import { setAuthData } from '@/external/bot-skeleton/services/api/observables/connection-status-stream';
+import { requestSessionActive } from '@deriv-com/auth-client';
+
+type UseTMBReturn = {
+    handleLogout: () => void;
+    isOAuth2Enabled: boolean;
+    is_tmb_enabled: boolean;
+    onRenderTMBCheck: () => Promise<void>;
+};
+
+interface TokenItem {
+    loginid?: string;
+    token?: string;
+    cur?: string;
+}
+
+const TMBState = {
+    isInitialized: false,
+    checkInProgress: false,
+};
+
+const useTMB = (): UseTMBReturn => {
+    const hasLoggedRef = useRef(false);
+
+    if (!hasLoggedRef.current) {
+        hasLoggedRef.current = true;
+    }
+
+    const isEndpointPage = useMemo(() => window.location.pathname.includes('endpoint'), []);
+    const isCallbackPage = useMemo(() => window.location.pathname === '/callback', []);
+    const oauthUrl = useMemo(() => generateOAuthURL(), []);
+    const domains = useMemo(
+        () => ['deriv.com', 'deriv.dev', 'binary.sx', 'pages.dev', 'localhost', 'deriv.be', 'deriv.me'],
+        []
+    );
+    const currentDomain = useMemo(() => window.location.hostname.split('.').slice(-2).join('.'), []);
+
+    const isProduction = useMemo(() => process.env.APP_ENV === 'production', []);
+    const isStaging = useMemo(() => process.env.APP_ENV === 'staging', []);
+    const isOAuth2Enabled = useMemo(() => isProduction || isStaging, [isProduction, isStaging]);
+    const is_tmb_enabled = useMemo(() => JSON.parse(localStorage.getItem('is_tmb_enabled') || 'false'), []);
+    const authTokenRef = useRef(localStorage.getItem('authToken'));
+
+    useEffect(() => {
+        if (!TMBState.isInitialized) {
+            TMBState.isInitialized = true;
+        }
+    }, []);
+
+    const logout = useCallback(async () => {
+        try {
+            localStorage.removeItem('authToken');
+            localStorage.removeItem('active_loginid');
+            localStorage.removeItem('clientAccounts');
+            localStorage.removeItem('accountsList');
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+    }, []);
+
+    const redirectToAuth = useCallback(async () => {
+        window.open(oauthUrl, '_self');
+    }, [oauthUrl]);
+
+    const handleLogout = useCallback(async () => {
+        try {
+            if (authTokenRef.current) await logout();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to logout', error);
+        }
+        removeCookies('affiliate_token', 'affiliate_tracking', 'utm_data', 'onfido_token', 'gclid');
+        if (domains.includes(currentDomain)) {
+            Cookies.set('logged_state', 'false', {
+                domain: currentDomain,
+                expires: 30,
+                path: '/',
+                secure: true,
+            });
+        }
+        redirectToAuth();
+    }, [logout, domains, currentDomain, redirectToAuth]);
+
+    const getActiveSessions = useCallback(async () => {
+        try {
+            return await requestSessionActive();
+        } catch (error) {
+            // eslint-disable-next-line no-console
+            console.error('Failed to get active sessions', error);
+        }
+    }, []);
+
+    const processTokens = useCallback((tokens: TokenItem[]) => {
+        const accountsList: Record<string, string> = {};
+        const clientAccounts: Record<string, { loginid: string; token: string; currency: string }> = {};
+
+        tokens.forEach((token: TokenItem) => {
+            if (token.loginid && token.token) {
+                accountsList[token.loginid] = token.token;
+                clientAccounts[token.loginid] = {
+                    loginid: token.loginid,
+                    token: token.token,
+                    currency: token.cur || '',
+                };
+            }
+        });
+
+        return { accountsList, clientAccounts };
+    }, []);
+
+    // Get account from URL query parameter
+    const getAccountFromURL = useCallback(() => {
+        const urlParams = new URLSearchParams(window.location.search);
+        return urlParams.get('account');
+    }, []);
+
+    const onRenderTMBCheck = useCallback(async () => {
+        if (isCallbackPage) return;
+        if (TMBState.checkInProgress) return;
+
+        TMBState.checkInProgress = true;
+
+        try {
+            const activeSessions = await getActiveSessions();
+
+            if (!activeSessions?.active && !isEndpointPage) {
+                console.error('Failed to get active sessions: No data returned');
+                TMBState.checkInProgress = false;
+                return handleLogout();
path: src/hooks/useTMB.ts
user: login: rupato-deriv
body: yes you are right we dont require to redirect them to the oauth page instead show them a logged out state of the app. nice catch 🙏 @mayuran-deriv 
created_at: 2025-05-28T02:58:59Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-28T03:07:46Z
body: **Preview Link**: https://Rupato-Implementatio.bot-65f.pages.dev
| Name | Result |
| :--- | :------ |
| **Build status**  | Completed ✅ |
| **Preview URL**  | [Visit Preview](https://Rupato-Implementatio.bot-65f.pages.dev) |
| **Action URL**  | [Visit Action](https://github.com/deriv-com/bot/actions/runs/15290471147) |
<!-- Sticky Pull Request CommentCloudflare Pages Preview Comment -->
author_association: CONTRIBUTOR