@@ -25,7 +25,7 @@ import {
2525import { Direction , EventTimeline } from "./event-timeline.ts" ;
2626import { getHttpUriForMxc } from "../content-repo.ts" ;
2727import * as utils from "../utils.ts" ;
28- import { normalize , noUnsafeEventProps , removeElement , MultiKeyMap } from "../utils.ts" ;
28+ import { normalize , noUnsafeEventProps , removeElement } from "../utils.ts" ;
2929import {
3030 type IEvent ,
3131 type IThreadBundledRelationship ,
@@ -77,6 +77,7 @@ import { compareEventOrdering } from "./compare-event-ordering.ts";
7777import { KnownMembership , type Membership } from "../@types/membership.ts" ;
7878import { type Capabilities , type IRoomVersionsCapability , RoomVersionStability } from "../serverCapabilities.ts" ;
7979import { type MSC4186Hero } from "../sliding-sync.ts" ;
80+ import { RoomStickyEvents , RoomStickyEventsEvent } from "./room-sticky-events.ts" ;
8081
8182// These constants are used as sane defaults when the homeserver doesn't support
8283// the m.room_versions capability. In practice, KNOWN_SAFE_ROOM_VERSION should be
@@ -325,6 +326,19 @@ export type RoomEventHandlerMap = {
325326 * @param room - The room containing the sticky events
326327 */
327328 [ RoomEvent . StickyEvents ] : ( added : MatrixEvent [ ] , removed : MatrixEvent [ ] , room : Room ) => void ;
329+ /**
330+ * Fires when sticky events are updated for a room.
331+ * For a list of all updated events use:
332+ * `const updated = added.filter(e => removed.includes(e));`
333+ * for a list of all new events use:
334+ * `const addedNew = added.filter(e => !removed.includes(e));`
335+ * for a list of all removed events use:
336+ * `const removedOnly = removed.filter(e => !added.includes(e));`
337+ * @param added - The events that were added to the map of sticky events (can be updated events for existing keys or new keys)
338+ * @param removed - The events that were removed from the map of sticky events (caused by expiration or updated keys)
339+ * @param room - The room containing the sticky events
340+ */
341+ [ RoomEvent . StickyEvents ] : ( added : MatrixEvent [ ] , removed : MatrixEvent [ ] , room : Room ) => void ;
328342 [ ThreadEvent . New ] : ( thread : Thread , toStartOfTimeline : boolean ) => void ;
329343 /**
330344 * Fires when a new poll instance is added to the room state
@@ -390,8 +404,6 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
390404 private getTypeWarning = false ;
391405 private membersPromise ?: Promise < boolean > ;
392406
393- private stickyEventsMap = new MultiKeyMap < MatrixEvent & { event : { msc4354_sticky : { duration_ms : number } } } > ( ) ;
394-
395407 // XXX: These should be read-only
396408 /**
397409 * The human-readable display name for this room.
@@ -462,6 +474,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
462474 */
463475 private roomReceipts = new RoomReceipts ( this ) ;
464476
477+ /**
478+ * Stores and tracks sticky events
479+ */
480+ private stickyEvents = new RoomStickyEvents ( ) ;
481+
465482 /**
466483 * Construct a new Room.
467484 *
@@ -509,6 +526,10 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
509526 // receipts. No need to remove the listener: it's on ourself anyway.
510527 this . on ( RoomEvent . Receipt , this . onReceipt ) ;
511528
529+ this . stickyEvents . on ( RoomStickyEventsEvent . Update , ( added , removed ) =>
530+ this . emit ( RoomEvent . StickyEvents , added , removed , this ) ,
531+ ) ;
532+
512533 // all our per-room timeline sets. the first one is the unfiltered ones;
513534 // the subsequent ones are the filtered ones in no particular order.
514535 this . timelineSets = [ new EventTimelineSet ( this , opts ) ] ;
@@ -3430,109 +3451,20 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
34303451 return this . accountData . get ( type ) ;
34313452 }
34323453
3433- public getStickyEvents ( ) : MapIterator < MatrixEvent > {
3434- return this . stickyEventsMap . values ( ) ;
3435- }
3436-
3437- public addStickyEvents ( event : MatrixEvent [ ] ) : void {
3438- const added = [ ] ;
3439- const removed = [ ] ;
3440- for ( const e of event ) {
3441- const mapStickyKey = Room . stickyKey ( e ) ;
3442- if ( ! mapStickyKey ) {
3443- logger . warn ( "Event from sticky sync section is missing sticky_key, ignoring" , e ) ;
3444- continue ;
3445- }
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- ) {
3455- logger . warn ( "Event from sticky sync section is missing sticky timeout" , e ) ;
3456- continue ;
3457- }
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- }
3481- }
3482- if ( added . length ) this . emit ( RoomEvent . StickyEvents , added , removed , this ) ;
3483- this . checkStickyTimer ( ) ;
3484- }
3485- private stickyEventTimer ?: NodeJS . Timeout ;
3486- private checkStickyTimer ( ) : void {
3487- if ( this . stickyEventTimer ) {
3488- clearTimeout ( this . stickyEventTimer ) ;
3489- this . stickyEventTimer = undefined ;
3490- }
3491- const nextExpiry = this . getNextStickyExpiryTs ( ) ;
3492- if ( nextExpiry && nextExpiry < Date . now ( ) ) {
3493- this . cleanExpiredStickyEvents ( ) ;
3494- } else if ( nextExpiry ) {
3495- this . stickyEventTimer = setTimeout ( this . cleanExpiredStickyEvents , nextExpiry - Date . now ( ) ) ;
3496- }
3497- }
3498-
3499- private getNextStickyExpiryTs ( ) : number | null {
3500- let nextExpiry = null ;
3501- for ( const event of this . stickyEventsMap . values ( ) ) {
3502- const expiry = event . getTs ( ) + event . event . msc4354_sticky . duration_ms ;
3503- if ( nextExpiry === null || expiry < nextExpiry ) {
3504- nextExpiry = expiry ;
3505- }
3506- }
3507- return nextExpiry ;
3508- }
3509- private cleanExpiredStickyEvents ( ) : void {
3510- const now = Date . now ( ) ;
3511- const toRemove : string [ ] [ ] = [ ] ;
3512- const removedEvents : MatrixEvent [ ] = [ ] ;
3513- for ( const [ key , event ] of this . stickyEventsMap . entries ( ) ) {
3514- const creationTs = event . getTs ( ) ;
3515- // we only added items with `sticky` into this map so we can assert non-null here
3516- if ( now > creationTs + event . event . msc4354_sticky . duration_ms ) {
3517- toRemove . push ( key ) ;
3518- removedEvents . push ( event ) ;
3519- }
3520- }
3521- for ( const key of toRemove ) {
3522- this . stickyEventsMap . delete ( key ) ;
3523- }
3524- if ( removedEvents . length ) {
3525- this . emit ( RoomEvent . StickyEvents , [ ] , removedEvents , this ) ;
3526- }
3527- this . checkStickyTimer ( ) ;
3454+ /**
3455+ * Get an iterator of currently active sticky events.
3456+ */
3457+ public unstableGetStickyEvents ( ) {
3458+ return this . stickyEvents . unstableGetStickyEvents ( ) ;
35283459 }
35293460
3530- private static stickyKey ( event : MatrixEvent ) : string [ ] | undefined {
3531- const stickyKey = event . getContent ( ) . sticky_key ;
3532- if ( ! stickyKey ) {
3533- return undefined ;
3534- }
3535- return [ event . getSender ( ) , event . getType ( ) , stickyKey ] ;
3461+ /**
3462+ * Add a series of sticky events, emitting `RoomEvent.StickyEvents` if any
3463+ * changes were made.
3464+ * @param events A set of new sticky events.
3465+ */
3466+ public unstableAddStickyEvents ( events : MatrixEvent [ ] ) : void {
3467+ return this . stickyEvents . unstableAddStickyEvents ( events ) ;
35363468 }
35373469
35383470 /**
0 commit comments