diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 181c4ed39..642b654e1 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -1296,9 +1296,11 @@ "SettingsPasscodeStart2" = "Note: if you forget your local passcode, you'll need to log out of Telegram Web A and log in again."; "CurrentPasswordPlaceholder" = "Current password"; "ChangeYourProfilePicture" = "Change your profile picture"; -"TooManyTabsTitle" = "Such error, many tabs"; -"TooManyTabsDescription" = "Telegram supports only one active tab with the app.\nPlease reload this page to continue using this tab or close it."; -"TooManyTabsReload" = "Reload app"; +"AppInactiveOtherClientTitle" = "Many sessions. So conflict."; +"AppInactiveOtherClientDescription" = "Please, keep only one version of the app open.\nReload this page to continue using this tab or close it."; +"AppInactiveAuthTitle" = "Many logins. So conflict."; +"AppInactiveAuthDescription" = "Please, keep only one tab open during login.\nReload this page to continue using this tab or close it."; +"AppInactiveReload" = "Reload App"; "SlowmodeEnabled" = "Slowmode enabled"; "SomethingWentWrong" = "Something went wrong"; "MediaViewDownloading" = "{count}% downloading..."; diff --git a/src/components/App.tsx b/src/components/App.tsx index 3845ffce2..fdde35eca 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -23,8 +23,7 @@ import { updateSizes } from '../util/windowSize'; import useTauriDrag from '../hooks/tauri/useTauriDrag'; import useAppLayout from '../hooks/useAppLayout'; -import useFlag from '../hooks/useFlag'; -import usePreviousDeprecated from '../hooks/usePreviousDeprecated'; +import usePrevious from '../hooks/usePrevious'; // import Test from './test/TestLocale'; import Auth from './auth/Auth'; @@ -40,7 +39,7 @@ type StateProps = { authState: GlobalState['authState']; isScreenLocked?: boolean; hasPasscode?: boolean; - isInactiveAuth?: boolean; + inactiveReason?: 'auth' | 'otherClient'; hasWebAuthTokenFailed?: boolean; isTestServer?: boolean; theme: ThemeKey; @@ -61,12 +60,11 @@ const App: FC = ({ authState, isScreenLocked, hasPasscode, - isInactiveAuth, + inactiveReason, hasWebAuthTokenFailed, isTestServer, theme, }) => { - const [isInactive, markInactive, unmarkInactive] = useFlag(false); const { isMobile } = useAppLayout(); const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'; @@ -135,7 +133,7 @@ const App: FC = ({ let activeKey: AppScreens; let page: UiLoaderPage | undefined; - if (isInactive) { + if (inactiveReason) { activeKey = AppScreens.inactive; } else if (isScreenLocked) { page = 'lock'; @@ -193,16 +191,14 @@ const App: FC = ({ }, []); useEffect(() => { - if (isInactiveAuth) { + if (inactiveReason) { document.title = INACTIVE_PAGE_TITLE; - markInactive(); } else { document.title = ACTIVE_PAGE_TITLE; - unmarkInactive(); } - }, [isInactiveAuth, markInactive, unmarkInactive]); + }, [inactiveReason]); - const prevActiveKey = usePreviousDeprecated(activeKey); + const prevActiveKey = usePrevious(activeKey); function renderContent() { switch (activeKey) { @@ -213,7 +209,7 @@ const App: FC = ({ case AppScreens.lock: return ; case AppScreens.inactive: - return ; + return ; } } @@ -255,7 +251,7 @@ export default withGlobal( authState: global.authState, isScreenLocked: global.passcode?.isScreenLocked, hasPasscode: global.passcode?.hasPasscode, - isInactiveAuth: selectTabState(global).isInactive, + inactiveReason: selectTabState(global).inactiveReason, hasWebAuthTokenFailed: global.hasWebAuthTokenFailed || global.hasWebAuthTokenPasswordRequired, theme: selectTheme(global), isTestServer: global.config?.isTestServer, diff --git a/src/components/main/AppInactive.tsx b/src/components/main/AppInactive.tsx index e15cdda41..50bb66ec5 100644 --- a/src/components/main/AppInactive.tsx +++ b/src/components/main/AppInactive.tsx @@ -1,7 +1,8 @@ -import type { FC } from '../../lib/teact/teact'; -import { useCallback } from '../../lib/teact/teact'; +import renderText from '../common/helpers/renderText'; import useHistoryBack from '../../hooks/useHistoryBack'; +import useLang from '../../hooks/useLang'; +import useLastCallback from '../../hooks/useLastCallback'; import Button from '../ui/Button'; @@ -9,10 +10,16 @@ import './AppInactive.scss'; import appInactivePath from '../../assets/app-inactive.png'; -const AppInactive: FC = () => { - const handleReload = useCallback(() => { +type OwnProps = { + inactiveReason: 'auth' | 'otherClient'; +}; + +const AppInactive = ({ inactiveReason }: OwnProps) => { + const lang = useLang(); + + const handleReload = useLastCallback(() => { window.location.reload(); - }, []); + }); useHistoryBack({ isActive: true, @@ -24,15 +31,18 @@ const AppInactive: FC = () => {
-

