diff --git a/src/api/gramjs/methods/client.ts b/src/api/gramjs/methods/client.ts index 90fadc93b..306442880 100644 --- a/src/api/gramjs/methods/client.ts +++ b/src/api/gramjs/methods/client.ts @@ -30,7 +30,12 @@ import { } from '../helpers'; import { ChatAbortController } from '../ChatAbortController'; import { - updateChannelState, getDifference, init as initUpdatesManager, processUpdate, reset as resetUpdatesManager, + updateChannelState, + getDifference, + init as initUpdatesManager, + processUpdate, + reset as resetUpdatesManager, + scheduleGetChannelDifference, } from '../updateManager'; const DEFAULT_USER_AGENT = 'Unknown UserAgent'; @@ -482,3 +487,7 @@ export function setAllowHttpTransport(allowHttpTransport: boolean) { export function setShouldDebugExportedSenders(value: boolean) { client.setShouldDebugExportedSenders(value); } + +export function requestChannelDifference(channelId: string) { + scheduleGetChannelDifference(channelId); +} diff --git a/src/api/gramjs/methods/index.ts b/src/api/gramjs/methods/index.ts index 814e46b5e..1661f1459 100644 --- a/src/api/gramjs/methods/index.ts +++ b/src/api/gramjs/methods/index.ts @@ -1,6 +1,6 @@ export { destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference, abortChatRequests, abortRequestGroup, - setForceHttpTransport, setShouldDebugExportedSenders, setAllowHttpTransport, + setForceHttpTransport, setShouldDebugExportedSenders, setAllowHttpTransport, requestChannelDifference, } from './client'; export { diff --git a/src/api/gramjs/updateManager.ts b/src/api/gramjs/updateManager.ts index d17993b23..8dcc598ad 100644 --- a/src/api/gramjs/updateManager.ts +++ b/src/api/gramjs/updateManager.ts @@ -6,7 +6,7 @@ import { UpdateConnectionState, UpdateServerTimeOffset } from '../../lib/gramjs/ import { DEBUG } from '../../config'; import localDb from './localDb'; import SortedQueue from '../../util/SortedQueue'; -import { dispatchUserAndChatUpdates, requestSync, updater } from './updater'; +import { dispatchUserAndChatUpdates, sendUpdate, updater } from './updater'; import { addEntitiesToLocalDb } from './helpers'; import { buildInputEntity } from './gramjsBuilders'; import { buildApiPeerId } from './apiBuilders/peers'; @@ -17,8 +17,8 @@ export type State = { pts: number; qts: number; }; -type SeqUpdate = GramJs.Updates | GramJs.UpdatesCombined; -type PtsUpdate = GramJs.TypeUpdate & { pts: number }; +type SeqUpdate = (GramJs.Updates | GramJs.UpdatesCombined) & { _isFromDifference?: true }; +type PtsUpdate = GramJs.TypeUpdate & { pts: number } & { _isFromDifference?: true }; const COMMON_BOX_QUEUE_ID = '0'; const CHANNEL_DIFFERENCE_LIMIT = 1000; @@ -49,7 +49,7 @@ export function applyState(state: State) { localDb.commonBoxState.qts = state.qts; } -export function processUpdate(update: Update) { +export function processUpdate(update: Update, isFromDifference?: boolean) { if (update instanceof UpdateConnectionState) { if (update.state === UpdateConnectionState.connected && isInited) { scheduleGetDifference(); @@ -70,6 +70,11 @@ export function processUpdate(update: Update) { } if (update instanceof GramJs.Updates || update instanceof GramJs.UpdatesCombined) { + if (isFromDifference) { + // eslint-disable-next-line no-underscore-dangle + (update as SeqUpdate)._isFromDifference = true; + } + saveSeqUpdate(update); return; } @@ -79,6 +84,10 @@ export function processUpdate(update: Update) { getChannelDifference(getUpdateChannelId(update)); return; } + if (isFromDifference) { + // eslint-disable-next-line no-underscore-dangle + (update as PtsUpdate)._isFromDifference = true; + } savePtsUpdate(update); return; } @@ -165,7 +174,8 @@ function popSeqQueue() { const localSeq = localDb.commonBoxState.seq; const seqStart = 'seqStart' in update ? update.seqStart : update.seq; - if (seqStart === 0) { + // eslint-disable-next-line no-underscore-dangle + if (seqStart === 0 || (update._isFromDifference && seqStart >= localSeq + 1)) { applyUpdate(update); } else if (seqStart === localSeq + 1) { clearTimeout(seqTimeout); @@ -198,7 +208,10 @@ function popPtsQueue(channelId: string) { return; } - if (pts === localPts + ptsCount) { + // eslint-disable-next-line no-underscore-dangle + if (update._isFromDifference && pts >= localPts + ptsCount) { + applyUpdate(update); + } else if (pts === localPts + ptsCount) { clearTimeout(PTS_TIMEOUTS.get(channelId)); PTS_TIMEOUTS.delete(channelId); @@ -216,7 +229,7 @@ function popPtsQueue(channelId: string) { popPtsQueue(channelId); } -function scheduleGetChannelDifference(channelId: string) { +export function scheduleGetChannelDifference(channelId: string) { if (PTS_TIMEOUTS.has(channelId)) return; const timeout = setTimeout(async () => { @@ -258,6 +271,11 @@ export async function getDifference() { return; } + sendUpdate({ + '@type': 'updateFetchingDifference', + isFetching: true, + }); + const response = await invoke(new GramJs.updates.GetDifference({ pts: localDb.commonBoxState.pts, date: localDb.commonBoxState.date, @@ -275,6 +293,10 @@ export async function getDifference() { if (response instanceof GramJs.updates.DifferenceEmpty) { localDb.commonBoxState.seq = response.seq; localDb.commonBoxState.date = response.date; + sendUpdate({ + '@type': 'updateFetchingDifference', + isFetching: false, + }); return; } @@ -285,7 +307,13 @@ export async function getDifference() { if (response instanceof GramJs.updates.DifferenceSlice) { getDifference(); + return; } + + sendUpdate({ + '@type': 'updateFetchingDifference', + isFetching: false, + }); } async function getChannelDifference(channelId: string) { @@ -336,7 +364,9 @@ async function getChannelDifference(channelId: string) { function forceSync() { reset(); - requestSync(); + sendUpdate({ + '@type': 'requestSync', + }); loadRemoteState(); } @@ -389,7 +419,7 @@ function processDifference( dispatchUserAndChatUpdates(difference.chats); difference.otherUpdates.forEach((update) => { - processUpdate(update); + processUpdate(update, true); }); } diff --git a/src/api/gramjs/updater.ts b/src/api/gramjs/updater.ts index e9aa623a0..544d52079 100644 --- a/src/api/gramjs/updater.ts +++ b/src/api/gramjs/updater.ts @@ -1,7 +1,7 @@ import type { GroupCallConnectionData } from '../../lib/secret-sauce'; import { Api as GramJs, connection } from '../../lib/gramjs'; import type { - ApiMessage, ApiMessageExtendedMediaPreview, ApiUpdateConnectionStateType, OnApiUpdate, + ApiMessage, ApiMessageExtendedMediaPreview, ApiUpdate, ApiUpdateConnectionStateType, OnApiUpdate, } from '../types'; import { DEBUG, GENERAL_TOPIC_ID } from '../../config'; @@ -117,10 +117,8 @@ export function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.T }); } -export function requestSync() { - onUpdate({ - '@type': 'requestSync', - }); +export function sendUpdate(update: ApiUpdate) { + onUpdate(update); } export function updater(update: Update) { diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index f113f71de..5de73537d 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -615,6 +615,11 @@ export type ApiUpdateMessageTranslations = { toLanguageCode: string; }; +export type ApiUpdateFetchingDifference = { + '@type': 'updateFetchingDifference'; + isFetching: boolean; +}; + export type ApiRequestReconnectApi = { '@type': 'requestReconnectApi'; }; @@ -649,7 +654,7 @@ export type ApiUpdate = ( ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton | ApiUpdateTranscribedAudio | ApiUpdateUserEmojiStatus | ApiUpdateMessageExtendedMedia | ApiUpdateConfig | ApiUpdateTopicNotifyExceptions | ApiUpdatePinnedTopic | ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses | - ApiUpdateRecentReactions | ApiRequestReconnectApi | ApiRequestSync + ApiUpdateRecentReactions | ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference ); export type OnApiUpdate = (update: ApiUpdate) => void; diff --git a/src/components/left/main/LeftMainHeader.tsx b/src/components/left/main/LeftMainHeader.tsx index 5eda65291..f95387ba6 100644 --- a/src/components/left/main/LeftMainHeader.tsx +++ b/src/components/left/main/LeftMainHeader.tsx @@ -93,7 +93,7 @@ type StateProps = hasPasscode?: boolean; canSetPasscode?: boolean; } - & Pick + & Pick & Pick; const CLEAR_DATE_SEARCH_PARAM = { date: undefined }; @@ -121,6 +121,7 @@ const LeftMainHeader: FC = ({ animationLevel, connectionState, isSyncing, + isFetchingDifference, isMessageListOpen, isConnectionStatusMinimized, areChatsLoaded, @@ -154,7 +155,12 @@ const LeftMainHeader: FC = ({ const archivedUnreadChatsCount = useFolderManagerForUnreadCounters()[ARCHIVED_FOLDER_ID]?.chatsCount || 0; const { connectionStatus, connectionStatusText, connectionStatusPosition } = useConnectionStatus( - lang, connectionState, isSyncing, isMessageListOpen, isConnectionStatusMinimized, !areChatsLoaded, + lang, + connectionState, + isSyncing || isFetchingDifference, + isMessageListOpen, + isConnectionStatusMinimized, + !areChatsLoaded, ); const handleLockScreenHotkey = useLastCallback((e: KeyboardEvent) => { @@ -485,7 +491,7 @@ export default memo(withGlobal( query: searchQuery, fetchingStatus, chatId, date, } = tabState.globalSearch; const { - currentUserId, connectionState, isSyncing, archiveSettings, + currentUserId, connectionState, isSyncing, archiveSettings, isFetchingDifference, } = global; const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey; @@ -499,6 +505,7 @@ export default memo(withGlobal( animationLevel, connectionState, isSyncing, + isFetchingDifference, isMessageListOpen: Boolean(selectCurrentMessageList(global)), isConnectionStatusMinimized, isCurrentUserPremium: selectIsCurrentUserPremium(global), diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index de1cb434f..a9aec4189 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -105,7 +105,8 @@ type StateProps = { shouldSkipHistoryAnimations?: boolean; currentTransitionKey: number; connectionState?: GlobalState['connectionState']; - isSyncing?: GlobalState['isSyncing']; + isSyncing?: boolean; + isFetchingDifference?: boolean; }; const MiddleHeader: FC = ({ @@ -132,6 +133,7 @@ const MiddleHeader: FC = ({ currentTransitionKey, connectionState, isSyncing, + isFetchingDifference, getCurrentPinnedIndexes, getLoadingPinnedId, onFocusPinnedMessage, @@ -319,7 +321,7 @@ const MiddleHeader: FC = ({ } }, [shouldUseStackedToolsClass, canRevealTools, canToolsCollideWithChatInfo, isRightColumnShown]); - const { connectionStatusText } = useConnectionStatus(lang, connectionState, isSyncing, true); + const { connectionStatusText } = useConnectionStatus(lang, connectionState, isSyncing || isFetchingDifference, true); function renderInfo() { if (messageListType === 'thread') { @@ -524,6 +526,7 @@ export default memo(withGlobal( currentTransitionKey: Math.max(0, messageLists.length - 1), connectionState: global.connectionState, isSyncing: global.isSyncing, + isFetchingDifference: global.isFetchingDifference, hasButtonInHeader: canStartBot || canRestartBot || canSubscribe || shouldSendJoinRequest, }; diff --git a/src/global/actions/api/initial.ts b/src/global/actions/api/initial.ts index 593162944..b6e101e91 100644 --- a/src/global/actions/api/initial.ts +++ b/src/global/actions/api/initial.ts @@ -179,6 +179,12 @@ addActionHandler('signOut', async (global, actions, payload): Promise => { } }); +addActionHandler('requestChannelDifference', (global, actions, payload): ActionReturnType => { + const { chatId } = payload; + + void callApi('requestChannelDifference', chatId); +}); + addActionHandler('reset', (global, actions): ActionReturnType => { clearStoredSession(); clearEncryptedSession(); diff --git a/src/global/actions/apiUpdaters/initial.ts b/src/global/actions/apiUpdaters/initial.ts index cd290a702..da9bac26e 100644 --- a/src/global/actions/apiUpdaters/initial.ts +++ b/src/global/actions/apiUpdaters/initial.ts @@ -23,6 +23,8 @@ import { clearWebTokenAuth } from '../../../util/routing'; import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { updateTabState } from '../../reducers/tabs'; import { setServerTimeOffset } from '../../../util/serverTime'; +import { isChatChannel, isChatSuperGroup } from '../../helpers'; +import { unique } from '../../../util/iteratees'; addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { switch (update['@type']) { @@ -59,7 +61,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { break; case 'requestReconnectApi': - global = getGlobal(); global = { ...global, isSynced: false }; setGlobal(global); @@ -74,6 +75,11 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { actions.sync(); break; + case 'updateFetchingDifference': + global = { ...global, isFetchingDifference: update.isFetching }; + setGlobal(global); + break; + case 'error': { Object.values(global.byTabId).forEach(({ id: tabId }) => { const paymentShippingError = getShippingError(update.error); @@ -216,6 +222,19 @@ function onUpdateConnectionState( }; setGlobal(global); + const channelStackIds = Object.values(global.byTabId) + .flatMap((tab) => tab.messageLists) + .map((messageList) => messageList.chatId) + .filter((chatId) => { + const chat = global.chats.byId[chatId]; + return isChatChannel(chat) || isChatSuperGroup(chat); + }); + if (connectionState === 'connectionStateReady' && channelStackIds.length) { + unique(channelStackIds).forEach((chatId) => { + actions.requestChannelDifference({ chatId }); + }); + } + if (connectionState === 'connectionStateBroken') { actions.signOut({ forceInitApi: true }); } diff --git a/src/global/types.ts b/src/global/types.ts index d09d3435f..f90e4b7c0 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -590,6 +590,7 @@ export type GlobalState = { isSyncing?: boolean; isUpdateAvailable?: boolean; isSynced?: boolean; + isFetchingDifference?: boolean; leftColumnWidth?: number; lastIsChatInfoShown?: boolean; initialUnreadNotifications?: number; @@ -1592,6 +1593,9 @@ export interface ActionPayloads { // Initial signOut: { forceInitApi?: boolean } | undefined; + requestChannelDifference: { + chatId: string; + }; // Misc setInstallPrompt: { canInstall: boolean } & WithTabId; diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts index c74d318fd..d1f070583 100644 --- a/src/hooks/useConnectionStatus.ts +++ b/src/hooks/useConnectionStatus.ts @@ -18,7 +18,7 @@ type ConnectionStatusPosition = export default function useConnectionStatus( lang: LangFn, connectionState: GlobalState['connectionState'], - isSyncing: GlobalState['isSyncing'], + isSyncing: boolean | undefined, hasMiddleHeader: boolean, isMinimized?: boolean, isDisabled?: boolean,