/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE * * Originally from: * https://github.com/zhukov/webogram * Copyright (C) 2014 Igor Zhukov * https://github.com/zhukov/webogram/blob/master/LICENSE */ import Modes from '../config/modes'; import {IS_WORKER} from '../helpers/context'; import makeError from '../helpers/makeError'; import {WorkerTaskTemplate} from '../types'; import MTProtoMessagePort from './mtproto/mtprotoMessagePort'; // import { stringify } from '../helpers/json'; class LocalStorage> { private prefix = ''; private cache: Partial = {}; private useStorage = true; constructor(/* private preserveKeys: (keyof Storage)[] */) { if(Modes.test) { this.prefix = 't_'; } } public get(key: T, useCache = true): Storage[T] { if(this.cache.hasOwnProperty(key) && useCache) { return this.cache[key]; } else if(this.useStorage) { let value: Storage[T]; try { value = localStorage.getItem(this.prefix + (key as string)) as any; } catch(err) { this.useStorage = false; throw makeError('STORAGE_OFFLINE'); } if(value !== null) { try { value = JSON.parse(value); } catch(err) { // console.error(err); } } else { value = undefined; } return value; } else { throw makeError('STORAGE_OFFLINE'); } } public set(obj: Partial, onlyLocal = false) { let lastError: any; for(const key in obj) { if(obj.hasOwnProperty(key)) { const value = obj[key]; this.cache[key] = value; if(!onlyLocal) { try { if(!this.useStorage) { throw makeError('STORAGE_OFFLINE'); } const stringified = JSON.stringify(value); localStorage.setItem(this.prefix + key, stringified); } catch(err) { this.useStorage = false; lastError = err; } } } } if(lastError) { throw lastError; } } public delete(key: keyof Storage, saveLocal = false) { // ! it is needed here key = '' + (key as string); if(!saveLocal) { delete this.cache[key]; } // if(this.useStorage) { try { localStorage.removeItem(this.prefix + (key as string)); } catch(err) { } // } } /* public clear(preserveKeys: (keyof Storage)[] = this.preserveKeys) { // if(this.useStorage) { try { let obj: Partial = {}; if(preserveKeys) { preserveKeys.forEach((key) => { const value = this.get(key); if(value !== undefined) { obj[key] = value; } }); } localStorage.clear(); if(preserveKeys) { this.set(obj); } } catch(err) { } // } } */ public clear() { const keys: string[] = ['dc', 'server_time_offset', 'xt_instance', 'user_auth', 'state_id', 'k_build']; for(let i = 1; i <= 5; ++i) { keys.push(`dc${i}_server_salt`); keys.push(`dc${i}_auth_key`); keys.push(`dc${i}_hash`); // only for WebA } for(const key of keys) { this.delete(key, true); } } public toggleStorage(enabled: boolean, clearWrite: boolean) { this.useStorage = enabled; if(!clearWrite) { return; } if(!enabled) { this.clear(); } else { return this.set(this.cache); } } } export interface LocalStorageProxyTask extends WorkerTaskTemplate { type: 'localStorageProxy', payload: { type: 'set' | 'get' | 'delete' | 'clear' | 'toggleStorage', args: any[] } }; export interface LocalStorageProxyTaskResponse extends WorkerTaskTemplate { type: 'localStorageProxy', payload: any }; export default class LocalStorageController> { private static STORAGES: LocalStorageController[] = []; // private log = (...args: any[]) => console.log('[SW LS]', ...args); // private log = (...args: any[]) => {}; private storage: LocalStorage; constructor(/* private preserveKeys: (keyof Storage)[] = [] */) { LocalStorageController.STORAGES.push(this); if(!IS_WORKER) { this.storage = new LocalStorage(/* preserveKeys */); } } private async proxy(type: LocalStorageProxyTask['payload']['type'], ...args: LocalStorageProxyTask['payload']['args']): Promise { if(IS_WORKER) { const port = MTProtoMessagePort.getInstance(); return port.invoke('localStorageProxy', {type, args}); } args = Array.prototype.slice.call(args); // @ts-ignore return this.storage[type].apply(this.storage, args as any); } public get(key: T, useCache?: boolean) { return this.proxy('get', key, useCache); } public set(obj: Partial, onlyLocal?: boolean) { return this.proxy('set', obj, onlyLocal); } public delete(key: keyof Storage, saveLocal?: boolean) { return this.proxy('delete', key, saveLocal); } public clear(/* preserveKeys?: (keyof Storage)[] */) { return this.proxy('clear'/* , preserveKeys */); } public toggleStorage(enabled: boolean, clearWrite: boolean) { return this.proxy('toggleStorage', enabled, clearWrite); } }