GramJS: Validate updates order (#2957)

This commit is contained in:
Alexander Zinchuk 2023-07-05 13:13:46 +02:00
parent 4244c014cb
commit d160b2b4cb
88 changed files with 1265 additions and 718 deletions

View File

@ -0,0 +1,23 @@
export class ChatAbortController extends AbortController {
private threads = new Map<number, AbortController>();
public getThreadSignal(threadId: number): AbortSignal {
let controller = this.threads.get(threadId);
if (!controller) {
controller = new AbortController();
this.threads.set(threadId, controller);
}
return controller.signal;
}
public abortThread(threadId: number, reason?: string): void {
this.threads.get(threadId)?.abort(reason);
this.threads.delete(threadId);
}
public abort(reason?: string): void {
super.abort(reason);
this.threads.forEach((controller) => controller.abort(reason));
this.threads.clear();
}
}

View File

@ -83,25 +83,30 @@ export function addPhotoToLocalDb(photo: GramJs.TypePhoto) {
} }
} }
function addChatToLocalDb(chat: GramJs.Chat | GramJs.Channel, noOverwrite = false) { export function addChatToLocalDb(chat: GramJs.Chat | GramJs.Channel) {
const id = buildApiPeerId(chat.id, chat instanceof GramJs.Chat ? 'chat' : 'channel'); const id = buildApiPeerId(chat.id, chat instanceof GramJs.Chat ? 'chat' : 'channel');
if (!noOverwrite || !localDb.chats[id]) { const storedChat = localDb.chats[id];
localDb.chats[id] = chat;
} const isStoredMin = storedChat && 'min' in storedChat && storedChat.min;
const isChatMin = 'min' in chat && chat.min;
if (storedChat && !isStoredMin && isChatMin) return;
localDb.chats[id] = chat;
} }
export function addUserToLocalDb(user: GramJs.User, shouldOverwrite = false) { export function addUserToLocalDb(user: GramJs.User) {
const id = buildApiPeerId(user.id, 'user'); const id = buildApiPeerId(user.id, 'user');
if (shouldOverwrite || !localDb.users[id]) { const storedUser = localDb.users[id];
localDb.users[id] = user; if (storedUser && !storedUser.min && user.min) return;
}
localDb.users[id] = user;
} }
export function addEntitiesWithPhotosToLocalDb(entities: (GramJs.TypeUser | GramJs.TypeChat)[]) { export function addEntitiesToLocalDb(entities: (GramJs.TypeUser | GramJs.TypeChat)[]) {
entities.forEach((entity) => { entities.forEach((entity) => {
if (entity instanceof GramJs.User && entity.photo) { if (entity instanceof GramJs.User) {
addUserToLocalDb(entity); addUserToLocalDb(entity);
} else if ((entity instanceof GramJs.Chat || entity instanceof GramJs.Channel) && entity.photo) { } else if ((entity instanceof GramJs.Chat || entity instanceof GramJs.Channel)) {
addChatToLocalDb(entity); addChatToLocalDb(entity);
} }
}); });
@ -147,3 +152,10 @@ export function log(suffix: keyof typeof LOG_SUFFIX, ...data: any) {
); );
/* eslint-enable max-len */ /* eslint-enable max-len */
} }
export function isResponseUpdate<T extends GramJs.AnyRequest>(result: T['__response']): result is GramJs.TypeUpdate {
return result instanceof GramJs.UpdatesTooLong || result instanceof GramJs.UpdateShortMessage
|| result instanceof GramJs.UpdateShortChatMessage || result instanceof GramJs.UpdateShort
|| result instanceof GramJs.UpdatesCombined || result instanceof GramJs.Updates
|| result instanceof GramJs.UpdateShortSentMessage;
}

View File

@ -17,6 +17,9 @@ export interface LocalDb {
stickerSets: Record<string, GramJs.StickerSet>; stickerSets: Record<string, GramJs.StickerSet>;
photos: Record<string, GramJs.Photo>; photos: Record<string, GramJs.Photo>;
webDocuments: Record<string, GramJs.TypeWebDocument>; webDocuments: Record<string, GramJs.TypeWebDocument>;
commonBoxState: Record<string, number>;
channelPtsById: Record<string, number>;
} }
const channel = IS_MULTITAB_SUPPORTED ? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) : undefined; const channel = IS_MULTITAB_SUPPORTED ? new BroadcastChannel(DATA_BROADCAST_CHANNEL_NAME) : undefined;
@ -77,17 +80,24 @@ function convertToVirtualClass(value: any): any {
function createLocalDbInitial(initial?: LocalDb): LocalDb { function createLocalDbInitial(initial?: LocalDb): LocalDb {
return [ return [
'localMessages', 'chats', 'users', 'messages', 'documents', 'stickerSets', 'photos', 'webDocuments', 'localMessages', 'chats', 'users', 'messages', 'documents', 'stickerSets', 'photos', 'webDocuments',
'commonBoxState', 'channelPtsById',
] ]
.reduce((acc: Record<string, any>, key) => { .reduce((acc: Record<string, any>, key) => {
const value = initial?.[key as keyof LocalDb] ?? {}; const value = initial?.[key as keyof LocalDb] ?? {};
const valueVirtualClass = Object.keys(value).reduce((acc2, key2) => { const convertedValue = Object.keys(value).reduce((acc2, key2) => {
if (key === 'commonBoxState' || key === 'channelPtsById') {
const typedValue = value as Record<string, number>;
acc2[key2] = typedValue[key2];
return acc2;
}
acc2[key2] = convertToVirtualClass(value[key2]); acc2[key2] = convertToVirtualClass(value[key2]);
return acc2; return acc2;
}, {} as Record<string, any>); }, {} as Record<string, any>);
acc[key] = IS_MULTITAB_SUPPORTED acc[key] = IS_MULTITAB_SUPPORTED
? createProxy(key, valueVirtualClass) ? createProxy(key, convertedValue)
: valueVirtualClass; : convertedValue;
return acc; return acc;
}, {} as LocalDb) as LocalDb; }, {} as LocalDb) as LocalDb;
} }

View File

@ -22,7 +22,7 @@ import {
buildBotSwitchWebview, buildBotSwitchWebview,
} from '../apiBuilders/bots'; } from '../apiBuilders/bots';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers'; import { addEntitiesToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
import { omitVirtualClassFields } from '../apiBuilders/helpers'; import { omitVirtualClassFields } from '../apiBuilders/helpers';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { buildApiUrlAuthResult } from '../apiBuilders/misc'; import { buildApiUrlAuthResult } from '../apiBuilders/misc';
@ -104,7 +104,7 @@ export async function fetchInlineBotResults({
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
isGallery: Boolean(result.gallery), isGallery: Boolean(result.gallery),
@ -143,7 +143,7 @@ export async function sendInlineBotResult({
...(isSilent && { silent: true }), ...(isSilent && { silent: true }),
...(replyingTo && { replyToMsgId: replyingTo }), ...(replyingTo && { replyToMsgId: replyingTo }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), true); }));
} }
export async function startBot({ export async function startBot({
@ -159,7 +159,7 @@ export async function startBot({
peer: buildInputPeer(bot.id, bot.accessHash), peer: buildInputPeer(bot.id, bot.accessHash),
randomId, randomId,
startParam, startParam,
}), true); }));
} }
export async function requestWebView({ export async function requestWebView({
@ -313,7 +313,7 @@ export async function sendWebViewData({
buttonText, buttonText,
data, data,
randomId, randomId,
}), true); }));
} }
export async function loadAttachBots({ export async function loadAttachBots({
@ -326,7 +326,7 @@ export async function loadAttachBots({
})); }));
if (result instanceof GramJs.AttachMenuBots) { if (result instanceof GramJs.AttachMenuBots) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
hash: result.hash.toString(), hash: result.hash.toString(),
bots: buildCollectionByKey(result.bots.map(buildApiAttachBot), 'id'), bots: buildCollectionByKey(result.bots.map(buildApiAttachBot), 'id'),
@ -346,7 +346,7 @@ export async function loadAttachBot({
})); }));
if (result instanceof GramJs.AttachMenuBotsBot) { if (result instanceof GramJs.AttachMenuBotsBot) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
bot: buildApiAttachBot(result.bot), bot: buildApiAttachBot(result.bot),
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),

View File

