Skip to content

Commit ae5fd64

Browse files
toger5Half-Shot
authored andcommitted
review
- fix types (`msc4354_sticky:number` -> `msc4354_sticky?: { duration_ms: number };`) - add `MultiKeyMap` Signed-off-by: Timo K <[email protected]>
1 parent 2308a10 commit ae5fd64

File tree

4 files changed

+90
-22
lines changed

4 files changed

+90
-22
lines changed

src/models/event.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export interface IEvent {
9696
membership?: Membership;
9797
unsigned: IUnsigned;
9898
redacts?: string;
99-
msc4354_sticky?: number;
99+
msc4354_sticky?: { duration_ms: number };
100100
}
101101

102102
export interface IAggregatedRelation {

src/models/room.ts

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import { Direction, EventTimeline } from "./event-timeline.ts";
2626
import { getHttpUriForMxc } from "../content-repo.ts";
2727
import * as utils from "../utils.ts";
28-
import { normalize, noUnsafeEventProps, removeElement } from "../utils.ts";
28+
import { normalize, noUnsafeEventProps, removeElement, MultiKeyMap } from "../utils.ts";
2929
import {
3030
type IEvent,
3131
type IThreadBundledRelationship,
@@ -312,7 +312,19 @@ export type RoomEventHandlerMap = {
312312
* @param summary - the room summary object
313313
*/
314314
[RoomEvent.Summary]: (summary: IRoomSummary) => void;
315-
[RoomEvent.StickyEvents]: (stickyEventMap: Map<string, MatrixEvent>, room: Room) => void;
315+
/**
316+
* Fires when sticky events are updated for a room.
317+
* For a list of all updated events use:
318+
* `const updated = added.filter(e => removed.includes(e));`
319+
* for a list of all new events use:
320+
* `const addedNew = added.filter(e => !removed.includes(e));`
321+
* for a list of all removed events use:
322+
* `const removedOnly = removed.filter(e => !added.includes(e));`
323+
* @param added - The events that were added to the map of sticky events (can be updated events for existing keys or new keys)
324+
* @param removed - The events that were removed from the map of sticky events (caused by expiration or updated keys)
325+
* @param room - The room containing the sticky events
326+
*/
327+
[RoomEvent.StickyEvents]: (added: MatrixEvent[], removed: MatrixEvent[], room: Room) => void;
316328
[ThreadEvent.New]: (thread: Thread, toStartOfTimeline: boolean) => void;
317329
/**
318330
* Fires when a new poll instance is added to the room state
@@ -378,7 +390,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
378390
private getTypeWarning = false;
379391
private membersPromise?: Promise<boolean>;
380392

381-
private stickyEventsMap = new Map<string, MatrixEvent>();
393+
private stickyEventsMap = new MultiKeyMap<MatrixEvent & { event: { msc4354_sticky: { duration_ms: number } } }>();
394+
382395
// XXX: These should be read-only
383396
/**
384397
* The human-readable display name for this room.
@@ -3417,24 +3430,56 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
34173430
return this.accountData.get(type);
34183431
}
34193432

3420-
public getStickyEventsMap(): Map<string, MatrixEvent> {
3421-
return this.stickyEventsMap;
3433+
public getStickyEvents(): MapIterator<MatrixEvent> {
3434+
return this.stickyEventsMap.values();
34223435
}
34233436

34243437
public addStickyEvents(event: MatrixEvent[]): void {
3438+
const added = [];
3439+
const removed = [];
34253440
for (const e of event) {
3426-
const stickyKey = Room.stickyKey(e);
3427-
if (!stickyKey) {
3441+
const mapStickyKey = Room.stickyKey(e);
3442+
if (!mapStickyKey) {
34283443
logger.warn("Event from sticky sync section is missing sticky_key, ignoring", e);
34293444
continue;
34303445
}
3431-
if ("sticky" in e.event) {
3446+
// With this we have the guarantee, that all events in stickyEventsMap are correctly formatted
3447+
if (
3448+
!(
3449+
e.event.msc4354_sticky &&
3450+
"duration_ms" in e.event.msc4354_sticky &&
3451+
e.event.msc4354_sticky.duration_ms !== undefined &&
3452+
typeof e.event.msc4354_sticky.duration_ms === "number"
3453+
)
3454+
) {
34323455
logger.warn("Event from sticky sync section is missing sticky timeout", e);
34333456
continue;
34343457
}
3435-
this.stickyEventsMap.set(stickyKey, e);
3436-
this.emit(RoomEvent.StickyEvents, this.stickyEventsMap, this);
3458+
const ev = e as MatrixEvent & { event: { msc4354_sticky: { duration_ms: number } } };
3459+
const prevEv = this.stickyEventsMap.get(mapStickyKey);
3460+
const sender = e.getSender();
3461+
if (!sender) {
3462+
logger.warn("Event from sticky sync section is missing sender", e);
3463+
continue;
3464+
} else if (prevEv && ev.getTs() < prevEv.getTs()) {
3465+
logger.info(
3466+
"ignored sticky event with older timestamp for on sticky_key",
3467+
ev.getContent().sticky_key,
3468+
ev,
3469+
);
3470+
} else if (prevEv && ev.getTs() === prevEv.getTs() && (ev.getId() ?? "") < (prevEv.getId() ?? "")) {
3471+
logger.info(
3472+
"ignored sticky event due to 'id tie break rule' on sticky_key",
3473+
ev.getContent().sticky_key,
3474+
ev,
3475+
);
3476+
} else {
3477+
added.push(ev);
3478+
if (prevEv) removed.push(prevEv);
3479+
this.stickyEventsMap.set(mapStickyKey, ev);
3480+
}
34373481
}
3482+
if (added.length) this.emit(RoomEvent.StickyEvents, added, removed, this);
34383483
this.checkStickyTimer();
34393484
}
34403485
private stickyEventTimer?: NodeJS.Timeout;
@@ -3454,7 +3499,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
34543499
private getNextStickyExpiryTs(): number | null {
34553500
let nextExpiry = null;
34563501
for (const event of this.stickyEventsMap.values()) {
3457-
const expiry = event.getTs() + event.event.msc4354_sticky!;
3502+
const expiry = event.getTs() + event.event.msc4354_sticky.duration_ms;
34583503
if (nextExpiry === null || expiry < nextExpiry) {
34593504
nextExpiry = expiry;
34603505
}
@@ -3463,29 +3508,31 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
34633508
}
34643509
private cleanExpiredStickyEvents(): void {
34653510
const now = Date.now();
3466-
const toRemove: string[] = [];
3511+
const toRemove: string[][] = [];
3512+
const removedEvents: MatrixEvent[] = [];
34673513
for (const [key, event] of this.stickyEventsMap.entries()) {
34683514
const creationTs = event.getTs();
34693515
// we only added items with `sticky` into this map so we can assert non-null here
3470-
if (now > creationTs + event.event.msc4354_sticky!) {
3516+
if (now > creationTs + event.event.msc4354_sticky.duration_ms) {
34713517
toRemove.push(key);
3518+
removedEvents.push(event);
34723519
}
34733520
}
34743521
for (const key of toRemove) {
34753522
this.stickyEventsMap.delete(key);
34763523
}
3477-
if (toRemove.length) {
3478-
this.emit(RoomEvent.StickyEvents, this.stickyEventsMap, this);
3524+
if (removedEvents.length) {
3525+
this.emit(RoomEvent.StickyEvents, [], removedEvents, this);
34793526
}
34803527
this.checkStickyTimer();
34813528
}
34823529

3483-
private static stickyKey(event: MatrixEvent): string | undefined {
3484-
const k = event.getContent().sticky_key;
3485-
if (!k) {
3530+
private static stickyKey(event: MatrixEvent): string[] | undefined {
3531+
const stickyKey = event.getContent().sticky_key;
3532+
if (!stickyKey) {
34863533
return undefined;
34873534
}
3488-
return event.getSender() + event.getType() + k;
3535+
return [event.getSender(), event.getType(), stickyKey];
34893536
}
34903537

34913538
/**

src/sync.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,8 +1422,8 @@ export class SyncApi {
14221422
this.processEventsForNotifs(room, timelineEvents);
14231423

14241424
const emitEvent = (e: MatrixEvent): boolean => client.emit(ClientEvent.Event, e);
1425-
// this fires a couple of times for some events. (eg state events are in the timeline and the state or sticky events)
1426-
// should this get a the sync section as an additional event emission param (e, syncSection))?
1425+
// this fires a couple of times for some events. (eg state events are in the timeline and the state)
1426+
// should this get a sync section as an additional event emission param (e, syncSection))?
14271427
stateEvents.forEach(emitEvent);
14281428
timelineEvents.forEach(emitEvent);
14291429
ephemeralEvents.forEach(emitEvent);

src/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,3 +765,24 @@ export class MapWithDefault<K, V> extends Map<K, V> {
765765
return this.get(key)!;
766766
}
767767
}
768+
769+
export class MultiKeyMap<V> {
770+
private map = new Map<string, V>();
771+
public get(key: Array<string>): V | undefined {
772+
return this.map.get(JSON.stringify(key));
773+
}
774+
public set(key: Array<string>, value: V): void {
775+
this.map.set(JSON.stringify(key), value);
776+
}
777+
public values(): MapIterator<V> {
778+
return this.map.values();
779+
}
780+
public entries(): MapIterator<[string[], V]> {
781+
return Array.from(this.map.entries())
782+
.map<[string[], V]>(([k, v]) => [JSON.parse(k) as string[], v])
783+
.values();
784+
}
785+
public delete(key: Array<string>): boolean {
786+
return this.map.delete(JSON.stringify(key));
787+
}
788+
}

0 commit comments

Comments
 (0)