Skip to content

Commit

Permalink
Support future sending via Widget API (MSC4157)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewFerr committed Jul 26, 2024
1 parent 8e7d296 commit 43327d1
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 7 deletions.
149 changes: 147 additions & 2 deletions spec/unit/embedded.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,25 @@ class MockWidgetApi extends EventEmitter {
public requestCapabilityToReceiveState = jest.fn();
public requestCapabilityToSendToDevice = jest.fn();
public requestCapabilityToReceiveToDevice = jest.fn();
public sendRoomEvent = jest.fn(() => ({ event_id: `$${Math.random()}` }));
public sendStateEvent = jest.fn();
public sendRoomEvent = jest.fn(
(eventType: string, content: unknown, roomId?: string, delay?: number, parentDelayId?: string) =>
delay === undefined && parentDelayId === undefined
? { event_id: `$${Math.random()}` }
: { delay_id: `id-${Math.random()}` },
);
public sendStateEvent = jest.fn(
(
eventType: string,
stateKey: string,
content: unknown,
roomId?: string,
delay?: number,
parentDelayId?: string,
) =>
delay === undefined && parentDelayId === undefined
? { event_id: `$${Math.random()}` }
: { delay_id: `id-${Math.random()}` },
);
public sendToDevice = jest.fn();
public requestOpenIDConnectToken = jest.fn(() => {
return testOIDCToken;
Expand Down Expand Up @@ -160,6 +177,134 @@ describe("RoomWidgetClient", () => {
});
});

describe("delayed events", () => {
describe("when supported", () => {
const doesServerSupportUnstableFeatureMock = jest.fn((feature) =>
Promise.resolve(feature === "org.matrix.msc4140"),
);

beforeAll(() => {
MatrixClient.prototype.doesServerSupportUnstableFeature = doesServerSupportUnstableFeatureMock;
});

afterAll(() => {
doesServerSupportUnstableFeatureMock.mockReset();
});

it("sends delayed message events", async () => {
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
await client._unstable_sendDelayedEvent(
"!1:example.org",
{ delay: 2000 },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
);
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
"org.matrix.rageshake_request",
{ request_id: 123 },
"!1:example.org",
2000,
undefined,
);
});

it("sends child action delayed message events", async () => {
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
const parentDelayId = `id-${Math.random()}`;
await client._unstable_sendDelayedEvent(
"!1:example.org",
{ parent_delay_id: parentDelayId },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
);
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
"org.matrix.rageshake_request",
{ request_id: 123 },
"!1:example.org",
undefined,
parentDelayId,
);
});

it("sends delayed state events", async () => {
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
await client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ delay: 2000 },
"org.example.foo",
{ hello: "world" },
"bar",
);
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
"org.example.foo",
"bar",
{ hello: "world" },
"!1:example.org",
2000,
undefined,
);
});

it("sends child action delayed state events", async () => {
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
const parentDelayId = `fg-${Math.random()}`;
await client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ parent_delay_id: parentDelayId },
"org.example.foo",
{ hello: "world" },
"bar",
);
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
"org.example.foo",
"bar",
{ hello: "world" },
"!1:example.org",
undefined,
parentDelayId,
);
});
});

describe("when unsupported", () => {
it("fails to send delayed message events", async () => {
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
await expect(
client._unstable_sendDelayedEvent(
"!1:example.org",
{ delay: 2000 },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
),
).rejects.toThrow("Server does not support");
});

it("fails to send delayed state events", async () => {
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
await expect(
client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ delay: 2000 },
"org.example.foo",
{ hello: "world" },
"bar",
),
).rejects.toThrow("Server does not support");
});
});
});

describe("initialization", () => {
it("requests permissions for specific message types", async () => {
await makeClient({ sendMessage: [MsgType.Text], receiveMessage: [MsgType.Text] });
Expand Down
2 changes: 1 addition & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ export const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666";
export const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms";
export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms";

const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
export const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";

enum CrossSigningKeyType {
MasterKey = "master_key",
Expand Down
59 changes: 55 additions & 4 deletions src/embedded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {

import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event";
import { ISendEventResponse, SendDelayedEventRequestOpts, SendDelayedEventResponse } from "./@types/requests";
import { EventType } from "./@types/event";
import { EventType, StateEvents } from "./@types/event";
import { logger } from "./logger";
import {
MatrixClient,
Expand All @@ -36,6 +36,7 @@ import {
IStartClientOpts,
SendToDeviceContentMap,
IOpenIDToken,
UNSTABLE_MSC4140_DELAYED_EVENTS,
} from "./client";
import { SyncApi, SyncState } from "./sync";
import { SlidingSyncSdk } from "./sliding-sync-sdk";
Expand Down Expand Up @@ -260,8 +261,17 @@ export class RoomWidgetClient extends MatrixClient {
delayOpts?: SendDelayedEventRequestOpts,
): Promise<ISendEventResponse | SendDelayedEventResponse> {
if (delayOpts) {
throw new Error("Delayed event sending via widgets is not implemented");
// TODO: updatePendingEvent for delayed events?
const response = await this.widgetApi.sendRoomEvent(
event.getType(),
event.getContent(),
room.roomId,
"delay" in delayOpts ? delayOpts.delay : undefined,

Check failure on line 269 in src/embedded.ts

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Expected 2-3 arguments, but got 5.
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
);
return this.validateSendDelayedEventResponse(response);
}

let response: ISendEventFromWidgetResponseData;
try {
response = await this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId);
Expand All @@ -271,7 +281,7 @@ export class RoomWidgetClient extends MatrixClient {
}

room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
return { event_id: response.event_id };
return this.validateSendEventResponse(response);
}

public async sendStateEvent(
Expand All @@ -280,7 +290,48 @@ export class RoomWidgetClient extends MatrixClient {
content: any,
stateKey = "",
): Promise<ISendEventResponse> {
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
const response = await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
return this.validateSendEventResponse(response);
}

/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
// eslint-disable-next-line
public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(
roomId: string,
delayOpts: SendDelayedEventRequestOpts,
eventType: K,
content: StateEvents[K],
stateKey = "",
): Promise<SendDelayedEventResponse> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}

const response = await this.widgetApi.sendStateEvent(
eventType,
stateKey,
content,
roomId,
"delay" in delayOpts ? delayOpts.delay : undefined,

Check failure on line 317 in src/embedded.ts

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Expected 3-4 arguments, but got 6.
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
);
return this.validateSendDelayedEventResponse(response);
}

private validateSendEventResponse(response: ISendEventFromWidgetResponseData): ISendEventResponse {
if (response.event_id === undefined) {
throw new Error("'event_id' absent from response to an event request");
}
return { event_id: response.event_id };
}

private validateSendDelayedEventResponse(response: ISendEventFromWidgetResponseData): SendDelayedEventResponse {
if (!response.delay_id) {
throw new Error("'delay_id' absent from response to a delayed event request");
}
return { delay_id: response.delay_id };

Check failure on line 334 in src/embedded.ts

View workflow job for this annotation

GitHub Actions / Typescript Syntax Check

Type '{}' is not assignable to type 'string'.
}

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

0 comments on commit 43327d1

Please sign in to comment.