import { addActionHandler, getGlobal, setGlobal, getActions, } from '../../index'; import type { ApiChat, ApiMessage } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; import type { Thread } from '../../types'; import { DEBUG, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID, } from '../../../config'; import { callApi } from '../../../api/gramjs'; import { buildCollectionByKey } from '../../../util/iteratees'; import { updateUsers, updateChats, updateThreadInfos, updateListedIds, safeReplaceViewportIds, addChatMessagesById, updateThread, } from '../../reducers'; import { selectCurrentMessageList, selectDraft, selectChatMessage, selectThreadInfo, selectEditingId, selectEditingDraft, } from '../../selectors'; import { init as initFolderManager } from '../../../util/folderManager'; const RELEASE_STATUS_TIMEOUT = 15000; // 15 sec; let releaseStatusTimeout: number | undefined; addActionHandler('sync', () => { if (DEBUG) { // eslint-disable-next-line no-console console.log('>>> START SYNC'); } if (releaseStatusTimeout) { clearTimeout(releaseStatusTimeout); } setGlobal({ ...getGlobal(), isSyncing: true }); // Workaround for `isSyncing = true` sometimes getting stuck for some reason releaseStatusTimeout = window.setTimeout(() => { setGlobal({ ...getGlobal(), isSyncing: false }); releaseStatusTimeout = undefined; }, RELEASE_STATUS_TIMEOUT); const { loadAllChats, preloadTopChatMessages } = getActions(); loadAllChats({ listType: 'active', shouldReplace: true, onReplace: async () => { await loadAndReplaceMessages(); setGlobal({ ...getGlobal(), lastSyncTime: Date.now(), isSyncing: false, }); if (DEBUG) { // eslint-disable-next-line no-console console.log('>>> FINISH SYNC'); } initFolderManager(); loadAllChats({ listType: 'archived', shouldReplace: true }); void callApi('fetchCurrentUser'); preloadTopChatMessages(); }, }); }); async function loadAndReplaceMessages() { let areMessagesLoaded = false; let global = getGlobal(); // Memoize drafts const draftChatIds = Object.keys(global.messages.byChatId); const draftsByChatId = draftChatIds.reduce>>((acc, chatId) => { acc[chatId] = {}; acc[chatId].draft = selectDraft(global, chatId, MAIN_THREAD_ID); acc[chatId].editingId = selectEditingId(global, chatId, MAIN_THREAD_ID); acc[chatId].editingDraft = selectEditingDraft(global, chatId, MAIN_THREAD_ID); return acc; }, {}); const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {}; const currentChat = currentChatId ? global.chats.byId[currentChatId] : undefined; if (currentChatId && currentChat) { const result = await loadTopMessages(currentChat); global = getGlobal(); const { chatId: newCurrentChatId } = selectCurrentMessageList(global) || {}; const threadInfo = currentThreadId && selectThreadInfo(global, currentChatId, currentThreadId); if (result && newCurrentChatId === currentChatId) { const currentMessageListInfo = global.messages.byChatId[currentChatId]; const localMessages = currentChatId === SERVICE_NOTIFICATIONS_USER_ID ? global.serviceNotifications.map(({ message }) => message) : []; const allMessages = ([] as ApiMessage[]).concat(result.messages, localMessages); const byId = buildCollectionByKey(allMessages, 'id'); const listedIds = Object.keys(byId).map(Number); global = { ...global, messages: { ...global.messages, byChatId: {}, }, }; global = addChatMessagesById(global, currentChatId, byId); global = updateListedIds(global, currentChatId, MAIN_THREAD_ID, listedIds); global = safeReplaceViewportIds(global, currentChatId, MAIN_THREAD_ID, listedIds); if (currentThreadId && threadInfo && threadInfo.originChannelId) { const { originChannelId } = threadInfo; const currentMessageListInfoOrigin = global.messages.byChatId[originChannelId]; const resultOrigin = await loadTopMessages(global.chats.byId[originChannelId]); if (resultOrigin) { const byIdOrigin = buildCollectionByKey(resultOrigin.messages, 'id'); const listedIdsOrigin = Object.keys(byIdOrigin).map(Number); global = { ...global, messages: { ...global.messages, byChatId: { ...global.messages.byChatId, [threadInfo.originChannelId]: { byId: byIdOrigin, threadsById: { [MAIN_THREAD_ID]: { ...(currentMessageListInfoOrigin?.threadsById[MAIN_THREAD_ID]), listedIds: listedIdsOrigin, viewportIds: listedIdsOrigin, outlyingIds: undefined, }, }, }, [currentChatId]: { ...global.messages.byChatId[currentChatId], threadsById: { ...global.messages.byChatId[currentChatId].threadsById, [currentThreadId]: { ...(currentMessageListInfo?.threadsById[currentThreadId]), outlyingIds: undefined, }, }, }, }, }, }; } } global = updateChats(global, buildCollectionByKey(result.chats, 'id')); global = updateUsers(global, buildCollectionByKey(result.users, 'id')); global = updateThreadInfos(global, currentChatId, result.threadInfos); areMessagesLoaded = true; } } if (!areMessagesLoaded) { global = { ...global, messages: { ...global.messages, byChatId: {}, }, }; } // Restore drafts Object.keys(draftsByChatId).forEach((chatId) => { global = updateThread(global, chatId, MAIN_THREAD_ID, draftsByChatId[chatId]); }); setGlobal(global); const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer; if (audioChatId && audioMessageId && !selectChatMessage(global, audioChatId, audioMessageId)) { getActions().closeAudioPlayer(); } } function loadTopMessages(chat: ApiChat) { return callApi('fetchMessages', { chat, threadId: MAIN_THREAD_ID, offsetId: chat.lastReadInboxMessageId, addOffset: -(Math.round(MESSAGE_LIST_SLICE / 2) + 1), limit: MESSAGE_LIST_SLICE, }); }