From 29c6273715fff61a4a1f95cfc67252ff9fe7d86a Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sat, 1 May 2021 14:25:03 +0400 Subject: [PATCH] Language change button for QR page Refactor apiManagerProxy --- src/components/languageChangeButton.ts | 76 +++++++ src/lib/appManagers/appDialogsManager.ts | 5 +- src/lib/appManagers/appDocsManager.ts | 8 +- src/lib/appManagers/appMessagesManager.ts | 13 +- src/lib/mtproto/mtprotoworker.ts | 242 +++++++++++----------- src/lib/mtproto/referenceDatabase.ts | 33 +++ src/pages/pageSignIn.ts | 50 +---- src/pages/pageSignQR.ts | 3 + 8 files changed, 253 insertions(+), 177 deletions(-) create mode 100644 src/components/languageChangeButton.ts diff --git a/src/components/languageChangeButton.ts b/src/components/languageChangeButton.ts new file mode 100644 index 00000000..92bfab3b --- /dev/null +++ b/src/components/languageChangeButton.ts @@ -0,0 +1,76 @@ +import { attachClickEvent, cancelEvent } from "../helpers/dom"; +import { Config, LangPackDifference, LangPackString } from "../layer"; +import I18n, { LangPackKey } from "../lib/langPack"; +import apiManager from "../lib/mtproto/mtprotoworker"; +import rootScope from "../lib/rootScope"; +import Button from "./button"; +import { putPreloader } from "./misc"; + +let set = false, times = 0; +rootScope.addEventListener('language_change', () => { + if(++times < 2) { + return; + } + + console.log('language_change'); + set = true; +}); + +function getLang(): Promise<[Config.config, LangPackString[], LangPackDifference.langPackDifference]> { + if(cachedPromise) return cachedPromise; + return cachedPromise = apiManager.invokeApiCacheable('help.getConfig').then(config => { + if(config.suggested_lang_code !== I18n.lastRequestedLangCode) { + //I18n.loadLangPack(config.suggested_lang_code); + + return Promise.all([ + config, + I18n.getStrings(config.suggested_lang_code, ['Login.ContinueOnLanguage']), + I18n.getCacheLangPack() + ]); + } else { + return [] as any; + } + }); +} + +let cachedPromise: ReturnType; + +export default function getLanguageChangeButton(appendTo: HTMLElement) { + if(set) return; + getLang().then(([config, strings]) => { + if(!config) { + return; + } + + const backup: LangPackString[] = []; + strings.forEach(string => { + const backupString = I18n.strings.get(string.key as LangPackKey); + if(!backupString) { + return; + } + + backup.push(backupString); + I18n.strings.set(string.key as LangPackKey, string); + }); + + const btnChangeLanguage = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.ContinueOnLanguage'}); + appendTo.append(btnChangeLanguage); + + rootScope.addEventListener('language_change', () => { + btnChangeLanguage.remove(); + }, true); + + backup.forEach(string => { + I18n.strings.set(string.key as LangPackKey, string); + }); + + attachClickEvent(btnChangeLanguage, (e) => { + cancelEvent(e); + + btnChangeLanguage.disabled = true; + putPreloader(btnChangeLanguage); + + I18n.getLangPack(config.suggested_lang_code); + }); + }); +} diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index b077595e..d01eb82b 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -17,16 +17,16 @@ import { logger, LogTypes } from "../logger"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; import { positionElementByIndex, replaceContent } from "../../helpers/dom"; +import apiUpdatesManager from "./apiUpdatesManager"; +import appPeersManager from './appPeersManager'; import appImManager from "./appImManager"; import appMessagesManager, { Dialog } from "./appMessagesManager"; import {MyDialogFilter as DialogFilter} from "../storages/filters"; -import appPeersManager from './appPeersManager'; import appStateManager from "./appStateManager"; import appUsersManager from "./appUsersManager"; import Button from "../../components/button"; import SetTransition from "../../components/singleTransition"; import sessionStorage from '../sessionStorage'; -import apiUpdatesManager from "./apiUpdatesManager"; import appDraftsManager, { MyDraftMessage } from "./appDraftsManager"; import ProgressivePreloader from "../../components/preloader"; import App from "../../config/app"; @@ -490,6 +490,7 @@ export class AppDialogsManager { //selectTab(0); (this.folders.menu.firstElementChild as HTMLElement).click(); + appMessagesManager.construct(); appStateManager.getState().then((state) => { appNotificationsManager.getNotifyPeerTypeSettings(); diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index a3ee6ec7..1e1227fb 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -30,13 +30,17 @@ export class AppDocsManager { private docs: {[docId: string]: MyDocument} = {}; private savingLottiePreview: {[docId: string]: true} = {}; - public onServiceWorkerFail() { + constructor() { + apiManager.onServiceWorkerFail = this.onServiceWorkerFail; + } + + public onServiceWorkerFail = () => { for(const id in this.docs) { const doc = this.docs[id]; delete doc.supportsStreaming; delete doc.url; } - } + }; public saveDoc(doc: Document, context?: ReferenceContext): MyDocument { if(doc._ === 'documentEmpty') { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 8832682d..0818a16f 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files"; import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object"; import { randomLong } from "../../helpers/random"; import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string"; -import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer"; +import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer"; import { InvokeApiOptions } from "../../types"; import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack"; import { logger, LogTypes } from "../logger"; @@ -42,7 +42,6 @@ import appStateManager from "./appStateManager"; import appUsersManager from "./appUsersManager"; import appWebPagesManager from "./appWebPagesManager"; import appDraftsManager from "./appDraftsManager"; -import pushHeavyTask from "../../helpers/heavyQueue"; import { getFileNameByLocation } from "../../helpers/fileName"; import appProfileManager from "./appProfileManager"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; @@ -193,9 +192,6 @@ export class AppMessagesManager { private groupedTempId = 0; constructor() { - this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, appUsersManager, appDraftsManager, appNotificationsManager, appStateManager, apiUpdatesManager, serverTimeManager); - this.filtersStorage = new FiltersStorage(this, appPeersManager, appUsersManager, appNotificationsManager, appStateManager, apiUpdatesManager, /* apiManager, */ rootScope); - rootScope.addMultipleEventsListeners({ updateMessageID: this.onUpdateMessageId, @@ -298,7 +294,7 @@ export class AppMessagesManager { this.reloadConversation(peerId); } }); - + appStateManager.getState().then(state => { if(state.maxSeenMsgId) { this.maxSeenId = state.maxSeenMsgId; @@ -308,6 +304,11 @@ export class AppMessagesManager { appNotificationsManager.start(); } + public construct() { + this.dialogsStorage = new DialogsStorage(this, appChatsManager, appPeersManager, appUsersManager, appDraftsManager, appNotificationsManager, appStateManager, apiUpdatesManager, serverTimeManager); + this.filtersStorage = new FiltersStorage(this, appPeersManager, appUsersManager, appNotificationsManager, appStateManager, apiUpdatesManager, /* apiManager, */ rootScope); + } + public getInputEntities(entities: MessageEntity[]) { var sendEntites = copy(entities); sendEntites.forEach((entity: any) => { diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 20b53641..cc0e5c6f 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -14,12 +14,9 @@ import { logger } from '../logger'; import rootScope from '../rootScope'; import webpWorkerController from '../webp/webpWorkerController'; import type { DownloadOptions } from './apiFileManager'; -import { ApiError } from './apiManager'; -import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service'; +import type { ServiceWorkerTask } from './mtproto.service'; import { UserAuth } from './mtproto_config'; import type { MTMessage } from './networker'; -import referenceDatabase from './referenceDatabase'; -import appDocsManager from '../appManagers/appDocsManager'; import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug'; import Socket from './transports/websocket'; @@ -80,136 +77,29 @@ export class ApiManagerProxy extends CryptoWorkerMethods { private sockets: Map = new Map(); + private taskListeners: {[taskType: string]: (task: any) => void} = {}; + + public onServiceWorkerFail: () => void; + constructor() { super(); this.log('constructor'); this.registerServiceWorker(); - /// #if !MTPROTO_SW - this.registerWorker(); - /// #endif - } - - public isServiceWorkerOnline() { - return this.isSWRegistered; - } - - private registerServiceWorker() { - if(!('serviceWorker' in navigator)) return; - - const worker = navigator.serviceWorker; - worker.register('./sw.js', {scope: './'}).then(registration => { - this.log('SW registered', registration); - this.isSWRegistered = true; - - const sw = registration.installing || registration.waiting || registration.active; - sw.addEventListener('statechange', (e) => { - this.log('SW statechange', e); - }); - - /// #if MTPROTO_SW - const controller = worker.controller || registration.installing || registration.waiting || registration.active; - this.onWorkerFirstMessage(controller); - /// #endif - }, (err) => { - this.isSWRegistered = false; - this.log.error('SW registration failed!', err); - appDocsManager.onServiceWorkerFail(); - }); - - worker.addEventListener('controllerchange', () => { - this.log.warn('controllerchange'); - this.releasePending(); - - worker.controller.addEventListener('error', (e) => { - this.log.error('controller error:', e); - }); - }); - - /// #if MTPROTO_SW - worker.addEventListener('message', this.onWorkerMessage); - /// #else - worker.addEventListener('message', (e) => { - const task: ServiceWorkerTask = e.data; - if(!isObject(task)) { - return; - } - - this.postMessage(task); - }); - /// #endif - - worker.addEventListener('messageerror', (e) => { - this.log.error('SW messageerror:', e); - }); - } - - private onWorkerFirstMessage(worker: any) { - if(!this.worker) { - this.worker = worker; - this.log('set webWorker'); - - this.postMessage = this.worker.postMessage.bind(this.worker); - - const isWebpSupported = webpWorkerController.isWebpSupported(); - this.log('WebP supported:', isWebpSupported); - this.postMessage({type: 'webpSupport', payload: isWebpSupported}); - - this.releasePending(); - } - } - - private onWorkerMessage = (e: MessageEvent) => { - //this.log('got message from worker:', e.data); - - const task = e.data; - - if(!isObject(task)) { - return; - } - - if(task.update) { - if(this.updatesProcessor) { - this.updatesProcessor(task.update); - } - } else if(task.progress) { - rootScope.broadcast('download_progress', task.progress); - } else if(task.type === 'reload') { + this.addTaskListener('reload', () => { location.reload(); - } else if(task.type === 'connectionStatusChange') { + }); + + this.addTaskListener('connectionStatusChange', (task: any) => { rootScope.broadcast('connection_status_change', task.payload); - } else if(task.type === 'convertWebp') { + }); + + this.addTaskListener('convertWebp', (task) => { webpWorkerController.postMessage(task); - } else if((task as ServiceWorkerTaskResponse).type === 'requestFilePart') { - const _task = task as ServiceWorkerTaskResponse; - - if(_task.error) { - const onError = (error: ApiError) => { - if(error?.type === 'FILE_REFERENCE_EXPIRED') { - // @ts-ignore - const bytes = _task.originalPayload[1].file_reference; - referenceDatabase.refreshReference(bytes).then(() => { - // @ts-ignore - _task.originalPayload[1].file_reference = referenceDatabase.getReferenceByLink(bytes); - const newTask: ServiceWorkerTask = { - type: _task.type, - id: _task.id, - payload: _task.originalPayload - }; + }); - this.postMessage(newTask); - }).catch(onError); - } else { - navigator.serviceWorker.controller.postMessage(task); - } - }; - - onError(_task.error); - } else { - navigator.serviceWorker.controller.postMessage(task); - } - } else if(task.type === 'socketProxy') { + this.addTaskListener('socketProxy', (task) => { const socketTask = task.payload; const id = socketTask.id; //console.log('socketProxy', socketTask, id); @@ -263,6 +153,110 @@ export class ApiManagerProxy extends CryptoWorkerMethods { socket.addEventListener('message', onMessage); this.sockets.set(id, socket); } + }); + + /// #if !MTPROTO_SW + this.registerWorker(); + /// #endif + } + + public isServiceWorkerOnline() { + return this.isSWRegistered; + } + + private registerServiceWorker() { + if(!('serviceWorker' in navigator)) return; + + const worker = navigator.serviceWorker; + worker.register('./sw.js', {scope: './'}).then(registration => { + this.log('SW registered', registration); + this.isSWRegistered = true; + + const sw = registration.installing || registration.waiting || registration.active; + sw.addEventListener('statechange', (e) => { + this.log('SW statechange', e); + }); + + /// #if MTPROTO_SW + const controller = worker.controller || registration.installing || registration.waiting || registration.active; + this.onWorkerFirstMessage(controller); + /// #endif + }, (err) => { + this.isSWRegistered = false; + this.log.error('SW registration failed!', err); + + if(this.onServiceWorkerFail) { + this.onServiceWorkerFail(); + } + }); + + worker.addEventListener('controllerchange', () => { + this.log.warn('controllerchange'); + this.releasePending(); + + worker.controller.addEventListener('error', (e) => { + this.log.error('controller error:', e); + }); + }); + + /// #if MTPROTO_SW + worker.addEventListener('message', this.onWorkerMessage); + /// #else + worker.addEventListener('message', (e) => { + const task: ServiceWorkerTask = e.data; + if(!isObject(task)) { + return; + } + + this.postMessage(task); + }); + /// #endif + + worker.addEventListener('messageerror', (e) => { + this.log.error('SW messageerror:', e); + }); + } + + private onWorkerFirstMessage(worker: any) { + if(!this.worker) { + this.worker = worker; + this.log('set webWorker'); + + this.postMessage = this.worker.postMessage.bind(this.worker); + + const isWebpSupported = webpWorkerController.isWebpSupported(); + this.log('WebP supported:', isWebpSupported); + this.postMessage({type: 'webpSupport', payload: isWebpSupported}); + + this.releasePending(); + } + } + + public addTaskListener(name: keyof ApiManagerProxy['taskListeners'], callback: ApiManagerProxy['taskListeners'][typeof name]) { + this.taskListeners[name] = callback; + } + + private onWorkerMessage = (e: MessageEvent) => { + //this.log('got message from worker:', e.data); + + const task = e.data; + + if(!isObject(task)) { + return; + } + + const callback = this.taskListeners[task.type]; + if(callback) { + callback(task); + return; + } + + if(task.update) { + if(this.updatesProcessor) { + this.updatesProcessor(task.update); + } + } else if(task.progress) { + rootScope.broadcast('download_progress', task.progress); } else if(task.hasOwnProperty('result') || task.hasOwnProperty('error')) { this.finalizeTask(task.taskId, task.result, task.error); } diff --git a/src/lib/mtproto/referenceDatabase.ts b/src/lib/mtproto/referenceDatabase.ts index 8675c567..47683d39 100644 --- a/src/lib/mtproto/referenceDatabase.ts +++ b/src/lib/mtproto/referenceDatabase.ts @@ -4,11 +4,14 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from "./mtproto.service"; +import type { ApiError } from "./apiManager"; import appMessagesManager from "../appManagers/appMessagesManager"; import { Photo } from "../../layer"; import { bytesToHex } from "../../helpers/bytes"; import { deepEqual } from "../../helpers/object"; import { MOUNT_CLASS_TO } from "../../config/debug"; +import apiManager from "./mtprotoworker"; export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage; export namespace ReferenceContext { @@ -34,6 +37,36 @@ class ReferenceDatabase { //private references: Map = new Map(); private links: {[hex: string]: ReferenceBytes} = {}; + constructor() { + apiManager.addTaskListener('requestFilePart', (task: ServiceWorkerTaskResponse) => { + if(task.error) { + const onError = (error: ApiError) => { + if(error?.type === 'FILE_REFERENCE_EXPIRED') { + // @ts-ignore + const bytes = task.originalPayload[1].file_reference; + referenceDatabase.refreshReference(bytes).then(() => { + // @ts-ignore + task.originalPayload[1].file_reference = referenceDatabase.getReferenceByLink(bytes); + const newTask: ServiceWorkerTask = { + type: task.type, + id: task.id, + payload: task.originalPayload + }; + + apiManager.postMessage(newTask); + }).catch(onError); + } else { + navigator.serviceWorker.controller.postMessage(task); + } + }; + + onError(task.error); + } else { + navigator.serviceWorker.controller.postMessage(task); + } + }); + } + public saveContext(reference: ReferenceBytes, context: ReferenceContext, contexts?: ReferenceContexts) { [contexts, reference] = this.getContexts(reference); if(!contexts) { diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts index 0f95e437..a3bb9af2 100644 --- a/src/pages/pageSignIn.ts +++ b/src/pages/pageSignIn.ts @@ -20,8 +20,7 @@ import fastSmoothScroll from "../helpers/fastSmoothScroll"; import { isTouchSupported } from "../helpers/touchSupport"; import App from "../config/app"; import Modes from "../config/modes"; -import I18n, { _i18n, i18n, LangPackKey } from "../lib/langPack"; -import { LangPackString } from "../layer"; +import { _i18n, i18n } from "../lib/langPack"; import lottieLoader from "../lib/lottieLoader"; import { ripple } from "../components/ripple"; import findUpTag from "../helpers/dom/findUpTag"; @@ -29,6 +28,8 @@ import findUpClassName from "../helpers/dom/findUpClassName"; import { randomLong } from "../helpers/random"; import AppStorage from "../lib/storage"; import CacheStorageController from "../lib/cacheStorage"; +import pageSignQR from "./pageSignQR"; +import getLanguageChangeButton from "../components/languageChangeButton"; type Country = _Country & { li?: HTMLLIElement[] @@ -383,7 +384,8 @@ let onFirstMount = () => { let qrMounted = false; btnQr.addEventListener('click', () => { - const promise = import('./pageSignQR'); + pageSignQR.mount(); + /* const promise = import('./pageSignQR'); btnQr.disabled = true; let preloaderDiv: HTMLElement; @@ -401,7 +403,7 @@ let onFirstMount = () => { preloaderDiv.remove(); } }, 200); - }); + }); */ }); inputWrapper.append(countryInputField.container, telInputField.container, signedCheckboxField.label, btnNext, btnQr); @@ -461,45 +463,7 @@ let onFirstMount = () => { }, 0); } - apiManager.invokeApi('help.getConfig').then(config => { - if(config.suggested_lang_code !== I18n.lastRequestedLangCode) { - //I18n.loadLangPack(config.suggested_lang_code); - - Promise.all([ - I18n.getStrings(config.suggested_lang_code, ['Login.ContinueOnLanguage']), - I18n.getCacheLangPack() - ]).then(res => { - const backup: LangPackString[] = []; - res[0].forEach(string => { - const backupString = I18n.strings.get(string.key as LangPackKey); - if(!backupString) { - return; - } - - backup.push(backupString); - I18n.strings.set(string.key as LangPackKey, string); - }); - - const btnChangeLanguage = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.ContinueOnLanguage'}); - inputWrapper.append(btnChangeLanguage); - - backup.forEach(string => { - I18n.strings.set(string.key as LangPackKey, string); - }); - - attachClickEvent(btnChangeLanguage, (e) => { - cancelEvent(e); - - btnChangeLanguage.disabled = true; - putPreloader(btnChangeLanguage); - - I18n.getLangPack(config.suggested_lang_code).then(() => { - btnChangeLanguage.remove(); - }); - }); - }); - } - }); + getLanguageChangeButton(inputWrapper); tryAgain(); }; diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts index dbe5bb98..1471ffd5 100644 --- a/src/pages/pageSignQR.ts +++ b/src/pages/pageSignQR.ts @@ -16,6 +16,7 @@ import { _i18n, i18n, LangPackKey } from '../lib/langPack'; import appStateManager from '../lib/appManagers/appStateManager'; import rootScope from '../lib/rootScope'; import { putPreloader } from '../components/misc'; +import getLanguageChangeButton from '../components/languageChangeButton'; let onFirstMount = async() => { const pageElement = page.pageEl; @@ -29,6 +30,8 @@ let onFirstMount = async() => { const btnBack = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.QR.Cancel'}); inputWrapper.append(btnBack); + getLanguageChangeButton(inputWrapper); + const container = imageDiv.parentElement; const h4 = document.createElement('h4');