Skip to content

Commit cddac44

Browse files
committed
Support future sending via Widget API (MSC4157)
Depends on matrix-org/matrix-widget-api#90
1 parent ff63e04 commit cddac44

File tree

3 files changed

+203
-7
lines changed

3 files changed

+203
-7
lines changed

spec/unit/embedded.spec.ts

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,25 @@ class MockWidgetApi extends EventEmitter {
5959
public requestCapabilityToReceiveState = jest.fn();
6060
public requestCapabilityToSendToDevice = jest.fn();
6161
public requestCapabilityToReceiveToDevice = jest.fn();
62-
public sendRoomEvent = jest.fn(() => ({ event_id: `$${Math.random()}` }));
63-
public sendStateEvent = jest.fn();
62+
public sendRoomEvent = jest.fn(
63+
(eventType: string, content: unknown, roomId?: string, delay?: number, parentDelayId?: string) =>
64+
delay === undefined && parentDelayId === undefined
65+
? { event_id: `$${Math.random()}` }
66+
: { delay_id: `id-${Math.random()}` },
67+
);
68+
public sendStateEvent = jest.fn(
69+
(
70+
eventType: string,
71+
stateKey: string,
72+
content: unknown,
73+
roomId?: string,
74+
delay?: number,
75+
parentDelayId?: string,
76+
) =>
77+
delay === undefined && parentDelayId === undefined
78+
? { event_id: `$${Math.random()}` }
79+
: { delay_id: `id-${Math.random()}` },
80+
);
6481
public sendToDevice = jest.fn();
6582
public requestOpenIDConnectToken = jest.fn(() => {
6683
return testOIDCToken;
@@ -160,6 +177,134 @@ describe("RoomWidgetClient", () => {
160177
});
161178
});
162179

180+
describe("delayed events", () => {
181+
describe("when supported", () => {
182+
const doesServerSupportUnstableFeatureMock = jest.fn((feature) =>
183+
Promise.resolve(feature === "org.matrix.msc4140"),
184+
);
185+
186+
beforeAll(() => {
187+
MatrixClient.prototype.doesServerSupportUnstableFeature = doesServerSupportUnstableFeatureMock;
188+
});
189+
190+
afterAll(() => {
191+
doesServerSupportUnstableFeatureMock.mockReset();
192+
});
193+
194+
it("sends delayed message events", async () => {
195+
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
196+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
197+
expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
198+
await client._unstable_sendDelayedEvent(
199+
"!1:example.org",
200+
{ delay: 2000 },
201+
null,
202+
"org.matrix.rageshake_request",
203+
{ request_id: 123 },
204+
);
205+
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
206+
"org.matrix.rageshake_request",
207+
{ request_id: 123 },
208+
"!1:example.org",
209+
2000,
210+
undefined,
211+
);
212+
});
213+
214+
it("sends child action delayed message events", async () => {
215+
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
216+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
217+
expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
218+
const parentDelayId = `id-${Math.random()}`;
219+
await client._unstable_sendDelayedEvent(
220+
"!1:example.org",
221+
{ parent_delay_id: parentDelayId },
222+
null,
223+
"org.matrix.rageshake_request",
224+
{ request_id: 123 },
225+
);
226+
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
227+
"org.matrix.rageshake_request",
228+
{ request_id: 123 },
229+
"!1:example.org",
230+
undefined,
231+
parentDelayId,
232+
);
233+
});
234+
235+
it("sends delayed state events", async () => {
236+
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
237+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
238+
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
239+
await client._unstable_sendDelayedStateEvent(
240+
"!1:example.org",
241+
{ delay: 2000 },
242+
"org.example.foo",
243+
{ hello: "world" },
244+
"bar",
245+
);
246+
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
247+
"org.example.foo",
248+
"bar",
249+
{ hello: "world" },
250+
"!1:example.org",
251+
2000,
252+
undefined,
253+
);
254+
});
255+
256+
it("sends child action delayed state events", async () => {
257+
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
258+
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
259+
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
260+
const parentDelayId = `fg-${Math.random()}`;
261+
await client._unstable_sendDelayedStateEvent(
262+
"!1:example.org",
263+
{ parent_delay_id: parentDelayId },
264+
"org.example.foo",
265+
{ hello: "world" },
266+
"bar",
267+
);
268+
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
269+
"org.example.foo",
270+
"bar",
271+
{ hello: "world" },
272+
"!1:example.org",
273+
undefined,
274+
parentDelayId,
275+
);
276+
});
277+
});
278+
279+
describe("when unsupported", () => {
280+
it("fails to send delayed message events", async () => {
281+
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
282+
await expect(
283+
client._unstable_sendDelayedEvent(
284+
"!1:example.org",
285+
{ delay: 2000 },
286+
null,
287+
"org.matrix.rageshake_request",
288+
{ request_id: 123 },
289+
),
290+
).rejects.toThrow("Server does not support");
291+
});
292+
293+
it("fails to send delayed state events", async () => {
294+
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
295+
await expect(
296+
client._unstable_sendDelayedStateEvent(
297+
"!1:example.org",
298+
{ delay: 2000 },
299+
"org.example.foo",
300+
{ hello: "world" },
301+
"bar",
302+
),
303+
).rejects.toThrow("Server does not support");
304+
});
305+
});
306+
});
307+
163308
describe("initialization", () => {
164309
it("requests permissions for specific message types", async () => {
165310
await makeClient({ sendMessage: [MsgType.Text], receiveMessage: [MsgType.Text] });

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ export const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666";
531531
export const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms";
532532
export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms";
533533

534-
const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
534+
export const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
535535

536536
enum CrossSigningKeyType {
537537
MasterKey = "master_key",

src/embedded.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727

2828
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event";
2929
import { ISendEventResponse, SendDelayedEventRequestOpts, SendDelayedEventResponse } from "./@types/requests";
30-
import { EventType } from "./@types/event";
30+
import { EventType, StateEvents } from "./@types/event";
3131
import { logger } from "./logger";
3232
import {
3333
MatrixClient,
@@ -36,6 +36,7 @@ import {
3636
IStartClientOpts,
3737
SendToDeviceContentMap,
3838
IOpenIDToken,
39+
UNSTABLE_MSC4140_DELAYED_EVENTS,
3940
} from "./client";
4041
import { SyncApi, SyncState } from "./sync";
4142
import { SlidingSyncSdk } from "./sliding-sync-sdk";
@@ -260,8 +261,17 @@ export class RoomWidgetClient extends MatrixClient {
260261
delayOpts?: SendDelayedEventRequestOpts,
261262
): Promise<ISendEventResponse | SendDelayedEventResponse> {
262263
if (delayOpts) {
263-
throw new Error("Delayed event sending via widgets is not implemented");
264+
// TODO: updatePendingEvent for delayed events?
265+
const response = await this.widgetApi.sendRoomEvent(
266+
event.getType(),
267+
event.getContent(),
268+
room.roomId,
269+
"delay" in delayOpts ? delayOpts.delay : undefined,
270+
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
271+
);
272+
return this.validateSendDelayedEventResponse(response);
264273
}
274+
265275
let response: ISendEventFromWidgetResponseData;
266276
try {
267277
response = await this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId);
@@ -271,7 +281,7 @@ export class RoomWidgetClient extends MatrixClient {
271281
}
272282

273283
room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
274-
return { event_id: response.event_id };
284+
return this.validateSendEventResponse(response);
275285
}
276286

277287
public async sendStateEvent(
@@ -280,7 +290,48 @@ export class RoomWidgetClient extends MatrixClient {
280290
content: any,
281291
stateKey = "",
282292
): Promise<ISendEventResponse> {
283-
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
293+
const response = await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
294+
return this.validateSendEventResponse(response);
295+
}
296+
297+
/**
298+
* @experimental This currently relies on an unstable MSC (MSC4140).
299+
*/
300+
// eslint-disable-next-line
301+
public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(
302+
roomId: string,
303+
delayOpts: SendDelayedEventRequestOpts,
304+
eventType: K,
305+
content: StateEvents[K],
306+
stateKey = "",
307+
): Promise<SendDelayedEventResponse> {
308+
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
309+
throw Error("Server does not support the delayed events API");
310+
}
311+
312+
const response = await this.widgetApi.sendStateEvent(
313+
eventType,
314+
stateKey,
315+
content,
316+
roomId,
317+
"delay" in delayOpts ? delayOpts.delay : undefined,
318+
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
319+
);
320+
return this.validateSendDelayedEventResponse(response);
321+
}
322+
323+
private validateSendEventResponse(response: ISendEventFromWidgetResponseData): ISendEventResponse {
324+
if (response.event_id === undefined) {
325+
throw new Error("'event_id' absent from response to an event request");
326+
}
327+
return { event_id: response.event_id };
328+
}
329+
330+
private validateSendDelayedEventResponse(response: ISendEventFromWidgetResponseData): SendDelayedEventResponse {
331+
if (!response.delay_id) {
332+
throw new Error("'delay_id' absent from response to a delayed event request");
333+
}
334+
return { delay_id: response.delay_id };
284335
}
285336

286337
public async sendToDevice(eventType: string, contentMap: SendToDeviceContentMap): Promise<{}> {

0 commit comments

Comments
 (0)