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');
if (!noOverwrite || !localDb.chats[id]) {
localDb.chats[id] = chat;
}
const storedChat = localDb.chats[id];
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');
if (shouldOverwrite || !localDb.users[id]) {
localDb.users[id] = user;
}
const storedUser = localDb.users[id];
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) => {
if (entity instanceof GramJs.User && entity.photo) {
if (entity instanceof GramJs.User) {
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);
}
});
@ -147,3 +152,10 @@ export function log(suffix: keyof typeof LOG_SUFFIX, ...data: any) {
);
/* 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>;
photos: Record<string, GramJs.Photo>;
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;
@ -77,17 +80,24 @@ function convertToVirtualClass(value: any): any {
function createLocalDbInitial(initial?: LocalDb): LocalDb {
return [
'localMessages', 'chats', 'users', 'messages', 'documents', 'stickerSets', 'photos', 'webDocuments',
'commonBoxState', 'channelPtsById',
]
.reduce((acc: Record<string, any>, key) => {
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]);
return acc2;
}, {} as Record<string, any>);
acc[key] = IS_MULTITAB_SUPPORTED
? createProxy(key, valueVirtualClass)
: valueVirtualClass;
? createProxy(key, convertedValue)
: convertedValue;
return acc;
}, {} as LocalDb) as LocalDb;
}

View File

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

View File

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

View File

@ -59,7 +59,7 @@ import {
generateRandomBigInt,
} from '../gramjsBuilders';
import {
addEntitiesWithPhotosToLocalDb,
addEntitiesToLocalDb,
addMessageToLocalDb,
addPhotoToLocalDb,
isChatFolder,
@ -69,6 +69,7 @@ import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
import { buildApiPhoto } from '../apiBuilders/common';
import { buildStickerSet } from '../apiBuilders/symbols';
import localDb from '../localDb';
import { updateChannelState } from '../updateManager';
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
type FullChatData = {
@ -153,6 +154,10 @@ export async function fetchChats({
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
const chat = buildApiChatFromDialog(dialog, peerEntity);
if (dialog.pts) {
updateChannelState(chat.id, dialog.pts);
}
if (
chat.id === SERVICE_NOTIFICATIONS_USER_ID
&& lastLocalServiceMessage
@ -222,13 +227,15 @@ export async function fetchChatSettings(chat: ApiChat) {
const result = await invokeRequest(new GramJs.messages.GetPeerSettings({
peer: buildInputPeer(id, accessHash),
}));
}), {
abortControllerChatId: id,
});
if (!result) {
return undefined;
}
addEntitiesWithPhotosToLocalDb(result.users);
addEntitiesToLocalDb(result.users);
return {
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 = {
...userStatusesById,
...bannedStatusesById,
@ -631,7 +642,9 @@ export async function createChannel({
broadcast: true,
title,
about,
}), undefined, true);
}), {
shouldThrow: true,
});
// `createChannel` can return a lot of different update types according to docs,
// but currently channel creation returns only `Updates` type.
@ -660,7 +673,9 @@ export async function createChannel({
await invokeRequest(new GramJs.channels.InviteToChannel({
channel: buildInputEntity(channel.id, channel.accessHash) as GramJs.InputChannel,
users: users.map(({ id, accessHash }) => buildInputEntity(id, accessHash)) as GramJs.InputUser[],
}), undefined, noErrorUpdate);
}), {
shouldThrow: noErrorUpdate,
});
} catch (err) {
// `noErrorUpdate` will cause an exception which we don't want either
}
@ -676,7 +691,10 @@ export function joinChannel({
}) {
return invokeRequest(new GramJs.channels.JoinChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true, true);
}), {
shouldReturnTrue: true,
shouldThrow: true,
});
}
export function deleteChatUser({
@ -688,7 +706,9 @@ export function deleteChatUser({
return invokeRequest(new GramJs.messages.DeleteChatUser({
chatId: buildInputEntity(chat.id, chat.accessHash) as BigInt.BigInteger,
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function deleteChat({
@ -698,7 +718,9 @@ export function deleteChat({
}) {
return invokeRequest(new GramJs.messages.DeleteChat({
chatId: buildInputEntity(chatId) as BigInt.BigInteger,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function leaveChannel({
@ -708,7 +730,9 @@ export function leaveChannel({
}) {
return invokeRequest(new GramJs.channels.LeaveChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function deleteChannel({
@ -718,7 +742,9 @@ export function deleteChannel({
}) {
return invokeRequest(new GramJs.channels.DeleteChannel({
channel: buildInputEntity(channelId, accessHash) as GramJs.InputChannel,
}), true);
}), {
shouldReturnTrue: true,
});
}
export async function createGroupChat({
@ -729,7 +755,9 @@ export async function createGroupChat({
const result = await invokeRequest(new GramJs.messages.CreateChat({
title,
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,
// but currently chat creation returns only `Updates` type.
@ -785,7 +813,9 @@ export async function editChatPhoto({
chatId: inputEntity as BigInt.BigInteger,
photo: inputPhoto,
}),
true,
{
shouldReturnTrue: true,
},
);
}
@ -826,7 +856,9 @@ export function toggleChatArchived({
peer: buildInputPeer(id, accessHash),
folderId,
})],
}), true);
}), {
shouldReturnTrue: true,
});
}
export async function fetchChatFolders() {
@ -988,7 +1020,9 @@ export function togglePreHistoryHidden({
return invokeRequest(new GramJs.channels.TogglePreHistoryHidden({
channel: channel as GramJs.InputChannel,
enabled: isEnabled,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function updateChatDefaultBannedRights({
@ -1000,7 +1034,9 @@ export function updateChatDefaultBannedRights({
return invokeRequest(new GramJs.messages.EditChatDefaultBannedRights({
peer,
bannedRights: buildChatBannedRights(bannedRights),
}), true);
}), {
shouldReturnTrue: true,
});
}
export function updateChatMemberBannedRights({
@ -1013,7 +1049,9 @@ export function updateChatMemberBannedRights({
channel,
participant,
bannedRights: buildChatBannedRights(bannedRights, untilDate),
}), true);
}), {
shouldReturnTrue: true,
});
}
export function updateChatAdmin({
@ -1027,7 +1065,9 @@ export function updateChatAdmin({
userId,
adminRights: buildChatAdminRights(adminRights),
rank: customTitle,
}), true);
}), {
shouldReturnTrue: true,
});
}
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,
title,
}),
true,
{
shouldReturnTrue: true,
},
);
}
@ -1073,7 +1115,9 @@ export function toggleSignatures({
return invokeRequest(new GramJs.channels.ToggleSignatures({
channel: channel as GramJs.InputChannel,
enabled: isEnabled,
}), true);
}), {
shouldReturnTrue: true,
});
}
type ChannelMembersFilter =
@ -1106,7 +1150,9 @@ export async function fetchMembers(
filter,
offset,
limit: MEMBERS_LOAD_SLICE,
}));
}), {
abortControllerChatId: chatId,
});
if (!result || result instanceof GramJs.channels.ChannelParticipantsNotModified) {
return undefined;
@ -1144,14 +1190,17 @@ export function setDiscussionGroup({
return invokeRequest(new GramJs.channels.SetDiscussionGroup({
broadcast: buildInputPeer(channel.id, channel.accessHash),
group: chat ? buildInputPeer(chat.id, chat.accessHash) : new GramJs.InputChannelEmpty(),
}), true);
}), {
shouldReturnTrue: true,
});
}
export async function migrateChat(chat: ApiChat) {
const result = await invokeRequest(
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,
@ -1226,14 +1275,20 @@ export async function addChatMembers(chat: ApiChat, users: ApiUser[], noErrorUpd
return await invokeRequest(new GramJs.channels.InviteToChannel({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
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 invokeRequest(new GramJs.messages.AddChatUser({
chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
userId: buildInputEntity(user.id, user.accessHash) as GramJs.InputUser,
}), true, noErrorUpdate);
}), {
shouldReturnTrue: true,
shouldThrow: noErrorUpdate,
});
}));
} catch (err) {
// `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({
chatId: buildInputEntity(chat.id) as BigInt.BigInteger,
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({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
enabled: isEnabled,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function toggleJoinRequest(chat: ApiChat, isEnabled: boolean) {
return invokeRequest(new GramJs.channels.ToggleJoinRequest({
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
enabled: isEnabled,
}), true);
}), {
shouldReturnTrue: true,
});
}
function preparePeers(
@ -1328,11 +1389,11 @@ function updateLocalDb(result: (
GramJs.messages.Chats | GramJs.messages.ChatsSlice | GramJs.TypeUpdates | GramJs.messages.ForumTopics
)) {
if ('users' in result) {
addEntitiesWithPhotosToLocalDb(result.users);
addEntitiesToLocalDb(result.users);
}
if ('chats' in result) {
addEntitiesWithPhotosToLocalDb(result.chats);
addEntitiesToLocalDb(result.chats);
}
if ('messages' in result) {
@ -1361,7 +1422,9 @@ export function setChatEnabledReactions({
return invokeRequest(new GramJs.messages.SetChatAvailableReactions({
peer: buildInputPeer(chat.id, chat.accessHash),
availableReactions: buildInputChatReactions(enabledReactions),
}), true);
}), {
shouldReturnTrue: true,
});
}
export function toggleIsProtected({
@ -1372,7 +1435,9 @@ export function toggleIsProtected({
return invokeRequest(new GramJs.messages.ToggleNoForwards({
peer: buildInputPeer(id, accessHash),
enabled: isProtected,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function toggleParticipantsHidden({
@ -1383,7 +1448,9 @@ export function toggleParticipantsHidden({
return invokeRequest(new GramJs.channels.ToggleParticipantsHidden({
channel: buildInputPeer(id, accessHash),
enabled: isEnabled,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function toggleForum({
@ -1394,7 +1461,10 @@ export function toggleForum({
return invokeRequest(new GramJs.channels.ToggleForum({
channel: buildInputPeer(id, accessHash),
enabled: isEnabled,
}), true, true);
}), {
shouldReturnTrue: true,
shouldThrow: true,
});
}
export async function createTopic({
@ -1540,7 +1610,9 @@ export function deleteTopic({
return invokeRequest(new GramJs.channels.DeleteTopicHistory({
channel: buildInputPeer(id, accessHash),
topMsgId: topicId,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function togglePinnedTopic({
@ -1556,7 +1628,9 @@ export function togglePinnedTopic({
channel: buildInputPeer(id, accessHash),
topicId,
pinned: isPinned,
}), true);
}), {
shouldReturnTrue: true,
});
}
export function editTopic({
@ -1578,7 +1652,9 @@ export function editTopic({
iconEmojiId: topicId !== GENERAL_TOPIC_ID && iconEmojiId ? BigInt(iconEmojiId) : undefined,
closed: isClosed,
hidden: isHidden,
}), true);
}), {
shouldReturnTrue: true,
});
}
export async function checkChatlistInvite({
@ -1613,7 +1689,10 @@ export function joinChatlistInvite({
return invokeRequest(new GramJs.chatlists.JoinChatlistInvite({
slug,
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), true, true);
}), {
shouldReturnTrue: true,
shouldThrow: true,
});
}
export async function fetchLeaveChatlistSuggestions({
@ -1644,7 +1723,9 @@ export function leaveChatlist({
filterId: folderId,
}),
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), true);
}), {
shouldReturnTrue: true,
});
}
export async function createChalistInvite({
@ -1660,7 +1741,9 @@ export async function createChalistInvite({
}),
title: title || '',
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), undefined, true);
}), {
shouldThrow: true,
});
if (!result || result.filter instanceof GramJs.DialogFilterDefault) return undefined;
@ -1699,7 +1782,9 @@ export async function editChatlistInvite({
slug,
title,
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
}), undefined, true);
}), {
shouldThrow: true,
});
if (!result) return undefined;
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 type { TwoFaParams } from '../../../lib/gramjs/client/2fa';
import type {
ApiInitialArgs,
ApiMediaFormat,
@ -21,13 +20,18 @@ import {
onRequestPhoneNumber, onRequestCode, onRequestPassword, onRequestRegistration,
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestQrCode, onWebAuthTokenFailed,
} from './auth';
import { updater } from '../updater';
import { setMessageBuilderCurrentUserId } from '../apiBuilders/messages';
import downloadMediaWithClient, { parseMediaUrl } from './media';
import { buildApiUser, buildApiUserFullInfo } from '../apiBuilders/users';
import localDb, { clearLocalDb } from '../localDb';
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_PLATFORM = 'Unknown platform';
@ -37,6 +41,8 @@ GramJsLogger.setLevel(DEBUG_GRAMJS ? 'debug' : 'warn');
const gramJsUpdateEventBuilder = { build: (update: object) => update };
const CHAT_ABORT_CONTROLLERS = new Map<string, ChatAbortController>();
let onUpdate: OnApiUpdate;
let client: TelegramClient;
let isConnected = false;
@ -81,7 +87,6 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
);
client.addEventHandler(handleGramJsUpdate, gramJsUpdateEventBuilder);
client.addEventHandler(updater, gramJsUpdateEventBuilder);
try {
if (DEBUG) {
@ -94,6 +99,7 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
}
try {
client.setPingCallback(getDifference);
await client.start({
phoneNumber: onRequestPhoneNumber,
phoneCode: onRequestCode,
@ -131,6 +137,8 @@ export async function init(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs)
onSessionUpdate(session.getSessionData());
onUpdate({ '@type': 'updateApiReady' });
initUpdatesManager(invokeRequest);
void fetchCurrentUser();
} catch (err) {
if (DEBUG) {
@ -150,7 +158,10 @@ export async function destroy(noLogOut = false, noClearLocalDb = false) {
await invokeRequest(new GramJs.auth.LogOut());
}
if (!noClearLocalDb) clearLocalDb();
if (!noClearLocalDb) {
clearLocalDb();
resetUpdatesManager();
}
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) {
isConnected = update.state === connection.UpdateConnectionState.connected;
} else if (update instanceof GramJs.UpdatesTooLong) {
void handleTerminatedSession();
} else if (update instanceof GramJs.UpdateConfig) {
// eslint-disable-next-line no-underscore-dangle
const currentUser = (update as GramJs.UpdateConfig & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] })
._entities
?.find((entity) => entity instanceof GramJs.User && buildApiPeerId(entity.id, 'user') === currentUserId);
if (!(currentUser instanceof GramJs.User)) return;
} else {
const updates = 'updates' in update ? update.updates : [update];
updates.forEach((nestedUpdate: any) => {
if (!(nestedUpdate instanceof GramJs.UpdateConfig)) return;
// eslint-disable-next-line no-underscore-dangle
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>(
request: T,
shouldReturnTrue: true,
shouldThrow?: boolean,
shouldIgnoreUpdates?: undefined,
dcId?: number,
shouldIgnoreErrors?: boolean,
): Promise<true | undefined>;
type InvokeRequestParams = {
shouldThrow?: boolean;
shouldIgnoreUpdates?: boolean;
dcId?: number;
shouldIgnoreErrors?: boolean;
abortControllerChatId?: string;
abortControllerThreadId?: number;
};
export async function invokeRequest<T extends GramJs.AnyRequest>(
request: T,
shouldReturnTrue?: boolean,
shouldThrow?: boolean,
shouldIgnoreUpdates?: boolean,
dcId?: number,
shouldIgnoreErrors?: boolean,
params?: InvokeRequestParams & { shouldReturnTrue?: false },
): Promise<T['__response'] | undefined>;
export async function invokeRequest<T extends GramJs.AnyRequest>(
request: T,
shouldReturnTrue = false,
shouldThrow = false,
shouldIgnoreUpdates = false,
dcId?: number,
shouldIgnoreErrors = false,
params?: InvokeRequestParams & { shouldReturnTrue: true },
): Promise<true | undefined>;
export async function invokeRequest<T extends GramJs.AnyRequest>(
request: T,
params: InvokeRequestParams & { shouldReturnTrue?: boolean } = {},
) {
if (!isConnected) {
if (DEBUG) {
log('INVOKE ERROR', request.className, 'Client is not connected');
const {
shouldThrow, shouldIgnoreUpdates, dcId, shouldIgnoreErrors, abortControllerChatId, abortControllerThreadId,
} = 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 {
@ -225,14 +248,14 @@ export async function invokeRequest<T extends GramJs.AnyRequest>(
log('INVOKE', request.className);
}
const result = await client.invoke(request, dcId);
const result = await client.invoke(request, dcId, abortSignal);
if (DEBUG) {
log('RESPONSE', request.className, result);
}
if (!shouldIgnoreUpdates) {
handleUpdates(result);
if (!shouldIgnoreUpdates && isResponseUpdate(result)) {
processUpdate(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(
args: { url: string; mediaFormat: ApiMediaFormat; start?: number; end?: number; isHtmlAllowed?: boolean },
onProgress?: ApiOnProgress,
@ -321,6 +314,18 @@ export function getTmpPassword(currentPassword: string, ttl?: number) {
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() {
const userFull = await invokeRequest(new GramJs.users.GetFullUser({
id: new GramJs.InputUserSelf(),
@ -335,7 +340,7 @@ export async function fetchCurrentUser() {
if (user.photo instanceof GramJs.Photo) {
localDb.photos[user.photo.id.toString()] = user.photo;
}
localDb.users[buildApiPeerId(user.id, 'user')] = user;
addUserToLocalDb(user);
const currentUserFullInfo = buildApiUserFullInfo(userFull);
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() {
try {
await invokeRequest(new GramJs.users.GetFullUser({
id: new GramJs.InputUserSelf(),
}), undefined, true);
}), {
shouldThrow: true,
});
} catch (err: any) {
if (err.message === 'AUTH_KEY_UNREGISTERED' || err.message === 'SESSION_REVOKED') {
onUpdate({
@ -429,6 +425,10 @@ export async function repairFileReference({
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];
if (message instanceof GramJs.MessageEmpty) return false;
addMessageToLocalDb(message);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ import * as methods from './methods';
let onUpdate: OnApiUpdate;
export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs, initialLocalDb?: LocalDb) {
export function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs, initialLocalDb?: LocalDb) {
onUpdate = _onUpdate;
initUpdater(handleUpdate);
@ -43,7 +43,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg
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> {

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

View File

@ -10,6 +10,7 @@ import { DATA_BROADCAST_CHANNEL_NAME, DEBUG } from '../../../config';
import generateIdFor from '../../../util/generateIdFor';
import { pause } from '../../../util/schedulers';
import { getCurrentTabId, subscribeToMasterChange } from '../../../util/establishMultitabRole';
import Deferred from '../../../util/Deferred';
type RequestStates = {
messageId: string;
@ -32,6 +33,9 @@ const savedLocalDb: LocalDb = {
stickerSets: {},
photos: {},
webDocuments: {},
commonBoxState: {},
channelPtsById: {},
};
// TODO Re-use `util/WorkerConnector.ts`
@ -57,6 +61,10 @@ export function initApiOnMasterTab(initialArgs: ApiInitialArgs) {
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) {
updateCallback = onUpdate;
@ -82,6 +90,22 @@ export function initApi(onUpdate: OnApiUpdate, initialArgs: ApiInitialArgs) {
return makeRequest({
type: 'initApi',
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
*/
export function callApiLocal<T extends keyof Methods>(fnName: T, ...args: MethodArgs<T>) {
if (!worker) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('API is not initialized');
}
if (!isInited) {
const deferred = new Deferred();
localApiRequestsQueue.push({ fnName, args, deferred });
return undefined;
return deferred.promise as MethodResponse<T>;
}
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>) {
if (!worker && isMasterTab) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('API is not initialized');
}
if (!isInited && isMasterTab) {
const deferred = new Deferred();
apiRequestsQueue.push({ fnName, args, deferred });
return undefined;
return deferred.promise as MethodResponse<T>;
}
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>>;
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[];
}) {
requestStates.get(data.messageId)?.callback?.(...data.callbackArgs);

View File

@ -21,7 +21,15 @@ onmessage = async (message: OriginMessageEvent) => {
switch (data.type) {
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;
}
case 'callMethod': {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,6 @@ type OwnProps = {
user?: ApiUser;
isSavedMessages?: boolean;
photo?: ApiPhoto;
lastSyncTime?: number;
canPlayVideo: boolean;
onClick: NoneToVoidFunction;
};
@ -44,7 +43,6 @@ const ProfilePhoto: FC<OwnProps> = ({
photo,
isSavedMessages,
canPlayVideo,
lastSyncTime,
onClick,
}) => {
// eslint-disable-next-line no-null/no-null
@ -60,13 +58,13 @@ const ProfilePhoto: FC<OwnProps> = ({
const { isVideo } = photo || {};
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 photoBlobUrl = useMedia(photoHash, undefined, undefined, lastSyncTime);
const photoBlobUrl = useMedia(photoHash);
const videoHash = canHaveMedia && photo && isVideo && getVideoAvatarMediaHash(photo);
const videoBlobUrl = useMedia(videoHash, undefined, undefined, lastSyncTime);
const videoBlobUrl = useMedia(videoHash);
const fullMediaData = videoBlobUrl || photoBlobUrl;
const [isVideoReady, markVideoReady] = useFlag();

View File

@ -48,7 +48,6 @@ type OwnProps = {
withSharedAnimation?: boolean;
sharedCanvasRef?: React.RefObject<HTMLCanvasElement>;
withTranslucentThumb?: boolean; // With shared canvas thumbs are opaque by default to provide better transition effect
cacheBuster?: number;
onVideoEnded?: AnyToVoidFunction;
onAnimatedStickerLoop?: AnyToVoidFunction;
};
@ -77,7 +76,6 @@ const StickerView: FC<OwnProps> = ({
withSharedAnimation,
withTranslucentThumb,
sharedCanvasRef,
cacheBuster,
onVideoEnded,
onAnimatedStickerLoop,
}) => {
@ -102,9 +100,7 @@ const StickerView: FC<OwnProps> = ({
const thumbDataUri = useThumbnail(sticker);
// 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 previewMediaData = useMedia(
previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay), undefined, cacheBuster,
);
const previewMediaData = useMedia(previewMediaHash, Boolean(previewMediaDataFromCache || !noPlay));
const thumbData = customColor ? thumbDataUri : (previewMediaData || thumbDataUri);
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
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)
const [isPlayerReady, markPlayerReady] = useFlag(Boolean(isLottie && fullMediaData && !previewMediaData));
// Delay mounting on Android until heavy animation ends
@ -129,7 +125,7 @@ const StickerView: FC<OwnProps> = ({
const coords = useCoordsInSharedCanvas(containerRef, sharedCanvasRef);
// 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 renderId = [

View File

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

View File

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

View File

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

View File

@ -54,7 +54,6 @@ type OwnProps = {
type StateProps = {
chat?: ApiChat;
currentTopicId?: number;
lastSyncTime?: number;
withInterfaceAnimations?: boolean;
};
@ -65,7 +64,6 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
currentTopicId,
isOpen,
isHidden,
lastSyncTime,
onTopicSearch,
onCloseAnimationEnd,
onOpenAnimationStart,
@ -85,10 +83,10 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
const { isMobile } = useAppLayout();
useEffect(() => {
if (lastSyncTime && chat && !chat.topics) {
if (chat && !chat.topics) {
loadTopics({ chatId: chat.id });
}
}, [chat, lastSyncTime, loadTopics]);
}, [chat, loadTopics]);
const [isScrolled, setIsScrolled] = useState(false);
const lang = useLang();
@ -126,7 +124,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id);
const [viewportIds, getMore] = useInfiniteScroll(() => {
if (!chat || !lastSyncTime) return;
if (!chat) return;
loadTopics({ chatId: chat.id });
}, orderedIds, !chat?.topicsCount || orderedIds.length >= chat.topicsCount, TOPICS_SLICE);
@ -294,7 +292,6 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
lastSyncTime: global.lastSyncTime,
currentTopicId: chatId === currentChatId ? currentThreadId : undefined,
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 { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import { AudioOrigin, LoadMoreDirection } from '../../../types';
import type { StateProps } from './helpers/createMapStateToProps';
import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang';
@ -35,7 +36,6 @@ const AudioResults: FC<OwnProps & StateProps> = ({
usersById,
globalMessagesByChatId,
foundIds,
lastSyncTime,
activeDownloads,
}) => {
const {
@ -47,7 +47,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
const lang = useLang();
const currentType = isVoice ? 'voice' : 'audio';
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [currentType, lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [currentType, searchMessagesGlobal, searchQuery]);
const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) {
@ -98,7 +98,6 @@ const AudioResults: FC<OwnProps & StateProps> = ({
origin={AudioOrigin.Search}
senderTitle={getSenderName(lang, message, chatsById, usersById)}
date={message.date}
lastSyncTime={lastSyncTime}
className="scroll-item"
onPlay={handlePlayAudio}
onDateClick={handleMessageFocus}

View File

@ -44,7 +44,6 @@ type StateProps = {
chat?: ApiChat;
privateChatUser?: ApiUser;
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
lastSyncTime?: number;
};
const ChatMessage: FC<OwnProps & StateProps> = ({
@ -53,7 +52,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chatId,
chat,
privateChatUser,
lastSyncTime,
}) => {
const { focusMessage } = getActions();
@ -85,7 +83,6 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
chat={chat}
user={privateChatUser}
isSavedMessages={privateChatUser?.isSelf}
lastSyncTime={lastSyncTime}
/>
<div className="info">
<div className="info-row">
@ -147,7 +144,6 @@ export default memo(withGlobal<OwnProps>(
return {
chat,
lastSyncTime: global.lastSyncTime,
...(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 { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types';
@ -9,6 +9,7 @@ import { selectTabState } from '../../../global/selectors';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers';
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
import useLang from '../../../hooks/useLang';
import useAppLayout from '../../../hooks/useAppLayout';
@ -33,7 +34,6 @@ type StateProps = {
fetchingStatus?: { chats?: boolean; messages?: boolean };
foundTopicIds?: number[];
searchChatId?: string;
lastSyncTime?: number;
};
const runThrottled = throttle((cb) => cb(), 500, true);
@ -45,7 +45,6 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
globalMessagesByChatId,
chatsById,
fetchingStatus,
lastSyncTime,
foundTopicIds,
searchChatId,
onSearchDateSelect,
@ -57,7 +56,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
const { isMobile } = useAppLayout();
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [searchQuery]);
const handleTopicClick = useCallback(
(id: number) => {
@ -167,7 +166,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const { byId: chatsById } = global.chats;
const { currentUserId, messages: { byChatId: globalMessagesByChatId }, lastSyncTime } = global;
const { currentUserId, messages: { byChatId: globalMessagesByChatId } } = global;
const {
fetchingStatus, resultsByType, foundTopicIds, chatId: searchChatId,
} = selectTabState(global).globalSearch;
@ -181,7 +180,6 @@ export default memo(withGlobal<OwnProps>(
chatsById,
fetchingStatus,
foundTopicIds,
lastSyncTime,
searchChatId,
};
},

View File

@ -1,9 +1,9 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiChat, ApiMessage } from '../../../api/types';
import { LoadMoreDirection } from '../../../types';
@ -49,7 +49,6 @@ type StateProps = {
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
chatsById: Record<string, ApiChat>;
fetchingStatus?: { chats?: boolean; messages?: boolean };
lastSyncTime?: number;
};
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 ChatResults: FC<OwnProps & StateProps> = ({
searchQuery, searchDate, dateSearchQuery, currentUserId,
localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds,
foundIds, globalMessagesByChatId, chatsById, fetchingStatus, lastSyncTime,
onReset, onSearchDateSelect,
searchQuery,
searchDate,
dateSearchQuery,
currentUserId,
localContactIds,
localChatIds,
localUserIds,
globalChatIds,
globalUserIds,
foundIds,
globalMessagesByChatId,
chatsById,
fetchingStatus,
onReset,
onSearchDateSelect,
}) => {
const {
openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
@ -77,7 +87,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
const [shouldShowMoreGlobal, setShouldShowMoreGlobal] = useState<boolean>(false);
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [searchQuery]);
const handleChatClick = useCallback(
(id: string) => {
@ -293,6 +303,9 @@ export default memo(withGlobal<OwnProps>(
const { byId: chatsById } = global.chats;
const { userIds: localContactIds } = global.contactList || {};
const {
currentUserId, messages,
} = global;
if (!localContactIds) {
return {
@ -300,9 +313,6 @@ export default memo(withGlobal<OwnProps>(
};
}
const {
currentUserId, messages, lastSyncTime,
} = global;
const {
fetchingStatus, globalResults, localResults, resultsByType,
} = selectTabState(global).globalSearch;
@ -322,7 +332,6 @@ export default memo(withGlobal<OwnProps>(
globalMessagesByChatId,
chatsById,
fetchingStatus,
lastSyncTime,
};
},
)(ChatResults));

View File

@ -1,20 +1,21 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { ApiMessage } from '../../../api/types';
import type { StateProps } from './helpers/createMapStateToProps';
import { LoadMoreDirection } from '../../../types';
import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers';
import { getMessageDocument } from '../../../global/helpers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -41,7 +42,6 @@ const FileResults: FC<OwnProps & StateProps> = ({
globalMessagesByChatId,
foundIds,
activeDownloads,
lastSyncTime,
}) => {
const {
searchMessagesGlobal,
@ -59,7 +59,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
});
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [searchQuery]);
const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) {

View File

@ -1,18 +1,19 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import { LoadMoreDirection } from '../../../types';
import type { StateProps } from './helpers/createMapStateToProps';
import { SLIDE_TRANSITION_DURATION } from '../../../config';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps';
import { formatMonthAndYear, toYearMonth } from '../../../util/dateFormat';
import { getSenderName } from './helpers/getSenderName';
import { throttle } from '../../../util/schedulers';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import useLang from '../../../hooks/useLang';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -38,7 +39,6 @@ const LinkResults: FC<OwnProps & StateProps> = ({
usersById,
globalMessagesByChatId,
foundIds,
lastSyncTime,
isChatProtected,
}) => {
const {
@ -57,7 +57,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
});
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [searchQuery]);
const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) {

View File

@ -1,17 +1,18 @@
import type { FC } from '../../../lib/teact/teact';
import React, {
memo, useCallback, useMemo, useRef,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { StateProps } from './helpers/createMapStateToProps';
import { LoadMoreDirection, MediaViewerOrigin } from '../../../types';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { SLIDE_TRANSITION_DURATION } from '../../../config';
import type { StateProps } from './helpers/createMapStateToProps';
import { createMapStateToProps } from './helpers/createMapStateToProps';
import buildClassName from '../../../util/buildClassName';
import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang';
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
@ -36,7 +37,6 @@ const MediaResults: FC<OwnProps & StateProps> = ({
isLoading,
globalMessagesByChatId,
foundIds,
lastSyncTime,
isChatProtected,
}) => {
const {
@ -55,7 +55,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
});
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
if (direction === LoadMoreDirection.Backwards) {
runThrottled(() => {
searchMessagesGlobal({
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
}, [lastSyncTime, searchMessagesGlobal, searchQuery]);
}, [searchMessagesGlobal, searchQuery]);
const foundMessages = useMemo(() => {
if (!foundIds || !globalMessagesByChatId) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -128,7 +128,6 @@ type StateProps = {
botInfo?: ApiBotInfo;
threadTopMessageId?: number;
hasLinkedChat?: boolean;
lastSyncTime?: number;
topic?: ApiTopic;
noMessageSendingAnimation?: boolean;
isServiceNotificationsChat?: boolean;
@ -178,7 +177,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
botInfo,
threadTopMessageId,
hasLinkedChat,
lastSyncTime,
withBottomShift,
withDefaultBg,
topic,
@ -238,10 +236,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
}, [firstUnreadId]);
useEffect(() => {
if (!isCurrentUserPremium && isChannelChat && isReady && lastSyncTime) {
if (!isCurrentUserPremium && isChannelChat && isReady) {
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)
useSyncEffect(() => {
@ -738,7 +736,6 @@ export default memo(withGlobal<OwnProps>(
botInfo,
threadTopMessageId,
hasLinkedChat: chatFullInfo ? Boolean(chatFullInfo.linkedChatId) : undefined,
lastSyncTime: global.lastSyncTime,
topic,
noMessageSendingAnimation: !selectPerformanceSettingsValue(global, 'messageSendingAnimations'),
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -129,8 +129,34 @@ addActionHandler('preloadTopChatMessages', async (global, actions): Promise<void
addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
const {
id, threadId = MAIN_THREAD_ID, noRequestThreadInfoUpdate,
id, threadId = MAIN_THREAD_ID, noRequestThreadInfoUpdate, tabId = getCurrentTabId(),
} = 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) {
return;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,6 @@ import { addCustomEmojiInputRenderCallback } from '../util/customEmojiManager';
import { throttle } from '../util/schedulers';
import useLastSyncTime from './useLastSyncTime';
let LOAD_QUEUE = new Set<string>();
const RENDER_HISTORY = new Set<string>();
const THROTTLE = 200;
@ -44,7 +42,6 @@ function notifyCustomEmojiRender(emojiId: string) {
addCustomEmojiInputRenderCallback(notifyCustomEmojiRender);
export default function useEnsureCustomEmoji(id?: string) {
const lastSyncTime = useLastSyncTime();
if (!id) return;
notifyCustomEmojiRender(id);
@ -53,7 +50,5 @@ export default function useEnsureCustomEmoji(id?: string) {
}
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,
noLoad = false,
mediaFormat = ApiMediaFormat.BlobUrl,
cacheBuster?: number,
delay?: number | false,
) => {
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;
};

View File

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

View File

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

View File

@ -14,29 +14,14 @@ class Logger {
_level = level || 'debug';
}
this.isBrowser = typeof process === 'undefined'
|| process.type === 'renderer'
|| process.browser === true
|| process.__nwjs;
if (!this.isBrowser) {
this.colors = {
start: '\x1b[2m',
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.colors = {
start: '%c',
warn: 'color : #ff00ff',
info: 'color : #ffff00',
debug: 'color : #00ffff',
error: 'color : #ff0000',
end: '',
};
this.messageFormat = '[%t] [%l] - [%m]';
}
@ -97,13 +82,8 @@ class Logger {
return;
}
if (this.canSend(level)) {
if (!this.isBrowser) {
// eslint-disable-next-line no-console
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);
}
// 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
*/
_handleAck() { }
_handleAck() {}
/**
* Handles future salt results, which don't come inside a

View File

@ -88,6 +88,6 @@ declare module 'mp4box' {
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;
}
}