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,12 +77,84 @@ 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(); + this.addTaskListener('reload', () => { + location.reload(); + }); + + this.addTaskListener('connectionStatusChange', (task: any) => { + rootScope.broadcast('connection_status_change', task.payload); + }); + + this.addTaskListener('convertWebp', (task) => { + webpWorkerController.postMessage(task); + }); + + this.addTaskListener('socketProxy', (task) => { + const socketTask = task.payload; + const id = socketTask.id; + //console.log('socketProxy', socketTask, id); + + if(socketTask.type === 'send') { + const socket = this.sockets.get(id); + socket.send(socketTask.payload); + } else if(socketTask.type === 'close') { + const socket = this.sockets.get(id); + socket.close(); + } else if(socketTask.type === 'setup') { + const socket = new Socket(socketTask.payload.dcId, socketTask.payload.url, socketTask.payload.logSuffix); + + const onOpen = () => { + //console.log('socketProxy onOpen'); + this.postMessage({ + type: 'socketProxy', + payload: { + type: 'open', + id + } + }); + }; + const onClose = () => { + this.postMessage({ + type: 'socketProxy', + payload: { + type: 'close', + id + } + }); + + socket.removeEventListener('open', onOpen); + socket.removeEventListener('close', onClose); + socket.removeEventListener('message', onMessage); + this.sockets.delete(id); + }; + const onMessage = (buffer: ArrayBuffer) => { + this.postMessage({ + type: 'socketProxy', + payload: { + type: 'message', + id, + payload: buffer + } + }); + }; + + socket.addEventListener('open', onOpen); + socket.addEventListener('close', onClose); + socket.addEventListener('message', onMessage); + this.sockets.set(id, socket); + } + }); + /// #if !MTPROTO_SW this.registerWorker(); /// #endif @@ -115,7 +184,10 @@ export class ApiManagerProxy extends CryptoWorkerMethods { }, (err) => { this.isSWRegistered = false; this.log.error('SW registration failed!', err); - appDocsManager.onServiceWorkerFail(); + + if(this.onServiceWorkerFail) { + this.onServiceWorkerFail(); + } }); worker.addEventListener('controllerchange', () => { @@ -160,6 +232,10 @@ export class ApiManagerProxy extends CryptoWorkerMethods { } } + 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); @@ -169,100 +245,18 @@ export class ApiManagerProxy extends CryptoWorkerMethods { 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.type === 'reload') { - location.reload(); - } else if(task.type === 'connectionStatusChange') { - rootScope.broadcast('connection_status_change', task.payload); - } else if(task.type === 'convertWebp') { - 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') { - const socketTask = task.payload; - const id = socketTask.id; - //console.log('socketProxy', socketTask, id); - - if(socketTask.type === 'send') { - const socket = this.sockets.get(id); - socket.send(socketTask.payload); - } else if(socketTask.type === 'close') { - const socket = this.sockets.get(id); - socket.close(); - } else if(socketTask.type === 'setup') { - const socket = new Socket(socketTask.payload.dcId, socketTask.payload.url, socketTask.payload.logSuffix); - - const onOpen = () => { - //console.log('socketProxy onOpen'); - this.postMessage({ - type: 'socketProxy', - payload: { - type: 'open', - id - } - }); - }; - const onClose = () => { - this.postMessage({ - type: 'socketProxy', - payload: { - type: 'close', - id - } - }); - - socket.removeEventListener('open', onOpen); - socket.removeEventListener('close', onClose); - socket.removeEventListener('message', onMessage); - this.sockets.delete(id); - }; - const onMessage = (buffer: ArrayBuffer) => { - this.postMessage({ - type: 'socketProxy', - payload: { - type: 'message', - id, - payload: buffer - } - }); - }; - - socket.addEventListener('open', onOpen); - socket.addEventListener('close', onClose); - socket.addEventListener('message', onMessage); - this.sockets.set(id, socket); - } } 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');