Browse Source

[breaking change] separate session and state db

master
morethanwords 3 years ago
parent
commit
54ace14d23
  1. 11
      src/components/chat/bubbles.ts
  2. 6
      src/components/chat/chat.ts
  3. 2
      src/components/chat/contextMenu.ts
  4. 14
      src/components/sidebarLeft/tabs/addContact.ts
  5. 30
      src/config/database.ts
  6. 14
      src/config/databases/index.ts
  7. 17
      src/config/databases/session.ts
  8. 27
      src/config/databases/state.ts
  9. 2
      src/lang.ts
  10. 6
      src/lib/appManagers/appDraftsManager.ts
  11. 10
      src/lib/appManagers/appEmojiManager.ts
  12. 14
      src/lib/appManagers/appImManager.ts
  13. 4
      src/lib/appManagers/appNotificationsManager.ts
  14. 59
      src/lib/appManagers/appStateManager.ts
  15. 5
      src/lib/appManagers/appStickersManager.ts
  16. 31
      src/lib/idb.ts
  17. 6
      src/lib/langPack.ts
  18. 34
      src/lib/mtproto/apiManager.ts
  19. 5
      src/lib/mtproto/mtproto_config.ts
  20. 27
      src/lib/mtproto/mtprotoworker.ts
  21. 2
      src/lib/mtproto/timeManager.ts
  22. 2
      src/lib/rootScope.ts
  23. 36
      src/lib/sessionStorage.ts
  24. 23
      src/lib/stateStorage.ts
  25. 79
      src/lib/storage.ts
  26. 2
      src/lib/storages/filters.ts

