@@ -154,7 +154,10 @@ export class StopGapWidget extends EventEmitter {
154
154
private kind : WidgetKind ;
155
155
private readonly virtual : boolean ;
156
156
private readUpToMap : { [ roomId : string ] : string } = { } ; // room ID to event ID
157
- private stickyPromise ?: ( ) => Promise < void > ; // This promise will be called and needs to resolve before the widget will actually become sticky.
157
+ // This promise will be called and needs to resolve before the widget will actually become sticky.
158
+ private stickyPromise ?: ( ) => Promise < void > ;
159
+ // Holds events that should be fed to the widget once they finish decrypting
160
+ private readonly eventsToFeed = new WeakSet < MatrixEvent > ( ) ;
158
161
159
162
public constructor ( private appTileProps : IAppTileProps ) {
160
163
super ( ) ;
@@ -465,12 +468,10 @@ export class StopGapWidget extends EventEmitter {
465
468
466
469
private onEvent = ( ev : MatrixEvent ) : void => {
467
470
this . client . decryptEventIfNeeded ( ev ) ;
468
- if ( ev . isBeingDecrypted ( ) || ev . isDecryptionFailure ( ) ) return ;
469
471
this . feedEvent ( ev ) ;
470
472
} ;
471
473
472
474
private onEventDecrypted = ( ev : MatrixEvent ) : void => {
473
- if ( ev . isDecryptionFailure ( ) ) return ;
474
475
this . feedEvent ( ev ) ;
475
476
} ;
476
477
@@ -480,72 +481,103 @@ export class StopGapWidget extends EventEmitter {
480
481
await this . messaging ?. feedToDevice ( ev . getEffectiveEvent ( ) as IRoomEvent , ev . isEncrypted ( ) ) ;
481
482
} ;
482
483
483
- private feedEvent ( ev : MatrixEvent ) : void {
484
- if ( ! this . messaging ) return ;
485
-
486
- // Check to see if this event would be before or after our "read up to" marker. If it's
487
- // before, or we can't decide, then we assume the widget will have already seen the event.
488
- // If the event is after, or we don't have a marker for the room, then we'll send it through.
489
- //
490
- // This approach of "read up to" prevents widgets receiving decryption spam from startup or
491
- // receiving out-of-order events from backfill and such.
492
- //
493
- // Skip marker timeline check for events with relations to unknown parent because these
494
- // events are not added to the timeline here and will be ignored otherwise:
495
- // https://github.com/matrix-org/matrix-js-sdk/blob/d3dfcd924201d71b434af3d77343b5229b6ed75e/src/models/room.ts#L2207-L2213
496
- let isRelationToUnknown : boolean | undefined = undefined ;
497
- const upToEventId = this . readUpToMap [ ev . getRoomId ( ) ! ] ;
498
- if ( upToEventId ) {
499
- // Small optimization for exact match (prevent search)
500
- if ( upToEventId === ev . getId ( ) ) {
501
- return ;
502
- }
484
+ /**
485
+ * Determines whether the event has a relation to an unknown parent.
486
+ */
487
+ private relatesToUnknown ( ev : MatrixEvent ) : boolean {
488
+ // Replies to unknown events don't count
489
+ if ( ! ev . relationEventId || ev . replyEventId ) return false ;
490
+ const room = this . client . getRoom ( ev . getRoomId ( ) ) ;
491
+ return room === null || ! room . findEventById ( ev . relationEventId ) ;
492
+ }
503
493
504
- // should be true to forward the event to the widget
505
- let shouldForward = false ;
506
-
507
- const room = this . client . getRoom ( ev . getRoomId ( ) ! ) ;
508
- if ( ! room ) return ;
509
- // Timelines are most recent last, so reverse the order and limit ourselves to 100 events
510
- // to avoid overusing the CPU.
511
- const timeline = room . getLiveTimeline ( ) ;
512
- const events = arrayFastClone ( timeline . getEvents ( ) ) . reverse ( ) . slice ( 0 , 100 ) ;
513
-
514
- for ( const timelineEvent of events ) {
515
- if ( timelineEvent . getId ( ) === upToEventId ) {
516
- break ;
517
- } else if ( timelineEvent . getId ( ) === ev . getId ( ) ) {
518
- shouldForward = true ;
519
- break ;
520
- }
521
- }
494
+ /**
495
+ * Determines whether the event comes from a room that we've been invited to
496
+ * (in which case we likely don't have the full timeline).
497
+ */
498
+ private isFromInvite ( ev : MatrixEvent ) : boolean {
499
+ const room = this . client . getRoom ( ev . getRoomId ( ) ) ;
500
+ return room ?. getMyMembership ( ) === KnownMembership . Invite ;
501
+ }
522
502
523
- if ( ! shouldForward ) {
524
- // checks that the event has a relation to unknown event
525
- isRelationToUnknown =
526
- ! ev . replyEventId && ! ! ev . relationEventId && ! room . findEventById ( ev . relationEventId ) ;
527
- if ( ! isRelationToUnknown ) {
528
- // Ignore the event: it is before our interest.
529
- return ;
530
- }
531
- }
503
+ /**
504
+ * Advances the "read up to" marker for a room to a certain event. No-ops if
505
+ * the event is before the marker.
506
+ * @returns Whether the "read up to" marker was advanced.
507
+ */
508
+ private advanceReadUpToMarker ( ev : MatrixEvent ) : boolean {
509
+ const evId = ev . getId ( ) ;
510
+ if ( evId === undefined ) return false ;
511
+ const roomId = ev . getRoomId ( ) ;
512
+ if ( roomId === undefined ) return false ;
513
+ const room = this . client . getRoom ( roomId ) ;
514
+ if ( room === null ) return false ;
515
+
516
+ const upToEventId = this . readUpToMap [ ev . getRoomId ( ) ! ] ;
517
+ if ( ! upToEventId ) {
518
+ // There's no marker yet; start it at this event
519
+ this . readUpToMap [ roomId ] = evId ;
520
+ return true ;
532
521
}
533
522
534
- // Skip marker assignment if membership is 'invite', otherwise 'm.room.member' from
535
- // invitation room will assign it and new state events will be not forwarded to the widget
536
- // because of empty timeline for invitation room and assigned marker.
537
- const evRoomId = ev . getRoomId ( ) ;
538
- const evId = ev . getId ( ) ;
539
- if ( evRoomId && evId ) {
540
- const room = this . client . getRoom ( evRoomId ) ;
541
- if ( room && room . getMyMembership ( ) === KnownMembership . Join && ! isRelationToUnknown ) {
542
- this . readUpToMap [ evRoomId ] = evId ;
523
+ // Small optimization for exact match (skip the search)
524
+ if ( upToEventId === evId ) return false ;
525
+
526
+ // Timelines are most recent last, so reverse the order and limit ourselves to 100 events
527
+ // to avoid overusing the CPU.
528
+ const timeline = room . getLiveTimeline ( ) ;
529
+ const events = arrayFastClone ( timeline . getEvents ( ) ) . reverse ( ) . slice ( 0 , 100 ) ;
530
+
531
+ for ( const timelineEvent of events ) {
532
+ if ( timelineEvent . getId ( ) === upToEventId ) {
533
+ // The event must be somewhere before the "read up to" marker
534
+ return false ;
535
+ } else if ( timelineEvent . getId ( ) === ev . getId ( ) ) {
536
+ // The event is after the marker; advance it
537
+ this . readUpToMap [ roomId ] = evId ;
538
+ return true ;
543
539
}
544
540
}
545
541
546
- const raw = ev . getEffectiveEvent ( ) ;
547
- this . messaging . feedEvent ( raw as IRoomEvent , this . eventListenerRoomId ! ) . catch ( ( e ) => {
548
- logger . error ( "Error sending event to widget: " , e ) ;
549
- } ) ;
542
+ // We can't say for sure whether the widget has seen the event; let's
543
+ // just assume that it has
544
+ return false ;
545
+ }
546
+
547
+ private feedEvent ( ev : MatrixEvent ) : void {
548
+ if ( this . messaging === null ) return ;
549
+ if (
550
+ // If we had decided earlier to feed this event to the widget, but
551
+ // it just wasn't ready, give it another try
552
+ this . eventsToFeed . delete ( ev ) ||
553
+ // Skip marker timeline check for events with relations to unknown parent because these
554
+ // events are not added to the timeline here and will be ignored otherwise:
555
+ // https://github.com/matrix-org/matrix-js-sdk/blob/d3dfcd924201d71b434af3d77343b5229b6ed75e/src/models/room.ts#L2207-L2213
556
+ this . relatesToUnknown ( ev ) ||
557
+ // Skip marker timeline check for rooms where membership is
558
+ // 'invite', otherwise the membership event from the invitation room
559
+ // will advance the marker and new state events will not be
560
+ // forwarded to the widget.
561
+ this . isFromInvite ( ev ) ||
562
+ // Check whether this event would be before or after our "read up to" marker. If it's
563
+ // before, or we can't decide, then we assume the widget will have already seen the event.
564
+ // If the event is after, or we don't have a marker for the room, then the marker will advance and we'll
565
+ // send it through.
566
+ // This approach of "read up to" prevents widgets receiving decryption spam from startup or
567
+ // receiving ancient events from backfill and such.
568
+ this . advanceReadUpToMarker ( ev )
569
+ ) {
570
+ // If the event is still being decrypted, remember that we want to
571
+ // feed it to the widget (even if not strictly in the order given by
572
+ // the timeline) and get back to it later
573
+ if ( ev . isBeingDecrypted ( ) || ev . isDecryptionFailure ( ) ) {
574
+ this . eventsToFeed . add ( ev ) ;
575
+ } else {
576
+ const raw = ev . getEffectiveEvent ( ) ;
577
+ this . messaging . feedEvent ( raw as IRoomEvent , this . eventListenerRoomId ! ) . catch ( ( e ) => {
578
+ logger . error ( "Error sending event to widget: " , e ) ;
579
+ } ) ;
580
+ }
581
+ }
550
582
}
551
583
}
0 commit comments