From 71cece7641768052fc24739a47fb98e72438e5bf Mon Sep 17 00:00:00 2001 From: maheichyk Date: Thu, 29 Feb 2024 17:19:17 +0300 Subject: [PATCH] Feeds event with relation to unknown to the widget (#12283) * Feeds event with relation to unknown to the widget Signed-off-by: Mikhail Aheichyk * Smaller changes Signed-off-by: Mikhail Aheichyk --------- Signed-off-by: Mikhail Aheichyk Co-authored-by: Mikhail Aheichyk --- src/stores/widgets/StopGapWidget.ts | 23 +++-- test/stores/widgets/StopGapWidget-test.ts | 101 +++++++++++++++++++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index c131a7e766..bec4371af2 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -495,6 +495,11 @@ export class StopGapWidget extends EventEmitter { // // This approach of "read up to" prevents widgets receiving decryption spam from startup or // receiving out-of-order events from backfill and such. + // + // Skip marker timeline check for events with relations to unknown parent because these + // events are not added to the timeline here and will be ignored otherwise: + // https://github.com/matrix-org/matrix-js-sdk/blob/d3dfcd924201d71b434af3d77343b5229b6ed75e/src/models/room.ts#L2207-L2213 + let isRelationToUnknown: boolean | undefined = undefined; const upToEventId = this.readUpToMap[ev.getRoomId()!]; if (upToEventId) { // Small optimization for exact match (prevent search) @@ -502,7 +507,8 @@ export class StopGapWidget extends EventEmitter { return; } - let isBeforeMark = true; + // should be true to forward the event to the widget + let shouldForward = false; const room = this.client.getRoom(ev.getRoomId()!); if (!room) return; @@ -515,14 +521,19 @@ export class StopGapWidget extends EventEmitter { if (timelineEvent.getId() === upToEventId) { break; } else if (timelineEvent.getId() === ev.getId()) { - isBeforeMark = false; + shouldForward = true; break; } } - if (isBeforeMark) { - // Ignore the event: it is before our interest. - return; + if (!shouldForward) { + // checks that the event has a relation to unknown event + isRelationToUnknown = + !ev.replyEventId && !!ev.relationEventId && !room.findEventById(ev.relationEventId); + if (!isRelationToUnknown) { + // Ignore the event: it is before our interest. + return; + } } } @@ -533,7 +544,7 @@ export class StopGapWidget extends EventEmitter { const evId = ev.getId(); if (evRoomId && evId) { const room = this.client.getRoom(evRoomId); - if (room && room.getMyMembership() === "join") { + if (room && room.getMyMembership() === "join" && !isRelationToUnknown) { this.readUpToMap[evRoomId] = evId; } } diff --git a/test/stores/widgets/StopGapWidget-test.ts b/test/stores/widgets/StopGapWidget-test.ts index 1a25556e6d..9d1ade0f39 100644 --- a/test/stores/widgets/StopGapWidget-test.ts +++ b/test/stores/widgets/StopGapWidget-test.ts @@ -16,7 +16,7 @@ limitations under the License. import { mocked, MockedObject } from "jest-mock"; import { last } from "lodash"; -import { MatrixEvent, MatrixClient, ClientEvent } from "matrix-js-sdk/src/matrix"; +import { MatrixEvent, MatrixClient, ClientEvent, EventTimeline } from "matrix-js-sdk/src/matrix"; import { ClientWidgetApi, WidgetApiFromWidgetAction } from "matrix-widget-api"; import { waitFor } from "@testing-library/react"; @@ -88,6 +88,105 @@ describe("StopGapWidget", () => { expect(messaging.feedToDevice).toHaveBeenCalledWith(event.getEffectiveEvent(), false); }); + describe("feed event", () => { + let event1: MatrixEvent; + let event2: MatrixEvent; + + beforeEach(() => { + event1 = mkEvent({ + event: true, + id: "$event-id1", + type: "org.example.foo", + user: "@alice:example.org", + content: { hello: "world" }, + room: "!1:example.org", + }); + + event2 = mkEvent({ + event: true, + id: "$event-id2", + type: "org.example.foo", + user: "@alice:example.org", + content: { hello: "world" }, + room: "!1:example.org", + }); + + const room = mkRoom(client, "!1:example.org"); + client.getRoom.mockImplementation((roomId) => (roomId === "!1:example.org" ? room : null)); + room.getLiveTimeline.mockReturnValue({ + getEvents: (): MatrixEvent[] => [event1, event2], + } as unknown as EventTimeline); + + messaging.feedEvent.mockResolvedValue(); + }); + + it("feeds incoming event to the widget", async () => { + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event2); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + }); + + it("should not feed incoming event to the widget if seen already", async () => { + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event2); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event2.getEffectiveEvent(), "!1:example.org"); + }); + + it("should not feed incoming event if not in timeline", () => { + const event = mkEvent({ + event: true, + id: "$event-id", + type: "org.example.foo", + user: "@alice:example.org", + content: { + hello: "world", + }, + room: "!1:example.org", + }); + + client.emit(ClientEvent.Event, event); + expect(messaging.feedEvent).toHaveBeenCalledWith(event.getEffectiveEvent(), "!1:example.org"); + }); + + it("feeds incoming event that is not in timeline but relates to unknown parent to the widget", async () => { + const event = mkEvent({ + event: true, + id: "$event-idRelation", + type: "org.example.foo", + user: "@alice:example.org", + content: { + "hello": "world", + "m.relates_to": { + event_id: "$unknown-parent", + rel_type: "m.reference", + }, + }, + room: "!1:example.org", + }); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledWith(event1.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); + + client.emit(ClientEvent.Event, event1); + expect(messaging.feedEvent).toHaveBeenCalledTimes(2); + expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org"); + }); + }); + describe("when there is a voice broadcast recording", () => { let voiceBroadcastInfoEvent: MatrixEvent; let voiceBroadcastRecording: VoiceBroadcastRecording;