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.";
"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...";

View File

@ -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<StateProps> = ({
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<StateProps> = ({
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<StateProps> = ({
}, []);
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<StateProps> = ({
case AppScreens.lock:
return <LockScreen isLocked={isScreenLocked} />;
case AppScreens.inactive:
return <AppInactive />;
return <AppInactive inactiveReason={inactiveReason!} />;
}
}
@ -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,

View File

@ -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 = () => {
<div id="AppInactive">
<div className="content">
<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">
Telegram supports only one active tab with the app.
<br />
Please reload this page to continue using this tab or close it.
{renderText(
lang(inactiveReason === 'auth' ? 'AppInactiveAuthDescription' : 'AppInactiveOtherClientDescription'),
['br'],
)}
</div>
<div className="actions">
<Button isText ripple onClick={handleReload}>
Reload app
{lang('AppInactiveReload')}
</Button>
</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 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;

View File

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

View File

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

View File

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

View File

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

View File

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

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