395 lines
10 KiB
TypeScript
395 lines
10 KiB
TypeScript
import { addCallback, removeCallback } from '../lib/teact/teactn';
|
|
|
|
import { addActionHandler, getGlobal } from './index';
|
|
|
|
import { GlobalState } from './types';
|
|
import { MAIN_THREAD_ID } from '../api/types';
|
|
|
|
import { onBeforeUnload, onIdle, throttle } from '../util/schedulers';
|
|
import {
|
|
DEBUG,
|
|
GLOBAL_STATE_CACHE_DISABLED,
|
|
GLOBAL_STATE_CACHE_KEY,
|
|
GLOBAL_STATE_CACHE_USER_LIST_LIMIT,
|
|
GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT,
|
|
GLOBAL_STATE_CACHE_CHATS_WITH_MESSAGES_LIMIT,
|
|
MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN,
|
|
DEFAULT_VOLUME,
|
|
DEFAULT_PLAYBACK_RATE,
|
|
ALL_FOLDER_ID,
|
|
ARCHIVED_FOLDER_ID,
|
|
} from '../config';
|
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../util/environment';
|
|
import { isHeavyAnimating } from '../hooks/useHeavyAnimationCheck';
|
|
import { pick, unique } from '../util/iteratees';
|
|
import {
|
|
selectCurrentChat,
|
|
selectCurrentMessageList,
|
|
selectVisibleUsers,
|
|
} from './selectors';
|
|
import { hasStoredSession } from '../util/sessions';
|
|
import { INITIAL_STATE } from './initialState';
|
|
import { parseLocationHash } from '../util/routing';
|
|
import { isUserId } from './helpers';
|
|
import { getOrderedIds } from '../util/folderManager';
|
|
|
|
const UPDATE_THROTTLE = 5000;
|
|
|
|
const updateCacheThrottled = throttle(() => onIdle(updateCache), UPDATE_THROTTLE, false);
|
|
|
|
let isCaching = false;
|
|
let unsubscribeFromBeforeUnload: NoneToVoidFunction | undefined;
|
|
|
|
export function initCache() {
|
|
if (GLOBAL_STATE_CACHE_DISABLED) {
|
|
return;
|
|
}
|
|
|
|
addActionHandler('saveSession', () => {
|
|
if (isCaching) {
|
|
return;
|
|
}
|
|
|
|
setupCaching();
|
|
});
|
|
|
|
addActionHandler('reset', () => {
|
|
localStorage.removeItem(GLOBAL_STATE_CACHE_KEY);
|
|
|
|
if (!isCaching) {
|
|
return;
|
|
}
|
|
|
|
clearCaching();
|
|
});
|
|
}
|
|
|
|
export function loadCache(initialState: GlobalState): GlobalState | undefined {
|
|
if (GLOBAL_STATE_CACHE_DISABLED) {
|
|
return undefined;
|
|
}
|
|
|
|
if (hasStoredSession(true)) {
|
|
setupCaching();
|
|
|
|
return readCache(initialState);
|
|
} else {
|
|
clearCaching();
|
|
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function setupCaching() {
|
|
isCaching = true;
|
|
unsubscribeFromBeforeUnload = onBeforeUnload(updateCache, true);
|
|
window.addEventListener('blur', updateCache);
|
|
addCallback(updateCacheThrottled);
|
|
}
|
|
|
|
function clearCaching() {
|
|
isCaching = false;
|
|
removeCallback(updateCacheThrottled);
|
|
window.removeEventListener('blur', updateCache);
|
|
if (unsubscribeFromBeforeUnload) {
|
|
unsubscribeFromBeforeUnload();
|
|
}
|
|
}
|
|
|
|
function readCache(initialState: GlobalState): 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;
|
|
|
|
if (DEBUG) {
|
|
// eslint-disable-next-line no-console
|
|
console.timeEnd('global-state-cache-read');
|
|
}
|
|
|
|
if (cached) {
|
|
migrateCache(cached, initialState);
|
|
}
|
|
|
|
const newState = {
|
|
...initialState,
|
|
...cached,
|
|
};
|
|
|
|
const parsedMessageList = !IS_SINGLE_COLUMN_LAYOUT ? parseLocationHash() : undefined;
|
|
|
|
return {
|
|
...newState,
|
|
messages: {
|
|
...newState.messages,
|
|
messageLists: parsedMessageList ? [parsedMessageList] : [],
|
|
},
|
|
};
|
|
}
|
|
|
|
function migrateCache(cached: GlobalState, initialState: GlobalState) {
|
|
// Migrate from legacy setting names
|
|
if ('shouldAutoDownloadMediaFromContacts' in cached.settings.byKey) {
|
|
const {
|
|
shouldAutoDownloadMediaFromContacts,
|
|
shouldAutoDownloadMediaInPrivateChats,
|
|
shouldAutoDownloadMediaInGroups,
|
|
shouldAutoDownloadMediaInChannels,
|
|
shouldAutoPlayVideos,
|
|
shouldAutoPlayGifs,
|
|
...rest
|
|
} = cached.settings.byKey;
|
|
|
|
cached.settings.byKey = {
|
|
...rest,
|
|
canAutoLoadPhotoFromContacts: shouldAutoDownloadMediaFromContacts,
|
|
canAutoLoadVideoFromContacts: shouldAutoDownloadMediaFromContacts,
|
|
canAutoLoadPhotoInPrivateChats: shouldAutoDownloadMediaInPrivateChats,
|
|
canAutoLoadVideoInPrivateChats: shouldAutoDownloadMediaInPrivateChats,
|
|
canAutoLoadPhotoInGroups: shouldAutoDownloadMediaInGroups,
|
|
canAutoLoadVideoInGroups: shouldAutoDownloadMediaInGroups,
|
|
canAutoLoadPhotoInChannels: shouldAutoDownloadMediaInChannels,
|
|
canAutoLoadVideoInChannels: shouldAutoDownloadMediaInChannels,
|
|
canAutoPlayVideos: shouldAutoPlayVideos,
|
|
canAutoPlayGifs: shouldAutoPlayGifs,
|
|
};
|
|
}
|
|
|
|
// Pre-fill settings with defaults
|
|
cached.settings.byKey = {
|
|
...initialState.settings.byKey,
|
|
...cached.settings.byKey,
|
|
};
|
|
|
|
cached.settings.themes = {
|
|
...initialState.settings.themes,
|
|
...cached.settings.themes,
|
|
};
|
|
|
|
cached.chatFolders = {
|
|
...initialState.chatFolders,
|
|
...cached.chatFolders,
|
|
};
|
|
|
|
if (!cached.stickers.greeting) {
|
|
cached.stickers.greeting = initialState.stickers.greeting;
|
|
}
|
|
|
|
if (!cached.activeDownloads) {
|
|
cached.activeDownloads = {
|
|
byChatId: {},
|
|
};
|
|
}
|
|
|
|
if (!cached.serviceNotifications) {
|
|
cached.serviceNotifications = [];
|
|
}
|
|
|
|
if (cached.audioPlayer.volume === undefined) {
|
|
cached.audioPlayer.volume = DEFAULT_VOLUME;
|
|
}
|
|
|
|
if (cached.audioPlayer.playbackRate === undefined) {
|
|
cached.audioPlayer.playbackRate = DEFAULT_PLAYBACK_RATE;
|
|
}
|
|
|
|
if (cached.mediaViewer.volume === undefined) {
|
|
cached.mediaViewer.volume = DEFAULT_VOLUME;
|
|
}
|
|
|
|
if (cached.mediaViewer.playbackRate === undefined) {
|
|
cached.mediaViewer.playbackRate = DEFAULT_PLAYBACK_RATE;
|
|
}
|
|
|
|
if (!cached.groupCalls) {
|
|
cached.groupCalls = initialState.groupCalls;
|
|
}
|
|
|
|
if (!cached.users.statusesById) {
|
|
cached.users.statusesById = {};
|
|
}
|
|
|
|
if (!cached.messages.sponsoredByChatId) {
|
|
cached.messages.sponsoredByChatId = {};
|
|
}
|
|
|
|
if (!cached.activeReactions) {
|
|
cached.activeReactions = {};
|
|
}
|
|
}
|
|
|
|
function updateCache() {
|
|
if (!isCaching || isHeavyAnimating()) {
|
|
return;
|
|
}
|
|
|
|
const global = getGlobal();
|
|
|
|
if (global.isLoggingOut) {
|
|
return;
|
|
}
|
|
|
|
const reducedGlobal: GlobalState = {
|
|
...INITIAL_STATE,
|
|
...pick(global, [
|
|
'authState',
|
|
'authPhoneNumber',
|
|
'authRememberMe',
|
|
'authNearestCountry',
|
|
'currentUserId',
|
|
'contactList',
|
|
'topPeers',
|
|
'topInlineBots',
|
|
'recentEmojis',
|
|
'push',
|
|
'shouldShowContextMenuHint',
|
|
'leftColumnWidth',
|
|
'serviceNotifications',
|
|
]),
|
|
audioPlayer: {
|
|
volume: global.audioPlayer.volume,
|
|
playbackRate: global.audioPlayer.playbackRate,
|
|
isMuted: global.audioPlayer.isMuted,
|
|
},
|
|
mediaViewer: {
|
|
volume: global.mediaViewer.volume,
|
|
playbackRate: global.mediaViewer.playbackRate,
|
|
isMuted: global.mediaViewer.isMuted,
|
|
},
|
|
isChatInfoShown: reduceShowChatInfo(global),
|
|
users: reduceUsers(global),
|
|
chats: reduceChats(global),
|
|
messages: reduceMessages(global),
|
|
globalSearch: {
|
|
recentlyFoundChatIds: global.globalSearch.recentlyFoundChatIds,
|
|
},
|
|
settings: reduceSettings(global),
|
|
chatFolders: reduceChatFolders(global),
|
|
groupCalls: reduceGroupCalls(global),
|
|
availableReactions: reduceAvailableReactions(global),
|
|
};
|
|
|
|
const json = JSON.stringify(reducedGlobal);
|
|
localStorage.setItem(GLOBAL_STATE_CACHE_KEY, json);
|
|
}
|
|
|
|
function reduceShowChatInfo(global: GlobalState): boolean {
|
|
return window.innerWidth > MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN
|
|
? global.isChatInfoShown
|
|
: false;
|
|
}
|
|
|
|
function reduceUsers(global: GlobalState): GlobalState['users'] {
|
|
const { users: { byId, statusesById }, currentUserId } = global;
|
|
const { chatId: currentChatId } = selectCurrentMessageList(global) || {};
|
|
const visibleUserIds = selectVisibleUsers(global)?.map(({ id }) => id);
|
|
|
|
const idsToSave = unique([
|
|
...currentUserId ? [currentUserId] : [],
|
|
...currentChatId && isUserId(currentChatId) ? [currentChatId] : [],
|
|
...visibleUserIds || [],
|
|
...global.topPeers.userIds || [],
|
|
...getOrderedIds(ALL_FOLDER_ID)?.filter(isUserId) || [],
|
|
...getOrderedIds(ARCHIVED_FOLDER_ID)?.filter(isUserId) || [],
|
|
...global.contactList?.userIds || [],
|
|
...global.globalSearch.recentlyFoundChatIds?.filter(isUserId) || [],
|
|
...Object.keys(byId),
|
|
]).slice(0, GLOBAL_STATE_CACHE_USER_LIST_LIMIT);
|
|
|
|
return {
|
|
byId: pick(byId, idsToSave),
|
|
statusesById: pick(statusesById, idsToSave),
|
|
};
|
|
}
|
|
|
|
function reduceChats(global: GlobalState): GlobalState['chats'] {
|
|
const { chats: { byId }, currentUserId } = global;
|
|
const currentChat = selectCurrentChat(global);
|
|
const idsToSave = unique([
|
|
...currentUserId ? [currentUserId] : [],
|
|
...currentChat ? [currentChat.id] : [],
|
|
...getOrderedIds(ALL_FOLDER_ID) || [],
|
|
...getOrderedIds(ARCHIVED_FOLDER_ID) || [],
|
|
...global.globalSearch.recentlyFoundChatIds || [],
|
|
...Object.keys(byId),
|
|
]).slice(0, GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT);
|
|
|
|
return {
|
|
...global.chats,
|
|
isFullyLoaded: {},
|
|
byId: pick(global.chats.byId, idsToSave),
|
|
};
|
|
}
|
|
|
|
function reduceMessages(global: GlobalState): GlobalState['messages'] {
|
|
const { currentUserId } = global;
|
|
const byChatId: GlobalState['messages']['byChatId'] = {};
|
|
const { chatId: currentChatId } = selectCurrentMessageList(global) || {};
|
|
const chatIdsToSave = [
|
|
...currentChatId ? [currentChatId] : [],
|
|
...currentUserId ? [currentUserId] : [],
|
|
...getOrderedIds(ALL_FOLDER_ID)?.slice(0, GLOBAL_STATE_CACHE_CHATS_WITH_MESSAGES_LIMIT) || [],
|
|
];
|
|
|
|
chatIdsToSave.forEach((chatId) => {
|
|
const current = global.messages.byChatId[chatId];
|
|
if (!current) {
|
|
return;
|
|
}
|
|
|
|
const mainThread = current.threadsById[MAIN_THREAD_ID];
|
|
if (!mainThread || !mainThread.viewportIds) {
|
|
return;
|
|
}
|
|
|
|
byChatId[chatId] = {
|
|
byId: pick(current.byId, mainThread.viewportIds),
|
|
threadsById: {
|
|
[MAIN_THREAD_ID]: mainThread,
|
|
},
|
|
};
|
|
});
|
|
|
|
return {
|
|
byChatId,
|
|
messageLists: [],
|
|
sponsoredByChatId: {},
|
|
};
|
|
}
|
|
|
|
function reduceSettings(global: GlobalState): GlobalState['settings'] {
|
|
const { byKey, themes } = global.settings;
|
|
|
|
return {
|
|
byKey,
|
|
themes,
|
|
privacy: {},
|
|
notifyExceptions: {},
|
|
};
|
|
}
|
|
|
|
function reduceChatFolders(global: GlobalState): GlobalState['chatFolders'] {
|
|
return {
|
|
...global.chatFolders,
|
|
activeChatFolder: 0,
|
|
};
|
|
}
|
|
|
|
function reduceGroupCalls(global: GlobalState): GlobalState['groupCalls'] {
|
|
return {
|
|
...global.groupCalls,
|
|
byId: {},
|
|
activeGroupCallId: undefined,
|
|
isGroupCallPanelHidden: undefined,
|
|
isFallbackConfirmOpen: undefined,
|
|
};
|
|
}
|
|
|
|
function reduceAvailableReactions(global: GlobalState): GlobalState['availableReactions'] {
|
|
return global.availableReactions
|
|
?.map((r) => pick(r, ['reaction', 'staticIcon', 'title', 'isInactive']));
|
|
}
|