From 54ace14d23a23c4e49c4ad2ff3f73d4adbc0581e Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sun, 6 Jun 2021 17:22:29 +0300 Subject: [PATCH] [breaking change] separate session and state db --- src/components/chat/bubbles.ts | 11 ++- src/components/chat/chat.ts | 6 +- src/components/chat/contextMenu.ts | 2 +- src/components/sidebarLeft/tabs/addContact.ts | 14 ++++ src/config/database.ts | 30 ------- src/config/databases/index.ts | 14 ++++ src/config/databases/session.ts | 17 ++++ src/config/databases/state.ts | 27 +++++++ src/lang.ts | 2 + src/lib/appManagers/appDraftsManager.ts | 6 +- src/lib/appManagers/appEmojiManager.ts | 10 +-- src/lib/appManagers/appImManager.ts | 14 ++-- .../appManagers/appNotificationsManager.ts | 4 +- src/lib/appManagers/appStateManager.ts | 59 +++++++++----- src/lib/appManagers/appStickersManager.ts | 5 +- src/lib/idb.ts | 49 +++++++----- src/lib/langPack.ts | 6 +- src/lib/mtproto/apiManager.ts | 34 ++++++-- src/lib/mtproto/mtproto_config.ts | 5 +- src/lib/mtproto/mtprotoworker.ts | 27 ++++++- src/lib/mtproto/timeManager.ts | 2 +- src/lib/rootScope.ts | 2 +- src/lib/sessionStorage.ts | 36 ++++----- src/lib/stateStorage.ts | 23 ++++++ src/lib/storage.ts | 79 ++++++++++++++++--- src/lib/storages/filters.ts | 2 +- 26 files changed, 341 insertions(+), 145 deletions(-) create mode 100644 src/components/sidebarLeft/tabs/addContact.ts delete mode 100644 src/config/database.ts create mode 100644 src/config/databases/index.ts create mode 100644 src/config/databases/session.ts create mode 100644 src/config/databases/state.ts create mode 100644 src/lib/stateStorage.ts diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index f5c7f806..c282a3e3 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -12,7 +12,7 @@ import type { AppInlineBotsManager } from "../../lib/appManagers/appInlineBotsMa import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager"; import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; -import type sessionStorage from '../../lib/sessionStorage'; +import type stateStorage from '../../lib/stateStorage'; import type Chat from "./chat"; import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import { getObjectKeysAndSort } from "../../helpers/object"; @@ -147,7 +147,14 @@ export default class ChatBubbles { [_ in MessageEntity['_']]: boolean }> = {}; - constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) { + constructor(private chat: Chat, + private appMessagesManager: AppMessagesManager, + private appStickersManager: AppStickersManager, + private appUsersManager: AppUsersManager, + private appInlineBotsManager: AppInlineBotsManager, + private appPhotosManager: AppPhotosManager, + private appPeersManager: AppPeersManager + ) { //this.chat.log.error('Bubbles construction'); this.listenerSetter = new ListenerSetter(); diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 589e298f..a8f98c12 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -21,7 +21,7 @@ import type { ApiManagerProxy } from "../../lib/mtproto/mtprotoworker"; import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager"; import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager"; import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager"; -import type sessionStorage from '../../lib/sessionStorage'; +import type stateStorage from '../../lib/stateStorage'; import EventListenerBase from "../../helpers/eventListenerBase"; import { logger, LogTypes } from "../../lib/logger"; import rootScope from "../../lib/rootScope"; @@ -82,7 +82,7 @@ export default class Chat extends EventListenerBase<{ public apiManager: ApiManagerProxy, public appDraftsManager: AppDraftsManager, public serverTimeManager: ServerTimeManager, - public storage: typeof sessionStorage, + public storage: typeof stateStorage, public appNotificationsManager: AppNotificationsManager, public appEmojiManager: AppEmojiManager ) { @@ -170,7 +170,7 @@ export default class Chat extends EventListenerBase<{ this.initPeerId = peerId; this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager); - this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager, this.storage); + this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appPeersManager); this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager); this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager); this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager); diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index 05320407..1e00fd2a 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -225,7 +225,7 @@ export default class ChatContextMenu { withSelection: true }, { icon: 'link', - text: 'CopyLink', + text: 'MessageContext.CopyMessageLink1', onClick: this.onCopyLinkClick, verify: () => this.appPeersManager.isChannel(this.peerId) && !this.message.pFlags.is_outgoing }, { diff --git a/src/components/sidebarLeft/tabs/addContact.ts b/src/components/sidebarLeft/tabs/addContact.ts new file mode 100644 index 00000000..025088d7 --- /dev/null +++ b/src/components/sidebarLeft/tabs/addContact.ts @@ -0,0 +1,14 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { SliderSuperTab } from "../../slider"; + +export default class AppAddContactTab extends SliderSuperTab { + protected init() { + this.container.classList.add('add-contact-container'); + this.setTitle('AddContactTitle'); + } +} diff --git a/src/config/database.ts b/src/config/database.ts deleted file mode 100644 index 94bb66c9..00000000 --- a/src/config/database.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * https://github.com/morethanwords/tweb - * Copyright (C) 2019-2021 Eduard Kuzmenko - * https://github.com/morethanwords/tweb/blob/master/LICENSE - */ - -import { IDBStore } from "../lib/idb"; -import Modes from "./modes"; - -export type DatabaseStoreName = 'session' | 'stickerSets' | 'users' | 'chats' | 'messages' | 'dialogs'; -export type DatabaseStore = Omit & {name: DatabaseStoreName}; -const Database = { - name: 'tweb' + (Modes.test ? '_test' : ''), - version: 7, - stores: [{ - name: 'session' - }, { - name: 'stickerSets' - }, { - name: 'users' - }, { - name: 'chats' - }, { - name: 'dialogs' - }, { - name: 'messages' - }] as DatabaseStore[], -}; - -export default Database; diff --git a/src/config/databases/index.ts b/src/config/databases/index.ts new file mode 100644 index 00000000..b6a569f6 --- /dev/null +++ b/src/config/databases/index.ts @@ -0,0 +1,14 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { IDBStore } from "../../lib/idb"; + +export type DatabaseStore = Omit & {name: StoreName}; +export type Database = { + name: string, + version: number, + stores: DatabaseStore[] +}; diff --git a/src/config/databases/session.ts b/src/config/databases/session.ts new file mode 100644 index 00000000..106729f8 --- /dev/null +++ b/src/config/databases/session.ts @@ -0,0 +1,17 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { Database } from '.'; + +const DATABASE_SESSION: Database<'session'> = { + name: 'telegram', + version: 1, + stores: [{ + name: 'session' + }] +}; + +export default DATABASE_SESSION; diff --git a/src/config/databases/state.ts b/src/config/databases/state.ts new file mode 100644 index 00000000..37fa35bf --- /dev/null +++ b/src/config/databases/state.ts @@ -0,0 +1,27 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import { Database } from '.'; + +const DATABASE_STATE: Database<'session' | 'stickerSets' | 'users' | 'chats' | 'messages' | 'dialogs'> = { + name: 'tweb', + version: 7, + stores: [{ + name: 'session' + }, { + name: 'stickerSets' + }, { + name: 'users' + }, { + name: 'chats' + }, { + name: 'dialogs' + }, { + name: 'messages' + }] +}; + +export default DATABASE_STATE; diff --git a/src/lang.ts b/src/lang.ts index 398195c1..2113e29e 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -437,6 +437,7 @@ const lang = { "NoResult": "No results", "Updating": "Updating...", "Emoji": "Emoji", + "AddContactTitle": "Add Contact", // * macos "AccountSettings.Filters": "Chat Folders", @@ -664,6 +665,7 @@ const lang = { "Message.Context.Select": "Select", "Message.Context.Pin": "Pin", "Message.Context.Unpin": "Unpin", + "MessageContext.CopyMessageLink1": "Copy Message Link", "NewPoll.Anonymous": "Anonymous Voting", "NewPoll.Explanation.Placeholder": "Add a Comment (Optional)", "NewPoll.OptionsAddOption": "Add an Option", diff --git a/src/lib/appManagers/appDraftsManager.ts b/src/lib/appManagers/appDraftsManager.ts index acd15e53..8aa07d74 100644 --- a/src/lib/appManagers/appDraftsManager.ts +++ b/src/lib/appManagers/appDraftsManager.ts @@ -21,7 +21,7 @@ import { tsNow } from "../../helpers/date"; import { deepEqual } from "../../helpers/object"; import { isObject } from "../mtproto/bin_utils"; import { MOUNT_CLASS_TO } from "../../config/debug"; -import sessionStorage from "../sessionStorage"; +import stateStorage from "../stateStorage"; export type MyDraftMessage = DraftMessage.draftMessage; @@ -30,7 +30,7 @@ export class AppDraftsManager { private getAllDraftPromise: Promise = null; constructor() { - sessionStorage.get('drafts').then(drafts => { + stateStorage.get('drafts').then(drafts => { this.drafts = drafts || {}; }); @@ -96,7 +96,7 @@ export class AppDraftsManager { delete this.drafts[key]; } - sessionStorage.set({ + stateStorage.set({ drafts: this.drafts }); diff --git a/src/lib/appManagers/appEmojiManager.ts b/src/lib/appManagers/appEmojiManager.ts index 6db5f143..76f8dcc5 100644 --- a/src/lib/appManagers/appEmojiManager.ts +++ b/src/lib/appManagers/appEmojiManager.ts @@ -11,7 +11,7 @@ import I18n from "../langPack"; import { isObject } from "../mtproto/bin_utils"; import apiManager from "../mtproto/mtprotoworker"; import SearchIndex from "../searchIndex"; -import sessionStorage from "../sessionStorage"; +import stateStorage from "../stateStorage"; import appStateManager from "./appStateManager"; type EmojiLangPack = { @@ -45,7 +45,7 @@ export class AppEmojiManager { private getRecentEmojisPromise: Promise; /* public getPopularEmoji() { - return sessionStorage.get('emojis_popular').then(popEmojis => { + return stateStorage.get('emojis_popular').then(popEmojis => { var result = [] if (popEmojis && popEmojis.length) { for (var i = 0, len = popEmojis.length; i < len; i++) { @@ -55,7 +55,7 @@ export class AppEmojiManager { return } - return sessionStorage.get('emojis_recent').then(recentEmojis => { + return stateStorage.get('emojis_recent').then(recentEmojis => { recentEmojis = recentEmojis || popular || [] var shortcut var code @@ -111,7 +111,7 @@ export class AppEmojiManager { } const storageKey: any = 'emojiKeywords_' + langCode; - return this.getKeywordsPromises[langCode] = sessionStorage.get(storageKey).then((pack: EmojiLangPack) => { + return this.getKeywordsPromises[langCode] = stateStorage.get(storageKey).then((pack: EmojiLangPack) => { if(!isObject(pack)) { pack = {} as any; } @@ -135,7 +135,7 @@ export class AppEmojiManager { packKeywords[keyword] = emoticons; } - sessionStorage.set({ + stateStorage.set({ [storageKey]: pack }); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 11684de1..24c2267c 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -35,7 +35,7 @@ import lottieLoader from '../lottieLoader'; import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import appDraftsManager from './appDraftsManager'; import serverTimeManager from '../mtproto/serverTimeManager'; -import sessionStorage from '../sessionStorage'; +import stateStorage from '../stateStorage'; import appDownloadManager from './appDownloadManager'; import { AppStateManager } from './appStateManager'; import { MOUNT_CLASS_TO } from '../../config/debug'; @@ -215,8 +215,8 @@ export class AppImManager { popup.show(); }); - sessionStorage.get('chatPositions').then((c) => { - sessionStorage.setToCache('chatPositions', c || {}); + stateStorage.get('chatPositions').then((c) => { + stateStorage.setToCache('chatPositions', c || {}); }); (window as any).showMaskedAlert = (element: HTMLAnchorElement, e: Event) => { @@ -385,7 +385,7 @@ export class AppImManager { const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : ''); - const chatPositions = sessionStorage.getFromCache('chatPositions'); + const chatPositions = stateStorage.getFromCache('chatPositions'); if(!(chat.bubbles.scrollable.getDistanceToEnd() <= 16 && chat.bubbles.scrollable.loadedAll.bottom) && Object.keys(chat.bubbles.bubbles).length) { const position = { mids: getObjectKeysAndSort(chat.bubbles.bubbles, 'desc'), @@ -401,7 +401,7 @@ export class AppImManager { this.log('deleted chat position'); } - sessionStorage.set({chatPositions}, true); + stateStorage.set({chatPositions}, true); //} } @@ -411,7 +411,7 @@ export class AppImManager { } const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : ''); - const cache = sessionStorage.getFromCache('chatPositions'); + const cache = stateStorage.getFromCache('chatPositions'); return cache && cache[key]; } @@ -820,7 +820,7 @@ export class AppImManager { apiManager, appDraftsManager, serverTimeManager, - sessionStorage, + stateStorage, appNotificationsManager, appEmojiManager ); diff --git a/src/lib/appManagers/appNotificationsManager.ts b/src/lib/appManagers/appNotificationsManager.ts index 63e1bc78..a3a0eb30 100644 --- a/src/lib/appManagers/appNotificationsManager.ts +++ b/src/lib/appManagers/appNotificationsManager.ts @@ -20,7 +20,7 @@ import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySetting import I18n from "../langPack"; import apiManager from "../mtproto/mtprotoworker"; import rootScope from "../rootScope"; -import sessionStorage from "../sessionStorage"; +import stateStorage from "../stateStorage"; import apiUpdatesManager from "./apiUpdatesManager"; import appPeersManager from "./appPeersManager"; import appStateManager from "./appStateManager"; @@ -268,7 +268,7 @@ export class AppNotificationsManager { } public updateLocalSettings = () => { - Promise.all(['notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush'].map(k => sessionStorage.get(k as any))) + Promise.all(['notify_nodesktop', 'notify_volume', 'notify_novibrate', 'notify_nopreview', 'notify_nopush'].map(k => stateStorage.get(k as any))) .then((updSettings) => { this.settings.nodesktop = updSettings[0]; this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1]; diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index e6fc1f3f..ba022f00 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -12,7 +12,7 @@ import type FiltersStorage from '../storages/filters'; import type DialogsStorage from '../storages/dialogs'; import EventListenerBase from '../../helpers/eventListenerBase'; import rootScope from '../rootScope'; -import sessionStorage from '../sessionStorage'; +import stateStorage from '../stateStorage'; import { logger } from '../logger'; import { copy, setDeepProperty, validateInitObject } from '../../helpers/object'; import App from '../../config/app'; @@ -20,6 +20,8 @@ import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug'; import AppStorage from '../storage'; import { Chat } from '../../layer'; import { isMobile } from '../../helpers/userAgent'; +import DATABASE_STATE from '../../config/databases/state'; +import sessionStorage from '../sessionStorage'; const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day const REFRESH_EVERY_WEEK = 24 * 60 * 60 * 1000 * 7; // 7 days @@ -174,22 +176,14 @@ export class AppStateManager extends EventListenerBase<{ private singlePeerMap: Map = new Map(); public storages = { - users: new AppStorage>({ - storeName: 'users' - }), - - chats: new AppStorage>({ - storeName: 'chats' - }), - - dialogs: new AppStorage>({ - storeName: 'dialogs' - }) + users: new AppStorage, typeof DATABASE_STATE>(DATABASE_STATE, 'users'), + chats: new AppStorage, typeof DATABASE_STATE>(DATABASE_STATE, 'chats'), + dialogs: new AppStorage, typeof DATABASE_STATE>(DATABASE_STATE, 'dialogs') }; public storagesResults: {[key in keyof AppStateManager['storages']]: any[]} = {} as any; - public storage = sessionStorage; + public storage = stateStorage; constructor() { super(); @@ -201,11 +195,10 @@ export class AppStateManager extends EventListenerBase<{ console.time('load state'); this.loaded = new Promise((resolve) => { const storagesKeys = Object.keys(this.storages) as Array; - const storagesPromises = storagesKeys.map(key => this.storages[key].getAll()); + const storagesPromises: Promise[] = storagesKeys.map(key => this.storages[key].getAll()); - const promises = ALL_KEYS - .concat('user_auth' as any) - .map(key => sessionStorage.get(key)) + const promises: Promise[] = ALL_KEYS.map(key => stateStorage.get(key)) + .concat(sessionStorage.get('user_auth')) .concat(storagesPromises); Promise.all(promises).then((arr) => { @@ -257,16 +250,40 @@ export class AppStateManager extends EventListenerBase<{ arr.splice(0, ALL_KEYS.length); // * Read auth - const auth: UserAuth = arr.shift() as any; + let auth = arr.shift() as UserAuth | number; + if(!auth) { // try to read Webogram's session from localStorage + try { + const keys = Object.keys(localStorage); + for(let i = 0; i < keys.length; ++i) { + const key = keys[i]; + let value: any; + try { + value = localStorage.getItem(key); + value = JSON.parse(value); + } catch(err) { + //console.error(err); + } + + sessionStorage.set({ + [key as any]: value + }); + } + + auth = sessionStorage.getFromCache('user_auth'); + } catch(err) { + this.log.error('localStorage import error', err); + } + } + if(auth) { // ! Warning ! DON'T delete this state.authState = {_: 'authStateSignedIn'}; - rootScope.broadcast('user_auth', typeof(auth) !== 'number' ? (auth as any).id : auth); // * support old version + rootScope.broadcast('user_auth', typeof(auth) === 'number' ? {dcID: 0, id: auth} : auth); // * support old version } // * Read storages for(let i = 0, length = storagesKeys.length; i < length; ++i) { - this.storagesResults[storagesKeys[i]] = arr[i]; + this.storagesResults[storagesKeys[i]] = arr[i] as any; } arr.splice(0, storagesKeys.length); @@ -362,7 +379,7 @@ export class AppStateManager extends EventListenerBase<{ this.state[key] = value; } - sessionStorage.set({ + this.storage.set({ [key]: value }); } diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index 297bbd61..4de1a58c 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -12,13 +12,12 @@ import appDocsManager from './appDocsManager'; import AppStorage from '../storage'; import { MOUNT_CLASS_TO } from '../../config/debug'; import { forEachReverse } from '../../helpers/array'; +import DATABASE_STATE from '../../config/databases/state'; const CACHE_TIME = 3600e3; export class AppStickersManager { - private storage = new AppStorage>({ - storeName: 'stickerSets' - }); + private storage = new AppStorage, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets'); private getStickerSetPromises: {[setId: string]: Promise} = {}; private getStickersByEmoticonsPromises: {[emoticon: string]: Promise} = {}; diff --git a/src/lib/idb.ts b/src/lib/idb.ts index cb356827..b81e2568 100644 --- a/src/lib/idb.ts +++ b/src/lib/idb.ts @@ -9,7 +9,8 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ -import Database from '../config/database'; +import { Database } from '../config/databases'; +import Modes from '../config/modes'; import { blobConstruct } from '../helpers/blob'; import { safeAssign } from '../helpers/object'; import { logger } from './logger'; @@ -37,27 +38,31 @@ export type IDBOptions = { const DEBUG = false; -export default class IDBStorage { - private static STORAGES: IDBStorage[] = []; +export default class IDBStorage> { + private static STORAGES: IDBStorage>[] = []; private openDbPromise: Promise; private db: IDBDatabase; private storageIsAvailable = true; private log: ReturnType; - private name: string = Database.name; - private version: number = Database.version; - private stores: IDBStore[] = Database.stores; - + private name: string; + private version: number; + private stores: IDBStore[]; private storeName: string; - constructor(options: IDBOptions) { - safeAssign(this, options); + constructor(db: T, storeName: typeof db['stores'][0]['name']) { + safeAssign(this, db); + this.storeName = storeName; this.log = logger('IDB-' + this.storeName); this.openDatabase(true); + if(Modes.test) { + this.name += '_test'; + } + IDBStorage.STORAGES.push(this); } @@ -74,17 +79,23 @@ export default class IDBStorage { public static deleteDatabase() { this.closeDatabases(); - return new Promise((resolve, reject) => { - const deleteRequest = indexedDB.deleteDatabase(Database.name); - - deleteRequest.onerror = () => { - reject(); - }; - - deleteRequest.onsuccess = () => { - resolve(); - }; + const storages = this.STORAGES; + const dbNames = Array.from(new Set(storages.map(storage => storage.name))); + const promises = dbNames.map(dbName => { + return new Promise((resolve, reject) => { + const deleteRequest = indexedDB.deleteDatabase(dbName); + + deleteRequest.onerror = () => { + reject(); + }; + + deleteRequest.onsuccess = () => { + resolve(); + }; + }); }); + + return Promise.all(promises); } public isAvailable() { diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index a71e5037..5eb5a1e7 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -11,7 +11,7 @@ import type lang from "../lang"; import type langSign from "../langSign"; import { LangPackDifference, LangPackString } from "../layer"; import apiManager from "./mtproto/mtprotoworker"; -import sessionStorage from "./sessionStorage"; +import stateStorage from "./stateStorage"; import App from "../config/app"; import rootScope from "./rootScope"; @@ -62,7 +62,7 @@ namespace I18n { export function getCacheLangPack(): Promise { if(cacheLangPackPromise) return cacheLangPackPromise; return cacheLangPackPromise = Promise.all([ - sessionStorage.get('langPack') as Promise, + stateStorage.get('langPack') as Promise, polyfillPromise ]).then(([langPack]) => { if(!langPack/* || true */) { @@ -177,7 +177,7 @@ namespace I18n { export function saveLangPack(langPack: LangPackDifference) { langPack.appVersion = App.langPackVersion; - return sessionStorage.set({langPack}).then(() => { + return stateStorage.set({langPack}).then(() => { applyLangPack(langPack); return langPack; }); diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts index 4d6658cc..dfba437a 100644 --- a/src/lib/mtproto/apiManager.ts +++ b/src/lib/mtproto/apiManager.ts @@ -9,8 +9,8 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ +import type { UserAuth } from './mtproto_config'; import sessionStorage from '../sessionStorage'; - import MTPNetworker, { MTMessage } from './networker'; import { isObject } from './bin_utils'; import networkerFactory from './networkerFactory'; @@ -104,17 +104,39 @@ export class ApiManager { //telegramMeWebService.setAuthorized(this.telegramMeNotified); } } */ + + public async getBaseDcId() { + if(this.baseDcId) { + return this.baseDcId; + } + + const baseDcId = await sessionStorage.get('dc'); + if(!this.baseDcId) { + if(!baseDcId) { + this.setBaseDcId(App.baseDcId); + } else { + this.baseDcId = baseDcId; + } + } + + return this.baseDcId; + } // mtpSetUserAuth - public setUserAuth(userId: number) { + public async setUserAuth(userAuth: UserAuth) { + if(!userAuth.dcID) { + const baseDcId = await this.getBaseDcId(); + userAuth.dcID = baseDcId; + } + sessionStorage.set({ - user_auth: userId + user_auth: userAuth }); //this.telegramMeNotify(true); /// #if !MTPROTO_WORKER - rootScope.broadcast('user_auth', userId); + rootScope.broadcast('user_auth', userAuth); /// #endif } @@ -429,8 +451,8 @@ export class ApiManager { if(dcId = (options.dcId || this.baseDcId)) { this.getNetworker(dcId, options).then(performRequest, rejectPromise); } else { - sessionStorage.get('dc').then((baseDcId) => { - this.getNetworker(this.baseDcId = dcId = baseDcId || App.baseDcId, options).then(performRequest, rejectPromise); + this.getBaseDcId().then(baseDcId => { + this.getNetworker(dcId = baseDcId, options).then(performRequest, rejectPromise); }); } diff --git a/src/lib/mtproto/mtproto_config.ts b/src/lib/mtproto/mtproto_config.ts index 8e6dd6b3..7ea3a3b2 100644 --- a/src/lib/mtproto/mtproto_config.ts +++ b/src/lib/mtproto/mtproto_config.ts @@ -4,6 +4,9 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -export type UserAuth = number; +/** + * Legacy Webogram's format, don't change dcID to camelCase. + */ +export type UserAuth = {dcID: number, id: number}; export const REPLIES_PEER_ID = 1271266957; diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index f544c799..028e9861 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -4,11 +4,12 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ +import type { LocalStorageProxyDeleteTask, LocalStorageProxySetTask } from '../storage'; +import type { InvokeApiOptions } from '../../types'; +import type { MethodDeclMap } from '../../layer'; import MTProtoWorker from 'worker-loader!./mtproto.worker'; //import './mtproto.worker'; import { isObject } from '../../helpers/object'; -import type { MethodDeclMap } from '../../layer'; -import type { InvokeApiOptions } from '../../types'; import CryptoWorkerMethods from '../crypto/crypto_methods'; import { logger } from '../logger'; import rootScope from '../rootScope'; @@ -93,6 +94,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { this.addTaskListener('clear', () => { const promise = IDBStorage.deleteDatabase(); + localStorage.clear(); // * clear legacy Webogram's localStorage promise.finally(() => { location.reload(); }); @@ -162,6 +164,21 @@ export class ApiManagerProxy extends CryptoWorkerMethods { } }); + this.addTaskListener('localStorageProxy', (task: LocalStorageProxySetTask | LocalStorageProxyDeleteTask) => { + const storageTask = task.payload; + if(storageTask.type === 'set') { + for(let i = 0, length = storageTask.keys.length; i < length; ++i) { + if(storageTask.values[i] !== undefined) { + localStorage.setItem(storageTask.keys[i], JSON.stringify(storageTask.values[i])); + } + } + } else if(storageTask.type === 'delete') { + for(let i = 0, length = storageTask.keys.length; i < length; ++i) { + localStorage.removeItem(storageTask.keys[i]); + } + } + }); + /// #if !MTPROTO_SW this.registerWorker(); /// #endif @@ -457,7 +474,11 @@ export class ApiManagerProxy extends CryptoWorkerMethods { return this.performTaskWorker('setQueueId', queueId); } - public setUserAuth(userAuth: UserAuth) { + public setUserAuth(userAuth: UserAuth | number) { + if(typeof(userAuth) === 'number') { + userAuth = {dcID: 0, id: userAuth}; + } + rootScope.broadcast('user_auth', userAuth); return this.performTaskWorker('setUserAuth', userAuth); } diff --git a/src/lib/mtproto/timeManager.ts b/src/lib/mtproto/timeManager.ts index 6b2b3da3..05ad2fc1 100644 --- a/src/lib/mtproto/timeManager.ts +++ b/src/lib/mtproto/timeManager.ts @@ -26,7 +26,7 @@ export class TimeManager { private timeOffset = 0; constructor() { - sessionStorage.get('server_time_offset').then((to: any) => { + sessionStorage.get('server_time_offset').then((to) => { if(to) { this.timeOffset = to; } diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 7302ebfe..7992566e 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -141,7 +141,7 @@ export class RootScope extends EventListenerBase<{ }); this.on('user_auth', (e) => { - this.myId = e; + this.myId = e.id; }); this.on('connection_status_change', (e) => { diff --git a/src/lib/sessionStorage.ts b/src/lib/sessionStorage.ts index 57096ba0..fb32c27a 100644 --- a/src/lib/sessionStorage.ts +++ b/src/lib/sessionStorage.ts @@ -4,33 +4,27 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import type { ChatSavedPosition } from './appManagers/appImManager'; -import type { State } from './appManagers/appStateManager'; -import type { AppDraftsManager } from './appManagers/appDraftsManager'; import type { AppInstance } from './mtproto/singleInstance'; +import type { UserAuth } from './mtproto/mtproto_config'; import { MOUNT_CLASS_TO } from '../config/debug'; -import { LangPackDifference } from '../layer'; import AppStorage from './storage'; +import DATABASE_SESSION from '../config/databases/session'; const sessionStorage = new AppStorage<{ dc: number, - user_auth: number, - dc1_auth_key: any, - dc2_auth_key: any, - dc3_auth_key: any, - dc4_auth_key: any, - dc5_auth_key: any, - max_seen_msg: number, + user_auth: UserAuth, + dc1_auth_key: string, + dc2_auth_key: string, + dc3_auth_key: string, + dc4_auth_key: string, + dc5_auth_key: string, + dc1_server_salt: string, + dc2_server_salt: string, + dc3_server_salt: string, + dc4_server_salt: string, + dc5_server_salt: string, server_time_offset: number, - xt_instance: AppInstance, - - chatPositions: { - [peerId_threadId: string]: ChatSavedPosition - }, - langPack: LangPackDifference, - drafts: AppDraftsManager['drafts'] -} & State>({ - storeName: 'session' -}); + xt_instance: AppInstance +}, typeof DATABASE_SESSION>(DATABASE_SESSION, 'session'); MOUNT_CLASS_TO.appStorage = sessionStorage; export default sessionStorage; diff --git a/src/lib/stateStorage.ts b/src/lib/stateStorage.ts new file mode 100644 index 00000000..6a4056bd --- /dev/null +++ b/src/lib/stateStorage.ts @@ -0,0 +1,23 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import type { ChatSavedPosition } from './appManagers/appImManager'; +import type { State } from './appManagers/appStateManager'; +import type { AppDraftsManager } from './appManagers/appDraftsManager'; +import { MOUNT_CLASS_TO } from '../config/debug'; +import { LangPackDifference } from '../layer'; +import AppStorage from './storage'; +import DATABASE_STATE from '../config/databases/state'; + +const stateStorage = new AppStorage<{ + chatPositions: { + [peerId_threadId: string]: ChatSavedPosition + }, + langPack: LangPackDifference, + drafts: AppDraftsManager['drafts'] +} & State, typeof DATABASE_STATE>(DATABASE_STATE, 'session'); +MOUNT_CLASS_TO.stateStorage = stateStorage; +export default stateStorage; diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 07e9340c..3f1ae2ac 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -9,16 +9,35 @@ * https://github.com/zhukov/webogram/blob/master/LICENSE */ -import { DatabaseStore, DatabaseStoreName } from "../config/database"; +import { Database } from "../config/databases"; +import DATABASE_SESSION from "../config/databases/session"; import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; import { throttle } from "../helpers/schedulers"; -import IDBStorage, { IDBOptions } from "./idb"; +import { WorkerTaskTemplate } from "../types"; +import IDBStorage from "./idb"; function noop() {} -export default class AppStorage/* Storage extends {[name: string]: any} *//* Storage extends Record */> { - private static STORAGES: AppStorage[] = []; - private storage: IDBStorage;//new CacheStorageController('session'); +export interface LocalStorageProxySetTask extends WorkerTaskTemplate { + type: 'localStorageProxy', + payload: { + type: 'set', + keys: string[], + values: any[] + } +}; + +export interface LocalStorageProxyDeleteTask extends WorkerTaskTemplate { + type: 'localStorageProxy', + payload: { + type: 'delete', + keys: string[] + } +}; + +export default class AppStorage, T extends Database/* Storage extends {[name: string]: any} *//* Storage extends Record */> { + private static STORAGES: AppStorage>[] = []; + private storage: IDBStorage;//new CacheStorageController('session'); //private cache: Partial<{[key: string]: Storage[typeof key]}> = {}; private cache: Partial = {}; @@ -35,8 +54,8 @@ export default class AppStorage/* Storage ex private deleteThrottled: () => void; private deleteDeferred = deferredPromise(); - constructor(storageOptions: Omit & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) { - this.storage = new IDBStorage(storageOptions); + constructor(private db: T, storeName: typeof db['stores'][number]['name']) { + this.storage = new IDBStorage(db, storeName); AppStorage.STORAGES.push(this); @@ -53,7 +72,20 @@ export default class AppStorage/* Storage ex //console.log('setItem: will set', key/* , value */); //await this.cacheStorage.delete(key); // * try to prevent memory leak in Chrome leading to 'Unexpected internal error.' //await this.storage.save(key, new Response(value, {headers: {'Content-Type': 'application/json'}})); - await this.storage.save(keys, keys.map(key => this.cache[key])); + + const values = keys.map(key => this.cache[key]); + if(db === DATABASE_SESSION && !('localStorage' in self)) { // * support legacy Webogram's localStorage + self.postMessage({ + type: 'localStorageProxy', + payload: { + type: 'set', + keys, + values + } + } as LocalStorageProxySetTask); + } + + await this.storage.save(keys, values); //console.log('setItem: have set', key/* , value */); } catch(e) { //this.useCS = false; @@ -78,6 +110,16 @@ export default class AppStorage/* Storage ex set.clear(); try { + if(db === DATABASE_SESSION && !('localStorage' in self)) { // * support legacy Webogram's localStorage + self.postMessage({ + type: 'localStorageProxy', + payload: { + type: 'delete', + keys + } + } as LocalStorageProxyDeleteTask); + } + await this.storage.delete(keys); } catch(e) { console.error('[AS]: delete error:', e, keys); @@ -107,7 +149,7 @@ export default class AppStorage/* Storage ex }, (error) => { if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(error)) { this.useStorage = false; - console.error('[AS]: get error:', error, keys, storageOptions.storeName); + console.error('[AS]: get error:', error, keys, storeName); } for(let i = 0, length = keys.length; i < length; ++i) { @@ -135,7 +177,7 @@ export default class AppStorage/* Storage ex return this.cache; } - public getFromCache(key: keyof Storage) { + public getFromCache(key: T) { return this.cache[key]; } @@ -143,12 +185,12 @@ export default class AppStorage/* Storage ex return this.cache[key] = value; } - public async get(key: keyof Storage, useCache = true): Promise { + public async get(key: T, useCache = true): Promise { if(this.cache.hasOwnProperty(key) && useCache) { return this.getFromCache(key); } else if(this.useStorage) { const r = this.getPromises.get(key); - if(r) return r; + if(r) return r as any; const p = deferredPromise(); this.getPromises.set(key, p); @@ -232,8 +274,21 @@ export default class AppStorage/* Storage ex storage.keysToDelete.clear(); storage.getPromises.forEach((deferred) => deferred.resolve()); storage.getPromises.clear(); + + if(storage.db === DATABASE_SESSION && 'localStorage' in self) { // * support legacy Webogram's localStorage + localStorage.clear(); + } + return storage.clear(); } else { + if(storage.db === DATABASE_SESSION && 'localStorage' in self) { // * support legacy Webogram's localStorage + for(const i in storage.cache) { + if(storage.cache[i] !== undefined) { + localStorage.setItem(i, JSON.stringify(storage.cache[i])); + } + } + } + return storage.set(storage.cache); } })).catch(noop); diff --git a/src/lib/storages/filters.ts b/src/lib/storages/filters.ts index 130c9760..e05c20fc 100644 --- a/src/lib/storages/filters.ts +++ b/src/lib/storages/filters.ts @@ -126,7 +126,7 @@ export default class FiltersStorage { } // exclude_read - if(pFlags.exclude_read && !dialog.unread_count) { + if(pFlags.exclude_read && !dialog.unread_count && !dialog.pFlags.unread_mark) { return false; }