Use new CryptoApi.encryptToDeviceMessages() to send encrypted to-device messages from widgets (#28315)
This commit is contained in:
parent
5c45ca5e3c
commit
c23c9dfacb
2 changed files with 106 additions and 16 deletions
|
@ -416,26 +416,54 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements {@link WidgetDriver#sendToDevice}
|
* Implements {@link WidgetDriver#sendToDevice}
|
||||||
* Encrypted to-device events are not supported.
|
|
||||||
*/
|
*/
|
||||||
public async sendToDevice(
|
public async sendToDevice(
|
||||||
eventType: string,
|
eventType: string,
|
||||||
encrypted: boolean,
|
encrypted: boolean,
|
||||||
contentMap: { [userId: string]: { [deviceId: string]: object } },
|
contentMap: { [userId: string]: { [deviceId: string]: object } },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (encrypted) throw new Error("Encrypted to-device events are not supported");
|
|
||||||
|
|
||||||
const client = MatrixClientPeg.safeGet();
|
const client = MatrixClientPeg.safeGet();
|
||||||
await client.queueToDevice({
|
|
||||||
eventType,
|
if (encrypted) {
|
||||||
batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
|
const crypto = client.getCrypto();
|
||||||
Object.entries(userContentMap).map(([deviceId, content]) => ({
|
if (!crypto) throw new Error("E2EE not enabled");
|
||||||
userId,
|
|
||||||
deviceId,
|
// attempt to re-batch these up into a single request
|
||||||
payload: content,
|
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[] {
|
private pickRooms(roomIds?: (string | Symbols.AnyRoom)[]): Room[] {
|
||||||
|
|
|
@ -201,10 +201,72 @@ describe("StopGapWidgetDriver", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("raises an error if encrypted", async () => {
|
it("sends encrypted messages", async () => {
|
||||||
await expect(driver.sendToDevice("org.example.foo", true, contentMap)).rejects.toThrow(
|
const encryptToDeviceMessages = jest
|
||||||
"Encrypted to-device events are not supported",
|
.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",
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue