|
|
@ -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 => { |
|
|
|
// @ts-ignore
|
|
|
|
this.pushToState(key, copy(STATE_INIT[key])); |
|
|
|
state[key] = copy(STATE_INIT[key]); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const users: typeof state['users'] = {}, chats: typeof state['chats'] = {}; |
|
|
|
// @ts-ignore
|
|
|
|
if(state.recentSearch?.length) { |
|
|
|
const s = this.storagesResults[key]; |
|
|
|
state.recentSearch.forEach(peerId => { |
|
|
|
if(s && s.length) { |
|
|
|
if(peerId < 0) chats[peerId] = state.chats[peerId]; |
|
|
|
s.length = 0; |
|
|
|
else users[peerId] = state.users[peerId]; |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
state.users = users; |
|
|
|
//state = this.state = new Proxy(state, getHandler());
|
|
|
|
state.chats = chats; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// * 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) { |
|
|
|
|
|
|
|
if(direct) { |
|
|
|
this.state[key] = value; |
|
|
|
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)) { |
|
|
|