Skip to content

Commit

Permalink
Add action & capability to update delayed events
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFerr committed Jul 25, 2024
1 parent 00e1fdd commit 7defbe1
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 2 deletions.
42 changes: 41 additions & 1 deletion src/ClientWidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ import {
IGetMediaConfigActionFromWidgetActionRequest,
IGetMediaConfigActionFromWidgetResponseData,
} from "./interfaces/GetMediaConfigAction";
import {
IUpdateDelayedEventFromWidgetActionRequest,
UpdateDelayedEventActionName,
} from "./interfaces/UpdateDelayedEventAction";
import {
IUploadFileActionFromWidgetActionRequest,
IUploadFileActionFromWidgetResponseData,
Expand Down Expand Up @@ -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<IWidgetApiErrorResponseData>(request, {
error: {message: "Missing capability"},
Expand Down Expand Up @@ -554,6 +558,40 @@ export class ClientWidgetApi extends EventEmitter {
});
}

private handleUpdateDelayedEvent(request: IUpdateDelayedEventFromWidgetActionRequest) {
if (!request.data.delay_id) {
return this.transport.reply<IWidgetApiErrorResponseData>(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<IWidgetApiErrorResponseData>(request, {
error: {message: "Invalid request - unsupported action"},
});
}

if (!this.hasCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent)) {
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
error: {message: "Missing capability"},
});
}

this.driver.updateDelayedEvent(request.data.delay_id, request.data.action).then(() => {
return this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
}).catch(e => {
console.error("error updating delayed event: ", e);
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
error: {message: "Error updating delayed event"},
});
});
}

private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise<void> {
if (!request.data.type) {
await this.transport.reply<IWidgetApiErrorResponseData>(request, {
Expand Down Expand Up @@ -826,6 +864,8 @@ export class ClientWidgetApi extends EventEmitter {
return this.handleGetMediaConfig(<IGetMediaConfigActionFromWidgetActionRequest>ev.detail);
case WidgetApiFromWidgetAction.MSC4039UploadFileAction:
return this.handleUploadFile(<IUploadFileActionFromWidgetActionRequest>ev.detail);
case WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent:
return this.handleUpdateDelayedEvent(<IUpdateDelayedEventFromWidgetActionRequest>ev.detail);

default:
return this.transport.reply(ev.detail, <IWidgetApiErrorResponseData>{
Expand Down
13 changes: 13 additions & 0 deletions src/driver/WidgetDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
IRoomEvent,
IRoomAccountData,
ITurnServer,
UpdateDelayedEventActionName,
} from "..";

export interface ISendEventDetails {
Expand Down Expand Up @@ -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<void> {
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.
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/Capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions src/interfaces/UpdateDelayedEventAction.ts
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions src/interfaces/WidgetApiAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
87 changes: 86 additions & 1 deletion test/ClientWidgetApi-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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([
Expand Down

0 comments on commit 7defbe1

Please sign in to comment.