You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-07-31 15:24:23 +03:00
Device Dehydration | js-sdk: store/load dehydration key (#4599)
* feat(dehydrated): Use the dehydrated key cache API * feat(dehydrated): Add signalling to device dehydration manager * feat(dehydrated): fix unneeded call getCachedKey * Upgrade to `matrix-sdk-crypto-wasm` v13.0.0 * review: quick fix and doc * apply changes from review * apply changes from review * fix comment * add some tests and emit an event on rehydration failure * factor out event counter into a test util, since it may be useful elsewhere * adjust test to cover a few more lines * fix documentation * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * fix missing bracket * add test for getting the dehydration key from SSSS --------- Co-authored-by: Hubert Chathi <hubertc@matrix.org> Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
@ -50,7 +50,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^12.1.0",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^13.0.0",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^6.0.0",
|
||||
|
@ -17,11 +17,13 @@ limitations under the License.
|
||||
import "fake-indexeddb/auto";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import { createClient, ClientEvent, MatrixClient, MatrixEvent } from "../../../src";
|
||||
import { ClientEvent, createClient, MatrixClient, MatrixEvent } from "../../../src";
|
||||
import { CryptoEvent } from "../../../src/crypto-api/index";
|
||||
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
|
||||
import { AddSecretStorageKeyOpts } from "../../../src/secret-storage";
|
||||
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
|
||||
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
|
||||
import { emitPromise, EventCounter } from "../../test-utils/test-utils";
|
||||
|
||||
describe("Device dehydration", () => {
|
||||
it("should rehydrate and dehydrate a device", async () => {
|
||||
@ -40,6 +42,12 @@ describe("Device dehydration", () => {
|
||||
|
||||
await initializeSecretStorage(matrixClient, "@alice:localhost", "http://test.server");
|
||||
|
||||
const creationEventCounter = new EventCounter(matrixClient, CryptoEvent.DehydratedDeviceCreated);
|
||||
const dehydrationKeyCachedEventCounter = new EventCounter(matrixClient, CryptoEvent.DehydrationKeyCached);
|
||||
const rehydrationStartedCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationStarted);
|
||||
const rehydrationCompletedCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationCompleted);
|
||||
const rehydrationProgressCounter = new EventCounter(matrixClient, CryptoEvent.RehydrationProgress);
|
||||
|
||||
// count the number of times the dehydration key gets set
|
||||
let setDehydrationCount = 0;
|
||||
matrixClient.on(ClientEvent.AccountData, (event: MatrixEvent) => {
|
||||
@ -74,6 +82,8 @@ describe("Device dehydration", () => {
|
||||
await crypto.startDehydration();
|
||||
|
||||
expect(dehydrationCount).toEqual(1);
|
||||
expect(creationEventCounter.counter).toEqual(1);
|
||||
expect(dehydrationKeyCachedEventCounter.counter).toEqual(1);
|
||||
|
||||
// a week later, we should have created another dehydrated device
|
||||
const dehydrationPromise = new Promise<void>((resolve, reject) => {
|
||||
@ -81,7 +91,10 @@ describe("Device dehydration", () => {
|
||||
});
|
||||
jest.advanceTimersByTime(7 * 24 * 60 * 60 * 1000);
|
||||
await dehydrationPromise;
|
||||
|
||||
expect(dehydrationKeyCachedEventCounter.counter).toEqual(1);
|
||||
expect(dehydrationCount).toEqual(2);
|
||||
expect(creationEventCounter.counter).toEqual(2);
|
||||
|
||||
// restart dehydration -- rehydrate the device that we created above,
|
||||
// and create a new dehydrated device. We also set `createNewKey`, so
|
||||
@ -113,6 +126,39 @@ describe("Device dehydration", () => {
|
||||
expect(setDehydrationCount).toEqual(2);
|
||||
expect(eventsResponse.mock.calls).toHaveLength(2);
|
||||
|
||||
expect(rehydrationStartedCounter.counter).toEqual(1);
|
||||
expect(rehydrationCompletedCounter.counter).toEqual(1);
|
||||
expect(creationEventCounter.counter).toEqual(3);
|
||||
expect(rehydrationProgressCounter.counter).toEqual(1);
|
||||
expect(dehydrationKeyCachedEventCounter.counter).toEqual(2);
|
||||
|
||||
// test that if we get an error when we try to rotate, it emits an event
|
||||
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 500,
|
||||
body: {
|
||||
errcode: "M_UNKNOWN",
|
||||
error: "Unknown error",
|
||||
},
|
||||
});
|
||||
const rotationErrorEventPromise = emitPromise(matrixClient, CryptoEvent.DehydratedDeviceRotationError);
|
||||
jest.advanceTimersByTime(7 * 24 * 60 * 60 * 1000);
|
||||
await rotationErrorEventPromise;
|
||||
|
||||
// Restart dehydration, but return an error for GET /dehydrated_device so that rehydration fails.
|
||||
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 500,
|
||||
body: {
|
||||
errcode: "M_UNKNOWN",
|
||||
error: "Unknown error",
|
||||
},
|
||||
});
|
||||
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", (_, opts) => {
|
||||
return {};
|
||||
});
|
||||
const rehydrationErrorEventPromise = emitPromise(matrixClient, CryptoEvent.RehydrationError);
|
||||
await crypto.startDehydration(true);
|
||||
await rehydrationErrorEventPromise;
|
||||
|
||||
matrixClient.stopClient();
|
||||
});
|
||||
});
|
||||
|
@ -566,6 +566,19 @@ if (globalThis.Olm) {
|
||||
|
||||
export const emitPromise = (e: EventEmitter, k: string): Promise<any> => new Promise((r) => e.once(k, r));
|
||||
|
||||
/**
|
||||
* Counts the number of times that an event was emitted.
|
||||
*/
|
||||
export class EventCounter {
|
||||
public counter;
|
||||
constructor(emitter: EventEmitter, event: string) {
|
||||
this.counter = 0;
|
||||
emitter.on(event, () => {
|
||||
this.counter++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the fake timers in a loop until the given promise resolves or rejects.
|
||||
*
|
||||
|
@ -44,7 +44,7 @@ import {
|
||||
MemoryCryptoStore,
|
||||
TypedEventEmitter,
|
||||
} from "../../../src";
|
||||
import { mkEvent } from "../../test-utils/test-utils";
|
||||
import { emitPromise, mkEvent } from "../../test-utils/test-utils";
|
||||
import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend";
|
||||
import { IEventDecryptionResult, IMegolmSessionData } from "../../../src/@types/crypto";
|
||||
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
|
||||
@ -1739,6 +1739,110 @@ describe("RustCrypto", () => {
|
||||
});
|
||||
expect(await rustCrypto.isDehydrationSupported()).toBe(true);
|
||||
});
|
||||
|
||||
it("should load the dehydration key from SSSS if available", async () => {
|
||||
fetchMock.config.overwriteRoutes = true;
|
||||
|
||||
const secretStorageCallbacks = {
|
||||
getSecretStorageKey: async (keys: any, name: string) => {
|
||||
return [[...Object.keys(keys.keys)][0], new Uint8Array(32)];
|
||||
},
|
||||
} as SecretStorageCallbacks;
|
||||
const secretStorage = new ServerSideSecretStorageImpl(new DummyAccountDataClient(), secretStorageCallbacks);
|
||||
|
||||
// Create a RustCrypto to set up device dehydration.
|
||||
const e2eKeyReceiver1 = new E2EKeyReceiver("http://server");
|
||||
const e2eKeyResponder1 = new E2EKeyResponder("http://server");
|
||||
e2eKeyResponder1.addKeyReceiver(TEST_USER, e2eKeyReceiver1);
|
||||
fetchMock.get("path:/_matrix/client/v3/room_keys/version", {
|
||||
status: 404,
|
||||
body: {
|
||||
errcode: "M_NOT_FOUND",
|
||||
error: "Not found",
|
||||
},
|
||||
});
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/device_signing/upload", {
|
||||
status: 200,
|
||||
body: {},
|
||||
});
|
||||
fetchMock.post("path:/_matrix/client/v3/keys/signatures/upload", {
|
||||
status: 200,
|
||||
body: {},
|
||||
});
|
||||
const rustCrypto1 = await makeTestRustCrypto(makeMatrixHttpApi(), TEST_USER, TEST_DEVICE_ID, secretStorage);
|
||||
|
||||
// dehydration requires secret storage and cross signing
|
||||
async function createSecretStorageKey() {
|
||||
return {
|
||||
keyInfo: {} as AddSecretStorageKeyOpts,
|
||||
privateKey: new Uint8Array(32),
|
||||
};
|
||||
}
|
||||
await rustCrypto1.bootstrapCrossSigning({ setupNewCrossSigning: true });
|
||||
await rustCrypto1.bootstrapSecretStorage({
|
||||
createSecretStorageKey,
|
||||
setupNewSecretStorage: true,
|
||||
setupNewKeyBackup: false,
|
||||
});
|
||||
|
||||
// we need to process a sync so that the OlmMachine will upload keys
|
||||
await rustCrypto1.preprocessToDeviceMessages([]);
|
||||
await rustCrypto1.onSyncCompleted({});
|
||||
|
||||
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
status: 404,
|
||||
body: {
|
||||
errcode: "M_NOT_FOUND",
|
||||
error: "Not found",
|
||||
},
|
||||
});
|
||||
let dehydratedDeviceBody: any;
|
||||
fetchMock.put("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", (_, opts) => {
|
||||
dehydratedDeviceBody = JSON.parse(opts.body as string);
|
||||
return {};
|
||||
});
|
||||
await rustCrypto1.startDehydration();
|
||||
await rustCrypto1.stop();
|
||||
|
||||
// Create another RustCrypto, using the same SecretStorage, to
|
||||
// rehydrate the device.
|
||||
const e2eKeyReceiver2 = new E2EKeyReceiver("http://server");
|
||||
const e2eKeyResponder2 = new E2EKeyResponder("http://server");
|
||||
e2eKeyResponder2.addKeyReceiver(TEST_USER, e2eKeyReceiver2);
|
||||
|
||||
const rustCrypto2 = await makeTestRustCrypto(
|
||||
makeMatrixHttpApi(),
|
||||
TEST_USER,
|
||||
"ANOTHERDEVICE",
|
||||
secretStorage,
|
||||
);
|
||||
|
||||
// dehydration requires secret storage and cross signing
|
||||
await rustCrypto2.bootstrapCrossSigning({ setupNewCrossSigning: true });
|
||||
|
||||
// we need to process a sync so that the OlmMachine will upload keys
|
||||
await rustCrypto2.preprocessToDeviceMessages([]);
|
||||
await rustCrypto2.onSyncCompleted({});
|
||||
|
||||
fetchMock.get("path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device", {
|
||||
device_id: dehydratedDeviceBody.device_id,
|
||||
device_data: dehydratedDeviceBody.device_data,
|
||||
});
|
||||
fetchMock.post(
|
||||
`path:/_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device/${encodeURIComponent(dehydratedDeviceBody.device_id)}/events`,
|
||||
{
|
||||
events: [],
|
||||
next_batch: "token",
|
||||
},
|
||||
);
|
||||
|
||||
// We check that a RehydrationCompleted event gets emitted, which
|
||||
// means that the device was successfully rehydrated.
|
||||
const rehydrationCompletedPromise = emitPromise(rustCrypto2, CryptoEvent.RehydrationCompleted);
|
||||
await rustCrypto2.startDehydration();
|
||||
await rehydrationCompletedPromise;
|
||||
await rustCrypto2.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("import & export secrets bundle", () => {
|
||||
|
@ -2311,6 +2311,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
|
||||
CryptoEvent.KeysChanged,
|
||||
CryptoEvent.DevicesUpdated,
|
||||
CryptoEvent.WillUpdateDevices,
|
||||
CryptoEvent.DehydratedDeviceCreated,
|
||||
CryptoEvent.DehydratedDeviceUploaded,
|
||||
CryptoEvent.RehydrationStarted,
|
||||
CryptoEvent.RehydrationProgress,
|
||||
CryptoEvent.RehydrationCompleted,
|
||||
CryptoEvent.RehydrationError,
|
||||
CryptoEvent.DehydrationKeyCached,
|
||||
CryptoEvent.DehydratedDeviceRotationError,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -90,4 +90,63 @@ export enum CryptoEvent {
|
||||
* `progress === total === -1`.
|
||||
*/
|
||||
LegacyCryptoStoreMigrationProgress = "crypto.legacyCryptoStoreMigrationProgress",
|
||||
|
||||
/**
|
||||
* Fires when a new dehydrated device is created locally.
|
||||
*
|
||||
* After the client calls {@link CryptoApi.startDehydration}, this event
|
||||
* will be fired every time a new dehydrated device is created. It may fire
|
||||
* before `startDehydration` returns.
|
||||
*/
|
||||
DehydratedDeviceCreated = "dehydration.DehydratedDeviceCreated",
|
||||
|
||||
/**
|
||||
* Fires when a new dehydrated device is successfully uploaded to the server.
|
||||
*
|
||||
* This should fire shortly after {@link DehydratedDeviceCreated} fires. If
|
||||
* upload is unsuccessful, this will be reported either by an error thrown
|
||||
* by {@link CryptoApi.startDehydration} (for errors that happen before
|
||||
* `startDehydration` returns), or by firing {@link DehydratedDeviceRotationError}
|
||||
* (for errors that happen during regular rotation of the dehydrated device)
|
||||
*/
|
||||
DehydratedDeviceUploaded = "dehydration.DehydratedDeviceUploaded",
|
||||
|
||||
/**
|
||||
* Fires when rehydration has started.
|
||||
*
|
||||
* After the client calls {@link CryptoApi.startDehydration}, this event will
|
||||
* fire if a dehydrated device is found and we attempt to rehydrate it.
|
||||
*/
|
||||
RehydrationStarted = "dehydration.RehydrationStarted",
|
||||
|
||||
/**
|
||||
* Fires during rehydration, to inform the application of rehydration progress.
|
||||
*
|
||||
* The payload is a pair `[roomKeyCount: number, toDeviceCount: number]`,
|
||||
* where `roomKeyCount` is the number of room keys that have been received
|
||||
* so far, and `toDeviceCount` is the number of to-device messages received
|
||||
* so far (including the messages containing room keys).
|
||||
*/
|
||||
RehydrationProgress = "dehydration.RehydrationProgress",
|
||||
|
||||
/** Fires when rehydration has completed successfully. */
|
||||
RehydrationCompleted = "dehydration.RehydrationCompleted",
|
||||
|
||||
/** Fires when there was an error in rehydration.
|
||||
*
|
||||
* The payload is an error message as a string.
|
||||
*/
|
||||
RehydrationError = "dehydration.RehydrationError",
|
||||
|
||||
/**
|
||||
* Fires when a dehydrated device key has been cached in the local database.
|
||||
*/
|
||||
DehydrationKeyCached = "dehydration.DehydrationKeyCached",
|
||||
|
||||
/**
|
||||
* Fires when an error occurs during periodic rotation of the dehydrated device.
|
||||
*
|
||||
* The payload is an error message as a string.
|
||||
*/
|
||||
DehydratedDeviceRotationError = "dehydration.DehydratedDeviceRotationError",
|
||||
}
|
||||
|
@ -30,4 +30,12 @@ export type CryptoEventHandlerMap = {
|
||||
[CryptoEvent.WillUpdateDevices]: (users: string[], initialFetch: boolean) => void;
|
||||
[CryptoEvent.DevicesUpdated]: (users: string[], initialFetch: boolean) => void;
|
||||
[CryptoEvent.LegacyCryptoStoreMigrationProgress]: (progress: number, total: number) => void;
|
||||
[CryptoEvent.DehydratedDeviceCreated]: () => void;
|
||||
[CryptoEvent.DehydratedDeviceUploaded]: () => void;
|
||||
[CryptoEvent.RehydrationStarted]: () => void;
|
||||
[CryptoEvent.RehydrationProgress]: (roomKeyCount: number, toDeviceCount: number) => void;
|
||||
[CryptoEvent.RehydrationCompleted]: () => void;
|
||||
[CryptoEvent.RehydrationError]: (msg: string) => void;
|
||||
[CryptoEvent.DehydrationKeyCached]: () => void;
|
||||
[CryptoEvent.DehydratedDeviceRotationError]: (msg: string) => void;
|
||||
} & RustBackupCryptoEventMap;
|
||||
|
@ -21,8 +21,10 @@ import { encodeUri } from "../utils.ts";
|
||||
import { IHttpOpts, MatrixError, MatrixHttpApi, Method } from "../http-api/index.ts";
|
||||
import { IToDeviceEvent } from "../sync-accumulator.ts";
|
||||
import { ServerSideSecretStorage } from "../secret-storage.ts";
|
||||
import { decodeBase64, encodeUnpaddedBase64 } from "../base64.ts";
|
||||
import { decodeBase64 } from "../base64.ts";
|
||||
import { Logger } from "../logger.ts";
|
||||
import { CryptoEvent, CryptoEventHandlerMap } from "../crypto-api/index.ts";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
|
||||
/**
|
||||
* The response body of `GET /_matrix/client/unstable/org.matrix.msc3814.v1/dehydrated_device`.
|
||||
@ -67,9 +69,7 @@ const DEHYDRATION_INTERVAL = 7 * 24 * 60 * 60 * 1000;
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class DehydratedDeviceManager {
|
||||
/** the secret key used for dehydrating and rehydrating */
|
||||
private key?: Uint8Array;
|
||||
export class DehydratedDeviceManager extends TypedEventEmitter<DehydratedDevicesEvents, DehydratedDevicesEventMap> {
|
||||
/** the ID of the interval for periodically replacing the dehydrated device */
|
||||
private intervalId?: ReturnType<typeof setInterval>;
|
||||
|
||||
@ -79,7 +79,14 @@ export class DehydratedDeviceManager {
|
||||
private readonly http: MatrixHttpApi<IHttpOpts & { onlyData: true }>,
|
||||
private readonly outgoingRequestProcessor: OutgoingRequestProcessor,
|
||||
private readonly secretStorage: ServerSideSecretStorage,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private async cacheKey(key: RustSdkCryptoJs.DehydratedDeviceKey): Promise<void> {
|
||||
await this.olmMachine.dehydratedDevices().saveDehydratedDeviceKey(key);
|
||||
this.emit(CryptoEvent.DehydrationKeyCached);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the server supports dehydrated devices.
|
||||
@ -133,6 +140,7 @@ export class DehydratedDeviceManager {
|
||||
// If rehydration fails, there isn't much we can do about it. Log
|
||||
// the error, and create a new device.
|
||||
this.logger.info("dehydration: Error rehydrating device:", e);
|
||||
this.emit(CryptoEvent.RehydrationError, (e as Error).message);
|
||||
}
|
||||
if (createNewKey) {
|
||||
await this.resetKey();
|
||||
@ -151,12 +159,15 @@ export class DehydratedDeviceManager {
|
||||
* Reset the dehydration key.
|
||||
*
|
||||
* Creates a new key and stores it in secret storage.
|
||||
*
|
||||
* @returns The newly-generated key.
|
||||
*/
|
||||
public async resetKey(): Promise<void> {
|
||||
const key = new Uint8Array(32);
|
||||
globalThis.crypto.getRandomValues(key);
|
||||
await this.secretStorage.store(SECRET_STORAGE_NAME, encodeUnpaddedBase64(key));
|
||||
this.key = key;
|
||||
public async resetKey(): Promise<RustSdkCryptoJs.DehydratedDeviceKey> {
|
||||
const key = RustSdkCryptoJs.DehydratedDeviceKey.createRandomKey();
|
||||
await this.secretStorage.store(SECRET_STORAGE_NAME, key.toBase64());
|
||||
// Also cache it in the rust SDK's crypto store.
|
||||
await this.cacheKey(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,19 +177,27 @@ export class DehydratedDeviceManager {
|
||||
*
|
||||
* @returns the key, if available, or `null` if no key is available
|
||||
*/
|
||||
private async getKey(create: boolean): Promise<Uint8Array | null> {
|
||||
if (this.key === undefined) {
|
||||
private async getKey(create: boolean): Promise<RustSdkCryptoJs.DehydratedDeviceKey | null> {
|
||||
const cachedKey = await this.olmMachine.dehydratedDevices().getDehydratedDeviceKey();
|
||||
if (cachedKey) return cachedKey;
|
||||
const keyB64 = await this.secretStorage.get(SECRET_STORAGE_NAME);
|
||||
if (keyB64 === undefined) {
|
||||
if (!create) {
|
||||
return null;
|
||||
}
|
||||
await this.resetKey();
|
||||
} else {
|
||||
this.key = decodeBase64(keyB64);
|
||||
return await this.resetKey();
|
||||
}
|
||||
|
||||
// We successfully found the key in secret storage: decode it, and cache it in
|
||||
// the rust SDK's crypto store.
|
||||
const bytes = decodeBase64(keyB64);
|
||||
try {
|
||||
const key = RustSdkCryptoJs.DehydratedDeviceKey.createKeyFromArray(bytes);
|
||||
await this.cacheKey(key);
|
||||
return key;
|
||||
} finally {
|
||||
bytes.fill(0);
|
||||
}
|
||||
return this.key!;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,6 +238,7 @@ export class DehydratedDeviceManager {
|
||||
}
|
||||
|
||||
this.logger.info("dehydration: dehydrated device found");
|
||||
this.emit(CryptoEvent.RehydrationStarted);
|
||||
|
||||
const rehydratedDevice = await this.olmMachine
|
||||
.dehydratedDevices()
|
||||
@ -255,8 +275,11 @@ export class DehydratedDeviceManager {
|
||||
nextBatch = eventResp.next_batch;
|
||||
const roomKeyInfos = await rehydratedDevice.receiveEvents(JSON.stringify(eventResp.events));
|
||||
roomKeyCount += roomKeyInfos.length;
|
||||
|
||||
this.emit(CryptoEvent.RehydrationProgress, roomKeyCount, toDeviceCount);
|
||||
}
|
||||
this.logger.info(`dehydration: received ${roomKeyCount} room keys from ${toDeviceCount} to-device events`);
|
||||
this.emit(CryptoEvent.RehydrationCompleted);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -270,9 +293,11 @@ export class DehydratedDeviceManager {
|
||||
const key = (await this.getKey(true))!;
|
||||
|
||||
const dehydratedDevice = await this.olmMachine.dehydratedDevices().create();
|
||||
this.emit(CryptoEvent.DehydratedDeviceCreated);
|
||||
const request = await dehydratedDevice.keysForUpload("Dehydrated device", key);
|
||||
|
||||
await this.outgoingRequestProcessor.makeOutgoingRequest(request);
|
||||
this.emit(CryptoEvent.DehydratedDeviceUploaded);
|
||||
|
||||
this.logger.info("dehydration: uploaded device");
|
||||
}
|
||||
@ -287,6 +312,7 @@ export class DehydratedDeviceManager {
|
||||
await this.createAndUploadDehydratedDevice();
|
||||
this.intervalId = setInterval(() => {
|
||||
this.createAndUploadDehydratedDevice().catch((error) => {
|
||||
this.emit(CryptoEvent.DehydratedDeviceRotationError, error.message);
|
||||
this.logger.error("Error creating dehydrated device:", error);
|
||||
});
|
||||
}, DEHYDRATION_INTERVAL);
|
||||
@ -304,3 +330,23 @@ export class DehydratedDeviceManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The events fired by the DehydratedDeviceManager
|
||||
* @internal
|
||||
*/
|
||||
type DehydratedDevicesEvents =
|
||||
| CryptoEvent.DehydratedDeviceCreated
|
||||
| CryptoEvent.DehydratedDeviceUploaded
|
||||
| CryptoEvent.RehydrationStarted
|
||||
| CryptoEvent.RehydrationProgress
|
||||
| CryptoEvent.RehydrationCompleted
|
||||
| CryptoEvent.RehydrationError
|
||||
| CryptoEvent.DehydrationKeyCached
|
||||
| CryptoEvent.DehydratedDeviceRotationError;
|
||||
|
||||
/**
|
||||
* A map of the {@link DehydratedDeviceEvents} fired by the {@link DehydratedDeviceManager} and their payloads.
|
||||
* @internal
|
||||
*/
|
||||
type DehydratedDevicesEventMap = Pick<CryptoEventHandlerMap, DehydratedDevicesEvents>;
|
||||
|
@ -183,12 +183,23 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, CryptoEventH
|
||||
);
|
||||
this.eventDecryptor = new EventDecryptor(this.logger, olmMachine, this.perSessionBackupDownloader);
|
||||
|
||||
// re-emit the events emitted by managers
|
||||
this.reemitter.reEmit(this.backupManager, [
|
||||
CryptoEvent.KeyBackupStatus,
|
||||
CryptoEvent.KeyBackupSessionsRemaining,
|
||||
CryptoEvent.KeyBackupFailed,
|
||||
CryptoEvent.KeyBackupDecryptionKeyCached,
|
||||
]);
|
||||
this.reemitter.reEmit(this.dehydratedDeviceManager, [
|
||||
CryptoEvent.DehydratedDeviceCreated,
|
||||
CryptoEvent.DehydratedDeviceUploaded,
|
||||
CryptoEvent.RehydrationStarted,
|
||||
CryptoEvent.RehydrationProgress,
|
||||
CryptoEvent.RehydrationCompleted,
|
||||
CryptoEvent.RehydrationError,
|
||||
CryptoEvent.DehydrationKeyCached,
|
||||
CryptoEvent.DehydratedDeviceRotationError,
|
||||
]);
|
||||
|
||||
this.crossSigningIdentity = new CrossSigningIdentity(olmMachine, this.outgoingRequestProcessor, secretStorage);
|
||||
|
||||
|
@ -1477,10 +1477,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^12.1.0":
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-12.1.0.tgz#2aef64eab2d30c0a1ace9c0fe876f53aa2949f14"
|
||||
integrity sha512-NhJFu/8FOGjnW7mDssRUzaMSwXrYOcCqgAjZyAw9KQ9unNADKEi7KoIKe7GtrG2PWtm36y2bUf+hB8vhSY6Wdw==
|
||||
"@matrix-org/matrix-sdk-crypto-wasm@^13.0.0":
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-13.0.0.tgz#658bed951e4c8a06a6dd545575a79cf32022d4ba"
|
||||
integrity sha512-2gtpjnxL42sdJAgkwitpMMI4cw7Gcjf5sW0MXoe+OAlXPlxIzyM+06F5JJ8ENvBeHkuV2RqtFIRrh8i90HLsMw==
|
||||
|
||||
"@matrix-org/olm@3.2.15":
|
||||
version "3.2.15"
|
||||
|
Reference in New Issue
Block a user