From c333f31f02d66963361c78e2fd588e40e4129bce Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 16 May 2022 13:34:12 +0200 Subject: [PATCH] Browser History: Fix opening chat from Left Search (#1869) --- src/components/left/search/ChatMessage.tsx | 2 +- src/hooks/useHistoryBack.ts | 27 ++++- src/lib/gramjs/client/MockClient.ts | 122 ++++++++++++--------- 3 files changed, 96 insertions(+), 55 deletions(-) diff --git a/src/components/left/search/ChatMessage.tsx b/src/components/left/search/ChatMessage.tsx index abc4ee2fd..352f10a70 100644 --- a/src/components/left/search/ChatMessage.tsx +++ b/src/components/left/search/ChatMessage.tsx @@ -62,7 +62,7 @@ const ChatMessage: FC = ({ const isRoundVideo = Boolean(getMessageRoundVideo(message)); const handleClick = useCallback(() => { - focusMessage({ chatId, messageId: message.id }); + focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true }); }, [chatId, focusMessage, message.id]); const lang = useLang(); diff --git a/src/hooks/useHistoryBack.ts b/src/hooks/useHistoryBack.ts index f91200678..769fbb9f7 100644 --- a/src/hooks/useHistoryBack.ts +++ b/src/hooks/useHistoryBack.ts @@ -48,6 +48,7 @@ let isAlteringHistory = false; // Unfortunately Safari doesn't really like when there's 2+ consequent history operations in one frame, so we need // to delay them to the next raf let deferredHistoryOperations: HistoryOperation[] = []; +let deferredPopstateOperations: HistoryOperationState[] = []; let isSafariGestureAnimation = false; // Do not remove: used for history unit tests @@ -84,13 +85,25 @@ function applyDeferredHistoryOperations() { const goOperations = deferredHistoryOperations.filter((op) => op.type === 'go') as HistoryOperationGo[]; const stateOperations = deferredHistoryOperations.filter((op) => op.type !== 'go') as HistoryOperationState[]; const goCount = goOperations.reduce((acc, op) => acc + op.delta, 0); - if (goCount) { - window.history.go(goCount); - } - - stateOperations.forEach((op) => window.history[op.type](op.data, '', op.hash)); deferredHistoryOperations = []; + + if (goCount) { + window.history.go(goCount); + + // If we have some `state` operations after the `go` operations, we need to wait until the popstate event + // so the order of operations is correctly preserved + if (stateOperations.length) { + deferredPopstateOperations.push(...stateOperations); + return; + } + } + + processStateOperations(stateOperations); +} + +function processStateOperations(stateOperations: HistoryOperationState[]) { + stateOperations.forEach((op) => window.history[op.type](op.data, '', op.hash)); } function deferHistoryOperation(historyOperation: HistoryOperation) { @@ -145,6 +158,10 @@ function cleanupTrashedState() { window.addEventListener('popstate', ({ state }: PopStateEvent) => { if (isAlteringHistory) { isAlteringHistory = false; + if (deferredPopstateOperations.length) { + processStateOperations(deferredPopstateOperations); + deferredPopstateOperations = []; + } return; } diff --git a/src/lib/gramjs/client/MockClient.ts b/src/lib/gramjs/client/MockClient.ts index 29ea7f84c..d854b3372 100644 --- a/src/lib/gramjs/client/MockClient.ts +++ b/src/lib/gramjs/client/MockClient.ts @@ -1,18 +1,18 @@ import BigInt from 'big-integer'; import { UpdateConnectionState } from '../network'; -import Request from '../tl/api'; -import { Api as GramJs } from '..'; +import Api from '../tl/api'; type Peer = { - peer: GramJs.Chat | GramJs.Channel | GramJs.User; - TEST_messages: GramJs.Message[]; - TEST_sendMessage: (data: CreateMessageParams) => GramJs.Message | undefined; + peer: Api.Chat | Api.Channel | Api.User; + inputPeer: Api.TypePeer; + TEST_messages: Api.Message[]; + TEST_sendMessage: (data: CreateMessageParams) => Api.Message | undefined; }; type CreateMessageParams = { fromId?: any; repliesChannelId?: any; - replyingTo?: GramJs.MessageReplyHeader; + replyingTo?: Api.MessageReplyHeader; }; class TelegramClient { @@ -24,7 +24,7 @@ class TelegramClient { private peers: Peer[] = []; - private dialogs: GramJs.Dialog[] = []; + private dialogs: Api.Dialog[] = []; start() { } @@ -57,12 +57,12 @@ class TelegramClient { const { id } = discussion.TEST_sendMessage({})!; discussion.TEST_sendMessage({ - fromId: new GramJs.PeerUser({ + fromId: new Api.PeerUser({ userId: user.peer.id, }), - replyingTo: new GramJs.MessageReplyHeader({ + replyingTo: new Api.MessageReplyHeader({ replyToMsgId: id, - replyToPeerId: new GramJs.PeerChannel({ + replyToPeerId: new Api.PeerChannel({ channelId: channel.peer.id, }), replyToTopId: message!.id, @@ -70,8 +70,8 @@ class TelegramClient { }); } - createDialog(peer: GramJs.TypePeer) { - return new GramJs.Dialog({ + createDialog(peer: Api.TypePeer) { + return new Api.Dialog({ peer, topMessage: 0, readInboxMaxId: 0, @@ -79,11 +79,11 @@ class TelegramClient { unreadCount: 0, unreadMentionsCount: 0, unreadReactionsCount: 0, - notifySettings: new GramJs.PeerNotifySettings({}), + notifySettings: new Api.PeerNotifySettings({}), }); } - createMessage(peer: GramJs.TypePeer) { + createMessage(peer: Api.TypePeer) { return ({ fromId, repliesChannelId, @@ -93,18 +93,18 @@ class TelegramClient { const p = this.getPeer(peer); if (!p || pi === undefined) return undefined; - const message = new GramJs.Message({ + const message = new Api.Message({ id: p.TEST_messages.length + 1, fromId, peerId: peer, date: Number(new Date()) / 1000 + pi * 60, message: 'lol @channel', - entities: [new GramJs.MessageEntityMention({ + entities: [new Api.MessageEntityMention({ offset: 4, length: 8, })], replyTo: replyingTo, - replies: new GramJs.MessageReplies({ + replies: new Api.MessageReplies({ comments: true, replies: 0, repliesPts: 0, @@ -117,22 +117,24 @@ class TelegramClient { } createChat() { - const chat = new GramJs.Chat({ + const chat = new Api.Chat({ id: BigInt(this.lastId++), title: 'Some chat', - photo: new GramJs.ChatPhotoEmpty(), + photo: new Api.ChatPhotoEmpty(), participantsCount: 1, date: 1000, version: 1, }); - const peerChat = new GramJs.PeerChat({ + const peerChat = new Api.PeerChat({ chatId: chat.id, }); this.dialogs.push(this.createDialog(peerChat)); - const testChat: Peer = { peer: chat, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChat) }; + const testChat: Peer = { + peer: chat, inputPeer: peerChat, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChat), + }; this.peers.push(testChat); @@ -144,24 +146,26 @@ class TelegramClient { username: string; isMegagroup?: boolean; }) { - const channel = new GramJs.Channel({ + const channel = new Api.Channel({ username, id: BigInt(this.lastId++), megagroup: isMegagroup ? true : undefined, title, - photo: new GramJs.ChatPhotoEmpty(), + photo: new Api.ChatPhotoEmpty(), participantsCount: 1, date: 1000, creator: true, }); - const peerChannel = new GramJs.PeerChannel({ + const peerChannel = new Api.PeerChannel({ channelId: channel.id, }); this.dialogs.push(this.createDialog(peerChannel)); - const testChat: Peer = { peer: channel, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChannel) }; + const testChat: Peer = { + peer: channel, inputPeer: peerChannel, TEST_messages: [], TEST_sendMessage: this.createMessage(peerChannel), + }; this.peers.push(testChat); @@ -175,7 +179,7 @@ class TelegramClient { firstName: string; lastName: string; }): Peer { - const user = new GramJs.User({ + const user = new Api.User({ // self: true, verified: true, id: BigInt(this.lastId++), @@ -192,23 +196,25 @@ class TelegramClient { // langCode?: string; }); - const peerUser = new GramJs.PeerUser({ + const peerUser = new Api.PeerUser({ userId: user.id, }); this.dialogs.push(this.createDialog(peerUser)); - const testChat: Peer = { peer: user, TEST_messages: [], TEST_sendMessage: this.createMessage(peerUser) }; + const testChat: Peer = { + peer: user, inputPeer: peerUser, TEST_messages: [], TEST_sendMessage: this.createMessage(peerUser), + }; this.peers.push(testChat); return testChat; } - invoke(request: Request) { + invoke(request: any) { // await new Promise(resolve => setTimeout(resolve, 1000)) - if (request instanceof GramJs.messages.GetDiscussionMessage) { - return new GramJs.messages.DiscussionMessage({ + if (request instanceof Api.messages.GetDiscussionMessage) { + return new Api.messages.DiscussionMessage({ messages: [ this.peers[3].TEST_messages[0], ], @@ -218,21 +224,21 @@ class TelegramClient { users: [], }); } - if (request instanceof GramJs.messages.GetHistory) { + if (request instanceof Api.messages.GetHistory) { const peer = this.getPeer(request.peer); if (!peer) return undefined; - return new GramJs.messages.Messages({ + return new Api.messages.Messages({ messages: peer.TEST_messages, chats: [], users: [], }); } - if (request instanceof GramJs.messages.GetReplies) { + if (request instanceof Api.messages.GetReplies) { const peer = this.peers[3]; if (!peer) return undefined; - return new GramJs.messages.ChannelMessages({ + return new Api.messages.ChannelMessages({ messages: peer.TEST_messages, pts: 0, count: peer.TEST_messages.length, @@ -240,8 +246,8 @@ class TelegramClient { users: [], }); } - if (request instanceof GramJs.messages.GetDialogFilters) { - return [new GramJs.DialogFilter({ + if (request instanceof Api.messages.GetDialogFilters) { + return [new Api.DialogFilter({ contacts: true, nonContacts: true, groups: true, @@ -258,13 +264,31 @@ class TelegramClient { excludePeers: [], })]; } - if (request instanceof GramJs.messages.GetPinnedDialogs) { - return new GramJs.messages.PeerDialogs({ + if (request instanceof Api.contacts.GetTopPeers) { + return new Api.contacts.TopPeers({ + categories: [new Api.TopPeerCategoryPeers({ + category: new Api.TopPeerCategoryCorrespondents(), + count: 1, + peers: [ + new Api.TopPeer({ + peer: this.peers[0].inputPeer, + rating: 100, + }), + ], + })], + chats: [], + users: [ + this.getUsers()[0], + ], + }); + } + if (request instanceof Api.messages.GetPinnedDialogs) { + return new Api.messages.PeerDialogs({ dialogs: [], chats: [], messages: [], users: [], - state: new GramJs.updates.State({ + state: new Api.updates.State({ pts: 0, qts: 0, date: 0, @@ -273,9 +297,9 @@ class TelegramClient { }), }); } - if (request instanceof GramJs.messages.GetDialogs) { - if (request.folderId || !(request.offsetPeer instanceof GramJs.InputPeerEmpty)) { - return new GramJs.messages.Dialogs({ + if (request instanceof Api.messages.GetDialogs) { + if (request.folderId || !(request.offsetPeer instanceof Api.InputPeerEmpty)) { + return new Api.messages.Dialogs({ dialogs: [], users: [], chats: [], @@ -283,7 +307,7 @@ class TelegramClient { }); } - return new GramJs.messages.Dialogs({ + return new Api.messages.Dialogs({ dialogs: this.dialogs, messages: this.getAllMessages(), chats: this.getChats(), @@ -294,7 +318,7 @@ class TelegramClient { // console.log(request.className, request); } - private getPeerIndex(peer: GramJs.TypeInputPeer) { + private getPeerIndex(peer: Api.TypeInputPeer) { const id = 'channelId' in peer ? peer.channelId : ( 'userId' in peer ? peer.userId : ( 'chatId' in peer ? peer.chatId : undefined @@ -306,7 +330,7 @@ class TelegramClient { return this.peers.findIndex((l) => l.peer.id.toString() === id.toString()); } - private getPeer(peer: GramJs.TypeInputPeer) { + private getPeer(peer: Api.TypeInputPeer) { const index = this.getPeerIndex(peer); if (index === undefined) return undefined; @@ -314,18 +338,18 @@ class TelegramClient { } private getAllMessages() { - return this.peers.reduce((acc: GramJs.Message[], el) => { + return this.peers.reduce((acc: Api.Message[], el) => { acc.push(...el.TEST_messages); return acc; }, []); } private getChats() { - return this.peers.filter((l) => !(l.peer instanceof GramJs.User)).map((l) => l.peer); + return this.peers.filter((l) => !(l.peer instanceof Api.User)).map((l) => l.peer); } private getUsers() { - return this.peers.filter((l) => l.peer instanceof GramJs.User).map((l) => l.peer); + return this.peers.filter((l) => l.peer instanceof Api.User).map((l) => l.peer); } }