message: Don't send prehistorical events to widgets during decryption at startup
  Fixes
  Tracking a localized read receipt of sorts appears to be the fastest and least complex approach, though not the greatest.
diff: @@ -55,6 +55,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
  import { ELEMENT_CLIENT_ID } from "../../identifiers";
  import { getUserLanguage } from "../../languageHandler";
  import { WidgetVariableCustomisations } from "../../customisations/WidgetVariables";
  +import { arrayFastClone } from "../../utils/arrays";
  // TODO: Destroy all of this code
  @@ -146,6 +147,7 @@ export class StopGapWidget extends EventEmitter {
  private scalarToken: string;
  private roomId?: string;
  private kind: WidgetKind;
  + private readUpToMap: {[roomId: string]: string} = {}; // room ID to event ID
  constructor(private appTileProps: IAppTileProps) {
  super();
  @@ -294,6 +296,14 @@ export class StopGapWidget extends EventEmitter {
  this.messaging.transport.reply(ev.detail, <IWidgetApiRequestEmptyData>{});
  });
  + // Populate the map of "read up to" events for this widget with the current event in every room.
  + // This is a bit inefficient, but should be okay. We do this for all rooms in case the widget
  + // requests timeline capabilities in other rooms down the road. It's just easier to manage here.
  + for (const room of MatrixClientPeg.get().getRooms()) {
  + // Timelines are most recent last
  + this.readUpToMap[room.roomId] = arrayFastClone(room.getLiveTimeline().getEvents()).reverse()[0].getId();
  + }
  +
  // Attach listeners for feeding events - the underlying widget classes handle permissions for us
  MatrixClientPeg.get().on('event', this.onEvent);
  MatrixClientPeg.get().on('Event.decrypted', this.onEventDecrypted);
  @@ -421,6 +431,43 @@ export class StopGapWidget extends EventEmitter {
  private feedEvent(ev: MatrixEvent) {
  if (!this.messaging) return;
  + // Check to see if this event would be before or after our "read up to" marker. If it's
  + // before, or we can't decide, then we assume the widget will have already seen the event.
  + // If the event is after, or we don't have a marker for the room, then we'll send it through.
  + //
  + // This approach of "read up to" prevents widgets receiving decryption spam from startup or
  + // receiving out-of-order events from backfill and such.
  + const upToEventId = this.readUpToMap[ev.getRoomId()];
  + if (upToEventId) {
  + // Small optimization for exact match (prevent search)
  + if (upToEventId === ev.getId()) {
  + return;
  + }
  +
  + let isBeforeMark = true;
  +
  + // Timelines are most recent last, so reverse the order and limit ourselves to 100 events
  + // to avoid overusing the CPU.
  + const timeline = MatrixClientPeg.get().getRoom(ev.getRoomId()).getLiveTimeline();
  + const events = arrayFastClone(timeline.getEvents()).reverse().slice(0, 100);
  +
  + for (const timelineEvent of events) {
  + if (timelineEvent.getId() === upToEventId) {
  + break;
  + } else if (timelineEvent.getId() === ev.getId()) {
  + isBeforeMark = false;
  + break;
  + }
  + }
  +
  + if (isBeforeMark) {
  + // Ignore the event: it is before our interest.
  + return;
  + }
  + }
  +
  + this.readUpToMap[ev.getRoomId()] = ev.getId();
  +
  const raw = ev.getEffectiveEvent();
  this.messaging.feedEvent(raw).catch(e => {
  console.error("Error sending event to widget: ", e);
  