Skip to content

Commit

Permalink
Use new CryptoApi.encryptToDeviceMessages() to send encrypted to-devi…
Browse files Browse the repository at this point in the history
…ce messages from widgets (#28315)
  • Loading branch information
hughns authored Oct 30, 2024
1 parent 5c45ca5 commit c23c9df
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 16 deletions.
54 changes: 41 additions & 13 deletions src/stores/widgets/StopGapWidgetDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,26 +416,54 @@ export class StopGapWidgetDriver extends WidgetDriver {

/**
* Implements {@link WidgetDriver#sendToDevice}
* Encrypted to-device events are not supported.
*/
public async sendToDevice(
eventType: string,
encrypted: boolean,
contentMap: { [userId: string]: { [deviceId: string]: object } },
): Promise<void> {
if (encrypted) throw new Error("Encrypted to-device events are not supported");

const client = MatrixClientPeg.safeGet();
await client.queueToDevice({
eventType,
batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
Object.entries(userContentMap).map(([deviceId, content]) => ({
userId,
deviceId,
payload: content,
})),
),
});

if (encrypted) {
const crypto = client.getCrypto();
if (!crypto) throw new Error("E2EE not enabled");

// attempt to re-batch these up into a single request
const invertedContentMap: { [content: string]: { userId: string; deviceId: string }[] } = {};

for (const userId of Object.keys(contentMap)) {
const userContentMap = contentMap[userId];
for (const deviceId of Object.keys(userContentMap)) {
const content = userContentMap[deviceId];
const stringifiedContent = JSON.stringify(content);
invertedContentMap[stringifiedContent] = invertedContentMap[stringifiedContent] || [];
invertedContentMap[stringifiedContent].push({ userId, deviceId });
}
}

await Promise.all(
Object.entries(invertedContentMap).map(async ([stringifiedContent, recipients]) => {
const batch = await crypto.encryptToDeviceMessages(
eventType,
recipients,
JSON.parse(stringifiedContent),
);

await client.queueToDevice(batch);
}),
);
} else {
await client.queueToDevice({
eventType,
batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
Object.entries(userContentMap).map(([deviceId, content]) => ({
userId,
deviceId,
payload: content,
})),
),
});
}
}

private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
Expand Down
68 changes: 65 additions & 3 deletions test/unit-tests/stores/widgets/StopGapWidgetDriver-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,72 @@ describe("StopGapWidgetDriver", () => {
});
});

it("raises an error if encrypted", async () => {
await expect(driver.sendToDevice("org.example.foo", true, contentMap)).rejects.toThrow(
"Encrypted to-device events are not supported",
it("sends encrypted messages", async () => {
const encryptToDeviceMessages = jest
.fn()
.mockImplementation(
(eventType, recipients: { userId: string; deviceId: string }[], content: object) => ({
eventType: "m.room.encrypted",
batch: recipients.map(({ userId, deviceId }) => ({
userId,
deviceId,
payload: {
eventType,
content,
},
})),
}),
);

MatrixClientPeg.safeGet().getCrypto()!.encryptToDeviceMessages = encryptToDeviceMessages;

await driver.sendToDevice("org.example.foo", true, {
"@alice:example.org": {
aliceMobile: {
hello: "alice",
},
},
"@bob:example.org": {
bobDesktop: {
hello: "bob",
},
},
});

expect(encryptToDeviceMessages).toHaveBeenCalledWith(
"org.example.foo",
[{ deviceId: "aliceMobile", userId: "@alice:example.org" }],
{
hello: "alice",
},
);
expect(encryptToDeviceMessages).toHaveBeenCalledWith(
"org.example.foo",
[{ deviceId: "bobDesktop", userId: "@bob:example.org" }],
{
hello: "bob",
},
);
expect(client.queueToDevice).toHaveBeenCalledWith({
eventType: "m.room.encrypted",
batch: expect.arrayContaining([
{
deviceId: "aliceMobile",
payload: { content: { hello: "alice" }, eventType: "org.example.foo" },
userId: "@alice:example.org",
},
]),
});
expect(client.queueToDevice).toHaveBeenCalledWith({
eventType: "m.room.encrypted",
batch: expect.arrayContaining([
{
deviceId: "bobDesktop",
payload: { content: { hello: "bob" }, eventType: "org.example.foo" },
userId: "@bob:example.org",
},
]),
});
});
});

Expand Down

0 comments on commit c23c9df

Please sign in to comment.