Skip to content

Commit

Permalink
Element R: emit events when devices have changed (#4019)
Browse files Browse the repository at this point in the history
* emit events when Rust crypto wasm tells us devices have changed

* lint

* add missing stub function

* apply workaround for queueMicrotask
  • Loading branch information
uhoreg authored Jan 31, 2024
1 parent f810363 commit d178fbf
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 1 deletion.
28 changes: 28 additions & 0 deletions spec/unit/rust-crypto/rust-crypto.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe("initRustCrypto", () => {
getSecretsFromInbox: jest.fn().mockResolvedValue([]),
deleteSecretsFromInbox: jest.fn(),
registerReceiveSecretCallback: jest.fn(),
registerDevicesUpdatedCallback: jest.fn(),
outgoingRequests: jest.fn(),
isBackupEnabled: jest.fn().mockResolvedValue(false),
verifyBackup: jest.fn().mockResolvedValue({ trusted: jest.fn().mockReturnValue(false) }),
Expand Down Expand Up @@ -1132,6 +1133,33 @@ describe("RustCrypto", () => {
rustCrypto.stop();
});

it("should emit events on device changes", async () => {
jest.useFakeTimers({ doNotFake: ["queueMicrotask"] });

fetchMock.post("path:/_matrix/client/v3/keys/upload", { one_time_key_counts: {} });
fetchMock.post("path:/_matrix/client/v3/keys/query", {
device_keys: {
[testData.TEST_USER_ID]: {
[testData.TEST_DEVICE_ID]: testData.SIGNED_TEST_DEVICE_DATA,
},
},
});

const rustCrypto = await makeTestRustCrypto(makeMatrixHttpApi(), testData.TEST_USER_ID);
const willUpdateCallback = jest.fn();
rustCrypto.on(CryptoEvent.WillUpdateDevices, willUpdateCallback);
const devicesUpdatedCallback = jest.fn();
rustCrypto.on(CryptoEvent.DevicesUpdated, devicesUpdatedCallback);

rustCrypto.onSyncCompleted({});

// wait for the devices to be updated
await rustCrypto.getUserDeviceInfo([testData.TEST_USER_ID]);
expect(willUpdateCallback).toHaveBeenCalledWith([testData.TEST_USER_ID], false);
expect(devicesUpdatedCallback).toHaveBeenCalledWith([testData.TEST_USER_ID], false);
rustCrypto.stop();
});

describe("requestDeviceVerification", () => {
it("throws an error if the device is unknown", async () => {
const rustCrypto = await makeTestRustCrypto();
Expand Down
3 changes: 3 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2360,6 +2360,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
CryptoEvent.KeyBackupSessionsRemaining,
CryptoEvent.KeyBackupFailed,
CryptoEvent.KeyBackupDecryptionKeyCached,
CryptoEvent.KeysChanged,
CryptoEvent.DevicesUpdated,
CryptoEvent.WillUpdateDevices,
]);
}

Expand Down
1 change: 1 addition & 0 deletions src/rust-crypto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ async function initOlmMachine(
await olmMachine.registerUserIdentityUpdatedCallback((userId: RustSdkCryptoJs.UserId) =>
rustCrypto.onUserIdentityUpdated(userId),
);
await olmMachine.registerDevicesUpdatedCallback((userIds: string[]) => rustCrypto.onDevicesUpdated(userIds));

// Check if there are any key backup secrets pending processing. There may be multiple secrets to process if several devices have gossiped them.
// The `registerReceiveSecretCallback` function will only be triggered for new secrets. If the client is restarted before processing them, the secrets will need to be manually handled.
Expand Down
48 changes: 47 additions & 1 deletion src/rust-crypto/rust-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
* Callback for `OlmMachine.registerUserIdentityUpdatedCallback`
*
* Called by the rust-sdk whenever there is an update to any user's cross-signing status. We re-check their trust
* status and emit a `UserTrustStatusChanged` event.
* status and emit a `UserTrustStatusChanged` event, as well as a `KeysChanged` if it is our own identity that changed.
*
* @param userId - the user with the updated identity
*/
Expand All @@ -1452,10 +1452,26 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
// If our own user identity has changed, we may now trust the key backup where we did not before.
// So, re-check the key backup status and enable it if available.
if (userId.toString() === this.userId) {
this.emit(CryptoEvent.KeysChanged, {});
await this.checkKeyBackupAndEnable();
}
}

/**
* Callback for `OlmMachine.registerDevicesUpdatedCallback`
*
* Called when users' devices have updated. Emits `WillUpdateDevices` and `DevicesUpdated`. In the JavaScript
* crypto backend, these events are called at separate times, with `WillUpdateDevices` being emitted just before
* the devices are saved, and `DevicesUpdated` being emitted just after. But the OlmMachine only gives us
* one event, so we emit both events here.
*
* @param userIds - an array of user IDs of users whose devices have updated.
*/
public async onDevicesUpdated(userIds: string[]): Promise<void> {
this.emit(CryptoEvent.WillUpdateDevices, userIds, false);
this.emit(CryptoEvent.DevicesUpdated, userIds, false);
}

/**
* Handles secret received from the rust secret inbox.
*
Expand Down Expand Up @@ -1828,6 +1844,9 @@ function rustEncryptionInfoToJsEncryptionInfo(
type RustCryptoEvents =
| CryptoEvent.VerificationRequestReceived
| CryptoEvent.UserTrustStatusChanged
| CryptoEvent.KeysChanged
| CryptoEvent.WillUpdateDevices
| CryptoEvent.DevicesUpdated
| RustBackupCryptoEvents;

type RustCryptoEventMap = {
Expand All @@ -1842,4 +1861,31 @@ type RustCryptoEventMap = {
[CryptoEvent.UserTrustStatusChanged]: (userId: string, userTrustLevel: UserVerificationStatus) => void;

[CryptoEvent.KeyBackupDecryptionKeyCached]: (version: string) => void;
/**
* Fires when the user's cross-signing keys have changed or cross-signing
* has been enabled/disabled. The client can use getStoredCrossSigningForUser
* with the user ID of the logged in user to check if cross-signing is
* enabled on the account. If enabled, it can test whether the current key
* is trusted using with checkUserTrust with the user ID of the logged
* in user. The checkOwnCrossSigningTrust function may be used to reconcile
* the trust in the account key.
*
* The cross-signing API is currently UNSTABLE and may change without notice.
* @experimental
*/
[CryptoEvent.KeysChanged]: (data: {}) => void;
/**
* Fires whenever the stored devices for a user will be updated
* @param users - A list of user IDs that will be updated
* @param initialFetch - If true, the store is empty (apart
* from our own device) and is being seeded.
*/
[CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void;
/**
* Fires whenever the stored devices for a user have changed
* @param users - A list of user IDs that were updated
* @param initialFetch - If true, the store was empty (apart
* from our own device) and has been seeded.
*/
[CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void;
} & RustBackupCryptoEventMap;

0 comments on commit d178fbf

Please sign in to comment.