@ -17,7 +17,7 @@ import {
} from '../apiBuilders/calls'; } from '../apiBuilders/calls';
import { buildApiUser } from '../apiBuilders/users'; import { buildApiUser } from '../apiBuilders/users';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { addEntitiesToLocalDb } from '../helpers';
import { GROUP_CALL_PARTICIPANTS_LIMIT } from '../../../config'; import { GROUP_CALL_PARTICIPANTS_LIMIT } from '../../../config';
let onUpdate: OnApiUpdate; let onUpdate: OnApiUpdate;
@ -39,8 +39,8 @@ export async function getGroupCall({
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
@ -59,7 +59,9 @@ export function discardGroupCall({
}) { }) {
return invokeRequest(new GramJs.phone.DiscardGroupCall({ return invokeRequest(new GramJs.phone.DiscardGroupCall({
call: buildInputGroupCall(call), call: buildInputGroupCall(call),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function editGroupCallParticipant({ export function editGroupCallParticipant({
@ -78,7 +80,9 @@ export function editGroupCallParticipant({
...(presentationPaused !== undefined && { presentationPaused }), ...(presentationPaused !== undefined && { presentationPaused }),
...(raiseHand !== undefined && { raiseHand }), ...(raiseHand !== undefined && { raiseHand }),
...(volume !== undefined && { volume }), ...(volume !== undefined && { volume }),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function editGroupCallTitle({ export function editGroupCallTitle({
@ -89,7 +93,9 @@ export function editGroupCallTitle({
return invokeRequest(new GramJs.phone.EditGroupCallTitle({ return invokeRequest(new GramJs.phone.EditGroupCallTitle({
title, title,
call: buildInputGroupCall(groupCall), call: buildInputGroupCall(groupCall),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function exportGroupCallInvite({ export async function exportGroupCallInvite({
@ -126,8 +132,8 @@ export async function fetchGroupCallParticipants({
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
@ -151,7 +157,9 @@ export function leaveGroupCall({
}) { }) {
return invokeRequest(new GramJs.phone.LeaveGroupCall({ return invokeRequest(new GramJs.phone.LeaveGroupCall({
call: buildInputGroupCall(call), call: buildInputGroupCall(call),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function joinGroupCall({ export async function joinGroupCall({
@ -215,7 +223,9 @@ export function joinGroupCallPresentation({
params: new GramJs.DataJSON({ params: new GramJs.DataJSON({
data: JSON.stringify(params), data: JSON.stringify(params),
}), }),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function toggleGroupCallStartSubscription({ export function toggleGroupCallStartSubscription({
@ -226,7 +236,10 @@ export function toggleGroupCallStartSubscription({
return invokeRequest(new GramJs.phone.ToggleGroupCallStartSubscription({ return invokeRequest(new GramJs.phone.ToggleGroupCallStartSubscription({
call: buildInputGroupCall(call), call: buildInputGroupCall(call),
subscribed, subscribed,
}), true, undefined, undefined, undefined, true); }), {
shouldReturnTrue: true,
shouldIgnoreErrors: true,
});
} }
export function leaveGroupCallPresentation({ export function leaveGroupCallPresentation({
@ -236,7 +249,9 @@ export function leaveGroupCallPresentation({
}) { }) {
return invokeRequest(new GramJs.phone.LeaveGroupCallPresentation({ return invokeRequest(new GramJs.phone.LeaveGroupCallPresentation({
call: buildInputGroupCall(call), call: buildInputGroupCall(call),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function getDhConfig() { export async function getDhConfig() {
@ -259,7 +274,9 @@ export function discardCall({
return invokeRequest(new GramJs.phone.DiscardCall({ return invokeRequest(new GramJs.phone.DiscardCall({
peer: buildInputPhoneCall(call), peer: buildInputPhoneCall(call),
reason: isBusy ? new GramJs.PhoneCallDiscardReasonBusy() : new GramJs.PhoneCallDiscardReasonHangup(), reason: isBusy ? new GramJs.PhoneCallDiscardReasonBusy() : new GramJs.PhoneCallDiscardReasonHangup(),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function requestCall({ export async function requestCall({
@ -286,7 +303,7 @@ export async function requestCall({
call, call,
}); });
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),
@ -302,7 +319,9 @@ export function setCallRating({
rating, rating,
peer: buildInputPhoneCall(call), peer: buildInputPhoneCall(call),
comment, comment,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function receivedCall({ export function receivedCall({
@ -337,7 +356,7 @@ export async function acceptCall({
call, call,
}); });
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),
@ -367,7 +386,7 @@ export async function confirmCall({
call, call,
}); });
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),

View File

@ -59,7 +59,7 @@ import {
generateRandomBigInt, generateRandomBigInt,
} from '../gramjsBuilders'; } from '../gramjsBuilders';
import { import {
addEntitiesWithPhotosToLocalDb, addEntitiesToLocalDb,
addMessageToLocalDb, addMessageToLocalDb,
addPhotoToLocalDb, addPhotoToLocalDb,
isChatFolder, isChatFolder,
@ -69,6 +69,7 @@ import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { buildApiPhoto } from '../apiBuilders/common'; import { buildApiPhoto } from '../apiBuilders/common';
import { buildStickerSet } from '../apiBuilders/symbols'; import { buildStickerSet } from '../apiBuilders/symbols';
import localDb from '../localDb'; import localDb from '../localDb';
import { updateChannelState } from '../updateManager';
import { scheduleMutedChatUpdate } from '../scheduleUnmute'; import { scheduleMutedChatUpdate } from '../scheduleUnmute';
type FullChatData = { type FullChatData = {
@ -153,6 +154,10 @@ export async function fetchChats({
const peerEntity = peersByKey[getPeerKey(dialog.peer)]; const peerEntity = peersByKey[getPeerKey(dialog.peer)];
const chat = buildApiChatFromDialog(dialog, peerEntity); const chat = buildApiChatFromDialog(dialog, peerEntity);
if (dialog.pts) {
updateChannelState(chat.id, dialog.pts);
}
if ( if (
chat.id === SERVICE_NOTIFICATIONS_USER_ID chat.id === SERVICE_NOTIFICATIONS_USER_ID
&& lastLocalServiceMessage && lastLocalServiceMessage
@ -222,13 +227,15 @@ export async function fetchChatSettings(chat: ApiChat) {
const result = await invokeRequest(new GramJs.messages.GetPeerSettings({ const result = await invokeRequest(new GramJs.messages.GetPeerSettings({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
})); }), {
abortControllerChatId: id,
});
if (!result) { if (!result) {
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),
@ -516,6 +523,10 @@ async function getFullChannelInfo(
} }
} }
if (result.fullChat.pts) {
updateChannelState(id, result.fullChat.pts);
}
const statusesById = { const statusesById = {
...userStatusesById, ...userStatusesById,
...bannedStatusesById, ...bannedStatusesById,
@ -631,7 +642,9 @@ export async function createChannel({
broadcast: true, broadcast: true,
title, title,
about, about,
}), undefined, true); }), {
shouldThrow: true,
});
// `createChannel` can return a lot of different update types according to docs, // `createChannel` can return a lot of different update types according to docs,
// but currently channel creation returns only `Updates` type. // but currently channel creation returns only `Updates` type.
@ -660,7 +673,9 @@ export async function createChannel({
await invokeRequest(new GramJs.channels.InviteToChannel({ await invokeRequest(new GramJs.channels.InviteToChannel({
channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel, channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel,
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[], users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
}), undefined, noErrorUpdate); }), {
shouldThrow: noErrorUpdate,
});
} catch (err) { } catch (err) {
// `noErrorUpdate` will cause an exception which we don't want either // `noErrorUpdate` will cause an exception which we don't want either
} }
@ -676,7 +691,10 @@ export function joinChannel({
}) { }) {
return invokeRequest(new GramJs.channels.JoinChannel({ return invokeRequest(new GramJs.channels.JoinChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel, channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true, true); }), {
shouldReturnTrue: true,
shouldThrow: true,
});
} }
export function deleteChatUser({ export function deleteChatUser({
@ -688,7 +706,9 @@ export function deleteChatUser({
return invokeRequest(new GramJs.messages.DeleteChatUser({ return invokeRequest(new GramJs.messages.DeleteChatUser({
chatId: buildInputEntity(chat.id, chat.accessHash) as BigInt.BigInteger, chatId: buildInputEntity(chat.id, chat.accessHash) as BigInt.BigInteger,
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function deleteChat({ export function deleteChat({
@ -698,7 +718,9 @@ export function deleteChat({
}) { }) {
return invokeRequest(new GramJs.messages.DeleteChat({ return invokeRequest(new GramJs.messages.DeleteChat({
chatId: buildInputEntity(chatId) as BigInt.BigInteger, chatId: buildInputEntity(chatId) as BigInt.BigInteger,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function leaveChannel({ export function leaveChannel({
@ -708,7 +730,9 @@ export function leaveChannel({
}) { }) {
return invokeRequest(new GramJs.channels.LeaveChannel({ return invokeRequest(new GramJs.channels.LeaveChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel, channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function deleteChannel({ export function deleteChannel({
@ -718,7 +742,9 @@ export function deleteChannel({
}) { }) {
return invokeRequest(new GramJs.channels.DeleteChannel({ return invokeRequest(new GramJs.channels.DeleteChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel, channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function createGroupChat({ export async function createGroupChat({
@ -729,7 +755,9 @@ export async function createGroupChat({
const result = await invokeRequest(new GramJs.messages.CreateChat({ const result = await invokeRequest(new GramJs.messages.CreateChat({
title, title,
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[], users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
}), undefined, true); }), {
shouldThrow: true,
});
// `createChat` can return a lot of different update types according to docs, // `createChat` can return a lot of different update types according to docs,
// but currently chat creation returns only `Updates` type. // but currently chat creation returns only `Updates` type.
@ -785,7 +813,9 @@ export async function editChatPhoto({
chatId: inputEntity as BigInt.BigInteger, chatId: inputEntity as BigInt.BigInteger,
photo: inputPhoto, photo: inputPhoto,
}), }),
true, {
shouldReturnTrue: true,
},
); );
} }
@ -826,7 +856,9 @@ export function toggleChatArchived({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
folderId, folderId,
})], })],
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function fetchChatFolders() { export async function fetchChatFolders() {
@ -988,7 +1020,9 @@ export function togglePreHistoryHidden({
return invokeRequest(new GramJs.channels.TogglePreHistoryHidden({ return invokeRequest(new GramJs.channels.TogglePreHistoryHidden({
channel: channel as GramJs.InputChannel, channel: channel as GramJs.InputChannel,
enabled: isEnabled, enabled: isEnabled,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function updateChatDefaultBannedRights({ export function updateChatDefaultBannedRights({
@ -1000,7 +1034,9 @@ export function updateChatDefaultBannedRights({
return invokeRequest(new GramJs.messages.EditChatDefaultBannedRights({ return invokeRequest(new GramJs.messages.EditChatDefaultBannedRights({
peer, peer,
bannedRights: buildChatBannedRights(bannedRights), bannedRights: buildChatBannedRights(bannedRights),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function updateChatMemberBannedRights({ export function updateChatMemberBannedRights({
@ -1013,7 +1049,9 @@ export function updateChatMemberBannedRights({
channel, channel,
participant, participant,
bannedRights: buildChatBannedRights(bannedRights, untilDate), bannedRights: buildChatBannedRights(bannedRights, untilDate),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function updateChatAdmin({ export function updateChatAdmin({
@ -1027,7 +1065,9 @@ export function updateChatAdmin({
userId, userId,
adminRights: buildChatAdminRights(adminRights), adminRights: buildChatAdminRights(adminRights),
rank: customTitle, rank: customTitle,
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function updateChatTitle(chat: ApiChat, title: string) { export async function updateChatTitle(chat: ApiChat, title: string) {
@ -1041,7 +1081,9 @@ export async function updateChatTitle(chat: ApiChat, title: string) {
chatId: inputEntity as BigInt.BigInteger, chatId: inputEntity as BigInt.BigInteger,
title, title,
}), }),
true, {
shouldReturnTrue: true,
},
); );
} }
@ -1073,7 +1115,9 @@ export function toggleSignatures({
return invokeRequest(new GramJs.channels.ToggleSignatures({ return invokeRequest(new GramJs.channels.ToggleSignatures({
channel: channel as GramJs.InputChannel, channel: channel as GramJs.InputChannel,
enabled: isEnabled, enabled: isEnabled,
}), true); }), {
shouldReturnTrue: true,
});
} }
type ChannelMembersFilter = type ChannelMembersFilter =
@ -1106,7 +1150,9 @@ export async function fetchMembers(
filter, filter,
offset, offset,
limit: MEMBERS_LOAD_SLICE, limit: MEMBERS_LOAD_SLICE,
})); }), {
abortControllerChatId: chatId,
});
if (!result || result instanceof GramJs.channels.ChannelParticipantsNotModified) { if (!result || result instanceof GramJs.channels.ChannelParticipantsNotModified) {
return undefined; return undefined;
@ -1144,14 +1190,17 @@ export function setDiscussionGroup({
return invokeRequest(new GramJs.channels.SetDiscussionGroup({ return invokeRequest(new GramJs.channels.SetDiscussionGroup({
broadcast: buildInputPeer(channel.id, channel.accessHash), broadcast: buildInputPeer(channel.id, channel.accessHash),
group: chat ? buildInputPeer(chat.id, chat.accessHash) : new GramJs.InputChannelEmpty(), group: chat ? buildInputPeer(chat.id, chat.accessHash) : new GramJs.InputChannelEmpty(),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function migrateChat(chat: ApiChat) { export async function migrateChat(chat: ApiChat) {
const result = await invokeRequest( const result = await invokeRequest(
new GramJs.messages.MigrateChat({ chatId: buildInputEntity(chat.id) as BigInt.BigInteger }), new GramJs.messages.MigrateChat({ chatId: buildInputEntity(chat.id) as BigInt.BigInteger }),
undefined, {
true, shouldThrow: true,
},
); );
// `migrateChat` can return a lot of different update types according to docs, // `migrateChat` can return a lot of different update types according to docs,
@ -1226,14 +1275,20 @@ export async function addChatMembers(chat: ApiChat, users: ApiUser[], noErrorUpd
return await invokeRequest(new GramJs.channels.InviteToChannel({ return await invokeRequest(new GramJs.channels.InviteToChannel({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
users: users.map((user) => buildInputEntity(user.id, user.accessHash)) as GramJs.InputUser[], users: users.map((user) => buildInputEntity(user.id, user.accessHash)) as GramJs.InputUser[],
}), true, noErrorUpdate); }), {
shouldReturnTrue: true,
shouldThrow: noErrorUpdate,
});
} }
return await Promise.all(users.map((user) => { return await Promise.all(users.map((user) => {
return invokeRequest(new GramJs.messages.AddChatUser({ return invokeRequest(new GramJs.messages.AddChatUser({
chatId: buildInputEntity(chat.id) as BigInt.BigInteger, chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}), true, noErrorUpdate); }), {
shouldReturnTrue: true,
shouldThrow: noErrorUpdate,
});
})); }));
} catch (err) { } catch (err) {
// `noErrorUpdate` will cause an exception which we don't want either // `noErrorUpdate` will cause an exception which we don't want either
@ -1274,7 +1329,9 @@ export function deleteChatMember(chat: ApiChat, user: ApiUser) {
return invokeRequest(new GramJs.messages.DeleteChatUser({ return invokeRequest(new GramJs.messages.DeleteChatUser({
chatId: buildInputEntity(chat.id) as BigInt.BigInteger, chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}), true); }), {
shouldReturnTrue: true,
});
} }
} }
@ -1282,14 +1339,18 @@ export function toggleJoinToSend(chat: ApiChat, isEnabled: boolean) {
return invokeRequest(new GramJs.channels.ToggleJoinToSend({ return invokeRequest(new GramJs.channels.ToggleJoinToSend({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
enabled: isEnabled, enabled: isEnabled,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function toggleJoinRequest(chat: ApiChat, isEnabled: boolean) { export function toggleJoinRequest(chat: ApiChat, isEnabled: boolean) {
return invokeRequest(new GramJs.channels.ToggleJoinRequest({ return invokeRequest(new GramJs.channels.ToggleJoinRequest({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
enabled: isEnabled, enabled: isEnabled,
}), true); }), {
shouldReturnTrue: true,
});
} }
function preparePeers( function preparePeers(
@ -1328,11 +1389,11 @@ function updateLocalDb(result: (
GramJs.messages.Chats | GramJs.messages.ChatsSlice | GramJs.TypeUpdates | GramJs.messages.ForumTopics GramJs.messages.Chats | GramJs.messages.ChatsSlice | GramJs.TypeUpdates | GramJs.messages.ForumTopics
)) { )) {
if ('users' in result) { if ('users' in result) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
} }
if ('chats' in result) { if ('chats' in result) {
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
} }
if ('messages' in result) { if ('messages' in result) {
@ -1361,7 +1422,9 @@ export function setChatEnabledReactions({
return invokeRequest(new GramJs.messages.SetChatAvailableReactions({ return invokeRequest(new GramJs.messages.SetChatAvailableReactions({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
availableReactions: buildInputChatReactions(enabledReactions), availableReactions: buildInputChatReactions(enabledReactions),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function toggleIsProtected({ export function toggleIsProtected({
@ -1372,7 +1435,9 @@ export function toggleIsProtected({
return invokeRequest(new GramJs.messages.ToggleNoForwards({ return invokeRequest(new GramJs.messages.ToggleNoForwards({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
enabled: isProtected, enabled: isProtected,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function toggleParticipantsHidden({ export function toggleParticipantsHidden({
@ -1383,7 +1448,9 @@ export function toggleParticipantsHidden({
return invokeRequest(new GramJs.channels.ToggleParticipantsHidden({ return invokeRequest(new GramJs.channels.ToggleParticipantsHidden({
channel: buildInputPeer(id, accessHash), channel: buildInputPeer(id, accessHash),
enabled: isEnabled, enabled: isEnabled,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function toggleForum({ export function toggleForum({
@ -1394,7 +1461,10 @@ export function toggleForum({
return invokeRequest(new GramJs.channels.ToggleForum({ return invokeRequest(new GramJs.channels.ToggleForum({
channel: buildInputPeer(id, accessHash), channel: buildInputPeer(id, accessHash),
enabled: isEnabled, enabled: isEnabled,
}), true, true); }), {
shouldReturnTrue: true,
shouldThrow: true,
});
} }
export async function createTopic({ export async function createTopic({
@ -1540,7 +1610,9 @@ export function deleteTopic({
return invokeRequest(new GramJs.channels.DeleteTopicHistory({ return invokeRequest(new GramJs.channels.DeleteTopicHistory({
channel: buildInputPeer(id, accessHash), channel: buildInputPeer(id, accessHash),
topMsgId: topicId, topMsgId: topicId,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function togglePinnedTopic({ export function togglePinnedTopic({
@ -1556,7 +1628,9 @@ export function togglePinnedTopic({
channel: buildInputPeer(id, accessHash), channel: buildInputPeer(id, accessHash),
topicId, topicId,
pinned: isPinned, pinned: isPinned,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function editTopic({ export function editTopic({
@ -1578,7 +1652,9 @@ export function editTopic({
iconEmojiId: topicId !== GENERAL_TOPIC_ID && iconEmojiId ? BigInt(iconEmojiId) : undefined, iconEmojiId: topicId !== GENERAL_TOPIC_ID && iconEmojiId ? BigInt(iconEmojiId) : undefined,
closed: isClosed, closed: isClosed,
hidden: isHidden, hidden: isHidden,
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function checkChatlistInvite({ export async function checkChatlistInvite({
@ -1613,7 +1689,10 @@ export function joinChatlistInvite({
return invokeRequest(new GramJs.chatlists.JoinChatlistInvite({ return invokeRequest(new GramJs.chatlists.JoinChatlistInvite({
slug, slug,
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)), peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), true, true); }), {
shouldReturnTrue: true,
shouldThrow: true,
});
} }
export async function fetchLeaveChatlistSuggestions({ export async function fetchLeaveChatlistSuggestions({
@ -1644,7 +1723,9 @@ export function leaveChatlist({
filterId: folderId, filterId: folderId,
}), }),
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)), peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function createChalistInvite({ export async function createChalistInvite({
@ -1660,7 +1741,9 @@ export async function createChalistInvite({
}), }),
title: title || '', title: title || '',
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)), peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), undefined, true); }), {
shouldThrow: true,
});
if (!result || result.filter instanceof GramJs.DialogFilterDefault) return undefined; if (!result || result.filter instanceof GramJs.DialogFilterDefault) return undefined;
@ -1699,7 +1782,9 @@ export async function editChatlistInvite({
slug, slug,
title, title,
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)), peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), undefined, true); }), {
shouldThrow: true,
});
if (!result) return undefined; if (!result) return undefined;
return buildApiChatlistExportedInvite(result); return buildApiChatlistExportedInvite(result);

View File

@ -5,7 +5,6 @@ import TelegramClient from '../../../lib/gramjs/client/TelegramClient';
import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index'; import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index';
import type { TwoFaParams } from '../../../lib/gramjs/client/2fa'; import type { TwoFaParams } from '../../../lib/gramjs/client/2fa';
import type { import type {
ApiInitialArgs, ApiInitialArgs,
ApiMediaFormat, ApiMediaFormat,
@ -21,13 +20,18 @@ import {
onRequestPhoneNumber, onRequestCode, onRequestPassword, onRequestRegistration, onRequestPhoneNumber, onRequestCode, onRequestPassword, onRequestRegistration,
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestQrCode, onWebAuthTokenFailed, onAuthError, onAuthReady, onCurrentUserUpdate, onRequestQrCode, onWebAuthTokenFailed,
} from './auth'; } from './auth';
import { updater } from '../updater';
import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages'; import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages';
import downloadMediaWithClient, { parseMediaUrl } from './media'; import downloadMediaWithClient, { parseMediaUrl } from './media';
import { buildApiUser, buildApiUserFullInfo } from '../apiBuilders/users'; import { buildApiUser, buildApiUserFullInfo } from '../apiBuilders/users';
import localDb, { clearLocalDb } from '../localDb'; import localDb, { clearLocalDb } from '../localDb';
import { buildApiPeerId } from '../apiBuilders/peers'; import { buildApiPeerId } from '../apiBuilders/peers';
import { addMessageToLocalDb, log } from '../helpers'; import {
addMessageToLocalDb, addUserToLocalDb, isResponseUpdate, log,
} from '../helpers';
import { ChatAbortController } from '../ChatAbortController';
import {
updateChannelState, getDifference, init as initUpdatesManager, processUpdate, reset as resetUpdatesManager,
} from '../updateManager';
const DEFAULT_USER_AGENT = 'Unknown UserAgent'; const DEFAULT_USER_AGENT = 'Unknown UserAgent';
const DEFAULT_PLATFORM = 'Unknown platform'; const DEFAULT_PLATFORM = 'Unknown platform';
@ -37,6 +41,8 @@ GramJsLogger.setLevel(DEBUG_GRAMJS ? 'debug' : 'warn');
const gramJsUpdateEventBuilder = { build: (update: object) => update }; const gramJsUpdateEventBuilder = { build: (update: object) => update };
const CHAT_ABORT_CONTROLLERS = new Map<string, ChatAbortController>();
let onUpdate: OnApiUpdate; let onUpdate: OnApiUpdate;
let client: TelegramClient; let client: TelegramClient;
let isConnected = false; let isConnected = false;
@ -81,7 +87,6 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
); );
client.addEventHandler(handleGramJsUpdate, gramJsUpdateEventBuilder); client.addEventHandler(handleGramJsUpdate, gramJsUpdateEventBuilder);
client.addEventHandler(updater, gramJsUpdateEventBuilder);
try { try {
if (DEBUG) { if (DEBUG) {
@ -94,6 +99,7 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
} }
try { try {
client.setPingCallback(getDifference);
await client.start({ await client.start({
phoneNumber: onRequestPhoneNumber, phoneNumber: onRequestPhoneNumber,
phoneCode: onRequestCode, phoneCode: onRequestCode,
@ -131,6 +137,8 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
onSessionUpdate(session.getSessionData()); onSessionUpdate(session.getSessionData());
onUpdate({ '@type': 'updateApiReady' }); onUpdate({ '@type': 'updateApiReady' });
initUpdatesManager(invokeRequest);
void fetchCurrentUser(); void fetchCurrentUser();
} catch (err) { } catch (err) {
if (DEBUG) { if (DEBUG) {
@ -150,7 +158,10 @@ export async function destroy(noLogOut = false, noClearLocalDb = false) {
await invokeRequest(new GramJs.auth.LogOut()); await invokeRequest(new GramJs.auth.LogOut());
} }
if (!noClearLocalDb) clearLocalDb(); if (!noClearLocalDb) {
clearLocalDb();
resetUpdatesManager();
}
await client.destroy(); await client.destroy();
} }
@ -170,54 +181,66 @@ function onSessionUpdate(sessionData: ApiSessionData) {
}); });
} }
function handleGramJsUpdate(update: any) { type UpdateConfig = GramJs.UpdateConfig & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] };
export function handleGramJsUpdate(update: any) {
processUpdate(update);
if (update instanceof connection.UpdateConnectionState) { if (update instanceof connection.UpdateConnectionState) {
isConnected = update.state === connection.UpdateConnectionState.connected; isConnected = update.state === connection.UpdateConnectionState.connected;
} else if (update instanceof GramJs.UpdatesTooLong) { } else if (update instanceof GramJs.UpdatesTooLong) {
void handleTerminatedSession(); void handleTerminatedSession();
} else if (update instanceof GramJs.UpdateConfig) { } else {
// eslint-disable-next-line no-underscore-dangle const updates = 'updates' in update ? update.updates : [update];
const currentUser = (update as GramJs.UpdateConfig & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }) updates.forEach((nestedUpdate: any) => {
._entities if (!(nestedUpdate instanceof GramJs.UpdateConfig)) return;
?.find((entity) => entity instanceof GramJs.User && buildApiPeerId(entity.id, 'user') === currentUserId); // eslint-disable-next-line no-underscore-dangle
if (!(currentUser instanceof GramJs.User)) return; const currentUser = (nestedUpdate as UpdateConfig)._entities
?.find((entity) => entity instanceof GramJs.User && buildApiPeerId(entity.id, 'user') === currentUserId);
if (!(currentUser instanceof GramJs.User)) return;
setIsPremium({ isPremium: Boolean(currentUser.premium) }); setIsPremium({ isPremium: Boolean(currentUser.premium) });
});
} }
} }
export async function invokeRequest<T extends GramJs.AnyRequest>( type InvokeRequestParams = {
request: T, shouldThrow?: boolean;
shouldReturnTrue: true, shouldIgnoreUpdates?: boolean;
shouldThrow?: boolean, dcId?: number;
shouldIgnoreUpdates?: undefined, shouldIgnoreErrors?: boolean;
dcId?: number, abortControllerChatId?: string;
shouldIgnoreErrors?: boolean, abortControllerThreadId?: number;
): Promise<true | undefined>; };
export async function invokeRequest<T extends GramJs.AnyRequest>( export async function invokeRequest<T extends GramJs.AnyRequest>(
request: T, request: T,
shouldReturnTrue?: boolean, params?: InvokeRequestParams & { shouldReturnTrue?: false },
shouldThrow?: boolean,
shouldIgnoreUpdates?: boolean,
dcId?: number,
shouldIgnoreErrors?: boolean,
): Promise<T['__response'] | undefined>; ): Promise<T['__response'] | undefined>;
export async function invokeRequest<T extends GramJs.AnyRequest>( export async function invokeRequest<T extends GramJs.AnyRequest>(
request: T, request: T,
shouldReturnTrue = false, params?: InvokeRequestParams & { shouldReturnTrue: true },
shouldThrow = false, ): Promise<true | undefined>;
shouldIgnoreUpdates = false,
dcId?: number, export async function invokeRequest<T extends GramJs.AnyRequest>(
shouldIgnoreErrors = false, request: T,
params: InvokeRequestParams & { shouldReturnTrue?: boolean } = {},
) { ) {
if (!isConnected) { const {
if (DEBUG) { shouldThrow, shouldIgnoreUpdates, dcId, shouldIgnoreErrors, abortControllerChatId, abortControllerThreadId,
log('INVOKE ERROR', request.className, 'Client is not connected'); } = params;
const shouldReturnTrue = Boolean(params.shouldReturnTrue);
let abortSignal: AbortSignal | undefined;
if (abortControllerChatId) {
let controller = CHAT_ABORT_CONTROLLERS.get(abortControllerChatId);
if (!controller) {
controller = new ChatAbortController();
CHAT_ABORT_CONTROLLERS.set(abortControllerChatId, controller);
} }
return undefined; abortSignal = abortControllerThreadId ? controller.getThreadSignal(abortControllerThreadId) : controller.signal;
} }
try { try {
@ -225,14 +248,14 @@ export async function invokeRequest<T extends GramJs.AnyRequest>(
log('INVOKE', request.className); log('INVOKE', request.className);
} }
const result = await client.invoke(request, dcId); const result = await client.invoke(request, dcId, abortSignal);
if (DEBUG) { if (DEBUG) {
log('RESPONSE', request.className, result); log('RESPONSE', request.className, result);
} }
if (!shouldIgnoreUpdates) { if (!shouldIgnoreUpdates && isResponseUpdate(result)) {
handleUpdates(result); processUpdate(result);
} }
return shouldReturnTrue ? result && true : result; return shouldReturnTrue ? result && true : result;
@ -256,36 +279,6 @@ export async function invokeRequest<T extends GramJs.AnyRequest>(
} }
} }
export function handleUpdates(result: {}) {
let manyUpdates;
let singleUpdate;
if (result instanceof GramJs.UpdatesCombined || result instanceof GramJs.Updates) {
manyUpdates = result;
} else if (typeof result === 'object' && 'updates' in result && (
result.updates instanceof GramJs.Updates || result.updates instanceof GramJs.UpdatesCombined
)) {
manyUpdates = result.updates;
} else if (
result instanceof GramJs.UpdateShortMessage
|| result instanceof GramJs.UpdateShortChatMessage
|| result instanceof GramJs.UpdateShort
|| result instanceof GramJs.UpdateShortSentMessage
) {
singleUpdate = result;
}
if (manyUpdates) {
injectUpdateEntities(manyUpdates);
manyUpdates.updates.forEach((update) => {
updater(update);
});
} else if (singleUpdate) {
updater(singleUpdate);
}
}
export async function downloadMedia( export async function downloadMedia(
args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number; isHtmlAllowed?: boolean }, args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number; isHtmlAllowed?: boolean },
onProgress?: ApiOnProgress, onProgress?: ApiOnProgress,
@ -321,6 +314,18 @@ export function getTmpPassword(currentPassword: string, ttl?: number) {
return client.getTmpPassword(currentPassword, ttl); return client.getTmpPassword(currentPassword, ttl);
} }
export function abortChatRequests(params: { chatId: string; threadId?: number }) {
const { chatId, threadId } = params;
const controller = CHAT_ABORT_CONTROLLERS.get(chatId);
if (!threadId) {
controller?.abort('Chat change');
CHAT_ABORT_CONTROLLERS.delete(chatId);
return;
}
controller?.abortThread(threadId, 'Thread change');
}
export async function fetchCurrentUser() { export async function fetchCurrentUser() {
const userFull = await invokeRequest(new GramJs.users.GetFullUser({ const userFull = await invokeRequest(new GramJs.users.GetFullUser({
id: new GramJs.InputUserSelf(), id: new GramJs.InputUserSelf(),
@ -335,7 +340,7 @@ export async function fetchCurrentUser() {
if (user.photo instanceof GramJs.Photo) { if (user.photo instanceof GramJs.Photo) {
localDb.photos[user.photo.id.toString()] = user.photo; localDb.photos[user.photo.id.toString()] = user.photo;
} }
localDb.users[buildApiPeerId(user.id, 'user')] = user; addUserToLocalDb(user);
const currentUserFullInfo = buildApiUserFullInfo(userFull); const currentUserFullInfo = buildApiUserFullInfo(userFull);
const currentUser = buildApiUser(user)!; const currentUser = buildApiUser(user)!;
@ -365,22 +370,13 @@ export function dispatchErrorUpdate<T extends GramJs.AnyRequest>(err: Error, req
}); });
} }
function injectUpdateEntities(result: GramJs.Updates | GramJs.UpdatesCombined) {
const entities = [...result.users, ...result.chats];
result.updates.forEach((update) => {
if (entities) {
// eslint-disable-next-line no-underscore-dangle
(update as any)._entities = entities;
}
});
}
async function handleTerminatedSession() { async function handleTerminatedSession() {
try { try {
await invokeRequest(new GramJs.users.GetFullUser({ await invokeRequest(new GramJs.users.GetFullUser({
id: new GramJs.InputUserSelf(), id: new GramJs.InputUserSelf(),
}), undefined, true); }), {
shouldThrow: true,
});
} catch (err: any) { } catch (err: any) {
if (err.message === 'AUTH_KEY_UNREGISTERED' || err.message === 'SESSION_REVOKED') { if (err.message === 'AUTH_KEY_UNREGISTERED' || err.message === 'SESSION_REVOKED') {
onUpdate({ onUpdate({
@ -429,6 +425,10 @@ export async function repairFileReference({
if (!result || result instanceof GramJs.messages.MessagesNotModified) return false; if (!result || result instanceof GramJs.messages.MessagesNotModified) return false;
if (peer && 'pts' in result) {
updateChannelState(peer.channelId.toString(), result.pts);
}
const message = result.messages[0]; const message = result.messages[0];
if (message instanceof GramJs.MessageEmpty) return false; if (message instanceof GramJs.MessageEmpty) return false;
addMessageToLocalDb(message); addMessageToLocalDb(message);

View File

@ -1,5 +1,5 @@
export { export {
destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference, destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference, abortChatRequests,
} from './client'; } from './client';
export { export {

View File

@ -7,7 +7,7 @@ import type {
} from '../../types'; } from '../../types';
import { USERNAME_PURCHASE_ERROR } from '../../../config'; import { USERNAME_PURCHASE_ERROR } from '../../../config';
import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { addEntitiesToLocalDb } from '../helpers';
import { buildApiExportedInvite, buildChatInviteImporter } from '../apiBuilders/chats'; import { buildApiExportedInvite, buildChatInviteImporter } from '../apiBuilders/chats';
import { buildApiUser } from '../apiBuilders/users'; import { buildApiUser } from '../apiBuilders/users';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
@ -25,7 +25,9 @@ export async function checkChatUsername({ username }: { username: string }) {
const result = await invokeRequest(new GramJs.channels.CheckUsername({ const result = await invokeRequest(new GramJs.channels.CheckUsername({
channel: new GramJs.InputChannelEmpty(), channel: new GramJs.InputChannelEmpty(),
username, username,
}), undefined, true); }), {
shouldThrow: true,
});
return { result, error: undefined }; return { result, error: undefined };
} catch (error) { } catch (error) {
@ -100,10 +102,12 @@ export async function fetchExportedChatInvites({
adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser, adminId: buildInputEntity(admin.id, admin.accessHash) as GramJs.InputUser,
limit, limit,
revoked: isRevoked || undefined, revoked: isRevoked || undefined,
})); }), {
abortControllerChatId: peer.id,
});
if (!exportedInvites) return undefined; if (!exportedInvites) return undefined;
addEntitiesWithPhotosToLocalDb(exportedInvites.users); addEntitiesToLocalDb(exportedInvites.users);
const invites = (exportedInvites.invites const invites = (exportedInvites.invites
.filter((invite): invite is GramJs.ChatInviteExported => invite instanceof GramJs.ChatInviteExported)) .filter((invite): invite is GramJs.ChatInviteExported => invite instanceof GramJs.ChatInviteExported))
@ -138,7 +142,7 @@ export async function editExportedChatInvite({
if (!invite) return undefined; if (!invite) return undefined;
addEntitiesWithPhotosToLocalDb(invite.users); addEntitiesToLocalDb(invite.users);
if (invite instanceof GramJs.messages.ExportedChatInvite && invite.invite instanceof GramJs.ChatInviteExported) { if (invite instanceof GramJs.messages.ExportedChatInvite && invite.invite instanceof GramJs.ChatInviteExported) {
const replaceInvite = buildApiExportedInvite(invite.invite); const replaceInvite = buildApiExportedInvite(invite.invite);
return { return {
@ -222,11 +226,13 @@ export async function fetchChatInviteImporters({
? buildInputEntity(offsetUser.id, offsetUser.accessHash) as GramJs.InputUser : new GramJs.InputUserEmpty(), ? buildInputEntity(offsetUser.id, offsetUser.accessHash) as GramJs.InputUser : new GramJs.InputUserEmpty(),
limit, limit,
requested: isRequested || undefined, requested: isRequested || undefined,
})); }), {
abortControllerChatId: peer.id,
});
if (!result) return undefined; if (!result) return undefined;
const users = result.users.map((user) => buildApiUser(user)).filter(Boolean); const users = result.users.map((user) => buildApiUser(user)).filter(Boolean);
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
importers: result.importers.map((importer) => buildChatInviteImporter(importer)), importers: result.importers.map((importer) => buildChatInviteImporter(importer)),
users: buildCollectionByKey(users, 'id'), users: buildCollectionByKey(users, 'id'),
@ -246,7 +252,9 @@ export function hideChatJoinRequest({
peer: buildInputPeer(peer.id, peer.accessHash), peer: buildInputPeer(peer.id, peer.accessHash),
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser, userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
approved: isApproved || undefined, approved: isApproved || undefined,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function hideAllChatJoinRequests({ export function hideAllChatJoinRequests({
@ -262,7 +270,9 @@ export function hideAllChatJoinRequests({
peer: buildInputPeer(peer.id, peer.accessHash), peer: buildInputPeer(peer.id, peer.accessHash),
approved: isApproved || undefined, approved: isApproved || undefined,
link, link,
}), true); }), {
shouldReturnTrue: true,
});
} }
export function hideChatReportPanel(chat: ApiChat) { export function hideChatReportPanel(chat: ApiChat) {

View File

@ -86,10 +86,6 @@ async function download(
entityType, entityId, sizeType, params, mediaMatchType, entityType, entityId, sizeType, params, mediaMatchType,
} = parsed; } = parsed;
if (!isConnected) {
return Promise.reject(new Error('ERROR: Client is not connected'));
}
if (entityType === 'staticMap') { if (entityType === 'staticMap') {
const accessHash = entityId; const accessHash = entityId;
const parsedParams = new URLSearchParams(params); const parsedParams = new URLSearchParams(params);

View File

@ -30,7 +30,7 @@ import {
SUPPORTED_IMAGE_CONTENT_TYPES, SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../config'; } from '../../../config';
import { handleUpdates, invokeRequest, uploadFile } from './client'; import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
import { import {
buildApiMessage, buildApiMessage,
buildLocalForwardedMessage, buildLocalForwardedMessage,
@ -61,7 +61,7 @@ import {
import { buildApiChatFromPreview, buildApiSendAsPeerId } from '../apiBuilders/chats'; import { buildApiChatFromPreview, buildApiSendAsPeerId } from '../apiBuilders/chats';
import { fetchFile } from '../../../util/files'; import { fetchFile } from '../../../util/files';
import { import {
addEntitiesWithPhotosToLocalDb, addEntitiesToLocalDb,
addMessageToLocalDb, addMessageToLocalDb,
deserializeBytes, deserializeBytes,
resolveMessageApiChatId, resolveMessageApiChatId,
@ -71,7 +71,7 @@ import { requestChatUpdate } from './chats';
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage'; import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
import { getServerTimeOffset } from '../../../util/serverTime'; import { getServerTimeOffset } from '../../../util/serverTime';
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { updater } from '../updater'; import { updateChannelState } from '../updateManager';
const FAST_SEND_TIMEOUT = 1000; const FAST_SEND_TIMEOUT = 1000;
const INPUT_WAVEFORM_LENGTH = 63; const INPUT_WAVEFORM_LENGTH = 63;
@ -117,7 +117,11 @@ export async function fetchMessages({
offsetId: Math.min(offsetId, MAX_INT_32), offsetId: Math.min(offsetId, MAX_INT_32),
}), }),
...pagination, ...pagination,
}), undefined, true); }), {
shouldThrow: true,
abortControllerChatId: chat.id,
abortControllerThreadId: threadId,
});
} catch (err: any) { } catch (err: any) {
if (err.message === 'CHANNEL_PRIVATE') { if (err.message === 'CHANNEL_PRIVATE') {
onUpdate({ onUpdate({
@ -167,8 +171,10 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
: new GramJs.messages.GetMessages({ : new GramJs.messages.GetMessages({
id: [new GramJs.InputMessageID({ id: messageId })], id: [new GramJs.InputMessageID({ id: messageId })],
}), }),
undefined, {
true, shouldThrow: true,
abortControllerChatId: chat.id,
},
); );
} catch (err: any) { } catch (err: any) {
const { message } = err; const { message } = err;
@ -191,6 +197,10 @@ export async function fetchMessage({ chat, messageId }: { chat: ApiChat; message
return undefined; return undefined;
} }
if ('pts' in result) {
updateChannelState(chat.id, result.pts);
}
const mtpMessage = result.messages[0]; const mtpMessage = result.messages[0];
if (!mtpMessage) { if (!mtpMessage) {
return undefined; return undefined;
@ -355,7 +365,10 @@ export function sendMessage(
...(noWebPage && { noWebpage: noWebPage }), ...(noWebPage && { noWebpage: noWebPage }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
...(shouldUpdateStickerSetOrder && { updateStickersetsOrder: shouldUpdateStickerSetOrder }), ...(shouldUpdateStickerSetOrder && { updateStickersetsOrder: shouldUpdateStickerSetOrder }),
}), false, true, true); }), {
shouldThrow: true,
shouldIgnoreUpdates: true,
});
if (update) handleLocalMessageUpdate(localMessage, update); if (update) handleLocalMessageUpdate(localMessage, update);
} catch (error: any) { } catch (error: any) {
onUpdate({ onUpdate({
@ -476,7 +489,9 @@ function sendGroupedMedia(
...(isSilent && { silent: isSilent }), ...(isSilent && { silent: isSilent }),
...(scheduledAt && { scheduleDate: scheduledAt }), ...(scheduledAt && { scheduleDate: scheduledAt }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), false, undefined, true); }), {
shouldIgnoreUpdates: true,
});
if (update) handleMultipleLocalMessagesUpdate(localMessages, update); if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
})(); })();
@ -573,7 +588,7 @@ export async function editMessage({
id: message.id, id: message.id,
...(isScheduled && { scheduleDate: message.date }), ...(isScheduled && { scheduleDate: message.date }),
...(noWebPage && { noWebpage: noWebPage }), ...(noWebPage && { noWebpage: noWebPage }),
}), true); }));
} }
export async function rescheduleMessage({ export async function rescheduleMessage({
@ -589,7 +604,7 @@ export async function rescheduleMessage({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
id: message.id, id: message.id,
scheduleDate: scheduledAt, scheduleDate: scheduledAt,
}), true); }));
} }
async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) { async function uploadMedia(localMessage: ApiMessage, attachment: ApiAttachment, onProgress: ApiOnProgress) {
@ -680,7 +695,7 @@ export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; thre
await invokeRequest(new GramJs.messages.UnpinAllMessages({ await invokeRequest(new GramJs.messages.UnpinAllMessages({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
...(threadId && { topMsgId: threadId }), ...(threadId && { topMsgId: threadId }),
}), true); }));
} }
export async function deleteMessages({ export async function deleteMessages({
@ -791,7 +806,11 @@ export async function sendMessageAction({
peer: buildInputPeer(peer.id, peer.accessHash), peer: buildInputPeer(peer.id, peer.accessHash),
topMsgId: threadId, topMsgId: threadId,
action: gramAction, action: gramAction,
}), undefined, true); }), {
shouldThrow: true,
abortControllerChatId: peer.id,
abortControllerThreadId: threadId,
});
return result; return result;
} catch (error) { } catch (error) {
// Prevent error from being displayed in UI // Prevent error from being displayed in UI
@ -964,8 +983,8 @@ export async function requestThreadInfoUpdate({
}); });
} }
addEntitiesWithPhotosToLocalDb(topMessageResult.users); addEntitiesToLocalDb(topMessageResult.users);
addEntitiesWithPhotosToLocalDb(topMessageResult.chats); addEntitiesToLocalDb(topMessageResult.chats);
const users = topMessageResult.users.map(buildApiUser).filter(Boolean); const users = topMessageResult.users.map(buildApiUser).filter(Boolean);
@ -1023,7 +1042,10 @@ export async function searchMessagesLocal({
minDate, minDate,
maxDate, maxDate,
...pagination, ...pagination,
})); }), {
abortControllerChatId: chat.id,
abortControllerThreadId: topMessageId,
});
if ( if (
!result !result
@ -1170,7 +1192,7 @@ export async function sendPollVote({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
msgId: messageId, msgId: messageId,
options: options.map(deserializeBytes), options: options.map(deserializeBytes),
}), true); }));
} }
export async function closePoll({ export async function closePoll({
@ -1309,7 +1331,9 @@ export async function forwardMessages({
...(toThreadId && { topMsgId: toThreadId }), ...(toThreadId && { topMsgId: toThreadId }),
...(scheduledAt && { scheduleDate: scheduledAt }), ...(scheduledAt && { scheduleDate: scheduledAt }),
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }), ...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
}), false, undefined, true); }), {
shouldIgnoreUpdates: true,
});
if (update) handleMultipleLocalMessagesUpdate(localMessages, update); if (update) handleMultipleLocalMessagesUpdate(localMessages, update);
} }
@ -1343,7 +1367,9 @@ export async function fetchScheduledHistory({ chat }: { chat: ApiChat }) {
const result = await invokeRequest(new GramJs.messages.GetScheduledHistory({ const result = await invokeRequest(new GramJs.messages.GetScheduledHistory({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
})); }), {
abortControllerChatId: id,
});
if ( if (
!result !result
@ -1368,15 +1394,15 @@ export async function sendScheduledMessages({ chat, ids }: { chat: ApiChat; ids:
await invokeRequest(new GramJs.messages.SendScheduledMessages({ await invokeRequest(new GramJs.messages.SendScheduledMessages({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
id: ids, id: ids,
}), true); }));
} }
function updateLocalDb(result: ( function updateLocalDb(result: (
GramJs.messages.MessagesSlice | GramJs.messages.Messages | GramJs.messages.ChannelMessages | GramJs.messages.MessagesSlice | GramJs.messages.Messages | GramJs.messages.ChannelMessages |
GramJs.messages.DiscussionMessage | GramJs.messages.SponsoredMessages GramJs.messages.DiscussionMessage | GramJs.messages.SponsoredMessages
)) { )) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
result.messages.forEach((message) => { result.messages.forEach((message) => {
if ((message instanceof GramJs.Message && isMessageWithMedia(message)) if ((message instanceof GramJs.Message && isMessageWithMedia(message))
@ -1396,7 +1422,10 @@ export async function fetchPinnedMessages({ chat, threadId }: { chat: ApiChat; t
limit: PINNED_MESSAGES_LIMIT, limit: PINNED_MESSAGES_LIMIT,
topMsgId: threadId, topMsgId: threadId,
}, },
)); ), {
abortControllerChatId: chat.id,
abortControllerThreadId: threadId,
});
if ( if (
!result !result
@ -1441,14 +1470,17 @@ export async function fetchSendAs({
}) { }) {
const result = await invokeRequest(new GramJs.channels.GetSendAs({ const result = await invokeRequest(new GramJs.channels.GetSendAs({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
}), undefined, undefined, undefined, undefined, true); }), {
shouldIgnoreErrors: true,
abortControllerChatId: chat.id,
});
if (!result) { if (!result) {
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean); const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
@ -1507,7 +1539,9 @@ export function readAllMentions({
}) { }) {
return invokeRequest(new GramJs.messages.ReadMentions({ return invokeRequest(new GramJs.messages.ReadMentions({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function readAllReactions({ export function readAllReactions({
@ -1517,7 +1551,9 @@ export function readAllReactions({
}) { }) {
return invokeRequest(new GramJs.messages.ReadReactions({ return invokeRequest(new GramJs.messages.ReadReactions({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function fetchUnreadMentions({ export async function fetchUnreadMentions({
@ -1652,13 +1688,17 @@ export async function translateText(params: TranslateTextParams) {
function handleMultipleLocalMessagesUpdate( function handleMultipleLocalMessagesUpdate(
localMessages: Record<string, ApiMessage>, update: GramJs.TypeUpdates, localMessages: Record<string, ApiMessage>, update: GramJs.TypeUpdates,
) { ) {
if (!('updates' in update)) return; if (!('updates' in update)) {
handleGramJsUpdate(update);
return;
}
update.updates.forEach((u) => { update.updates.forEach((u) => {
if (u instanceof GramJs.UpdateMessageID) { if (u instanceof GramJs.UpdateMessageID) {
const localMessage = localMessages[u.randomId.toString()]; const localMessage = localMessages[u.randomId.toString()];
handleLocalMessageUpdate(localMessage, u); handleLocalMessageUpdate(localMessage, u);
} else { } else {
updater(u); handleGramJsUpdate(u);
} }
}); });
} }
@ -1672,7 +1712,7 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU
} }
if (!messageUpdate) { if (!messageUpdate) {
handleUpdates(update); handleGramJsUpdate(update);
return; return;
} }
@ -1719,5 +1759,5 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU
}, },
}); });
handleUpdates(update); handleGramJsUpdate(update);
} }

View File

@ -14,7 +14,7 @@ import type {
} from '../../types'; } from '../../types';
import localDb from '../localDb'; import localDb from '../localDb';
import { import {
addEntitiesWithPhotosToLocalDb, addEntitiesToLocalDb,
deserializeBytes, deserializeBytes,
serializeBytes, serializeBytes,
} from '../helpers'; } from '../helpers';
@ -121,7 +121,7 @@ export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice) {
localDb.webDocuments[result.photo.url] = result.photo; localDb.webDocuments[result.photo.url] = result.photo;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
form: buildApiPaymentForm(result), form: buildApiPaymentForm(result),
@ -140,7 +140,7 @@ export async function getReceipt(chat: ApiChat, msgId: number) {
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
receipt: buildApiReceipt(result), receipt: buildApiReceipt(result),
@ -152,7 +152,7 @@ export async function fetchPremiumPromo() {
const result = await invokeRequest(new GramJs.help.GetPremiumPromo()); const result = await invokeRequest(new GramJs.help.GetPremiumPromo());
if (!result) return undefined; if (!result) return undefined;
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
result.videos.forEach((video) => { result.videos.forEach((video) => {

View File

@ -9,7 +9,7 @@ import { buildApiUser } from '../apiBuilders/users';
import { buildApiAvailableReaction, buildApiReaction, buildMessagePeerReaction } from '../apiBuilders/messages'; import { buildApiAvailableReaction, buildApiReaction, buildMessagePeerReaction } from '../apiBuilders/messages';
import { invokeRequest } from './client'; import { invokeRequest } from './client';
import localDb from '../localDb'; import localDb from '../localDb';
import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { addEntitiesToLocalDb } from '../helpers';
export function sendWatchingEmojiInteraction({ export function sendWatchingEmojiInteraction({
chat, chat,
@ -22,7 +22,9 @@ export function sendWatchingEmojiInteraction({
action: new GramJs.SendMessageEmojiInteractionSeen({ action: new GramJs.SendMessageEmojiInteractionSeen({
emoticon, emoticon,
}), }),
})); }), {
abortControllerChatId: chat.id,
});
} }
export function sendEmojiInteraction({ export function sendEmojiInteraction({
@ -48,7 +50,9 @@ export function sendEmojiInteraction({
}), }),
}), }),
}), }),
})); }), {
abortControllerChatId: chat.id,
});
} }
export async function getAvailableReactions() { export async function getAvailableReactions() {
@ -92,7 +96,10 @@ export function sendReaction({
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
msgId: messageId, msgId: messageId,
...(shouldAddToRecent && { addToRecent: true }), ...(shouldAddToRecent && { addToRecent: true }),
}), true, true); }), {
shouldReturnTrue: true,
shouldThrow: true,
});
} }
export function fetchMessageReactions({ export function fetchMessageReactions({
@ -103,7 +110,10 @@ export function fetchMessageReactions({
return invokeRequest(new GramJs.messages.GetMessagesReactions({ return invokeRequest(new GramJs.messages.GetMessagesReactions({
id: ids, id: ids,
peer: buildInputPeer(chat.id, chat.accessHash), peer: buildInputPeer(chat.id, chat.accessHash),
}), true); }), {
shouldReturnTrue: true,
abortControllerChatId: chat.id,
});
} }
export async function fetchMessageReactionsList({ export async function fetchMessageReactionsList({
@ -123,7 +133,7 @@ export async function fetchMessageReactionsList({
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
const { nextOffset, reactions, count } = result; const { nextOffset, reactions, count } = result;

View File

@ -36,7 +36,7 @@ import {
import { getClient, invokeRequest, uploadFile } from './client'; import { getClient, invokeRequest, uploadFile } from './client';
import { buildCollectionByKey } from '../../../util/iteratees'; import { buildCollectionByKey } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime'; import { getServerTime } from '../../../util/serverTime';
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb } from '../helpers'; import { addEntitiesToLocalDb, addPhotoToLocalDb } from '../helpers';
import localDb from '../localDb'; import localDb from '../localDb';
const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en']; const BETA_LANG_CODES = ['ar', 'fa', 'id', 'ko', 'uz', 'en'];
@ -54,14 +54,18 @@ export function updateProfile({
firstName: firstName || '', firstName: firstName || '',
lastName: lastName || '', lastName: lastName || '',
about: about || '', about: about || '',
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function checkUsername(username: string) { export async function checkUsername(username: string) {
try { try {
const result = await invokeRequest(new GramJs.account.CheckUsername({ const result = await invokeRequest(new GramJs.account.CheckUsername({
username, username,
}), undefined, true); }), {
shouldThrow: true,
});
return { result, error: undefined }; return { result, error: undefined };
} catch (error) { } catch (error) {
@ -79,7 +83,9 @@ export async function checkUsername(username: string) {
} }
export function updateUsername(username: string) { export function updateUsername(username: string) {
return invokeRequest(new GramJs.account.UpdateUsername({ username }), true); return invokeRequest(new GramJs.account.UpdateUsername({ username }), {
shouldReturnTrue: true,
});
} }
export async function updateProfilePhoto(photo?: ApiPhoto, isFallback?: boolean) { export async function updateProfilePhoto(photo?: ApiPhoto, isFallback?: boolean) {
@ -90,7 +96,7 @@ export async function updateProfilePhoto(photo?: ApiPhoto, isFallback?: boolean)
})); }));
if (!result) return undefined; if (!result) return undefined;
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
if (result.photo instanceof GramJs.Photo) { if (result.photo instanceof GramJs.Photo) {
addPhotoToLocalDb(result.photo); addPhotoToLocalDb(result.photo);
return { return {
@ -110,7 +116,7 @@ export async function uploadProfilePhoto(file: File, isFallback?: boolean, isVid
if (!result) return undefined; if (!result) return undefined;
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
if (result.photo instanceof GramJs.Photo) { if (result.photo instanceof GramJs.Photo) {
addPhotoToLocalDb(result.photo); addPhotoToLocalDb(result.photo);
return { return {
@ -137,7 +143,7 @@ export async function uploadContactProfilePhoto({
if (!result) return undefined; if (!result) return undefined;
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
const users = result.users.map(buildApiUser).filter(Boolean); const users = result.users.map(buildApiUser).filter(Boolean);
@ -157,7 +163,9 @@ export async function uploadContactProfilePhoto({
export async function deleteProfilePhotos(photos: ApiPhoto[]) { export async function deleteProfilePhotos(photos: ApiPhoto[]) {
const photoIds = photos.map(buildInputPhoto).filter(Boolean); const photoIds = photos.map(buildInputPhoto).filter(Boolean);
const isDeleted = await invokeRequest(new GramJs.photos.DeletePhotos({ id: photoIds }), true); const isDeleted = await invokeRequest(new GramJs.photos.DeletePhotos({ id: photoIds }), {
shouldReturnTrue: true,
});
if (isDeleted) { if (isDeleted) {
photos.forEach((photo) => { photos.forEach((photo) => {
delete localDb.photos[photo.id]; delete localDb.photos[photo.id];
@ -271,7 +279,7 @@ export async function fetchWebAuthorizations() {
if (!result) { if (!result) {
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),
@ -290,7 +298,9 @@ export function terminateAllWebAuthorizations() {
export async function fetchNotificationExceptions() { export async function fetchNotificationExceptions() {
const result = await invokeRequest(new GramJs.account.GetNotifyExceptions({ const result = await invokeRequest(new GramJs.account.GetNotifyExceptions({
compareSound: true, compareSound: true,
}), undefined, undefined, true); }), {
shouldIgnoreUpdates: true,
});
if (!(result instanceof GramJs.Updates || result instanceof GramJs.UpdatesCombined)) { if (!(result instanceof GramJs.Updates || result instanceof GramJs.UpdatesCombined)) {
return undefined; return undefined;
@ -582,8 +592,8 @@ function updateLocalDb(
GramJs.Updates | GramJs.UpdatesCombined GramJs.Updates | GramJs.UpdatesCombined
), ),
) { ) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
} }
export async function fetchCountryList({ langCode = 'en' }: { langCode?: LangCode }) { export async function fetchCountryList({ langCode = 'en' }: { langCode?: LangCode }) {

View File

@ -6,7 +6,7 @@ import type {
} from '../../types'; } from '../../types';
import { invokeRequest } from './client'; import { invokeRequest } from './client';
import { addEntitiesWithPhotosToLocalDb } from '../helpers'; import { addEntitiesToLocalDb } from '../helpers';
import { buildInputEntity } from '../gramjsBuilders'; import { buildInputEntity } from '../gramjsBuilders';
import { import {
buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildMessagePublicForwards, buildGraph, buildChannelStatistics, buildGroupStatistics, buildMessageStatistics, buildMessagePublicForwards, buildGraph,
@ -18,7 +18,9 @@ export async function fetchChannelStatistics({
}: { chat: ApiChat; dcId?: number }) { }: { chat: ApiChat; dcId?: number }) {
const result = await invokeRequest(new GramJs.stats.GetBroadcastStats({ const result = await invokeRequest(new GramJs.stats.GetBroadcastStats({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
}), undefined, undefined, undefined, dcId); }), {
dcId,
});
if (!result) { if (!result) {
return undefined; return undefined;
@ -35,13 +37,15 @@ export async function fetchGroupStatistics({
}: { chat: ApiChat; dcId?: number }) { }: { chat: ApiChat; dcId?: number }) {
const result = await invokeRequest(new GramJs.stats.GetMegagroupStats({ const result = await invokeRequest(new GramJs.stats.GetMegagroupStats({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
}), undefined, undefined, undefined, dcId); }), {
dcId,
});
if (!result) { if (!result) {
return undefined; return undefined;
} }
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
return { return {
users: result.users.map(buildApiUser).filter(Boolean), users: result.users.map(buildApiUser).filter(Boolean),
@ -61,7 +65,9 @@ export async function fetchMessageStatistics({
const result = await invokeRequest(new GramJs.stats.GetMessageStats({ const result = await invokeRequest(new GramJs.stats.GetMessageStats({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
msgId: messageId, msgId: messageId,
}), undefined, undefined, undefined, dcId); }), {
dcId,
});
if (!result) { if (!result) {
return undefined; return undefined;
@ -83,14 +89,16 @@ export async function fetchMessagePublicForwards({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel, channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
msgId: messageId, msgId: messageId,
offsetPeer: new GramJs.InputPeerEmpty(), offsetPeer: new GramJs.InputPeerEmpty(),
}), undefined, undefined, undefined, dcId); }), {
dcId,
});
if (!result) { if (!result) {
return undefined; return undefined;
} }
if ('chats' in result) { if ('chats' in result) {
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
} }
return buildMessagePublicForwards(result); return buildMessagePublicForwards(result);
@ -110,7 +118,9 @@ export async function fetchStatisticsAsyncGraph({
const result = await invokeRequest(new GramJs.stats.LoadAsyncGraph({ const result = await invokeRequest(new GramJs.stats.LoadAsyncGraph({
token, token,
...(x && { x: BigInt(x) }), ...(x && { x: BigInt(x) }),
}), undefined, undefined, undefined, dcId); }), {
dcId,
});
if (!result) { if (!result) {
return undefined; return undefined;

View File

@ -164,7 +164,9 @@ export async function fetchStickers(
stickerset: 'id' in stickerSetInfo stickerset: 'id' in stickerSetInfo
? buildInputStickerSet(stickerSetInfo.id, stickerSetInfo.accessHash) ? buildInputStickerSet(stickerSetInfo.id, stickerSetInfo.accessHash)
: buildInputStickerSetShortName(stickerSetInfo.shortName), : buildInputStickerSetShortName(stickerSetInfo.shortName),
}), undefined, true); }), {
shouldThrow: true,
});
if (!(result instanceof GramJs.messages.StickerSet)) { if (!(result instanceof GramJs.messages.StickerSet)) {
return undefined; return undefined;
@ -314,7 +316,7 @@ export function saveGif({ gif, shouldUnsave }: { gif: ApiVideo; shouldUnsave?: b
unsave: shouldUnsave, unsave: shouldUnsave,
}); });
return invokeRequest(request, true); return invokeRequest(request, { shouldReturnTrue: true });
} }
export async function installStickerSet({ stickerSetId, accessHash }: { stickerSetId: string; accessHash: string }) { export async function installStickerSet({ stickerSetId, accessHash }: { stickerSetId: string; accessHash: string }) {

View File

@ -18,7 +18,7 @@ import {
import { buildApiUser, buildApiUserFullInfo, buildApiUsersAndStatuses } from '../apiBuilders/users'; import { buildApiUser, buildApiUserFullInfo, buildApiUsersAndStatuses } from '../apiBuilders/users';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { buildApiPhoto } from '../apiBuilders/common'; import { buildApiPhoto } from '../apiBuilders/common';
import { addEntitiesWithPhotosToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers'; import { addEntitiesToLocalDb, addPhotoToLocalDb, addUserToLocalDb } from '../helpers';
import { buildApiPeerId } from '../apiBuilders/peers'; import { buildApiPeerId } from '../apiBuilders/peers';
import localDb from '../localDb'; import localDb from '../localDb';
@ -47,7 +47,7 @@ export async function fetchFullUser({
} }
updateLocalDb(result); updateLocalDb(result);
addUserToLocalDb(result.users[0], true); addEntitiesToLocalDb(result.users);
if (result.fullUser.profilePhoto instanceof GramJs.Photo) { if (result.fullUser.profilePhoto instanceof GramJs.Photo) {
localDb.photos[result.fullUser.profilePhoto.id.toString()] = result.fullUser.profilePhoto; localDb.photos[result.fullUser.profilePhoto.id.toString()] = result.fullUser.profilePhoto;
@ -142,11 +142,7 @@ export async function fetchContactList() {
return undefined; return undefined;
} }
result.users.forEach((user) => { addEntitiesToLocalDb(result.users);
if (user instanceof GramJs.User) {
addUserToLocalDb(user, true);
}
});
const { users, userStatusesById } = buildApiUsersAndStatuses(result.users); const { users, userStatusesById } = buildApiUsersAndStatuses(result.users);
@ -165,11 +161,7 @@ export async function fetchUsers({ users }: { users: ApiUser[] }) {
return undefined; return undefined;
} }
result.forEach((user) => { addEntitiesToLocalDb(result);
if (user instanceof GramJs.User) {
addUserToLocalDb(user, true);
}
});
return buildApiUsersAndStatuses(result); return buildApiUsersAndStatuses(result);
} }
@ -219,7 +211,9 @@ export function updateContact({
lastName, lastName,
phone: phoneNumber, phone: phoneNumber,
...(shouldSharePhoneNumber && { addPhonePrivacyException: shouldSharePhoneNumber }), ...(shouldSharePhoneNumber && { addPhonePrivacyException: shouldSharePhoneNumber }),
}), true); }), {
shouldReturnTrue: true,
});
} }
export async function deleteContact({ export async function deleteContact({
@ -294,18 +288,22 @@ export function reportSpam(userOrChat: ApiUser | ApiChat) {
return invokeRequest(new GramJs.messages.ReportSpam({ return invokeRequest(new GramJs.messages.ReportSpam({
peer: buildInputPeer(id, accessHash), peer: buildInputPeer(id, accessHash),
}), true); }), {
shouldReturnTrue: true,
});
} }
export function updateEmojiStatus(emojiStatus: ApiSticker, expires?: number) { export function updateEmojiStatus(emojiStatus: ApiSticker, expires?: number) {
return invokeRequest(new GramJs.account.UpdateEmojiStatus({ return invokeRequest(new GramJs.account.UpdateEmojiStatus({
emojiStatus: buildInputEmojiStatus(emojiStatus, expires), emojiStatus: buildInputEmojiStatus(emojiStatus, expires),
}), true); }), {
shouldReturnTrue: true,
});
} }
function updateLocalDb(result: (GramJs.photos.Photos | GramJs.photos.PhotosSlice | GramJs.messages.Chats)) { function updateLocalDb(result: (GramJs.photos.Photos | GramJs.photos.PhotosSlice | GramJs.messages.Chats)) {
if ('chats' in result) { if ('chats' in result) {
addEntitiesWithPhotosToLocalDb(result.chats); addEntitiesToLocalDb(result.chats);
} }
if ('photos' in result) { if ('photos' in result) {
@ -313,6 +311,6 @@ function updateLocalDb(result: (GramJs.photos.Photos | GramJs.photos.PhotosSlice
} }
if ('users' in result) { if ('users' in result) {
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesToLocalDb(result.users);
} }
} }

View File

@ -26,7 +26,7 @@ import * as methods from './methods';
let onUpdate: OnApiUpdate; let onUpdate: OnApiUpdate;
export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs, initialLocalDb?: LocalDb) { export function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs, initialLocalDb?: LocalDb) {
onUpdate = _onUpdate; onUpdate = _onUpdate;
initUpdater(handleUpdate); initUpdater(handleUpdate);
@ -43,7 +43,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg
if (initialLocalDb) updateFullLocalDb(initialLocalDb); if (initialLocalDb) updateFullLocalDb(initialLocalDb);
await initClient(handleUpdate, initialArgs); initClient(handleUpdate, initialArgs);
} }
export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>): MethodResponse<T> { export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>): MethodResponse<T> {

View File

@ -0,0 +1,412 @@
import { Api as GramJs } from '../../lib/gramjs';
import type { Update } from './updater';
import type { invokeRequest } from './methods/client';
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../lib/gramjs/network';
import { DEBUG } from '../../config';
import localDb from './localDb';
import SortedQueue from '../../util/SortedQueue';
import { dispatchUserAndChatUpdates, requestSync, updater } from './updater';
import { addEntitiesToLocalDb } from './helpers';
import { buildInputEntity } from './gramjsBuilders';
import { buildApiPeerId } from './apiBuilders/peers';
export type State = {
seq: number;
date: number;
pts: number;
qts: number;
};
type SeqUpdate = GramJs.Updates | GramJs.UpdatesCombined;
type PtsUpdate = GramJs.TypeUpdate & { pts: number };
const COMMON_BOX_QUEUE_ID = '0';
const CHANNEL_DIFFERENCE_LIMIT = 1000;
const UPDATE_WAIT_TIMEOUT = 500;
let invoke: typeof invokeRequest;
let isInited = false;
let seqTimeout: ReturnType<typeof setTimeout> | undefined;
const PTS_TIMEOUTS = new Map<string, ReturnType<typeof setTimeout>>();
const SEQ_QUEUE = new SortedQueue<SeqUpdate>(seqComparator);
const PTS_QUEUE = new Map<string, SortedQueue<PtsUpdate>>();
export async function init(invokeReq: typeof invokeRequest) {
invoke = invokeReq;
await loadRemoteState();
isInited = true;
scheduleGetDifference();
}
export function applyState(state: State) {
localDb.commonBoxState.seq = state.seq;
localDb.commonBoxState.date = state.date;
localDb.commonBoxState.pts = state.pts;
localDb.commonBoxState.qts = state.qts;
}
export function processUpdate(update: Update) {
if (update instanceof UpdateConnectionState) {
if (update.state === UpdateConnectionState.connected && isInited) {
scheduleGetDifference();
}
updater(update);
return;
}
if (update instanceof UpdateServerTimeOffset) {
updater(update);
return;
}
if (localDb.commonBoxState.seq === undefined) {
// Drop updates received before first sync
return;
}
if (update instanceof GramJs.Updates || update instanceof GramJs.UpdatesCombined) {
saveSeqUpdate(update);
return;
}
if ('pts' in update) {
if (update instanceof GramJs.UpdateChannelTooLong) {
getChannelDifference(getUpdateChannelId(update));
return;
}
savePtsUpdate(update);
return;
}
updater(update);
}
export function updateChannelState(channelId: string, pts: number) {
const channel = localDb.chats[channelId];
if (!(channel instanceof GramJs.Channel)) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error(`[UpdateManager] Channel ${channelId} not found in localDb`);
}
return;
}
const currentState = localDb.channelPtsById[channelId];
if (currentState && currentState < pts) {
scheduleGetChannelDifference(channelId);
return;
}
localDb.channelPtsById[channelId] = pts;
}
function applyUpdate(updateObject: SeqUpdate | PtsUpdate) {
if ('seq' in updateObject) {
if (updateObject.seq) localDb.commonBoxState.seq = updateObject.seq;
localDb.commonBoxState.date = updateObject.date;
}
if ('qts' in updateObject) {
localDb.commonBoxState.qts = updateObject.qts;
}
if ('pts' in updateObject) {
const channelId = getUpdateChannelId(updateObject);
if (channelId !== COMMON_BOX_QUEUE_ID) {
localDb.channelPtsById[channelId] = updateObject.pts;
} else {
localDb.commonBoxState.pts = updateObject.pts;
}
}
if (updateObject instanceof GramJs.UpdatesCombined || updateObject instanceof GramJs.Updates) {
const entities = updateObject.users.concat(updateObject.chats);
updateObject.updates.forEach((update) => {
if (entities) {
// eslint-disable-next-line no-underscore-dangle
(update as any)._entities = entities;
}
processUpdate(update);
});
} else {
updater(updateObject);
}
}
function saveSeqUpdate(update: GramJs.Updates | GramJs.UpdatesCombined) {
SEQ_QUEUE.add(update);
popSeqQueue();
}
function savePtsUpdate(update: PtsUpdate) {
const channelId = getUpdateChannelId(update);
const ptsQueue = PTS_QUEUE.get(channelId) || new SortedQueue<PtsUpdate>(ptsComparator);
ptsQueue.add(update);
PTS_QUEUE.set(channelId, ptsQueue);
popPtsQueue(channelId);
}
function popSeqQueue() {
if (!SEQ_QUEUE.size) return;
const update = SEQ_QUEUE.pop()!;
const localSeq = localDb.commonBoxState.seq;
const seqStart = 'seqStart' in update ? update.seqStart : update.seq;
if (seqStart === 0 || seqStart === localSeq + 1) {
clearTimeout(seqTimeout);
seqTimeout = undefined;
applyUpdate(update);
} else if (seqStart > localSeq + 1) {
SEQ_QUEUE.add(update); // Return update to queue
scheduleGetDifference();
return; // Prevent endless loop
}
popSeqQueue();
}
function popPtsQueue(channelId: string) {
const ptsQueue = PTS_QUEUE.get(channelId);
if (!ptsQueue?.size) return;
const update = ptsQueue.pop()!;
const localPts = channelId === COMMON_BOX_QUEUE_ID ? localDb.commonBoxState.pts : localDb.channelPtsById[channelId];
const pts = update.pts;
const ptsCount = getPtsCount(update);
if (localPts === undefined) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('[UpdateManager] Got pts update without local state', channelId);
}
return;
}
if (pts === localPts + ptsCount) {
clearTimeout(PTS_TIMEOUTS.get(channelId));
PTS_TIMEOUTS.delete(channelId);
applyUpdate(update);
} else if (pts > localPts + ptsCount) {
ptsQueue.add(update); // Return update to queue
if (channelId === COMMON_BOX_QUEUE_ID) {
scheduleGetDifference();
} else {
scheduleGetChannelDifference(channelId);
}
return; // Prevent endless loop
}
popPtsQueue(channelId);
}
function scheduleGetChannelDifference(channelId: string) {
if (PTS_TIMEOUTS.has(channelId)) return;
const timeout = setTimeout(async () => {
await getChannelDifference(channelId);
PTS_TIMEOUTS.delete(channelId);
}, UPDATE_WAIT_TIMEOUT);
PTS_TIMEOUTS.set(channelId, timeout);
}
function scheduleGetDifference() {
if (seqTimeout) return;
seqTimeout = setTimeout(async () => {
await getDifference();
seqTimeout = undefined;
}, UPDATE_WAIT_TIMEOUT);
}
function getUpdateChannelId(update: Update) {
if ('channelId' in update && 'pts' in update) {
return buildApiPeerId(update.channelId, 'channel');
}
if (update instanceof GramJs.UpdateNewChannelMessage || update instanceof GramJs.UpdateEditChannelMessage) {
const peer = update.message.peerId as GramJs.PeerChannel;
return buildApiPeerId(peer.channelId, 'channel');
}
return COMMON_BOX_QUEUE_ID;
}
export async function getDifference() {
if (!isInited) {
throw new Error('UpdatesManager not initialized');
}
if (!localDb.commonBoxState?.date) {
forceSync();
return;
}
const response = await invoke(new GramJs.updates.GetDifference({
pts: localDb.commonBoxState.pts,
date: localDb.commonBoxState.date,
qts: localDb.commonBoxState.qts,
}));
SEQ_QUEUE.clear();
PTS_QUEUE.get(COMMON_BOX_QUEUE_ID)?.clear();
if (!response || response instanceof GramJs.updates.DifferenceTooLong) {
forceSync();
return;
}
if (response instanceof GramJs.updates.DifferenceEmpty) {
localDb.commonBoxState.seq = response.seq;
localDb.commonBoxState.date = response.date;
return;
}
processDifference(response);
const newState = response instanceof GramJs.updates.DifferenceSlice ? response.intermediateState : response.state;
applyState(newState);
if (response instanceof GramJs.updates.DifferenceSlice) {
getDifference();
}
}
async function getChannelDifference(channelId: string) {
const channel = localDb.chats[channelId];
if (!channel || !(channel instanceof GramJs.Channel) || !channel.accessHash || !localDb.channelPtsById[channelId]) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('[UpdateManager] Channel for difference not found', channelId, channel);
}
return;
}
const response = await invoke(new GramJs.updates.GetChannelDifference({
channel: buildInputEntity(channelId, channel.accessHash.toString()) as GramJs.InputChannel,
pts: localDb.channelPtsById[channelId],
filter: new GramJs.ChannelMessagesFilterEmpty(),
limit: CHANNEL_DIFFERENCE_LIMIT,
}));
PTS_QUEUE.delete(channelId);
if (!response) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('[UpdatesManager] Failed to get ChannelDifference', channelId, channel);
}
return;
}
if (response instanceof GramJs.updates.ChannelDifferenceTooLong) {
forceSync();
return;
}
localDb.channelPtsById[channelId] = response.pts;
if (response instanceof GramJs.updates.ChannelDifferenceEmpty) {
return;
}
processDifference(response);
if (!response.final) {
getChannelDifference(channelId);
}
}
function forceSync() {
reset();
requestSync();
loadRemoteState();
}
export function reset() {
PTS_QUEUE.clear();
SEQ_QUEUE.clear();
clearTimeout(seqTimeout);
seqTimeout = undefined;
PTS_TIMEOUTS.forEach((timeout) => {
clearTimeout(timeout);
});
PTS_TIMEOUTS.clear();
localDb.commonBoxState = {};
Object.keys(localDb.channelPtsById).forEach((channelId) => {
localDb.channelPtsById[channelId] = 0;
});
isInited = false;
}
async function loadRemoteState() {
const remoteState = await invoke(new GramJs.updates.GetState());
if (!remoteState) return;
applyState(remoteState);
isInited = true;
}
function processDifference(
difference: GramJs.updates.Difference | GramJs.updates.DifferenceSlice | GramJs.updates.ChannelDifference,
) {
difference.newMessages.forEach((message) => {
updater(new GramJs.UpdateNewMessage({
message,
pts: 0,
ptsCount: 0,
}));
});
addEntitiesToLocalDb(difference.users);
addEntitiesToLocalDb(difference.chats);
dispatchUserAndChatUpdates(difference.users);
dispatchUserAndChatUpdates(difference.chats);
difference.otherUpdates.forEach((update) => {
processUpdate(update);
});
}
function getPtsCount(update: PtsUpdate) {
return 'ptsCount' in update ? update.ptsCount : 0;
}
function seqComparator(a: SeqUpdate, b: SeqUpdate) {
const seqA = 'seqStart' in a ? a.seqStart : a.seq;
const seqB = 'seqStart' in b ? b.seqStart : b.seq;
return seqA - seqB;
}
function ptsComparator(a: PtsUpdate, b: PtsUpdate) {
const diff = a.pts - b.pts;
if (diff !== 0) {
return diff;
}
return getPtsCount(b) - getPtsCount(a);
}

View File

@ -42,7 +42,7 @@ import localDb from './localDb';
import { omitVirtualClassFields } from './apiBuilders/helpers'; import { omitVirtualClassFields } from './apiBuilders/helpers';
import { import {
addMessageToLocalDb, addMessageToLocalDb,
addEntitiesWithPhotosToLocalDb, addEntitiesToLocalDb,
addPhotoToLocalDb, addPhotoToLocalDb,
resolveMessageApiChatId, resolveMessageApiChatId,
serializeBytes, serializeBytes,
@ -68,7 +68,7 @@ import { buildApiEmojiInteraction, buildStickerSet } from './apiBuilders/symbols
import { buildApiBotMenuButton } from './apiBuilders/bots'; import { buildApiBotMenuButton } from './apiBuilders/bots';
import { scheduleMutedTopicUpdate, scheduleMutedChatUpdate } from './scheduleUnmute'; import { scheduleMutedTopicUpdate, scheduleMutedChatUpdate } from './scheduleUnmute';
type Update = ( export type Update = (
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] } (GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
) | typeof connection.UpdateConnectionState; ) | typeof connection.UpdateConnectionState;
@ -82,7 +82,7 @@ export function init(_onUpdate: OnApiUpdate) {
const sentMessageIds = new Set(); const sentMessageIds = new Set();
function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.TypeChat)[]) { export function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.TypeChat)[]) {
entities entities
.filter((e) => e instanceof GramJs.User) .filter((e) => e instanceof GramJs.User)
.map(buildApiUser) .map(buildApiUser)
@ -117,6 +117,12 @@ function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.TypeChat
}); });
} }
export function requestSync() {
onUpdate({
'@type': 'requestSync',
});
}
export function updater(update: Update) { export function updater(update: Update) {
if (update instanceof connection.UpdateServerTimeOffset) { if (update instanceof connection.UpdateServerTimeOffset) {
setServerTimeOffset(update.timeOffset); setServerTimeOffset(update.timeOffset);
@ -160,7 +166,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -930,7 +936,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -948,7 +954,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -961,7 +967,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -975,7 +981,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -1013,7 +1019,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
@ -1027,7 +1033,7 @@ export function updater(update: Update) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
const entities = update._entities; const entities = update._entities;
if (entities) { if (entities) {
addEntitiesWithPhotosToLocalDb(entities); addEntitiesToLocalDb(entities);
dispatchUserAndChatUpdates(entities); dispatchUserAndChatUpdates(entities);
} }
onUpdate({ '@type': 'updateConfig' }); onUpdate({ '@type': 'updateConfig' });

View File

@ -10,6 +10,7 @@ import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../../config';
import generateIdFor from '../../../util/generateIdFor'; import generateIdFor from '../../../util/generateIdFor';
import { pause } from '../../../util/schedulers'; import { pause } from '../../../util/schedulers';
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole'; import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
import Deferred from '../../../util/Deferred';
type RequestStates = { type RequestStates = {
messageId: string; messageId: string;
@ -32,6 +33,9 @@ const savedLocalDb: LocalDb = {
stickerSets: {}, stickerSets: {},
photos: {}, photos: {},
webDocuments: {}, webDocuments: {},
commonBoxState: {},
channelPtsById: {},
}; };
// TODO Re-use `util/WorkerConnector.ts` // TODO Re-use `util/WorkerConnector.ts`
@ -57,6 +61,10 @@ export function initApiOnMasterTab(initialArgs: ApiInitialArgs) {
let updateCallback: OnApiUpdate; let updateCallback: OnApiUpdate;
let localApiRequestsQueue: { fnName: any; args: any; deferred: Deferred<any> }[] = [];
let apiRequestsQueue: { fnName: any; args: any; deferred: Deferred<any> }[] = [];
let isInited = false;
export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) { export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
updateCallback = onUpdate; updateCallback = onUpdate;
@ -82,6 +90,22 @@ export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
return makeRequest({ return makeRequest({
type: 'initApi', type: 'initApi',
args: [initialArgs, savedLocalDb], args: [initialArgs, savedLocalDb],
}).then(() => {
isInited = true;
apiRequestsQueue.forEach((request) => {
callApi(request.fnName, ...request.args)
.then(request.deferred.resolve)
.catch(request.deferred.reject);
});
apiRequestsQueue = [];
localApiRequestsQueue.forEach((request) => {
callApiLocal(request.fnName, ...request.args)
.then(request.deferred.resolve)
.catch(request.deferred.reject);
});
localApiRequestsQueue = [];
}); });
} }
@ -108,13 +132,11 @@ export function callApiOnMasterTab(payload: any) {
* Mostly needed to disconnect worker when re-electing master * Mostly needed to disconnect worker when re-electing master
*/ */
export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) { export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
if (!worker) { if (!isInited) {
if (DEBUG) { const deferred = new Deferred();
// eslint-disable-next-line no-console localApiRequestsQueue.push({ fnName, args, deferred });
console.warn('API is not initialized');
}
return undefined; return deferred.promise as MethodResponse<T>;
} }
const promise = makeRequest({ const promise = makeRequest({
@ -150,13 +172,11 @@ export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: Method
} }
export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) { export function callApi<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
if (!worker && isMasterTab) { if (!isInited && isMasterTab) {
if (DEBUG) { const deferred = new Deferred();
// eslint-disable-next-line no-console apiRequestsQueue.push({ fnName, args, deferred });
console.warn('API is not initialized');
}
return undefined; return deferred.promise as MethodResponse<T>;
} }
const promise = isMasterTab ? makeRequest({ const promise = isMasterTab ? makeRequest({
@ -236,7 +256,8 @@ function subscribeToWorker(onUpdate: OnApiUpdate) {
}); });
} }
export function handleMethodResponse(data: { messageId: string; export function handleMethodResponse(data: {
messageId: string;
response?: ThenArg<MethodResponse<keyof Methods>>; response?: ThenArg<MethodResponse<keyof Methods>>;
error?: { message: string }; error?: { message: string };
}) { }) {
@ -250,7 +271,8 @@ export function handleMethodResponse(data: { messageId: string;
} }
} }
export function handleMethodCallback(data: { messageId: string; export function handleMethodCallback(data: {
messageId: string;
callbackArgs: any[]; callbackArgs: any[];
}) { }) {
requestStates.get(data.messageId)?.callback?.(...data.callbackArgs); requestStates.get(data.messageId)?.callback?.(...data.callbackArgs);

View File

@ -21,7 +21,15 @@ onmessage = async (message: OriginMessageEvent) => {
switch (data.type) { switch (data.type) {
case 'initApi': { case 'initApi': {
await initApi(onUpdate, data.args[0], data.args[1]); const { messageId, args } = data;
await initApi(onUpdate, args[0], args[1]);
if (messageId) {
sendToOrigin({
type: 'methodResponse',
messageId,
response: true,
});
}
break; break;
} }
case 'callMethod': { case 'callMethod': {

View File

@ -619,6 +619,10 @@ export type ApiRequestInitApi = {
'@type': 'requestInitApi'; '@type': 'requestInitApi';
}; };
export type ApiRequestSync = {
'@type': 'requestSync';
};
export type ApiUpdate = ( export type ApiUpdate = (
ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate | ApiUpdateReady | ApiUpdateSession | ApiUpdateWebAuthTokenFailed | ApiUpdateRequestUserUpdate |
ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser | ApiUpdateAuthorizationState | ApiUpdateAuthorizationError | ApiUpdateConnectionState | ApiUpdateCurrentUser |
@ -645,7 +649,7 @@ export type ApiUpdate = (
ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton | ApiUpdateTranscribedAudio | ApiUpdateUserEmojiStatus | ApiUpdatePhoneCallConnectionState | ApiUpdateBotMenuButton | ApiUpdateTranscribedAudio | ApiUpdateUserEmojiStatus |
ApiUpdateMessageExtendedMedia | ApiUpdateConfig | ApiUpdateTopicNotifyExceptions | ApiUpdatePinnedTopic | ApiUpdateMessageExtendedMedia | ApiUpdateConfig | ApiUpdateTopicNotifyExceptions | ApiUpdatePinnedTopic |
ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses | ApiUpdatePinnedTopicsOrder | ApiUpdateTopic | ApiUpdateTopics | ApiUpdateRecentEmojiStatuses |
ApiUpdateRecentReactions | ApiRequestInitApi ApiUpdateRecentReactions | ApiRequestInitApi | ApiRequestSync
); );
export type OnApiUpdate = (update: ApiUpdate) => void; export type OnApiUpdate = (update: ApiUpdate) => void;

View File

@ -12,11 +12,11 @@ import AnimatedIconWithPreview from './AnimatedIconWithPreview';
type OwnProps = type OwnProps =
Partial<AnimatedIconProps> Partial<AnimatedIconProps>
& { sticker?: ApiSticker; noLoad?: boolean; forcePreview?: boolean; lastSyncTime?: number }; & { sticker?: ApiSticker; noLoad?: boolean; forcePreview?: boolean };
function AnimatedIconFromSticker(props: OwnProps) { function AnimatedIconFromSticker(props: OwnProps) {
const { const {
sticker, noLoad, forcePreview, lastSyncTime, ...otherProps sticker, noLoad, forcePreview, ...otherProps
} = props; } = props;
const thumbDataUri = sticker?.thumbnail?.dataUri; const thumbDataUri = sticker?.thumbnail?.dataUri;
@ -25,9 +25,8 @@ function AnimatedIconFromSticker(props: OwnProps) {
sticker ? getStickerPreviewHash(sticker.id) : undefined, sticker ? getStickerPreviewHash(sticker.id) : undefined,
noLoad && !forcePreview, noLoad && !forcePreview,
ApiMediaFormat.BlobUrl, ApiMediaFormat.BlobUrl,
lastSyncTime,
); );
const tgsUrl = useMedia(localMediaHash, noLoad, undefined, lastSyncTime); const tgsUrl = useMedia(localMediaHash, noLoad);
return ( return (
<AnimatedIconWithPreview <AnimatedIconWithPreview

View File

@ -50,7 +50,6 @@ type OwnProps = {
uploadProgress?: number; uploadProgress?: number;
origin: AudioOrigin; origin: AudioOrigin;
date?: number; date?: number;
lastSyncTime?: number;
noAvatars?: boolean; noAvatars?: boolean;
className?: string; className?: string;
isSelectable?: boolean; isSelectable?: boolean;
@ -84,7 +83,6 @@ const Audio: FC<OwnProps> = ({
uploadProgress, uploadProgress,
origin, origin,
date, date,
lastSyncTime,
noAvatars, noAvatars,
className, className,
isSelectable, isSelectable,
@ -114,7 +112,7 @@ const Audio: FC<OwnProps> = ({
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const [isActivated, setIsActivated] = useState(false); const [isActivated, setIsActivated] = useState(false);
const shouldLoad = (isActivated || PRELOAD) && lastSyncTime; const shouldLoad = isActivated || PRELOAD;
const coverHash = getMessageMediaHash(message, 'pictogram'); const coverHash = getMessageMediaHash(message, 'pictogram');
const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl); const coverBlobUrl = useMedia(coverHash, false, ApiMediaFormat.BlobUrl);

View File

@ -53,7 +53,6 @@ type OwnProps = {
withVideo?: boolean; withVideo?: boolean;
loopIndefinitely?: boolean; loopIndefinitely?: boolean;
noPersonalPhoto?: boolean; noPersonalPhoto?: boolean;
lastSyncTime?: number;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void; onClick?: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => void;
}; };
@ -69,7 +68,6 @@ const Avatar: FC<OwnProps> = ({
isSavedMessages, isSavedMessages,
withVideo, withVideo,
loopIndefinitely, loopIndefinitely,
lastSyncTime,
noPersonalPhoto, noPersonalPhoto,
onClick, onClick,
}) => { }) => {
@ -98,8 +96,8 @@ const Avatar: FC<OwnProps> = ({
} }
} }
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl, lastSyncTime); const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl, lastSyncTime); const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl); const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
// `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again // `videoBlobUrl` can be taken from memory cache, so we need to check `shouldLoadVideo` again
const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo); const shouldPlayVideo = Boolean(videoBlobUrl && shouldLoadVideo);

View File

@ -1,10 +1,9 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useEffect, useMemo, useState, memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { GlobalState } from '../../global/types'; import type { FC } from '../../lib/teact/teact';
import type { import type {
ApiChat, ApiCountryCode, ApiUser, ApiUsername, ApiChat, ApiCountryCode, ApiUser, ApiUsername,
} from '../../api/types'; } from '../../api/types';
@ -56,13 +55,11 @@ type StateProps =
topicId?: number; topicId?: number;
description?: string; description?: string;
chatInviteLink?: string; chatInviteLink?: string;
} };
& Pick<GlobalState, 'lastSyncTime'>;
const runDebounced = debounce((cb) => cb(), 500, false); const runDebounced = debounce((cb) => cb(), 500, false);
const ChatExtra: FC<OwnProps & StateProps> = ({ const ChatExtra: FC<OwnProps & StateProps> = ({
lastSyncTime,
user, user,
chat, chat,
forceShowSelf, forceShowSelf,
@ -96,10 +93,9 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
}, [isMuted]); }, [isMuted]);
useEffect(() => { useEffect(() => {
if (lastSyncTime && userId) { if (!userId) return;
loadFullUser({ userId }); loadFullUser({ userId });
} }, [userId]);
}, [loadFullUser, userId, lastSyncTime]);
const isTopicInfo = Boolean(topicId && topicId !== MAIN_THREAD_ID); const isTopicInfo = Boolean(topicId && topicId !== MAIN_THREAD_ID);
@ -264,7 +260,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { chatOrUserId }): StateProps => { (global, { chatOrUserId }): StateProps => {
const { lastSyncTime, countryList: { phoneCodes: phoneCodeList } } = global; const { countryList: { phoneCodes: phoneCodeList } } = global;
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined; const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
const user = isUserId(chatOrUserId) ? selectUser(global, chatOrUserId) : undefined; const user = isUserId(chatOrUserId) ? selectUser(global, chatOrUserId) : undefined;
@ -284,7 +280,6 @@ export default memo(withGlobal<OwnProps>(
); );
return { return {
lastSyncTime,
phoneCodeList, phoneCodeList,
chat, chat,
user, user,

View File

@ -93,7 +93,7 @@ const Document: FC<OwnProps> = ({
const documentHash = getMessageMediaHash(message, 'download'); const documentHash = getMessageMediaHash(message, 'download');
const { loadProgress: downloadProgress, mediaData } = useMediaWithLoadProgress( const { loadProgress: downloadProgress, mediaData } = useMediaWithLoadProgress(
documentHash, !shouldDownload, getMessageMediaFormat(message, 'download'), undefined, undefined, true, documentHash, !shouldDownload, getMessageMediaFormat(message, 'download'), undefined, true,
); );
const isLoaded = Boolean(mediaData); const isLoaded = Boolean(mediaData);

View File

@ -1,12 +1,10 @@
import type { MouseEvent as ReactMouseEvent } from 'react';
import type { FC } from '../../lib/teact/teact';
import React, { useEffect, memo, useMemo } from '../../lib/teact/teact'; import React, { useEffect, memo, useMemo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { import type {
ApiChat, ApiTopic, ApiThreadInfo, ApiTypingStatus, ApiChat, ApiTopic, ApiThreadInfo, ApiTypingStatus,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState } from '../../global/types';
import type { LangFn } from '../../hooks/useLang'; import type { LangFn } from '../../hooks/useLang';
import { MediaViewerOrigin } from '../../types'; import { MediaViewerOrigin } from '../../types';
@ -62,8 +60,7 @@ type StateProps =
onlineCount?: number; onlineCount?: number;
areMessagesLoaded: boolean; areMessagesLoaded: boolean;
messagesCount?: number; messagesCount?: number;
} };
& Pick<GlobalState, 'lastSyncTime'>;
const GroupChatInfo: FC<OwnProps & StateProps> = ({ const GroupChatInfo: FC<OwnProps & StateProps> = ({
typingStatus, typingStatus,
@ -82,7 +79,6 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
chat, chat,
onlineCount, onlineCount,
areMessagesLoaded, areMessagesLoaded,
lastSyncTime,
topic, topic,
messagesCount, messagesCount,
onClick, onClick,
@ -93,19 +89,21 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
loadProfilePhotos, loadProfilePhotos,
} = getActions(); } = getActions();
const lang = useLang();
const isSuperGroup = chat && isChatSuperGroup(chat); const isSuperGroup = chat && isChatSuperGroup(chat);
const isTopic = Boolean(chat?.isForum && threadInfo && topic); const isTopic = Boolean(chat?.isForum && threadInfo && topic);
const { id: chatId, isMin, isRestricted } = chat || {}; const { id: chatId, isMin, isRestricted } = chat || {};
useEffect(() => { useEffect(() => {
if (chatId && !isMin && lastSyncTime) { if (chatId && !isMin) {
if (withFullInfo) loadFullChat({ chatId }); if (withFullInfo) loadFullChat({ chatId });
if (withMediaViewer) loadProfilePhotos({ profileId: chatId }); if (withMediaViewer) loadProfilePhotos({ profileId: chatId });
} }
}, [chatId, isMin, lastSyncTime, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]); }, [chatId, isMin, withFullInfo, loadFullChat, loadProfilePhotos, isSuperGroup, withMediaViewer]);
const handleAvatarViewerOpen = useLastCallback( const handleAvatarViewerOpen = useLastCallback(
(e: ReactMouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => { (e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
if (chat && hasMedia) { if (chat && hasMedia) {
e.stopPropagation(); e.stopPropagation();
openMediaViewer({ openMediaViewer({
@ -117,7 +115,6 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
}, },
); );
const lang = useLang();
const mainUsername = useMemo(() => chat && withUsername && getMainUsername(chat), [chat, withUsername]); const mainUsername = useMemo(() => chat && withUsername && getMainUsername(chat), [chat, withUsername]);
if (!chat) { if (!chat) {
@ -225,7 +222,6 @@ function getGroupStatus(lang: LangFn, chat: ApiChat) {
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }): StateProps => { (global, { chatId, threadId }): StateProps => {
const { lastSyncTime } = global;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined; const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined; const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
@ -234,7 +230,6 @@ export default memo(withGlobal<OwnProps>(
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!); const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
return { return {
lastSyncTime,
chat, chat,
threadInfo, threadInfo,
onlineCount, onlineCount,

View File

@ -5,7 +5,6 @@ import type { FC } from '../../lib/teact/teact';
import type { import type {
ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember, ApiUser, ApiTypingStatus, ApiUserStatus, ApiChatMember,
} from '../../api/types'; } from '../../api/types';
import type { GlobalState } from '../../global/types';
import { MediaViewerOrigin } from '../../types'; import { MediaViewerOrigin } from '../../types';
import { import {
@ -49,8 +48,7 @@ type StateProps =
userStatus?: ApiUserStatus; userStatus?: ApiUserStatus;
isSavedMessages?: boolean; isSavedMessages?: boolean;
areMessagesLoaded: boolean; areMessagesLoaded: boolean;
} };
& Pick<GlobalState, 'lastSyncTime'>;
const PrivateChatInfo: FC<OwnProps & StateProps> = ({ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
typingStatus, typingStatus,
@ -69,7 +67,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
userStatus, userStatus,
isSavedMessages, isSavedMessages,
areMessagesLoaded, areMessagesLoaded,
lastSyncTime,
adminMember, adminMember,
}) => { }) => {
const { const {
@ -78,14 +75,16 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
loadProfilePhotos, loadProfilePhotos,
} = getActions(); } = getActions();
const lang = useLang();
const { id: userId } = user || {}; const { id: userId } = user || {};
useEffect(() => { useEffect(() => {
if (userId && lastSyncTime) { if (userId) {
if (withFullInfo) loadFullUser({ userId }); if (withFullInfo) loadFullUser({ userId });
if (withMediaViewer) loadProfilePhotos({ profileId: userId }); if (withMediaViewer) loadProfilePhotos({ profileId: userId });
} }
}, [userId, loadFullUser, loadProfilePhotos, lastSyncTime, withFullInfo, withMediaViewer]); }, [userId, withFullInfo, withMediaViewer]);
const handleAvatarViewerOpen = useLastCallback( const handleAvatarViewerOpen = useLastCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => { (e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
@ -100,7 +99,6 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
}, },
); );
const lang = useLang();
const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]); const mainUsername = useMemo(() => user && withUsername && getMainUsername(user), [user, withUsername]);
if (!user) { if (!user) {
@ -189,14 +187,12 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { userId, forceShowSelf }): StateProps => { (global, { userId, forceShowSelf }): StateProps => {
const { lastSyncTime } = global;
const user = selectUser(global, userId); const user = selectUser(global, userId);
const userStatus = selectUserStatus(global, userId); const userStatus = selectUserStatus(global, userId);
const isSavedMessages = !forceShowSelf && user && user.isSelf; const isSavedMessages = !forceShowSelf && user && user.isSelf;
const areMessagesLoaded = Boolean(selectChatMessages(global, userId)); const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
return { return {
lastSyncTime,
user, user,
userStatus, userStatus,
isSavedMessages, isSavedMessages,

View File

@ -33,7 +33,6 @@ type OwnProps = {
user?: ApiUser; user?: ApiUser;
isSavedMessages?: boolean; isSavedMessages?: boolean;
photo?: ApiPhoto; photo?: ApiPhoto;
lastSyncTime?: number;
canPlayVideo: boolean; canPlayVideo: boolean;
onClick: NoneToVoidFunction; onClick: NoneToVoidFunction;
}; };
@ -44,7 +43,6 @@ const ProfilePhoto: FC<OwnProps> = ({
photo, photo,
isSavedMessages, isSavedMessages,
canPlayVideo, canPlayVideo,
lastSyncTime,
onClick, onClick,
}) => { }) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -60,13 +58,13 @@ const ProfilePhoto: FC<OwnProps> = ({
const { isVideo } = photo || {}; const { isVideo } = photo || {};
const avatarHash = canHaveMedia && getChatAvatarHash(userOrChat, 'normal'); const avatarHash = canHaveMedia && getChatAvatarHash(userOrChat, 'normal');
const avatarBlobUrl = useMedia(avatarHash, undefined, undefined, lastSyncTime); const avatarBlobUrl = useMedia(avatarHash);
const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`; const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`;
const photoBlobUrl = useMedia(photoHash, undefined, undefined, lastSyncTime); const photoBlobUrl = useMedia(photoHash);
const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo); const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo);
const videoBlobUrl = useMedia(videoHash, undefined, undefined, lastSyncTime); const videoBlobUrl = useMedia(videoHash);
const fullMediaData = videoBlobUrl || photoBlobUrl; const fullMediaData = videoBlobUrl || photoBlobUrl;
const [isVideoReady, markVideoReady] = useFlag(); const [isVideoReady, markVideoReady] = useFlag();

View File

@ -48,7 +48,6 @@ type OwnProps = {
withSharedAnimation?: boolean; withSharedAnimation?: boolean;
sharedCanvasRef?: React.RefObject<HTMLCanvasElement>; sharedCanvasRef?: React.RefObject<HTMLCanvasElement>;
withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect
cacheBuster?: number;
onVideoEnded?: AnyToVoidFunction; onVideoEnded?: AnyToVoidFunction;
onAnimatedStickerLoop?: AnyToVoidFunction; onAnimatedStickerLoop?: AnyToVoidFunction;
}; };
@ -77,7 +76,6 @@ const StickerView: FC<OwnProps> = ({
withSharedAnimation, withSharedAnimation,
withTranslucentThumb, withTranslucentThumb,
sharedCanvasRef, sharedCanvasRef,
cacheBuster,
onVideoEnded, onVideoEnded,
onAnimatedStickerLoop, onAnimatedStickerLoop,
}) => { }) => {
@ -102,9 +100,7 @@ const StickerView: FC<OwnProps> = ({
const thumbDataUri = useThumbnail(sticker); const thumbDataUri = useThumbnail(sticker);
// Use preview instead of thumb but only if it's already loaded or when playing an animation is disabled // Use preview instead of thumb but only if it's already loaded or when playing an animation is disabled
const previewMediaDataFromCache: string | undefined = mediaLoader.getFromMemory(previewMediaHash); const previewMediaDataFromCache: string | undefined = mediaLoader.getFromMemory(previewMediaHash);
const previewMediaData = useMedia( const previewMediaData = useMedia(previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay));
previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay), undefined, cacheBuster,
);
const thumbData = customColor ? thumbDataUri : (previewMediaData || thumbDataUri); const thumbData = customColor ? thumbDataUri : (previewMediaData || thumbDataUri);
const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall); const shouldForcePreview = isUnsupportedVideo || (isStatic && isSmall);
@ -113,7 +109,7 @@ const StickerView: FC<OwnProps> = ({
// If preloaded preview is forced, it will render as thumb, so no need to load it again // If preloaded preview is forced, it will render as thumb, so no need to load it again
const shouldSkipFullMedia = Boolean(fullMediaHash === previewMediaHash && previewMediaData); const shouldSkipFullMedia = Boolean(fullMediaHash === previewMediaHash && previewMediaData);
const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia, undefined, cacheBuster); const fullMediaData = useMedia(fullMediaHash, !shouldLoad || shouldSkipFullMedia);
// If Lottie data is loaded we will only render thumb if it's good enough (from preview) // If Lottie data is loaded we will only render thumb if it's good enough (from preview)
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData)); const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
// Delay mounting on Android until heavy animation ends // Delay mounting on Android until heavy animation ends
@ -129,7 +125,7 @@ const StickerView: FC<OwnProps> = ({
const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef); const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef);
// Preload preview for Message Input and local message // Preload preview for Message Input and local message
useMedia(previewMediaHash, !shouldLoad || !shouldPreloadPreview, undefined, cacheBuster); useMedia(previewMediaHash, !shouldLoad || !shouldPreloadPreview);
const randomIdPrefix = useMemo(() => generateIdFor(ID_STORE, true), []); const randomIdPrefix = useMemo(() => generateIdFor(ID_STORE, true), []);
const renderId = [ const renderId = [

View File

@ -88,7 +88,6 @@ type StateProps = {
isSelectedForum?: boolean; isSelectedForum?: boolean;
canScrollDown?: boolean; canScrollDown?: boolean;
canChangeFolder?: boolean; canChangeFolder?: boolean;
lastSyncTime?: number;
lastMessageTopic?: ApiTopic; lastMessageTopic?: ApiTopic;
typingStatus?: ApiTypingStatus; typingStatus?: ApiTypingStatus;
withInterfaceAnimations?: boolean; withInterfaceAnimations?: boolean;
@ -117,7 +116,6 @@ const Chat: FC<OwnProps & StateProps> = ({
isSelectedForum, isSelectedForum,
canScrollDown, canScrollDown,
canChangeFolder, canChangeFolder,
lastSyncTime,
lastMessageTopic, lastMessageTopic,
typingStatus, typingStatus,
onDragEnter, onDragEnter,
@ -219,10 +217,10 @@ const Chat: FC<OwnProps & StateProps> = ({
// Load the forum topics to display unread count badge // Load the forum topics to display unread count badge
useEffect(() => { useEffect(() => {
if (isIntersecting && lastSyncTime && isForum && chat && chat.listedTopicIds === undefined) { if (isIntersecting && isForum && chat && chat.listedTopicIds === undefined) {
loadTopics({ chatId }); loadTopics({ chatId });
} }
}, [chat, chatId, isForum, isIntersecting, lastSyncTime, loadTopics]); }, [chat, chatId, isForum, isIntersecting]);
if (!chat) { if (!chat) {
return undefined; return undefined;
@ -254,7 +252,6 @@ const Chat: FC<OwnProps & StateProps> = ({
user={user} user={user}
userStatus={userStatus} userStatus={userStatus}
isSavedMessages={user?.isSelf} isSavedMessages={user?.isSelf}
lastSyncTime={lastSyncTime}
/> />
<AvatarBadge chatId={chatId} /> <AvatarBadge chatId={chatId} />
{chat.isCallActive && chat.isCallNotEmpty && ( {chat.isCallActive && chat.isCallNotEmpty && (
@ -363,7 +360,6 @@ export default memo(withGlobal<OwnProps>(
isSelectedForum, isSelectedForum,
canScrollDown: isSelected && messageListType === 'thread', canScrollDown: isSelected && messageListType === 'thread',
canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1, canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1,
lastSyncTime: global.lastSyncTime,
...(isOutgoing && chat.lastMessage && { ...(isOutgoing && chat.lastMessage && {
lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage), lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage),
}), }),

View File

@ -43,7 +43,6 @@ type StateProps = {
orderedFolderIds?: number[]; orderedFolderIds?: number[];
activeChatFolder: number; activeChatFolder: number;
currentUserId?: string; currentUserId?: string;
lastSyncTime?: number;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
maxFolders: number; maxFolders: number;
maxFolderInvites: number; maxFolderInvites: number;
@ -63,7 +62,6 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
activeChatFolder, activeChatFolder,
currentUserId, currentUserId,
isForumPanelOpen, isForumPanelOpen,
lastSyncTime,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
maxFolders, maxFolders,
shouldHideFolderTabs, shouldHideFolderTabs,
@ -88,10 +86,8 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
const lang = useLang(); const lang = useLang();
useEffect(() => { useEffect(() => {
if (lastSyncTime) { loadChatFolders();
loadChatFolders(); }, []);
}
}, [lastSyncTime, loadChatFolders]);
const allChatsFolder: ApiChatFolder = useMemo(() => { const allChatsFolder: ApiChatFolder = useMemo(() => {
return { return {
@ -275,7 +271,6 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
folderId={isFolder ? activeFolder.id : undefined} folderId={isFolder ? activeFolder.id : undefined}
isActive={isActive} isActive={isActive}
isForumPanelOpen={isForumPanelOpen} isForumPanelOpen={isForumPanelOpen}
lastSyncTime={lastSyncTime}
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
onSettingsScreenSelect={onSettingsScreenSelect} onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onLeftColumnContentChange} onLeftColumnContentChange={onLeftColumnContentChange}
@ -331,7 +326,6 @@ export default memo(withGlobal<OwnProps>(
}, },
}, },
currentUserId, currentUserId,
lastSyncTime,
archiveSettings, archiveSettings,
} = global; } = global;
const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global); const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global);
@ -342,7 +336,6 @@ export default memo(withGlobal<OwnProps>(
orderedFolderIds, orderedFolderIds,
activeChatFolder, activeChatFolder,
currentUserId, currentUserId,
lastSyncTime,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
hasArchivedChats: Boolean(archived?.length), hasArchivedChats: Boolean(archived?.length),
maxFolders: selectCurrentLimit(global, 'dialogFilters'), maxFolders: selectCurrentLimit(global, 'dialogFilters'),

View File

@ -41,7 +41,6 @@ type OwnProps = {
canDisplayArchive?: boolean; canDisplayArchive?: boolean;
archiveSettings: GlobalState['archiveSettings']; archiveSettings: GlobalState['archiveSettings'];
isForumPanelOpen?: boolean; isForumPanelOpen?: boolean;
lastSyncTime?: number;
foldersDispatch: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
onSettingsScreenSelect: (screen: SettingsScreens) => void; onSettingsScreenSelect: (screen: SettingsScreens) => void;
onLeftColumnContentChange: (content: LeftColumnContent) => void; onLeftColumnContentChange: (content: LeftColumnContent) => void;

View File

@ -54,7 +54,6 @@ type OwnProps = {
type StateProps = { type StateProps = {
chat?: ApiChat; chat?: ApiChat;
currentTopicId?: number; currentTopicId?: number;
lastSyncTime?: number;
withInterfaceAnimations?: boolean; withInterfaceAnimations?: boolean;
}; };
@ -65,7 +64,6 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
currentTopicId, currentTopicId,
isOpen, isOpen,
isHidden, isHidden,
lastSyncTime,
onTopicSearch, onTopicSearch,
onCloseAnimationEnd, onCloseAnimationEnd,
onOpenAnimationStart, onOpenAnimationStart,
@ -85,10 +83,10 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
useEffect(() => { useEffect(() => {
if (lastSyncTime && chat && !chat.topics) { if (chat && !chat.topics) {
loadTopics({ chatId: chat.id }); loadTopics({ chatId: chat.id });
} }
}, [chat, lastSyncTime, loadTopics]); }, [chat, loadTopics]);
const [isScrolled, setIsScrolled] = useState(false); const [isScrolled, setIsScrolled] = useState(false);
const lang = useLang(); const lang = useLang();
@ -126,7 +124,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id); const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id);
const [viewportIds, getMore] = useInfiniteScroll(() => { const [viewportIds, getMore] = useInfiniteScroll(() => {
if (!chat || !lastSyncTime) return; if (!chat) return;
loadTopics({ chatId: chat.id }); loadTopics({ chatId: chat.id });
}, orderedIds, !chat?.topicsCount || orderedIds.length >= chat.topicsCount, TOPICS_SLICE); }, orderedIds, !chat?.topicsCount || orderedIds.length >= chat.topicsCount, TOPICS_SLICE);
@ -294,7 +292,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
chat, chat,
lastSyncTime: global.lastSyncTime,
currentTopicId: chatId === currentChatId ? currentThreadId : undefined, currentTopicId: chatId === currentChatId ? currentThreadId : undefined,
withInterfaceAnimations: selectCanAnimateInterface(global), withInterfaceAnimations: selectCanAnimateInterface(global),
}; };

View File

@ -1,16 +1,17 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import { AudioOrigin, LoadMoreDirection } from '../../../types'; import { AudioOrigin, LoadMoreDirection } from '../../../types';
import type { StateProps } from './helpers/createMapStateToProps';
import { SLIDE_TRANSITION_DURATION } from '../../../config'; import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps'; import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat'; import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName'; import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -35,7 +36,6 @@ const AudioResults: FC<OwnProps & StateProps> = ({
usersById, usersById,
globalMessagesByChatId, globalMessagesByChatId,
foundIds, foundIds,
lastSyncTime,
activeDownloads, activeDownloads,
}) => { }) => {
const { const {
@ -47,7 +47,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
const lang = useLang(); const lang = useLang();
const currentType = isVoice ? 'voice' : 'audio'; const currentType = isVoice ? 'voice' : 'audio';
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: currentType, type: currentType,
@ -55,7 +55,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [currentType, lastSyncTime, searchMessagesGlobal, searchQuery]); }, [currentType, searchMessagesGlobal, searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) { if (!foundIds || !globalMessagesByChatId) {
@ -98,7 +98,6 @@ const AudioResults: FC<OwnProps & StateProps> = ({
origin={AudioOrigin.Search} origin={AudioOrigin.Search}
senderTitle={getSenderName(lang, message, chatsById, usersById)} senderTitle={getSenderName(lang, message, chatsById, usersById)}
date={message.date} date={message.date}
lastSyncTime={lastSyncTime}
className="scroll-item" className="scroll-item"
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}

View File

@ -44,7 +44,6 @@ type StateProps = {
chat?: ApiChat; chat?: ApiChat;
privateChatUser?: ApiUser; privateChatUser?: ApiUser;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus; lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
lastSyncTime?: number;
}; };
const ChatMessage: FC<OwnProps & StateProps> = ({ const ChatMessage: FC<OwnProps & StateProps> = ({
@ -53,7 +52,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chatId, chatId,
chat, chat,
privateChatUser, privateChatUser,
lastSyncTime,
}) => { }) => {
const { focusMessage } = getActions(); const { focusMessage } = getActions();
@ -85,7 +83,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chat={chat} chat={chat}
user={privateChatUser} user={privateChatUser}
isSavedMessages={privateChatUser?.isSelf} isSavedMessages={privateChatUser?.isSelf}
lastSyncTime={lastSyncTime}
/> />
<div className="info"> <div className="info">
<div className="info-row"> <div className="info-row">
@ -147,7 +144,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
chat, chat,
lastSyncTime: global.lastSyncTime,
...(privateChatUserId && { privateChatUser }), ...(privateChatUserId && { privateChatUser }),
}; };
}, },

View File

@ -1,7 +1,7 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact'; import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiChat, ApiMessage } from '../../../api/types'; import type { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
@ -9,6 +9,7 @@ import { selectTabState } from '../../../global/selectors';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import { renderMessageSummary } from '../../common/helpers/renderMessageText'; import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useAppLayout from '../../../hooks/useAppLayout'; import useAppLayout from '../../../hooks/useAppLayout';
@ -33,7 +34,6 @@ type StateProps = {
fetchingStatus?: { chats?: boolean; messages?: boolean }; fetchingStatus?: { chats?: boolean; messages?: boolean };
foundTopicIds?: number[]; foundTopicIds?: number[];
searchChatId?: string; searchChatId?: string;
lastSyncTime?: number;
}; };
const runThrottled = throttle((cb) => cb(), 500, true); const runThrottled = throttle((cb) => cb(), 500, true);
@ -45,7 +45,6 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
globalMessagesByChatId, globalMessagesByChatId,
chatsById, chatsById,
fetchingStatus, fetchingStatus,
lastSyncTime,
foundTopicIds, foundTopicIds,
searchChatId, searchChatId,
onSearchDateSelect, onSearchDateSelect,
@ -57,7 +56,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: 'text', type: 'text',
@ -65,7 +64,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [lastSyncTime, searchMessagesGlobal, searchQuery]); }, [searchQuery]);
const handleTopicClick = useCallback( const handleTopicClick = useCallback(
(id: number) => { (id: number) => {
@ -167,7 +166,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { byId: chatsById } = global.chats; const { byId: chatsById } = global.chats;
const { currentUserId, messages: { byChatId: globalMessagesByChatId }, lastSyncTime } = global; const { currentUserId, messages: { byChatId: globalMessagesByChatId } } = global;
const { const {
fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId, fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId,
} = selectTabState(global).globalSearch; } = selectTabState(global).globalSearch;
@ -181,7 +180,6 @@ export default memo(withGlobal<OwnProps>(
chatsById, chatsById,
fetchingStatus, fetchingStatus,
foundTopicIds, foundTopicIds,
lastSyncTime,
searchChatId, searchChatId,
}; };
}, },

View File

@ -1,9 +1,9 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useMemo, useRef, useState, memo, useCallback, useMemo, useRef, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiChat, ApiMessage } from '../../../api/types'; import type { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
@ -49,7 +49,6 @@ type StateProps = {
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>; globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
chatsById: Record<string, ApiChat>; chatsById: Record<string, ApiChat>;
fetchingStatus?: { chats?: boolean; messages?: boolean }; fetchingStatus?: { chats?: boolean; messages?: boolean };
lastSyncTime?: number;
}; };
const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4; const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4;
@ -58,10 +57,21 @@ const LESS_LIST_ITEMS_AMOUNT = 5;
const runThrottled = throttle((cb) => cb(), 500, false); const runThrottled = throttle((cb) => cb(), 500, false);
const ChatResults: FC<OwnProps & StateProps> = ({ const ChatResults: FC<OwnProps & StateProps> = ({
searchQuery, searchDate, dateSearchQuery, currentUserId, searchQuery,
localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds, searchDate,
foundIds, globalMessagesByChatId, chatsById, fetchingStatus, lastSyncTime, dateSearchQuery,
onReset, onSearchDateSelect, currentUserId,
localContactIds,
localChatIds,
localUserIds,
globalChatIds,
globalUserIds,
foundIds,
globalMessagesByChatId,
chatsById,
fetchingStatus,
onReset,
onSearchDateSelect,
}) => { }) => {
const { const {
openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
@ -77,7 +87,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false); const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: 'text', type: 'text',
@ -85,7 +95,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [lastSyncTime, searchMessagesGlobal, searchQuery]); }, [searchQuery]);
const handleChatClick = useCallback( const handleChatClick = useCallback(
(id: string) => { (id: string) => {
@ -293,6 +303,9 @@ export default memo(withGlobal<OwnProps>(
const { byId: chatsById } = global.chats; const { byId: chatsById } = global.chats;
const { userIds: localContactIds } = global.contactList || {}; const { userIds: localContactIds } = global.contactList || {};
const {
currentUserId, messages,
} = global;
if (!localContactIds) { if (!localContactIds) {
return { return {
@ -300,9 +313,6 @@ export default memo(withGlobal<OwnProps>(
}; };
} }
const {
currentUserId, messages, lastSyncTime,
} = global;
const { const {
fetchingStatus, globalResults, localResults, resultsByType, fetchingStatus, globalResults, localResults, resultsByType,
} = selectTabState(global).globalSearch; } = selectTabState(global).globalSearch;
@ -322,7 +332,6 @@ export default memo(withGlobal<OwnProps>(
globalMessagesByChatId, globalMessagesByChatId,
chatsById, chatsById,
fetchingStatus, fetchingStatus,
lastSyncTime,
}; };
}, },
)(ChatResults)); )(ChatResults));

View File

@ -1,20 +1,21 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useMemo, useRef, memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiMessage } from '../../../api/types'; import type { ApiMessage } from '../../../api/types';
import type { StateProps } from './helpers/createMapStateToProps';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import { SLIDE_TRANSITION_DURATION } from '../../../config'; import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps'; import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat'; import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName'; import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import { getMessageDocument } from '../../../global/helpers'; import { getMessageDocument } from '../../../global/helpers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -41,7 +42,6 @@ const FileResults: FC<OwnProps & StateProps> = ({
globalMessagesByChatId, globalMessagesByChatId,
foundIds, foundIds,
activeDownloads, activeDownloads,
lastSyncTime,
}) => { }) => {
const { const {
searchMessagesGlobal, searchMessagesGlobal,
@ -59,7 +59,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
}); });
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: CURRENT_TYPE, type: CURRENT_TYPE,
@ -67,7 +67,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [lastSyncTime, searchMessagesGlobal, searchQuery]); }, [searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) { if (!foundIds || !globalMessagesByChatId) {

View File

@ -1,18 +1,19 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useMemo, useRef, memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import type { StateProps } from './helpers/createMapStateToProps';
import { SLIDE_TRANSITION_DURATION } from '../../../config'; import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps'; import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat'; import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName'; import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -38,7 +39,6 @@ const LinkResults: FC<OwnProps & StateProps> = ({
usersById, usersById,
globalMessagesByChatId, globalMessagesByChatId,
foundIds, foundIds,
lastSyncTime,
isChatProtected, isChatProtected,
}) => { }) => {
const { const {
@ -57,7 +57,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
}); });
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: CURRENT_TYPE, type: CURRENT_TYPE,
@ -65,7 +65,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [lastSyncTime, searchMessagesGlobal, searchQuery]); }, [searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) { if (!foundIds || !globalMessagesByChatId) {

View File

@ -1,17 +1,18 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useMemo, useRef, memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { StateProps } from './helpers/createMapStateToProps';
import { LoadMoreDirection, MediaViewerOrigin } from '../../../types'; import { LoadMoreDirection, MediaViewerOrigin } from '../../../types';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { SLIDE_TRANSITION_DURATION } from '../../../config'; import { SLIDE_TRANSITION_DURATION } from '../../../config';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps'; import { createMapStateToProps } from './helpers/createMapStateToProps';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'; import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -36,7 +37,6 @@ const MediaResults: FC<OwnProps & StateProps> = ({
isLoading, isLoading,
globalMessagesByChatId, globalMessagesByChatId,
foundIds, foundIds,
lastSyncTime,
isChatProtected, isChatProtected,
}) => { }) => {
const { const {
@ -55,7 +55,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
}); });
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
searchMessagesGlobal({ searchMessagesGlobal({
type: CURRENT_TYPE, type: CURRENT_TYPE,
@ -63,7 +63,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
}); });
} }
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading // eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
}, [lastSyncTime, searchMessagesGlobal, searchQuery]); }, [searchMessagesGlobal, searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) { if (!foundIds || !globalMessagesByChatId) {

View File

@ -13,7 +13,6 @@ export type StateProps = {
usersById: Record<string, ApiUser>; usersById: Record<string, ApiUser>;
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>; globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
foundIds?: string[]; foundIds?: string[];
lastSyncTime?: number;
searchChatId?: string; searchChatId?: string;
activeDownloads: TabState['activeDownloads']['byChatId']; activeDownloads: TabState['activeDownloads']['byChatId'];
isChatProtected?: boolean; isChatProtected?: boolean;
@ -49,7 +48,6 @@ export function createMapStateToProps(type: ApiGlobalMessageSearchType) {
searchChatId: chatId, searchChatId: chatId,
activeDownloads, activeDownloads,
isChatProtected: chatId ? selectChat(global, chatId)?.isProtected : undefined, isChatProtected: chatId ? selectChat(global, chatId)?.isProtected : undefined,
lastSyncTime: global.lastSyncTime,
}; };
}; };
} }

View File

@ -26,9 +26,7 @@ type OwnProps = {
onScreenSelect: (screen: SettingsScreens) => void; onScreenSelect: (screen: SettingsScreens) => void;
}; };
type StateProps = { type StateProps = Pick<ISettings, 'languages' | 'language' | 'canTranslate' | 'doNotTranslate'>;
lastSyncTime?: number;
} & Pick<ISettings, 'languages' | 'language' | 'canTranslate' | 'doNotTranslate'>;
const SettingsLanguage: FC<OwnProps & StateProps> = ({ const SettingsLanguage: FC<OwnProps & StateProps> = ({
isActive, isActive,
@ -36,7 +34,6 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
language, language,
canTranslate, canTranslate,
doNotTranslate, doNotTranslate,
lastSyncTime,
onScreenSelect, onScreenSelect,
onReset, onReset,
}) => { }) => {
@ -52,10 +49,10 @@ const SettingsLanguage: FC<OwnProps & StateProps> = ({
const lang = useLang(); const lang = useLang();
useEffect(() => { useEffect(() => {
if (lastSyncTime && !languages?.length) { if (!languages?.length) {
loadLanguages(); loadLanguages();
} }
}, [languages, lastSyncTime, loadLanguages]); }, [languages]);
const handleChange = useCallback((langCode: string) => { const handleChange = useCallback((langCode: string) => {
setSelectedLanguage(langCode); setSelectedLanguage(langCode);
@ -161,7 +158,6 @@ export default memo(withGlobal<OwnProps>(
} = global.settings.byKey; } = global.settings.byKey;
return { return {
lastSyncTime: global.lastSyncTime,
languages, languages,
language, language,
canTranslate, canTranslate,

View File

@ -1,7 +1,7 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useEffect } from '../../../lib/teact/teact'; import React, { memo, useEffect } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import { SettingsScreens } from '../../../types'; import { SettingsScreens } from '../../../types';
import type { ApiUser } from '../../../api/types'; import type { ApiUser } from '../../../api/types';
@ -23,7 +23,6 @@ type OwnProps = {
type StateProps = { type StateProps = {
sessionCount: number; sessionCount: number;
currentUser?: ApiUser; currentUser?: ApiUser;
lastSyncTime?: number;
canBuyPremium?: boolean; canBuyPremium?: boolean;
}; };
@ -33,7 +32,6 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
onReset, onReset,
currentUser, currentUser,
sessionCount, sessionCount,
lastSyncTime,
canBuyPremium, canBuyPremium,
}) => { }) => {
const { const {
@ -46,10 +44,10 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
const profileId = currentUser?.id; const profileId = currentUser?.id;
useEffect(() => { useEffect(() => {
if (profileId && lastSyncTime) { if (profileId) {
loadProfilePhotos({ profileId }); loadProfilePhotos({ profileId });
} }
}, [lastSyncTime, profileId, loadProfilePhotos]); }, [profileId, loadProfilePhotos]);
useHistoryBack({ useHistoryBack({
isActive, isActive,
@ -57,10 +55,8 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
}); });
useEffect(() => { useEffect(() => {
if (lastSyncTime) { loadAuthorizations();
loadAuthorizations(); }, []);
}
}, [lastSyncTime, loadAuthorizations]);
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
@ -160,12 +156,11 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { currentUserId, lastSyncTime } = global; const { currentUserId } = global;
return { return {
sessionCount: global.activeSessions.orderedHashes.length, sessionCount: global.activeSessions.orderedHashes.length,
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined, currentUser: currentUserId ? selectUser(global, currentUserId) : undefined,
lastSyncTime,
canBuyPremium: !selectIsPremiumPurchaseBlocked(global), canBuyPremium: !selectIsPremiumPurchaseBlocked(global),
}; };
}, },

View File

@ -1,4 +1,3 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
useEffect, memo, useState, useRef, useLayoutEffect, useEffect, memo, useState, useRef, useLayoutEffect,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -6,6 +5,7 @@ import { addExtraClass } from '../../lib/teact/teact-dom';
import { requestNextMutation } from '../../lib/fasterdom/fasterdom'; import { requestNextMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, getGlobal, withGlobal } from '../../global'; import { getActions, getGlobal, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { LangCode } from '../../types'; import type { LangCode } from '../../types';
import type { import type {
ApiAttachBot, ApiAttachBot,
@ -45,7 +45,6 @@ import { Bundles, loadBundle } from '../../util/moduleLoader';
import updateIcon from '../../util/updateIcon'; import updateIcon from '../../util/updateIcon';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useBackgroundMode from '../../hooks/useBackgroundMode'; import useBackgroundMode from '../../hooks/useBackgroundMode';
import useBeforeUnload from '../../hooks/useBeforeUnload'; import useBeforeUnload from '../../hooks/useBeforeUnload';
import useSyncEffect from '../../hooks/useSyncEffect'; import useSyncEffect from '../../hooks/useSyncEffect';
@ -104,7 +103,6 @@ export interface OwnProps {
type StateProps = { type StateProps = {
isMasterTab?: boolean; isMasterTab?: boolean;
chat?: ApiChat; chat?: ApiChat;
lastSyncTime?: number;
isLeftColumnOpen: boolean; isLeftColumnOpen: boolean;
isMiddleColumnOpen: boolean; isMiddleColumnOpen: boolean;
isRightColumnOpen: boolean; isRightColumnOpen: boolean;
@ -148,6 +146,7 @@ type StateProps = {
chatlistModal?: TabState['chatlistModal']; chatlistModal?: TabState['chatlistModal'];
noRightColumnAnimation?: boolean; noRightColumnAnimation?: boolean;
withInterfaceAnimations?: boolean; withInterfaceAnimations?: boolean;
isSynced?: boolean;
}; };
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
@ -158,7 +157,6 @@ const REACTION_PICKER_LOADING_DELAY_MS = 7000; // 7 sec
let DEBUG_isLogged = false; let DEBUG_isLogged = false;
const Main: FC<OwnProps & StateProps> = ({ const Main: FC<OwnProps & StateProps> = ({
lastSyncTime,
isMobile, isMobile,
isLeftColumnOpen, isLeftColumnOpen,
isMiddleColumnOpen, isMiddleColumnOpen,
@ -204,6 +202,7 @@ const Main: FC<OwnProps & StateProps> = ({
isMasterTab, isMasterTab,
chatlistModal, chatlistModal,
noRightColumnAnimation, noRightColumnAnimation,
isSynced,
}) => { }) => {
const { const {
initMain, initMain,
@ -299,7 +298,7 @@ const Main: FC<OwnProps & StateProps> = ({
// Initial API calls // Initial API calls
useEffect(() => { useEffect(() => {
if (lastSyncTime && isMasterTab) { if (isMasterTab && isSynced) {
updateIsOnline(true); updateIsOnline(true);
loadConfig(); loadConfig();
loadAppConfig(); loadAppConfig();
@ -320,45 +319,40 @@ const Main: FC<OwnProps & StateProps> = ({
loadRecentReactions(); loadRecentReactions();
loadFeaturedEmojiStickers(); loadFeaturedEmojiStickers();
} }
}, [ }, [isMasterTab, isSynced]);
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
loadPremiumGifts, checkAppVersion, loadConfig, loadGenericEmojiEffects, loadDefaultTopicIcons, loadTopReactions,
loadDefaultStatusIcons, loadRecentReactions, loadRecentEmojiStatuses, isCurrentUserPremium, isMasterTab, initMain,
]);
// Initial Premium API calls // Initial Premium API calls
useEffect(() => { useEffect(() => {
if (lastSyncTime && isMasterTab && isCurrentUserPremium) { if (isMasterTab && isCurrentUserPremium) {
loadDefaultStatusIcons(); loadDefaultStatusIcons();
loadRecentEmojiStatuses(); loadRecentEmojiStatuses();
} }
}, [isCurrentUserPremium, isMasterTab, lastSyncTime, loadDefaultStatusIcons, loadRecentEmojiStatuses]); }, [isCurrentUserPremium, isMasterTab]);
// Language-based API calls // Language-based API calls
useEffect(() => { useEffect(() => {
if (lastSyncTime && isMasterTab) { if (isMasterTab) {
if (language !== BASE_EMOJI_KEYWORD_LANG) { if (language !== BASE_EMOJI_KEYWORD_LANG) {
loadEmojiKeywords({ language: language! }); loadEmojiKeywords({ language: language! });
} }
loadCountryList({ langCode: language }); loadCountryList({ langCode: language });
} }
}, [language, lastSyncTime, loadCountryList, loadEmojiKeywords, isMasterTab]); }, [language, isMasterTab]);
// Re-fetch cached saved emoji for `localDb` // Re-fetch cached saved emoji for `localDb`
useEffectWithPrevDeps(([prevLastSyncTime]) => { useEffect(() => {
if (!prevLastSyncTime && lastSyncTime && isMasterTab) { if (isMasterTab) {
loadCustomEmojis({ loadCustomEmojis({
ids: Object.keys(getGlobal().customEmojis.byId), ids: Object.keys(getGlobal().customEmojis.byId),
ignoreCache: true, ignoreCache: true,
}); });
} }
}, [lastSyncTime, isMasterTab, loadCustomEmojis]); }, [isMasterTab]);
// Sticker sets // Sticker sets
useEffect(() => { useEffect(() => {
if (lastSyncTime && isMasterTab) { if (isMasterTab && isSynced) {
if (!addedSetIds || !addedCustomEmojiIds) { if (!addedSetIds || !addedCustomEmojiIds) {
loadStickerSets(); loadStickerSets();
loadFavoriteStickers(); loadFavoriteStickers();
@ -368,45 +362,40 @@ const Main: FC<OwnProps & StateProps> = ({
loadAddedStickers(); loadAddedStickers();
} }
} }
}, [ }, [addedSetIds, addedCustomEmojiIds, isMasterTab, isSynced]);
lastSyncTime, addedSetIds, loadStickerSets, loadFavoriteStickers, loadAddedStickers, addedCustomEmojiIds,
isMasterTab,
]);
// Check version when service chat is ready // Check version when service chat is ready
useEffect(() => { useEffect(() => {
if (lastSyncTime && isServiceChatReady && isMasterTab) { if (isServiceChatReady && isMasterTab) {
checkVersionNotification(); checkVersionNotification();
} }
}, [lastSyncTime, isServiceChatReady, checkVersionNotification, isMasterTab]); }, [isServiceChatReady, isMasterTab]);
// Ensure time format // Ensure time format
useEffect(() => { useEffect(() => {
if (lastSyncTime && !wasTimeFormatSetManually) { if (!wasTimeFormatSetManually) {
ensureTimeFormat(); ensureTimeFormat();
} }
}, [lastSyncTime, wasTimeFormatSetManually, ensureTimeFormat]); }, [wasTimeFormatSetManually]);
// Parse deep link // Parse deep link
useEffect(() => { useEffect(() => {
const parsedInitialLocationHash = parseInitialLocationHash(); const parsedInitialLocationHash = parseInitialLocationHash();
if (lastSyncTime && parsedInitialLocationHash?.tgaddr) { if (parsedInitialLocationHash?.tgaddr) {
processDeepLink(decodeURIComponent(parsedInitialLocationHash.tgaddr)); processDeepLink(decodeURIComponent(parsedInitialLocationHash.tgaddr));
} }
}, [lastSyncTime]); }, []);
useEffectWithPrevDeps(([prevLastSyncTime]) => { useEffect(() => {
const parsedLocationHash = parseLocationHash(); const parsedLocationHash = parseLocationHash();
if (!parsedLocationHash) return; if (!parsedLocationHash) return;
if (!prevLastSyncTime && lastSyncTime) { openChat({
openChat({ id: parsedLocationHash.chatId,
id: parsedLocationHash.chatId, threadId: parsedLocationHash.threadId,
threadId: parsedLocationHash.threadId, type: parsedLocationHash.type,
type: parsedLocationHash.type, });
}); }, []);
}
}, [lastSyncTime, openChat]);
// Restore Transition slide class after async rendering // Restore Transition slide class after async rendering
useLayoutEffect(() => { useLayoutEffect(() => {
@ -579,7 +568,6 @@ export default memo(withGlobal<OwnProps>(
language, wasTimeFormatSetManually, language, wasTimeFormatSetManually,
}, },
}, },
lastSyncTime,
} = global; } = global;
const { const {
@ -623,7 +611,6 @@ export default memo(withGlobal<OwnProps>(
const deleteFolderDialog = deleteFolderDialogModal ? selectChatFolder(global, deleteFolderDialogModal) : undefined; const deleteFolderDialog = deleteFolderDialogModal ? selectChatFolder(global, deleteFolderDialogModal) : undefined;
return { return {
lastSyncTime,
isLeftColumnOpen: isLeftColumnShown, isLeftColumnOpen: isLeftColumnShown,
isMiddleColumnOpen: Boolean(chatId), isMiddleColumnOpen: Boolean(chatId),
isRightColumnOpen: selectIsRightColumnShown(global, isMobile), isRightColumnOpen: selectIsRightColumnShown(global, isMobile),
@ -668,6 +655,7 @@ export default memo(withGlobal<OwnProps>(
requestedDraft, requestedDraft,
chatlistModal, chatlistModal,
noRightColumnAnimation, noRightColumnAnimation,
isSynced: global.isSynced,
}; };
}, },
)(Main)); )(Main));

View File

@ -33,7 +33,6 @@ type UseMediaProps = {
message?: ApiMessage; message?: ApiMessage;
avatarOwner?: ApiChat | ApiUser; avatarOwner?: ApiChat | ApiUser;
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
lastSyncTime?: number;
delay: number | false; delay: number | false;
}; };
@ -88,7 +87,6 @@ export const useMediaProps = ({
&& getMessageMediaHash(message, 'pictogram'), && getMessageMediaHash(message, 'pictogram'),
undefined, undefined,
ApiMediaFormat.BlobUrl, ApiMediaFormat.BlobUrl,
undefined,
delay, delay,
); );
const previewMediaHash = getMediaHash(); const previewMediaHash = getMediaHash();
@ -96,7 +94,6 @@ export const useMediaProps = ({
previewMediaHash, previewMediaHash,
undefined, undefined,
ApiMediaFormat.BlobUrl, ApiMediaFormat.BlobUrl,
undefined,
delay, delay,
); );
const { const {
@ -106,7 +103,6 @@ export const useMediaProps = ({
getMediaHash(true), getMediaHash(true),
undefined, undefined,
message && getMessageMediaFormat(message, 'full'), message && getMessageMediaFormat(message, 'full'),
undefined,
delay, delay,
); );

View File

@ -589,8 +589,7 @@ export default memo(withGlobal<OwnProps>(
const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined; const userFullInfo = isPrivate ? selectUserFullInfo(global, chatId) : undefined;
const chatFullInfo = !isPrivate ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = !isPrivate ? selectChatFullInfo(global, chatId) : undefined;
const canGiftPremium = Boolean( const canGiftPremium = Boolean(
global.lastSyncTime userFullInfo?.premiumGifts?.length
&& userFullInfo?.premiumGifts?.length
&& !selectIsPremiumPurchaseBlocked(global), && !selectIsPremiumPurchaseBlocked(global),
); );

View File

@ -128,7 +128,6 @@ type StateProps = {
botInfo?: ApiBotInfo; botInfo?: ApiBotInfo;
threadTopMessageId?: number; threadTopMessageId?: number;
hasLinkedChat?: boolean; hasLinkedChat?: boolean;
lastSyncTime?: number;
topic?: ApiTopic; topic?: ApiTopic;
noMessageSendingAnimation?: boolean; noMessageSendingAnimation?: boolean;
isServiceNotificationsChat?: boolean; isServiceNotificationsChat?: boolean;
@ -178,7 +177,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
botInfo, botInfo,
threadTopMessageId, threadTopMessageId,
hasLinkedChat, hasLinkedChat,
lastSyncTime,
withBottomShift, withBottomShift,
withDefaultBg, withDefaultBg,
topic, topic,
@ -238,10 +236,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
}, [firstUnreadId]); }, [firstUnreadId]);
useEffect(() => { useEffect(() => {
if (!isCurrentUserPremium && isChannelChat && isReady && lastSyncTime) { if (!isCurrentUserPremium && isChannelChat && isReady) {
loadSponsoredMessages({ chatId }); loadSponsoredMessages({ chatId });
} }
}, [isCurrentUserPremium, chatId, isReady, isChannelChat, lastSyncTime, loadSponsoredMessages]); }, [isCurrentUserPremium, chatId, isReady, isChannelChat]);
// Updated only once when messages are loaded (as we want the unread divider to keep its position) // Updated only once when messages are loaded (as we want the unread divider to keep its position)
useSyncEffect(() => { useSyncEffect(() => {
@ -738,7 +736,6 @@ export default memo(withGlobal<OwnProps>(
botInfo, botInfo,
threadTopMessageId, threadTopMessageId,
hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined, hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined,
lastSyncTime: global.lastSyncTime,
topic, topic,
noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'), noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'),
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID, isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,

View File

@ -1,4 +1,3 @@
import type { RefObject } from 'react';
import React, { import React, {
useEffect, useState, memo, useMemo, useEffect, useState, memo, useMemo,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
@ -93,7 +92,7 @@ import './MiddleColumn.scss';
import styles from './MiddleColumn.module.scss'; import styles from './MiddleColumn.module.scss';
interface OwnProps { interface OwnProps {
leftColumnRef: RefObject<HTMLDivElement>; leftColumnRef: React.RefObject<HTMLDivElement>;
isMobile?: boolean; isMobile?: boolean;
} }
@ -138,7 +137,6 @@ type StateProps = {
activeEmojiInteractions?: ActiveEmojiInteraction[]; activeEmojiInteractions?: ActiveEmojiInteraction[];
shouldJoinToSend?: boolean; shouldJoinToSend?: boolean;
shouldSendJoinRequest?: boolean; shouldSendJoinRequest?: boolean;
lastSyncTime?: number;
pinnedIds?: number[]; pinnedIds?: number[];
topMessageId?: number; topMessageId?: number;
}; };
@ -192,7 +190,6 @@ function MiddleColumn({
shouldJoinToSend, shouldJoinToSend,
shouldSendJoinRequest, shouldSendJoinRequest,
shouldLoadFullChat, shouldLoadFullChat,
lastSyncTime,
pinnedIds, pinnedIds,
topMessageId, topMessageId,
}: OwnProps & StateProps) { }: OwnProps & StateProps) {
@ -320,10 +317,10 @@ function MiddleColumn({
}, [chatId, isPrivate, loadUser]); }, [chatId, isPrivate, loadUser]);
useEffect(() => { useEffect(() => {
if (!areChatSettingsLoaded && lastSyncTime) { if (!areChatSettingsLoaded) {
loadChatSettings({ chatId: chatId! }); loadChatSettings({ chatId: chatId! });
} }
}, [chatId, isPrivate, areChatSettingsLoaded, lastSyncTime, loadChatSettings]); }, [chatId, isPrivate, areChatSettingsLoaded]);
useEffect(() => { useEffect(() => {
if (chatId && shouldLoadFullChat && isReady) { if (chatId && shouldLoadFullChat && isReady) {
@ -662,7 +659,7 @@ export default memo(withGlobal<OwnProps>(
messageLanguageModal, messageLanguageModal,
} = selectTabState(global); } = selectTabState(global);
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
const { leftColumnWidth, lastSyncTime } = global; const { leftColumnWidth } = global;
const state: StateProps = { const state: StateProps = {
theme, theme,
@ -682,7 +679,6 @@ export default memo(withGlobal<OwnProps>(
currentTransitionKey: Math.max(0, messageLists.length - 1), currentTransitionKey: Math.max(0, messageLists.length - 1),
activeEmojiInteractions, activeEmojiInteractions,
leftColumnWidth, leftColumnWidth,
lastSyncTime,
}; };
if (!currentMessageList) { if (!currentMessageList) {
@ -711,7 +707,7 @@ export default memo(withGlobal<OwnProps>(
const canRestartBot = Boolean(bot && selectIsUserBlocked(global, bot.id)); const canRestartBot = Boolean(bot && selectIsUserBlocked(global, bot.id));
const canStartBot = !canRestartBot && isBotNotStarted; const canStartBot = !canRestartBot && isBotNotStarted;
const shouldLoadFullChat = Boolean( const shouldLoadFullChat = Boolean(
chat && isChatGroup(chat) && !selectChatFullInfo(global, chat.id) && lastSyncTime, chat && isChatGroup(chat) && !selectChatFullInfo(global, chat.id),
); );
const replyingToId = selectReplyingToId(global, chatId, threadId); const replyingToId = selectReplyingToId(global, chatId, threadId);
const shouldBlockSendInForum = chat?.isForum const shouldBlockSendInForum = chat?.isForum

View File

@ -1,10 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useEffect, useLayoutEffect, useRef, memo, useEffect, useLayoutEffect, useRef,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { requestMutation } from '../../lib/fasterdom/fasterdom'; import { requestMutation } from '../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { GlobalState, MessageListType } from '../../global/types'; import type { GlobalState, MessageListType } from '../../global/types';
import type { Signal } from '../../util/signals'; import type { Signal } from '../../util/signals';
import type { import type {
@ -101,7 +101,6 @@ type StateProps = {
messagesCount?: number; messagesCount?: number;
isComments?: boolean; isComments?: boolean;
isChatWithSelf?: boolean; isChatWithSelf?: boolean;
lastSyncTime?: number;
hasButtonInHeader?: boolean; hasButtonInHeader?: boolean;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
currentTransitionKey: number; currentTransitionKey: number;
@ -128,7 +127,6 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
messagesCount, messagesCount,
isComments, isComments,
isChatWithSelf, isChatWithSelf,
lastSyncTime,
hasButtonInHeader, hasButtonInHeader,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentTransitionKey, currentTransitionKey,
@ -166,10 +164,10 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
const isForum = chat?.isForum; const isForum = chat?.isForum;
useEffect(() => { useEffect(() => {
if (lastSyncTime && isReady && (threadId === MAIN_THREAD_ID || isForum)) { if (isReady && (threadId === MAIN_THREAD_ID || isForum)) {
loadPinnedMessages({ chatId, threadId }); loadPinnedMessages({ chatId, threadId });
} }
}, [chatId, loadPinnedMessages, lastSyncTime, threadId, isReady, isForum]); }, [chatId, threadId, isReady, isForum]);
useEnsureMessage(chatId, pinnedMessageId, pinnedMessage); useEnsureMessage(chatId, pinnedMessageId, pinnedMessage);
@ -484,7 +482,6 @@ export default memo(withGlobal<OwnProps>(
const { const {
isLeftColumnShown, shouldSkipHistoryAnimations, audioPlayer, messageLists, isLeftColumnShown, shouldSkipHistoryAnimations, audioPlayer, messageLists,
} = selectTabState(global); } = selectTabState(global);
const { lastSyncTime } = global;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer; const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
@ -523,7 +520,6 @@ export default memo(withGlobal<OwnProps>(
chat, chat,
messagesCount, messagesCount,
isChatWithSelf: selectIsChatWithSelf(global, chatId), isChatWithSelf: selectIsChatWithSelf(global, chatId),
lastSyncTime,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
currentTransitionKey: Math.max(0, messageLists.length - 1), currentTransitionKey: Math.max(0, messageLists.length - 1),
connectionState: global.connectionState, connectionState: global.connectionState,

View File

@ -1,10 +1,10 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, useEffect, useLayoutEffect, useMemo, useRef, useState, memo, useEffect, useLayoutEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom'; import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { import type {
TabState, MessageListType, GlobalState, ApiDraft, MessageList, TabState, MessageListType, GlobalState, ApiDraft, MessageList,
} from '../../../global/types'; } from '../../../global/types';
@ -179,7 +179,6 @@ type StateProps =
groupChatMembers?: ApiChatMember[]; groupChatMembers?: ApiChatMember[];
currentUserId?: string; currentUserId?: string;
recentEmojis: string[]; recentEmojis: string[];
lastSyncTime?: number;
contentToBeScheduled?: TabState['contentToBeScheduled']; contentToBeScheduled?: TabState['contentToBeScheduled'];
shouldSuggestStickers?: boolean; shouldSuggestStickers?: boolean;
shouldSuggestCustomEmoji?: boolean; shouldSuggestCustomEmoji?: boolean;
@ -207,8 +206,7 @@ type StateProps =
attachmentSettings: GlobalState['attachmentSettings']; attachmentSettings: GlobalState['attachmentSettings'];
slowMode?: ApiChatFullInfo['slowMode']; slowMode?: ApiChatFullInfo['slowMode'];
shouldUpdateStickerSetOrder?: boolean; shouldUpdateStickerSetOrder?: boolean;
} };
& Pick<GlobalState, 'connectionState'>;
enum MainButtonState { enum MainButtonState {
Send = 'send', Send = 'send',
@ -251,7 +249,6 @@ const Composer: FC<OwnProps & StateProps> = ({
isForCurrentMessageList, isForCurrentMessageList,
isCurrentUserPremium, isCurrentUserPremium,
canSendVoiceByPrivacy, canSendVoiceByPrivacy,
connectionState,
isChatWithBot, isChatWithBot,
isChatWithSelf, isChatWithSelf,
isChannel, isChannel,
@ -269,7 +266,6 @@ const Composer: FC<OwnProps & StateProps> = ({
topInlineBotIds, topInlineBotIds,
currentUserId, currentUserId,
captionLimit, captionLimit,
lastSyncTime,
contentToBeScheduled, contentToBeScheduled,
shouldSuggestStickers, shouldSuggestStickers,
shouldSuggestCustomEmoji, shouldSuggestCustomEmoji,
@ -349,16 +345,16 @@ const Composer: FC<OwnProps & StateProps> = ({
}, [chatId]); }, [chatId]);
useEffect(() => { useEffect(() => {
if (chatId && lastSyncTime && isReady) { if (chatId && isReady) {
loadScheduledHistory({ chatId }); loadScheduledHistory({ chatId });
} }
}, [isReady, chatId, loadScheduledHistory, lastSyncTime, threadId]); }, [isReady, chatId, loadScheduledHistory, threadId]);
useEffect(() => { useEffect(() => {
if (chatId && chat && lastSyncTime && !sendAsPeerIds && isReady && isChatSuperGroup(chat)) { if (chatId && chat && !sendAsPeerIds && isReady && isChatSuperGroup(chat)) {
loadSendAs({ chatId }); loadSendAs({ chatId });
} }
}, [chat, chatId, isReady, lastSyncTime, loadSendAs, sendAsPeerIds]); }, [chat, chatId, isReady, loadSendAs, sendAsPeerIds]);
const shouldAnimateSendAsButtonRef = useRef(false); const shouldAnimateSendAsButtonRef = useRef(false);
useSyncEffect(([prevChatId, prevSendAsPeerIds]) => { useSyncEffect(([prevChatId, prevSendAsPeerIds]) => {
@ -509,7 +505,7 @@ const Composer: FC<OwnProps & StateProps> = ({
help: inlineBotHelp, help: inlineBotHelp,
loadMore: loadMoreForInlineBot, loadMore: loadMoreForInlineBot,
} = useInlineBotTooltip( } = useInlineBotTooltip(
Boolean(isReady && isForCurrentMessageList && !hasAttachments && lastSyncTime), Boolean(isReady && isForCurrentMessageList && !hasAttachments),
chatId, chatId,
getHtml, getHtml,
inlineBots, inlineBots,
@ -564,7 +560,7 @@ const Composer: FC<OwnProps & StateProps> = ({
insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId); insertHtmlAndUpdateCursor(buildCustomEmojiHtml(emoji), inputId);
}); });
useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage, lastSyncTime); useDraft(draft, chatId, threadId, getHtml, setHtml, editingMessage);
const resetComposer = useLastCallback((shouldPreserveInput = false) => { const resetComposer = useLastCallback((shouldPreserveInput = false) => {
if (!shouldPreserveInput) { if (!shouldPreserveInput) {
@ -742,7 +738,7 @@ const Composer: FC<OwnProps & StateProps> = ({
isSilent?: boolean; isSilent?: boolean;
scheduledAt?: number; scheduledAt?: number;
}) => { }) => {
if (connectionState !== 'connectionStateReady' || !currentMessageList) { if (!currentMessageList) {
return; return;
} }
@ -790,7 +786,7 @@ const Composer: FC<OwnProps & StateProps> = ({
}); });
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => { const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
if (connectionState !== 'connectionStateReady' || !currentMessageList) { if (!currentMessageList) {
return; return;
} }
@ -1008,7 +1004,7 @@ const Composer: FC<OwnProps & StateProps> = ({
const handleInlineBotSelect = useLastCallback(( const handleInlineBotSelect = useLastCallback((
inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean, inlineResult: ApiBotInlineResult | ApiBotInlineMediaResult, isSilent?: boolean, isScheduleRequested?: boolean,
) => { ) => {
if (connectionState !== 'connectionStateReady' || !currentMessageList) { if (!currentMessageList) {
return; return;
} }
@ -1627,7 +1623,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
isOnActiveTab: !tabState.isBlurred, isOnActiveTab: !tabState.isBlurred,
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType), editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
connectionState: global.connectionState,
replyingToId, replyingToId,
draft: selectDraft(global, chatId, threadId), draft: selectDraft(global, chatId, threadId),
chat, chat,
@ -1652,7 +1647,6 @@ export default memo(withGlobal<OwnProps>(
groupChatMembers: chatFullInfo?.members, groupChatMembers: chatFullInfo?.members,
topInlineBotIds: global.topInlineBots?.userIds, topInlineBotIds: global.topInlineBots?.userIds,
currentUserId, currentUserId,
lastSyncTime: global.lastSyncTime,
contentToBeScheduled: tabState.contentToBeScheduled, contentToBeScheduled: tabState.contentToBeScheduled,
shouldSuggestStickers, shouldSuggestStickers,
shouldSuggestCustomEmoji, shouldSuggestCustomEmoji,

View File

@ -68,7 +68,6 @@ export type OwnProps = {
type StateProps = { type StateProps = {
isLeftColumnShown: boolean; isLeftColumnShown: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
lastSyncTime?: number;
isBackgroundTranslucent?: boolean; isBackgroundTranslucent?: boolean;
}; };
@ -82,7 +81,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
canSendGifs, canSendGifs,
isLeftColumnShown, isLeftColumnShown,
isCurrentUserPremium, isCurrentUserPremium,
lastSyncTime,
onLoad, onLoad,
onClose, onClose,
onEmojiSelect, onEmojiSelect,
@ -112,6 +110,8 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile); const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose, undefined, isMobile);
const { shouldRender, transitionClassNames } = useShowTransition(isOpen, onClose, false, false); const { shouldRender, transitionClassNames } = useShowTransition(isOpen, onClose, false, false);
const lang = useLang();
if (!isActivated && isOpen) { if (!isActivated && isOpen) {
isActivated = true; isActivated = true;
} }
@ -127,10 +127,10 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
}, [canSendPlainText]); }, [canSendPlainText]);
useEffect(() => { useEffect(() => {
if (lastSyncTime && isCurrentUserPremium) { if (isCurrentUserPremium) {
loadPremiumSetStickers(); loadPremiumSetStickers();
} }
}, [isCurrentUserPremium, lastSyncTime, loadPremiumSetStickers]); }, [isCurrentUserPremium, loadPremiumSetStickers]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!isMobile || !isOpen || isAttachmentModal) { if (!isMobile || !isOpen || isAttachmentModal) {
@ -204,8 +204,6 @@ const SymbolMenu: FC<OwnProps & StateProps> = ({
onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder); onStickerSelect?.(sticker, isSilent, shouldSchedule, true, canUpdateStickerSetsOrder);
}); });
const lang = useLang();
function renderContent(isActive: boolean, isFrom: boolean) { function renderContent(isActive: boolean, isFrom: boolean) {
switch (activeTab) { switch (activeTab) {
case SymbolMenuTabs.Emoji: case SymbolMenuTabs.Emoji:
@ -350,7 +348,6 @@ export default memo(withGlobal<OwnProps>(
return { return {
isLeftColumnShown: selectTabState(global).isLeftColumnShown, isLeftColumnShown: selectTabState(global).isLeftColumnShown,
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
lastSyncTime: global.lastSyncTime,
isBackgroundTranslucent: selectIsContextMenuTranslucent(global), isBackgroundTranslucent: selectIsContextMenuTranslucent(global),
}; };
}, },

View File

@ -37,14 +37,13 @@ const useDraft = (
getHtml: Signal<string>, getHtml: Signal<string>,
setHtml: (html: string) => void, setHtml: (html: string) => void,
editedMessage: ApiMessage | undefined, editedMessage: ApiMessage | undefined,
lastSyncTime?: number,
) => { ) => {
const { saveDraft, clearDraft, loadCustomEmojis } = getActions(); const { saveDraft, clearDraft, loadCustomEmojis } = getActions();
const isEditing = Boolean(editedMessage); const isEditing = Boolean(editedMessage);
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => { const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}, shouldForce = false) => {
if (isEditing || !lastSyncTime) return; if (isEditing) return;
const html = getHtml(); const html = getHtml();

View File

@ -32,7 +32,6 @@ type OwnProps = {
album: IAlbum; album: IAlbum;
observeIntersection: ObserveFn; observeIntersection: ObserveFn;
hasCustomAppendix?: boolean; hasCustomAppendix?: boolean;
lastSyncTime?: number;
isOwn: boolean; isOwn: boolean;
isProtected?: boolean; isProtected?: boolean;
albumLayout: IAlbumLayout; albumLayout: IAlbumLayout;
@ -49,7 +48,6 @@ const Album: FC<OwnProps & StateProps> = ({
album, album,
observeIntersection, observeIntersection,
hasCustomAppendix, hasCustomAppendix,
lastSyncTime,
isOwn, isOwn,
isProtected, isProtected,
albumLayout, albumLayout,
@ -107,7 +105,6 @@ const Album: FC<OwnProps & StateProps> = ({
canAutoLoad={canAutoLoad} canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay} canAutoPlay={canAutoPlay}
uploadProgress={uploadProgress} uploadProgress={uploadProgress}
lastSyncTime={lastSyncTime}
dimensions={dimensions} dimensions={dimensions}
isProtected={isProtected} isProtected={isProtected}
onClick={onMediaClick} onClick={onMediaClick}

View File

@ -24,7 +24,6 @@ type OwnProps = {
customEmojiId: string; customEmojiId: string;
withEffects?: boolean; withEffects?: boolean;
isOwn?: boolean; isOwn?: boolean;
lastSyncTime?: number;
forceLoadPreview?: boolean; forceLoadPreview?: boolean;
messageId?: number; messageId?: number;
chatId?: string; chatId?: string;

View File

@ -25,7 +25,6 @@ type OwnProps = {
withEffects?: boolean; withEffects?: boolean;
isOwn?: boolean; isOwn?: boolean;
observeIntersection?: ObserveFn; observeIntersection?: ObserveFn;
lastSyncTime?: number;
forceLoadPreview?: boolean; forceLoadPreview?: boolean;
messageId?: number; messageId?: number;
chatId?: string; chatId?: string;
@ -43,7 +42,6 @@ const QUALITY = 1;
const AnimatedEmoji: FC<OwnProps & StateProps> = ({ const AnimatedEmoji: FC<OwnProps & StateProps> = ({
isOwn, isOwn,
observeIntersection, observeIntersection,
lastSyncTime,
forceLoadPreview, forceLoadPreview,
messageId, messageId,
chatId, chatId,
@ -67,7 +65,6 @@ const AnimatedEmoji: FC<OwnProps & StateProps> = ({
quality={QUALITY} quality={QUALITY}
noLoad={!isIntersecting} noLoad={!isIntersecting}
forcePreview={forceLoadPreview} forcePreview={forceLoadPreview}
lastSyncTime={lastSyncTime}
play={isIntersecting} play={isIntersecting}
forceOnHeavyAnimation forceOnHeavyAnimation
ref={ref} ref={ref}

View File

@ -20,13 +20,11 @@ const DEFAULT_PREVIEW_DIMENSIONS = {
type OwnProps = { type OwnProps = {
message: ApiMessage; message: ApiMessage;
canAutoLoadMedia?: boolean; canAutoLoadMedia?: boolean;
lastSyncTime?: number;
}; };
const Game: FC<OwnProps> = ({ const Game: FC<OwnProps> = ({
message, message,
canAutoLoadMedia, canAutoLoadMedia,
lastSyncTime,
}) => { }) => {
const { clickBotInlineButton } = getActions(); const { clickBotInlineButton } = getActions();
const game = message.content.game!; const game = message.content.game!;
@ -34,8 +32,8 @@ const Game: FC<OwnProps> = ({
title, description, title, description,
} = game; } = game;
const photoHash = Boolean(lastSyncTime) && getGamePreviewPhotoHash(game); const photoHash = getGamePreviewPhotoHash(game);
const videoHash = Boolean(lastSyncTime) && getGamePreviewVideoHash(game); const videoHash = getGamePreviewVideoHash(game);
const photoBlobUrl = useMedia(photoHash, !canAutoLoadMedia); const photoBlobUrl = useMedia(photoHash, !canAutoLoadMedia);
const videoBlobUrl = useMedia(videoHash, !canAutoLoadMedia); const videoBlobUrl = useMedia(videoHash, !canAutoLoadMedia);

View File

@ -19,14 +19,14 @@ import styles from './InvoiceMediaPreview.module.scss';
type OwnProps = { type OwnProps = {
message: ApiMessage; message: ApiMessage;
lastSyncTime?: number; isConnected: boolean;
}; };
const POLLING_INTERVAL = 30000; const POLLING_INTERVAL = 30000;
const InvoiceMediaPreview: FC<OwnProps> = ({ const InvoiceMediaPreview: FC<OwnProps> = ({
message, message,
lastSyncTime, isConnected,
}) => { }) => {
const { openInvoice, loadExtendedMedia } = getActions(); const { openInvoice, loadExtendedMedia } = getActions();
const lang = useLang(); const lang = useLang();
@ -38,7 +38,7 @@ const InvoiceMediaPreview: FC<OwnProps> = ({
loadExtendedMedia({ chatId, ids: [id] }); loadExtendedMedia({ chatId, ids: [id] });
}); });
useInterval(refreshExtendedMedia, lastSyncTime ? POLLING_INTERVAL : undefined); useInterval(refreshExtendedMedia, isConnected ? POLLING_INTERVAL : undefined);
const { const {
amount, amount,

View File

@ -54,7 +54,6 @@ const SVG_PIN = { __html: '<svg version="1.1" class="round-pin" xmlns="http://ww
type OwnProps = { type OwnProps = {
message: ApiMessage; message: ApiMessage;
peer?: ApiUser | ApiChat; peer?: ApiUser | ApiChat;
lastSyncTime?: number;
isInSelectMode?: boolean; isInSelectMode?: boolean;
isSelected?: boolean; isSelected?: boolean;
theme: ISettings['theme']; theme: ISettings['theme'];
@ -63,7 +62,6 @@ type OwnProps = {
const Location: FC<OwnProps> = ({ const Location: FC<OwnProps> = ({
message, message,
peer, peer,
lastSyncTime,
isInSelectMode, isInSelectMode,
isSelected, isSelected,
theme, theme,
@ -91,7 +89,7 @@ const Location: FC<OwnProps> = ({
width, height, zoom, scale, width, height, zoom, scale,
} = DEFAULT_MAP_CONFIG; } = DEFAULT_MAP_CONFIG;
const mediaHash = Boolean(lastSyncTime) && buildStaticMapHash(point, width, height, zoom, scale); const mediaHash = buildStaticMapHash(point, width, height, zoom, scale);
const mediaBlobUrl = useMedia(mediaHash); const mediaBlobUrl = useMedia(mediaHash);
const prevMediaBlobUrl = usePrevious(mediaBlobUrl); const prevMediaBlobUrl = usePrevious(mediaBlobUrl);
const mapBlobUrl = mediaBlobUrl || prevMediaBlobUrl; const mapBlobUrl = mediaBlobUrl || prevMediaBlobUrl;

View File

@ -1,4 +1,3 @@
import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
memo, memo,
useEffect, useEffect,
@ -8,6 +7,7 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { import type {
ActiveEmojiInteraction, ActiveReaction, ChatTranslatedMessages, MessageListType, ActiveEmojiInteraction, ActiveReaction, ChatTranslatedMessages, MessageListType,
} from '../../../global/types'; } from '../../../global/types';
@ -220,7 +220,6 @@ type StateProps = {
isChannel?: boolean; isChannel?: boolean;
isGroup?: boolean; isGroup?: boolean;
canReply?: boolean; canReply?: boolean;
lastSyncTime?: number;
highlight?: string; highlight?: string;
animatedEmoji?: string; animatedEmoji?: string;
animatedCustomEmoji?: string; animatedCustomEmoji?: string;
@ -256,6 +255,7 @@ type StateProps = {
requestedTranslationLanguage?: string; requestedTranslationLanguage?: string;
withReactionEffects?: boolean; withReactionEffects?: boolean;
withStickerEffects?: boolean; withStickerEffects?: boolean;
isConnected: boolean;
}; };
type MetaPosition = type MetaPosition =
@ -330,7 +330,6 @@ const Message: FC<OwnProps & StateProps> = ({
isChannel, isChannel,
isGroup, isGroup,
canReply, canReply,
lastSyncTime,
highlight, highlight,
animatedEmoji, animatedEmoji,
animatedCustomEmoji, animatedCustomEmoji,
@ -364,6 +363,7 @@ const Message: FC<OwnProps & StateProps> = ({
requestedTranslationLanguage, requestedTranslationLanguage,
withReactionEffects, withReactionEffects,
withStickerEffects, withStickerEffects,
isConnected,
onPinnedIntersectionChange, onPinnedIntersectionChange,
}) => { }) => {
const { const {
@ -789,7 +789,6 @@ const Message: FC<OwnProps & StateProps> = ({
user={avatarUser} user={avatarUser}
chat={avatarChat} chat={avatarChat}
text={hiddenName} text={hiddenName}
lastSyncTime={lastSyncTime}
onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined} onClick={(avatarUser || avatarChat) ? handleAvatarClick : undefined}
/> />
); );
@ -915,7 +914,6 @@ const Message: FC<OwnProps & StateProps> = ({
observeIntersection={observeIntersectionForLoading} observeIntersection={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying} observeIntersectionForPlaying={observeIntersectionForPlaying}
shouldLoop={shouldLoopStickers} shouldLoop={shouldLoopStickers}
lastSyncTime={lastSyncTime}
shouldPlayEffect={( shouldPlayEffect={(
sticker.hasEffect && (( sticker.hasEffect && ((
memoFirstUnreadIdRef.current && messageId >= memoFirstUnreadIdRef.current memoFirstUnreadIdRef.current && messageId >= memoFirstUnreadIdRef.current
@ -932,7 +930,6 @@ const Message: FC<OwnProps & StateProps> = ({
withEffects={withStickerEffects && isUserId(chatId)} withEffects={withStickerEffects && isUserId(chatId)}
isOwn={isOwn} isOwn={isOwn}
observeIntersection={observeIntersectionForLoading} observeIntersection={observeIntersectionForLoading}
lastSyncTime={lastSyncTime}
forceLoadPreview={isLocal} forceLoadPreview={isLocal}
messageId={messageId} messageId={messageId}
chatId={chatId} chatId={chatId}
@ -945,7 +942,6 @@ const Message: FC<OwnProps & StateProps> = ({
withEffects={withStickerEffects && isUserId(chatId)} withEffects={withStickerEffects && isUserId(chatId)}
isOwn={isOwn} isOwn={isOwn}
observeIntersection={observeIntersectionForLoading} observeIntersection={observeIntersectionForLoading}
lastSyncTime={lastSyncTime}
forceLoadPreview={isLocal} forceLoadPreview={isLocal}
messageId={messageId} messageId={messageId}
chatId={chatId} chatId={chatId}
@ -960,7 +956,6 @@ const Message: FC<OwnProps & StateProps> = ({
isOwn={isOwn} isOwn={isOwn}
isProtected={isProtected} isProtected={isProtected}
hasCustomAppendix={hasCustomAppendix} hasCustomAppendix={hasCustomAppendix}
lastSyncTime={lastSyncTime}
onMediaClick={handleAlbumMediaClick} onMediaClick={handleAlbumMediaClick}
/> />
)} )}
@ -993,7 +988,6 @@ const Message: FC<OwnProps & StateProps> = ({
message={message} message={message}
observeIntersection={observeIntersectionForLoading} observeIntersection={observeIntersectionForLoading}
canAutoLoad={canAutoLoadMedia} canAutoLoad={canAutoLoadMedia}
lastSyncTime={lastSyncTime}
isDownloading={isDownloading} isDownloading={isDownloading}
/> />
)} )}
@ -1007,7 +1001,6 @@ const Message: FC<OwnProps & StateProps> = ({
canAutoLoad={canAutoLoadMedia} canAutoLoad={canAutoLoadMedia}
canAutoPlay={canAutoPlayMedia} canAutoPlay={canAutoPlayMedia}
uploadProgress={uploadProgress} uploadProgress={uploadProgress}
lastSyncTime={lastSyncTime}
isDownloading={isDownloading} isDownloading={isDownloading}
isProtected={isProtected} isProtected={isProtected}
asForwarded={asForwarded} asForwarded={asForwarded}
@ -1021,7 +1014,6 @@ const Message: FC<OwnProps & StateProps> = ({
message={message} message={message}
origin={AudioOrigin.Inline} origin={AudioOrigin.Inline}
uploadProgress={uploadProgress} uploadProgress={uploadProgress}
lastSyncTime={lastSyncTime}
isSelectable={isInDocumentGroup} isSelectable={isInDocumentGroup}
isSelected={isSelected} isSelected={isSelected}
noAvatars={noAvatars} noAvatars={noAvatars}
@ -1062,13 +1054,12 @@ const Message: FC<OwnProps & StateProps> = ({
<Game <Game
message={message} message={message}
canAutoLoadMedia={canAutoLoadMedia} canAutoLoadMedia={canAutoLoadMedia}
lastSyncTime={lastSyncTime}
/> />
)} )}
{invoice?.extendedMedia && ( {invoice?.extendedMedia && (
<InvoiceMediaPreview <InvoiceMediaPreview
message={message} message={message}
lastSyncTime={lastSyncTime} isConnected={isConnected}
/> />
)} )}
@ -1108,7 +1099,6 @@ const Message: FC<OwnProps & StateProps> = ({
canAutoLoad={canAutoLoadMedia} canAutoLoad={canAutoLoadMedia}
canAutoPlay={canAutoPlayMedia} canAutoPlay={canAutoPlayMedia}
asForwarded={asForwarded} asForwarded={asForwarded}
lastSyncTime={lastSyncTime}
isDownloading={isDownloading} isDownloading={isDownloading}
isProtected={isProtected} isProtected={isProtected}
theme={theme} theme={theme}
@ -1129,7 +1119,6 @@ const Message: FC<OwnProps & StateProps> = ({
{location && ( {location && (
<Location <Location
message={message} message={message}
lastSyncTime={lastSyncTime}
isInSelectMode={isInSelectMode} isInSelectMode={isInSelectMode}
isSelected={isSelected} isSelected={isSelected}
theme={theme} theme={theme}
@ -1346,7 +1335,6 @@ export default memo(withGlobal<OwnProps>(
const { const {
focusedMessage, forwardMessages, activeReactions, activeEmojiInteractions, focusedMessage, forwardMessages, activeReactions, activeEmojiInteractions,
} = selectTabState(global); } = selectTabState(global);
const { lastSyncTime } = global;
const { const {
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup, message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
} = ownProps; } = ownProps;
@ -1431,6 +1419,8 @@ export default memo(withGlobal<OwnProps>(
const chatTranslations = selectChatTranslations(global, chatId); const chatTranslations = selectChatTranslations(global, chatId);
const requestedTranslationLanguage = selectRequestedTranslationLanguage(global, chatId, message.id); const requestedTranslationLanguage = selectRequestedTranslationLanguage(global, chatId, message.id);
const isConnected = global.connectionState === 'connectionStateReady';
return { return {
theme: selectTheme(global), theme: selectTheme(global),
chatUsernames, chatUsernames,
@ -1453,7 +1443,6 @@ export default memo(withGlobal<OwnProps>(
isChannel, isChannel,
isGroup, isGroup,
canReply, canReply,
lastSyncTime,
highlight, highlight,
animatedEmoji, animatedEmoji,
animatedCustomEmoji, animatedCustomEmoji,
@ -1492,6 +1481,7 @@ export default memo(withGlobal<OwnProps>(
hasLinkedChat: Boolean(chatFullInfo?.linkedChatId), hasLinkedChat: Boolean(chatFullInfo?.linkedChatId),
withReactionEffects: selectPerformanceSettingsValue(global, 'reactionEffects'), withReactionEffects: selectPerformanceSettingsValue(global, 'reactionEffects'),
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'), withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
isConnected,
...((canShowSender || isLocation) && { sender }), ...((canShowSender || isLocation) && { sender }),
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }), ...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
...(typeof uploadProgress === 'number' && { uploadProgress }), ...(typeof uploadProgress === 'number' && { uploadProgress }),

View File

@ -39,7 +39,6 @@ type OwnProps = {
message: ApiMessage; message: ApiMessage;
observeIntersection: ObserveFn; observeIntersection: ObserveFn;
canAutoLoad?: boolean; canAutoLoad?: boolean;
lastSyncTime?: number;
isDownloading?: boolean; isDownloading?: boolean;
}; };
@ -51,7 +50,6 @@ const RoundVideo: FC<OwnProps> = ({
message, message,
observeIntersection, observeIntersection,
canAutoLoad, canAutoLoad,
lastSyncTime,
isDownloading, isDownloading,
}) => { }) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
@ -66,19 +64,17 @@ const RoundVideo: FC<OwnProps> = ({
const isIntersecting = useIsIntersecting(ref, observeIntersection); const isIntersecting = useIsIntersecting(ref, observeIntersection);
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad); const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
const shouldLoad = Boolean(isLoadAllowed && isIntersecting && lastSyncTime); const shouldLoad = Boolean(isLoadAllowed && isIntersecting);
const { mediaData, loadProgress } = useMediaWithLoadProgress( const { mediaData, loadProgress } = useMediaWithLoadProgress(
getMessageMediaHash(message, 'inline'), getMessageMediaHash(message, 'inline'),
!shouldLoad, !shouldLoad,
getMessageMediaFormat(message, 'inline'), getMessageMediaFormat(message, 'inline'),
lastSyncTime,
); );
const { loadProgress: downloadProgress } = useMediaWithLoadProgress( const { loadProgress: downloadProgress } = useMediaWithLoadProgress(
getMessageMediaHash(message, 'download'), getMessageMediaHash(message, 'download'),
!isDownloading, !isDownloading,
ApiMediaFormat.BlobUrl, ApiMediaFormat.BlobUrl,
lastSyncTime,
); );
const [isPlayerReady, markPlayerReady] = useFlag(); const [isPlayerReady, markPlayerReady] = useFlag();

View File

@ -32,7 +32,6 @@ type OwnProps = {
observeIntersection: ObserveFn; observeIntersection: ObserveFn;
observeIntersectionForPlaying: ObserveFn; observeIntersectionForPlaying: ObserveFn;
shouldLoop?: boolean; shouldLoop?: boolean;
lastSyncTime?: number;
shouldPlayEffect?: boolean; shouldPlayEffect?: boolean;
withEffect?: boolean; withEffect?: boolean;
onPlayEffect?: VoidFunction; onPlayEffect?: VoidFunction;
@ -40,7 +39,7 @@ type OwnProps = {
}; };
const Sticker: FC<OwnProps> = ({ const Sticker: FC<OwnProps> = ({
message, observeIntersection, observeIntersectionForPlaying, shouldLoop, lastSyncTime, message, observeIntersection, observeIntersectionForPlaying, shouldLoop,
shouldPlayEffect, withEffect, onPlayEffect, onStopEffect, shouldPlayEffect, withEffect, onPlayEffect, onStopEffect,
}) => { }) => {
const { showNotification, openStickerSet } = getActions(); const { showNotification, openStickerSet } = getActions();
@ -65,7 +64,6 @@ const Sticker: FC<OwnProps> = ({
mediaHashEffect, mediaHashEffect,
!canLoad || !hasEffect, !canLoad || !hasEffect,
ApiMediaFormat.BlobUrl, ApiMediaFormat.BlobUrl,
lastSyncTime,
); );
const [isPlayingEffect, startPlayingEffect, stopPlayingEffect] = useFlag(); const [isPlayingEffect, startPlayingEffect, stopPlayingEffect] = useFlag();
@ -137,7 +135,6 @@ const Sticker: FC<OwnProps> = ({
noLoad={!canLoad} noLoad={!canLoad}
noPlay={!canPlay} noPlay={!canPlay}
withSharedAnimation withSharedAnimation
cacheBuster={lastSyncTime}
/> />
{hasEffect && withEffect && canLoad && isPlayingEffect && ( {hasEffect && withEffect && canLoad && isPlayingEffect && (
<AnimatedSticker <AnimatedSticker

View File

@ -48,7 +48,6 @@ export type OwnProps = {
forcedWidth?: number; forcedWidth?: number;
dimensions?: IMediaDimensions; dimensions?: IMediaDimensions;
asForwarded?: boolean; asForwarded?: boolean;
lastSyncTime?: number;
isDownloading?: boolean; isDownloading?: boolean;
isProtected?: boolean; isProtected?: boolean;
onClick?: (id: number) => void; onClick?: (id: number) => void;
@ -65,7 +64,6 @@ const Video: FC<OwnProps> = ({
canAutoPlay, canAutoPlay,
uploadProgress, uploadProgress,
forcedWidth, forcedWidth,
lastSyncTime,
dimensions, dimensions,
asForwarded, asForwarded,
isDownloading, isDownloading,
@ -95,13 +93,13 @@ const Video: FC<OwnProps> = ({
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad); const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
const shouldLoad = Boolean(isLoadAllowed && isIntersectingForLoading && lastSyncTime); const shouldLoad = Boolean(isLoadAllowed && isIntersectingForLoading);
const [isPlayAllowed, setIsPlayAllowed] = useState(Boolean(canAutoPlay && !isSpoilerShown)); const [isPlayAllowed, setIsPlayAllowed] = useState(Boolean(canAutoPlay && !isSpoilerShown));
const fullMediaHash = getMessageMediaHash(message, 'inline'); const fullMediaHash = getMessageMediaHash(message, 'inline');
const [isFullMediaPreloaded] = useState(Boolean(fullMediaHash && mediaLoader.getFromMemory(fullMediaHash))); const [isFullMediaPreloaded] = useState(Boolean(fullMediaHash && mediaLoader.getFromMemory(fullMediaHash)));
const { mediaData, loadProgress } = useMediaWithLoadProgress( const { mediaData, loadProgress } = useMediaWithLoadProgress(
fullMediaHash, !shouldLoad, getMessageMediaFormat(message, 'inline'), lastSyncTime, fullMediaHash, !shouldLoad, getMessageMediaFormat(message, 'inline'),
); );
const fullMediaData = localBlobUrl || mediaData; const fullMediaData = localBlobUrl || mediaData;
const [isPlayerReady, markPlayerReady] = useFlag(); const [isPlayerReady, markPlayerReady] = useFlag();
@ -112,8 +110,8 @@ const Video: FC<OwnProps> = ({
const previewMediaHash = getMessageMediaHash(message, 'preview'); const previewMediaHash = getMessageMediaHash(message, 'preview');
const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash))); const [isPreviewPreloaded] = useState(Boolean(previewMediaHash && mediaLoader.getFromMemory(previewMediaHash)));
const canLoadPreview = isIntersectingForLoading && lastSyncTime; const canLoadPreview = isIntersectingForLoading;
const previewBlobUrl = useMedia(previewMediaHash, !canLoadPreview, undefined, lastSyncTime); const previewBlobUrl = useMedia(previewMediaHash, !canLoadPreview);
const previewClassNames = useMediaTransition((hasThumb || previewBlobUrl) && !isPlayerReady); const previewClassNames = useMediaTransition((hasThumb || previewBlobUrl) && !isPlayerReady);
const noThumb = !hasThumb || previewBlobUrl || isPlayerReady; const noThumb = !hasThumb || previewBlobUrl || isPlayerReady;
@ -127,7 +125,6 @@ const Video: FC<OwnProps> = ({
getMessageMediaHash(message, 'download'), getMessageMediaHash(message, 'download'),
!isDownloading, !isDownloading,
getMessageMediaFormat(message, 'download'), getMessageMediaFormat(message, 'download'),
lastSyncTime,
); );
const { isUploading, isTransferring, transferProgress } = getMediaTransferState( const { isUploading, isTransferring, transferProgress } = getMediaTransferState(

View File

@ -34,7 +34,6 @@ type OwnProps = {
canAutoPlay?: boolean; canAutoPlay?: boolean;
inPreview?: boolean; inPreview?: boolean;
asForwarded?: boolean; asForwarded?: boolean;
lastSyncTime?: number;
isDownloading?: boolean; isDownloading?: boolean;
isProtected?: boolean; isProtected?: boolean;
theme: ISettings['theme']; theme: ISettings['theme'];
@ -50,7 +49,6 @@ const WebPage: FC<OwnProps> = ({
canAutoPlay, canAutoPlay,
inPreview, inPreview,
asForwarded, asForwarded,
lastSyncTime,
isDownloading = false, isDownloading = false,
isProtected, isProtected,
theme, theme,
@ -162,7 +160,6 @@ const WebPage: FC<OwnProps> = ({
noAvatars={noAvatars} noAvatars={noAvatars}
canAutoLoad={canAutoLoad} canAutoLoad={canAutoLoad}
canAutoPlay={canAutoPlay} canAutoPlay={canAutoPlay}
lastSyncTime={lastSyncTime}
asForwarded={asForwarded} asForwarded={asForwarded}
isDownloading={isDownloading} isDownloading={isDownloading}
isProtected={isProtected} isProtected={isProtected}

View File

@ -1,12 +1,14 @@
import type { FC } from '../../lib/teact/teact';
import React, { memo } from '../../lib/teact/teact'; import React, { memo } from '../../lib/teact/teact';
import { withGlobal } from '../../global'; import { withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiMessage, ApiChat } from '../../api/types'; import type { ApiMessage, ApiChat } from '../../api/types';
import { selectChat, selectChatMessage, selectTabState } from '../../global/selectors'; import { selectChat, selectChatMessage, selectTabState } from '../../global/selectors';
import { buildCollectionByKey } from '../../util/iteratees'; import { buildCollectionByKey } from '../../util/iteratees';
import { getMessagePoll } from '../../global/helpers'; import { getMessagePoll } from '../../global/helpers';
import renderText from '../common/helpers/renderText'; import renderText from '../common/helpers/renderText';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
@ -23,7 +25,6 @@ type OwnProps = {
type StateProps = { type StateProps = {
chat?: ApiChat; chat?: ApiChat;
message?: ApiMessage; message?: ApiMessage;
lastSyncTime?: number;
}; };
const PollResults: FC<OwnProps & StateProps> = ({ const PollResults: FC<OwnProps & StateProps> = ({
@ -31,9 +32,9 @@ const PollResults: FC<OwnProps & StateProps> = ({
isActive, isActive,
chat, chat,
message, message,
lastSyncTime,
}) => { }) => {
const lang = useLang(); const lang = useLang();
useHistoryBack({ useHistoryBack({
isActive, isActive,
onBack: onClose, onBack: onClose,
@ -54,7 +55,7 @@ const PollResults: FC<OwnProps & StateProps> = ({
<div className="PollResults" dir={lang.isRtl ? 'rtl' : undefined}> <div className="PollResults" dir={lang.isRtl ? 'rtl' : undefined}>
<h3 className="poll-question" dir="auto">{renderText(summary.question, ['emoji', 'br'])}</h3> <h3 className="poll-question" dir="auto">{renderText(summary.question, ['emoji', 'br'])}</h3>
<div className="poll-results-list custom-scroll"> <div className="poll-results-list custom-scroll">
{Boolean(lastSyncTime) && summary.answers.map((answer) => ( {summary.answers.map((answer) => (
<PollAnswerResults <PollAnswerResults
key={`${message.id}-${answer.option}`} key={`${message.id}-${answer.option}`}
chat={chat} chat={chat}
@ -64,7 +65,6 @@ const PollResults: FC<OwnProps & StateProps> = ({
totalVoters={results.totalVoters!} totalVoters={results.totalVoters!}
/> />
))} ))}
{!lastSyncTime && <Loading />}
</div> </div>
</div> </div>
); );
@ -75,7 +75,6 @@ export default memo(withGlobal(
const { const {
pollResults: { chatId, messageId }, pollResults: { chatId, messageId },
} = selectTabState(global); } = selectTabState(global);
const { lastSyncTime } = global;
if (!chatId || !messageId) { if (!chatId || !messageId) {
return {}; return {};
@ -87,7 +86,6 @@ export default memo(withGlobal(
return { return {
chat, chat,
message, message,
lastSyncTime,
}; };
}, },
)(PollResults)); )(PollResults));

View File

@ -1,9 +1,9 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
useEffect, useMemo, useRef, useState, memo, useEffect, useMemo, useRef, useState, memo,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { import type {
ApiMessage, ApiMessage,
ApiChat, ApiChat,
@ -98,7 +98,6 @@ type StateProps = {
userStatusesById: Record<string, ApiUserStatus>; userStatusesById: Record<string, ApiUserStatus>;
isRightColumnShown: boolean; isRightColumnShown: boolean;
isRestricted?: boolean; isRestricted?: boolean;
lastSyncTime?: number;
activeDownloadIds?: number[]; activeDownloadIds?: number[];
isChatProtected?: boolean; isChatProtected?: boolean;
}; };
@ -138,7 +137,6 @@ const Profile: FC<OwnProps & StateProps> = ({
chatsById, chatsById,
isRightColumnShown, isRightColumnShown,
isRestricted, isRestricted,
lastSyncTime,
activeDownloadIds, activeDownloadIds,
isChatProtected, isChatProtected,
}) => { }) => {
@ -190,7 +188,6 @@ const Profile: FC<OwnProps & StateProps> = ({
chatsById, chatsById,
messagesById, messagesById,
foundIds, foundIds,
lastSyncTime,
topicId, topicId,
); );
const isFirstTab = resultType === 'members' || (!hasMembersTab && resultType === 'media'); const isFirstTab = resultType === 'members' || (!hasMembersTab && resultType === 'media');
@ -224,10 +221,8 @@ const Profile: FC<OwnProps & StateProps> = ({
const profileId = resolvedUserId || chatId; const profileId = resolvedUserId || chatId;
useEffect(() => { useEffect(() => {
if (lastSyncTime) { loadProfilePhotos({ profileId });
loadProfilePhotos({ profileId }); }, [profileId]);
}
}, [loadProfilePhotos, profileId, lastSyncTime]);
const handleSelectMedia = useLastCallback((mediaId: number) => { const handleSelectMedia = useLastCallback((mediaId: number) => {
openMediaViewer({ openMediaViewer({
@ -398,7 +393,6 @@ const Profile: FC<OwnProps & StateProps> = ({
message={messagesById[id]} message={messagesById[id]}
origin={AudioOrigin.SharedMedia} origin={AudioOrigin.SharedMedia}
date={messagesById[id].date} date={messagesById[id].date}
lastSyncTime={lastSyncTime}
className="scroll-item" className="scroll-item"
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
@ -415,7 +409,6 @@ const Profile: FC<OwnProps & StateProps> = ({
senderTitle={getSenderName(lang, messagesById[id], chatsById, usersById)} senderTitle={getSenderName(lang, messagesById[id], chatsById, usersById)}
origin={AudioOrigin.SharedMedia} origin={AudioOrigin.SharedMedia}
date={messagesById[id].date} date={messagesById[id].date}
lastSyncTime={lastSyncTime}
className="scroll-item" className="scroll-item"
onPlay={handlePlayAudio} onPlay={handlePlayAudio}
onDateClick={handleMessageFocus} onDateClick={handleMessageFocus}
@ -565,7 +558,6 @@ export default memo(withGlobal<OwnProps>(
currentUserId: global.currentUserId, currentUserId: global.currentUserId,
isRightColumnShown: selectIsRightColumnShown(global, isMobile), isRightColumnShown: selectIsRightColumnShown(global, isMobile),
isRestricted: chat?.isRestricted, isRestricted: chat?.isRestricted,
lastSyncTime: global.lastSyncTime,
activeDownloadIds: activeDownloads?.ids, activeDownloadIds: activeDownloads?.ids,
usersById, usersById,
userStatusesById, userStatusesById,

View File

@ -23,7 +23,6 @@ export default function useProfileViewportIds(
chatsById?: Record<string, ApiChat>, chatsById?: Record<string, ApiChat>,
chatMessages?: Record<number, ApiMessage>, chatMessages?: Record<number, ApiMessage>,
foundIds?: number[], foundIds?: number[],
lastSyncTime?: number,
topicId?: number, topicId?: number,
) { ) {
const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType; const resultType = tabType === 'members' || !mediaSearchType ? tabType : mediaSearchType;
@ -49,31 +48,31 @@ export default function useProfileViewportIds(
}, [chatsById, commonChatIds]); }, [chatsById, commonChatIds]);
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForLoadableItems( const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForLoadableItems(
resultType, loadMoreMembers, lastSyncTime, memberIds, loadMoreMembers, memberIds,
); );
const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia( const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia(
'media', resultType, searchMessages, lastSyncTime, chatMessages, foundIds, topicId, 'media', resultType, searchMessages, chatMessages, foundIds, topicId,
); );
const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia( const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia(
'documents', resultType, searchMessages, lastSyncTime, chatMessages, foundIds, topicId, 'documents', resultType, searchMessages, chatMessages, foundIds, topicId,
); );
const [linkViewportIds, getMoreLinks, noProfileInfoForLinks] = useInfiniteScrollForSharedMedia( const [linkViewportIds, getMoreLinks, noProfileInfoForLinks] = useInfiniteScrollForSharedMedia(
'links', resultType, searchMessages, lastSyncTime, chatMessages, foundIds, topicId, 'links', resultType, searchMessages, chatMessages, foundIds, topicId,
); );
const [audioViewportIds, getMoreAudio, noProfileInfoForAudio] = useInfiniteScrollForSharedMedia( const [audioViewportIds, getMoreAudio, noProfileInfoForAudio] = useInfiniteScrollForSharedMedia(
'audio', resultType, searchMessages, lastSyncTime, chatMessages, foundIds, topicId, 'audio', resultType, searchMessages, chatMessages, foundIds, topicId,
); );
const [voiceViewportIds, getMoreVoices, noProfileInfoForVoices] = useInfiniteScrollForSharedMedia( const [voiceViewportIds, getMoreVoices, noProfileInfoForVoices] = useInfiniteScrollForSharedMedia(
'voice', resultType, searchMessages, lastSyncTime, chatMessages, foundIds, topicId, 'voice', resultType, searchMessages, chatMessages, foundIds, topicId,
); );
const [commonChatViewportIds, getMoreCommonChats, noProfileInfoForCommonChats] = useInfiniteScrollForLoadableItems( const [commonChatViewportIds, getMoreCommonChats, noProfileInfoForCommonChats] = useInfiniteScrollForLoadableItems(
resultType, loadCommonChats, lastSyncTime, chatIds, loadCommonChats, chatIds,
); );
let viewportIds: number[] | string[] | undefined; let viewportIds: number[] | string[] | undefined;
@ -122,13 +121,11 @@ export default function useProfileViewportIds(
} }
function useInfiniteScrollForLoadableItems( function useInfiniteScrollForLoadableItems(
currentResultType?: ProfileTabType,
handleLoadMore?: AnyToVoidFunction, handleLoadMore?: AnyToVoidFunction,
lastSyncTime?: number,
itemIds?: string[], itemIds?: string[],
) { ) {
const [viewportIds, getMore] = useInfiniteScroll( const [viewportIds, getMore] = useInfiniteScroll(
lastSyncTime ? handleLoadMore : undefined, handleLoadMore,
itemIds, itemIds,
undefined, undefined,
MEMBERS_SLICE, MEMBERS_SLICE,
@ -143,7 +140,6 @@ function useInfiniteScrollForSharedMedia(
forSharedMediaType: SharedMediaType, forSharedMediaType: SharedMediaType,
currentResultType?: ProfileTabType, currentResultType?: ProfileTabType,
handleLoadMore?: AnyToVoidFunction, handleLoadMore?: AnyToVoidFunction,
lastSyncTime?: number,
chatMessages?: Record<number, ApiMessage>, chatMessages?: Record<number, ApiMessage>,
foundIds?: number[], foundIds?: number[],
topicId?: number, topicId?: number,
@ -165,7 +161,7 @@ function useInfiniteScrollForSharedMedia(
}, [chatMessages, foundIds, currentResultType, forSharedMediaType]); }, [chatMessages, foundIds, currentResultType, forSharedMediaType]);
const [viewportIds, getMore] = useInfiniteScroll( const [viewportIds, getMore] = useInfiniteScroll(
lastSyncTime ? handleLoadMore : undefined, handleLoadMore,
messageIdsRef.current, messageIdsRef.current,
undefined, undefined,
forSharedMediaType === 'media' ? SHARED_MEDIA_SLICE : MESSAGE_SEARCH_SLICE, forSharedMediaType === 'media' ? SHARED_MEDIA_SLICE : MESSAGE_SEARCH_SLICE,

View File

@ -45,7 +45,6 @@ type StateProps = {
canChangeInfo?: boolean; canChangeInfo?: boolean;
canInvite?: boolean; canInvite?: boolean;
exportedInvites?: ApiExportedInvite[]; exportedInvites?: ApiExportedInvite[];
lastSyncTime?: number;
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
}; };
@ -61,7 +60,6 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
canChangeInfo, canChangeInfo,
canInvite, canInvite,
exportedInvites, exportedInvites,
lastSyncTime,
isActive, isActive,
availableReactions, availableReactions,
onScreenSelect, onScreenSelect,
@ -98,12 +96,10 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
}); });
useEffect(() => { useEffect(() => {
if (lastSyncTime) { loadExportedChatInvites({ chatId });
loadExportedChatInvites({ chatId }); loadExportedChatInvites({ chatId, isRevoked: true });
loadExportedChatInvites({ chatId, isRevoked: true }); loadChatJoinRequests({ chatId });
loadChatJoinRequests({ chatId }); }, [chatId]);
}
}, [chatId, loadExportedChatInvites, lastSyncTime, loadChatJoinRequests]);
useEffect(() => { useEffect(() => {
if (progress === ManagementProgress.Complete) { if (progress === ManagementProgress.Complete) {
@ -378,7 +374,6 @@ export default memo(withGlobal<OwnProps>(
isSignaturesShown, isSignaturesShown,
canChangeInfo: getHasAdminRight(chat, 'changeInfo'), canChangeInfo: getHasAdminRight(chat, 'changeInfo'),
canInvite: getHasAdminRight(chat, 'inviteUsers'), canInvite: getHasAdminRight(chat, 'inviteUsers'),
lastSyncTime: global.lastSyncTime,
exportedInvites: invites, exportedInvites: invites,
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
}; };

View File

@ -56,7 +56,6 @@ type StateProps = {
canInvite?: boolean; canInvite?: boolean;
canEditForum?: boolean; canEditForum?: boolean;
exportedInvites?: ApiExportedInvite[]; exportedInvites?: ApiExportedInvite[];
lastSyncTime?: number;
isChannelsPremiumLimitReached: boolean; isChannelsPremiumLimitReached: boolean;
availableReactions?: ApiAvailableReaction[]; availableReactions?: ApiAvailableReaction[];
}; };
@ -98,7 +97,6 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
canEditForum, canEditForum,
isActive, isActive,
exportedInvites, exportedInvites,
lastSyncTime,
isChannelsPremiumLimitReached, isChannelsPremiumLimitReached,
availableReactions, availableReactions,
onScreenSelect, onScreenSelect,
@ -140,12 +138,12 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
}); });
useEffect(() => { useEffect(() => {
if (lastSyncTime && canInvite) { if (canInvite) {
loadExportedChatInvites({ chatId }); loadExportedChatInvites({ chatId });
loadExportedChatInvites({ chatId, isRevoked: true }); loadExportedChatInvites({ chatId, isRevoked: true });
loadChatJoinRequests({ chatId }); loadChatJoinRequests({ chatId });
} }
}, [chatId, loadExportedChatInvites, lastSyncTime, canInvite, loadChatJoinRequests]); }, [chatId, canInvite]);
// Resetting `isForum` switch on flood wait error // Resetting `isForum` switch on flood wait error
useEffect(() => { useEffect(() => {
@ -509,7 +507,6 @@ export default memo(withGlobal<OwnProps>(
canBanUsers: isBasicGroup ? chat.isCreator : getHasAdminRight(chat, 'banUsers'), canBanUsers: isBasicGroup ? chat.isCreator : getHasAdminRight(chat, 'banUsers'),
canInvite: isBasicGroup ? chat.isCreator : getHasAdminRight(chat, 'inviteUsers'), canInvite: isBasicGroup ? chat.isCreator : getHasAdminRight(chat, 'inviteUsers'),
exportedInvites: invites, exportedInvites: invites,
lastSyncTime: global.lastSyncTime,
isChannelsPremiumLimitReached: limitReachedModal?.limit === 'channels', isChannelsPremiumLimitReached: limitReachedModal?.limit === 'channels',
availableReactions: global.availableReactions, availableReactions: global.availableReactions,
canEditForum, canEditForum,

View File

@ -1,18 +1,3 @@
import './ui/initial';
import './ui/chats';
import './ui/messages';
import './ui/globalSearch';
import './ui/localSearch';
import './ui/stickerSearch';
import './ui/users';
import './ui/settings';
import './ui/misc';
import './ui/payments';
import './ui/calls';
import './ui/mediaViewer';
import './ui/passcode';
import './ui/reactions';
import './api/initial'; import './api/initial';
import './api/chats'; import './api/chats';
import './api/messages'; import './api/messages';
@ -30,6 +15,21 @@ import './api/payments';
import './api/reactions'; import './api/reactions';
import './api/statistics'; import './api/statistics';
import './ui/initial';
import './ui/chats';
import './ui/messages';
import './ui/globalSearch';
import './ui/localSearch';
import './ui/stickerSearch';
import './ui/users';
import './ui/settings';
import './ui/misc';
import './ui/payments';
import './ui/calls';
import './ui/mediaViewer';
import './ui/passcode';
import './ui/reactions';
import './apiUpdaters/initial'; import './apiUpdaters/initial';
import './apiUpdaters/chats'; import './apiUpdaters/chats';
import './apiUpdaters/messages'; import './apiUpdaters/messages';

View File

@ -129,8 +129,34 @@ addActionHandler('preloadTopChatMessages', async (global, actions): Promise<void
addActionHandler('openChat', (global, actions, payload): ActionReturnType => { addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
const { const {
id, threadId = MAIN_THREAD_ID, noRequestThreadInfoUpdate, id, threadId = MAIN_THREAD_ID, noRequestThreadInfoUpdate, tabId = getCurrentTabId(),
} = payload; } = payload;
const currentMessageList = selectCurrentMessageList(global, tabId);
const currentChatId = currentMessageList?.chatId;
const currentThreadId = currentMessageList?.threadId;
if (currentChatId && (currentChatId !== id || currentThreadId !== threadId)) {
const [isChatOpened, isThreadOpened] = Object.values(global.byTabId)
.reduce(([accHasChatOpened, accHasThreadOpened], { id: otherTabId }) => {
if (otherTabId === tabId || (accHasChatOpened && accHasThreadOpened)) {
return [accHasChatOpened, accHasThreadOpened];
}
const otherMessageList = selectCurrentMessageList(global, otherTabId);
const isSameChat = otherMessageList?.chatId === currentChatId;
const isSameThread = isSameChat && otherMessageList?.threadId === currentThreadId;
return [accHasChatOpened || isSameChat, accHasThreadOpened || isSameThread];
}, [currentChatId === id, false]);
const shouldAbortChatRequests = !isChatOpened || !isThreadOpened;
if (shouldAbortChatRequests) {
callApi('abortChatRequests', { chatId: currentChatId, threadId: isChatOpened ? currentThreadId : undefined });
}
}
if (!id) { if (!id) {
return; return;
} }

View File

@ -573,6 +573,7 @@ addActionHandler('reportMessages', async (global, actions, payload): Promise<voi
addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => { addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => {
const { action, chatId, threadId } = payload!; const { action, chatId, threadId } = payload!;
if (global.connectionState !== 'connectionStateReady') return;
if (chatId === global.currentUserId) return; // Message actions are disabled in Saved Messages if (chatId === global.currentUserId) return; // Message actions are disabled in Saved Messages
const chat = selectChat(global, chatId)!; const chat = selectChat(global, chatId)!;

View File

@ -84,6 +84,7 @@ addActionHandler('sendEmojiInteraction', (global, actions, payload): ActionRetur
const { const {
messageId, chatId, emoji, interactions, messageId, chatId, emoji, interactions,
} = payload!; } = payload!;
if (global.connectionState !== 'connectionStateReady') return;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
@ -296,7 +297,9 @@ addActionHandler('sendWatchingEmojiInteraction', (global, actions, payload): Act
return undefined; return undefined;
} }
callApi('sendWatchingEmojiInteraction', { chat, emoticon }); if (global.connectionState === 'connectionStateReady') {
callApi('sendWatchingEmojiInteraction', { chat, emoticon });
}
return updateTabState(global, { return updateTabState(global, {
activeEmojiInteractions: tabState.activeEmojiInteractions.map((activeEmojiInteraction) => { activeEmojiInteractions: tabState.activeEmojiInteractions.map((activeEmojiInteraction) => {

View File

@ -594,6 +594,7 @@ function buildInputPrivacyRules(global: GlobalState, {
} }
addActionHandler('updateIsOnline', (global, actions, payload): ActionReturnType => { addActionHandler('updateIsOnline', (global, actions, payload): ActionReturnType => {
if (global.connectionState !== 'connectionStateReady') return;
callApi('updateIsOnline', payload); callApi('updateIsOnline', payload);
}); });

View File

@ -70,8 +70,8 @@ addActionHandler('sync', (global, actions): ActionReturnType => {
global = getGlobal(); global = getGlobal();
global = { global = {
...global, ...global,
lastSyncTime: Date.now(),
isSyncing: false, isSyncing: false,
isSynced: true,
}; };
setGlobal(global); setGlobal(global);
@ -244,9 +244,9 @@ function loadTopMessages(chat: ApiChat, threadId: number, lastReadInboxId?: numb
let previousGlobal: GlobalState | undefined; let previousGlobal: GlobalState | undefined;
// RAF can be unreliable when device goes into sleep mode, so sync logic is handled outside any component // RAF can be unreliable when device goes into sleep mode, so sync logic is handled outside any component
addCallback((global: GlobalState) => { addCallback((global: GlobalState) => {
const { connectionState, authState } = global; const { connectionState, authState, isSynced } = global;
const { isMasterTab } = selectTabState(global); const { isMasterTab } = selectTabState(global);
if (!isMasterTab || (previousGlobal?.connectionState === connectionState if (!isMasterTab || isSynced || (previousGlobal?.connectionState === connectionState
&& previousGlobal?.authState === authState)) { && previousGlobal?.authState === authState)) {
previousGlobal = global; previousGlobal = global;
return; return;

View File

@ -62,6 +62,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
actions.initApi(); actions.initApi();
break; break;
case 'requestSync':
actions.sync();
break;
case 'error': { case 'error': {
Object.values(global.byTabId).forEach(({ id: tabId }) => { Object.values(global.byTabId).forEach(({ id: tabId }) => {
const paymentShippingError = getShippingError(update.error); const paymentShippingError = getShippingError(update.error);

View File

@ -589,7 +589,7 @@ export type GlobalState = {
currentUserId?: string; currentUserId?: string;
isSyncing?: boolean; isSyncing?: boolean;
isUpdateAvailable?: boolean; isUpdateAvailable?: boolean;
lastSyncTime?: number; isSynced?: boolean;
leftColumnWidth?: number; leftColumnWidth?: number;
lastIsChatInfoShown?: boolean; lastIsChatInfoShown?: boolean;
initialUnreadNotifications?: number; initialUnreadNotifications?: number;

View File

@ -3,8 +3,6 @@ import { addCustomEmojiInputRenderCallback } from '../util/customEmojiManager';
import { throttle } from '../util/schedulers'; import { throttle } from '../util/schedulers';
import useLastSyncTime from './useLastSyncTime';
let LOAD_QUEUE = new Set<string>(); let LOAD_QUEUE = new Set<string>();
const RENDER_HISTORY = new Set<string>(); const RENDER_HISTORY = new Set<string>();
const THROTTLE = 200; const THROTTLE = 200;
@ -44,7 +42,6 @@ function notifyCustomEmojiRender(emojiId: string) {
addCustomEmojiInputRenderCallback(notifyCustomEmojiRender); addCustomEmojiInputRenderCallback(notifyCustomEmojiRender);
export default function useEnsureCustomEmoji(id?: string) { export default function useEnsureCustomEmoji(id?: string) {
const lastSyncTime = useLastSyncTime();
if (!id) return; if (!id) return;
notifyCustomEmojiRender(id); notifyCustomEmojiRender(id);
@ -53,7 +50,5 @@ export default function useEnsureCustomEmoji(id?: string) {
} }
LOAD_QUEUE.add(id); LOAD_QUEUE.add(id);
if (lastSyncTime) { loadFromQueue();
loadFromQueue();
}
} }

View File

@ -1,34 +0,0 @@
import { useEffect, useState } from '../lib/teact/teact';
import { addCallback } from '../lib/teact/teactn';
import { getGlobal } from '../global';
import type { GlobalState } from '../global/types';
type LastSyncTimeSetter = (time: number) => void;
const handlers = new Set<LastSyncTimeSetter>();
let prevGlobal: GlobalState | undefined;
addCallback((global: GlobalState) => {
if (global.lastSyncTime && global.lastSyncTime !== prevGlobal?.lastSyncTime) {
for (const handler of handlers) {
handler(global.lastSyncTime);
}
}
prevGlobal = global;
});
export default function useLastSyncTime() {
const [lastSyncTime, setLastSyncTime] = useState(getGlobal().lastSyncTime);
useEffect(() => {
handlers.add(setLastSyncTime);
return () => {
handlers.delete(setLastSyncTime);
};
}, []);
return lastSyncTime;
}

View File

@ -9,7 +9,6 @@ const useMedia = (
mediaHash: string | false | undefined, mediaHash: string | false | undefined,
noLoad = false, noLoad = false,
mediaFormat = ApiMediaFormat.BlobUrl, mediaFormat = ApiMediaFormat.BlobUrl,
cacheBuster?: number,
delay?: number | false, delay?: number | false,
) => { ) => {
const mediaData = mediaHash ? mediaLoader.getFromMemory(mediaHash) : undefined; const mediaData = mediaHash ? mediaLoader.getFromMemory(mediaHash) : undefined;
@ -28,7 +27,7 @@ const useMedia = (
} }
}); });
} }
}, [noLoad, mediaHash, mediaData, mediaFormat, cacheBuster, forceUpdate, delay]); }, [noLoad, mediaHash, mediaData, mediaFormat, forceUpdate, delay]);
return mediaData; return mediaData;
}; };

View File

@ -17,7 +17,6 @@ export default function useMediaWithLoadProgress(
mediaHash: string | undefined, mediaHash: string | undefined,
noLoad = false, noLoad = false,
mediaFormat = ApiMediaFormat.BlobUrl, mediaFormat = ApiMediaFormat.BlobUrl,
cacheBuster?: number,
delay?: number | false, delay?: number | false,
isHtmlAllowed = false, isHtmlAllowed = false,
) { ) {
@ -66,8 +65,7 @@ export default function useMediaWithLoadProgress(
} }
} }
}, [ }, [
noLoad, mediaHash, mediaData, mediaFormat, cacheBuster, forceUpdate, isStreaming, delay, handleProgress, noLoad, mediaHash, mediaData, mediaFormat, forceUpdate, isStreaming, delay, handleProgress, isHtmlAllowed, id,
isHtmlAllowed, id,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -988,16 +988,12 @@ class TelegramClient {
for (const x of [...update.users, ...update.chats]) { for (const x of [...update.users, ...update.chats]) {
entities.push(x); entities.push(x);
} }
for (const u of update.updates) { this._processUpdate(update, entities);
this._processUpdate(u, update.updates, entities);
}
} else if (update instanceof constructors.UpdateShort) { } else if (update instanceof constructors.UpdateShort) {
this._processUpdate(update.update, undefined); this._processUpdate(update.update, undefined);
} else { } else {
this._processUpdate(update, undefined); this._processUpdate(update, undefined);
} }
// TODO add caching
// this._stateCache.update(update)
} }
_processUpdate(update, entities) { _processUpdate(update, entities) {

View File

@ -14,29 +14,14 @@ class Logger {
_level = level || 'debug'; _level = level || 'debug';
} }
this.isBrowser = typeof process === 'undefined' this.colors = {
|| process.type === 'renderer' start: '%c',
|| process.browser === true warn: 'color : #ff00ff',
|| process.__nwjs; info: 'color : #ffff00',
if (!this.isBrowser) { debug: 'color : #00ffff',
this.colors = { error: 'color : #ff0000',
start: '\x1b[2m', end: '',
warn: '\x1b[35m', };
info: '\x1b[33m',
debug: '\x1b[36m',
error: '\x1b[31m',
end: '\x1b[0m',
};
} else {
this.colors = {
start: '%c',
warn: 'color : #ff00ff',
info: 'color : #ffff00',
debug: 'color : #00ffff',
error: 'color : #ff0000',
end: '',
};
}
this.messageFormat = '[%t] [%l] - [%m]'; this.messageFormat = '[%t] [%l] - [%m]';
} }
@ -97,13 +82,8 @@ class Logger {
return; return;
} }
if (this.canSend(level)) { if (this.canSend(level)) {
if (!this.isBrowser) { // eslint-disable-next-line no-console
// eslint-disable-next-line no-console console.log(this.colors.start + this.format(message, level), color);
console.log(color + this.format(message, level) + this.colors.end);
} else {
// eslint-disable-next-line no-console
console.log(this.colors.start + this.format(message, level), color);
}
} }
} }
} }

View File

@ -749,7 +749,7 @@ class MTProtoSender {
/** /**
* Handles a server acknowledge about our messages. Normally these can be ignored * Handles a server acknowledge about our messages. Normally these can be ignored
*/ */
_handleAck() { } _handleAck() {}
/** /**
* Handles future salt results, which don't come inside a * Handles future salt results, which don't come inside a

View File

@ -88,6 +88,6 @@ declare module 'mp4box' {
export function createFile(): MP4File; export function createFile(): MP4File;
export { }; export {};
} }

44
src/util/SortedQueue.ts Normal file
View File

@ -0,0 +1,44 @@
export default class SortedQueue<T> {
private queue: T[];
constructor(private comparator: (a: T, b: T) => number) {
this.queue = [];
}
add(item: T): void {
const index = this.binarySearch(item);
this.queue.splice(index, 0, item);
}
pop(): T | undefined {
return this.queue.shift();
}
get size(): number {
return this.queue.length;
}
clear(): void {
this.queue = [];
}
private binarySearch(item: T): number {
let left = 0;
let right = this.queue.length;
while (left < right) {
const middle = Math.floor((left + right) / 2);
const comparison = this.comparator(item, this.queue[middle]);
if (comparison === 0) {
return middle;
} else if (comparison > 0) {
left = middle + 1;
} else {
right = middle;
}
}
return left;
}
}