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_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_NAME = 'tt-media';
export const MEDIA_CACHE_NAME_AVATARS = 'tt-media-avatars';

View File

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

View File

@ -2,50 +2,30 @@ import './intervals';
import type { ActionReturnType, GlobalState } from './types';
import { IS_MOCKED_CLIENT } from '../config';
import { isCacheApiSupported } from '../util/cacheApi';
import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMultitabRole';
import { initGlobal } from '../util/init';
import { cloneDeep } from '../util/iteratees';
import { isLocalMessageId } from '../util/messageKey';
import { Bundles, loadBundle } from '../util/moduleLoader';
import { parseLocationHash } from '../util/routing';
import { clearStoredSession } from '../util/sessions';
import { updatePeerColors } from '../util/theme';
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
import { initializeChatMediaSearchResults } from './reducers/localSearch';
import { updateTabState } from './reducers/tabs';
import { initCache, loadCache } from './cache';
import { initCache } from './cache';
import {
addActionHandler, getGlobal, setGlobal,
} from './index';
import { INITIAL_GLOBAL_STATE, INITIAL_TAB_STATE } from './initialState';
import { replaceTabThreadParam, replaceThreadParam, updatePasscodeSettings } from './reducers';
import { INITIAL_TAB_STATE } from './initialState';
import { replaceTabThreadParam, replaceThreadParam } from './reducers';
import { selectTabState, selectThreadParam } from './selectors';
initCache();
addActionHandler('initShared', (prevGlobal, actions, payload): ActionReturnType => {
addActionHandler('initShared', async (prevGlobal, actions, payload): Promise<void> => {
const { force } = payload || {};
if (!force && 'byTabId' in prevGlobal) return 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;
await initGlobal(force, prevGlobal);
});
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 { betterView } from './util/betterView';
import { establishMultitabRole, subscribeToMasterChange } from './util/establishMultitabRole';
import { initGlobal } from './util/init';
import { initLocalization } from './util/localization';
import { requestGlobal, subscribeToMultitabBroadcastChannel } from './util/multitab';
import { checkAndAssignPermanentWebVersion } from './util/permanentWebVersion';
@ -57,7 +58,7 @@ async function init() {
});
}
getActions().initShared();
await initGlobal();
getActions().init();
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 {
DEBUG, GLOBAL_STATE_CACHE_KEY, SESSION_USER_KEY,
DEBUG, IS_SCREEN_LOCKED_CACHE_KEY,
SESSION_USER_KEY,
} from '../config';
const DC_IDS = [1, 2, 3, 4, 5];
@ -109,7 +110,5 @@ export function importTestSession() {
}
function checkSessionLocked() {
const stateFromCache = JSON.parse(localStorage.getItem(GLOBAL_STATE_CACHE_KEY) || '{}');
return Boolean(stateFromCache?.passcode?.isScreenLocked);
return localStorage.getItem(IS_SCREEN_LOCKED_CACHE_KEY) === 'true';
}