App Inactive: Support disconnecting when opening in another tab

This commit is contained in:
Alexander Zinchuk 2021-05-06 01:47:36 +03:00
parent baffa925d8
commit 987dfacaf5
11 changed files with 135 additions and 9 deletions

View File

@ -1,25 +1,42 @@
import { FC, useEffect } from './lib/teact/teact';
import React, { withGlobal } from './lib/teact/teactn';
import { GlobalState } from './global/types';
import { GlobalActions, GlobalState } from './global/types';
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
import { pick } from './util/iteratees';
import { updateSizes } from './util/windowSize';
import { addActiveTabChangeListener } from './util/activeTabMonitor';
import useFlag from './hooks/useFlag';
import Auth from './components/auth/Auth';
import UiLoader from './components/common/UiLoader';
import Main from './components/main/Main.async';
import AppInactive from './components/main/AppInactive';
// import Test from './components/test/TestNoRedundancy';
type StateProps = Pick<GlobalState, 'authState' | 'authIsSessionRemembered'>;
type DispatchProps = Pick<GlobalActions, 'disconnect'>;
const App: FC<StateProps & DispatchProps> = ({ authState, authIsSessionRemembered, disconnect }) => {
const [isInactive, markInactive] = useFlag(false);
const App: FC<StateProps> = ({ authState, authIsSessionRemembered }) => {
useEffect(() => {
updateSizes();
}, []);
addActiveTabChangeListener(() => {
disconnect();
document.title = `${PAGE_TITLE}${INACTIVE_MARKER}`;
markInactive();
});
}, [disconnect, markInactive]);
// return <Test />;
if (isInactive) {
return <AppInactive />;
}
if (authState) {
switch (authState) {
case 'authorizationStateWaitPhoneNumber':
@ -49,4 +66,5 @@ function renderMain() {
export default withGlobal(
(global): StateProps => pick(global, ['authState', 'authIsSessionRemembered']),
(setGlobal, actions): DispatchProps => pick(actions, ['disconnect']),
)(App);

View File

@ -95,6 +95,10 @@ export async function destroy() {
await client.destroy();
}
export async function disconnect() {
await client.disconnect();
}
export function getClient() {
return client;
}

View File

@ -1,4 +1,6 @@
export { destroy, downloadMedia, fetchCurrentUser } from './client';
export {
destroy, disconnect, downloadMedia, fetchCurrentUser,
} from './client';
export {
provideAuthPhoneNumber, provideAuthCode, provideAuthPassword, provideAuthRegistration, restartAuth, restartAuthWithQr,

BIN
src/assets/app-inactive.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -0,0 +1,32 @@
#AppInactive {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.content {
max-width: 28rem;
margin: auto;
padding: 1.5rem;
text-align: center;
}
.title {
margin-top: 1rem;
}
.description {
color: var(--color-text-secondary);
font-size: 0.875rem;
}
img {
width: 100%;
max-width: 20rem;
}
.Button {
margin-top: 1rem;
}
}

View File

@ -0,0 +1,32 @@
import React, { FC } from '../../lib/teact/teact';
import Button from '../ui/Button';
import appInactivePath from '../../assets/app-inactive.png';
import './AppInactive.scss';
const AppInactive: FC = () => {
const handleReload = () => {
window.location.reload();
};
return (
<div id="AppInactive">
<div className="content">
<img src={appInactivePath} alt="" />
<h3 className="title">Such error, many tabs</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.
</div>
<div className="actions">
<Button isText ripple onClick={handleReload}>Reload app</Button>
</div>
</div>
</div>
);
};
export default AppInactive;

View File

@ -5,7 +5,9 @@ import { GlobalActions } from '../../global/types';
import { ApiMessage } from '../../api/types';
import '../../modules/actions/all';
import { ANIMATION_END_DELAY, DEBUG } from '../../config';
import {
ANIMATION_END_DELAY, DEBUG, INACTIVE_MARKER, PAGE_TITLE,
} from '../../config';
import { pick } from '../../util/iteratees';
import {
selectChatMessage,
@ -44,7 +46,6 @@ type StateProps = {
type DispatchProps = Pick<GlobalActions, 'loadAnimatedEmojis'>;
const APP_NAME = 'Telegram';
const ANIMATION_DURATION = 350;
const NOTIFICATION_INTERVAL = 1000;
@ -122,6 +123,11 @@ const Main: FC<StateProps & DispatchProps> = ({
clearInterval(notificationInterval);
notificationInterval = window.setInterval(() => {
if (document.title.includes(INACTIVE_MARKER)) {
updateIcon(false);
return;
}
if (index % 2 === 0) {
const newUnread = selectCountNotMutedUnread(getGlobal()) - initialUnread;
if (newUnread > 0) {
@ -129,7 +135,7 @@ const Main: FC<StateProps & DispatchProps> = ({
updateIcon(true);
}
} else {
document.title = APP_NAME;
document.title = PAGE_TITLE;
updateIcon(false);
}
@ -138,7 +144,11 @@ const Main: FC<StateProps & DispatchProps> = ({
}, () => {
clearInterval(notificationInterval);
notificationInterval = undefined;
document.title = APP_NAME;
if (!document.title.includes(INACTIVE_MARKER)) {
document.title = PAGE_TITLE;
}
updateIcon(false);
});

View File

@ -1,3 +1,6 @@
export const PAGE_TITLE = 'Telegram';
export const INACTIVE_MARKER = ' [Inactive]';
export const APP_INFO = process.env.APP_INFO || 'Telegram T';
export const DEBUG = (

View File

@ -383,7 +383,7 @@ export type GlobalState = {
export type ActionTypes = (
// system
'init' | 'reset' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
'init' | 'reset' | 'disconnect' | 'initApi' | 'apiUpdate' | 'sync' | 'saveSession' | 'afterSync' |
'showNotification' | 'dismissNotification' | 'showError' | 'dismissError' |
// ui
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |

View File

@ -126,6 +126,12 @@ addReducer('reset', () => {
getDispatch().init();
});
addReducer('disconnect', () => {
(async () => {
await callApi('disconnect');
})();
});
addReducer('loadNearestCountry', (global) => {
if (global.connectionState !== 'connectionStateReady') {
return;

View File

@ -0,0 +1,19 @@
const STORAGE_KEY = 'tt-active-tab';
const INTERVAL = 2000;
const tabKey = String(Date.now() + Math.random());
localStorage.setItem(STORAGE_KEY, tabKey);
let callback: NoneToVoidFunction;
const interval = window.setInterval(() => {
if (callback && localStorage.getItem(STORAGE_KEY) !== tabKey) {
callback();
clearInterval(interval);
}
}, INTERVAL);
export function addActiveTabChangeListener(_callback: NoneToVoidFunction) {
callback = _callback;
}