2022-08-31 15:00:45 +02:00

208 lines
6.5 KiB
TypeScript

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<Record<string, Partial<Thread>>>((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,
});
}