11
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 { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager"; import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; 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 type Chat from "./chat";
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import { getObjectKeysAndSort } from "../../helpers/object"; import { getObjectKeysAndSort } from "../../helpers/object";
@ -147,7 +147,14 @@ export default class ChatBubbles {
[_ in MessageEntity['_']]: boolean [_ 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.chat.log.error('Bubbles construction');
this.listenerSetter = new ListenerSetter(); this.listenerSetter = new ListenerSetter();

6
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 { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager"; import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager";
import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager"; 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 EventListenerBase from "../../helpers/eventListenerBase";
import { logger, LogTypes } from "../../lib/logger"; import { logger, LogTypes } from "../../lib/logger";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
@ -82,7 +82,7 @@ export default class Chat extends EventListenerBase<{
public apiManager: ApiManagerProxy, public apiManager: ApiManagerProxy,
public appDraftsManager: AppDraftsManager, public appDraftsManager: AppDraftsManager,
public serverTimeManager: ServerTimeManager, public serverTimeManager: ServerTimeManager,
public storage: typeof sessionStorage, public storage: typeof stateStorage,
public appNotificationsManager: AppNotificationsManager, public appNotificationsManager: AppNotificationsManager,
public appEmojiManager: AppEmojiManager public appEmojiManager: AppEmojiManager
) { ) {
@ -170,7 +170,7 @@ export default class Chat extends EventListenerBase<{
this.initPeerId = peerId; this.initPeerId = peerId;
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager); 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.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.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); this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);

2
src/components/chat/contextMenu.ts

@ -225,7 +225,7 @@ export default class ChatContextMenu {
withSelection: true withSelection: true
}, { }, {
icon: 'link', icon: 'link',
text: 'CopyLink', text: 'MessageContext.CopyMessageLink1',
onClick: this.onCopyLinkClick, onClick: this.onCopyLinkClick,
verify: () => this.appPeersManager.isChannel(this.peerId) && !this.message.pFlags.is_outgoing verify: () => this.appPeersManager.isChannel(this.peerId) && !this.message.pFlags.is_outgoing
}, { }, {

14
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');
}
}

30
src/config/database.ts

@ -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<IDBStore, 'name'> & {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;

14
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<StoreName extends string> = Omit<IDBStore, 'name'> & {name: StoreName};
export type Database<StoreName extends string> = {
name: string,
version: number,
stores: DatabaseStore<StoreName>[]
};

17
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;

27
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;

2
src/lang.ts

@ -437,6 +437,7 @@ const lang = {
"NoResult": "No results", "NoResult": "No results",
"Updating": "Updating...", "Updating": "Updating...",
"Emoji": "Emoji", "Emoji": "Emoji",
"AddContactTitle": "Add Contact",
// * macos // * macos
"AccountSettings.Filters": "Chat Folders", "AccountSettings.Filters": "Chat Folders",
@ -664,6 +665,7 @@ const lang = {
"Message.Context.Select": "Select", "Message.Context.Select": "Select",
"Message.Context.Pin": "Pin", "Message.Context.Pin": "Pin",
"Message.Context.Unpin": "Unpin", "Message.Context.Unpin": "Unpin",
"MessageContext.CopyMessageLink1": "Copy Message Link",
"NewPoll.Anonymous": "Anonymous Voting", "NewPoll.Anonymous": "Anonymous Voting",
"NewPoll.Explanation.Placeholder": "Add a Comment (Optional)", "NewPoll.Explanation.Placeholder": "Add a Comment (Optional)",
"NewPoll.OptionsAddOption": "Add an Option", "NewPoll.OptionsAddOption": "Add an Option",

6
src/lib/appManagers/appDraftsManager.ts

@ -21,7 +21,7 @@ import { tsNow } from "../../helpers/date";
import { deepEqual } from "../../helpers/object"; import { deepEqual } from "../../helpers/object";
import { isObject } from "../mtproto/bin_utils"; import { isObject } from "../mtproto/bin_utils";
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import sessionStorage from "../sessionStorage"; import stateStorage from "../stateStorage";
export type MyDraftMessage = DraftMessage.draftMessage; export type MyDraftMessage = DraftMessage.draftMessage;
@ -30,7 +30,7 @@ export class AppDraftsManager {
private getAllDraftPromise: Promise<void> = null; private getAllDraftPromise: Promise<void> = null;
constructor() { constructor() {
sessionStorage.get('drafts').then(drafts => { stateStorage.get('drafts').then(drafts => {
this.drafts = drafts || {}; this.drafts = drafts || {};
}); });
@ -96,7 +96,7 @@ export class AppDraftsManager {
delete this.drafts[key]; delete this.drafts[key];
} }
sessionStorage.set({ stateStorage.set({
drafts: this.drafts drafts: this.drafts
}); });

10
src/lib/appManagers/appEmojiManager.ts

@ -11,7 +11,7 @@ import I18n from "../langPack";
import { isObject } from "../mtproto/bin_utils"; import { isObject } from "../mtproto/bin_utils";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import SearchIndex from "../searchIndex"; import SearchIndex from "../searchIndex";
import sessionStorage from "../sessionStorage"; import stateStorage from "../stateStorage";
import appStateManager from "./appStateManager"; import appStateManager from "./appStateManager";
type EmojiLangPack = { type EmojiLangPack = {
@ -45,7 +45,7 @@ export class AppEmojiManager {
private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>; private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>;
/* public getPopularEmoji() { /* public getPopularEmoji() {
return sessionStorage.get('emojis_popular').then(popEmojis => { return stateStorage.get('emojis_popular').then(popEmojis => {
var result = [] var result = []
if (popEmojis && popEmojis.length) { if (popEmojis && popEmojis.length) {
for (var i = 0, len = popEmojis.length; i < len; i++) { for (var i = 0, len = popEmojis.length; i < len; i++) {
@ -55,7 +55,7 @@ export class AppEmojiManager {
return return
} }
return sessionStorage.get('emojis_recent').then(recentEmojis => { return stateStorage.get('emojis_recent').then(recentEmojis => {
recentEmojis = recentEmojis || popular || [] recentEmojis = recentEmojis || popular || []
var shortcut var shortcut
var code var code
@ -111,7 +111,7 @@ export class AppEmojiManager {
} }
const storageKey: any = 'emojiKeywords_' + langCode; 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)) { if(!isObject(pack)) {
pack = {} as any; pack = {} as any;
} }
@ -135,7 +135,7 @@ export class AppEmojiManager {
packKeywords[keyword] = emoticons; packKeywords[keyword] = emoticons;
} }
sessionStorage.set({ stateStorage.set({
[storageKey]: pack [storageKey]: pack
}); });

14
src/lib/appManagers/appImManager.ts

@ -35,7 +35,7 @@ import lottieLoader from '../lottieLoader';
import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'; import useHeavyAnimationCheck, { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
import appDraftsManager from './appDraftsManager'; import appDraftsManager from './appDraftsManager';
import serverTimeManager from '../mtproto/serverTimeManager'; import serverTimeManager from '../mtproto/serverTimeManager';
import sessionStorage from '../sessionStorage'; import stateStorage from '../stateStorage';
import appDownloadManager from './appDownloadManager'; import appDownloadManager from './appDownloadManager';
import { AppStateManager } from './appStateManager'; import { AppStateManager } from './appStateManager';
import { MOUNT_CLASS_TO } from '../../config/debug'; import { MOUNT_CLASS_TO } from '../../config/debug';
@ -215,8 +215,8 @@ export class AppImManager {
popup.show(); popup.show();
}); });
sessionStorage.get('chatPositions').then((c) => { stateStorage.get('chatPositions').then((c) => {
sessionStorage.setToCache('chatPositions', c || {}); stateStorage.setToCache('chatPositions', c || {});
}); });
(window as any).showMaskedAlert = (element: HTMLAnchorElement, e: Event) => { (window as any).showMaskedAlert = (element: HTMLAnchorElement, e: Event) => {
@ -385,7 +385,7 @@ export class AppImManager {
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : ''); 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) { if(!(chat.bubbles.scrollable.getDistanceToEnd() <= 16 && chat.bubbles.scrollable.loadedAll.bottom) && Object.keys(chat.bubbles.bubbles).length) {
const position = { const position = {
mids: getObjectKeysAndSort(chat.bubbles.bubbles, 'desc'), mids: getObjectKeysAndSort(chat.bubbles.bubbles, 'desc'),
@ -401,7 +401,7 @@ export class AppImManager {
this.log('deleted chat position'); 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 key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
const cache = sessionStorage.getFromCache('chatPositions'); const cache = stateStorage.getFromCache('chatPositions');
return cache && cache[key]; return cache && cache[key];
} }
@ -820,7 +820,7 @@ export class AppImManager {
apiManager, apiManager,
appDraftsManager, appDraftsManager,
serverTimeManager, serverTimeManager,
sessionStorage, stateStorage,
appNotificationsManager, appNotificationsManager,
appEmojiManager appEmojiManager
); );

4
src/lib/appManagers/appNotificationsManager.ts

@ -20,7 +20,7 @@ import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySetting
import I18n from "../langPack"; import I18n from "../langPack";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage"; import stateStorage from "../stateStorage";
import apiUpdatesManager from "./apiUpdatesManager"; import apiUpdatesManager from "./apiUpdatesManager";
import appPeersManager from "./appPeersManager"; import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager"; import appStateManager from "./appStateManager";
@ -268,7 +268,7 @@ export class AppNotificationsManager {
} }
public updateLocalSettings = () => { 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) => { .then((updSettings) => {
this.settings.nodesktop = updSettings[0]; this.settings.nodesktop = updSettings[0];
this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1]; this.settings.volume = updSettings[1] === undefined ? 0.5 : updSettings[1];

59
src/lib/appManagers/appStateManager.ts

@ -12,7 +12,7 @@ import type FiltersStorage from '../storages/filters';
import type DialogsStorage from '../storages/dialogs'; import type DialogsStorage from '../storages/dialogs';
import EventListenerBase from '../../helpers/eventListenerBase'; import EventListenerBase from '../../helpers/eventListenerBase';
import rootScope from '../rootScope'; import rootScope from '../rootScope';
import sessionStorage from '../sessionStorage'; import stateStorage from '../stateStorage';
import { logger } from '../logger'; import { logger } from '../logger';
import { copy, setDeepProperty, validateInitObject } from '../../helpers/object'; import { copy, setDeepProperty, validateInitObject } from '../../helpers/object';
import App from '../../config/app'; import App from '../../config/app';
@ -20,6 +20,8 @@ import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import AppStorage from '../storage'; import AppStorage from '../storage';
import { Chat } from '../../layer'; import { Chat } from '../../layer';
import { isMobile } from '../../helpers/userAgent'; 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 = 24 * 60 * 60 * 1000; // 1 day
const REFRESH_EVERY_WEEK = 24 * 60 * 60 * 1000 * 7; // 7 days const REFRESH_EVERY_WEEK = 24 * 60 * 60 * 1000 * 7; // 7 days
@ -174,22 +176,14 @@ export class AppStateManager extends EventListenerBase<{
private singlePeerMap: Map<string, number> = new Map(); private singlePeerMap: Map<string, number> = new Map();
public storages = { public storages = {
users: new AppStorage<Record<number, User>>({ users: new AppStorage<Record<number, User>, typeof DATABASE_STATE>(DATABASE_STATE, 'users'),
storeName: 'users' chats: new AppStorage<Record<number, Chat>, typeof DATABASE_STATE>(DATABASE_STATE, 'chats'),
}), dialogs: new AppStorage<Record<number, Dialog>, typeof DATABASE_STATE>(DATABASE_STATE, 'dialogs')
chats: new AppStorage<Record<number, Chat>>({
storeName: 'chats'
}),
dialogs: new AppStorage<Record<number, Dialog>>({
storeName: 'dialogs'
})
}; };
public storagesResults: {[key in keyof AppStateManager['storages']]: any[]} = {} as any; public storagesResults: {[key in keyof AppStateManager['storages']]: any[]} = {} as any;
public storage = sessionStorage; public storage = stateStorage;
constructor() { constructor() {
super(); super();
@ -201,11 +195,10 @@ export class AppStateManager extends EventListenerBase<{
console.time('load state'); console.time('load state');
this.loaded = new Promise((resolve) => { this.loaded = new Promise((resolve) => {
const storagesKeys = Object.keys(this.storages) as Array<keyof AppStateManager['storages']>; const storagesKeys = Object.keys(this.storages) as Array<keyof AppStateManager['storages']>;
const storagesPromises = storagesKeys.map(key => this.storages[key].getAll()); const storagesPromises: Promise<any>[] = storagesKeys.map(key => this.storages[key].getAll());
const promises = ALL_KEYS const promises: Promise<any>[] = ALL_KEYS.map(key => stateStorage.get(key))
.concat('user_auth' as any) .concat(sessionStorage.get('user_auth'))
.map(key => sessionStorage.get(key))
.concat(storagesPromises); .concat(storagesPromises);
Promise.all(promises).then((arr) => { Promise.all(promises).then((arr) => {
@ -257,16 +250,40 @@ export class AppStateManager extends EventListenerBase<{
arr.splice(0, ALL_KEYS.length); arr.splice(0, ALL_KEYS.length);
// * Read auth // * 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) { if(auth) {
// ! Warning ! DON'T delete this // ! Warning ! DON'T delete this
state.authState = {_: 'authStateSignedIn'}; 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 // * Read storages
for(let i = 0, length = storagesKeys.length; i < length; ++i) { 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); arr.splice(0, storagesKeys.length);
@ -362,7 +379,7 @@ export class AppStateManager extends EventListenerBase<{
this.state[key] = value; this.state[key] = value;
} }
sessionStorage.set({ this.storage.set({
[key]: value [key]: value
}); });
} }

5
src/lib/appManagers/appStickersManager.ts

@ -12,13 +12,12 @@ import appDocsManager from './appDocsManager';
import AppStorage from '../storage'; import AppStorage from '../storage';
import { MOUNT_CLASS_TO } from '../../config/debug'; import { MOUNT_CLASS_TO } from '../../config/debug';
import { forEachReverse } from '../../helpers/array'; import { forEachReverse } from '../../helpers/array';
import DATABASE_STATE from '../../config/databases/state';
const CACHE_TIME = 3600e3; const CACHE_TIME = 3600e3;
export class AppStickersManager { export class AppStickersManager {
private storage = new AppStorage<Record<string, MessagesStickerSet>>({ private storage = new AppStorage<Record<string, MessagesStickerSet>, typeof DATABASE_STATE>(DATABASE_STATE, 'stickerSets');
storeName: 'stickerSets'
});
private getStickerSetPromises: {[setId: string]: Promise<MessagesStickerSet>} = {}; private getStickerSetPromises: {[setId: string]: Promise<MessagesStickerSet>} = {};
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {}; private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {};

31
src/lib/idb.ts

@ -9,7 +9,8 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * 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 { blobConstruct } from '../helpers/blob';
import { safeAssign } from '../helpers/object'; import { safeAssign } from '../helpers/object';
import { logger } from './logger'; import { logger } from './logger';
@ -37,27 +38,31 @@ export type IDBOptions = {
const DEBUG = false; const DEBUG = false;
export default class IDBStorage { export default class IDBStorage<T extends Database<any>> {
private static STORAGES: IDBStorage[] = []; private static STORAGES: IDBStorage<Database<any>>[] = [];
private openDbPromise: Promise<IDBDatabase>; private openDbPromise: Promise<IDBDatabase>;
private db: IDBDatabase; private db: IDBDatabase;
private storageIsAvailable = true; private storageIsAvailable = true;
private log: ReturnType<typeof logger>; private log: ReturnType<typeof logger>;
private name: string = Database.name; private name: string;
private version: number = Database.version; private version: number;
private stores: IDBStore[] = Database.stores; private stores: IDBStore[];
private storeName: string; private storeName: string;
constructor(options: IDBOptions) { constructor(db: T, storeName: typeof db['stores'][0]['name']) {
safeAssign(this, options); safeAssign(this, db);
this.storeName = storeName;
this.log = logger('IDB-' + this.storeName); this.log = logger('IDB-' + this.storeName);
this.openDatabase(true); this.openDatabase(true);
if(Modes.test) {
this.name += '_test';
}
IDBStorage.STORAGES.push(this); IDBStorage.STORAGES.push(this);
} }
@ -74,8 +79,11 @@ export default class IDBStorage {
public static deleteDatabase() { public static deleteDatabase() {
this.closeDatabases(); this.closeDatabases();
const storages = this.STORAGES;
const dbNames = Array.from(new Set(storages.map(storage => storage.name)));
const promises = dbNames.map(dbName => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const deleteRequest = indexedDB.deleteDatabase(Database.name); const deleteRequest = indexedDB.deleteDatabase(dbName);
deleteRequest.onerror = () => { deleteRequest.onerror = () => {
reject(); reject();
@ -85,6 +93,9 @@ export default class IDBStorage {
resolve(); resolve();
}; };
}); });
});
return Promise.all(promises);
} }
public isAvailable() { public isAvailable() {

6
src/lib/langPack.ts

@ -11,7 +11,7 @@ import type lang from "../lang";
import type langSign from "../langSign"; import type langSign from "../langSign";
import { LangPackDifference, LangPackString } from "../layer"; import { LangPackDifference, LangPackString } from "../layer";
import apiManager from "./mtproto/mtprotoworker"; import apiManager from "./mtproto/mtprotoworker";
import sessionStorage from "./sessionStorage"; import stateStorage from "./stateStorage";
import App from "../config/app"; import App from "../config/app";
import rootScope from "./rootScope"; import rootScope from "./rootScope";
@ -62,7 +62,7 @@ namespace I18n {
export function getCacheLangPack(): Promise<LangPackDifference> { export function getCacheLangPack(): Promise<LangPackDifference> {
if(cacheLangPackPromise) return cacheLangPackPromise; if(cacheLangPackPromise) return cacheLangPackPromise;
return cacheLangPackPromise = Promise.all([ return cacheLangPackPromise = Promise.all([
sessionStorage.get('langPack') as Promise<LangPackDifference>, stateStorage.get('langPack') as Promise<LangPackDifference>,
polyfillPromise polyfillPromise
]).then(([langPack]) => { ]).then(([langPack]) => {
if(!langPack/* || true */) { if(!langPack/* || true */) {
@ -177,7 +177,7 @@ namespace I18n {
export function saveLangPack(langPack: LangPackDifference) { export function saveLangPack(langPack: LangPackDifference) {
langPack.appVersion = App.langPackVersion; langPack.appVersion = App.langPackVersion;
return sessionStorage.set({langPack}).then(() => { return stateStorage.set({langPack}).then(() => {
applyLangPack(langPack); applyLangPack(langPack);
return langPack; return langPack;
}); });

34
src/lib/mtproto/apiManager.ts

@ -9,8 +9,8 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * https://github.com/zhukov/webogram/blob/master/LICENSE
*/ */
import type { UserAuth } from './mtproto_config';
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
import MTPNetworker, { MTMessage } from './networker'; import MTPNetworker, { MTMessage } from './networker';
import { isObject } from './bin_utils'; import { isObject } from './bin_utils';
import networkerFactory from './networkerFactory'; import networkerFactory from './networkerFactory';
@ -105,16 +105,38 @@ export class ApiManager {
} }
} */ } */
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 // mtpSetUserAuth
public setUserAuth(userId: number) { public async setUserAuth(userAuth: UserAuth) {
if(!userAuth.dcID) {
const baseDcId = await this.getBaseDcId();
userAuth.dcID = baseDcId;
}
sessionStorage.set({ sessionStorage.set({
user_auth: userId user_auth: userAuth
}); });
//this.telegramMeNotify(true); //this.telegramMeNotify(true);
/// #if !MTPROTO_WORKER /// #if !MTPROTO_WORKER
rootScope.broadcast('user_auth', userId); rootScope.broadcast('user_auth', userAuth);
/// #endif /// #endif
} }
@ -429,8 +451,8 @@ export class ApiManager {
if(dcId = (options.dcId || this.baseDcId)) { if(dcId = (options.dcId || this.baseDcId)) {
this.getNetworker(dcId, options).then(performRequest, rejectPromise); this.getNetworker(dcId, options).then(performRequest, rejectPromise);
} else { } else {
sessionStorage.get('dc').then((baseDcId) => { this.getBaseDcId().then(baseDcId => {
this.getNetworker(this.baseDcId = dcId = baseDcId || App.baseDcId, options).then(performRequest, rejectPromise); this.getNetworker(dcId = baseDcId, options).then(performRequest, rejectPromise);
}); });
} }

5
src/lib/mtproto/mtproto_config.ts

@ -4,6 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * 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; export const REPLIES_PEER_ID = 1271266957;

27
src/lib/mtproto/mtprotoworker.ts

@ -4,11 +4,12 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * 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 MTProtoWorker from 'worker-loader!./mtproto.worker';
//import './mtproto.worker'; //import './mtproto.worker';
import { isObject } from '../../helpers/object'; import { isObject } from '../../helpers/object';
import type { MethodDeclMap } from '../../layer';
import type { InvokeApiOptions } from '../../types';
import CryptoWorkerMethods from '../crypto/crypto_methods'; import CryptoWorkerMethods from '../crypto/crypto_methods';
import { logger } from '../logger'; import { logger } from '../logger';
import rootScope from '../rootScope'; import rootScope from '../rootScope';
@ -93,6 +94,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
this.addTaskListener('clear', () => { this.addTaskListener('clear', () => {
const promise = IDBStorage.deleteDatabase(); const promise = IDBStorage.deleteDatabase();
localStorage.clear(); // * clear legacy Webogram's localStorage
promise.finally(() => { promise.finally(() => {
location.reload(); 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 /// #if !MTPROTO_SW
this.registerWorker(); this.registerWorker();
/// #endif /// #endif
@ -457,7 +474,11 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
return this.performTaskWorker('setQueueId', queueId); 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); rootScope.broadcast('user_auth', userAuth);
return this.performTaskWorker('setUserAuth', userAuth); return this.performTaskWorker('setUserAuth', userAuth);
} }

2
src/lib/mtproto/timeManager.ts

@ -26,7 +26,7 @@ export class TimeManager {
private timeOffset = 0; private timeOffset = 0;
constructor() { constructor() {
sessionStorage.get('server_time_offset').then((to: any) => { sessionStorage.get('server_time_offset').then((to) => {
if(to) { if(to) {
this.timeOffset = to; this.timeOffset = to;
} }

2
src/lib/rootScope.ts

@ -141,7 +141,7 @@ export class RootScope extends EventListenerBase<{
}); });
this.on('user_auth', (e) => { this.on('user_auth', (e) => {
this.myId = e; this.myId = e.id;
}); });
this.on('connection_status_change', (e) => { this.on('connection_status_change', (e) => {

36
src/lib/sessionStorage.ts

@ -4,33 +4,27 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * 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 { AppInstance } from './mtproto/singleInstance';
import type { UserAuth } from './mtproto/mtproto_config';
import { MOUNT_CLASS_TO } from '../config/debug'; import { MOUNT_CLASS_TO } from '../config/debug';
import { LangPackDifference } from '../layer';
import AppStorage from './storage'; import AppStorage from './storage';
import DATABASE_SESSION from '../config/databases/session';
const sessionStorage = new AppStorage<{ const sessionStorage = new AppStorage<{
dc: number, dc: number,
user_auth: number, user_auth: UserAuth,
dc1_auth_key: any, dc1_auth_key: string,
dc2_auth_key: any, dc2_auth_key: string,
dc3_auth_key: any, dc3_auth_key: string,
dc4_auth_key: any, dc4_auth_key: string,
dc5_auth_key: any, dc5_auth_key: string,
max_seen_msg: number, dc1_server_salt: string,
dc2_server_salt: string,
dc3_server_salt: string,
dc4_server_salt: string,
dc5_server_salt: string,
server_time_offset: number, server_time_offset: number,
xt_instance: AppInstance, xt_instance: AppInstance
}, typeof DATABASE_SESSION>(DATABASE_SESSION, 'session');
chatPositions: {
[peerId_threadId: string]: ChatSavedPosition
},
langPack: LangPackDifference,
drafts: AppDraftsManager['drafts']
} & State>({
storeName: 'session'
});
MOUNT_CLASS_TO.appStorage = sessionStorage; MOUNT_CLASS_TO.appStorage = sessionStorage;
export default sessionStorage; export default sessionStorage;

23
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;

79
src/lib/storage.ts

@ -9,16 +9,35 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * 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 { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { throttle } from "../helpers/schedulers"; import { throttle } from "../helpers/schedulers";
import IDBStorage, { IDBOptions } from "./idb"; import { WorkerTaskTemplate } from "../types";
import IDBStorage from "./idb";
function noop() {} function noop() {}
export default class AppStorage<Storage extends Record<string, any>/* Storage extends {[name: string]: any} *//* Storage extends Record<string, any> */> { export interface LocalStorageProxySetTask extends WorkerTaskTemplate {
private static STORAGES: AppStorage<any>[] = []; type: 'localStorageProxy',
private storage: IDBStorage;//new CacheStorageController('session'); payload: {
type: 'set',
keys: string[],
values: any[]
}
};
export interface LocalStorageProxyDeleteTask extends WorkerTaskTemplate {
type: 'localStorageProxy',
payload: {
type: 'delete',
keys: string[]
}
};
export default class AppStorage<Storage extends Record<string, any>, T extends Database<any>/* Storage extends {[name: string]: any} *//* Storage extends Record<string, any> */> {
private static STORAGES: AppStorage<any, Database<any>>[] = [];
private storage: IDBStorage<T>;//new CacheStorageController('session');
//private cache: Partial<{[key: string]: Storage[typeof key]}> = {}; //private cache: Partial<{[key: string]: Storage[typeof key]}> = {};
private cache: Partial<Storage> = {}; private cache: Partial<Storage> = {};
@ -35,8 +54,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
private deleteThrottled: () => void; private deleteThrottled: () => void;
private deleteDeferred = deferredPromise<void>(); private deleteDeferred = deferredPromise<void>();
constructor(storageOptions: Omit<IDBOptions, 'storeName' | 'stores'> & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) { constructor(private db: T, storeName: typeof db['stores'][number]['name']) {
this.storage = new IDBStorage(storageOptions); this.storage = new IDBStorage<T>(db, storeName);
AppStorage.STORAGES.push(this); AppStorage.STORAGES.push(this);
@ -53,7 +72,20 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
//console.log('setItem: will set', key/* , value */); //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.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(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 */); //console.log('setItem: have set', key/* , value */);
} catch(e) { } catch(e) {
//this.useCS = false; //this.useCS = false;
@ -78,6 +110,16 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
set.clear(); set.clear();
try { 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); await this.storage.delete(keys);
} catch(e) { } catch(e) {
console.error('[AS]: delete error:', e, keys); console.error('[AS]: delete error:', e, keys);
@ -107,7 +149,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
}, (error) => { }, (error) => {
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(error)) { if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(error)) {
this.useStorage = false; 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) { for(let i = 0, length = keys.length; i < length; ++i) {
@ -135,7 +177,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
return this.cache; return this.cache;
} }
public getFromCache(key: keyof Storage) { public getFromCache<T extends keyof Storage>(key: T) {
return this.cache[key]; return this.cache[key];
} }
@ -143,12 +185,12 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
return this.cache[key] = value; return this.cache[key] = value;
} }
public async get(key: keyof Storage, useCache = true): Promise<Storage[typeof key]> { public async get<T extends keyof Storage>(key: T, useCache = true): Promise<Storage[T]> {
if(this.cache.hasOwnProperty(key) && useCache) { if(this.cache.hasOwnProperty(key) && useCache) {
return this.getFromCache(key); return this.getFromCache(key);
} else if(this.useStorage) { } else if(this.useStorage) {
const r = this.getPromises.get(key); const r = this.getPromises.get(key);
if(r) return r; if(r) return r as any;
const p = deferredPromise<Storage[typeof key]>(); const p = deferredPromise<Storage[typeof key]>();
this.getPromises.set(key, p); this.getPromises.set(key, p);
@ -232,8 +274,21 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
storage.keysToDelete.clear(); storage.keysToDelete.clear();
storage.getPromises.forEach((deferred) => deferred.resolve()); storage.getPromises.forEach((deferred) => deferred.resolve());
storage.getPromises.clear(); storage.getPromises.clear();
if(storage.db === DATABASE_SESSION && 'localStorage' in self) { // * support legacy Webogram's localStorage
localStorage.clear();
}
return storage.clear(); return storage.clear();
} else { } 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); return storage.set(storage.cache);
} }
})).catch(noop); })).catch(noop);

2
src/lib/storages/filters.ts

@ -126,7 +126,7 @@ export default class FiltersStorage {
} }
// exclude_read // exclude_read
if(pFlags.exclude_read && !dialog.unread_count) { if(pFlags.exclude_read && !dialog.unread_count && !dialog.pFlags.unread_mark) {
return false; return false;
} }

Loading…
Cancel
Save