From c2068f799c19ae29b203d512657de15328a933df Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 17 Mar 2023 12:55:37 -0400 Subject: [PATCH] Fix reading state events without a specific state key And add some tests for reading state events while we're at it. Regressed by a8fc2b929564fac88d0bf0e6f175c8299190871e --- src/ClientWidgetApi.ts | 4 +- src/models/WidgetEventCapability.ts | 2 +- test/ClientWidgetApi-test.ts | 123 ++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index cdceb39..fecbefc 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -178,7 +178,7 @@ export class ClientWidgetApi extends EventEmitter { return this.allowedEvents.some(e => e.matchesAsRoomEvent(EventDirection.Receive, eventType, msgtype)); } - public canReceiveStateEvent(eventType: string, stateKey: string): boolean { + public canReceiveStateEvent(eventType: string, stateKey: string | null): boolean { return this.allowedEvents.some(e => e.matchesAsStateEvent(EventDirection.Receive, eventType, stateKey)); } @@ -401,7 +401,7 @@ export class ClientWidgetApi extends EventEmitter { let events: Promise = Promise.resolve([]); if (request.data.state_key !== undefined) { const stateKey = request.data.state_key === true ? undefined : request.data.state_key.toString(); - if (!stateKey || !this.canReceiveStateEvent(request.data.type, stateKey)) { + if (!this.canReceiveStateEvent(request.data.type, stateKey ?? null)) { return this.transport.reply(request, { error: {message: "Cannot read state events of this type"}, }); diff --git a/src/models/WidgetEventCapability.ts b/src/models/WidgetEventCapability.ts index 724bc22..16d933e 100644 --- a/src/models/WidgetEventCapability.ts +++ b/src/models/WidgetEventCapability.ts @@ -37,7 +37,7 @@ export class WidgetEventCapability { ) { } - public matchesAsStateEvent(direction: EventDirection, eventType: string, stateKey: string): boolean { + public matchesAsStateEvent(direction: EventDirection, eventType: string, stateKey: string | null): boolean { if (this.kind !== EventKind.State) return false; // not a state event if (this.direction !== direction) return false; // direction mismatch if (this.eventType !== eventType) return false; // event type mismatch diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index c8501bd..e9654cf 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -28,6 +28,7 @@ import { WidgetApiFromWidgetAction } from '../src/interfaces/WidgetApiAction'; import { WidgetApiDirection } from '../src/interfaces/WidgetApiDirection'; import { Widget } from '../src/models/Widget'; import { PostmessageTransport } from '../src/transport/PostmessageTransport'; +import { IReadEventFromWidgetActionRequest } from '../src'; jest.mock('../src/transport/PostmessageTransport') @@ -74,6 +75,7 @@ describe('ClientWidgetApi', () => { document.body.appendChild(iframe); driver = { + readStateEvents: jest.fn(), readEventRelations: jest.fn(), validateCapabilities: jest.fn(), searchUserDirectory: jest.fn(), @@ -111,6 +113,127 @@ describe('ClientWidgetApi', () => { expect(clientWidgetApi.hasCapability('m.sticker')).toBe(false); }); + describe('org.matrix.msc2876.read_events action', () => { + it('reads state events with any state key', async () => { + driver.readStateEvents.mockResolvedValue([ + createRoomEvent({ type: 'net.example.test', state_key: 'A' }), + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + ]) + + const event: IReadEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC2876ReadEvents, + data: { + type: 'net.example.test', + state_key: true, + }, + }; + + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test']); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + events: [ + createRoomEvent({ type: 'net.example.test', state_key: 'A' }), + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + ], + }); + }); + + expect(driver.readStateEvents).toBeCalledWith( + 'net.example.test', undefined, 0, null, + ) + }); + + it('fails to read state events with any state key', async () => { + const event: IReadEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC2876ReadEvents, + data: { + type: 'net.example.test', + state_key: true, + }, + }; + + await loadIframe([]); // Without the required capability + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + error: { message: expect.any(String) }, + }); + }); + + expect(driver.readStateEvents).not.toBeCalled() + }); + + it('reads state events with a specific state key', async () => { + driver.readStateEvents.mockResolvedValue([ + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + ]) + + const event: IReadEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC2876ReadEvents, + data: { + type: 'net.example.test', + state_key: 'B', + }, + }; + + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#B']); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + events: [ + createRoomEvent({ type: 'net.example.test', state_key: 'B' }), + ], + }); + }); + + expect(driver.readStateEvents).toBeCalledWith( + 'net.example.test', 'B', 0, null, + ) + }); + + it('fails to read state events with a specific state key', async () => { + const event: IReadEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC2876ReadEvents, + data: { + type: 'net.example.test', + state_key: 'B', + }, + }; + + // Request the capability for the wrong state key + await loadIframe(['org.matrix.msc2762.receive.state_event:net.example.test#A']); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + error: { message: expect.any(String) }, + }); + }); + + expect(driver.readStateEvents).not.toBeCalled() + }); + }) + describe('org.matrix.msc3869.read_relations action', () => { it('should present as supported api version', () => { const event: ISupportedVersionsActionRequest = {