beta state fix
This commit is contained in:
parent
d132ef290a
commit
64efb48923
@ -132,12 +132,13 @@ export function setDeepProperty(object: any, key: string, value: any) {
|
|||||||
getDeepProperty(object, splitted.slice(0, -1).join('.'))[splitted.pop()] = value;
|
getDeepProperty(object, splitted.slice(0, -1).join('.'))[splitted.pop()] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateInitObject(initObject: any, currentObject: any) {
|
export function validateInitObject(initObject: any, currentObject: any, onReplace?: (key: string) => void, previousKey?: string) {
|
||||||
for(const i in initObject) {
|
for(const key in initObject) {
|
||||||
if(typeof(currentObject[i]) !== typeof(initObject[i])) {
|
if(typeof(currentObject[key]) !== typeof(initObject[key])) {
|
||||||
currentObject[i] = copy(initObject[i]);
|
currentObject[key] = copy(initObject[key]);
|
||||||
} else if(isObject(initObject[i])) {
|
onReplace && onReplace(previousKey || key);
|
||||||
validateInitObject(initObject[i], currentObject[i]);
|
} else if(isObject(initObject[key])) {
|
||||||
|
validateInitObject(initObject[key], currentObject[key], onReplace, previousKey || key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import apiManagerProxy from "../mtproto/mtprotoworker";
|
|||||||
import apiManager from '../mtproto/mtprotoworker';
|
import apiManager from '../mtproto/mtprotoworker';
|
||||||
import { RichTextProcessor } from "../richtextprocessor";
|
import { RichTextProcessor } from "../richtextprocessor";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
import AppStorage from "../storage";
|
|
||||||
import apiUpdatesManager from "./apiUpdatesManager";
|
import apiUpdatesManager from "./apiUpdatesManager";
|
||||||
import appMessagesManager from "./appMessagesManager";
|
import appMessagesManager from "./appMessagesManager";
|
||||||
import appPeersManager from "./appPeersManager";
|
import appPeersManager from "./appPeersManager";
|
||||||
@ -33,9 +32,7 @@ export type ChatRights = keyof ChatBannedRights['pFlags'] | keyof ChatAdminRight
|
|||||||
export type UserTyping = Partial<{userId: number, action: SendMessageAction, timeout: number}>;
|
export type UserTyping = Partial<{userId: number, action: SendMessageAction, timeout: number}>;
|
||||||
|
|
||||||
export class AppChatsManager {
|
export class AppChatsManager {
|
||||||
private storage = new AppStorage<Record<number, Chat>>({
|
private storage = appStateManager.storages.chats;
|
||||||
storeName: 'chats'
|
|
||||||
});
|
|
||||||
|
|
||||||
private chats: {[id: number]: Chat.channel | Chat.chat | any} = {};
|
private chats: {[id: number]: Chat.channel | Chat.chat | any} = {};
|
||||||
//private usernames: any = {};
|
//private usernames: any = {};
|
||||||
@ -74,20 +71,16 @@ export class AppChatsManager {
|
|||||||
updateChannelUserTyping: this.onUpdateUserTyping
|
updateChannelUserTyping: this.onUpdateUserTyping
|
||||||
});
|
});
|
||||||
|
|
||||||
let storageChats: Chat[];
|
appStateManager.getState().then((state) => {
|
||||||
const getStorageChatsPromise = this.storage.getAll().then(chats => {
|
const chats = appStateManager.storagesResults.chats;
|
||||||
storageChats = chats as any;
|
if(chats.length) {
|
||||||
});
|
|
||||||
|
|
||||||
appStateManager.addLoadPromise(getStorageChatsPromise).then((state) => {
|
|
||||||
if(storageChats.length) {
|
|
||||||
this.chats = {};
|
this.chats = {};
|
||||||
for(let i = 0, length = storageChats.length; i < length; ++i) {
|
for(let i = 0, length = chats.length; i < length; ++i) {
|
||||||
const user = storageChats[i];
|
const chat = chats[i];
|
||||||
this.chats[user.id] = user;
|
if(chat) {
|
||||||
|
this.chats[chat.id] = chat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if(state.chats) {
|
|
||||||
this.chats = state.chats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
appStateManager.addEventListener('peerNeeded', (peerId: number) => {
|
appStateManager.addEventListener('peerNeeded', (peerId: number) => {
|
||||||
|
@ -500,13 +500,15 @@ export class AppDialogsManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(state.dialogs?.length) {
|
if(appStateManager.storagesResults.dialogs.length) {
|
||||||
appDraftsManager.getAllDrafts();
|
appDraftsManager.getAllDrafts();
|
||||||
appDraftsManager.addMissedDialogs();
|
appDraftsManager.addMissedDialogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.loadDialogs();
|
return this.loadDialogs();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
return;
|
||||||
|
|
||||||
const isLoadedMain = appMessagesManager.dialogsStorage.isDialogsLoaded(0);
|
const isLoadedMain = appMessagesManager.dialogsStorage.isDialogsLoaded(0);
|
||||||
const isLoadedArchive = appMessagesManager.dialogsStorage.isDialogsLoaded(1);
|
const isLoadedArchive = appMessagesManager.dialogsStorage.isDialogsLoaded(1);
|
||||||
const wasLoaded = isLoadedMain || isLoadedArchive;
|
const wasLoaded = isLoadedMain || isLoadedArchive;
|
||||||
|
@ -19,9 +19,9 @@ import { MessageEntity, DraftMessage, MessagesSaveDraft } from "../../layer";
|
|||||||
import apiManager from "../mtproto/mtprotoworker";
|
import apiManager from "../mtproto/mtprotoworker";
|
||||||
import { tsNow } from "../../helpers/date";
|
import { tsNow } from "../../helpers/date";
|
||||||
import { deepEqual } from "../../helpers/object";
|
import { deepEqual } from "../../helpers/object";
|
||||||
import appStateManager from "./appStateManager";
|
|
||||||
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";
|
||||||
|
|
||||||
export type MyDraftMessage = DraftMessage.draftMessage;
|
export type MyDraftMessage = DraftMessage.draftMessage;
|
||||||
|
|
||||||
@ -30,8 +30,8 @@ export class AppDraftsManager {
|
|||||||
private getAllDraftPromise: Promise<void> = null;
|
private getAllDraftPromise: Promise<void> = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
appStateManager.getState().then(state => {
|
sessionStorage.get('drafts').then(drafts => {
|
||||||
this.drafts = state.drafts;
|
this.drafts = drafts || {};
|
||||||
});
|
});
|
||||||
|
|
||||||
rootScope.addMultipleEventsListeners({
|
rootScope.addMultipleEventsListeners({
|
||||||
@ -96,7 +96,9 @@ export class AppDraftsManager {
|
|||||||
delete this.drafts[key];
|
delete this.drafts[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
appStateManager.pushToState('drafts', this.drafts);
|
sessionStorage.set({
|
||||||
|
drafts: this.drafts
|
||||||
|
});
|
||||||
|
|
||||||
if(options.notify) {
|
if(options.notify) {
|
||||||
// console.warn(dT(), 'save draft', peerId, apiDraft, options)
|
// console.warn(dT(), 'save draft', peerId, apiDraft, options)
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import type { Dialog } from './appMessagesManager';
|
import type { Dialog } from './appMessagesManager';
|
||||||
import type { UserAuth } from '../mtproto/mtproto_config';
|
import type { UserAuth } from '../mtproto/mtproto_config';
|
||||||
import type { AppUsersManager } from './appUsersManager';
|
import type { AppUsersManager, User } from './appUsersManager';
|
||||||
import type { AppChatsManager } from './appChatsManager';
|
import type { AppChatsManager } from './appChatsManager';
|
||||||
import type { AuthState } from '../../types';
|
import type { AuthState } from '../../types';
|
||||||
import type FiltersStorage from '../storages/filters';
|
import type FiltersStorage from '../storages/filters';
|
||||||
@ -17,9 +17,10 @@ import rootScope from '../rootScope';
|
|||||||
import sessionStorage from '../sessionStorage';
|
import sessionStorage from '../sessionStorage';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { copy, setDeepProperty, validateInitObject } from '../../helpers/object';
|
import { copy, setDeepProperty, validateInitObject } from '../../helpers/object';
|
||||||
import { getHeavyAnimationPromise } from '../../hooks/useHeavyAnimationCheck';
|
|
||||||
import App from '../../config/app';
|
import App from '../../config/app';
|
||||||
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
|
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
|
||||||
|
import AppStorage from '../storage';
|
||||||
|
import { Chat } from '../../layer';
|
||||||
|
|
||||||
const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day
|
const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day
|
||||||
const STATE_VERSION = App.version;
|
const STATE_VERSION = App.version;
|
||||||
@ -37,12 +38,9 @@ export type Theme = {
|
|||||||
background: Background
|
background: Background
|
||||||
};
|
};
|
||||||
|
|
||||||
export type State = Partial<{
|
export type State = {
|
||||||
dialogs: Dialog[],
|
|
||||||
allDialogsLoaded: DialogsStorage['allDialogsLoaded'],
|
allDialogsLoaded: DialogsStorage['allDialogsLoaded'],
|
||||||
chats: {[peerId: string]: ReturnType<AppChatsManager['getChat']>},
|
pinnedOrders: DialogsStorage['pinnedOrders'],
|
||||||
users: {[peerId: string]: ReturnType<AppUsersManager['getUser']>},
|
|
||||||
messages: any[],
|
|
||||||
contactsList: number[],
|
contactsList: number[],
|
||||||
updates: Partial<{
|
updates: Partial<{
|
||||||
seq: number,
|
seq: number,
|
||||||
@ -84,16 +82,12 @@ export type State = Partial<{
|
|||||||
},
|
},
|
||||||
nightTheme?: boolean, // ! DEPRECATED
|
nightTheme?: boolean, // ! DEPRECATED
|
||||||
},
|
},
|
||||||
keepSigned: boolean,
|
keepSigned: boolean
|
||||||
drafts: AppDraftsManager['drafts']
|
};
|
||||||
}>;
|
|
||||||
|
|
||||||
export const STATE_INIT: State = {
|
export const STATE_INIT: State = {
|
||||||
dialogs: [],
|
|
||||||
allDialogsLoaded: {},
|
allDialogsLoaded: {},
|
||||||
chats: {},
|
pinnedOrders: {},
|
||||||
users: {},
|
|
||||||
messages: [],
|
|
||||||
contactsList: [],
|
contactsList: [],
|
||||||
updates: {},
|
updates: {},
|
||||||
filters: {},
|
filters: {},
|
||||||
@ -147,14 +141,13 @@ export const STATE_INIT: State = {
|
|||||||
sound: false
|
sound: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keepSigned: true,
|
keepSigned: true
|
||||||
drafts: {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ALL_KEYS = Object.keys(STATE_INIT) as any as Array<keyof State>;
|
const ALL_KEYS = Object.keys(STATE_INIT) as any as Array<keyof State>;
|
||||||
|
|
||||||
const REFRESH_KEYS = ['dialogs', 'allDialogsLoaded', 'messages', 'contactsList', 'stateCreatedTime',
|
const REFRESH_KEYS = ['dialogs', 'allDialogsLoaded', 'messages', 'contactsList', 'stateCreatedTime',
|
||||||
'updates', 'maxSeenMsgId', 'filters', 'topPeers'] as any as Array<keyof State>;
|
'updates', 'maxSeenMsgId', 'filters', 'topPeers', 'pinnedOrders'] as any as Array<keyof State>;
|
||||||
|
|
||||||
export class AppStateManager extends EventListenerBase<{
|
export class AppStateManager extends EventListenerBase<{
|
||||||
save: (state: State) => Promise<void>,
|
save: (state: State) => Promise<void>,
|
||||||
@ -163,8 +156,6 @@ export class AppStateManager extends EventListenerBase<{
|
|||||||
}> {
|
}> {
|
||||||
public static STATE_INIT = STATE_INIT;
|
public static STATE_INIT = STATE_INIT;
|
||||||
private loaded: Promise<State>;
|
private loaded: Promise<State>;
|
||||||
private loadPromises: Promise<any>[] = [];
|
|
||||||
private loadAllPromise: Promise<any>;
|
|
||||||
private log = logger('STATE'/* , LogLevels.error */);
|
private log = logger('STATE'/* , LogLevels.error */);
|
||||||
|
|
||||||
private state: State;
|
private state: State;
|
||||||
@ -172,73 +163,147 @@ export class AppStateManager extends EventListenerBase<{
|
|||||||
private neededPeers: Map<number, Set<string>> = new Map();
|
private neededPeers: Map<number, Set<string>> = new Map();
|
||||||
private singlePeerMap: Map<string, number> = new Map();
|
private singlePeerMap: Map<string, number> = new Map();
|
||||||
|
|
||||||
|
public storages = {
|
||||||
|
users: new AppStorage<Record<number, User>>({
|
||||||
|
storeName: 'users'
|
||||||
|
}),
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.loadSavedState();
|
this.loadSavedState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadSavedState(): Promise<State> {
|
public loadSavedState(): Promise<State> {
|
||||||
if(this.loadAllPromise) return this.loadAllPromise;
|
if(this.loaded) return this.loaded;
|
||||||
//console.time('load state');
|
console.time('load state');
|
||||||
this.loaded = new Promise((resolve) => {
|
this.loaded = new Promise((resolve) => {
|
||||||
Promise.all(ALL_KEYS.concat('user_auth' as any).map(key => sessionStorage.get(key))).then((arr) => {
|
const storagesKeys = Object.keys(this.storages) as Array<keyof AppStateManager['storages']>;
|
||||||
let state: State = {};
|
const storagesPromises = storagesKeys.map(key => this.storages[key].getAll());
|
||||||
|
|
||||||
|
const promises = ALL_KEYS
|
||||||
|
.concat('user_auth' as any)
|
||||||
|
.map(key => sessionStorage.get(key))
|
||||||
|
.concat(storagesPromises);
|
||||||
|
|
||||||
|
Promise.all(promises).then((arr) => {
|
||||||
|
/* const self = this;
|
||||||
|
const skipHandleKeys = new Set(['isProxy', 'filters', 'drafts']);
|
||||||
|
const getHandler = (path?: string) => {
|
||||||
|
return {
|
||||||
|
get(target: any, key: any) {
|
||||||
|
if(key === 'isProxy') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prop = target[key];
|
||||||
|
|
||||||
|
if(prop !== undefined && !skipHandleKeys.has(key) && !prop.isProxy && typeof(prop) === 'object') {
|
||||||
|
target[key] = new Proxy(prop, getHandler(path || key));
|
||||||
|
return target[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop;
|
||||||
|
},
|
||||||
|
set(target: any, key: any, value: any) {
|
||||||
|
console.log('Setting', target, `.${key} to equal`, value, path);
|
||||||
|
|
||||||
|
target[key] = value;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
self.pushToState(path || key, path ? self.state[path] : value, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}; */
|
||||||
|
|
||||||
|
let state: State = this.state = {} as any;
|
||||||
|
|
||||||
// ! then can't store false values
|
// ! then can't store false values
|
||||||
ALL_KEYS.forEach((key, idx) => {
|
for(let i = 0, length = ALL_KEYS.length; i < length; ++i) {
|
||||||
const value = arr[idx];
|
const key = ALL_KEYS[i];
|
||||||
|
const value = arr[i];
|
||||||
if(value !== undefined) {
|
if(value !== undefined) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
state[key] = value;
|
state[key] = value;
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
this.pushToState(key, copy(STATE_INIT[key]));
|
||||||
state[key] = copy(STATE_INIT[key]);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
arr.splice(0, ALL_KEYS.length);
|
||||||
|
|
||||||
|
// * Read auth
|
||||||
|
const auth: UserAuth = arr.shift() as any;
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// * Read storages
|
||||||
|
for(let i = 0, length = storagesKeys.length; i < length; ++i) {
|
||||||
|
this.storagesResults[storagesKeys[i]] = arr[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.splice(0, storagesKeys.length);
|
||||||
|
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
/* if(state.version !== STATE_VERSION) {
|
if((state.stateCreatedTime + REFRESH_EVERY) < time) {
|
||||||
state = copy(STATE_INIT);
|
|
||||||
} else */if((state.stateCreatedTime + REFRESH_EVERY) < time/* || true *//* && false */) {
|
|
||||||
if(DEBUG) {
|
if(DEBUG) {
|
||||||
this.log('will refresh state', state.stateCreatedTime, time);
|
this.log('will refresh state', state.stateCreatedTime, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
REFRESH_KEYS.forEach(key => {
|
REFRESH_KEYS.forEach(key => {
|
||||||
|
this.pushToState(key, copy(STATE_INIT[key]));
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
state[key] = copy(STATE_INIT[key]);
|
const s = this.storagesResults[key];
|
||||||
|
if(s && s.length) {
|
||||||
|
s.length = 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const users: typeof state['users'] = {}, chats: typeof state['chats'] = {};
|
|
||||||
if(state.recentSearch?.length) {
|
|
||||||
state.recentSearch.forEach(peerId => {
|
|
||||||
if(peerId < 0) chats[peerId] = state.chats[peerId];
|
|
||||||
else users[peerId] = state.users[peerId];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
state.users = users;
|
|
||||||
state.chats = chats;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//state = this.state = new Proxy(state, getHandler());
|
||||||
|
|
||||||
|
// * support old version
|
||||||
if(!state.settings.hasOwnProperty('themes') && state.settings.background) {
|
if(!state.settings.hasOwnProperty('themes') && state.settings.background) {
|
||||||
const theme = STATE_INIT.settings.themes.find(t => t.name === STATE_INIT.settings.theme);
|
const theme = STATE_INIT.settings.themes.find(t => t.name === STATE_INIT.settings.theme);
|
||||||
if(theme) {
|
if(theme) {
|
||||||
theme.background = copy(state.settings.background);
|
state.settings.themes.find(t => t.name === theme.name).background = copy(state.settings.background);
|
||||||
|
this.pushToState('settings', state.settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// * support old version
|
||||||
if(!state.settings.hasOwnProperty('theme') && state.settings.hasOwnProperty('nightTheme')) {
|
if(!state.settings.hasOwnProperty('theme') && state.settings.hasOwnProperty('nightTheme')) {
|
||||||
state.settings.theme = state.settings.nightTheme ? 'night' : 'day';
|
state.settings.theme = state.settings.nightTheme ? 'night' : 'day';
|
||||||
|
this.pushToState('settings', state.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateInitObject(STATE_INIT, state);
|
validateInitObject(STATE_INIT, state, (missingKey) => {
|
||||||
|
// @ts-ignore
|
||||||
|
this.pushToState(missingKey, state[missingKey]);
|
||||||
|
});
|
||||||
|
|
||||||
this.state = state;
|
if(state.version !== STATE_VERSION) {
|
||||||
this.state.version = STATE_VERSION;
|
this.pushToState('version', STATE_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
// ! probably there is better place for it
|
// ! probably there is better place for it
|
||||||
rootScope.settings = this.state.settings;
|
rootScope.settings = state.settings;
|
||||||
|
|
||||||
if(DEBUG) {
|
if(DEBUG) {
|
||||||
this.log('state res', state, copy(state));
|
this.log('state res', state, copy(state));
|
||||||
@ -246,29 +311,12 @@ export class AppStateManager extends EventListenerBase<{
|
|||||||
|
|
||||||
//return resolve();
|
//return resolve();
|
||||||
|
|
||||||
const auth: UserAuth = arr[arr.length - 1] as any;
|
console.timeEnd('load state');
|
||||||
if(auth) {
|
resolve(state);
|
||||||
// ! Warning ! DON'T delete this
|
|
||||||
this.state.authState = {_: 'authStateSignedIn'};
|
|
||||||
rootScope.broadcast('user_auth', typeof(auth) !== 'number' ? (auth as any).id : auth); // * support old version
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.timeEnd('load state');
|
|
||||||
resolve(this.state);
|
|
||||||
}).catch(resolve);
|
}).catch(resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.addLoadPromise(this.loaded);
|
return this.loaded;
|
||||||
}
|
|
||||||
|
|
||||||
public addLoadPromise(promise: Promise<any>) {
|
|
||||||
if(!this.loaded) {
|
|
||||||
return this.loadSavedState();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loadPromises.push(promise);
|
|
||||||
return this.loadAllPromise = Promise.all(this.loadPromises)
|
|
||||||
.then(() => this.state, () => this.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getState() {
|
public getState() {
|
||||||
@ -284,20 +332,16 @@ export class AppStateManager extends EventListenerBase<{
|
|||||||
this.pushToState(first, this.state[first]);
|
this.pushToState(first, this.state[first]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public pushToState<T extends keyof State>(key: T, value: State[T]) {
|
public pushToState<T extends keyof State>(key: T, value: State[T], direct = true) {
|
||||||
this.state[key] = value;
|
if(direct) {
|
||||||
|
this.state[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
sessionStorage.set({
|
sessionStorage.set({
|
||||||
[key]: value
|
[key]: value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setPeer(peerId: number, peer: any) {
|
|
||||||
const container = peerId > 0 ? this.state.users : this.state.chats;
|
|
||||||
if(container.hasOwnProperty(peerId)) return;
|
|
||||||
container[peerId] = peer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public requestPeer(peerId: number, type: string, limit?: number) {
|
public requestPeer(peerId: number, type: string, limit?: number) {
|
||||||
let set = this.neededPeers.get(peerId);
|
let set = this.neededPeers.get(peerId);
|
||||||
if(set && set.has(type)) {
|
if(set && set.has(type)) {
|
||||||
@ -351,4 +395,4 @@ export class AppStateManager extends EventListenerBase<{
|
|||||||
|
|
||||||
const appStateManager = new AppStateManager();
|
const appStateManager = new AppStateManager();
|
||||||
MOUNT_CLASS_TO.appStateManager = appStateManager;
|
MOUNT_CLASS_TO.appStateManager = appStateManager;
|
||||||
export default appStateManager;
|
export default appStateManager;
|
||||||
|
@ -22,7 +22,6 @@ import serverTimeManager from "../mtproto/serverTimeManager";
|
|||||||
import { RichTextProcessor } from "../richtextprocessor";
|
import { RichTextProcessor } from "../richtextprocessor";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
import searchIndexManager from "../searchIndexManager";
|
import searchIndexManager from "../searchIndexManager";
|
||||||
import AppStorage from "../storage";
|
|
||||||
import apiUpdatesManager from "./apiUpdatesManager";
|
import apiUpdatesManager from "./apiUpdatesManager";
|
||||||
import appChatsManager from "./appChatsManager";
|
import appChatsManager from "./appChatsManager";
|
||||||
import appPeersManager from "./appPeersManager";
|
import appPeersManager from "./appPeersManager";
|
||||||
@ -33,9 +32,7 @@ import appStateManager from "./appStateManager";
|
|||||||
export type User = MTUser.user;
|
export type User = MTUser.user;
|
||||||
|
|
||||||
export class AppUsersManager {
|
export class AppUsersManager {
|
||||||
private storage = new AppStorage<Record<number, User>>({
|
private storage = appStateManager.storages.users;
|
||||||
storeName: 'users'
|
|
||||||
});
|
|
||||||
|
|
||||||
private users: {[userId: number]: User} = {};
|
private users: {[userId: number]: User} = {};
|
||||||
private usernames: {[username: string]: number} = {};
|
private usernames: {[username: string]: number} = {};
|
||||||
@ -113,20 +110,16 @@ export class AppUsersManager {
|
|||||||
searchIndexManager.indexObject(userId, this.getUserSearchText(userId), this.contactsIndex);
|
searchIndexManager.indexObject(userId, this.getUserSearchText(userId), this.contactsIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
let storageUsers: User[];
|
appStateManager.getState().then((state) => {
|
||||||
const getStorageUsersPromise = this.storage.getAll().then(users => {
|
const users = appStateManager.storagesResults.users;
|
||||||
storageUsers = users as any;
|
if(users.length) {
|
||||||
});
|
|
||||||
|
|
||||||
appStateManager.addLoadPromise(getStorageUsersPromise).then((state) => {
|
|
||||||
if(storageUsers.length) {
|
|
||||||
this.users = {};
|
this.users = {};
|
||||||
for(let i = 0, length = storageUsers.length; i < length; ++i) {
|
for(let i = 0, length = users.length; i < length; ++i) {
|
||||||
const user = storageUsers[i];
|
const user = users[i];
|
||||||
this.users[user.id] = user;
|
if(user) {
|
||||||
|
this.users[user.id] = user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if(state.users) {
|
|
||||||
this.users = state.users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const contactsList = state.contactsList;
|
const contactsList = state.contactsList;
|
||||||
|
299
src/lib/idb.ts
299
src/lib/idb.ts
@ -165,140 +165,39 @@ export default class IDBStorage {
|
|||||||
|
|
||||||
public delete(entryName: string | string[]): Promise<void> {
|
public delete(entryName: string | string[]): Promise<void> {
|
||||||
//return Promise.resolve();
|
//return Promise.resolve();
|
||||||
return this.openDatabase().then((db) => {
|
if(!Array.isArray(entryName)) {
|
||||||
return new Promise((resolve, reject) => {
|
entryName = [].concat(entryName);
|
||||||
try {
|
}
|
||||||
//this.log('delete: `' + entryName + '`');
|
|
||||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
||||||
const objectStore = transaction.objectStore(this.storeName);
|
|
||||||
|
|
||||||
transaction.onerror = (e) => {
|
return this.getObjectStore('readwrite', (objectStore) => {
|
||||||
reject(transaction.error);
|
return (entryName as string[]).map((entryName) => objectStore.delete(entryName));
|
||||||
clearTimeout(timeout);
|
}, 'delete: ' + entryName.join(', '));
|
||||||
};
|
|
||||||
|
|
||||||
transaction.oncomplete = (e) => {
|
|
||||||
this.log('delete: transaction complete', entryName);
|
|
||||||
resolve();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.log.error('delete: transaction not finished', entryName, transaction);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
if(!Array.isArray(entryName)) {
|
|
||||||
entryName = [].concat(entryName);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i = 0, length = entryName.length; i < length; ++i) {
|
|
||||||
const request = objectStore.delete(entryName[i]);
|
|
||||||
request.onerror = (error) => {
|
|
||||||
reject(transaction.error);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch(error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteAll() {
|
public deleteAll() {
|
||||||
return this.openDatabase().then((db) => {
|
return this.getObjectStore('readwrite', (objectStore) => objectStore.clear(), 'deleteAll');
|
||||||
//this.log('deleteAll');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
||||||
|
|
||||||
const objectStore = transaction.objectStore(this.storeName);
|
|
||||||
var request = objectStore.clear();
|
|
||||||
} catch(error) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.log.error('deleteAll: request not finished', request);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
request.onsuccess = (event) => {
|
|
||||||
resolve();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
request.onerror = (error) => {
|
|
||||||
reject(error);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public save(entryName: string | string[], value: any | any[]) {
|
public save(entryName: string | string[], value: any | any[]) {
|
||||||
return this.openDatabase().then((db) => {
|
// const handleError = (error: Error) => {
|
||||||
//this.log('save:', entryName, value);
|
// this.log.error('save: transaction error:', entryName, value, db, error, error && error.name);
|
||||||
|
// if((!error || error.name === 'InvalidStateError')/* && false */) {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// this.save(entryName, value);
|
||||||
|
// }, 2e3);
|
||||||
|
// } else {
|
||||||
|
// //console.error('IndexedDB saveFile transaction error:', error, error && error.name);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const handleError = (error: Error) => {
|
if(!Array.isArray(entryName)) {
|
||||||
this.log.error('save: transaction error:', entryName, value, db, error, error && error.name);
|
entryName = [].concat(entryName);
|
||||||
if((!error || error.name === 'InvalidStateError')/* && false */) {
|
value = [].concat(value);
|
||||||
setTimeout(() => {
|
}
|
||||||
this.save(entryName, value);
|
|
||||||
}, 2e3);
|
return this.getObjectStore('readwrite', (objectStore) => {
|
||||||
} else {
|
return (entryName as string[]).map((entryName, idx) => objectStore.put(value[idx], entryName));
|
||||||
//console.error('IndexedDB saveFile transaction error:', error, error && error.name);
|
}, 'save: ' + entryName.join(', '));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
|
||||||
|
|
||||||
transaction.onerror = (e) => {
|
|
||||||
handleError(transaction.error);
|
|
||||||
reject(transaction.error);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
transaction.oncomplete = (e) => {
|
|
||||||
this.log('save: transaction complete:', entryName);
|
|
||||||
resolve();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.log.error('save: transaction not finished', entryName, transaction);
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
/* transaction.addEventListener('abort', (e) => {
|
|
||||||
//handleError();
|
|
||||||
this.log.error('IndexedDB: save transaction abort!', transaction.error);
|
|
||||||
}); */
|
|
||||||
|
|
||||||
const objectStore = transaction.objectStore(this.storeName);
|
|
||||||
|
|
||||||
if(!Array.isArray(entryName)) {
|
|
||||||
entryName = [].concat(entryName);
|
|
||||||
value = [].concat(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i = 0, length = entryName.length; i < length; ++i) {
|
|
||||||
const request = objectStore.put(value[i], entryName[i]);
|
|
||||||
request.onerror = (error) => {
|
|
||||||
reject(transaction.error);
|
|
||||||
clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch(error) {
|
|
||||||
handleError(error);
|
|
||||||
reject(error);
|
|
||||||
|
|
||||||
/* this.storageIsAvailable = false;
|
|
||||||
throw error; */
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveFile(fileName: string, blob: Blob | Uint8Array) {
|
public saveFile(fileName: string, blob: Blob | Uint8Array) {
|
||||||
@ -374,95 +273,83 @@ export default class IDBStorage {
|
|||||||
return blob.size || blob.byteLength || blob.length;
|
return blob.size || blob.byteLength || blob.length;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
public get<T>(entryName: string): Promise<T> {
|
public get<T>(entryName: string[]): Promise<T[]>;
|
||||||
|
public get<T>(entryName: string): Promise<T>;
|
||||||
|
public get<T>(entryName: string | string[]): Promise<T> | Promise<T[]> {
|
||||||
//return Promise.reject();
|
//return Promise.reject();
|
||||||
|
|
||||||
|
if(!Array.isArray(entryName)) {
|
||||||
|
entryName = [].concat(entryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getObjectStore<T>('readonly', (objectStore) => {
|
||||||
|
return (entryName as string[]).map((entryName) => objectStore.get(entryName));
|
||||||
|
}, 'get: ' + entryName.join(', '));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getObjectStore<T>(mode: IDBTransactionMode, objectStore: (objectStore: IDBObjectStore) => IDBRequest | IDBRequest[], log: string) {
|
||||||
|
const perf = performance.now();
|
||||||
|
|
||||||
|
this.log(log + ': start');
|
||||||
|
|
||||||
return this.openDatabase().then((db) => {
|
return this.openDatabase().then((db) => {
|
||||||
//this.log('get pre:', fileName);
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const transaction = db.transaction([this.storeName], mode);
|
||||||
try {
|
|
||||||
const transaction = db.transaction([this.storeName], 'readonly');
|
|
||||||
/* transaction.onabort = (e) => {
|
|
||||||
this.log.error('get transaction onabort?', e);
|
|
||||||
}; */
|
|
||||||
const objectStore = transaction.objectStore(this.storeName);
|
|
||||||
var request = objectStore.get(entryName);
|
|
||||||
|
|
||||||
//this.log.log('IDB get:', fileName, request);
|
|
||||||
} catch(err) {
|
|
||||||
this.log.error('get error:', err, entryName, request, request.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.log.error('get request not finished!', entryName, request);
|
|
||||||
reject();
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
request.onsuccess = function(event) {
|
|
||||||
const result = request.result;
|
|
||||||
if(result === undefined) {
|
|
||||||
reject('NO_ENTRY_FOUND');
|
|
||||||
} /* else if(typeof result === 'string' &&
|
|
||||||
result.substr(0, 5) === 'data:') {
|
|
||||||
resolve(dataUrlToBlob(result));
|
|
||||||
} */else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
transaction.onerror = (e) => {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
}
|
reject(transaction.error);
|
||||||
|
|
||||||
request.onerror = () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
transaction.oncomplete = (e) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
this.log(log + ': end', performance.now() - perf);
|
||||||
|
|
||||||
|
const results = r.map(r => r.result);
|
||||||
|
resolve(isArray ? results : results[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
this.log.error('transaction not finished', transaction);
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
/* transaction.addEventListener('abort', (e) => {
|
||||||
|
//handleError();
|
||||||
|
this.log.error('IndexedDB: transaction abort!', transaction.error);
|
||||||
|
}); */
|
||||||
|
|
||||||
|
const requests = objectStore(transaction.objectStore(this.storeName));
|
||||||
|
|
||||||
|
const isArray = Array.isArray(requests);
|
||||||
|
const r: IDBRequest[] = isArray ? requests : [].concat(requests) as any;
|
||||||
|
|
||||||
|
// const length = r.length;
|
||||||
|
// /* let left = length;
|
||||||
|
|
||||||
|
// const onRequestFinished = (error?: Error) => {
|
||||||
|
// if(!--left) {
|
||||||
|
// resolve(result);
|
||||||
|
// clearTimeout(timeout);
|
||||||
|
// }
|
||||||
|
// }; */
|
||||||
|
|
||||||
|
// for(let i = 0; i < length; ++i) {
|
||||||
|
// const request = r[i];
|
||||||
|
// request.onsuccess = () => {
|
||||||
|
// onRequestFinished();
|
||||||
|
// };
|
||||||
|
|
||||||
|
// request.onerror = (e) => {
|
||||||
|
// onRequestFinished(transaction.error);
|
||||||
|
// };
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAll<T>(): Promise<T[]> {
|
public getAll<T>(): Promise<T[]> {
|
||||||
return this.openDatabase().then((db) => {
|
return this.getObjectStore<T[]>('readonly', (objectStore) => objectStore.getAll(), 'getAll');
|
||||||
//this.log('getAll pre:', fileName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const transaction = db.transaction([this.storeName], 'readonly');
|
|
||||||
/* transaction.onabort = (e) => {
|
|
||||||
this.log.error('getAll transaction onabort?', e);
|
|
||||||
}; */
|
|
||||||
const objectStore = transaction.objectStore(this.storeName);
|
|
||||||
var request = objectStore.getAll();
|
|
||||||
|
|
||||||
//this.log.log('IDB getAll:', fileName, request);
|
|
||||||
} catch(err) {
|
|
||||||
this.log.error('getAll error:', err, request, request.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
this.log.error('getAll request not finished!', request);
|
|
||||||
reject();
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
request.onsuccess = function(event) {
|
|
||||||
const result = request.result;
|
|
||||||
if(result === undefined) {
|
|
||||||
reject('NO_ENTRY_FOUND');
|
|
||||||
} /* else if(typeof result === 'string' &&
|
|
||||||
result.substr(0, 5) === 'data:') {
|
|
||||||
resolve(dataUrlToBlob(result));
|
|
||||||
} */else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.onerror = () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
reject();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* public getAllKeys(): Promise<Array<string>> {
|
/* public getAllKeys(): Promise<Array<string>> {
|
||||||
@ -512,4 +399,4 @@ export default class IDBStorage {
|
|||||||
|
|
||||||
return Promise.resolve(fakeWriter);
|
return Promise.resolve(fakeWriter);
|
||||||
} */
|
} */
|
||||||
}
|
}
|
||||||
|
@ -312,6 +312,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private releasePending() {
|
private releasePending() {
|
||||||
|
//return;
|
||||||
|
|
||||||
if(this.postMessage) {
|
if(this.postMessage) {
|
||||||
this.debug && this.log.debug('releasing tasks, length:', this.pending.length);
|
this.debug && this.log.debug('releasing tasks, length:', this.pending.length);
|
||||||
this.pending.forEach(pending => {
|
this.pending.forEach(pending => {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import type { ChatSavedPosition } from './appManagers/appImManager';
|
import type { ChatSavedPosition } from './appManagers/appImManager';
|
||||||
import type { State } from './appManagers/appStateManager';
|
import type { State } from './appManagers/appStateManager';
|
||||||
|
import type { AppDraftsManager } from './appManagers/appDraftsManager';
|
||||||
import { MOUNT_CLASS_TO } from '../config/debug';
|
import { MOUNT_CLASS_TO } from '../config/debug';
|
||||||
import { LangPackDifference } from '../layer';
|
import { LangPackDifference } from '../layer';
|
||||||
import AppStorage from './storage';
|
import AppStorage from './storage';
|
||||||
@ -24,7 +25,8 @@ const sessionStorage = new AppStorage<{
|
|||||||
chatPositions: {
|
chatPositions: {
|
||||||
[peerId_threadId: string]: ChatSavedPosition
|
[peerId_threadId: string]: ChatSavedPosition
|
||||||
},
|
},
|
||||||
langPack: LangPackDifference
|
langPack: LangPackDifference,
|
||||||
|
drafts: AppDraftsManager['drafts']
|
||||||
} & State>({
|
} & State>({
|
||||||
storeName: 'session'
|
storeName: 'session'
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DatabaseStore, DatabaseStoreName } from "../config/database";
|
import { DatabaseStore, DatabaseStoreName } from "../config/database";
|
||||||
|
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
|
||||||
import { throttle } from "../helpers/schedulers";
|
import { throttle } from "../helpers/schedulers";
|
||||||
import IDBStorage, { IDBOptions } from "./idb";
|
import IDBStorage, { IDBOptions } from "./idb";
|
||||||
|
|
||||||
@ -20,8 +21,13 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
//private cache: Partial<{[key: string]: Storage[typeof key]}> = {};
|
//private cache: Partial<{[key: string]: Storage[typeof key]}> = {};
|
||||||
private cache: Partial<Storage> = {};
|
private cache: Partial<Storage> = {};
|
||||||
private useStorage = true;
|
private useStorage = true;
|
||||||
|
|
||||||
|
private getPromises: Map<keyof Storage, CancellablePromise<Storage[keyof Storage]>> = new Map();
|
||||||
|
private getThrottled: () => void;
|
||||||
|
|
||||||
private keysToSet: Set<keyof Storage> = new Set();
|
private keysToSet: Set<keyof Storage> = new Set();
|
||||||
private saveThrottled: () => void;
|
private saveThrottled: () => void;
|
||||||
|
private saveResolve: () => void;
|
||||||
|
|
||||||
constructor(storageOptions: Omit<IDBOptions, 'storeName' | 'stores'> & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) {
|
constructor(storageOptions: Omit<IDBOptions, 'storeName' | 'stores'> & {stores?: DatabaseStore[], storeName: DatabaseStoreName}) {
|
||||||
this.storage = new IDBStorage(storageOptions);
|
this.storage = new IDBStorage(storageOptions);
|
||||||
@ -29,24 +35,57 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
AppStorage.STORAGES.push(this);
|
AppStorage.STORAGES.push(this);
|
||||||
|
|
||||||
this.saveThrottled = throttle(async() => {
|
this.saveThrottled = throttle(async() => {
|
||||||
if(!this.keysToSet.size) {
|
if(this.keysToSet.size) {
|
||||||
return;
|
const keys = Array.from(this.keysToSet.values()) as string[];
|
||||||
|
this.keysToSet.clear();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//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]));
|
||||||
|
//console.log('setItem: have set', key/* , value */);
|
||||||
|
} catch(e) {
|
||||||
|
//this.useCS = false;
|
||||||
|
console.error('[AS]: set error:', e, keys/* , value */);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = Array.from(this.keysToSet.values()) as string[];
|
if(this.saveResolve) {
|
||||||
this.keysToSet.clear();
|
this.saveResolve();
|
||||||
|
this.saveResolve = undefined;
|
||||||
try {
|
|
||||||
//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]));
|
|
||||||
//console.log('setItem: have set', key/* , value */);
|
|
||||||
} catch(e) {
|
|
||||||
//this.useCS = false;
|
|
||||||
console.error('[AS]: set error:', e, keys/* , value */);
|
|
||||||
}
|
}
|
||||||
}, 50, false);
|
}, 16, false);
|
||||||
|
|
||||||
|
this.getThrottled = throttle(async() => {
|
||||||
|
const keys = Array.from(this.getPromises.keys());
|
||||||
|
|
||||||
|
this.storage.get(keys as string[]).then(values => {
|
||||||
|
for(let i = 0, length = keys.length; i < length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const deferred = this.getPromises.get(key);
|
||||||
|
if(deferred) {
|
||||||
|
// @ts-ignore
|
||||||
|
deferred.resolve(this.cache[key] = values[i]);
|
||||||
|
this.getPromises.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error) => {
|
||||||
|
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(error)) {
|
||||||
|
this.useStorage = false;
|
||||||
|
console.error('[AS]: get error:', error, keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let i = 0, length = keys.length; i < length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const deferred = this.getPromises.get(key);
|
||||||
|
if(deferred) {
|
||||||
|
deferred.reject(error);
|
||||||
|
this.getPromises.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 16, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCache() {
|
public getCache() {
|
||||||
@ -65,19 +104,15 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
if(this.cache.hasOwnProperty(key)) {
|
if(this.cache.hasOwnProperty(key)) {
|
||||||
return this.getFromCache(key);
|
return this.getFromCache(key);
|
||||||
} else if(this.useStorage) {
|
} else if(this.useStorage) {
|
||||||
let value: any;
|
const r = this.getPromises.get(key);
|
||||||
try {
|
if(r) return r;
|
||||||
value = await this.storage.get(key as string);
|
|
||||||
//console.log('[AS]: get result:', key, value);
|
|
||||||
//value = JSON.parse(value);
|
|
||||||
} catch(e) {
|
|
||||||
if(!['NO_ENTRY_FOUND', 'STORAGE_OFFLINE'].includes(e)) {
|
|
||||||
this.useStorage = false;
|
|
||||||
console.error('[AS]: get error:', e, key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.cache[key] = value;
|
const p = deferredPromise<Storage[typeof key]>();
|
||||||
|
this.getPromises.set(key, p);
|
||||||
|
|
||||||
|
this.getThrottled();
|
||||||
|
|
||||||
|
return p;
|
||||||
}/* else {
|
}/* else {
|
||||||
throw 'something went wrong';
|
throw 'something went wrong';
|
||||||
} */
|
} */
|
||||||
@ -87,7 +122,7 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
return this.storage.getAll();
|
return this.storage.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async set(obj: Partial<Storage>, onlyLocal = false) {
|
public set(obj: Partial<Storage>, onlyLocal = false) {
|
||||||
//console.log('storageSetValue', obj, callback, arguments);
|
//console.log('storageSetValue', obj, callback, arguments);
|
||||||
|
|
||||||
for(const key in obj) {
|
for(const key in obj) {
|
||||||
@ -115,6 +150,10 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
this.saveResolve = resolve;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async delete(key: keyof Storage, saveLocal = false) {
|
public async delete(key: keyof Storage, saveLocal = false) {
|
||||||
@ -151,6 +190,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
|
|||||||
|
|
||||||
if(!enabled) {
|
if(!enabled) {
|
||||||
storage.keysToSet.clear();
|
storage.keysToSet.clear();
|
||||||
|
storage.getPromises.forEach((deferred) => deferred.resolve());
|
||||||
|
storage.getPromises.clear();
|
||||||
return storage.clear();
|
return storage.clear();
|
||||||
} else {
|
} else {
|
||||||
return storage.set(storage.cache);
|
return storage.set(storage.cache);
|
||||||
|
@ -23,14 +23,11 @@ import apiManager from "../mtproto/mtprotoworker";
|
|||||||
import searchIndexManager from "../searchIndexManager";
|
import searchIndexManager from "../searchIndexManager";
|
||||||
import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array";
|
import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
import AppStorage from "../storage";
|
|
||||||
import { safeReplaceObject } from "../../helpers/object";
|
import { safeReplaceObject } from "../../helpers/object";
|
||||||
import { AppStateManager } from "../appManagers/appStateManager";
|
import { AppStateManager } from "../appManagers/appStateManager";
|
||||||
|
|
||||||
export default class DialogsStorage {
|
export default class DialogsStorage {
|
||||||
private storage = new AppStorage<Record<number, Dialog>>({
|
private storage: AppStateManager['storages']['dialogs'];
|
||||||
storeName: 'dialogs'
|
|
||||||
});
|
|
||||||
|
|
||||||
private dialogs: {[peerId: string]: Dialog} = {};
|
private dialogs: {[peerId: string]: Dialog} = {};
|
||||||
public byFolders: {[folderId: number]: Dialog[]} = {};
|
public byFolders: {[folderId: number]: Dialog[]} = {};
|
||||||
@ -64,7 +61,7 @@ export default class DialogsStorage {
|
|||||||
private apiUpdatesManager: ApiUpdatesManager,
|
private apiUpdatesManager: ApiUpdatesManager,
|
||||||
private serverTimeManager: ServerTimeManager
|
private serverTimeManager: ServerTimeManager
|
||||||
) {
|
) {
|
||||||
this.dialogs = this.storage.getCache();
|
this.storage = this.appStateManager.storages.dialogs;
|
||||||
|
|
||||||
this.reset();
|
this.reset();
|
||||||
|
|
||||||
@ -85,28 +82,33 @@ export default class DialogsStorage {
|
|||||||
updatePinnedDialogs: this.onUpdatePinnedDialogs,
|
updatePinnedDialogs: this.onUpdatePinnedDialogs,
|
||||||
});
|
});
|
||||||
|
|
||||||
let storageDialogs: Dialog[];
|
appStateManager.getState().then((state) => {
|
||||||
const getStorageDialogsPromise = this.storage.getAll().then(dialogs => {
|
this.pinnedOrders = state.pinnedOrders || {};
|
||||||
storageDialogs = dialogs as any;
|
if(!this.pinnedOrders[0]) this.pinnedOrders[0] = [];
|
||||||
|
if(!this.pinnedOrders[1]) this.pinnedOrders[1] = [];
|
||||||
|
|
||||||
|
const dialogs = appStateManager.storagesResults.dialogs;
|
||||||
|
if(dialogs.length) {
|
||||||
|
for(let i = 0, length = dialogs.length; i < length; ++i) {
|
||||||
|
const dialog = dialogs[i];
|
||||||
|
if(dialog) {
|
||||||
|
dialog.top_message = this.appMessagesManager.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog
|
||||||
|
|
||||||
forEachReverse(storageDialogs, dialog => {
|
if(dialog.topMessage) {
|
||||||
dialog.top_message = this.appMessagesManager.getServerMessageId(dialog.top_message); // * fix outgoing message to avoid copying dialog
|
this.appMessagesManager.saveMessages([dialog.topMessage]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveDialog(dialog);
|
||||||
|
|
||||||
this.saveDialog(dialog);
|
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись
|
||||||
|
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
|
||||||
if(dialog.topMessage) {
|
if(message.deleted) {
|
||||||
this.appMessagesManager.saveMessages([dialog.topMessage]);
|
this.appMessagesManager.reloadConversation(dialog.peerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ! WARNING, убрать это когда нужно будет делать чтобы pending сообщения сохранялись
|
|
||||||
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
|
|
||||||
if(message.deleted) {
|
|
||||||
this.appMessagesManager.reloadConversation(dialog.peerId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
appStateManager.addLoadPromise(getStorageDialogsPromise).then((state) => {
|
|
||||||
this.allDialogsLoaded = state.allDialogsLoaded || {};
|
this.allDialogsLoaded = state.allDialogsLoaded || {};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -247,7 +249,11 @@ export default class DialogsStorage {
|
|||||||
const order = this.pinnedOrders[dialog.folder_id];
|
const order = this.pinnedOrders[dialog.folder_id];
|
||||||
|
|
||||||
const foundIndex = order.indexOf(dialog.peerId);
|
const foundIndex = order.indexOf(dialog.peerId);
|
||||||
const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerId) - 1 : foundIndex;
|
let pinnedIndex = foundIndex;
|
||||||
|
if(foundIndex === -1) {
|
||||||
|
pinnedIndex = order.push(dialog.peerId) - 1;
|
||||||
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders);
|
||||||
|
}
|
||||||
|
|
||||||
return this.generateDialogPinnedDateByIndex(pinnedIndex);
|
return this.generateDialogPinnedDateByIndex(pinnedIndex);
|
||||||
}
|
}
|
||||||
@ -270,6 +276,38 @@ export default class DialogsStorage {
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setDialogToState(dialog: Dialog) {
|
||||||
|
const historyStorage = this.appMessagesManager.getHistoryStorage(dialog.peerId);
|
||||||
|
const history = [].concat(historyStorage.history.slice);
|
||||||
|
let incomingMessage: any;
|
||||||
|
for(let i = 0, length = history.length; i < length; ++i) {
|
||||||
|
const mid = history[i];
|
||||||
|
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, mid);
|
||||||
|
if(!message.pFlags.is_outgoing) {
|
||||||
|
incomingMessage = message;
|
||||||
|
|
||||||
|
if(message.fromId !== dialog.peerId) {
|
||||||
|
this.appStateManager.requestPeer(message.fromId, 'topMessage_' + dialog.peerId, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.topMessage = incomingMessage;
|
||||||
|
|
||||||
|
if(dialog.peerId < 0 && dialog.pts) {
|
||||||
|
const newPts = this.apiUpdatesManager.channelStates[-dialog.peerId].pts;
|
||||||
|
dialog.pts = newPts;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storage.set({
|
||||||
|
[dialog.peerId]: dialog
|
||||||
|
});
|
||||||
|
|
||||||
|
this.appStateManager.requestPeer(dialog.peerId, 'dialog');
|
||||||
|
}
|
||||||
|
|
||||||
public pushDialog(dialog: Dialog, offsetDate?: number) {
|
public pushDialog(dialog: Dialog, offsetDate?: number) {
|
||||||
const dialogs = this.getFolder(dialog.folder_id);
|
const dialogs = this.getFolder(dialog.folder_id);
|
||||||
const pos = dialogs.findIndex(d => d.peerId === dialog.peerId);
|
const pos = dialogs.findIndex(d => d.peerId === dialog.peerId);
|
||||||
@ -280,34 +318,7 @@ export default class DialogsStorage {
|
|||||||
//if(!this.dialogs[dialog.peerId]) {
|
//if(!this.dialogs[dialog.peerId]) {
|
||||||
this.dialogs[dialog.peerId] = dialog;
|
this.dialogs[dialog.peerId] = dialog;
|
||||||
|
|
||||||
const historyStorage = this.appMessagesManager.getHistoryStorage(dialog.peerId);
|
this.setDialogToState(dialog);
|
||||||
const history = [].concat(historyStorage.history.slice);
|
|
||||||
let incomingMessage: any;
|
|
||||||
for(const mid of history) {
|
|
||||||
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, mid);
|
|
||||||
if(!message.pFlags.is_outgoing) {
|
|
||||||
incomingMessage = message;
|
|
||||||
|
|
||||||
if(message.fromId !== dialog.peerId) {
|
|
||||||
this.appStateManager.requestPeer(message.fromId, 'topMessage_' + dialog.peerId, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.topMessage = incomingMessage;
|
|
||||||
|
|
||||||
if(dialog.peerId < 0 && dialog.pts) {
|
|
||||||
const newPts = this.apiUpdatesManager.channelStates[-dialog.peerId].pts;
|
|
||||||
dialog.pts = newPts;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.storage.set({
|
|
||||||
[dialog.peerId]: dialog
|
|
||||||
});
|
|
||||||
|
|
||||||
this.appStateManager.requestPeer(dialog.peerId, 'dialog');
|
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if(offsetDate &&
|
if(offsetDate &&
|
||||||
@ -591,6 +602,7 @@ export default class DialogsStorage {
|
|||||||
if(dialog.pFlags?.pinned) {
|
if(dialog.pFlags?.pinned) {
|
||||||
delete dialog.pFlags.pinned;
|
delete dialog.pFlags.pinned;
|
||||||
this.pinnedOrders[folder_id].findAndSplice(p => p === dialog.peerId);
|
this.pinnedOrders[folder_id].findAndSplice(p => p === dialog.peerId);
|
||||||
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders);
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.folder_id = folder_id;
|
dialog.folder_id = folder_id;
|
||||||
@ -622,6 +634,7 @@ export default class DialogsStorage {
|
|||||||
if(!update.pFlags.pinned) {
|
if(!update.pFlags.pinned) {
|
||||||
delete dialog.pFlags.pinned;
|
delete dialog.pFlags.pinned;
|
||||||
this.pinnedOrders[folderId].findAndSplice(p => p === dialog.peerId);
|
this.pinnedOrders[folderId].findAndSplice(p => p === dialog.peerId);
|
||||||
|
this.appStateManager.pushToState('pinnedOrders', this.pinnedOrders);
|
||||||
} else { // means set
|
} else { // means set
|
||||||
dialog.pFlags.pinned = true;
|
dialog.pFlags.pinned = true;
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,7 @@ export default class FiltersStorage {
|
|||||||
private rootScope: typeof _rootScope) {
|
private rootScope: typeof _rootScope) {
|
||||||
|
|
||||||
this.appStateManager.getState().then((state) => {
|
this.appStateManager.getState().then((state) => {
|
||||||
if(state.filters) {
|
this.filters = state.filters;
|
||||||
for(const filterId in state.filters) {
|
|
||||||
this.saveDialogFilter(state.filters[filterId], false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rootScope.addMultipleEventsListeners({
|
rootScope.addMultipleEventsListeners({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user