Skip to content

Commit ac09799

Browse files
authored
Support MSC4157: Futures (#90)
1 parent 0125d45 commit ac09799

File tree

5 files changed

+324
-29
lines changed

5 files changed

+324
-29
lines changed

src/ClientWidgetApi.ts

+47-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
2+
* Copyright 2020 - 2024 The Matrix.org Foundation C.I.C.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,7 @@ import { IContentLoadedActionRequest } from "./interfaces/ContentLoadedAction";
2424
import { WidgetApiFromWidgetAction, WidgetApiToWidgetAction } from "./interfaces/WidgetApiAction";
2525
import { IWidgetApiErrorResponseData } from "./interfaces/IWidgetApiErrorResponse";
2626
import { Capability, MatrixCapabilities } from "./interfaces/Capabilities";
27-
import { IOpenIDUpdate, ISendEventDetails, WidgetDriver } from "./driver/WidgetDriver";
27+
import { IOpenIDUpdate, ISendEventDetails, ISendFutureDetails, WidgetDriver } from "./driver/WidgetDriver";
2828
import {
2929
ICapabilitiesActionResponseData,
3030
INotifyCapabilitiesActionRequestData,
@@ -477,21 +477,31 @@ export class ClientWidgetApi extends EventEmitter {
477477
});
478478
}
479479

480-
const isState = request.data.state_key !== null && request.data.state_key !== undefined;
481-
let sendEventPromise: Promise<ISendEventDetails>;
482-
if (isState) {
483-
if (!this.canSendStateEvent(request.data.type, request.data.state_key!)) {
480+
let sendEventPromise: Promise<ISendEventDetails|ISendFutureDetails>;
481+
if (request.data.state_key !== undefined) {
482+
if (!this.canSendStateEvent(request.data.type, request.data.state_key)) {
484483
return this.transport.reply<IWidgetApiErrorResponseData>(request, {
485484
error: {message: "Cannot send state events of this type"},
486485
});
487486
}
488487

489-
sendEventPromise = this.driver.sendEvent(
490-
request.data.type,
491-
request.data.content || {},
492-
request.data.state_key,
493-
request.data.room_id,
494-
);
488+
if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) {
489+
sendEventPromise = this.driver.sendEvent(
490+
request.data.type,
491+
request.data.content || {},
492+
request.data.state_key,
493+
request.data.room_id,
494+
);
495+
} else {
496+
sendEventPromise = this.driver.sendFuture(
497+
request.data.future_timeout ?? null,
498+
request.data.future_group_id ?? null,
499+
request.data.type,
500+
request.data.content || {},
501+
request.data.state_key,
502+
request.data.room_id,
503+
);
504+
}
495505
} else {
496506
const content = request.data.content as { msgtype?: string } || {};
497507
const msgtype = content['msgtype'];
@@ -501,18 +511,36 @@ export class ClientWidgetApi extends EventEmitter {
501511
});
502512
}
503513

504-
sendEventPromise = this.driver.sendEvent(
505-
request.data.type,
506-
content,
507-
null, // not sending a state event
508-
request.data.room_id,
509-
);
514+
if (request.data.future_timeout === undefined && request.data.future_group_id === undefined) {
515+
sendEventPromise = this.driver.sendEvent(
516+
request.data.type,
517+
content,
518+
null, // not sending a state event
519+
request.data.room_id,
520+
);
521+
} else {
522+
sendEventPromise = this.driver.sendFuture(
523+
request.data.future_timeout ?? null,
524+
request.data.future_group_id ?? null,
525+
request.data.type,
526+
content,
527+
null, // not sending a state event
528+
request.data.room_id,
529+
);
530+
}
510531
}
511532

