@@ -55,6 +55,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
5555import { ELEMENT_CLIENT_ID } from "../../identifiers" ;
5656import { getUserLanguage } from "../../languageHandler" ;
5757import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables" ;
58+ import { arrayFastClone } from "../../utils/arrays" ;
5859
5960// TODO: Destroy all of this code
6061
@@ -146,6 +147,7 @@ export class StopGapWidget extends EventEmitter {
146147 private scalarToken : string ;
147148 private roomId ?: string ;
148149 private kind : WidgetKind ;
150+ private readUpToMap : { [ roomId : string ] : string } = { } ; // room ID to event ID
149151
150152 constructor ( private appTileProps : IAppTileProps ) {
151153 super ( ) ;
@@ -294,6 +296,14 @@ export class StopGapWidget extends EventEmitter {
294296 this . messaging . transport . reply ( ev . detail , < IWidgetApiRequestEmptyData > { } ) ;
295297 } ) ;
296298
299+ // Populate the map of "read up to" events for this widget with the current event in every room.
300+ // This is a bit inefficient, but should be okay. We do this for all rooms in case the widget
301+ // requests timeline capabilities in other rooms down the road. It's just easier to manage here.
302+ for ( const room of MatrixClientPeg . get ( ) . getRooms ( ) ) {
303+ // Timelines are most recent last
304+ this . readUpToMap [ room . roomId ] = arrayFastClone ( room . getLiveTimeline ( ) . getEvents ( ) ) . reverse ( ) [ 0 ] . getId ( ) ;
305+ }
306+
297307 // Attach listeners for feeding events - the underlying widget classes handle permissions for us
298308 MatrixClientPeg . get ( ) . on ( 'event' , this . onEvent ) ;
299309 MatrixClientPeg . get ( ) . on ( 'Event.decrypted' , this . onEventDecrypted ) ;
@@ -421,6 +431,43 @@ export class StopGapWidget extends EventEmitter {
421431 private feedEvent ( ev : MatrixEvent ) {
422432 if ( ! this . messaging ) return ;
423433
434+ // Check to see if this event would be before or after our "read up to" marker. If it's
435+ // before, or we can't decide, then we assume the widget will have already seen the event.
436+ // If the event is after, or we don't have a marker for the room, then we'll send it through.
437+ //
438+ // This approach of "read up to" prevents widgets receiving decryption spam from startup or
439+ // receiving out-of-order events from backfill and such.
440+ const upToEventId = this . readUpToMap [ ev . getRoomId ( ) ] ;
441+ if ( upToEventId ) {
442+ // Small optimization for exact match (prevent search)
443+ if ( upToEventId === ev . getId ( ) ) {
444+ return ;
445+ }
446+
447+ let isBeforeMark = true ;
448+
449+ // Timelines are most recent last, so reverse the order and limit ourselves to 100 events
450+ // to avoid overusing the CPU.
451+ const timeline = MatrixClientPeg . get ( ) . getRoom ( ev . getRoomId ( ) ) . getLiveTimeline ( ) ;
452+ const events = arrayFastClone ( timeline . getEvents ( ) ) . reverse ( ) . slice ( 0 , 100 ) ;
453+
454+ for ( const timelineEvent of events ) {
455+ if ( timelineEvent . getId ( ) === upToEventId ) {
456+ break ;
457+ } else if ( timelineEvent . getId ( ) === ev . getId ( ) ) {
458+ isBeforeMark = false ;
459+ break ;
460+ }
461+ }
462+
463+ if ( isBeforeMark ) {
464+ // Ignore the event: it is before our interest.
465+ return ;
466+ }
467+ }
468+
469+ this . readUpToMap [ ev . getRoomId ( ) ] = ev . getId ( ) ;
470+
424471 const raw = ev . getEffectiveEvent ( ) ;
425472 this . messaging . feedEvent ( raw ) . catch ( e => {
426473 console . error ( "Error sending event to widget: " , e ) ;
0 commit comments