Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions src/ClientWidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,25 +686,31 @@ export class ClientWidgetApi extends EventEmitter {
});
}

let updateDelayedEvent: (delayId: string) => Promise<void>;
switch (request.data.action) {
case UpdateDelayedEventAction.Cancel:
updateDelayedEvent = this.driver.cancelScheduledDelayedEvent;
break;
case UpdateDelayedEventAction.Restart:
updateDelayedEvent = this.driver.restartScheduledDelayedEvent;
break;
case UpdateDelayedEventAction.Send:
this.driver
.updateDelayedEvent(request.data.delay_id, request.data.action)
.then(() => {
return this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
})
.catch((e: unknown) => {
console.error("error updating delayed event: ", e);
this.handleDriverError(e, request, "Error updating delayed event");
});
updateDelayedEvent = this.driver.sendScheduledDelayedEvent;
break;
default:
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
error: { message: "Invalid request - unsupported action" },
});
}
updateDelayedEvent
.call(this.driver, request.data.delay_id)
.then(() => {
return this.transport.reply<IWidgetApiAcknowledgeResponseData>(request, {});
})
.catch((e: unknown) => {
console.error("error updating delayed event: ", e);
this.handleDriverError(e, request, "Error updating delayed event");
});
}

private async handleSendToDevice(request: ISendToDeviceFromWidgetActionRequest): Promise<void> {
Expand Down
33 changes: 28 additions & 5 deletions src/WidgetApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,15 +476,38 @@ export class WidgetApi extends EventEmitter {
/**
* @deprecated This currently relies on an unstable MSC (MSC4157).
*/
public updateDelayedEvent(
delayId: string,
action: UpdateDelayedEventAction,
): Promise<IUpdateDelayedEventFromWidgetResponseData> {
public cancelScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
{
delay_id: delayId,
action,
action: UpdateDelayedEventAction.Cancel,
},
);
}

/**
* @deprecated This currently relies on an unstable MSC (MSC4157).
*/
public restartScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
{
delay_id: delayId,
action: UpdateDelayedEventAction.Restart,
},
);
}

/**
* @deprecated This currently relies on an unstable MSC (MSC4157).
*/
public sendScheduledDelayedEvent(delayId: string): Promise<IUpdateDelayedEventFromWidgetResponseData> {
return this.transport.send<IUpdateDelayedEventFromWidgetRequestData, IUpdateDelayedEventFromWidgetResponseData>(
WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
{
delay_id: delayId,
action: UpdateDelayedEventAction.Send,
},
);
}
Expand Down
29 changes: 25 additions & 4 deletions src/driver/WidgetDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
IRoomAccountData,
ITurnServer,
IWidgetApiErrorResponseDataDetails,
UpdateDelayedEventAction,
} from "..";

