Skip to content

Commit

Permalink
Support MSC4157: Futures (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFerr authored Jul 16, 2024
1 parent 0125d45 commit ac09799
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 29 deletions.
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 { IContentLoadedActionRequest } from "./interfaces/ContentLoadedAction";
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 @@ -477,21 +477,31 @@ export class ClientWidgetApi extends EventEmitter {
});
}

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 @@ export class ClientWidgetApi extends EventEmitter {
});
}

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

0 comments on commit ac09799

Please sign in to comment.