Such error, many tabs

+

+ {inactiveReason === 'auth' ? lang('AppInactiveAuthTitle') : lang('AppInactiveOtherClientTitle')} +

- Telegram supports only one active tab with the app. -
- Please reload this page to continue using this tab or close it. + {renderText( + lang(inactiveReason === 'auth' ? 'AppInactiveAuthDescription' : 'AppInactiveOtherClientDescription'), + ['br'], + )}
diff --git a/src/config.ts b/src/config.ts index 8b38d6f46..f43a7522f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -67,6 +67,7 @@ export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500]; export const DATA_BROADCAST_CHANNEL_PREFIX = 'tt-global'; export const ESTABLISH_BROADCAST_CHANNEL_PREFIX = 'tt-establish'; export const MULTITAB_LOCALSTORAGE_KEY_PREFIX = 'tt-multitab'; +export const INTERCLIENT_BROADCAST_CHANNEL = 'tgweb'; export const DC_IDS = [1, 2, 3, 4, 5] as const; export const DOWNLOAD_WORKERS = 16; diff --git a/src/global/actions/apiUpdaters/initial.ts b/src/global/actions/apiUpdaters/initial.ts index ab38ad22d..440af3ac3 100644 --- a/src/global/actions/apiUpdaters/initial.ts +++ b/src/global/actions/apiUpdaters/initial.ts @@ -183,7 +183,7 @@ function onUpdateAuthorizationState(global: T, update: Ap }; Object.values(global.byTabId).forEach(({ id: tabId }) => { global = updateTabState(global, { - isInactive: false, + inactiveReason: undefined, }, tabId); }); setGlobal(global); diff --git a/src/global/init.ts b/src/global/init.ts index 57d17addb..9fcfeb899 100644 --- a/src/global/init.ts +++ b/src/global/init.ts @@ -115,7 +115,7 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => { Object.values(global.byTabId).forEach(({ id: otherTabId }) => { if (otherTabId === tabId) return; global = updateTabState(global, { - isInactive: true, + inactiveReason: 'auth', }, otherTabId); }); } diff --git a/src/global/types/tabState.ts b/src/global/types/tabState.ts index b6cd7d5a8..a89b0b912 100644 --- a/src/global/types/tabState.ts +++ b/src/global/types/tabState.ts @@ -101,7 +101,7 @@ export type TabState = { id: number; isBlurred?: boolean; isMasterTab: boolean; - isInactive?: boolean; + inactiveReason?: 'auth' | 'otherClient'; shouldPreventComposerAnimation?: boolean; inviteHash?: string; canInstall?: boolean; diff --git a/src/index.tsx b/src/index.tsx index dace3136e..72efb8400 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,6 +15,7 @@ import { selectTabState } from './global/selectors'; import { selectSharedSettings } from './global/selectors/sharedState'; import { betterView } from './util/betterView'; import { IS_TAURI } from './util/browser/globalEnvironment'; +import listenOtherClients from './util/browser/listenOtherClients'; import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/browser/multitab'; import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole'; import { initGlobal } from './util/init'; @@ -51,6 +52,7 @@ async function init() { if (!(window as any).isCompatTestPassed) return; checkAndAssignPermanentWebVersion(); + listenOtherClients(); subscribeToMultitabBroadcastChannel(); await requestGlobal(APP_VERSION); diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 79cfe6fa7..9e2805f56 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -1102,9 +1102,11 @@ export interface LangPair { 'SettingsPasscodeStart2': undefined; 'CurrentPasswordPlaceholder': undefined; 'ChangeYourProfilePicture': undefined; - 'TooManyTabsTitle': undefined; - 'TooManyTabsDescription': undefined; - 'TooManyTabsReload': undefined; + 'AppInactiveOtherClientTitle': undefined; + 'AppInactiveOtherClientDescription': undefined; + 'AppInactiveAuthTitle': undefined; + 'AppInactiveAuthDescription': undefined; + 'AppInactiveReload': undefined; 'SlowmodeEnabled': undefined; 'SomethingWentWrong': undefined; 'VideoPlayerBuffering': undefined; diff --git a/src/util/browser/listenOtherClients.ts b/src/util/browser/listenOtherClients.ts new file mode 100644 index 000000000..935087c33 --- /dev/null +++ b/src/util/browser/listenOtherClients.ts @@ -0,0 +1,22 @@ +import { getGlobal, setGlobal } from '../../global'; + +import { APP_CODE_NAME, INTERCLIENT_BROADCAST_CHANNEL } from '../../config'; +import { updateTabState } from '../../global/reducers/tabs'; +import { selectTabState } from '../../global/selectors'; + +export default function listenOtherClients() { + const channel = new BroadcastChannel(INTERCLIENT_BROADCAST_CHANNEL); + + channel.addEventListener('message', (event) => { + if (event.data !== APP_CODE_NAME) { + let global = getGlobal(); + const tabState = selectTabState(global); + global = updateTabState(global, { + inactiveReason: 'otherClient', + }, tabState.id); + setGlobal(global); + } + }); + + channel.postMessage(APP_CODE_NAME); +}