512533
sendEventPromise.then(sentEvent => {
513534
return this.transport.reply<ISendEventFromWidgetResponseData>(request, {
514535
room_id: sentEvent.roomId,
515-
event_id: sentEvent.eventId,
536+
...("eventId" in sentEvent ? {
537+
event_id: sentEvent.eventId,
538+
} : {
539+
future_group_id: sentEvent.futureGroupId,
540+
send_token: sentEvent.sendToken,
541+
cancel_token: sentEvent.cancelToken,
542+
...("refreshToken" in sentEvent && { refresh_token: sentEvent.refreshToken }),
543+
}),
516544
});
517545
}).catch(e => {
518546
console.error("error sending event: ", e);

src/WidgetApi.ts

+25-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
2+
* Copyright 2020 - 2024 The Matrix.org Foundation C.I.C.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -400,22 +400,41 @@ export class WidgetApi extends EventEmitter {
400400
eventType: string,
401401
content: unknown,
402402
roomId?: string,
403+
futureTimeout?: number,
404+
futureGroupId?: string,
403405
): Promise<ISendEventFromWidgetResponseData> {
404-
return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>(
405-
WidgetApiFromWidgetAction.SendEvent,
406-
{type: eventType, content, room_id: roomId},
407-
);
406+
return this.sendEvent(eventType, undefined, content, roomId, futureTimeout, futureGroupId);
408407
}
409408

410409
public sendStateEvent(
411410
eventType: string,
412411
stateKey: string,
413412
content: unknown,
414413
roomId?: string,
414+
futureTimeout?: number,
415+
futureGroupId?: string,
416+
): Promise<ISendEventFromWidgetResponseData> {
417+
return this.sendEvent(eventType, stateKey, content, roomId, futureTimeout, futureGroupId);
418+
}
419+
420+
private sendEvent(
421+
eventType: string,
422+
stateKey: string | undefined,
423+
content: unknown,
424+
roomId?: string,
425+
futureTimeout?: number,
426+
futureGroupId?: string,
415427
): Promise<ISendEventFromWidgetResponseData> {
416428
return this.transport.send<ISendEventFromWidgetRequestData, ISendEventFromWidgetResponseData>(
417429
WidgetApiFromWidgetAction.SendEvent,
418-
{type: eventType, content, state_key: stateKey, room_id: roomId},
430+
{
431+
type: eventType,
432+
content,
433+
...(stateKey !== undefined && { state_key: stateKey }),
434+
...(roomId !== undefined && { room_id: roomId }),
435+
...(futureTimeout !== undefined && { future_timeout: futureTimeout }),
436+
...(futureGroupId !== undefined && { future_group_id: futureGroupId }),
437+
},
419438
);
420439
}
421440

src/driver/WidgetDriver.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
2+
* Copyright 2020 - 2024 The Matrix.org Foundation C.I.C.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,14 @@ export interface ISendEventDetails {
2929
eventId: string;
3030
}
3131

32+
export interface ISendFutureDetails {
33+
roomId: string;
34+
futureGroupId: string;
35+
sendToken: string;
36+
cancelToken: string;
37+
refreshToken?: string;
38+
}
39+
3240
export interface IOpenIDUpdate {
3341
state: OpenIDRequestState;
3442
token?: IOpenIDCredentials;
@@ -103,6 +111,36 @@ export abstract class WidgetDriver {
103111
return Promise.reject(new Error("Failed to override function"));
104112
}
105113

114+
/**
115+
* @experimental Part of MSC4140 & MSC4157
116+
* Sends a future into a room. If `roomId` is falsy, the client should send the future
117+
* into the room the user is currently looking at. The widget API will have already
118+
* verified that the widget is capable of sending the future's event to that room.
119+
* @param {number|null} futureTimeout The future's timeout, or null for an action future.
120+
* May not be null if {@link futureGroupId} is null.
121+
* @param {string|null} futureGroupId The ID of the group the future belongs to,
122+
* or null if it will be put in a new group. May not be null if {@link futureTimeout} is null.
123+
* @param {string} eventType The event type of the event to be sent by the future.
124+
* @param {*} content The content for the event to be sent by the future.
125+
* @param {string|null} stateKey The state key if the event to be sent by the future is
126+
* a state event, otherwise null. May be an empty string.
127+
* @param {string|null} roomId The room ID to send the future to. If falsy, the room the
128+
* user is currently looking at.
129+
* @returns {Promise<ISendFutureDetails>} Resolves when the future has been sent with
130+
* details of that future.
131+
* @throws Rejected when the future could not be sent.
132+
*/
133+
public sendFuture(
134+
futureTimeout: number | null,
135+
futureGroupId: string | null,
136+
eventType: string,
137+
content: unknown,
138+
stateKey: string | null = null,
139+
roomId: string | null = null,
140+
): Promise<ISendFutureDetails> {
141+
return Promise.reject(new Error("Failed to override function"));
142+
}
143+
106144
/**
107145
* Sends a to-device event. The widget API will have already verified that the widget
108146
* is capable of sending the event.

src/interfaces/SendEventAction.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
2+
* Copyright 2020 - 2024 The Matrix.org Foundation C.I.C.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,10 @@ export interface ISendEventFromWidgetRequestData extends IWidgetApiRequestData {
2424
type: string;
2525
content: unknown;
2626
room_id?: string; // eslint-disable-line camelcase
27+
28+
// MSC4157: Futures
29+
future_timeout?: number; // eslint-disable-line camelcase
30+
future_group_id?: string; // eslint-disable-line camelcase
2731
}
2832

2933
export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest {
@@ -33,7 +37,13 @@ export interface ISendEventFromWidgetActionRequest extends IWidgetApiRequest {
3337

3438
export interface ISendEventFromWidgetResponseData extends IWidgetApiResponseData {
3539
room_id: string; // eslint-disable-line camelcase
36-
event_id: string; // eslint-disable-line camelcase
40+
event_id?: string; // eslint-disable-line camelcase
41+
42+
// MSC4157: Futures
43+
future_group_id?: string; // eslint-disable-line camelcase
44+
send_token?: string; // eslint-disable-line camelcase
45+
cancel_token?: string; // eslint-disable-line camelcase
46+
refresh_token?: string; // eslint-disable-line camelcase
3747
}
3848

3949
export interface ISendEventFromWidgetActionResponse extends ISendEventFromWidgetActionRequest {

0 commit comments

Comments
 (0)