Check active Web K tabs (#6319)

This commit is contained in:
zubiden 2025-10-08 12:33:37 +02:00 committed by Alexander Zinchuk
parent c011a436f9
commit 750b45ed1c
10 changed files with 67 additions and 32 deletions

View File

@ -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."; "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"; "CurrentPasswordPlaceholder" = "Current password";
"ChangeYourProfilePicture" = "Change your profile picture"; "ChangeYourProfilePicture" = "Change your profile picture";
"TooManyTabsTitle" = "Such error, many tabs"; "AppInactiveOtherClientTitle" = "Many sessions. So conflict.";
"TooManyTabsDescription" = "Telegram supports only one active tab with the app.\nPlease reload this page to continue using this tab or close it."; "AppInactiveOtherClientDescription" = "Please, keep only one version of the app open.\nReload this page to continue using this tab or close it.";
"TooManyTabsReload" = "Reload app"; "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"; "SlowmodeEnabled" = "Slowmode enabled";
"SomethingWentWrong" = "Something went wrong"; "SomethingWentWrong" = "Something went wrong";
"MediaViewDownloading" = "{count}% downloading..."; "MediaViewDownloading" = "{count}% downloading...";

View File

@ -23,8 +23,7 @@ import { updateSizes } from '../util/windowSize';
import useTauriDrag from '../hooks/tauri/useTauriDrag'; import useTauriDrag from '../hooks/tauri/useTauriDrag';
import useAppLayout from '../hooks/useAppLayout'; import useAppLayout from '../hooks/useAppLayout';
import useFlag from '../hooks/useFlag'; import usePrevious from '../hooks/usePrevious';
import usePreviousDeprecated from '../hooks/usePreviousDeprecated';
// import Test from './test/TestLocale'; // import Test from './test/TestLocale';
import Auth from './auth/Auth'; import Auth from './auth/Auth';
@ -40,7 +39,7 @@ type StateProps = {
authState: GlobalState['authState']; authState: GlobalState['authState'];
isScreenLocked?: boolean; isScreenLocked?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
isInactiveAuth?: boolean; inactiveReason?: 'auth' | 'otherClient';
hasWebAuthTokenFailed?: boolean; hasWebAuthTokenFailed?: boolean;
isTestServer?: boolean; isTestServer?: boolean;
theme: ThemeKey; theme: ThemeKey;
@ -61,12 +60,11 @@ const App: FC<StateProps> = ({
authState, authState,
isScreenLocked, isScreenLocked,
hasPasscode, hasPasscode,
isInactiveAuth, inactiveReason,
hasWebAuthTokenFailed, hasWebAuthTokenFailed,
isTestServer, isTestServer,
theme, theme,
}) => { }) => {
const [isInactive, markInactive, unmarkInactive] = useFlag(false);
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android'; const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
@ -135,7 +133,7 @@ const App: FC<StateProps> = ({
let activeKey: AppScreens; let activeKey: AppScreens;
let page: UiLoaderPage | undefined; let page: UiLoaderPage | undefined;
if (isInactive) { if (inactiveReason) {
activeKey = AppScreens.inactive; activeKey = AppScreens.inactive;
} else if (isScreenLocked) { } else if (isScreenLocked) {
page = 'lock'; page = 'lock';
@ -193,16 +191,14 @@ const App: FC<StateProps> = ({
}, []); }, []);
useEffect(() => { useEffect(() => {
if (isInactiveAuth) { if (inactiveReason) {
document.title = INACTIVE_PAGE_TITLE; document.title = INACTIVE_PAGE_TITLE;
markInactive();
} else { } else {
document.title = ACTIVE_PAGE_TITLE; document.title = ACTIVE_PAGE_TITLE;
unmarkInactive();
} }
}, [isInactiveAuth, markInactive, unmarkInactive]); }, [inactiveReason]);
const prevActiveKey = usePreviousDeprecated(activeKey); const prevActiveKey = usePrevious(activeKey);
function renderContent() { function renderContent() {
switch (activeKey) { switch (activeKey) {
@ -213,7 +209,7 @@ const App: FC<StateProps> = ({
case AppScreens.lock: case AppScreens.lock:
return <LockScreen isLocked={isScreenLocked} />; return <LockScreen isLocked={isScreenLocked} />;
case AppScreens.inactive: case AppScreens.inactive:
return <AppInactive />; return <AppInactive inactiveReason={inactiveReason!} />;
} }
} }
@ -255,7 +251,7 @@ export default withGlobal(
authState: global.authState, authState: global.authState,
isScreenLocked: global.passcode?.isScreenLocked, isScreenLocked: global.passcode?.isScreenLocked,
hasPasscode: global.passcode?.hasPasscode, hasPasscode: global.passcode?.hasPasscode,
isInactiveAuth: selectTabState(global).isInactive, inactiveReason: selectTabState(global).inactiveReason,
hasWebAuthTokenFailed: global.hasWebAuthTokenFailed || global.hasWebAuthTokenPasswordRequired, hasWebAuthTokenFailed: global.hasWebAuthTokenFailed || global.hasWebAuthTokenPasswordRequired,
theme: selectTheme(global), theme: selectTheme(global),
isTestServer: global.config?.isTestServer, isTestServer: global.config?.isTestServer,

View File

@ -1,7 +1,8 @@
import type { FC } from '../../lib/teact/teact'; import renderText from '../common/helpers/renderText';
import { useCallback } from '../../lib/teact/teact';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import Button from '../ui/Button'; import Button from '../ui/Button';
@ -9,10 +10,16 @@ import './AppInactive.scss';
import appInactivePath from '../../assets/app-inactive.png'; import appInactivePath from '../../assets/app-inactive.png';
const AppInactive: FC = () => { type OwnProps = {
const handleReload = useCallback(() => { inactiveReason: 'auth' | 'otherClient';
};
const AppInactive = ({ inactiveReason }: OwnProps) => {
const lang = useLang();
const handleReload = useLastCallback(() => {
window.location.reload(); window.location.reload();
}, []); });
useHistoryBack({ useHistoryBack({
isActive: true, isActive: true,
@ -24,15 +31,18 @@ const AppInactive: FC = () => {
<div id="AppInactive"> <div id="AppInactive">
<div className="content"> <div className="content">
<img src={appInactivePath} alt="" /> <img src={appInactivePath} alt="" />
<h3 className="title">Such error, many tabs</h3> <h3 className="title">
{inactiveReason === 'auth' ? lang('AppInactiveAuthTitle') : lang('AppInactiveOtherClientTitle')}
</h3>
<div className="description"> <div className="description">
Telegram supports only one active tab with the app. {renderText(
<br /> lang(inactiveReason === 'auth' ? 'AppInactiveAuthDescription' : 'AppInactiveOtherClientDescription'),
Please reload this page to continue using this tab or close it. ['br'],
)}
</div> </div>
<div className="actions"> <div className="actions">
<Button isText ripple onClick={handleReload}> <Button isText ripple onClick={handleReload}>
Reload app {lang('AppInactiveReload')}
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -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 DATA_BROADCAST_CHANNEL_PREFIX = 'tt-global';
export const ESTABLISH_BROADCAST_CHANNEL_PREFIX = 'tt-establish'; export const ESTABLISH_BROADCAST_CHANNEL_PREFIX = 'tt-establish';
export const MULTITAB_LOCALSTORAGE_KEY_PREFIX = 'tt-multitab'; 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 DC_IDS = [1, 2, 3, 4, 5] as const;
export const DOWNLOAD_WORKERS = 16; export const DOWNLOAD_WORKERS = 16;

View File

@ -183,7 +183,7 @@ function onUpdateAuthorizationState<T extends GlobalState>(global: T, update: Ap
}; };
Object.values(global.byTabId).forEach(({ id: tabId }) => { Object.values(global.byTabId).forEach(({ id: tabId }) => {
global = updateTabState(global, { global = updateTabState(global, {
isInactive: false, inactiveReason: undefined,
}, tabId); }, tabId);
}); });
setGlobal(global); setGlobal(global);

View File

@ -115,7 +115,7 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => {
Object.values(global.byTabId).forEach(({ id: otherTabId }) => { Object.values(global.byTabId).forEach(({ id: otherTabId }) => {
if (otherTabId === tabId) return; if (otherTabId === tabId) return;
global = updateTabState(global, { global = updateTabState(global, {
isInactive: true, inactiveReason: 'auth',
}, otherTabId); }, otherTabId);
}); });
} }

View File

@ -101,7 +101,7 @@ export type TabState = {
id: number; id: number;
isBlurred?: boolean; isBlurred?: boolean;
isMasterTab: boolean; isMasterTab: boolean;
isInactive?: boolean; inactiveReason?: 'auth' | 'otherClient';
shouldPreventComposerAnimation?: boolean; shouldPreventComposerAnimation?: boolean;
inviteHash?: string; inviteHash?: string;
canInstall?: boolean; canInstall?: boolean;

View File

@ -15,6 +15,7 @@ import { selectTabState } from './global/selectors';
import { selectSharedSettings } from './global/selectors/sharedState'; import { selectSharedSettings } from './global/selectors/sharedState';
import { betterView } from './util/betterView'; import { betterView } from './util/betterView';
import { IS_TAURI } from './util/browser/globalEnvironment'; import { IS_TAURI } from './util/browser/globalEnvironment';
import listenOtherClients from './util/browser/listenOtherClients';
import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/browser/multitab'; import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/browser/multitab';
import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole'; import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole';
import { initGlobal } from './util/init'; import { initGlobal } from './util/init';
@ -51,6 +52,7 @@ async function init() {
if (!(window as any).isCompatTestPassed) return; if (!(window as any).isCompatTestPassed) return;
checkAndAssignPermanentWebVersion(); checkAndAssignPermanentWebVersion();
listenOtherClients();
subscribeToMultitabBroadcastChannel(); subscribeToMultitabBroadcastChannel();
await requestGlobal(APP_VERSION); await requestGlobal(APP_VERSION);

View File

@ -1102,9 +1102,11 @@ export interface LangPair {
'SettingsPasscodeStart2': undefined; 'SettingsPasscodeStart2': undefined;
'CurrentPasswordPlaceholder': undefined; 'CurrentPasswordPlaceholder': undefined;
'ChangeYourProfilePicture': undefined; 'ChangeYourProfilePicture': undefined;
'TooManyTabsTitle': undefined; 'AppInactiveOtherClientTitle': undefined;
'TooManyTabsDescription': undefined; 'AppInactiveOtherClientDescription': undefined;
'TooManyTabsReload': undefined; 'AppInactiveAuthTitle': undefined;
'AppInactiveAuthDescription': undefined;
'AppInactiveReload': undefined;
'SlowmodeEnabled': undefined; 'SlowmodeEnabled': undefined;
'SomethingWentWrong': undefined; 'SomethingWentWrong': undefined;
'VideoPlayerBuffering': undefined; 'VideoPlayerBuffering': undefined;

View File

@ -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);
}