Global: Migrate cache from localStorage to IndexedDB (#4770)

This commit is contained in:
Alexander Zinchuk 2024-08-06 20:06:14 +02:00
parent 66d54b2f63
commit f590963b12
6 changed files with 96 additions and 48 deletions

View File

@ -42,6 +42,8 @@ export const GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT = 200;
export const GLOBAL_STATE_CACHE_ARCHIVED_CHAT_LIST_LIMIT = 10; export const GLOBAL_STATE_CACHE_ARCHIVED_CHAT_LIST_LIMIT = 10;
export const GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT = 150; export const GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT = 150;
export const IS_SCREEN_LOCKED_CACHE_KEY = 'tt-is-screen-locked';
export const MEDIA_CACHE_DISABLED = false; export const MEDIA_CACHE_DISABLED = false;
export const MEDIA_CACHE_NAME = 'tt-media'; export const MEDIA_CACHE_NAME = 'tt-media';
export const MEDIA_CACHE_NAME_AVATARS = 'tt-media-avatars'; export const MEDIA_CACHE_NAME_AVATARS = 'tt-media-avatars';

View File

@ -19,8 +19,10 @@ import {
GLOBAL_STATE_CACHE_DISABLED, GLOBAL_STATE_CACHE_DISABLED,
GLOBAL_STATE_CACHE_KEY, GLOBAL_STATE_CACHE_KEY,
GLOBAL_STATE_CACHE_USER_LIST_LIMIT, GLOBAL_STATE_CACHE_USER_LIST_LIMIT,
IS_SCREEN_LOCKED_CACHE_KEY,
SAVED_FOLDER_ID, SAVED_FOLDER_ID,
} from '../config'; } from '../config';
import { MAIN_IDB_STORE } from '../util/browser/idb';
import { getOrderedIds } from '../util/folderManager'; import { getOrderedIds } from '../util/folderManager';
import { import {
compact, pick, pickTruthy, unique, compact, pick, pickTruthy, unique,
@ -50,21 +52,41 @@ const updateCacheThrottled = throttle(() => onIdle(() => updateCache()), UPDATE_
const updateCacheForced = () => updateCache(true); const updateCacheForced = () => updateCache(true);
let isCaching = false; let isCaching = false;
let isRemovingCache = false;
let unsubscribeFromBeforeUnload: NoneToVoidFunction | undefined; let unsubscribeFromBeforeUnload: NoneToVoidFunction | undefined;
export function cacheGlobal(global: GlobalState) {
return MAIN_IDB_STORE.set(GLOBAL_STATE_CACHE_KEY, global);
}
export function loadCachedGlobal() {
return MAIN_IDB_STORE.get<GlobalState>(GLOBAL_STATE_CACHE_KEY);
}
export function removeGlobalFromCache() {
return MAIN_IDB_STORE.del(GLOBAL_STATE_CACHE_KEY);
}
function cacheIsScreenLocked(global: GlobalState) {
if (global?.passcode?.isScreenLocked) localStorage.setItem(IS_SCREEN_LOCKED_CACHE_KEY, 'true');
}
export function initCache() { export function initCache() {
if (GLOBAL_STATE_CACHE_DISABLED) { if (GLOBAL_STATE_CACHE_DISABLED) {
return; return;
} }
const resetCache = () => { const resetCache = () => {
localStorage.removeItem(GLOBAL_STATE_CACHE_KEY); isRemovingCache = true;
removeGlobalFromCache().finally(() => {
localStorage.removeItem(IS_SCREEN_LOCKED_CACHE_KEY);
isRemovingCache = false;
if (!isCaching) {
return;
}
if (!isCaching) { clearCaching();
return; });
}
clearCaching();
}; };
addActionHandler('saveSession', (): ActionReturnType => { addActionHandler('saveSession', (): ActionReturnType => {
@ -79,12 +101,12 @@ export function initCache() {
addActionHandler('reset', resetCache); addActionHandler('reset', resetCache);
} }
export function loadCache(initialState: GlobalState): GlobalState | undefined { export async function loadCache(initialState: GlobalState): Promise<GlobalState | undefined> {
if (GLOBAL_STATE_CACHE_DISABLED) { if (GLOBAL_STATE_CACHE_DISABLED) {
return undefined; return undefined;
} }
const cache = readCache(initialState); const cache = await readCache(initialState);
if (cache.passcode.hasPasscode || hasStoredSession()) { if (cache.passcode.hasPasscode || hasStoredSession()) {
setupCaching(); setupCaching();
@ -113,14 +135,17 @@ export function clearCaching() {
} }
} }
function readCache(initialState: GlobalState): GlobalState { async function readCache(initialState: GlobalState): Promise<GlobalState> {
if (DEBUG) { if (DEBUG) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.time('global-state-cache-read'); console.time('global-state-cache-read');
} }
const json = localStorage.getItem(GLOBAL_STATE_CACHE_KEY); const json = localStorage.getItem(GLOBAL_STATE_CACHE_KEY);
const cached = json ? JSON.parse(json) as GlobalState : undefined; const cachedFromLocalStorage = json ? JSON.parse(json) as GlobalState : undefined;
if (cachedFromLocalStorage) localStorage.removeItem(GLOBAL_STATE_CACHE_KEY);
const cached = cachedFromLocalStorage || await loadCachedGlobal();
if (DEBUG) { if (DEBUG) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -231,7 +256,7 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
function updateCache(force?: boolean) { function updateCache(force?: boolean) {
const global = getGlobal(); const global = getGlobal();
if (!isCaching || global.isLoggingOut || (!force && isHeavyAnimating())) { if (isRemovingCache || !isCaching || global.isLoggingOut || (!force && isHeavyAnimating())) {
return; return;
} }
@ -248,16 +273,16 @@ export function forceUpdateCache(noEncrypt = false) {
void encryptSession(undefined, serializedGlobal); void encryptSession(undefined, serializedGlobal);
} }
const serializedGlobalClean = JSON.stringify(clearGlobalForLockScreen(global, false)); cacheIsScreenLocked(global);
localStorage.setItem(GLOBAL_STATE_CACHE_KEY, serializedGlobalClean); cacheGlobal(clearGlobalForLockScreen(global, false));
return; return;
} }
localStorage.setItem(GLOBAL_STATE_CACHE_KEY, serializedGlobal); cacheIsScreenLocked(global);
cacheGlobal(reduceGlobal(global));
} }
export function serializeGlobal<T extends GlobalState>(global: T) { function reduceGlobal<T extends GlobalState>(global: T) {
const reducedGlobal: GlobalState = { const reducedGlobal: GlobalState = {
...INITIAL_GLOBAL_STATE, ...INITIAL_GLOBAL_STATE,
...pick(global, [ ...pick(global, [
@ -314,7 +339,11 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
]), ]),
}; };
return JSON.stringify(reducedGlobal); return reducedGlobal;
}
export function serializeGlobal<T extends GlobalState>(global: T) {
return JSON.stringify(reduceGlobal(global));
} }
function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['customEmojis'] { function reduceCustomEmojis<T extends GlobalState>(global: T): GlobalState['customEmojis'] {

View File

@ -2,50 +2,30 @@ import './intervals';
import type { ActionReturnType, GlobalState } from './types'; import type { ActionReturnType, GlobalState } from './types';
import { IS_MOCKED_CLIENT } from '../config';
import { isCacheApiSupported } from '../util/cacheApi'; import { isCacheApiSupported } from '../util/cacheApi';
import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMultitabRole'; import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMultitabRole';
import { initGlobal } from '../util/init';
import { cloneDeep } from '../util/iteratees'; import { cloneDeep } from '../util/iteratees';
import { isLocalMessageId } from '../util/messageKey'; import { isLocalMessageId } from '../util/messageKey';
import { Bundles, loadBundle } from '../util/moduleLoader'; import { Bundles, loadBundle } from '../util/moduleLoader';
import { parseLocationHash } from '../util/routing'; import { parseLocationHash } from '../util/routing';
import { clearStoredSession } from '../util/sessions';
import { updatePeerColors } from '../util/theme'; import { updatePeerColors } from '../util/theme';
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment'; import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
import { initializeChatMediaSearchResults } from './reducers/localSearch'; import { initializeChatMediaSearchResults } from './reducers/localSearch';
import { updateTabState } from './reducers/tabs'; import { updateTabState } from './reducers/tabs';
import { initCache, loadCache } from './cache'; import { initCache } from './cache';
import { import {
addActionHandler, getGlobal, setGlobal, addActionHandler, getGlobal, setGlobal,
} from './index'; } from './index';
import { INITIAL_GLOBAL_STATE, INITIAL_TAB_STATE } from './initialState'; import { INITIAL_TAB_STATE } from './initialState';
import { replaceTabThreadParam, replaceThreadParam, updatePasscodeSettings } from './reducers'; import { replaceTabThreadParam, replaceThreadParam } from './reducers';
import { selectTabState, selectThreadParam } from './selectors'; import { selectTabState, selectThreadParam } from './selectors';
initCache(); initCache();
addActionHandler('initShared', (prevGlobal, actions, payload): ActionReturnType => { addActionHandler('initShared', async (prevGlobal, actions, payload): Promise<void> => {
const { force } = payload || {}; const { force } = payload || {};
if (!force && 'byTabId' in prevGlobal) return prevGlobal; await initGlobal(force, prevGlobal);
const initial = cloneDeep(INITIAL_GLOBAL_STATE);
let global = loadCache(initial) || initial;
if (IS_MOCKED_CLIENT) global.authState = 'authorizationStateReady';
const { hasPasscode, isScreenLocked } = global.passcode;
if (hasPasscode && !isScreenLocked) {
global = updatePasscodeSettings(global, {
isScreenLocked: true,
});
clearStoredSession();
}
if (force) {
global.byTabId = prevGlobal.byTabId;
}
return global;
}); });
addActionHandler('init', (global, actions, payload): ActionReturnType => { addActionHandler('init', (global, actions, payload): ActionReturnType => {

View File

@ -15,6 +15,7 @@ import { enableStrict, requestMutation } from './lib/fasterdom/fasterdom';
import { selectTabState } from './global/selectors'; import { selectTabState } from './global/selectors';
import { betterView } from './util/betterView'; import { betterView } from './util/betterView';
import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole'; import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole';
import { initGlobal } from './util/init';
import { initLocalization } from './util/localization'; import { initLocalization } from './util/localization';
import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/multitab'; import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/multitab';
import { checkAndAssignPermanentWebVersion } from './util/permanentWebVersion'; import { checkAndAssignPermanentWebVersion } from './util/permanentWebVersion';
@ -57,7 +58,7 @@ async function init() {
}); });
} }
getActions().initShared(); await initGlobal();
getActions().init(); getActions().init();
getActions().updateShouldEnableDebugLog(); getActions().updateShouldEnableDebugLog();

37
src/util/init.ts Normal file
View File

@ -0,0 +1,37 @@
import type { GlobalState } from '../global/types';
import { IS_MOCKED_CLIENT } from '../config';
import { loadCache } from '../global/cache';
import {
getGlobal, setGlobal,
} from '../global/index';
import { INITIAL_GLOBAL_STATE } from '../global/initialState';
import { updatePasscodeSettings } from '../global/reducers';
import { cloneDeep } from './iteratees';
import { clearStoredSession } from './sessions';
export async function initGlobal(force: boolean = false, prevGlobal?: GlobalState) {
prevGlobal = prevGlobal || getGlobal();
if (!force && 'byTabId' in prevGlobal) {
return;
}
const initial = cloneDeep(INITIAL_GLOBAL_STATE);
let global = await loadCache(initial) || initial;
if (IS_MOCKED_CLIENT) global.authState = 'authorizationStateReady';
const { hasPasscode, isScreenLocked } = global.passcode;
if (hasPasscode && !isScreenLocked) {
global = updatePasscodeSettings(global, {
isScreenLocked: true,
});
clearStoredSession();
}
if (force) {
global.byTabId = prevGlobal.byTabId;
}
setGlobal(global);
}

View File

@ -1,7 +1,8 @@
import type { ApiSessionData } from '../api/types'; import type { ApiSessionData } from '../api/types';
import { import {
DEBUG, GLOBAL_STATE_CACHE_KEY, SESSION_USER_KEY, DEBUG, IS_SCREEN_LOCKED_CACHE_KEY,
SESSION_USER_KEY,
} from '../config'; } from '../config';
const DC_IDS = [1, 2, 3, 4, 5]; const DC_IDS = [1, 2, 3, 4, 5];
@ -109,7 +110,5 @@ export function importTestSession() {
} }
function checkSessionLocked() { function checkSessionLocked() {
const stateFromCache = JSON.parse(localStorage.getItem(GLOBAL_STATE_CACHE_KEY) || '{}'); return localStorage.getItem(IS_SCREEN_LOCKED_CACHE_KEY) === 'true';
return Boolean(stateFromCache?.passcode?.isScreenLocked);
} }