From aa77eab6376a5749c2e7b2c4f3b8ea38d604f219 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 17 Jul 2024 14:45:03 -0400 Subject: [PATCH 01/12] Replace future_group_id with parent_future_id --- src/ClientWidgetApi.ts | 13 +++++------- src/WidgetApi.ts | 12 +++++------ src/driver/WidgetDriver.ts | 11 ++++------ src/interfaces/SendEventAction.ts | 7 ++----- test/ClientWidgetApi-test.ts | 34 +++++++++++-------------------- 5 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 7415bef..38512d8 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -485,7 +485,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) { + if (request.data.future_timeout === undefined && request.data.parent_future_id === undefined) { sendEventPromise = this.driver.sendEvent( request.data.type, request.data.content || {}, @@ -495,7 +495,7 @@ export class ClientWidgetApi extends EventEmitter { } else { sendEventPromise = this.driver.sendFuture( request.data.future_timeout ?? null, - request.data.future_group_id ?? null, + request.data.parent_future_id ?? null, request.data.type, request.data.content || {}, request.data.state_key, @@ -511,7 +511,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) { + if (request.data.future_timeout === undefined && request.data.parent_future_id === undefined) { sendEventPromise = this.driver.sendEvent( request.data.type, content, @@ -521,7 +521,7 @@ export class ClientWidgetApi extends EventEmitter { } else { sendEventPromise = this.driver.sendFuture( request.data.future_timeout ?? null, - request.data.future_group_id ?? null, + request.data.parent_future_id ?? null, request.data.type, content, null, // not sending a state event @@ -536,10 +536,7 @@ export class ClientWidgetApi extends EventEmitter { ...("eventId" in sentEvent ? { event_id: sentEvent.eventId, } : { - future_group_id: sentEvent.futureGroupId, - send_token: sentEvent.sendToken, - cancel_token: sentEvent.cancelToken, - ...("refreshToken" in sentEvent && { refresh_token: sentEvent.refreshToken }), + future_id: sentEvent.futureId, }), }); }).catch(e => { diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index 807dc40..7f4b755 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -401,9 +401,9 @@ export class WidgetApi extends EventEmitter { content: unknown, roomId?: string, futureTimeout?: number, - futureGroupId?: string, + parentFutureId?: string, ): Promise { - return this.sendEvent(eventType, undefined, content, roomId, futureTimeout, futureGroupId); + return this.sendEvent(eventType, undefined, content, roomId, futureTimeout, parentFutureId); } public sendStateEvent( @@ -412,9 +412,9 @@ export class WidgetApi extends EventEmitter { content: unknown, roomId?: string, futureTimeout?: number, - futureGroupId?: string, + parentFutureId?: string, ): Promise { - return this.sendEvent(eventType, stateKey, content, roomId, futureTimeout, futureGroupId); + return this.sendEvent(eventType, stateKey, content, roomId, futureTimeout, parentFutureId); } private sendEvent( @@ -423,7 +423,7 @@ export class WidgetApi extends EventEmitter { content: unknown, roomId?: string, futureTimeout?: number, - futureGroupId?: string, + parentFutureId?: string, ): Promise { return this.transport.send( WidgetApiFromWidgetAction.SendEvent, @@ -433,7 +433,7 @@ export class WidgetApi extends EventEmitter { ...(stateKey !== undefined && { state_key: stateKey }), ...(roomId !== undefined && { room_id: roomId }), ...(futureTimeout !== undefined && { future_timeout: futureTimeout }), - ...(futureGroupId !== undefined && { future_group_id: futureGroupId }), + ...(parentFutureId !== undefined && { parent_future_id: parentFutureId }), }, ); } diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index f847bdf..8f007e8 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -31,10 +31,7 @@ export interface ISendEventDetails { export interface ISendFutureDetails { roomId: string; - futureGroupId: string; - sendToken: string; - cancelToken: string; - refreshToken?: string; + futureId: string; } export interface IOpenIDUpdate { @@ -117,8 +114,8 @@ export abstract class WidgetDriver { * into the room the user is currently looking at. The widget API will have already * verified that the widget is capable of sending the future's event to that room. * @param {number|null} futureTimeout The future's timeout, or null for an action future. - * May not be null if {@link futureGroupId} is null. - * @param {string|null} futureGroupId The ID of the group the future belongs to, + * May not be null if {@link parentFutureId} is null. + * @param {string|null} parentFutureId The ID of the future this one is grouped with, * or null if it will be put in a new group. May not be null if {@link futureTimeout} is null. * @param {string} eventType The event type of the event to be sent by the future. * @param {*} content The content for the event to be sent by the future. @@ -132,7 +129,7 @@ export abstract class WidgetDriver { */ public sendFuture( futureTimeout: number | null, - futureGroupId: string | null, + parentFutureId: string | null, eventType: string, content: unknown, stateKey: string | null = null, diff --git a/src/interfaces/SendEventAction.ts b/src/interfaces/SendEventAction.ts index aa3d1df..3638ea6 100644 --- a/src/interfaces/SendEventAction.ts +++ b/src/interfaces/SendEventAction.ts @@ -27,7 +27,7 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData { // MSC4157: Futures future_timeout?: number; // eslint-disable-line camelcase - future_group_id?: string; // eslint-disable-line camelcase + parent_future_id?: string; // eslint-disable-line camelcase } export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest { @@ -40,10 +40,7 @@ export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData event_id?: string; // eslint-disable-line camelcase // MSC4157: Futures - future_group_id?: string; // eslint-disable-line camelcase - send_token?: string; // eslint-disable-line camelcase - cancel_token?: string; // eslint-disable-line camelcase - refresh_token?: string; // eslint-disable-line camelcase + future_id?: string; // eslint-disable-line camelcase } export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidgetActionRequest { diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index c630286..929fde5 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -211,14 +211,12 @@ describe('ClientWidgetApi', () => { describe('send_event action for futures', () => { it('sends message futures', async () => { const roomId = '!room:example.org'; - const futureGroupId = 'fg'; + const parentFutureId = 'fp'; + const timeoutFutureId = 'ft'; driver.sendFuture.mockResolvedValue({ roomId, - futureGroupId, - sendToken: 'st', - cancelToken: 'ct', - refreshToken: 'rt', + futureId: timeoutFutureId, }); const event: ISendEventFromWidgetActionRequest = { @@ -231,7 +229,7 @@ describe('ClientWidgetApi', () => { content: {}, room_id: roomId, future_timeout: 5000, - future_group_id: futureGroupId, + parent_future_id: parentFutureId, }, }; @@ -245,16 +243,13 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { room_id: roomId, - future_group_id: futureGroupId, - send_token: 'st', - cancel_token: 'ct', - refresh_token: 'rt', + future_id: timeoutFutureId, }); }); expect(driver.sendFuture).toHaveBeenCalledWith( event.data.future_timeout, - event.data.future_group_id, + event.data.parent_future_id, event.data.type, event.data.content, null, @@ -264,14 +259,12 @@ describe('ClientWidgetApi', () => { it('sends state futures', async () => { const roomId = '!room:example.org'; - const futureGroupId = 'fg'; + const parentFutureId = 'fp'; + const timeoutFutureId = 'ft'; driver.sendFuture.mockResolvedValue({ roomId, - futureGroupId, - sendToken: 'st', - cancelToken: 'ct', - refreshToken: 'rt', + futureId: timeoutFutureId, }); const event: ISendEventFromWidgetActionRequest = { @@ -285,7 +278,7 @@ describe('ClientWidgetApi', () => { state_key: '', room_id: roomId, future_timeout: 5000, - future_group_id: futureGroupId, + parent_future_id: parentFutureId, }, }; @@ -299,16 +292,13 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { room_id: roomId, - future_group_id: futureGroupId, - send_token: 'st', - cancel_token: 'ct', - refresh_token: 'rt', + future_id: timeoutFutureId, }); }); expect(driver.sendFuture).toHaveBeenCalledWith( event.data.future_timeout, - event.data.future_group_id, + event.data.parent_future_id, event.data.type, event.data.content, '', From 27ed96b88348cc47054c086857232da1f2b075a9 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 25 Jul 2024 08:33:30 -0400 Subject: [PATCH 02/12] Rename some fields timeout to delay & futureId to delayId --- src/ClientWidgetApi.ts | 14 +++++++------- src/WidgetApi.ts | 20 +++++++++---------- src/driver/WidgetDriver.ts | 14 +++++++------- src/interfaces/SendEventAction.ts | 6 +++--- test/ClientWidgetApi-test.ts | 32 +++++++++++++++---------------- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 38512d8..cd60ab9 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -485,7 +485,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.future_timeout === undefined && request.data.parent_future_id === undefined) { + if (request.data.delay === undefined && request.data.parent_delay_id === undefined) { sendEventPromise = this.driver.sendEvent( request.data.type, request.data.content || {}, @@ -494,8 +494,8 @@ export class ClientWidgetApi extends EventEmitter { ); } else { sendEventPromise = this.driver.sendFuture( - request.data.future_timeout ?? null, - request.data.parent_future_id ?? null, + request.data.delay ?? null, + request.data.parent_delay_id ?? null, request.data.type, request.data.content || {}, request.data.state_key, @@ -511,7 +511,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.future_timeout === undefined && request.data.parent_future_id === undefined) { + if (request.data.delay === undefined && request.data.parent_delay_id === undefined) { sendEventPromise = this.driver.sendEvent( request.data.type, content, @@ -520,8 +520,8 @@ export class ClientWidgetApi extends EventEmitter { ); } else { sendEventPromise = this.driver.sendFuture( - request.data.future_timeout ?? null, - request.data.parent_future_id ?? null, + request.data.delay ?? null, + request.data.parent_delay_id ?? null, request.data.type, content, null, // not sending a state event @@ -536,7 +536,7 @@ export class ClientWidgetApi extends EventEmitter { ...("eventId" in sentEvent ? { event_id: sentEvent.eventId, } : { - future_id: sentEvent.futureId, + delay_id: sentEvent.delayId, }), }); }).catch(e => { diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index 7f4b755..934d3d9 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -400,10 +400,10 @@ export class WidgetApi extends EventEmitter { eventType: string, content: unknown, roomId?: string, - futureTimeout?: number, - parentFutureId?: string, + delay?: number, + parentDelayId?: string, ): Promise { - return this.sendEvent(eventType, undefined, content, roomId, futureTimeout, parentFutureId); + return this.sendEvent(eventType, undefined, content, roomId, delay, parentDelayId); } public sendStateEvent( @@ -411,10 +411,10 @@ export class WidgetApi extends EventEmitter { stateKey: string, content: unknown, roomId?: string, - futureTimeout?: number, - parentFutureId?: string, + delay?: number, + parentDelayId?: string, ): Promise { - return this.sendEvent(eventType, stateKey, content, roomId, futureTimeout, parentFutureId); + return this.sendEvent(eventType, stateKey, content, roomId, delay, parentDelayId); } private sendEvent( @@ -422,8 +422,8 @@ export class WidgetApi extends EventEmitter { stateKey: string | undefined, content: unknown, roomId?: string, - futureTimeout?: number, - parentFutureId?: string, + delay?: number, + parentDelayId?: string, ): Promise { return this.transport.send( WidgetApiFromWidgetAction.SendEvent, @@ -432,8 +432,8 @@ export class WidgetApi extends EventEmitter { content, ...(stateKey !== undefined && { state_key: stateKey }), ...(roomId !== undefined && { room_id: roomId }), - ...(futureTimeout !== undefined && { future_timeout: futureTimeout }), - ...(parentFutureId !== undefined && { parent_future_id: parentFutureId }), + ...(delay !== undefined && { delay }), + ...(parentDelayId !== undefined && { parent_delay_id: parentDelayId }), }, ); } diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index 8f007e8..9b92a0d 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -31,7 +31,7 @@ export interface ISendEventDetails { export interface ISendFutureDetails { roomId: string; - futureId: string; + delayId: string; } export interface IOpenIDUpdate { @@ -113,10 +113,10 @@ export abstract class WidgetDriver { * Sends a future into a room. If `roomId` is falsy, the client should send the future * into the room the user is currently looking at. The widget API will have already * verified that the widget is capable of sending the future's event to that room. - * @param {number|null} futureTimeout The future's timeout, or null for an action future. - * May not be null if {@link parentFutureId} is null. - * @param {string|null} parentFutureId The ID of the future this one is grouped with, - * or null if it will be put in a new group. May not be null if {@link futureTimeout} is null. + * @param {number|null} delay The future's timeout, or null for an action future. + * May not be null if {@link parentDelayId} is null. + * @param {string|null} parentDelayId The ID of the future this one is grouped with, + * or null if it will be put in a new group. May not be null if {@link delay} is null. * @param {string} eventType The event type of the event to be sent by the future. * @param {*} content The content for the event to be sent by the future. * @param {string|null} stateKey The state key if the event to be sent by the future is @@ -128,8 +128,8 @@ export abstract class WidgetDriver { * @throws Rejected when the future could not be sent. */ public sendFuture( - futureTimeout: number | null, - parentFutureId: string | null, + delay: number | null, + parentDelayId: string | null, eventType: string, content: unknown, stateKey: string | null = null, diff --git a/src/interfaces/SendEventAction.ts b/src/interfaces/SendEventAction.ts index 3638ea6..c82ee93 100644 --- a/src/interfaces/SendEventAction.ts +++ b/src/interfaces/SendEventAction.ts @@ -26,8 +26,8 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData { room_id?: string; // eslint-disable-line camelcase // MSC4157: Futures - future_timeout?: number; // eslint-disable-line camelcase - parent_future_id?: string; // eslint-disable-line camelcase + delay?: number; // eslint-disable-line camelcase + parent_delay_id?: string; // eslint-disable-line camelcase } export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest { @@ -40,7 +40,7 @@ export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData event_id?: string; // eslint-disable-line camelcase // MSC4157: Futures - future_id?: string; // eslint-disable-line camelcase + delay_id?: string; // eslint-disable-line camelcase } export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidgetActionRequest { diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 929fde5..2897473 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -211,12 +211,12 @@ describe('ClientWidgetApi', () => { describe('send_event action for futures', () => { it('sends message futures', async () => { const roomId = '!room:example.org'; - const parentFutureId = 'fp'; - const timeoutFutureId = 'ft'; + const parentDelayId = 'fp'; + const timeoutDelayId = 'ft'; driver.sendFuture.mockResolvedValue({ roomId, - futureId: timeoutFutureId, + delayId: timeoutDelayId, }); const event: ISendEventFromWidgetActionRequest = { @@ -228,8 +228,8 @@ describe('ClientWidgetApi', () => { type: 'm.room.message', content: {}, room_id: roomId, - future_timeout: 5000, - parent_future_id: parentFutureId, + delay: 5000, + parent_delay_id: parentDelayId, }, }; @@ -243,13 +243,13 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { room_id: roomId, - future_id: timeoutFutureId, + delay_id: timeoutDelayId, }); }); expect(driver.sendFuture).toHaveBeenCalledWith( - event.data.future_timeout, - event.data.parent_future_id, + event.data.delay, + event.data.parent_delay_id, event.data.type, event.data.content, null, @@ -259,12 +259,12 @@ describe('ClientWidgetApi', () => { it('sends state futures', async () => { const roomId = '!room:example.org'; - const parentFutureId = 'fp'; - const timeoutFutureId = 'ft'; + const parentDelayId = 'fp'; + const timeoutDelayId = 'ft'; driver.sendFuture.mockResolvedValue({ roomId, - futureId: timeoutFutureId, + delayId: timeoutDelayId, }); const event: ISendEventFromWidgetActionRequest = { @@ -277,8 +277,8 @@ describe('ClientWidgetApi', () => { content: {}, state_key: '', room_id: roomId, - future_timeout: 5000, - parent_future_id: parentFutureId, + delay: 5000, + parent_delay_id: parentDelayId, }, }; @@ -292,13 +292,13 @@ describe('ClientWidgetApi', () => { await waitFor(() => { expect(transport.reply).toHaveBeenCalledWith(event, { room_id: roomId, - future_id: timeoutFutureId, + delay_id: timeoutDelayId, }); }); expect(driver.sendFuture).toHaveBeenCalledWith( - event.data.future_timeout, - event.data.parent_future_id, + event.data.delay, + event.data.parent_delay_id, event.data.type, event.data.content, '', From 4da29528159b23303e0a66b9415b4a57b45bc002 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 25 Jul 2024 09:59:57 -0400 Subject: [PATCH 03/12] Rename Future methods & docstrings --- src/ClientWidgetApi.ts | 8 ++++---- src/driver/WidgetDriver.ts | 32 +++++++++++++++---------------- src/interfaces/SendEventAction.ts | 4 ++-- test/ClientWidgetApi-test.ts | 16 ++++++++-------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index cd60ab9..f16e6dd 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -24,7 +24,7 @@ import { IContentLoadedActionRequest } from "./interfaces/ContentLoadedAction"; import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction"; import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse"; import { Capability, MatrixCapabilities } from "./interfaces/Capabilities"; -import { IOpenIDUpdate, ISendEventDetails, ISendFutureDetails, WidgetDriver } from "./driver/WidgetDriver"; +import { IOpenIDUpdate, ISendEventDetails, ISendDelayedEventDetails, WidgetDriver } from "./driver/WidgetDriver"; import { ICapabilitiesActionResponseData, INotifyCapabilitiesActionRequestData, @@ -477,7 +477,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - let sendEventPromise: Promise; + let sendEventPromise: Promise; if (request.data.state_key !== undefined) { if (!this.canSendStateEvent(request.data.type, request.data.state_key)) { return this.transport.reply(request, { @@ -493,7 +493,7 @@ export class ClientWidgetApi extends EventEmitter { request.data.room_id, ); } else { - sendEventPromise = this.driver.sendFuture( + sendEventPromise = this.driver.sendDelayedEvent( request.data.delay ?? null, request.data.parent_delay_id ?? null, request.data.type, @@ -519,7 +519,7 @@ export class ClientWidgetApi extends EventEmitter { request.data.room_id, ); } else { - sendEventPromise = this.driver.sendFuture( + sendEventPromise = this.driver.sendDelayedEvent( request.data.delay ?? null, request.data.parent_delay_id ?? null, request.data.type, diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index 9b92a0d..78c2c9c 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -29,7 +29,7 @@ export interface ISendEventDetails { eventId: string; } -export interface ISendFutureDetails { +export interface ISendDelayedEventDetails { roomId: string; delayId: string; } @@ -110,31 +110,31 @@ export abstract class WidgetDriver { /** * @experimental Part of MSC4140 & MSC4157 - * Sends a future into a room. If `roomId` is falsy, the client should send the future + * Sends a delayed event into a room. If `roomId` is falsy, the client should send it * into the room the user is currently looking at. The widget API will have already - * verified that the widget is capable of sending the future's event to that room. - * @param {number|null} delay The future's timeout, or null for an action future. - * May not be null if {@link parentDelayId} is null. - * @param {string|null} parentDelayId The ID of the future this one is grouped with, + * verified that the widget is capable of sending the event to that room. + * @param {number|null} delay How much later to send the event, or null to not send the + * event automatically. May not be null if {@link parentDelayId} is null. + * @param {string|null} parentDelayId The ID of the delayed event this one is grouped with, * or null if it will be put in a new group. May not be null if {@link delay} is null. - * @param {string} eventType The event type of the event to be sent by the future. - * @param {*} content The content for the event to be sent by the future. - * @param {string|null} stateKey The state key if the event to be sent by the future is - * a state event, otherwise null. May be an empty string. - * @param {string|null} roomId The room ID to send the future to. If falsy, the room the + * @param {string} eventType The event type of the event to be sent. + * @param {*} content The content for the event to be sent. + * @param {string|null} stateKey The state key if the event to be sent a state event, + * otherwise null. May be an empty string. + * @param {string|null} roomId The room ID to send the event to. If falsy, the room the * user is currently looking at. - * @returns {Promise} Resolves when the future has been sent with - * details of that future. - * @throws Rejected when the future could not be sent. + * @returns {Promise} Resolves when the delayed event has been + * prepared with details of how to refer to it for updating/sending/canceling it later. + * @throws Rejected when the delayed event could not be sent. */ - public sendFuture( + public sendDelayedEvent( delay: number | null, parentDelayId: string | null, eventType: string, content: unknown, stateKey: string | null = null, roomId: string | null = null, - ): Promise { + ): Promise { return Promise.reject(new Error("Failed to override function")); } diff --git a/src/interfaces/SendEventAction.ts b/src/interfaces/SendEventAction.ts index c82ee93..45963ad 100644 --- a/src/interfaces/SendEventAction.ts +++ b/src/interfaces/SendEventAction.ts @@ -25,7 +25,7 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData { content: unknown; room_id?: string; // eslint-disable-line camelcase - // MSC4157: Futures + // MSC4157 delay?: number; // eslint-disable-line camelcase parent_delay_id?: string; // eslint-disable-line camelcase } @@ -39,7 +39,7 @@ export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData room_id: string; // eslint-disable-line camelcase event_id?: string; // eslint-disable-line camelcase - // MSC4157: Futures + // MSC4157 delay_id?: string; // eslint-disable-line camelcase } diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 2897473..d20a8bb 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -80,7 +80,7 @@ describe('ClientWidgetApi', () => { readStateEvents: jest.fn(), readEventRelations: jest.fn(), sendEvent: jest.fn(), - sendFuture: jest.fn(), + sendDelayedEvent: jest.fn(), validateCapabilities: jest.fn(), searchUserDirectory: jest.fn(), getMediaConfig: jest.fn(), @@ -208,13 +208,13 @@ describe('ClientWidgetApi', () => { }); }); - describe('send_event action for futures', () => { - it('sends message futures', async () => { + describe('send_event action for delayed events', () => { + it('sends message events', async () => { const roomId = '!room:example.org'; const parentDelayId = 'fp'; const timeoutDelayId = 'ft'; - driver.sendFuture.mockResolvedValue({ + driver.sendDelayedEvent.mockResolvedValue({ roomId, delayId: timeoutDelayId, }); @@ -247,7 +247,7 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendFuture).toHaveBeenCalledWith( + expect(driver.sendDelayedEvent).toHaveBeenCalledWith( event.data.delay, event.data.parent_delay_id, event.data.type, @@ -257,12 +257,12 @@ describe('ClientWidgetApi', () => { ); }); - it('sends state futures', async () => { + it('sends state events', async () => { const roomId = '!room:example.org'; const parentDelayId = 'fp'; const timeoutDelayId = 'ft'; - driver.sendFuture.mockResolvedValue({ + driver.sendDelayedEvent.mockResolvedValue({ roomId, delayId: timeoutDelayId, }); @@ -296,7 +296,7 @@ describe('ClientWidgetApi', () => { }); }); - expect(driver.sendFuture).toHaveBeenCalledWith( + expect(driver.sendDelayedEvent).toHaveBeenCalledWith( event.data.delay, event.data.parent_delay_id, event.data.type, From 00e1fdd5dde81f51077e79a9c65e728a8dd50e13 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 25 Jul 2024 10:55:06 -0400 Subject: [PATCH 04/12] Require capability for sending delayed events --- src/ClientWidgetApi.ts | 11 +++++++++-- src/interfaces/Capabilities.ts | 4 ++++ test/ClientWidgetApi-test.ts | 36 ++++++++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index f16e6dd..ca27b66 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -477,6 +477,13 @@ export class ClientWidgetApi extends EventEmitter { }); } + const isDelayedEvent = request.data.delay === undefined && request.data.parent_delay_id === undefined; + if (isDelayedEvent && !this.hasCapability(MatrixCapabilities.MSC4157SendDelayedEvent)) { + return this.transport.reply(request, { + error: {message: "Missing capability"}, + }); + } + let sendEventPromise: Promise; if (request.data.state_key !== undefined) { if (!this.canSendStateEvent(request.data.type, request.data.state_key)) { @@ -485,7 +492,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.delay === undefined && request.data.parent_delay_id === undefined) { + if (!isDelayedEvent) { sendEventPromise = this.driver.sendEvent( request.data.type, request.data.content || {}, @@ -511,7 +518,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - if (request.data.delay === undefined && request.data.parent_delay_id === undefined) { + if (!isDelayedEvent) { sendEventPromise = this.driver.sendEvent( request.data.type, content, diff --git a/src/interfaces/Capabilities.ts b/src/interfaces/Capabilities.ts index ab49341..a6b9983 100644 --- a/src/interfaces/Capabilities.ts +++ b/src/interfaces/Capabilities.ts @@ -38,6 +38,10 @@ export enum MatrixCapabilities { * @deprecated It is not recommended to rely on this existing - it can be removed without notice. */ MSC4039UploadFile = "org.matrix.msc4039.upload_file", + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC4157SendDelayedEvent = "org.matrix.msc4157.send.delayed_event", } export type Capability = MatrixCapabilities | string; diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index d20a8bb..5276a18 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -209,7 +209,37 @@ describe('ClientWidgetApi', () => { }); describe('send_event action for delayed events', () => { - it('sends message events', async () => { + it('fails to send delayed events', async () => { + const event: ISendEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.SendEvent, + data: { + type: 'm.room.message', + content: {}, + delay: 5000, + }, + }; + + await loadIframe([ + `org.matrix.msc2762.timeline:${event.data.room_id}`, + `org.matrix.msc2762.send.event:${event.data.type}`, + // Without the required capability + ]); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + error: { message: expect.any(String) }, + }); + }); + + expect(driver.sendDelayedEvent).not.toBeCalled() + }); + + it('sends delayed message events', async () => { const roomId = '!room:example.org'; const parentDelayId = 'fp'; const timeoutDelayId = 'ft'; @@ -236,6 +266,7 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.event:${event.data.type}`, + 'org.matrix.msc4157.send.delayed_event', ]); emitEvent(new CustomEvent('', { detail: event })); @@ -257,7 +288,7 @@ describe('ClientWidgetApi', () => { ); }); - it('sends state events', async () => { + it('sends delayed state events', async () => { const roomId = '!room:example.org'; const parentDelayId = 'fp'; const timeoutDelayId = 'ft'; @@ -285,6 +316,7 @@ describe('ClientWidgetApi', () => { await loadIframe([ `org.matrix.msc2762.timeline:${event.data.room_id}`, `org.matrix.msc2762.send.state_event:${event.data.type}`, + 'org.matrix.msc4157.send.delayed_event', ]); emitEvent(new CustomEvent('', { detail: event })); From 7defbe134575de4ad00eb132e72fe7f0fa3f7246 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 25 Jul 2024 11:54:29 -0400 Subject: [PATCH 05/12] Add action & capability to update delayed events --- src/ClientWidgetApi.ts | 42 ++++++++++- src/driver/WidgetDriver.ts | 13 ++++ src/index.ts | 1 + src/interfaces/Capabilities.ts | 4 + src/interfaces/UpdateDelayedEventAction.ts | 43 +++++++++++ src/interfaces/WidgetApiAction.ts | 5 ++ test/ClientWidgetApi-test.ts | 87 +++++++++++++++++++++- 7 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/interfaces/UpdateDelayedEventAction.ts diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index ca27b66..784122b 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -90,6 +90,10 @@ import { IGetMediaConfigActionFromWidgetActionRequest, IGetMediaConfigActionFromWidgetResponseData, } from "./interfaces/GetMediaConfigAction"; +import { + IUpdateDelayedEventFromWidgetActionRequest, + UpdateDelayedEventActionName, +} from "./interfaces/UpdateDelayedEventAction"; import { IUploadFileActionFromWidgetActionRequest, IUploadFileActionFromWidgetResponseData, @@ -477,7 +481,7 @@ export class ClientWidgetApi extends EventEmitter { }); } - const isDelayedEvent = request.data.delay === undefined && request.data.parent_delay_id === undefined; + const isDelayedEvent = request.data.delay !== undefined || request.data.parent_delay_id !== undefined; if (isDelayedEvent && !this.hasCapability(MatrixCapabilities.MSC4157SendDelayedEvent)) { return this.transport.reply(request, { error: {message: "Missing capability"}, @@ -554,6 +558,40 @@ export class ClientWidgetApi extends EventEmitter { }); } + private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) { + if (!request.data.delay_id) { + return this.transport.reply(request, { + error: {message: "Invalid request - missing delay_id"}, + }); + } + + switch (request.data.action) { + case UpdateDelayedEventActionName.Cancel: + case UpdateDelayedEventActionName.Send: + case UpdateDelayedEventActionName.Refresh: + break + default: + return this.transport.reply(request, { + error: {message: "Invalid request - unsupported action"}, + }); + } + + if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { + return this.transport.reply(request, { + error: {message: "Missing capability"}, + }); + } + + this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { + return this.transport.reply(request, {}); + }).catch(e => { + console.error("error updating delayed event: ", e); + return this.transport.reply(request, { + error: {message: "Error updating delayed event"}, + }); + }); + } + private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise { if (!request.data.type) { await this.transport.reply(request, { @@ -826,6 +864,8 @@ export class ClientWidgetApi extends EventEmitter { return this.handleGetMediaConfig(ev.detail); case WidgetApiFromWidgetAction.MSC4039UploadFileAction: return this.handleUploadFile(ev.detail); + case WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent: + return this.handleUpdateDelayedEvent(ev.detail); default: return this.transport.reply(ev.detail, { diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index 78c2c9c..b0e8199 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -22,6 +22,7 @@ import { IRoomEvent, IRoomAccountData, ITurnServer, + UpdateDelayedEventActionName, } from ".."; export interface ISendEventDetails { @@ -138,6 +139,18 @@ export abstract class WidgetDriver { return Promise.reject(new Error("Failed to override function")); } + /** + * @experimental Part of MSC4140 & MSC4157 + * Run the specified {@link action} for the delayed event matching the provided {@link delayId}. + * @throws Rejected when there is no matching delayed event, or when the action failed to run. + */ + public updateDelayedEvent( + delayId: string, + action: UpdateDelayedEventActionName | string, + ): Promise { + return Promise.reject(new Error("Failed to override function")); + } + /** * Sends a to-device event. The widget API will have already verified that the widget * is capable of sending the event. diff --git a/src/index.ts b/src/index.ts index 1a75a84..c66009c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,6 +59,7 @@ export * from "./interfaces/NavigateAction"; export * from "./interfaces/TurnServerActions"; export * from "./interfaces/ReadRelationsAction"; export * from "./interfaces/GetMediaConfigAction"; +export * from "./interfaces/UpdateDelayedEventAction"; export * from "./interfaces/UploadFileAction"; // Complex models diff --git a/src/interfaces/Capabilities.ts b/src/interfaces/Capabilities.ts index a6b9983..229a576 100644 --- a/src/interfaces/Capabilities.ts +++ b/src/interfaces/Capabilities.ts @@ -42,6 +42,10 @@ export enum MatrixCapabilities { * @deprecated It is not recommended to rely on this existing - it can be removed without notice. */ MSC4157SendDelayedEvent = "org.matrix.msc4157.send.delayed_event", + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update.delayed_event", } export type Capability = MatrixCapabilities | string; diff --git a/src/interfaces/UpdateDelayedEventAction.ts b/src/interfaces/UpdateDelayedEventAction.ts new file mode 100644 index 0000000..988443b --- /dev/null +++ b/src/interfaces/UpdateDelayedEventAction.ts @@ -0,0 +1,43 @@ +/* + * Copyright 2020 - 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; +import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; +import { IWidgetApiResponseData } from "./IWidgetApiResponse"; + +export enum UpdateDelayedEventActionName { + Cancel = "cancel", + Send = "send", + Refresh = "refresh", +} + +export interface IUpdateDelayedEventFromWidgetRequestData extends IWidgetApiRequestData { + delay_id: string; // eslint-disable-line camelcase + action: UpdateDelayedEventActionName | string; +} + +export interface IUpdateDelayedEventFromWidgetActionRequest extends IWidgetApiRequest { + action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent; + data: IUpdateDelayedEventFromWidgetRequestData; +} + +export interface IUpdateDelayedEventFromWidgetResponseData extends IWidgetApiResponseData { + // nothing +} + +export interface IUpdateDelayedEventFromWidgetActionResponse extends IUpdateDelayedEventFromWidgetActionRequest { + response: IUpdateDelayedEventFromWidgetResponseData; +} diff --git a/src/interfaces/WidgetApiAction.ts b/src/interfaces/WidgetApiAction.ts index de78e52..27345ce 100644 --- a/src/interfaces/WidgetApiAction.ts +++ b/src/interfaces/WidgetApiAction.ts @@ -79,6 +79,11 @@ export enum WidgetApiFromWidgetAction { * @deprecated It is not recommended to rely on this existing - it can be removed without notice. */ MSC4039UploadFileAction = "org.matrix.msc4039.upload_file", + + /** + * @deprecated It is not recommended to rely on this existing - it can be removed without notice. + */ + MSC4157UpdateDelayedEvent = "org.matrix.msc4157.update.delayed_event", } export type WidgetApiAction = WidgetApiToWidgetAction | WidgetApiFromWidgetAction | string; diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 5276a18..22b4d47 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -28,7 +28,11 @@ 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, ISendEventFromWidgetActionRequest } from '../src'; +import { + IReadEventFromWidgetActionRequest, + ISendEventFromWidgetActionRequest, + IUpdateDelayedEventFromWidgetActionRequest, +} from '../src'; import { IGetMediaConfigActionFromWidgetActionRequest } from '../src/interfaces/GetMediaConfigAction'; import { IUploadFileActionFromWidgetActionRequest } from '../src/interfaces/UploadFileAction'; @@ -81,6 +85,7 @@ describe('ClientWidgetApi', () => { readEventRelations: jest.fn(), sendEvent: jest.fn(), sendDelayedEvent: jest.fn(), + updateDelayedEvent: jest.fn(), validateCapabilities: jest.fn(), searchUserDirectory: jest.fn(), getMediaConfig: jest.fn(), @@ -339,6 +344,86 @@ describe('ClientWidgetApi', () => { }); }); + describe('update_delayed_event action', () => { + it('fails to update delayed events', async () => { + const event: IUpdateDelayedEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, + data: { + delay_id: 'f', + action: 'send', + }, + }; + + 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.updateDelayedEvent).not.toBeCalled() + }); + + it('fails to update delayed events with unsupported action', async () => { + const event: IUpdateDelayedEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, + data: { + delay_id: 'f', + action: 'unknown', + }, + }; + + await loadIframe(['org.matrix.msc4157.update.delayed_event']); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toBeCalledWith(event, { + error: { message: expect.any(String) }, + }); + }); + + expect(driver.updateDelayedEvent).not.toBeCalled() + }); + + it('updates delayed events', async () => { + driver.updateDelayedEvent.mockResolvedValue(undefined); + + const event: IUpdateDelayedEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, + data: { + delay_id: 'f', + action: 'send', + }, + }; + + await loadIframe(['org.matrix.msc4157.update.delayed_event']); + + emitEvent(new CustomEvent('', { detail: event })); + + await waitFor(() => { + expect(transport.reply).toHaveBeenCalledWith(event, {}); + }); + + expect(driver.updateDelayedEvent).toHaveBeenCalledWith( + event.data.delay_id, + event.data.action, + ); + }); + }); + describe('org.matrix.msc2876.read_events action', () => { it('reads state events with any state key', async () => { driver.readStateEvents.mockResolvedValue([ From 05b13add47cad74be32158428bb0a4827dd9d28a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 25 Jul 2024 13:48:43 -0400 Subject: [PATCH 06/12] Order delayed event actions alphabetically --- src/ClientWidgetApi.ts | 2 +- src/interfaces/UpdateDelayedEventAction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 784122b..0bce564 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -567,8 +567,8 @@ export class ClientWidgetApi extends EventEmitter { switch (request.data.action) { case UpdateDelayedEventActionName.Cancel: - case UpdateDelayedEventActionName.Send: case UpdateDelayedEventActionName.Refresh: + case UpdateDelayedEventActionName.Send: break default: return this.transport.reply(request, { diff --git a/src/interfaces/UpdateDelayedEventAction.ts b/src/interfaces/UpdateDelayedEventAction.ts index 988443b..8e2e05d 100644 --- a/src/interfaces/UpdateDelayedEventAction.ts +++ b/src/interfaces/UpdateDelayedEventAction.ts @@ -20,8 +20,8 @@ import { IWidgetApiResponseData } from "./IWidgetApiResponse"; export enum UpdateDelayedEventActionName { Cancel = "cancel", - Send = "send", Refresh = "refresh", + Send = "send", } export interface IUpdateDelayedEventFromWidgetRequestData extends IWidgetApiRequestData { From 9ce0402fb04d87b4043987f6b007bb6596411749 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 26 Jul 2024 09:29:18 -0400 Subject: [PATCH 07/12] Rename "refresh" action to "restart" --- src/ClientWidgetApi.ts | 2 +- src/interfaces/UpdateDelayedEventAction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 0bce564..99609ab 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -567,7 +567,7 @@ export class ClientWidgetApi extends EventEmitter { switch (request.data.action) { case UpdateDelayedEventActionName.Cancel: - case UpdateDelayedEventActionName.Refresh: + case UpdateDelayedEventActionName.Restart: case UpdateDelayedEventActionName.Send: break default: diff --git a/src/interfaces/UpdateDelayedEventAction.ts b/src/interfaces/UpdateDelayedEventAction.ts index 8e2e05d..eb62697 100644 --- a/src/interfaces/UpdateDelayedEventAction.ts +++ b/src/interfaces/UpdateDelayedEventAction.ts @@ -20,7 +20,7 @@ import { IWidgetApiResponseData } from "./IWidgetApiResponse"; export enum UpdateDelayedEventActionName { Cancel = "cancel", - Refresh = "refresh", + Restart = "restart", Send = "send", } From 363ccdd717ef9a9b2036310839aca35973c4bc19 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 26 Jul 2024 12:12:20 -0400 Subject: [PATCH 08/12] Support updateDelayedEvent in widget API --- src/WidgetApi.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index 934d3d9..feda8ee 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -85,6 +85,10 @@ import { IUploadFileActionFromWidgetRequestData, IUploadFileActionFromWidgetResponseData, } from "./interfaces/UploadFileAction"; +import { + IUpdateDelayedEventFromWidgetRequestData, + IUpdateDelayedEventFromWidgetResponseData, +} from "./interfaces/UpdateDelayedEventAction"; /** * API handler for widgets. This raises events for each action @@ -438,6 +442,22 @@ export class WidgetApi extends EventEmitter { ); } + /** + * @deprecated This currently relies on an unstable MSC (MSC4157). + */ + public updateDelayedEvent( + delayId: string, + action: string, + ): Promise { + return this.transport.send( + WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, + { + delay_id: delayId, + action, + }, + ); + } + /** * Sends a to-device event. * @param {string} eventType The type of events being sent. From 7d6fef7092f14be1aede5f49130591322f0aec31 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 26 Jul 2024 13:05:10 -0400 Subject: [PATCH 09/12] Make delayed event update actions more strict Use type checks to disallow arbitrary string values as actions --- src/ClientWidgetApi.ts | 8 ++-- src/WidgetApi.ts | 3 +- src/driver/WidgetDriver.ts | 4 +- src/interfaces/UpdateDelayedEventAction.ts | 4 +- test/ClientWidgetApi-test.ts | 49 ++++++++++++---------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 99609ab..03c2d94 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -92,7 +92,7 @@ import { } from "./interfaces/GetMediaConfigAction"; import { IUpdateDelayedEventFromWidgetActionRequest, - UpdateDelayedEventActionName, + UpdateDelayedEventAction, } from "./interfaces/UpdateDelayedEventAction"; import { IUploadFileActionFromWidgetActionRequest, @@ -566,9 +566,9 @@ export class ClientWidgetApi extends EventEmitter { } switch (request.data.action) { - case UpdateDelayedEventActionName.Cancel: - case UpdateDelayedEventActionName.Restart: - case UpdateDelayedEventActionName.Send: + case UpdateDelayedEventAction.Cancel: + case UpdateDelayedEventAction.Restart: + case UpdateDelayedEventAction.Send: break default: return this.transport.reply(request, { diff --git a/src/WidgetApi.ts b/src/WidgetApi.ts index feda8ee..c5fa2c1 100644 --- a/src/WidgetApi.ts +++ b/src/WidgetApi.ts @@ -88,6 +88,7 @@ import { import { IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData, + UpdateDelayedEventAction, } from "./interfaces/UpdateDelayedEventAction"; /** @@ -447,7 +448,7 @@ export class WidgetApi extends EventEmitter { */ public updateDelayedEvent( delayId: string, - action: string, + action: UpdateDelayedEventAction, ): Promise { return this.transport.send( WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, diff --git a/src/driver/WidgetDriver.ts b/src/driver/WidgetDriver.ts index b0e8199..dd89ea9 100644 --- a/src/driver/WidgetDriver.ts +++ b/src/driver/WidgetDriver.ts @@ -22,7 +22,7 @@ import { IRoomEvent, IRoomAccountData, ITurnServer, - UpdateDelayedEventActionName, + UpdateDelayedEventAction, } from ".."; export interface ISendEventDetails { @@ -146,7 +146,7 @@ export abstract class WidgetDriver { */ public updateDelayedEvent( delayId: string, - action: UpdateDelayedEventActionName | string, + action: UpdateDelayedEventAction, ): Promise { return Promise.reject(new Error("Failed to override function")); } diff --git a/src/interfaces/UpdateDelayedEventAction.ts b/src/interfaces/UpdateDelayedEventAction.ts index eb62697..9ba0179 100644 --- a/src/interfaces/UpdateDelayedEventAction.ts +++ b/src/interfaces/UpdateDelayedEventAction.ts @@ -18,7 +18,7 @@ import { IWidgetApiRequest, IWidgetApiRequestData } from "./IWidgetApiRequest"; import { WidgetApiFromWidgetAction } from "./WidgetApiAction"; import { IWidgetApiResponseData } from "./IWidgetApiResponse"; -export enum UpdateDelayedEventActionName { +export enum UpdateDelayedEventAction { Cancel = "cancel", Restart = "restart", Send = "send", @@ -26,7 +26,7 @@ export enum UpdateDelayedEventActionName { export interface IUpdateDelayedEventFromWidgetRequestData extends IWidgetApiRequestData { delay_id: string; // eslint-disable-line camelcase - action: UpdateDelayedEventActionName | string; + action: UpdateDelayedEventAction; } export interface IUpdateDelayedEventFromWidgetActionRequest extends IWidgetApiRequest { diff --git a/test/ClientWidgetApi-test.ts b/test/ClientWidgetApi-test.ts index 22b4d47..57a8465 100644 --- a/test/ClientWidgetApi-test.ts +++ b/test/ClientWidgetApi-test.ts @@ -32,6 +32,7 @@ import { IReadEventFromWidgetActionRequest, ISendEventFromWidgetActionRequest, IUpdateDelayedEventFromWidgetActionRequest, + UpdateDelayedEventAction, } from '../src'; import { IGetMediaConfigActionFromWidgetActionRequest } from '../src/interfaces/GetMediaConfigAction'; import { IUploadFileActionFromWidgetActionRequest } from '../src/interfaces/UploadFileAction'; @@ -353,7 +354,7 @@ describe('ClientWidgetApi', () => { action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { delay_id: 'f', - action: 'send', + action: UpdateDelayedEventAction.Send, }, }; @@ -378,7 +379,7 @@ describe('ClientWidgetApi', () => { action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, data: { delay_id: 'f', - action: 'unknown', + action: 'unknown' as UpdateDelayedEventAction, }, }; @@ -398,29 +399,35 @@ describe('ClientWidgetApi', () => { it('updates delayed events', async () => { driver.updateDelayedEvent.mockResolvedValue(undefined); - const event: IUpdateDelayedEventFromWidgetActionRequest = { - api: WidgetApiDirection.FromWidget, - widgetId: 'test', - requestId: '0', - action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, - data: { - delay_id: 'f', - action: 'send', - }, - }; + for (const action of [ + UpdateDelayedEventAction.Cancel, + UpdateDelayedEventAction.Restart, + UpdateDelayedEventAction.Send, + ]) { + const event: IUpdateDelayedEventFromWidgetActionRequest = { + api: WidgetApiDirection.FromWidget, + widgetId: 'test', + requestId: '0', + action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent, + data: { + delay_id: 'f', + action, + }, + }; - await loadIframe(['org.matrix.msc4157.update.delayed_event']); + await loadIframe(['org.matrix.msc4157.update.delayed_event']); - emitEvent(new CustomEvent('', { detail: event })); + emitEvent(new CustomEvent('', { detail: event })); - await waitFor(() => { - expect(transport.reply).toHaveBeenCalledWith(event, {}); - }); + await waitFor(() => { + expect(transport.reply).toHaveBeenCalledWith(event, {}); + }); - expect(driver.updateDelayedEvent).toHaveBeenCalledWith( - event.data.delay_id, - event.data.action, - ); + expect(driver.updateDelayedEvent).toHaveBeenCalledWith( + event.data.delay_id, + event.data.action, + ); + } }); }); From eba940bacb669d0fe03bb4ef54544754ecfc0ed0 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 26 Jul 2024 13:06:50 -0400 Subject: [PATCH 10/12] Test event sending from widget API --- test/WidgetApi-test.ts | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/WidgetApi-test.ts b/test/WidgetApi-test.ts index 38c1ece..e236606 100644 --- a/test/WidgetApi-test.ts +++ b/test/WidgetApi-test.ts @@ -17,6 +17,7 @@ import { UnstableApiVersion } from '../src/interfaces/ApiVersion'; import { IGetMediaConfigActionFromWidgetResponseData } from '../src/interfaces/GetMediaConfigAction'; import { IReadRelationsFromWidgetResponseData } from '../src/interfaces/ReadRelationsAction'; +import { ISendEventFromWidgetResponseData } from '../src/interfaces/SendEventAction'; import { ISupportedVersionsActionResponseData } from '../src/interfaces/SupportedVersionsAction'; import { IUploadFileActionFromWidgetResponseData } from '../src/interfaces/UploadFileAction'; import { IUserDirectorySearchFromWidgetResponseData } from '../src/interfaces/UserDirectorySearchAction'; @@ -96,6 +97,113 @@ describe('WidgetApi', () => { }); }); + describe('sendEvent', () => { + beforeEach(() => { + jest.mocked(PostmessageTransport.prototype.send).mockResolvedValueOnce( + { + room_id: '!room-id', + event_id: '$event', + } as ISendEventFromWidgetResponseData, + ); + }); + + it('sends message events', async () => { + await expect(widgetApi.sendRoomEvent( + 'm.room.message', + {}, + '!room-id', + )).resolves.toEqual({ + room_id: '!room-id', + event_id: '$event', + }); + }); + + it('sends state events', async () => { + await expect(widgetApi.sendStateEvent( + 'm.room.topic', + "", + {}, + '!room-id', + )).resolves.toEqual({ + room_id: '!room-id', + event_id: '$event', + }); + }); + }); + + describe('delayed sendEvent', () => { + beforeEach(() => { + jest.mocked(PostmessageTransport.prototype.send).mockResolvedValueOnce( + { + room_id: '!room-id', + delay_id: 'id', + } as ISendEventFromWidgetResponseData, + ); + }); + + it('sends delayed message events', async () => { + await expect(widgetApi.sendRoomEvent( + 'm.room.message', + {}, + '!room-id', + 2000, + )).resolves.toEqual({ + room_id: '!room-id', + delay_id: 'id', + }); + }); + + it('sends delayed state events', async () => { + await expect(widgetApi.sendStateEvent( + 'm.room.topic', + "", + {}, + '!room-id', + 2000, + )).resolves.toEqual({ + room_id: '!room-id', + delay_id: 'id', + }); + }); + + it('sends delayed child action message events', async () => { + await expect(widgetApi.sendRoomEvent( + 'm.room.message', + {}, + '!room-id', + null, + 'id-parent', + )).resolves.toEqual({ + room_id: '!room-id', + delay_id: 'id', + }); + }); + + it('sends delayed child action state events', async () => { + await expect(widgetApi.sendStateEvent( + 'm.room.topic', + "", + {}, + '!room-id', + null, + 'id-parent', + )).resolves.toEqual({ + room_id: '!room-id', + delay_id: 'id', + }); + }); + }); + + describe('updateDelayedEvent', () => { + beforeEach(() => { + jest.mocked(PostmessageTransport.prototype.send).mockResolvedValueOnce({}); + }); + + it('updates delayed events', async () => { + await expect(widgetApi.updateDelayedEvent('id', 'send')).resolves.toEqual({}); + }); + }); + describe('getClientVersions', () => { beforeEach(() => { jest.mocked(PostmessageTransport.prototype.send).mockResolvedValueOnce( From 9c2ac46fdb9bf3f2a364f580cf937612ea39140e Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 30 Jul 2024 11:45:49 -0400 Subject: [PATCH 11/12] Check for update capability before the action name Co-authored-by: Timo <16718859+toger5@users.noreply.github.com> --- src/ClientWidgetApi.ts | 50 ++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index 03c2d94..f154573 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -558,39 +558,37 @@ export class ClientWidgetApi extends EventEmitter { }); } - private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) { - if (!request.data.delay_id) { - return this.transport.reply(request, { - error: {message: "Invalid request - missing delay_id"}, - }); - } +private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) { + if (!request.data.delay_id) { + return this.transport.reply(request, { + error: {message: "Invalid request - missing delay_id"}, + }); + } - switch (request.data.action) { - case UpdateDelayedEventAction.Cancel: - case UpdateDelayedEventAction.Restart: - case UpdateDelayedEventAction.Send: - break - default: + if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { + return this.transport.reply(request, { + error: {message: "Missing capability"}, + }); + } + + switch (request.data.action) { + case UpdateDelayedEventAction.Cancel: + case UpdateDelayedEventAction.Restart: + case UpdateDelayedEventAction.Send: + this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { + return this.transport.reply(request, {}); + }).catch(e => { + console.error("error updating delayed event: ", e); return this.transport.reply(request, { - error: {message: "Invalid request - unsupported action"}, + error: {message: "Error updating delayed event"}, }); - } - - if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { - return this.transport.reply(request, { - error: {message: "Missing capability"}, }); - } - - this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { - return this.transport.reply(request, {}); - }).catch(e => { - console.error("error updating delayed event: ", e); + default: return this.transport.reply(request, { - error: {message: "Error updating delayed event"}, + error: {message: "Invalid request - unsupported action"}, }); - }); } +} private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise { if (!request.data.type) { From f239242f0b51212126db320c410548a0e0d3da52 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 30 Jul 2024 12:01:26 -0400 Subject: [PATCH 12/12] Lint --- src/ClientWidgetApi.ts | 53 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/ClientWidgetApi.ts b/src/ClientWidgetApi.ts index f154573..854f8c1 100644 --- a/src/ClientWidgetApi.ts +++ b/src/ClientWidgetApi.ts @@ -558,37 +558,38 @@ export class ClientWidgetApi extends EventEmitter { }); } -private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) { - if (!request.data.delay_id) { - return this.transport.reply(request, { - error: {message: "Invalid request - missing delay_id"}, - }); - } + private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) { + if (!request.data.delay_id) { + return this.transport.reply(request, { + error: {message: "Invalid request - missing delay_id"}, + }); + } - if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { - return this.transport.reply(request, { - error: {message: "Missing capability"}, - }); - } + if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) { + return this.transport.reply(request, { + error: {message: "Missing capability"}, + }); + } - switch (request.data.action) { - case UpdateDelayedEventAction.Cancel: - case UpdateDelayedEventAction.Restart: - case UpdateDelayedEventAction.Send: - this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { - return this.transport.reply(request, {}); - }).catch(e => { - console.error("error updating delayed event: ", e); + switch (request.data.action) { + case UpdateDelayedEventAction.Cancel: + case UpdateDelayedEventAction.Restart: + case UpdateDelayedEventAction.Send: + this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => { + return this.transport.reply(request, {}); + }).catch(e => { + console.error("error updating delayed event: ", e); + return this.transport.reply(request, { + error: {message: "Error updating delayed event"}, + }); + }); + break; + default: return this.transport.reply(request, { - error: {message: "Error updating delayed event"}, + error: {message: "Invalid request - unsupported action"}, }); - }); - default: - return this.transport.reply(request, { - error: {message: "Invalid request - unsupported action"}, - }); + } } -} private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise { if (!request.data.type) {