export interface ISendEventDetails {
Expand Down Expand Up @@ -142,10 +141,32 @@ export abstract class WidgetDriver {

/**
* @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.
* Cancel the scheduled delivery of the delayed event matching the provided {@link delayId}.
* @throws Rejected when there is no matching delayed event,
* or when the delayed event failed to be cancelled.
*/
public updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise<void> {
public cancelScheduledDelayedEvent(delayId: string): Promise<void> {
return Promise.reject(new Error("Failed to override function"));
}

/**
* @experimental Part of MSC4140 & MSC4157
* Restart the scheduled delivery of the delayed event matching the provided {@link delayId}.
* @throws Rejected when there is no matching delayed event,
* or when the delayed event failed to be restarted.
*/
public restartScheduledDelayedEvent(delayId: string): Promise<void> {
return Promise.reject(new Error("Failed to override function"));
}

/**
* @experimental Part of MSC4140 & MSC4157
* Immediately send the delayed event matching the provided {@link delayId},
* instead of waiting for its scheduled delivery.
* @throws Rejected when there is no matching delayed event,
* or when the delayed event failed to be sent.
*/
public sendScheduledDelayedEvent(delayId: string): Promise<void> {
return Promise.reject(new Error("Failed to override function"));
}

Expand Down
158 changes: 128 additions & 30 deletions test/ClientWidgetApi-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ describe("ClientWidgetApi", () => {
readEventRelations: jest.fn(),
sendEvent: jest.fn(),
sendDelayedEvent: jest.fn(),
updateDelayedEvent: jest.fn(),
cancelScheduledDelayedEvent: jest.fn(),
restartScheduledDelayedEvent: jest.fn(),
sendScheduledDelayedEvent: jest.fn(),
sendToDevice: jest.fn(),
askOpenID: jest.fn(),
readRoomAccountData: jest.fn(),
Expand Down Expand Up @@ -903,7 +905,57 @@ describe("ClientWidgetApi", () => {
});

describe("update_delayed_event action", () => {
it("fails to update delayed events", async () => {
it("fails to cancel delayed events", async () => {
const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
requestId: "0",
action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
data: {
delay_id: "f",
action: UpdateDelayedEventAction.Cancel,
},
};

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.cancelScheduledDelayedEvent).not.toBeCalled();
});

it("fails to restart delayed events", async () => {
const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
requestId: "0",
action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
data: {
delay_id: "f",
action: UpdateDelayedEventAction.Restart,
},
};

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.restartScheduledDelayedEvent).not.toBeCalled();
});

it("fails to send delayed events", async () => {
const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
Expand All @@ -925,7 +977,7 @@ describe("ClientWidgetApi", () => {
});
});

expect(driver.updateDelayedEvent).not.toBeCalled();
expect(driver.sendScheduledDelayedEvent).not.toBeCalled();
});

it("fails to update delayed events with unsupported action", async () => {
Expand All @@ -950,42 +1002,88 @@ describe("ClientWidgetApi", () => {
});
});

expect(driver.updateDelayedEvent).not.toBeCalled();
expect(driver.cancelScheduledDelayedEvent).not.toBeCalled();
expect(driver.restartScheduledDelayedEvent).not.toBeCalled();
expect(driver.sendScheduledDelayedEvent).not.toBeCalled();
});

it("updates delayed events", async () => {
driver.updateDelayedEvent.mockResolvedValue(undefined);
it("can cancel delayed events", async () => {
driver.cancelScheduledDelayedEvent.mockResolvedValue(undefined);

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,
},
};
const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
requestId: "0",
action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
data: {
delay_id: "f",
action: UpdateDelayedEventAction.Cancel,
},
};

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.cancelScheduledDelayedEvent).toHaveBeenCalledWith(event.data.delay_id);
});

it("can restart delayed events", async () => {
driver.restartScheduledDelayedEvent.mockResolvedValue(undefined);

const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
requestId: "0",
action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
data: {
delay_id: "f",
action: UpdateDelayedEventAction.Restart,
},
};

await loadIframe(["org.matrix.msc4157.update_delayed_event"]);

emitEvent(new CustomEvent("", { detail: event }));

await waitFor(() => {
expect(transport.reply).toHaveBeenCalledWith(event, {});
});

expect(driver.restartScheduledDelayedEvent).toHaveBeenCalledWith(event.data.delay_id);
});

it("can send delayed events", async () => {
driver.sendScheduledDelayedEvent.mockResolvedValue(undefined);

const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
widgetId: "test",
requestId: "0",
action: WidgetApiFromWidgetAction.MSC4157UpdateDelayedEvent,
data: {
delay_id: "f",
action: UpdateDelayedEventAction.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);
}
expect(driver.sendScheduledDelayedEvent).toHaveBeenCalledWith(event.data.delay_id);
});

it("should reject requests when the driver throws an exception", async () => {
driver.updateDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object"));
driver.sendScheduledDelayedEvent.mockRejectedValue(new Error("M_BAD_JSON: Content must be a JSON object"));

const event: IUpdateDelayedEventFromWidgetActionRequest = {
api: WidgetApiDirection.FromWidget,
Expand All @@ -1012,7 +1110,7 @@ describe("ClientWidgetApi", () => {
it("should reject with Matrix API error response thrown by driver", async () => {
driver.processError.mockImplementation(processCustomMatrixError);

driver.updateDelayedEvent.mockRejectedValue(
driver.sendScheduledDelayedEvent.mockRejectedValue(
new CustomMatrixError("failed to update delayed event", 400, "M_NOT_JSON", {
reason: "Content must be a JSON object.",
}),
Expand Down
Loading