diff --git a/src/components/connectionStatus.ts b/src/components/connectionStatus.ts index f7fae52c..0f650a7f 100644 --- a/src/components/connectionStatus.ts +++ b/src/components/connectionStatus.ts @@ -15,6 +15,9 @@ import Button from "./button"; import ProgressivePreloader from "./preloader"; import SetTransition from "./singleTransition"; import sessionStorage from '../lib/sessionStorage'; +import { ConnectionStatus } from "../lib/mtproto/connectionStatus"; +import { cancelEvent } from "../helpers/dom/cancelEvent"; +import apiManager from "../lib/mtproto/mtprotoworker"; export default class ConnectionStatusComponent { public static CHANGE_STATE_DELAY = 1000; @@ -23,9 +26,10 @@ export default class ConnectionStatusComponent { private statusEl: HTMLElement; private statusPreloader: ProgressivePreloader; - private currentLangPackKey = ''; + private currentLangPackKey: LangPackKey; - private connectingTimeout: number; + private hadConnect = false; + private retryAt: number; private connecting = false; private updating = false; @@ -77,13 +81,13 @@ export default class ConnectionStatusComponent { /* let bool = true; document.addEventListener('dblclick', () => { - rootScope.broadcast('connection_status_change', { + rootScope.dispatchEvent('connection_status_change', { dcId: 2, isFileDownload: false, isFileNetworker: false, isFileUpload: false, name: "NET-2", - online: bool = !bool, + status: bool ? (bool = false, ConnectionStatus.Closed) : (bool = true, ConnectionStatus.Connected), _: "networkerStatus" }); }); */ @@ -101,34 +105,61 @@ export default class ConnectionStatusComponent { } const status = rootScope.connectionStatus['NET-' + baseDcId]; - const online = status && status.online; + const online = status && status.status === ConnectionStatus.Connected; if(this.connecting && online) { apiUpdatesManager.forceGetDifference(); } + if(online && !this.hadConnect) { + this.hadConnect = true; + } + this.connecting = !online; - this.connectingTimeout = status && status.timeout; + this.retryAt = status && status.retryAt; DEBUG && this.log('connecting', this.connecting); this.setState(); }); }; - private setStatusText = (langPackKey: LangPackKey) => { + private setStatusText = (langPackKey: LangPackKey, args?: any[]) => { if(this.currentLangPackKey === langPackKey) return; this.currentLangPackKey = langPackKey; - replaceContent(this.statusEl, i18n(langPackKey)); + replaceContent(this.statusEl, i18n(langPackKey, args)); this.statusPreloader.attach(this.statusEl); }; private setState = () => { const timeout = ConnectionStatusComponent.CHANGE_STATE_DELAY; if(this.connecting) { - // if(this.connectingTimeout) { - // this.setStatusText('ConnectionStatus.Reconnect'); - // } else { + if(this.hadConnect) { + if(this.retryAt !== undefined) { + const timerSpan = document.createElement('span'); + const retryAt = this.retryAt; + const setTime = () => { + const now = Date.now(); + timerSpan.innerText = '' + Math.round((retryAt - now) / 1000); + if(now > retryAt) { + clearInterval(interval); + } + }; + setTime(); + const interval = setInterval(setTime, 1e3); + + const a = document.createElement('a'); + a.classList.add('force-reconnect'); + a.append(i18n('ConnectionStatus.Reconnect')); + a.addEventListener('click', (e) => { + cancelEvent(e); + apiManager.forceReconnect(); + }); + this.setStatusText('ConnectionStatus.ReconnectIn', [timerSpan, a]); + } else { + this.setStatusText('ConnectionStatus.Reconnecting'); + } + } else { this.setStatusText('ConnectionStatus.Waiting'); - // } + } } else if(this.updating) { this.setStatusText('Updating'); } diff --git a/src/config/app.ts b/src/config/app.ts index 6fef9fe3..f04c962d 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -15,7 +15,7 @@ const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', version: '0.5.7', - langPackVersion: '0.2.3', + langPackVersion: '0.2.4', langPack: 'macos', langPackCode: 'en', domains: [] as string[], diff --git a/src/lang.ts b/src/lang.ts index 2780bc7a..96af6d5c 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -47,6 +47,7 @@ const lang = { "ChatList.Menu.SwitchTo.Z": "Switch to Z version", "ConnectionStatus.ReconnectIn": "Reconnect in %ds, %s", "ConnectionStatus.Reconnect": "reconnect", + "ConnectionStatus.Reconnecting": "Reconnecting...", "ConnectionStatus.Waiting": "Waiting for network...", "Deactivated.Title": "Too many tabs...", "Deactivated.Subtitle": "Telegram supports only one active tab with the app.\nClick anywhere to continue using this tab.", diff --git a/src/lib/mtproto/connectionStatus.ts b/src/lib/mtproto/connectionStatus.ts new file mode 100644 index 00000000..d5582704 --- /dev/null +++ b/src/lib/mtproto/connectionStatus.ts @@ -0,0 +1,22 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +export enum ConnectionStatus { + Connected, + Connecting, + Closed +}; + +export type ConnectionStatusChange = { + _: 'networkerStatus', + status: ConnectionStatus, + dcId: number, + name: string, + isFileNetworker: boolean, + isFileDownload: boolean, + isFileUpload: boolean, + retryAt?: number +}; diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index e4ffdb4f..3826d967 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -189,7 +189,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { }); window.addEventListener('online', (event) => { - this.postMessage({type: 'online'}); + this.forceReconnect(); }); /// #if !MTPROTO_SW @@ -543,6 +543,10 @@ export class ApiManagerProxy extends CryptoWorkerMethods { public startAll() { return this.performTaskWorkerVoid('startAll'); } + + public forceReconnect() { + this.postMessage({type: 'online'}); + } } const apiManagerProxy = new ApiManagerProxy(); diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index dc1c298c..576de5f6 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -33,6 +33,7 @@ import HTTP from './transports/http'; import type TcpObfuscated from './transports/tcpObfuscated'; import { bigInt2str, rightShift_, str2bigInt } from '../../vendor/leemon'; import { forEachReverse } from '../../helpers/array'; +import { ConnectionStatus } from './connectionStatus'; //console.error('networker included!', new Error().stack); @@ -121,6 +122,7 @@ export default class MTPNetworker { private log: ReturnType; public isOnline = false; + public status: ConnectionStatus = ConnectionStatus.Closed; private lastResponseTime = 0; private debug = DEBUG /* && false */ || Modes.debug; @@ -190,7 +192,7 @@ export default class MTPNetworker { // } if((this.transport as TcpObfuscated).connected) { - this.setConnectionStatus(true); + this.setConnectionStatus(ConnectionStatus.Connected); } } @@ -694,7 +696,7 @@ export default class MTPNetworker { } this.log.error('timeout', message); - this.setConnectionStatus(false); + this.setConnectionStatus(ConnectionStatus.Closed); /* this.getEncryptedOutput(message).then(bytes => { this.log.error('timeout encrypted', bytes); @@ -703,7 +705,7 @@ export default class MTPNetworker { promise.finally(() => { clearTimeout(timeout); - this.setConnectionStatus(true); + this.setConnectionStatus(ConnectionStatus.Connected); if(!--this.activeRequests && this.onDrain) { this.onDrainTimeout = self.setTimeout(() => { @@ -722,21 +724,23 @@ export default class MTPNetworker { return promise; } - public setConnectionStatus(online: boolean, timeout?: number) { - const willChange = this.isOnline !== online; - this.isOnline = online; + public setConnectionStatus(status: ConnectionStatus, retryAt?: number) { + const isOnline = status === ConnectionStatus.Connected; + const willChange = this.status !== status; + this.isOnline = isOnline; + this.status = status; if(willChange) { if(networkerFactory.onConnectionStatusChange) { networkerFactory.onConnectionStatusChange({ - _: 'networkerStatus', - online: this.isOnline, + _: 'networkerStatus', + status, dcId: this.dcId, name: this.name, isFileNetworker: this.isFileNetworker, isFileDownload: this.isFileDownload, isFileUpload: this.isFileUpload, - timeout + retryAt }); } diff --git a/src/lib/mtproto/networkerFactory.ts b/src/lib/mtproto/networkerFactory.ts index e16cd415..ac9017d5 100644 --- a/src/lib/mtproto/networkerFactory.ts +++ b/src/lib/mtproto/networkerFactory.ts @@ -9,8 +9,9 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ +import type { ConnectionStatusChange } from "./connectionStatus"; import MTPNetworker from "./networker"; -import { ConnectionStatusChange, InvokeApiOptions } from "../../types"; +import { InvokeApiOptions } from "../../types"; import MTTransport from "./transports/transport"; import App from "../../config/app"; import { MOUNT_CLASS_TO } from "../../config/debug"; diff --git a/src/lib/mtproto/transports/tcpObfuscated.ts b/src/lib/mtproto/transports/tcpObfuscated.ts index 44392f59..5aa4e358 100644 --- a/src/lib/mtproto/transports/tcpObfuscated.ts +++ b/src/lib/mtproto/transports/tcpObfuscated.ts @@ -10,6 +10,7 @@ import MTPNetworker from "../networker"; import Obfuscation from "./obfuscation"; import MTTransport, { MTConnection, MTConnectionConstructable } from "./transport"; import intermediatePacketCodec from './intermediate'; +import { ConnectionStatus } from "../connectionStatus"; export default class TcpObfuscated implements MTTransport { private codec = intermediatePacketCodec; @@ -58,7 +59,7 @@ export default class TcpObfuscated implements MTTransport { if(this.networker) { this.pending.length = 0; // ! clear queue and reformat messages to container, because if sending simultaneously 10+ messages, connection will die - this.networker.setConnectionStatus(true); + this.networker.setConnectionStatus(ConnectionStatus.Connected); this.networker.cleanupSent(); this.networker.resend(); } else { @@ -113,15 +114,16 @@ export default class TcpObfuscated implements MTTransport { private onClose = () => { this.clear(); - let needTimeout: number; + let needTimeout: number, retryAt: number; if(this.autoReconnect) { const time = Date.now(); const diff = time - this.lastCloseTime; needTimeout = !isNaN(diff) && diff < this.retryTimeout ? this.retryTimeout - diff : 0; + retryAt = time + needTimeout; } if(this.networker) { - this.networker.setConnectionStatus(false, needTimeout); + this.networker.setConnectionStatus(ConnectionStatus.Closed, retryAt); this.pending.length = 0; } @@ -168,6 +170,7 @@ export default class TcpObfuscated implements MTTransport { } } + this.networker.setConnectionStatus(ConnectionStatus.Connecting); this.connect(); } diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 2af3c13b..2ca316e0 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -9,7 +9,6 @@ import type { MyDocument } from "./appManagers/appDocsManager"; import type { AppMessagesManager, Dialog, MessagesStorage } from "./appManagers/appMessagesManager"; import type { Poll, PollResults } from "./appManagers/appPollsManager"; import type { MyDialogFilter } from "./storages/filters"; -import type { ConnectionStatusChange } from "../types"; import type { UserTyping } from "./appManagers/appProfileManager"; import type Chat from "../components/chat/chat"; import type { UserAuth } from "./mtproto/mtproto_config"; @@ -17,6 +16,7 @@ import type { State, Theme } from "./appManagers/appStateManager"; import type { MyDraftMessage } from "./appManagers/appDraftsManager"; import type { PushSubscriptionNotify } from "./mtproto/webPushApiManager"; import type { PushNotificationObject } from "./serviceWorker/push"; +import type { ConnectionStatusChange } from "./mtproto/connectionStatus"; import EventListenerBase from "../helpers/eventListenerBase"; import { MOUNT_CLASS_TO } from "../config/debug"; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index af47af1c..013763f9 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -442,11 +442,17 @@ &-button { color: #2e3939; align-self: center; - pointer-events: none; + cursor: default; padding-left: 4.5rem; text-align: left; height: 3.5rem; + .force-reconnect { + cursor: pointer; + text-decoration: underline; + color: inherit; + } + transform: translateY(-100%); .preloader-container { @@ -468,6 +474,7 @@ bottom: 0; right: 0; overflow: hidden; + background-color: var(--surface-color); transform: translateY(0); height: 100%; diff --git a/src/scss/style.scss b/src/scss/style.scss index 57733fb8..1e45b3af 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -557,7 +557,7 @@ input:-webkit-autofill:active { } .bg-warning { - background: #fed85a; + background: #fed85a !important; } .contextmenu { diff --git a/src/types.d.ts b/src/types.d.ts index 783e11c2..700a3273 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -79,14 +79,3 @@ export namespace AuthState { _: 'authStateSignedIn' }; } - -export type ConnectionStatusChange = { - _: 'networkerStatus', - online: boolean, - dcId: number, - name: string, - isFileNetworker: boolean, - isFileDownload: boolean, - isFileUpload: boolean, - timeout?: number -};