Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sending delayed events (Futures) / MSC4157 #90

Merged
merged 15 commits into from
Jul 16, 2024
Merged
66 changes: 47 additions & 19 deletions src/ClientWidgetApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
* 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.
Expand All @@ -24,7 +24,7 @@
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction";
import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse";
import { Capability, MatrixCapabilities } from "./interfaces/Capabilities";
import { IOpenIDUpdate, ISendEventDetails, WidgetDriver } from "./driver/WidgetDriver";
import { IOpenIDUpdate, ISendEventDetails, ISendFutureDetails, WidgetDriver } from "./driver/WidgetDriver";
import {
ICapabilitiesActionResponseData,
INotifyCapabilitiesActionRequestData,
Expand Down Expand Up @@ -322,7 +322,7 @@
});
}

const onErr = (e: any) => {

Check warning on line 325 in src/ClientWidgetApi.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
console.error("[ClientWidgetApi] Failed to handle navigation: ", e);
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
error: {message: "Error handling navigation"},
Expand Down Expand Up @@ -429,7 +429,7 @@
if (request.data.room_ids) {
askRoomIds = request.data.room_ids as string[];
if (!Array.isArray(askRoomIds)) {
askRoomIds = [askRoomIds as any as string];

Check warning on line 432 in src/ClientWidgetApi.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
}
for (const roomId of askRoomIds) {
if (!this.canUseRoomTimeline(roomId)) {
Expand Down Expand Up @@ -477,21 +477,31 @@
});
}

const isState = request.data.state_key !== null && request.data.state_key !== undefined;
let sendEventPromise: Promise<ISendEventDetails>;
if (isState) {
if (!this.canSendStateEvent(request.data.type, request.data.state_key!)) {
let sendEventPromise: Promise<ISendEventDetails|ISendFutureDetails>;
if (request.data.state_key !== undefined) {
if (!this.canSendStateEvent(request.data.type, request.data.state_key)) {
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
error: {message: "Cannot send state events of this type"},
});
}

sendEventPromise = this.driver.sendEvent(
request.data.type,
request.data.content || {},
request.data.state_key,
request.data.room_id,
);
if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) {
sendEventPromise = this.driver.sendEvent(
request.data.type,
request.data.content || {},
request.data.state_key,
request.data.room_id,
);
} else {
sendEventPromise = this.driver.sendFuture(
request.data.future_timeout ?? null,
request.data.future_group_id ?? null,
request.data.type,
request.data.content || {},
request.data.state_key,
request.data.room_id,
);
}
} else {
const content = request.data.content as { msgtype?: string } || {};
const msgtype = content['msgtype'];
Expand All @@ -501,18 +511,36 @@
});
}

sendEventPromise = this.driver.sendEvent(
request.data.type,
content,
null, // not sending a state event
request.data.room_id,
);
if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) {
sendEventPromise = this.driver.sendEvent(
request.data.type,
content,
null, // not sending a state event
request.data.room_id,
);
} else {
sendEventPromise = this.driver.sendFuture(
request.data.future_timeout ?? null,
request.data.future_group_id ?? null,
request.data.type,
content,
null, // not sending a state event
request.data.room_id,
);
}
}

sendEventPromise.then(sentEvent => {
return this.transport.reply<ISendEventFromWidgetResponseData>(request, {
room_id: sentEvent.roomId,
event_id: sentEvent.eventId,
...("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 }),
}),
});
}).catch(e => {
console.error("error sending event: ", e);
Expand Down
31 changes: 25 additions & 6 deletions src/WidgetApi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
* 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.
Expand Down Expand Up @@ -400,22 +400,41 @@ export class WidgetApi extends EventEmitter {
eventType: string,
content: unknown,
roomId?: string,
futureTimeout?: number,
futureGroupId?: string,
): Promise<ISendEventFromWidgetResponseData> {
return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>(
WidgetApiFromWidgetAction.SendEvent,
{type: eventType, content, room_id: roomId},
);
return this.sendEvent(eventType, undefined, content, roomId, futureTimeout, futureGroupId);
}

public sendStateEvent(
eventType: string,
stateKey: string,
content: unknown,
roomId?: string,
futureTimeout?: number,
futureGroupId?: string,
): Promise<ISendEventFromWidgetResponseData> {
return this.sendEvent(eventType, stateKey, content, roomId, futureTimeout, futureGroupId);
}

private sendEvent(
eventType: string,
stateKey: string | undefined,
content: unknown,
roomId?: string,
futureTimeout?: number,
futureGroupId?: string,
): Promise<ISendEventFromWidgetResponseData> {
return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>(
WidgetApiFromWidgetAction.SendEvent,
{type: eventType, content, state_key: stateKey, room_id: roomId},
{
type: eventType,
content,
...(stateKey !== undefined && { state_key: stateKey }),
...(roomId !== undefined && { room_id: roomId }),
...(futureTimeout !== undefined && { future_timeout: futureTimeout }),
...(futureGroupId !== undefined && { future_group_id: futureGroupId }),
},
);
}

Expand Down
40 changes: 39 additions & 1 deletion src/driver/WidgetDriver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
* 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.
Expand Down Expand Up @@ -29,6 +29,14 @@ export interface ISendEventDetails {
eventId: string;
}

export interface ISendFutureDetails {
roomId: string;
futureGroupId: string;
sendToken: string;
cancelToken: string;
refreshToken?: string;
}

export interface IOpenIDUpdate {
state: OpenIDRequestState;
token?: IOpenIDCredentials;
Expand Down Expand Up @@ -103,6 +111,36 @@ export abstract class WidgetDriver {
return Promise.reject(new Error("Failed to override function"));
}

/**
* @experimental Part of MSC4140 & MSC4157
* 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 futureGroupId} is null.
* @param {string|null} futureGroupId The ID of the group the future belongs to,
* 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.
* @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
* user is currently looking at.
* @returns {Promise<ISendFutureDetails>} Resolves when the future has been sent with
* details of that future.
* @throws Rejected when the future could not be sent.
*/
public sendFuture(
futureTimeout: number | null,
futureGroupId: string | null,
eventType: string,
content: unknown,
stateKey: string | null = null,
roomId: string | null = null,
): Promise<ISendFutureDetails> {
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
14 changes: 12 additions & 2 deletions src/interfaces/SendEventAction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
* 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.
Expand All @@ -24,6 +24,10 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData {
type: string;
content: unknown;
room_id?: string; // eslint-disable-line camelcase

// MSC4157: Futures
future_timeout?: number; // eslint-disable-line camelcase
future_group_id?: string; // eslint-disable-line camelcase
}

export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest {
Expand All @@ -33,7 +37,13 @@ export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest {

export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData {
room_id: string; // eslint-disable-line camelcase
event_id: string; // eslint-disable-line camelcase
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
}

export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidgetActionRequest {
Expand Down
Loading
Loading