Skip to content

Commit

Permalink
Order recently sent messages by local timestamp (#7012)
Browse files Browse the repository at this point in the history
  • Loading branch information
hpeebles authored Dec 9, 2024
1 parent 98ddc7e commit 0799af9
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 6 deletions.
7 changes: 7 additions & 0 deletions frontend/openchat-client/src/stores/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { draftMessagesStore } from "./draftMessages";
import { blockedUsers } from "./blockedUsers";
import { createLsBoolStore } from "./localStorageSetting";
import { configKeys } from "../utils/config";
import { recentlySentMessagesStore } from "./recentlySentMessages";

let currentScope: ChatListScope = { kind: "direct_chat" };
chatListScopeStore.subscribe((s) => (currentScope = s));
Expand Down Expand Up @@ -544,6 +545,7 @@ export const threadEvents = derived(
currentChatBlockedOrSuspendedUsers,
currentUserIdStore,
messageFiltersStore,
recentlySentMessagesStore,
],
([
$serverEvents,
Expand All @@ -556,6 +558,7 @@ export const threadEvents = derived(
$blockedOrSuspendedUsers,
$currentUserId,
$messageFilters,
$recentlySentMessagesStore,
]) => {
if ($messageContext === undefined || $messageContext.threadRootMessageIndex === undefined)
return [];
Expand All @@ -574,6 +577,7 @@ export const threadEvents = derived(
$blockedOrSuspendedUsers,
$currentUserId,
$messageFilters,
$recentlySentMessagesStore,
);
},
);
Expand Down Expand Up @@ -728,6 +732,7 @@ export const eventsStore: Readable<EventWrapper<ChatEvent>[]> = derived(
currentChatBlockedOrSuspendedUsers,
currentUserIdStore,
messageFiltersStore,
recentlySentMessagesStore,
],
([
$serverEventsForSelectedChat,
Expand All @@ -740,6 +745,7 @@ export const eventsStore: Readable<EventWrapper<ChatEvent>[]> = derived(
$blockedOrSuspendedUsers,
$currentUserId,
$messageFilters,
$recentlySentMessagesStore,
]) => {
const chatId = get(selectedChatId) ?? { kind: "group_chat", groupId: "" };
const failedForChat = $failedMessages.get({ chatId });
Expand All @@ -756,6 +762,7 @@ export const eventsStore: Readable<EventWrapper<ChatEvent>[]> = derived(
$blockedOrSuspendedUsers,
$currentUserId,
$messageFilters,
$recentlySentMessagesStore,
);
},
);
Expand Down
12 changes: 10 additions & 2 deletions frontend/openchat-client/src/stores/mapStore.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import type { Writable } from "svelte/store";
import { get } from "svelte/store";

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function createMapStore<K, V>(store: Writable<Map<K, V>>) {
let storeValue = new Map<K, V>();
store.subscribe((v) => (storeValue = v));

return {
subscribe: store.subscribe,
get: storeValue.get,
has: storeValue.has,
size: () => storeValue.size,
set: store.set,
insert: (key: K, value: V) => {
store.update((map) => {
map.set(key, value);
return map;
});
},
update: (updater: (value: Map<K, V>) => Map<K, V>) => {
store.update(updater);
},
delete: (key: K): boolean => {
if (get(store).has(key)) {
if (storeValue.has(key)) {
store.update((map) => {
map.delete(key);
return map;
Expand Down
25 changes: 25 additions & 0 deletions frontend/openchat-client/src/stores/recentlySentMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createMapStore } from "./mapStore";
import { writable } from "svelte/store";

// Key: MessageId, Value: Timestamp
export const recentlySentMessagesStore = createMapStore<bigint, bigint>(
writable(new Map<bigint, bigint>()),
);

function pruneOldMessages(): void {
if (recentlySentMessagesStore.size() > 0) {
const oneMinuteAgo = BigInt(Date.now() - 60000);
recentlySentMessagesStore.update((map) => {
const newMap = new Map<bigint, bigint>();
for (const [key, value] of map.entries()) {
if (value > oneMinuteAgo) {
newMap.set(key, value);
}
}
return newMap;
});
}
}

// Prune old messages every 31 seconds
window.setInterval(pruneOldMessages, 31000);
2 changes: 2 additions & 0 deletions frontend/openchat-client/src/stores/unconfirmed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type MessageContext,
MessageContextMap,
} from "openchat-shared";
import { recentlySentMessagesStore } from "./recentlySentMessages";

export type UnconfirmedState = {
messages: EventWrapper<Message>[];
Expand Down Expand Up @@ -81,6 +82,7 @@ function createUnconfirmedStore() {
}
return state;
});
recentlySentMessagesStore.insert(message.event.messageId, message.timestamp);
},
contains: (key: MessageContext, messageId: bigint): boolean => {
return storeValue.get(key)?.messageIds.has(messageId) ?? false;
Expand Down
32 changes: 28 additions & 4 deletions frontend/openchat-client/src/utils/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,28 @@ function updateReplyContexts(
}
}

function createMessageSortFunction(
unconfirmed: Set<bigint>,
recentlySent: Map<bigint, bigint>,
): (a: EventWrapper<ChatEvent>, b: EventWrapper<ChatEvent>) => number {
return (a: EventWrapper<ChatEvent>, b: EventWrapper<ChatEvent>): number => {
// If either message is still unconfirmed, and both were sent recently, use both of their local timestamps,
// otherwise we will be comparing the local timestamp of one with the server timestamp of the other
if (a.event.kind === "message" && b.event.kind === "message") {
if (unconfirmed.has(a.event.messageId) || unconfirmed.has(b.event.messageId)) {
const aTimestampOverride = recentlySent.get(a.event.messageId);
const bTimestampOverride = recentlySent.get(b.event.messageId);

if (aTimestampOverride && bTimestampOverride) {
return aTimestampOverride > bTimestampOverride ? 1 : -1;
}
}
}

return sortByTimestampThenEventIndex(a, b);
};
}

function sortByTimestampThenEventIndex(
a: EventWrapper<ChatEvent>,
b: EventWrapper<ChatEvent>,
Expand Down Expand Up @@ -1389,6 +1411,7 @@ export function mergeEventsAndLocalUpdates(
blockedUsers: Set<string>,
currentUserId: string,
messageFilters: MessageFilter[],
recentlySentMessages: Map<bigint, bigint>,
): EventWrapper<ChatEvent>[] {
const eventIndexes = new DRange();
eventIndexes.add(expiredEventRanges);
Expand Down Expand Up @@ -1466,7 +1489,7 @@ export function mergeEventsAndLocalUpdates(
if (unconfirmed.length > 0) {
unconfirmed.sort(sortByTimestampThenEventIndex);

let anyAdded = false;
let unconfirmedAdded = new Set<bigint>();
for (const message of unconfirmed) {
// Only include unconfirmed events that are either contiguous with the loaded confirmed events, or are the
// first events in a new chat
Expand All @@ -1478,11 +1501,12 @@ export function mergeEventsAndLocalUpdates(
.some((s) => s.low - 1 <= message.index && message.index <= s.high + 1))
) {
merged.push(processEvent(message));
anyAdded = true;
unconfirmedAdded.add(message.event.messageId);
}
}
if (anyAdded) {
merged.sort(sortByTimestampThenEventIndex);
if (unconfirmedAdded.size > 0) {
const sortFn = createMessageSortFunction(unconfirmedAdded, recentlySentMessages);
merged.sort(sortFn);
}
}

Expand Down

0 comments on commit 0799af9

Please sign in to comment.