diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 50fe0652e9b..6053152ef9b 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -662,24 +662,28 @@ describe("SlidingSyncSdk", () => { }); it("can update device lists", () => { + client!.crypto!.processDeviceLists = jest.fn(); ext.onResponse({ device_lists: { changed: ["@alice:localhost"], left: ["@bob:localhost"], }, }); - // TODO: more assertions? + expect(client!.crypto!.processDeviceLists).toHaveBeenCalledWith( + { oldSyncToken: "yep" }, + { changed: ["@alice:localhost"], left: ["@bob:localhost"] }, + ); }); - it("can update OTK counts", () => { + it("can update OTK counts", async () => { client!.crypto!.updateOneTimeKeyCount = jest.fn(); - ext.onResponse({ + await ext.onResponse({ device_one_time_keys_count: { signed_curve25519: 42, }, }); expect(client!.crypto!.updateOneTimeKeyCount).toHaveBeenCalledWith(42); - ext.onResponse({ + await ext.onResponse({ device_one_time_keys_count: { not_signed_curve25519: 42, // missing field -> default to 0 @@ -688,12 +692,12 @@ describe("SlidingSyncSdk", () => { expect(client!.crypto!.updateOneTimeKeyCount).toHaveBeenCalledWith(0); }); - it("can update fallback keys", () => { - ext.onResponse({ + it("can update fallback keys", async () => { + await ext.onResponse({ device_unused_fallback_key_types: ["signed_curve25519"], }); expect(client!.crypto!.getNeedsNewFallback()).toEqual(false); - ext.onResponse({ + await ext.onResponse({ device_unused_fallback_key_types: ["not_signed_curve25519"], }); expect(client!.crypto!.getNeedsNewFallback()).toEqual(true); diff --git a/src/common-crypto/CryptoBackend.ts b/src/common-crypto/CryptoBackend.ts index afe77d17759..342e751739c 100644 --- a/src/common-crypto/CryptoBackend.ts +++ b/src/common-crypto/CryptoBackend.ts @@ -21,6 +21,8 @@ import { CryptoApi } from "../crypto-api"; import { DeviceTrustLevel, UserTrustLevel } from "../crypto/CrossSigning"; import { IEncryptedEventInfo } from "../crypto/api"; import { IEventDecryptionResult } from "../@types/crypto"; +import { ISyncStateData } from "../sync"; +import { ISyncResponse } from "../sync-accumulator"; /** * Common interface for the crypto implementations @@ -113,6 +115,16 @@ export interface SyncCryptoCallbacks { */ processKeyCounts(oneTimeKeysCounts?: Record, unusedFallbackKeys?: string[]): Promise; + /** + * Handle the notification from /sync or /keys/changes that device lists have + * been changed. + * + * @param syncData - Object containing sync tokens associated with this sync + * @param deviceLists - device_lists field from /sync, or response from + * /keys/changes + */ + processDeviceLists(syncData: ISyncStateData, deviceLists?: Required["device_lists"]): Promise; + /** * Called by the /sync loop whenever an m.room.encryption event is received. * diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 4c10b1c1658..a33da88ad9a 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -2922,16 +2922,16 @@ export class Crypto extends TypedEventEmitter["device_lists"], + deviceLists?: Required["device_lists"], ): Promise { // Initial syncs don't have device change lists. We'll either get the complete list // of changes for the interval or will have invalidated everything in willProcessSync - if (!syncData.oldSyncToken) return; + if (!syncData.oldSyncToken || !deviceLists) return; // Here, we're relying on the fact that we only ever save the sync data after // sucessfully saving the device list data, so we're guaranteed that the device @@ -2941,7 +2941,7 @@ export class Crypto extends TypedEventEmitter(), unusedFallbackKeys = new Set(), + devices = new RustSdkCryptoJs.DeviceLists(), }: { events?: IToDeviceEvent[]; oneTimeKeysCounts?: Map; unusedFallbackKeys?: Set; + devices?: RustSdkCryptoJs.DeviceLists; }): Promise { const result = await this.olmMachine.receiveSyncChanges( events ? JSON.stringify(events) : "[]", - new RustSdkCryptoJs.DeviceLists(), + devices, oneTimeKeysCounts, unusedFallbackKeys, ); @@ -229,6 +234,23 @@ export class RustCrypto implements CryptoBackend { } } + /** called by the sync loop to process + * + * @param syncData - Object containing sync tokens associated with this sync + * @param deviceLists - device_lists field from /sync, or response from + */ + public async processDeviceLists( + syncData: ISyncStateData, + deviceLists: Required["device_lists"], + ): Promise { + // Initial syncs don't have device change lists. We'll either get the complete list + // of changes for the interval or will have invalidated everything in willProcessSync + if (!syncData.oldSyncToken || !deviceLists) return; + + const devices = new RustSdkCryptoJs.DeviceLists(deviceLists.changed, deviceLists.left); + await this.receiveSyncChanges({ devices }); + } + /** called by the sync loop on m.room.encrypted events * * @param room - in which the event was received diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 93e29e0baa3..1afd15d2546 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -85,14 +85,12 @@ class ExtensionE2EE implements Extension { // Handle device list updates - if (data["device_lists"]) { - await this.crypto.handleDeviceListChanges( - { - oldSyncToken: "yep", // XXX need to do this so the device list changes get processed :( - }, - data["device_lists"], - ); - } + await this.crypto.processDeviceLists( + { + oldSyncToken: "yep", // XXX need to do this so the device list changes get processed :( + }, + data["device_lists"], + ); // Handle one_time_keys_count if (data["device_one_time_keys_count"]) { diff --git a/src/sync.ts b/src/sync.ts index ec7bd5244f3..607729c83c1 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1514,14 +1514,12 @@ export class SyncApi { } // Handle device list updates - if (data.device_lists) { - if (this.syncOpts.crypto) { - await this.syncOpts.crypto.handleDeviceListChanges(syncEventData, data.device_lists); - } else { - // FIXME if we *don't* have a crypto module, we still need to - // invalidate the device lists. But that would require a - // substantial bit of rework :/. - } + if (this.syncOpts.cryptoCallbacks) { + await this.syncOpts.cryptoCallbacks.processDeviceLists(syncEventData, data.device_lists); + } else { + // FIXME if we *don't* have a crypto module, we still need to + // invalidate the device lists. But that would require a + // substantial bit of rework :/. } // Handle one_time_keys_count and unused fallback keys