Browser History: Fix opening chat from Left Search (#1869)

This commit is contained in:
Alexander Zinchuk 2022-05-16 13:34:12 +02:00
parent e4b2b0911f
commit c333f31f02
3 changed files with 96 additions and 55 deletions

View File

@ -62,7 +62,7 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
const isRoundVideo = Boolean(getMessageRoundVideo(message)); const isRoundVideo = Boolean(getMessageRoundVideo(message));
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
focusMessage({ chatId, messageId: message.id }); focusMessage({ chatId, messageId: message.id, shouldReplaceHistory: true });
}, [chatId, focusMessage, message.id]); }, [chatId, focusMessage, message.id]);
const lang = useLang(); const lang = useLang();

View File

@ -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 // 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 // to delay them to the next raf
let deferredHistoryOperations: HistoryOperation[] = []; let deferredHistoryOperations: HistoryOperation[] = [];
let deferredPopstateOperations: HistoryOperationState[] = [];
let isSafariGestureAnimation = false; let isSafariGestureAnimation = false;
// Do not remove: used for history unit tests // 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 goOperations = deferredHistoryOperations.filter((op) => op.type === 'go') as HistoryOperationGo[];
const stateOperations = deferredHistoryOperations.filter((op) => op.type !== 'go') as HistoryOperationState[]; const stateOperations = deferredHistoryOperations.filter((op) => op.type !== 'go') as HistoryOperationState[];
const goCount = goOperations.reduce((acc, op) => acc + op.delta, 0); 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 = []; 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) { function deferHistoryOperation(historyOperation: HistoryOperation) {
@ -145,6 +158,10 @@ function cleanupTrashedState() {
window.addEventListener('popstate', ({ state }: PopStateEvent) => { window.addEventListener('popstate', ({ state }: PopStateEvent) => {
if (isAlteringHistory) { if (isAlteringHistory) {
isAlteringHistory = false; isAlteringHistory = false;
if (deferredPopstateOperations.length) {
processStateOperations(deferredPopstateOperations);
deferredPopstateOperations = [];
}
return; return;
} }

View File

@ -1,18 +1,18 @@
import BigInt from 'big-integer'; import BigInt from 'big-integer';
import { UpdateConnectionState } from '../network'; import { UpdateConnectionState } from '../network';
import Request from '../tl/api'; import Api from '../tl/api';
import { Api as GramJs } from '..';
type Peer = { type Peer = {
peer: GramJs.Chat | GramJs.Channel | GramJs.User; peer: Api.Chat | Api.Channel | Api.User;
TEST_messages: GramJs.Message[]; inputPeer: Api.TypePeer;
TEST_sendMessage: (data: CreateMessageParams) => GramJs.Message | undefined; TEST_messages: Api.Message[];
TEST_sendMessage: (data: CreateMessageParams) => Api.Message | undefined;
}; };
type CreateMessageParams = { type CreateMessageParams = {
fromId?: any; fromId?: any;
repliesChannelId?: any; repliesChannelId?: any;
replyingTo?: GramJs.MessageReplyHeader; replyingTo?: Api.MessageReplyHeader;
}; };
class TelegramClient { class TelegramClient {
@ -24,7 +24,7 @@ class TelegramClient {
private peers: Peer[] = []; private peers: Peer[] = [];
private dialogs: GramJs.Dialog[] = []; private dialogs: Api.Dialog[] = [];
start() { start() {
} }
@ -57,12 +57,12 @@ class TelegramClient {
const { id } = discussion.TEST_sendMessage({})!; const { id } = discussion.TEST_sendMessage({})!;
discussion.TEST_sendMessage({ discussion.TEST_sendMessage({
fromId: new GramJs.PeerUser({ fromId: new Api.PeerUser({
userId: user.peer.id, userId: user.peer.id,
}), }),
replyingTo: new GramJs.MessageReplyHeader({ replyingTo: new Api.MessageReplyHeader({
replyToMsgId: id, replyToMsgId: id,
replyToPeerId: new GramJs.PeerChannel({ replyToPeerId: new Api.PeerChannel({
channelId: channel.peer.id, channelId: channel.peer.id,
}), }),
replyToTopId: message!.id, replyToTopId: message!.id,
@ -70,8 +70,8 @@ class TelegramClient {
}); });
} }
createDialog(peer: GramJs.TypePeer) { createDialog(peer: Api.TypePeer) {
return new GramJs.Dialog({ return new Api.Dialog({
peer, peer,
topMessage: 0, topMessage: 0,
readInboxMaxId: 0, readInboxMaxId: 0,
@ -79,11 +79,11 @@ class TelegramClient {
unreadCount: 0, unreadCount: 0,
unreadMentionsCount: 0, unreadMentionsCount: 0,
unreadReactionsCount: 0, unreadReactionsCount: 0,
notifySettings: new GramJs.PeerNotifySettings({}), notifySettings: new Api.PeerNotifySettings({}),
}); });
} }
createMessage(peer: GramJs.TypePeer) { createMessage(peer: Api.TypePeer) {
return ({ return ({
fromId, fromId,
repliesChannelId, repliesChannelId,
@ -93,18 +93,18 @@ class TelegramClient {
const p = this.getPeer(peer); const p = this.getPeer(peer);
if (!p || pi === undefined) return undefined; if (!p || pi === undefined) return undefined;
const message = new GramJs.Message({ const message = new Api.Message({
id: p.TEST_messages.length + 1, id: p.TEST_messages.length + 1,
fromId, fromId,
peerId: peer, peerId: peer,
date: Number(new Date()) / 1000 + pi * 60, date: Number(new Date()) / 1000 + pi * 60,
message: 'lol @channel', message: 'lol @channel',
entities: [new GramJs.MessageEntityMention({ entities: [new Api.MessageEntityMention({
offset: 4, offset: 4,
length: 8, length: 8,
})], })],
replyTo: replyingTo, replyTo: replyingTo,
replies: new GramJs.MessageReplies({ replies: new Api.MessageReplies({
comments: true, comments: true,
replies: 0, replies: 0,
repliesPts: 0, repliesPts: 0,
@ -117,22 +117,24 @@ class TelegramClient {
} }
createChat() { createChat() {
const chat = new GramJs.Chat({ const chat = new Api.Chat({
id: BigInt(this.lastId++), id: BigInt(this.lastId++),
title: 'Some chat', title: 'Some chat',
photo: new GramJs.ChatPhotoEmpty(), photo: new Api.ChatPhotoEmpty(),
participantsCount: 1, participantsCount: 1,
date: 1000, date: 1000,
version: 1, version: 1,
}); });
const peerChat = new GramJs.PeerChat({ const peerChat = new Api.PeerChat({
chatId: chat.id, chatId: chat.id,
}); });
this.dialogs.push(this.createDialog(peerChat)); 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); this.peers.push(testChat);
@ -144,24 +146,26 @@ class TelegramClient {
username: string; username: string;
isMegagroup?: boolean; isMegagroup?: boolean;
}) { }) {
const channel = new GramJs.Channel({ const channel = new Api.Channel({
username, username,
id: BigInt(this.lastId++), id: BigInt(this.lastId++),
megagroup: isMegagroup ? true : undefined, megagroup: isMegagroup ? true : undefined,
title, title,
photo: new GramJs.ChatPhotoEmpty(), photo: new Api.ChatPhotoEmpty(),
participantsCount: 1, participantsCount: 1,
date: 1000, date: 1000,
creator: true, creator: true,
}); });
const peerChannel = new GramJs.PeerChannel({ const peerChannel = new Api.PeerChannel({
channelId: channel.id, channelId: channel.id,
}); });
this.dialogs.push(this.createDialog(peerChannel)); 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); this.peers.push(testChat);
@ -175,7 +179,7 @@ class TelegramClient {
firstName: string; firstName: string;
lastName: string; lastName: string;
}): Peer { }): Peer {
const user = new GramJs.User({ const user = new Api.User({
// self: true, // self: true,
verified: true, verified: true,
id: BigInt(this.lastId++), id: BigInt(this.lastId++),
@ -192,23 +196,25 @@ class TelegramClient {
// langCode?: string; // langCode?: string;
}); });
const peerUser = new GramJs.PeerUser({ const peerUser = new Api.PeerUser({
userId: user.id, userId: user.id,
}); });
this.dialogs.push(this.createDialog(peerUser)); 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); this.peers.push(testChat);
return testChat; return testChat;
} }
invoke(request: Request) { invoke(request: any) {
// await new Promise(resolve => setTimeout(resolve, 1000)) // await new Promise(resolve => setTimeout(resolve, 1000))
if (request instanceof GramJs.messages.GetDiscussionMessage) { if (request instanceof Api.messages.GetDiscussionMessage) {
return new GramJs.messages.DiscussionMessage({ return new Api.messages.DiscussionMessage({
messages: [ messages: [
this.peers[3].TEST_messages[0], this.peers[3].TEST_messages[0],
], ],
@ -218,21 +224,21 @@ class TelegramClient {
users: [], users: [],
}); });
} }
if (request instanceof GramJs.messages.GetHistory) { if (request instanceof Api.messages.GetHistory) {
const peer = this.getPeer(request.peer); const peer = this.getPeer(request.peer);
if (!peer) return undefined; if (!peer) return undefined;
return new GramJs.messages.Messages({ return new Api.messages.Messages({
messages: peer.TEST_messages, messages: peer.TEST_messages,
chats: [], chats: [],
users: [], users: [],
}); });
} }
if (request instanceof GramJs.messages.GetReplies) { if (request instanceof Api.messages.GetReplies) {
const peer = this.peers[3]; const peer = this.peers[3];
if (!peer) return undefined; if (!peer) return undefined;
return new GramJs.messages.ChannelMessages({ return new Api.messages.ChannelMessages({
messages: peer.TEST_messages, messages: peer.TEST_messages,
pts: 0, pts: 0,
count: peer.TEST_messages.length, count: peer.TEST_messages.length,
@ -240,8 +246,8 @@ class TelegramClient {
users: [], users: [],
}); });
} }
if (request instanceof GramJs.messages.GetDialogFilters) { if (request instanceof Api.messages.GetDialogFilters) {
return [new GramJs.DialogFilter({ return [new Api.DialogFilter({
contacts: true, contacts: true,
nonContacts: true, nonContacts: true,
groups: true, groups: true,
@ -258,13 +264,31 @@ class TelegramClient {
excludePeers: [], excludePeers: [],
})]; })];
} }
if (request instanceof GramJs.messages.GetPinnedDialogs) { if (request instanceof Api.contacts.GetTopPeers) {
return new GramJs.messages.PeerDialogs({ 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: [], dialogs: [],
chats: [], chats: [],
messages: [], messages: [],
users: [], users: [],
state: new GramJs.updates.State({ state: new Api.updates.State({
pts: 0, pts: 0,
qts: 0, qts: 0,
date: 0, date: 0,
@ -273,9 +297,9 @@ class TelegramClient {
}), }),
}); });
} }
if (request instanceof GramJs.messages.GetDialogs) { if (request instanceof Api.messages.GetDialogs) {
if (request.folderId || !(request.offsetPeer instanceof GramJs.InputPeerEmpty)) { if (request.folderId || !(request.offsetPeer instanceof Api.InputPeerEmpty)) {
return new GramJs.messages.Dialogs({ return new Api.messages.Dialogs({
dialogs: [], dialogs: [],
users: [], users: [],
chats: [], chats: [],
@ -283,7 +307,7 @@ class TelegramClient {
}); });
} }
return new GramJs.messages.Dialogs({ return new Api.messages.Dialogs({
dialogs: this.dialogs, dialogs: this.dialogs,
messages: this.getAllMessages(), messages: this.getAllMessages(),
chats: this.getChats(), chats: this.getChats(),
@ -294,7 +318,7 @@ class TelegramClient {
// console.log(request.className, request); // console.log(request.className, request);
} }
private getPeerIndex(peer: GramJs.TypeInputPeer) { private getPeerIndex(peer: Api.TypeInputPeer) {
const id = 'channelId' in peer ? peer.channelId : ( const id = 'channelId' in peer ? peer.channelId : (
'userId' in peer ? peer.userId : ( 'userId' in peer ? peer.userId : (
'chatId' in peer ? peer.chatId : undefined 'chatId' in peer ? peer.chatId : undefined
@ -306,7 +330,7 @@ class TelegramClient {
return this.peers.findIndex((l) => l.peer.id.toString() === id.toString()); 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); const index = this.getPeerIndex(peer);
if (index === undefined) return undefined; if (index === undefined) return undefined;
@ -314,18 +338,18 @@ class TelegramClient {
} }
private getAllMessages() { private getAllMessages() {
return this.peers.reduce((acc: GramJs.Message[], el) => { return this.peers.reduce((acc: Api.Message[], el) => {
acc.push(...el.TEST_messages); acc.push(...el.TEST_messages);
return acc; return acc;
}, []); }, []);
} }
private getChats() { 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() { 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);
} }
} }