From f10d39fee7a5953f85babe81998357d562f4443c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 8 Dec 2022 17:48:33 -0500 Subject: [PATCH 001/123] Update ownership metadata --- .github/CODEOWNERS | 2 +- LICENSE | 1 + README.md | 2 +- docs/index.md | 6 +++--- package.json | 10 +++++----- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b5cd5127..c241a616 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* turt2live +* @vector-im/bridges diff --git a/LICENSE b/LICENSE index ba1f98c7..5862332b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ MIT License +Copyright (c) 2022 New Vector Ltd Copyright (c) 2018 - 2022 Travis Ralston Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 33d05d5d..da2b7c0f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # matrix-bot-sdk -[![npm version](https://badge.fury.io/js/matrix-bot-sdk.svg)](https://www.npmjs.com/package/matrix-bot-sdk) +[![npm version](https://badge.fury.io/js/@vector-im%2Fmatrix-bot-sdk.svg)](https://www.npmjs.com/package/@vector-im/matrix-bot-sdk) TypeScript/JavaScript SDK for Matrix bots. For help and support, visit [#matrix-bot-sdk:t2bot.io](https://matrix.to/#/#matrix-bot-sdk:t2bot.io) diff --git a/docs/index.md b/docs/index.md index 291897b9..7164bf4c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -[![npm version](https://badge.fury.io/js/matrix-bot-sdk.svg)](https://www.npmjs.com/package/matrix-bot-sdk) +[![npm version](https://badge.fury.io/js/@vector-im%2Fmatrix-bot-sdk.svg)](https://www.npmjs.com/package/@vector-im/matrix-bot-sdk) TypeScript/JavaScript SDK for Matrix bots. For help and support, visit [#matrix-bot-sdk:t2bot.io](https://matrix.to/#/#matrix-bot-sdk:t2bot.io) @@ -11,7 +11,7 @@ TypeScript/JavaScript SDK for Matrix bots. For help and support, visit [#matrix- ## Installing -This package can be found on [npm](https://www.npmjs.com/package/matrix-bot-sdk): +This package can be found on [npm](https://www.npmjs.com/package/@vector-im/matrix-bot-sdk): ``` -npm install matrix-bot-sdk +npm install @vector-im/matrix-bot-sdk ``` diff --git a/package.json b/package.json index 0f4e8359..51e24882 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,17 @@ { - "name": "matrix-bot-sdk", + "name": "@vector-im/matrix-bot-sdk", "version": "develop", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", - "url": "git+https://github.com/turt2live/matrix-bot-sdk.git" + "url": "git+https://github.com/vector-im/matrix-bot-sdk.git" }, - "author": "turt2live", + "author": "Element", "license": "MIT", "bugs": { - "url": "https://github.com/turt2live/matrix-bot-sdk/issues" + "url": "https://github.com/vector-im/matrix-bot-sdk/issues" }, - "homepage": "https://github.com/turt2live/matrix-bot-sdk#readme", + "homepage": "https://github.com/vector-im/matrix-bot-sdk#readme", "scripts": { "prepublishOnly": "yarn build", "docs": "jsdoc -c jsdoc.json -P package.json -u docs/tutorials", From 5d625116977a625c373115b0e982aa8e49a13334 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 8 Dec 2022 00:10:07 -0500 Subject: [PATCH 002/123] Query & claim needed keys before encrypting (#270) Ensure that the bot has all keys needed for sharing a room key with recipients before encryping an event in that room. Signed-off-by: Andrew Ferrazzutti --- src/e2ee/RustEngine.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 7cbed950..8ab7fecd 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -32,10 +32,11 @@ export class RustEngine { public constructor(public readonly machine: OlmMachine, private client: MatrixClient) { } - public async run() { + public async run(...types: RequestType[]) { // Note: we should not be running this until it runs out, so cache the value into a variable const requests = await this.machine.outgoingRequests(); for (const request of requests) { + if (types.length && !types.includes(request.type)) continue; switch (request.type) { case RequestType.KeysUpload: await this.processKeysUploadRequest(request); @@ -106,6 +107,14 @@ export class RustEngine { settings.rotationPeriod = BigInt(encEv.rotationPeriodMs); settings.rotationPeriodMessages = BigInt(encEv.rotationPeriodMessages); + await this.run(RequestType.KeysQuery); + await this.lock.acquire(SYNC_LOCK_NAME, async () => { + const keysClaim = await this.machine.getMissingSessions(members); + if (keysClaim) { + await this.processKeysClaimRequest(keysClaim); + } + }); + await this.lock.acquire(roomId, async () => { const requests = JSON.parse(await this.machine.shareRoomKey(new RoomId(roomId), members, settings)); for (const req of requests) { From 6e9c7007ff83bf603310c5d8655cc17ebfd82fce Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 8 Dec 2022 00:43:44 -0500 Subject: [PATCH 003/123] Handle pre-shared invite keys (#271) * Handle pre-shared invite keys Signed-off-by: Andrew Ferrazzutti * Use assignment for changes to membership array * Catch member lookup errors in prepareEncrypt Treat an error in looking up room members of a particular membership type as there being no members of that type. Return early if no members are found. * Resolve conflict on `members` variable Signed-off-by: Andrew Ferrazzutti --- src/e2ee/RustEngine.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 8ab7fecd..f9664b66 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -14,9 +14,11 @@ import { import * as AsyncLock from "async-lock"; import { MatrixClient } from "../MatrixClient"; +import { extractRequestError, LogService } from "../logging/LogService"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; import { EncryptionAlgorithm } from "../models/Crypto"; import { EncryptionEvent } from "../models/events/EncryptionEvent"; +import { Membership } from "../models/events/MembershipEvent"; /** * @internal @@ -75,9 +77,7 @@ export class RustEngine { } public async prepareEncrypt(roomId: string, roomInfo: ICryptoRoomInformation) { - // TODO: Handle pre-shared invite keys too - const members = (await this.client.getJoinedRoomMembers(roomId)).map(u => new UserId(u)); - + let memberships: Membership[] = ["join", "invite"]; let historyVis = HistoryVisibility.Joined; switch (roomInfo.historyVisibility) { case "world_readable": @@ -91,8 +91,23 @@ export class RustEngine { break; case "joined": default: - // Default and other cases handled by assignment before switch + memberships = ["join"]; + } + + const members = new Set(); + for (const membership of memberships) { + try { + (await this.client.getRoomMembersByMembership(roomId, membership)) + .map(u => new UserId(u.membershipFor)) + .forEach(u => void members.add(u)); + } catch (err) { + LogService.warn("RustEngine", `Failed to get room members for membership type "${membership}" in ${roomId}`, extractRequestError(err)); + } + } + if (members.size === 0) { + return; } + const membersArray = Array.from(members); const encEv = new EncryptionEvent({ type: "m.room.encryption", @@ -109,14 +124,14 @@ export class RustEngine { await this.run(RequestType.KeysQuery); await this.lock.acquire(SYNC_LOCK_NAME, async () => { - const keysClaim = await this.machine.getMissingSessions(members); + const keysClaim = await this.machine.getMissingSessions(membersArray); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); } }); await this.lock.acquire(roomId, async () => { - const requests = JSON.parse(await this.machine.shareRoomKey(new RoomId(roomId), members, settings)); + const requests = JSON.parse(await this.machine.shareRoomKey(new RoomId(roomId), membersArray, settings)); for (const req of requests) { await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages); } From b505f758094192a3c044a53e3b1581dc9ff542f9 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 18 Jan 2023 17:11:30 +0000 Subject: [PATCH 004/123] Allow zero values for LRU config options We'd like to disable the LRU to tempoarily workaround some caching issues, which requires us to be able to set the values of maxAgeMs/maxCached to `0`. --- src/appservice/Appservice.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index c1e44cf8..af74a77c 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -246,8 +246,8 @@ export class Appservice extends EventEmitter { options.joinStrategy = new AppserviceJoinRoomStrategy(options.joinStrategy, this); if (!options.intentOptions) options.intentOptions = {}; - if (!options.intentOptions.maxAgeMs) options.intentOptions.maxAgeMs = 60 * 60 * 1000; - if (!options.intentOptions.maxCached) options.intentOptions.maxCached = 10000; + if (options.intentOptions.maxAgeMs === undefined) options.intentOptions.maxAgeMs = 60 * 60 * 1000; + if (options.intentOptions.maxCached === undefined) options.intentOptions.maxCached = 10000; this.intentsCache = new LRU({ max: options.intentOptions.maxCached, From 3900fe2e5c245d5aca8875ddf9d582c7ae0e0536 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Mar 2023 16:49:51 +0100 Subject: [PATCH 005/123] v0.6.4-element.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09f98872..263068aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "develop", + "version": "0.6.4", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 9c2036c52812bbd5b7e4df3f604b0d62c131a619 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 28 Mar 2023 10:15:00 -0600 Subject: [PATCH 006/123] Use the new members array? --- src/e2ee/RustEngine.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index d9bb9d1c..4970e02c 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -127,9 +127,9 @@ export class RustEngine { settings.rotationPeriodMessages = BigInt(encEv.rotationPeriodMessages); await this.lock.acquire(SYNC_LOCK_NAME, async () => { - await this.machine.updateTrackedUsers(members); // just in case we missed some + await this.machine.updateTrackedUsers(membersArray); // just in case we missed some await this.runOnly(RequestType.KeysQuery); - const keysClaim = await this.machine.getMissingSessions(members); + const keysClaim = await this.machine.getMissingSessions(membersArray); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); } From b19bd4cb0948ceb882746a8dec6b882052cf5c49 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 5 Apr 2023 12:52:33 -0600 Subject: [PATCH 007/123] Fix key query and claim APIs to support async functionality (#314) * Fix key query and claim APIs to support async functionality * fix copy/paste fail --- src/appservice/Appservice.ts | 58 ++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 0d3482ab..90b385c3 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -958,16 +958,26 @@ export class Appservice extends EventEmitter { } let responded = false; - this.emit("query.key_claim", req.body, async (result: MSC3983KeyClaimResponse | Promise | undefined | null) => { - if (result?.then) result = await result; - if (!result) { - res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); - responded = true; - return; - } - - res.status(200).json(result); + this.emit("query.key_claim", req.body, (result: MSC3983KeyClaimResponse | Promise | undefined | null) => { responded = true; + + const handleResult = (result2: MSC3983KeyClaimResponse) => { + if (!result2) { + res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); + return; + } + + res.status(200).json(result2); + }; + + if ((result as Promise)?.then) { + (result as Promise).then(r => handleResult(r)).catch(e => { + LogService.error("Appservice", "Error handling key claim API", e); + res.status(500).json({ errcode: "M_UNKNOWN" }); + }); + } else { + handleResult(result as MSC3983KeyClaimResponse); + } }); if (!responded) { res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); @@ -986,18 +996,28 @@ export class Appservice extends EventEmitter { } let responded = false; - this.emit("query.key", req.body, async (result: MSC3984KeyQueryResponse | Promise | undefined | null) => { - if ((result as any)?.then) result = await result; - if (!result) { - res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); - responded = true; - return; - } + this.emit("query.key", req.body, (result: MSC3984KeyQueryResponse | Promise | undefined | null) => { + responded = true; - // Implementation note: we could probably query the device keys from our storage if we wanted to. + const handleResult = (result2: MSC3984KeyQueryResponse) => { + if (!result2) { + res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); + return; + } - res.status(200).json(result); - responded = true; + // Implementation note: we could probably query the device keys from our storage if we wanted to. + + res.status(200).json(result2); + }; + + if ((result as Promise)?.then) { + (result as Promise).then(r => handleResult(r)).catch(e => { + LogService.error("Appservice", "Error handling key query API", e); + res.status(500).json({ errcode: "M_UNKNOWN" }); + }); + } else { + handleResult(result as MSC3984KeyQueryResponse); + } }); if (!responded) { res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); From 33c7242373b3d17a44b6bf8c09028d829d6a0c45 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 6 Apr 2023 09:26:58 -0600 Subject: [PATCH 008/123] Better interface --- src/appservice/Appservice.ts | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 90b385c3..539d68c1 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -970,14 +970,10 @@ export class Appservice extends EventEmitter { res.status(200).json(result2); }; - if ((result as Promise)?.then) { - (result as Promise).then(r => handleResult(r)).catch(e => { - LogService.error("Appservice", "Error handling key claim API", e); - res.status(500).json({ errcode: "M_UNKNOWN" }); - }); - } else { - handleResult(result as MSC3983KeyClaimResponse); - } + Promise.resolve(result).then(r => handleResult(r)).catch(e => { + LogService.error("Appservice", "Error handling key claim API", e); + res.status(500).json({ errcode: "M_UNKNOWN", error: "Error handling key claim API" }); + }); }); if (!responded) { res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); @@ -1010,14 +1006,10 @@ export class Appservice extends EventEmitter { res.status(200).json(result2); }; - if ((result as Promise)?.then) { - (result as Promise).then(r => handleResult(r)).catch(e => { - LogService.error("Appservice", "Error handling key query API", e); - res.status(500).json({ errcode: "M_UNKNOWN" }); - }); - } else { - handleResult(result as MSC3984KeyQueryResponse); - } + Promise.resolve(result).then(r => handleResult(r)).catch(e => { + LogService.error("Appservice", "Error handling key query API", e); + res.status(500).json({ errcode: "M_UNKNOWN", error: "Error handling key query API" }); + }); }); if (!responded) { res.status(404).json({ errcode: "M_UNRECOGNIZED", error: "Endpoint not implemented" }); From aa7394c3139a9e117b3af0fed4169449417d252b Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 6 Apr 2023 16:31:17 +0100 Subject: [PATCH 009/123] 0.6.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 263068aa..330819b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.6.4", + "version": "0.6.5", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 3bd23c42bf44381012b8c41aeecb7fff23c50abf Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 14 Apr 2023 22:33:38 +0900 Subject: [PATCH 010/123] Allow usage of the SQLite-based crypto store Signed-off-by: Andrew Ferrazzutti --- package.json | 2 +- src/e2ee/CryptoClient.ts | 7 ++- src/e2ee/RustEngine.ts | 2 +- src/storage/RustSdkCryptoStorageProvider.ts | 18 +++++--- yarn.lock | 48 ++++++++++++++------- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 72eaa776..7f04c40f 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.3", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.4", "@types/express": "^4.17.13", "another-json": "^0.2.0", "async-lock": "^1.3.2", diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 91cd3e9c..74ddfb50 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -86,7 +86,12 @@ export class CryptoClient { LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); - const machine = await OlmMachine.initialize(new UserId(await this.client.getUserId()), new DeviceId(this.deviceId), this.storage.storagePath); + const machine = await OlmMachine.initialize( + new UserId(await this.client.getUserId()), + new DeviceId(this.deviceId), + this.storage.storagePath, "", + this.storage.storageType, + ); this.engine = new RustEngine(machine, this.client); await this.engine.run(); diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index dad4a440..c9eae9c8 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -52,7 +52,7 @@ export class RustEngine { await this.processKeysClaimRequest(request); break; case RequestType.ToDevice: - await this.processToDeviceRequest(request); + await this.processToDeviceRequest(request as ToDeviceRequest); break; case RequestType.RoomMessage: throw new Error("Bindings error: Sending room messages is not supported"); diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index aedb478b..ef207067 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -4,13 +4,16 @@ import * as mkdirp from "mkdirp"; import * as path from "path"; import * as sha512 from "hash.js/lib/hash/sha/512"; import * as sha256 from "hash.js/lib/hash/sha/256"; +import { StoreType as RustSdkCryptoStoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { ICryptoStorageProvider } from "./ICryptoStorageProvider"; import { IAppserviceCryptoStorageProvider } from "./IAppserviceStorageProvider"; import { ICryptoRoomInformation } from "../e2ee/ICryptoRoomInformation"; +export { RustSdkCryptoStoreType }; + /** - * A crypto storage provider for the default rust-sdk store (sled, file-based). + * A crypto storage provider for the file-based rust-sdk store. * @category Storage providers */ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { @@ -20,7 +23,10 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { * Creates a new rust-sdk storage provider. * @param {string} storagePath The *directory* to persist database details to. */ - public constructor(public readonly storagePath: string) { + public constructor( + public readonly storagePath: string, + public readonly storageType: RustSdkCryptoStoreType, + ) { this.storagePath = path.resolve(this.storagePath); mkdirp.sync(storagePath); @@ -53,7 +59,7 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { } /** - * An appservice crypto storage provider for the default rust-sdk store (sled, file-based). + * An appservice crypto storage provider for the file-based rust-sdk store. * @category Storage providers */ export class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorageProvider implements IAppserviceCryptoStorageProvider { @@ -61,13 +67,13 @@ export class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorage * Creates a new rust-sdk storage provider. * @param {string} baseStoragePath The *directory* to persist database details to. */ - public constructor(private baseStoragePath: string) { - super(path.join(baseStoragePath, "_default")); + public constructor(private baseStoragePath: string, storageType: RustSdkCryptoStoreType) { + super(path.join(baseStoragePath, "_default"), storageType); } public storageForUser(userId: string): ICryptoStorageProvider { // sha256 because sha512 is a bit big for some operating systems const key = sha256().update(userId).digest('hex'); - return new RustSdkCryptoStorageProvider(path.join(this.baseStoragePath, key)); + return new RustSdkCryptoStorageProvider(path.join(this.baseStoragePath, key), this.storageType); } } diff --git a/yarn.lock b/yarn.lock index 8b009d88..03678e00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -584,12 +584,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.3": - version "0.1.0-beta.3" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.3.tgz#a07225dd180d9d227c24ba62bba439939446d113" - integrity sha512-jHFn6xBeNqfsY5gX60akbss7iFBHZwXycJWMw58Mjz08OwOi7AbTxeS9I2Pa4jX9/M2iinskmGZbzpqOT2fM3A== +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.4": + version "0.1.0-beta.4" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.4.tgz#80456b2e2cc731982f0d3c6aece80cefa1ebb797" + integrity sha512-XjCp/tG3LRMxMj/MMZfypD5BtW3J1B6oXY2Og8Ed0SyU4uWdglalMwrBUKlDotJr0/Q/2OTspGjD+ytAzCspyw== dependencies: - node-downloader-helper "^2.1.1" + https-proxy-agent "^5.0.1" + node-downloader-helper "^2.1.5" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -977,6 +978,13 @@ acorn@^8.7.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1589,6 +1597,13 @@ debug@2.6.9, debug@^2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -1596,13 +1611,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decamelize@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2554,6 +2562,14 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -3657,10 +3673,10 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-downloader-helper@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/node-downloader-helper/-/node-downloader-helper-2.1.1.tgz#533427a3cdc163931b106d0fe6d522f83deac7ab" - integrity sha512-ouk8MGmJj1gYymbJwi1L8Mr6PdyheJLwfsmyx0KtsvyJ+7Fpf0kBBzM8Gmx8Mt/JBfRWP1PQm6dAGV6x7eNedw== +node-downloader-helper@^2.1.5: + version "2.1.6" + resolved "https://registry.yarnpkg.com/node-downloader-helper/-/node-downloader-helper-2.1.6.tgz#f73ac458e3ac8c21afd0b952a994eab99c64b879" + integrity sha512-VkOvAXIopI3xMuM/MC5UL7NqqnizQ/9QXZt28jR8FPZ6fHLQm4xe4+YXJ9FqsWwLho5BLXrF51nfOQ0QcohRkQ== node-int64@^0.4.0: version "0.4.0" From c73cacf8f0be98740feaaf8f2c0cab78ef3528a6 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 14 Apr 2023 22:36:06 +0900 Subject: [PATCH 011/123] Add types for lowdb Signed-off-by: Andrew Ferrazzutti --- package.json | 1 + src/storage/RustSdkCryptoStorageProvider.ts | 2 +- yarn.lock | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f04c40f..e4998c66 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "@types/async-lock": "^1.3.0", "@types/expect": "^24.3.0", "@types/jest": "^27.5.1", + "@types/lowdb": "^1.0.11", "@types/mocha": "^8", "@types/node": "^16", "@types/simple-mock": "^0.8.2", diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index ef207067..323307cb 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -17,7 +17,7 @@ export { RustSdkCryptoStoreType }; * @category Storage providers */ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { - private db: any; + private db: lowdb.LowdbSync; /** * Creates a new rust-sdk storage provider. diff --git a/yarn.lock b/yarn.lock index 03678e00..e853435b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -780,6 +780,18 @@ resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== +"@types/lodash@*": + version "4.14.194" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76" + integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g== + +"@types/lowdb@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@types/lowdb/-/lowdb-1.0.11.tgz#d8336a635ea0dbd48a7f6f62fb9fccc5ec358ae3" + integrity sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg== + dependencies: + "@types/lodash" "*" + "@types/markdown-it@^12.2.3": version "12.2.3" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" From bce5d600bf26b53129378a06948e77d64e119e83 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 14 Apr 2023 23:22:51 +0900 Subject: [PATCH 012/123] Update tests Signed-off-by: Andrew Ferrazzutti --- test/MatrixClientTest.ts | 3 ++- test/TestUtils.ts | 3 ++- test/appservice/IntentTest.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index f7980df9..f5bbabed 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1,5 +1,6 @@ import * as tmp from "tmp"; import * as simple from "simple-mock"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EventKind, @@ -51,7 +52,7 @@ describe('MatrixClient', () => { const homeserverUrl = "https://example.org"; const accessToken = "example_token"; - const client = new MatrixClient(homeserverUrl, accessToken, null, new RustSdkCryptoStorageProvider(tmp.dirSync().name)); + const client = new MatrixClient(homeserverUrl, accessToken, null, new RustSdkCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled)); expect(client.crypto).toBeDefined(); }); diff --git a/test/TestUtils.ts b/test/TestUtils.ts index f62f692f..6593689a 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,5 +1,6 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { IStorageProvider, MatrixClient, RustSdkCryptoStorageProvider, setRequestFn } from "../src"; @@ -39,7 +40,7 @@ export function createTestClient( const http = new HttpBackend(); const hsUrl = "https://localhost"; const accessToken = "s3cret"; - const client = new MatrixClient(hsUrl, accessToken, storage, crypto ? new RustSdkCryptoStorageProvider(tmp.dirSync().name) : null); + const client = new MatrixClient(hsUrl, accessToken, storage, crypto ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled) : null); (client).userId = userId; // private member access setRequestFn(http.requestFn); diff --git a/test/appservice/IntentTest.ts b/test/appservice/IntentTest.ts index 3b040054..07713238 100644 --- a/test/appservice/IntentTest.ts +++ b/test/appservice/IntentTest.ts @@ -1,6 +1,7 @@ import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; import * as tmp from "tmp"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { expectArrayEquals } from "../TestUtils"; import { @@ -1136,7 +1137,7 @@ describe('Intent', () => { beforeEach(() => { storage = new MemoryStorageProvider(); - cryptoStorage = new RustSdkAppserviceCryptoStorageProvider(tmp.dirSync().name); + cryptoStorage = new RustSdkAppserviceCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled); options = { homeserverUrl: hsUrl, storage: storage, From 00a0b2741807ec38d2bc9a3e48c8f5275dcb17f5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 17 Apr 2023 18:08:10 +0900 Subject: [PATCH 013/123] Update examples Signed-off-by: Andrew Ferrazzutti --- examples/bot.ts | 4 +++- examples/encryption_appservice.ts | 4 +++- examples/encryption_bot.ts | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/bot.ts b/examples/bot.ts index ad74f6dd..2ad2957d 100644 --- a/examples/bot.ts +++ b/examples/bot.ts @@ -9,6 +9,8 @@ import { SimpleFsStorageProvider, } from "../src"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; + LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); @@ -25,7 +27,7 @@ const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN'; const storage = new SimpleFsStorageProvider("./examples/storage/bot.json"); -const crypto = new RustSdkCryptoStorageProvider("./examples/storage/bot_sled"); +const crypto = new RustSdkCryptoStorageProvider("./examples/storage/bot_sled", StoreType.Sled); const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); AutojoinRoomsMixin.setupOnClient(client); diff --git a/examples/encryption_appservice.ts b/examples/encryption_appservice.ts index 3079f152..e6b0af50 100644 --- a/examples/encryption_appservice.ts +++ b/examples/encryption_appservice.ts @@ -15,6 +15,8 @@ import { SimpleRetryJoinStrategy, } from "../src"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; + LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); @@ -30,7 +32,7 @@ try { const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const storage = new SimpleFsStorageProvider("./examples/storage/encryption_appservice.json"); -const crypto = new RustSdkAppserviceCryptoStorageProvider("./examples/storage/encryption_appservice_sled"); +const crypto = new RustSdkAppserviceCryptoStorageProvider("./examples/storage/encryption_appservice_sled", StoreType.Sled); const worksImage = fs.readFileSync("./examples/static/it-works.png"); const registration: IAppserviceRegistration = { diff --git a/examples/encryption_bot.ts b/examples/encryption_bot.ts index 18d5dee7..3aa6a257 100644 --- a/examples/encryption_bot.ts +++ b/examples/encryption_bot.ts @@ -12,6 +12,8 @@ import { SimpleFsStorageProvider, } from "../src"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; + LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); @@ -28,7 +30,7 @@ const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN'; const storage = new SimpleFsStorageProvider("./examples/storage/encryption_bot.json"); -const crypto = new RustSdkCryptoStorageProvider("./examples/storage/encryption_bot_sled"); +const crypto = new RustSdkCryptoStorageProvider("./examples/storage/encryption_bot_sled", StoreType.Sled); const worksImage = fs.readFileSync("./examples/static/it-works.png"); const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); From dc8202a701aff30e2fecf60f18f850a38650917b Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 17 Apr 2023 18:15:06 +0900 Subject: [PATCH 014/123] Fix lint rules for new the imports in examples Signed-off-by: Andrew Ferrazzutti --- examples/bot.ts | 4 ++-- examples/encryption_appservice.ts | 3 +-- examples/encryption_bot.ts | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/bot.ts b/examples/bot.ts index 2ad2957d..a1ac99c1 100644 --- a/examples/bot.ts +++ b/examples/bot.ts @@ -1,3 +1,5 @@ +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; + import { AutojoinRoomsMixin, LogLevel, @@ -9,8 +11,6 @@ import { SimpleFsStorageProvider, } from "../src"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; - LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); diff --git a/examples/encryption_appservice.ts b/examples/encryption_appservice.ts index e6b0af50..90c8f3f6 100644 --- a/examples/encryption_appservice.ts +++ b/examples/encryption_appservice.ts @@ -1,4 +1,5 @@ import * as fs from "fs"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { Appservice, @@ -15,8 +16,6 @@ import { SimpleRetryJoinStrategy, } from "../src"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; - LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); diff --git a/examples/encryption_bot.ts b/examples/encryption_bot.ts index 3aa6a257..011b1565 100644 --- a/examples/encryption_bot.ts +++ b/examples/encryption_bot.ts @@ -1,4 +1,5 @@ import * as fs from "fs"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EncryptionAlgorithm, @@ -12,8 +13,6 @@ import { SimpleFsStorageProvider, } from "../src"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; - LogService.setLogger(new RichConsoleLogger()); LogService.setLevel(LogLevel.TRACE); LogService.muteModule("Metrics"); From 4c07639f3209f34823f4e79e6cda8ca7c977d2e8 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 20 Apr 2023 21:29:30 +0900 Subject: [PATCH 015/123] Test the SQLite crypto store Run all tests with both the Sled and SQLite store types Signed-off-by: Andrew Ferrazzutti --- test/DMsTest.ts | 16 ++- test/MatrixClientTest.ts | 194 ++++++++++++++-------------- test/TestUtils.ts | 12 +- test/encryption/CryptoClientTest.ts | 86 ++++++------ test/encryption/RoomTrackerTest.ts | 51 ++++---- 5 files changed, 185 insertions(+), 174 deletions(-) diff --git a/test/DMsTest.ts b/test/DMsTest.ts index f5134892..ddfcba28 100644 --- a/test/DMsTest.ts +++ b/test/DMsTest.ts @@ -1,7 +1,8 @@ import * as simple from "simple-mock"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EncryptionAlgorithm } from "../src"; -import { createTestClient, TEST_DEVICE_ID } from "./TestUtils"; +import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "./TestUtils"; describe('DMs', () => { it('should update the cache when an sync requests happen', async () => { @@ -297,9 +298,9 @@ describe('DMs', () => { await flush; }); - it('should create an encrypted DM if supported', async () => { + it('should create an encrypted DM if supported', () => testCryptoStores(async (cryptoStoreType) => { const selfUserId = "@self:example.org"; - const { client, http } = createTestClient(null, selfUserId, true); + const { client, http } = createTestClient(null, selfUserId, cryptoStoreType); const dms = client.dms; const dmRoomId = "!dm:example.org"; @@ -359,11 +360,11 @@ describe('DMs', () => { expect(dms.isDm(dmRoomId)).toBe(true); await flush; - }); + })); - it('should create an unencrypted DM when the target user has no devices', async () => { + it('should create an unencrypted DM when the target user has no devices', () => testCryptoStores(async (cryptoStoreType) => { const selfUserId = "@self:example.org"; - const { client, http } = createTestClient(null, selfUserId, true); + const { client, http } = createTestClient(null, selfUserId, cryptoStoreType); const dms = client.dms; const dmRoomId = "!dm:example.org"; @@ -411,5 +412,6 @@ describe('DMs', () => { expect(dms.isDm(dmRoomId)).toBe(true); await flush; - }); + + })); }); diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index f5bbabed..69aa55a7 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -22,7 +22,7 @@ import { ServerVersions, setRequestFn, } from "../src"; -import { createTestClient, expectArrayEquals, TEST_DEVICE_ID } from "./TestUtils"; +import { createTestClient, expectArrayEquals, testCryptoStores, TEST_DEVICE_ID } from "./TestUtils"; tmp.setGracefulCleanup(); @@ -2248,8 +2248,8 @@ describe('MatrixClient', () => { expect(eventSpy.callCount).toBe(5); }); - it('should process crypto if enabled', async () => { - const { client: realClient } = createTestClient(null, "@alice:example.org", true); + it('should process crypto if enabled', () => testCryptoStores(async (cryptoStoreType) => { + const { client: realClient } = createTestClient(null, "@alice:example.org", cryptoStoreType); const client = (realClient); const sync = { @@ -2278,7 +2278,7 @@ describe('MatrixClient', () => { await client.processSync(sync); expect(spy.callCount).toBe(1); - }); + })); }); describe('getEvent', () => { @@ -2326,8 +2326,8 @@ describe('MatrixClient', () => { expect(result["processed"]).toBeTruthy(); }); - it('should try decryption', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try decryption', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!abc123:example.org"; const eventId = "$example:example.org"; @@ -2368,10 +2368,10 @@ describe('MatrixClient', () => { expect(processSpy.callCount).toBe(2); expect(isEncSpy.callCount).toBe(1); expect(decryptSpy.callCount).toBe(1); - }); + })); - it('should not try decryption in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try decryption in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!abc123:example.org"; const eventId = "$example:example.org"; @@ -2412,7 +2412,7 @@ describe('MatrixClient', () => { expect(processSpy.callCount).toBe(1); expect(isEncSpy.callCount).toBe(1); expect(decryptSpy.callCount).toBe(0); - }); + })); }); describe('getRawEvent', () => { @@ -2460,8 +2460,8 @@ describe('MatrixClient', () => { expect(result["processed"]).toBeTruthy(); }); - it('should not try decryption in any rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try decryption in any rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!abc123:example.org"; const eventId = "$example:example.org"; @@ -2502,7 +2502,7 @@ describe('MatrixClient', () => { expect(processSpy.callCount).toBe(1); expect(isEncSpy.callCount).toBe(0); expect(decryptSpy.callCount).toBe(0); - }); + })); }); describe('getRoomState', () => { @@ -3467,8 +3467,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3518,10 +3518,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyText(roomId, originalEvent, replyText, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3561,7 +3561,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyText(roomId, originalEvent, replyText, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); it('should use encoded plain text as the HTML component', async () => { const { client, http, hsUrl } = createTestClient(); @@ -3647,8 +3647,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3698,10 +3698,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyHtmlText(roomId, originalEvent, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3741,7 +3741,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyHtmlText(roomId, originalEvent, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('replyNotice', () => { @@ -3786,8 +3786,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3837,10 +3837,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyNotice(roomId, originalEvent, replyText, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -3880,7 +3880,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyNotice(roomId, originalEvent, replyText, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); it('should use encoded plain text as the HTML component', async () => { const { client, http, hsUrl } = createTestClient(); @@ -3966,8 +3966,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4017,10 +4017,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyHtmlNotice(roomId, originalEvent, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4060,7 +4060,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.replyHtmlNotice(roomId, originalEvent, replyHtml), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendNotice', () => { @@ -4086,8 +4086,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4119,10 +4119,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendNotice(roomId, eventContent.body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4143,7 +4143,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendNotice(roomId, eventContent.body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendHtmlNotice', () => { @@ -4171,8 +4171,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4206,10 +4206,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendHtmlNotice(roomId, eventContent.formatted_body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4232,7 +4232,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendHtmlNotice(roomId, eventContent.formatted_body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendText', () => { @@ -4258,8 +4258,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4291,10 +4291,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendText(roomId, eventContent.body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4315,7 +4315,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendText(roomId, eventContent.body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendHtmlText', () => { @@ -4343,8 +4343,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4378,10 +4378,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendHtmlText(roomId, eventContent.formatted_body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4404,7 +4404,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendHtmlText(roomId, eventContent.formatted_body), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendMessage', () => { @@ -4431,8 +4431,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4465,10 +4465,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendMessage(roomId, eventPlainContent), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4490,7 +4490,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendMessage(roomId, eventContent), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendEvent', () => { @@ -4517,8 +4517,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should try to encrypt in encrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should try to encrypt in encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4552,10 +4552,10 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendEvent(roomId, eventType, eventPlainContent), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); - it('should not try to encrypt in unencrypted rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4577,7 +4577,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendEvent(roomId, eventType, eventContent), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendRawEvent', () => { @@ -4604,8 +4604,8 @@ describe('MatrixClient', () => { expect(result).toEqual(eventId); }); - it('should not try to encrypt in any rooms', async () => { - const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", true); + it('should not try to encrypt in any rooms', () => testCryptoStores(async (cryptoStoreType) => { + const { client, http, hsUrl } = createTestClient(null, "@alice:example.org", cryptoStoreType); const roomId = "!testing:example.org"; const eventId = "$something:example.org"; @@ -4627,7 +4627,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.sendRawEvent(roomId, eventType, eventContent), http.flushAllExpected()]); expect(result).toEqual(eventId); - }); + })); }); describe('sendStateEvent', () => { @@ -6688,9 +6688,9 @@ describe('MatrixClient', () => { } }); - it('should call the right endpoint', async () => { + it('should call the right endpoint', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); // @ts-ignore const keys: OTKs = { @@ -6719,7 +6719,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.uploadDeviceOneTimeKeys(keys), http.flushAllExpected()]); expect(result).toMatchObject(counts); - }); + })); }); describe('checkOneTimeKeyCounts', () => { @@ -6735,9 +6735,9 @@ describe('MatrixClient', () => { } }); - it('should call the right endpoint', async () => { + it('should call the right endpoint', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); const counts: OTKCounts = { [OTKAlgorithm.Signed]: 12, @@ -6752,7 +6752,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.checkOneTimeKeyCounts(), http.flushAllExpected()]); expect(result).toMatchObject(counts); - }); + })); }); describe('getUserDevices', () => { @@ -6789,9 +6789,9 @@ describe('MatrixClient', () => { expect(result).toMatchObject(response); }); - it('should call the right endpoint with a default timeout', async () => { + it('should call the right endpoint with a default timeout', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); const requestBody = { "@alice:example.org": [], @@ -6820,7 +6820,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.getUserDevices(Object.keys(requestBody)), http.flushAllExpected()]); expect(result).toMatchObject(response); - }); + })); }); describe('claimOneTimeKeys', () => { @@ -6836,9 +6836,9 @@ describe('MatrixClient', () => { } }); - it('should call the right endpoint', async () => { + it('should call the right endpoint', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); const request = { "@alice:example.org": { @@ -6874,11 +6874,11 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.claimOneTimeKeys(request), http.flushAllExpected()]); expect(result).toMatchObject(response); - }); + })); - it('should use the timeout parameter', async () => { + it('should use the timeout parameter', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); const request = { "@alice:example.org": { @@ -6916,13 +6916,13 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.claimOneTimeKeys(request, timeout), http.flushAllExpected()]); expect(result).toMatchObject(response); - }); + })); }); describe('sendToDevices', () => { - it('should call the right endpoint', async () => { + it('should call the right endpoint', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http, hsUrl } = createTestClient(null, userId, true); + const { client, http, hsUrl } = createTestClient(null, userId, cryptoStoreType); const type = "org.example.message"; const messages = { @@ -6947,13 +6947,13 @@ describe('MatrixClient', () => { }); await Promise.all([client.sendToDevices(type, messages), http.flushAllExpected()]); - }); + })); }); describe('getOwnDevices', () => { - it('should call the right endpoint', async () => { + it('should call the right endpoint', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@test:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); const devices = ["schema not followed for simplicity"]; @@ -6964,7 +6964,7 @@ describe('MatrixClient', () => { const [res] = await Promise.all([client.getOwnDevices(), http.flushAllExpected()]); expect(res).toMatchObject(devices); - }); + })); }); describe('getRelationsForEvent', () => { diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 6593689a..43faead0 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -30,7 +30,7 @@ export function testDelay(ms: number): Promise { export function createTestClient( storage: IStorageProvider = null, userId: string = null, - crypto = false, + cryptoStoreType?: StoreType, ): { client: MatrixClient; http: HttpBackend; @@ -40,9 +40,17 @@ export function createTestClient( const http = new HttpBackend(); const hsUrl = "https://localhost"; const accessToken = "s3cret"; - const client = new MatrixClient(hsUrl, accessToken, storage, crypto ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled) : null); + const client = new MatrixClient(hsUrl, accessToken, storage, cryptoStoreType !== undefined ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, cryptoStoreType) : null); (client).userId = userId; // private member access setRequestFn(http.requestFn); return { http, hsUrl, accessToken, client }; } + +const CRYPTO_STORE_TYPES = [StoreType.Sled, StoreType.Sqlite] + +export async function testCryptoStores(fn: (StoreType) => Promise): Promise { + for (const st of CRYPTO_STORE_TYPES) { + await fn(st); + } +} diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index 5f9a3d2d..bab445f6 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -2,7 +2,7 @@ import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; import { EncryptedFile, MatrixClient, MembershipEvent, OTKAlgorithm, RoomEncryptionAlgorithm } from "../../src"; -import { createTestClient, TEST_DEVICE_ID } from "../TestUtils"; +import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; export function bindNullEngine(http: HttpBackend) { http.when("POST", "/keys/upload").respond(200, (path, obj) => { @@ -23,9 +23,9 @@ export function bindNullEngine(http: HttpBackend) { } describe('CryptoClient', () => { - it('should not have a device ID or be ready until prepared', async () => { + it('should not have a device ID or be ready until prepared', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); @@ -41,13 +41,13 @@ describe('CryptoClient', () => { expect(client.crypto.clientDeviceId).toEqual(TEST_DEVICE_ID); expect(client.crypto.isReady).toEqual(true); - }); + })); describe('prepare', () => { - it('should prepare the room tracker', async () => { + it('should prepare the room tracker', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; const roomIds = ["!a:example.org", "!b:example.org"]; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); @@ -64,11 +64,11 @@ describe('CryptoClient', () => { http.flushAllExpected(), ]); expect(prepareSpy.callCount).toEqual(1); - }); + })); - it('should use a stored device ID', async () => { + it('should use a stored device ID', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); @@ -82,13 +82,13 @@ describe('CryptoClient', () => { ]); expect(whoamiSpy.callCount).toEqual(0); expect(client.crypto.clientDeviceId).toEqual(TEST_DEVICE_ID); - }); + })); }); describe('isRoomEncrypted', () => { - it('should fail when the crypto has not been prepared', async () => { + it('should fail when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client } = createTestClient(null, userId, true); + const { client } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // await client.crypto.prepare([]); // deliberately commented @@ -101,11 +101,11 @@ describe('CryptoClient', () => { } catch (e) { expect(e.message).toEqual("End-to-end encryption has not initialized"); } - }); + })); - it('should return false for unknown rooms', async () => { + it('should return false for unknown rooms', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); client.getRoomStateEvent = () => Promise.reject(new Error("not used")); @@ -118,11 +118,11 @@ describe('CryptoClient', () => { const result = await client.crypto.isRoomEncrypted("!new:example.org"); expect(result).toEqual(false); - }); + })); - it('should return false for unencrypted rooms', async () => { + it('should return false for unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); client.getRoomStateEvent = () => Promise.reject(new Error("implied 404")); @@ -135,11 +135,11 @@ describe('CryptoClient', () => { const result = await client.crypto.isRoomEncrypted("!new:example.org"); expect(result).toEqual(false); - }); + })); - it('should return true for encrypted rooms (redacted state)', async () => { + it('should return true for encrypted rooms (redacted state)', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); client.getRoomStateEvent = () => Promise.resolve({}); @@ -152,11 +152,11 @@ describe('CryptoClient', () => { const result = await client.crypto.isRoomEncrypted("!new:example.org"); expect(result).toEqual(true); - }); + })); - it('should return true for encrypted rooms', async () => { + it('should return true for encrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const { client, http } = createTestClient(null, userId, true); + const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); client.getRoomStateEvent = () => Promise.resolve({ algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2 }); @@ -169,7 +169,7 @@ describe('CryptoClient', () => { const result = await client.crypto.isRoomEncrypted("!new:example.org"); expect(result).toEqual(true); - }); + })); }); describe('sign', () => { @@ -177,15 +177,15 @@ describe('CryptoClient', () => { let client: MatrixClient; let http: HttpBackend; - beforeEach(async () => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); client = mclient; http = mhttp; await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // client crypto not prepared for the one test which wants that state - }); + })); it('should fail when the crypto has not been prepared', async () => { try { @@ -234,15 +234,15 @@ describe('CryptoClient', () => { let client: MatrixClient; let http: HttpBackend; - beforeEach(async () => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); client = mclient; http = mhttp; await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // client crypto not prepared for the one test which wants that state - }); + })); it('should fail when the crypto has not been prepared', async () => { try { @@ -284,14 +284,14 @@ describe('CryptoClient', () => { const userId = "@alice:example.org"; let client: MatrixClient; - beforeEach(async () => { - const { client: mclient } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient } = createTestClient(null, userId, cryptoStoreType); client = mclient; await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // client crypto not prepared for the one test which wants that state - }); + })); it('should fail when the crypto has not been prepared', async () => { try { @@ -310,15 +310,15 @@ describe('CryptoClient', () => { let client: MatrixClient; let http: HttpBackend; - beforeEach(async () => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); client = mclient; http = mhttp; await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // client crypto not prepared for the one test which wants that state - }); + })); it('should fail when the crypto has not been prepared', async () => { try { @@ -396,15 +396,15 @@ describe('CryptoClient', () => { return JSON.parse(JSON.stringify(testFile)); } - beforeEach(async () => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); client = mclient; http = mhttp; await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); // client crypto not prepared for the one test which wants that state - }); + })); it('should fail when the crypto has not been prepared', async () => { try { @@ -467,8 +467,8 @@ describe('CryptoClient', () => { let client: MatrixClient; let http: HttpBackend; - beforeEach(async () => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, true); + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); client = mclient; http = mhttp; @@ -478,7 +478,7 @@ describe('CryptoClient', () => { client.crypto.prepare([]), http.flushAllExpected(), ]); - }); + })); it('should update tracked users on membership changes', async () => { const targetUserIds = ["@bob:example.org", "@charlie:example.org"]; diff --git a/test/encryption/RoomTrackerTest.ts b/test/encryption/RoomTrackerTest.ts index 2dd1453e..78b2d6b9 100644 --- a/test/encryption/RoomTrackerTest.ts +++ b/test/encryption/RoomTrackerTest.ts @@ -1,7 +1,8 @@ import * as simple from "simple-mock"; +import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EncryptionEventContent, MatrixClient, RoomEncryptionAlgorithm, RoomTracker } from "../../src"; -import { createTestClient, TEST_DEVICE_ID } from "../TestUtils"; +import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; import { bindNullEngine } from "./CryptoClientTest"; function prepareQueueSpies( @@ -38,10 +39,10 @@ function prepareQueueSpies( } describe('RoomTracker', () => { - it('should queue room updates when rooms are joined', async () => { + it('should queue room updates when rooms are joined', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!a:example.org"; - const { client, http } = createTestClient(null, "@user:example.org", true); + const { client, http } = createTestClient(null, "@user:example.org", cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); bindNullEngine(http); await Promise.all([ @@ -64,12 +65,12 @@ describe('RoomTracker', () => { client.emit("room.join", roomId); }); expect(queueSpy.callCount).toEqual(1); - }); + })); - it('should queue room updates when encryption events are received', async () => { + it('should queue room updates when encryption events are received', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!a:example.org"; - const { client, http } = createTestClient(null, "@user:example.org", true); + const { client, http } = createTestClient(null, "@user:example.org", cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); bindNullEngine(http); await Promise.all([ @@ -102,7 +103,7 @@ describe('RoomTracker', () => { }); await new Promise(resolve => setTimeout(() => resolve(), 250)); expect(queueSpy.callCount).toEqual(1); - }); + })); describe('prepare', () => { it('should queue updates for rooms', async () => { @@ -123,11 +124,11 @@ describe('RoomTracker', () => { }); describe('queueRoomCheck', () => { - it('should store unknown rooms', async () => { + it('should store unknown rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!b:example.org"; const content = { algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2, rid: "1" }; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const [readSpy, stateSpy, storeSpy] = prepareQueueSpies(client, roomId, content); @@ -136,13 +137,13 @@ describe('RoomTracker', () => { expect(readSpy.callCount).toEqual(1); expect(stateSpy.callCount).toEqual(2); // m.room.encryption and m.room.history_visibility expect(storeSpy.callCount).toEqual(1); - }); + })); - it('should skip known rooms', async () => { + it('should skip known rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!b:example.org"; const content = { algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2, rid: "1" }; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const [readSpy, stateSpy, storeSpy] = prepareQueueSpies(client, roomId, { algorithm: "no" }, content); @@ -151,13 +152,13 @@ describe('RoomTracker', () => { expect(readSpy.callCount).toEqual(1); expect(stateSpy.callCount).toEqual(0); expect(storeSpy.callCount).toEqual(0); - }); + })); - it('should not store unencrypted rooms', async () => { + it('should not store unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!b:example.org"; const content = { algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2, rid: "1" }; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const [readSpy, stateSpy, storeSpy] = prepareQueueSpies(client, roomId, content); client.getRoomStateEvent = async (rid: string, et: string, sk: string) => { @@ -170,15 +171,15 @@ describe('RoomTracker', () => { expect(readSpy.callCount).toEqual(1); expect(stateSpy.callCount).toEqual(1); expect(storeSpy.callCount).toEqual(0); - }); + })); }); describe('getRoomCryptoConfig', () => { - it('should return the config as-is', async () => { + it('should return the config as-is', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!a:example.org"; const content: Partial = { algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2 }; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const readSpy = simple.stub().callFn((rid: string) => { expect(rid).toEqual(roomId); @@ -191,13 +192,13 @@ describe('RoomTracker', () => { const config = await tracker.getRoomCryptoConfig(roomId); expect(readSpy.callCount).toEqual(1); expect(config).toMatchObject(content); - }); + })); - it('should queue unknown rooms', async () => { + it('should queue unknown rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!a:example.org"; const content: Partial = { algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2 }; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const readSpy = simple.stub().callFn((rid: string) => { expect(rid).toEqual(roomId); @@ -217,12 +218,12 @@ describe('RoomTracker', () => { expect(readSpy.callCount).toEqual(2); expect(queueSpy.callCount).toEqual(1); expect(config).toMatchObject(content); - }); + })); - it('should return empty for unencrypted rooms', async () => { + it('should return empty for unencrypted rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!a:example.org"; - const { client } = createTestClient(null, "@user:example.org", true); + const { client } = createTestClient(null, "@user:example.org", cryptoStoreType); const readSpy = simple.stub().callFn((rid: string) => { expect(rid).toEqual(roomId); @@ -241,6 +242,6 @@ describe('RoomTracker', () => { expect(readSpy.callCount).toEqual(2); expect(queueSpy.callCount).toEqual(1); expect(config).toMatchObject({}); - }); + })); }); }); From 3f24fb5396f4925fbb38e2e0177ac83de988aa98 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 20 Apr 2023 21:32:45 +0900 Subject: [PATCH 016/123] Fix lint rules Signed-off-by: Andrew Ferrazzutti --- test/DMsTest.ts | 2 -- test/encryption/RoomTrackerTest.ts | 1 - 2 files changed, 3 deletions(-) diff --git a/test/DMsTest.ts b/test/DMsTest.ts index ddfcba28..dae6f54c 100644 --- a/test/DMsTest.ts +++ b/test/DMsTest.ts @@ -1,5 +1,4 @@ import * as simple from "simple-mock"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EncryptionAlgorithm } from "../src"; import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "./TestUtils"; @@ -412,6 +411,5 @@ describe('DMs', () => { expect(dms.isDm(dmRoomId)).toBe(true); await flush; - })); }); diff --git a/test/encryption/RoomTrackerTest.ts b/test/encryption/RoomTrackerTest.ts index 78b2d6b9..4ce0a00c 100644 --- a/test/encryption/RoomTrackerTest.ts +++ b/test/encryption/RoomTrackerTest.ts @@ -1,5 +1,4 @@ import * as simple from "simple-mock"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EncryptionEventContent, MatrixClient, RoomEncryptionAlgorithm, RoomTracker } from "../../src"; import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; From a983376ae80a4fcd4c231dc11ab969ff754ce87d Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 20 Apr 2023 21:35:12 +0900 Subject: [PATCH 017/123] Add missing semicolon Signed-off-by: Andrew Ferrazzutti --- test/TestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 43faead0..7d6f9f8d 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -47,7 +47,7 @@ export function createTestClient( return { http, hsUrl, accessToken, client }; } -const CRYPTO_STORE_TYPES = [StoreType.Sled, StoreType.Sqlite] +const CRYPTO_STORE_TYPES = [StoreType.Sled, StoreType.Sqlite]; export async function testCryptoStores(fn: (StoreType) => Promise): Promise { for (const st of CRYPTO_STORE_TYPES) { From d4c888619ac72e1a65a8d07c9b96f4ed9ff5d9e5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 21 Apr 2023 22:12:30 +0900 Subject: [PATCH 018/123] Reset version back to "develop" for jsdoc, for now --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 34cfd214..65c317bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.6.5", + "version": "develop", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 5b75a2fa15173288dfb49d27ccc3f596d55c0060 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 25 Apr 2023 22:49:32 +0900 Subject: [PATCH 019/123] Support node 20, drop node 16 --- .github/workflows/docs.yml | 2 +- .github/workflows/static_analysis.yml | 8 ++++---- .node-version | 1 + package.json | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 .node-version diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 558b75f8..103510a3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,7 +10,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' + node-version-file: .node-version - run: yarn install - run: yarn docs - name: Build and deploy docs diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 1c3cdcda..714f290c 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' # Target desired node version + node-version-file: .node-version # Target desired node version - run: yarn install - run: yarn lint @@ -23,7 +23,7 @@ jobs: build: strategy: matrix: - node: [ 16, 18 ] + node: [ 18, 20 ] name: 'Build Node ${{ matrix.node }}' runs-on: ubuntu-latest steps: @@ -38,7 +38,7 @@ jobs: build-docs: strategy: matrix: - node: [ 16, 18 ] + node: [ 18, 20 ] name: 'Build Docs Node ${{ matrix.node }}' runs-on: ubuntu-latest steps: @@ -52,7 +52,7 @@ jobs: tests: strategy: matrix: - node: [ 16, 18 ] + node: [ 18, 20 ] name: 'Tests Node ${{ matrix.node }}' runs-on: ubuntu-latest steps: diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18 diff --git a/package.json b/package.json index 65c317bb..b6654c07 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "main": "./lib/index.js", "typings": "./lib/index.d.ts", "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "keywords": [ "matrix", From 82c0f14f1cc26940f62fc88ee86c511224725233 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 26 Apr 2023 00:51:20 +0900 Subject: [PATCH 020/123] Update deps --- package.json | 4 ++-- yarn.lock | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index b6654c07..f9b18204 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@types/jest": "^27.5.1", "@types/lowdb": "^1.0.11", "@types/mocha": "^8", - "@types/node": "^16", + "@types/node": "^18", "@types/simple-mock": "^0.8.2", "@typescript-eslint/eslint-plugin": "^5.26.0", "@typescript-eslint/parser": "^5.26.0", @@ -94,7 +94,7 @@ "simple-mock": "^0.8.0", "tmp": "^0.2.1", "ts-jest": "^28.0.3", - "typescript": "^4.7.2" + "typescript": "^5.0.4" }, "jest": { "preset": "ts-jest", diff --git a/yarn.lock b/yarn.lock index e853435b..b95a0ba8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -820,10 +820,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.36.tgz#c0d5f2fe76b47b63e0e0efc3d2049a9970d68794" integrity sha512-V3orv+ggDsWVHP99K3JlwtH20R7J4IhI1Kksgc+64q5VxgfRkQG8Ws3MFm/FZOKDYGy9feGFlZ70/HpCNe9QaA== -"@types/node@^16": - version "16.11.36" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.36.tgz#9ab9f8276987132ed2b225cace2218ba794fc751" - integrity sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA== +"@types/node@^18": + version "18.16.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.0.tgz#4668bc392bb6938637b47e98b1f2ed5426f33316" + integrity sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ== "@types/prettier@^2.1.5": version "2.6.3" @@ -4851,11 +4851,16 @@ typescript@^3.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== -typescript@^4.5.4, typescript@^4.7.2: +typescript@^4.5.4: version "4.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== + uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" From 18875a26f17d25523f54cf5507b240587046ff04 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 26 Apr 2023 01:23:15 +0900 Subject: [PATCH 021/123] Update eslint --- package.json | 6 +- test/appservice/IntentTest.ts | 4 +- yarn.lock | 645 +++++++++++++++++++++++----------- 3 files changed, 443 insertions(+), 212 deletions(-) diff --git a/package.json b/package.json index f9b18204..bb640315 100644 --- a/package.json +++ b/package.json @@ -80,10 +80,10 @@ "@types/mocha": "^8", "@types/node": "^18", "@types/simple-mock": "^0.8.2", - "@typescript-eslint/eslint-plugin": "^5.26.0", - "@typescript-eslint/parser": "^5.26.0", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "@typescript-eslint/parser": "^5.59.1", "better-docs": "^2.7.2", - "eslint": "^8.16.0", + "eslint": "^8.39.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-matrix-org": "^0.5.2", diff --git a/test/appservice/IntentTest.ts b/test/appservice/IntentTest.ts index 07713238..66b2ddfc 100644 --- a/test/appservice/IntentTest.ts +++ b/test/appservice/IntentTest.ts @@ -1133,7 +1133,7 @@ describe('Intent', () => { let storage: IAppserviceStorageProvider; let cryptoStorage: IAppserviceCryptoStorageProvider; let options: IAppserviceOptions; - let intent: Intent; + let intent: Intent; // eslint-disable-line @typescript-eslint/no-unused-vars beforeEach(() => { storage = new MemoryStorageProvider(); @@ -1160,7 +1160,7 @@ describe('Intent', () => { }, }, }; - intent = new Intent(options, userId, appservice); // eslint-disable-line @typescript-eslint/no-unused-vars + intent = new Intent(options, userId, appservice); }); // TODO: Test once device_id impersonation set up diff --git a/yarn.lock b/yarn.lock index b95a0ba8..6e66e337 100644 --- a/yarn.lock +++ b/yarn.lock @@ -307,29 +307,51 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@eslint/eslintrc@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" - integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.4.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" + integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + +"@eslint/eslintrc@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" + integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.2" - globals "^13.15.0" + espree "^9.5.1" + globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@humanwhocodes/config-array@^0.9.2": - version "0.9.5" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" - integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== +"@eslint/js@8.39.0": + version "8.39.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" + integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== + +"@humanwhocodes/config-array@^0.11.8": + version "0.11.8" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9" + integrity sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g== dependencies: "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" - minimatch "^3.0.4" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" @@ -605,7 +627,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -840,6 +862,11 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + "@types/serve-static@*": version "1.13.10" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" @@ -870,84 +897,88 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2" - integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA== +"@typescript-eslint/eslint-plugin@^5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz#9b09ee1541bff1d2cebdcb87e7ce4a4003acde08" + integrity sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg== dependencies: - "@typescript-eslint/scope-manager" "5.26.0" - "@typescript-eslint/type-utils" "5.26.0" - "@typescript-eslint/utils" "5.26.0" + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/type-utils" "5.59.1" + "@typescript-eslint/utils" "5.59.1" debug "^4.3.4" - functional-red-black-tree "^1.0.1" + grapheme-splitter "^1.0.4" ignore "^5.2.0" - regexpp "^3.2.0" + natural-compare-lite "^1.4.0" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2" - integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q== +"@typescript-eslint/parser@^5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.1.tgz#73c2c12127c5c1182d2e5b71a8fa2a85d215cbb4" + integrity sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g== dependencies: - "@typescript-eslint/scope-manager" "5.26.0" - "@typescript-eslint/types" "5.26.0" - "@typescript-eslint/typescript-estree" "5.26.0" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339" - integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw== +"@typescript-eslint/scope-manager@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz#8a20222719cebc5198618a5d44113705b51fd7fe" + integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA== dependencies: - "@typescript-eslint/types" "5.26.0" - "@typescript-eslint/visitor-keys" "5.26.0" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" -"@typescript-eslint/type-utils@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013" - integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A== +"@typescript-eslint/type-utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111" + integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw== dependencies: - "@typescript-eslint/utils" "5.26.0" + "@typescript-eslint/typescript-estree" "5.59.1" + "@typescript-eslint/utils" "5.59.1" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3" - integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA== +"@typescript-eslint/types@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d" + integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg== -"@typescript-eslint/typescript-estree@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3" - integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w== +"@typescript-eslint/typescript-estree@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c" + integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA== dependencies: - "@typescript-eslint/types" "5.26.0" - "@typescript-eslint/visitor-keys" "5.26.0" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/visitor-keys" "5.59.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4" - integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg== +"@typescript-eslint/utils@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473" + integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA== dependencies: + "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.26.0" - "@typescript-eslint/types" "5.26.0" - "@typescript-eslint/typescript-estree" "5.26.0" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.1" + "@typescript-eslint/types" "5.59.1" + "@typescript-eslint/typescript-estree" "5.59.1" eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.26.0": - version "5.26.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57" - integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q== +"@typescript-eslint/visitor-keys@5.59.1": + version "5.59.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058" + integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA== dependencies: - "@typescript-eslint/types" "5.26.0" + "@typescript-eslint/types" "5.59.1" eslint-visitor-keys "^3.3.0" accepts@~1.3.8: @@ -985,10 +1016,10 @@ acorn@^4.0.4, acorn@~4.0.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" integrity sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug== -acorn@^8.7.1: - version "8.7.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" - integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.8.0: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== agent-base@6: version "6.0.2" @@ -1072,20 +1103,28 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-includes@^3.1.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== +array-includes@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.6.tgz#9e9e720e194f198266ba9e18c29e6a9b0e4b225f" + integrity sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" + es-abstract "^1.20.4" + get-intrinsic "^1.1.3" is-string "^1.0.7" array-union@^2.1.0: @@ -1093,14 +1132,24 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@^1.2.5: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" - integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== +array.prototype.flat@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" + integrity sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz#1aae7903c2100433cb8261cd4ed310aab5c4a183" + integrity sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-shim-unscopables "^1.0.0" asap@~2.0.3: @@ -1142,6 +1191,11 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1602,7 +1656,7 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== -debug@2.6.9, debug@^2.6.9: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1827,7 +1881,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: +es-abstract@^1.19.0, es-abstract@^1.19.5: version "1.20.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== @@ -1856,6 +1910,55 @@ es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19 string.prototype.trimstart "^1.0.5" unbox-primitive "^1.0.2" +es-abstract@^1.20.4: + version "1.21.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" + integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== + dependencies: + array-buffer-byte-length "^1.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.2.0" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.10" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.7" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.9" + +es-set-tostringtag@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" + integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== + dependencies: + get-intrinsic "^1.1.3" + has "^1.0.3" + has-tostringtag "^1.0.0" + es-shim-unscopables@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" @@ -1902,39 +2005,41 @@ eslint-config-google@^0.14.0: resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a" integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw== -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== +eslint-import-resolver-node@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz#83b375187d412324a1963d84fa664377a23eb4d7" + integrity sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA== dependencies: debug "^3.2.7" - resolve "^1.20.0" + is-core-module "^2.11.0" + resolve "^1.22.1" -eslint-module-utils@^2.7.3: - version "2.7.3" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" - integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== +eslint-module-utils@^2.7.4: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== dependencies: debug "^3.2.7" - find-up "^2.1.0" eslint-plugin-import@^2.26.0: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + version "2.27.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz#876a6d03f52608a3e5bb439c2550588e51dd6c65" + integrity sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.7.4" has "^1.0.3" - is-core-module "^2.8.1" + is-core-module "^2.11.0" is-glob "^4.0.3" minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" + object.values "^1.1.6" + resolve "^1.22.1" + semver "^6.3.0" tsconfig-paths "^3.14.1" eslint-plugin-matrix-org@^0.5.2: @@ -1955,22 +2060,15 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: +eslint-visitor-keys@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== @@ -1980,34 +2078,46 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.16.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae" - integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA== - dependencies: - "@eslint/eslintrc" "^1.3.0" - "@humanwhocodes/config-array" "^0.9.2" +eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== + +eslint@^8.39.0: + version "8.39.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" + integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.39.0" + "@humanwhocodes/config-array" "^0.11.8" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.3.2" - esquery "^1.4.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.15.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + grapheme-splitter "^1.0.4" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" @@ -2015,30 +2125,28 @@ eslint@^8.16.0: minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" - regexpp "^3.2.0" strip-ansi "^6.0.1" strip-json-comments "^3.1.0" text-table "^0.2.0" - v8-compile-cache "^2.0.3" -espree@^9.3.2: - version "9.3.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" - integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== +espree@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== dependencies: - acorn "^8.7.1" + acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.0" esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" @@ -2228,13 +2336,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== - dependencies: - locate-path "^2.0.0" - find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2264,6 +2365,13 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -2321,11 +2429,6 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - functions-have-names@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -2350,6 +2453,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -2387,7 +2499,7 @@ glob-parent@^5.1.2: dependencies: is-glob "^4.0.1" -glob-parent@^6.0.1: +glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -2416,13 +2528,20 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.15.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" - integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== +globals@^13.19.0: + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" +globalthis@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -2435,11 +2554,23 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.3, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2475,6 +2606,11 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -2642,11 +2778,29 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +internal-slot@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" + integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== + dependencies: + get-intrinsic "^1.2.0" + has "^1.0.3" + side-channel "^1.0.4" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + is-typed-array "^1.1.10" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -2672,11 +2826,23 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-callable@^1.1.3, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== +is-core-module@^2.11.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== + dependencies: + has "^1.0.3" + is-core-module@^2.8.1: version "2.9.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" @@ -2738,6 +2904,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" @@ -2782,6 +2953,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-typed-array@^1.1.10, is-typed-array@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -3228,6 +3410,11 @@ jest@^28.1.0: import-local "^3.0.2" jest-cli "^28.1.0" +js-sdsl@^4.1.4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.4.0.tgz#8b437dbe642daa95760400b602378ed8ffea8430" + integrity sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg== + js-stringify@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" @@ -3393,14 +3580,6 @@ linkify-it@^3.0.1: dependencies: uc.micro "^1.0.1" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -3600,7 +3779,7 @@ minimalistic-assert@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -3653,6 +3832,11 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -3727,6 +3911,11 @@ object-inspect@^1.12.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -3742,14 +3931,24 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +object.values@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.6.tgz#4abbaa71eba47d63589d402856f908243eea9b1d" + integrity sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" on-finished@2.4.1: version "2.4.1" @@ -3796,13 +3995,6 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== - dependencies: - p-try "^1.0.0" - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -3817,13 +4009,6 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= - dependencies: - p-limit "^1.1.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -3838,11 +4023,6 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= - p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -3883,11 +4063,6 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -4261,11 +4436,6 @@ regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" @@ -4348,7 +4518,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.1.6, resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.1.6, resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -4357,6 +4527,15 @@ resolve@^1.1.6, resolve@^1.20.0, resolve@^1.22.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== + dependencies: + is-core-module "^2.11.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -4398,6 +4577,15 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4593,6 +4781,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" @@ -4602,6 +4799,15 @@ string.prototype.trimend@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + string.prototype.trimstart@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" @@ -4611,6 +4817,15 @@ string.prototype.trimstart@^1.0.5: define-properties "^1.1.4" es-abstract "^1.19.5" +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -4846,6 +5061,15 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-array-length@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" + integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + is-typed-array "^1.1.9" + typescript@^3.2.2: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" @@ -4918,11 +5142,6 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - v8-to-istanbul@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz#be0dae58719fc53cb97e5c7ac1d7e6d4f5b19511" @@ -5000,6 +5219,18 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-typed-array@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" From 563d129bb2cd27be752770c44873696a79d44739 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 26 Apr 2023 17:47:58 +0900 Subject: [PATCH 022/123] Remove superfluous comment --- .github/workflows/static_analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 714f290c..2de0c462 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version-file: .node-version # Target desired node version + node-version-file: .node-version - run: yarn install - run: yarn lint From 795323d596bab1752df9a2993037e8a0dc83ae26 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 26 Apr 2023 22:56:24 +0900 Subject: [PATCH 023/123] Update Jest This fixes incompatibility issues with ts-jest and Typescript 5.0.4. --- package.json | 4 +- yarn.lock | 874 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 514 insertions(+), 364 deletions(-) diff --git a/package.json b/package.json index bb640315..94d4786b 100644 --- a/package.json +++ b/package.json @@ -88,12 +88,12 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-matrix-org": "^0.5.2", "get-port": "^5", - "jest": "^28.1.0", + "jest": "^29.5.0", "jsdoc": "^3.6.10", "matrix-mock-request": "^2.1.0", "simple-mock": "^0.8.0", "tmp": "^0.2.1", - "ts-jest": "^28.0.3", + "ts-jest": "^29.1.0", "typescript": "^5.0.4" }, "jest": { diff --git a/yarn.lock b/yarn.lock index 6e66e337..1e098456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -124,6 +124,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== +"@babel/helper-plugin-utils@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + "@babel/helper-simple-access@^7.17.7": version "7.18.2" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" @@ -206,6 +211,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.7.2": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" + integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -374,62 +386,61 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.0.tgz#db78222c3d3b0c1db82f1b9de51094c2aaff2176" - integrity sha512-tscn3dlJFGay47kb4qVruQg/XWlmvU0xp3EJOjzzY+sBaI+YgwKcvAmTcyYU7xEiLLIY5HCdWRooAL8dqkFlDA== +"@jest/console@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.5.0.tgz#593a6c5c0d3f75689835f1b3b4688c4f8544cb57" + integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^28.1.0" - jest-util "^28.1.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" -"@jest/core@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.0.tgz#784a1e6ce5358b46fcbdcfbbd93b1b713ed4ea80" - integrity sha512-/2PTt0ywhjZ4NwNO4bUqD9IVJfmFVhVKGlhvSpmEfUCuxYf/3NHcKmRFI+I71lYzbTT3wMuYpETDCTHo81gC/g== +"@jest/core@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.5.0.tgz#76674b96904484e8214614d17261cc491e5f1f03" + integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== dependencies: - "@jest/console" "^28.1.0" - "@jest/reporters" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^29.5.0" + "@jest/reporters" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^28.0.2" - jest-config "^28.1.0" - jest-haste-map "^28.1.0" - jest-message-util "^28.1.0" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-resolve-dependencies "^28.1.0" - jest-runner "^28.1.0" - jest-runtime "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" - jest-watcher "^28.1.0" + jest-changed-files "^29.5.0" + jest-config "^29.5.0" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-resolve-dependencies "^29.5.0" + jest-runner "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" + jest-watcher "^29.5.0" micromatch "^4.0.4" - pretty-format "^28.1.0" - rimraf "^3.0.0" + pretty-format "^29.5.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.0.tgz#dedf7d59ec341b9292fcf459fd0ed819eb2e228a" - integrity sha512-S44WGSxkRngzHslhV6RoAExekfF7Qhwa6R5+IYFa81mpcj0YgdBnRSmvHe3SNwOt64yXaE5GG8Y2xM28ii5ssA== +"@jest/environment@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" + integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== dependencies: - "@jest/fake-timers" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^28.1.0" + jest-mock "^29.5.0" "@jest/expect-utils@^28.1.0": version "28.1.0" @@ -438,46 +449,54 @@ dependencies: jest-get-type "^28.0.2" -"@jest/expect@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.0.tgz#2e5a31db692597070932366a1602b5157f0f217c" - integrity sha512-be9ETznPLaHOmeJqzYNIXv1ADEzENuQonIoobzThOYPuK/6GhrWNIJDVTgBLCrz3Am73PyEU2urQClZp0hLTtA== +"@jest/expect-utils@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" + integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== dependencies: - expect "^28.1.0" - jest-snapshot "^28.1.0" + jest-get-type "^29.4.3" -"@jest/fake-timers@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.0.tgz#ea77878aabd5c5d50e1fc53e76d3226101e33064" - integrity sha512-Xqsf/6VLeAAq78+GNPzI7FZQRf5cCHj1qgQxCjws9n8rKw8r1UYoeaALwBvyuzOkpU3c1I6emeMySPa96rxtIg== +"@jest/expect@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.5.0.tgz#80952f5316b23c483fbca4363ce822af79c38fba" + integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== dependencies: - "@jest/types" "^28.1.0" - "@sinonjs/fake-timers" "^9.1.1" + expect "^29.5.0" + jest-snapshot "^29.5.0" + +"@jest/fake-timers@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c" + integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== + dependencies: + "@jest/types" "^29.5.0" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^28.1.0" - jest-mock "^28.1.0" - jest-util "^28.1.0" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" + jest-util "^29.5.0" -"@jest/globals@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.0.tgz#a4427d2eb11763002ff58e24de56b84ba79eb793" - integrity sha512-3m7sTg52OTQR6dPhsEQSxAvU+LOBbMivZBwOvKEZ+Rb+GyxVnXi9HKgOTYkx/S99T8yvh17U4tNNJPIEQmtwYw== +"@jest/globals@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.5.0.tgz#6166c0bfc374c58268677539d0c181f9c1833298" + integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== dependencies: - "@jest/environment" "^28.1.0" - "@jest/expect" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/types" "^29.5.0" + jest-mock "^29.5.0" -"@jest/reporters@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.0.tgz#5183a28b9b593b6000fa9b89b031c7216b58a9a0" - integrity sha512-qxbFfqap/5QlSpIizH9c/bFCDKsQlM4uAKSOvZrP+nIdrjqre3FmKzpTtYyhsaVcOSNK7TTt2kjm+4BJIjysFA== +"@jest/reporters@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.5.0.tgz#985dfd91290cd78ddae4914ba7921bcbabe8ac9b" + integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" - "@jridgewell/trace-mapping" "^0.3.7" + "@jest/console" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" + "@jridgewell/trace-mapping" "^0.3.15" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -489,13 +508,13 @@ istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-util "^28.1.0" - jest-worker "^28.1.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + jest-worker "^29.5.0" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" - terminal-link "^2.0.0" - v8-to-istanbul "^9.0.0" + v8-to-istanbul "^9.0.1" "@jest/schemas@^28.0.2": version "28.0.2" @@ -504,55 +523,62 @@ dependencies: "@sinclair/typebox" "^0.23.3" -"@jest/source-map@^28.0.2": - version "28.0.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.0.2.tgz#914546f4410b67b1d42c262a1da7e0406b52dc90" - integrity sha512-Y9dxC8ZpN3kImkk0LkK5XCEneYMAXlZ8m5bflmSL5vrwyeUpJfentacCUg6fOb8NOpOO7hz2+l37MV77T6BFPw== +"@jest/schemas@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788" + integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== dependencies: - "@jridgewell/trace-mapping" "^0.3.7" + "@sinclair/typebox" "^0.25.16" + +"@jest/source-map@^29.4.3": + version "29.4.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" + integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.15" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.0.tgz#fd149dee123510dd2fcadbbf5f0020f98ad7f12c" - integrity sha512-sBBFIyoPzrZho3N+80P35A5oAkSKlGfsEFfXFWuPGBsW40UAjCkGakZhn4UQK4iQlW2vgCDMRDOob9FGKV8YoQ== +"@jest/test-result@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.5.0.tgz#7c856a6ca84f45cc36926a4e9c6b57f1973f1408" + integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== dependencies: - "@jest/console" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^29.5.0" + "@jest/types" "^29.5.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.0.tgz#ce7294bbe986415b9a30e218c7e705e6ebf2cdf2" - integrity sha512-tZCEiVWlWNTs/2iK9yi6o3AlMfbbYgV4uuZInSVdzZ7ftpHZhCMuhvk2HLYhCZzLgPFQ9MnM1YaxMnh3TILFiQ== +"@jest/test-sequencer@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz#34d7d82d3081abd523dbddc038a3ddcb9f6d3cc4" + integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== dependencies: - "@jest/test-result" "^28.1.0" + "@jest/test-result" "^29.5.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" + jest-haste-map "^29.5.0" slash "^3.0.0" -"@jest/transform@^28.1.0": - version "28.1.0" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.0.tgz#224a3c9ba4cc98e2ff996c0a89a2d59db15c74ce" - integrity sha512-omy2xe5WxlAfqmsTjTPxw+iXRTRnf+NtX0ToG+4S0tABeb4KsKmPUHq5UBuwunHg3tJRwgEQhEp0M/8oiatLEA== +"@jest/transform@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.5.0.tgz#cf9c872d0965f0cbd32f1458aa44a2b1988b00f9" + integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^28.1.0" - "@jridgewell/trace-mapping" "^0.3.7" + "@jest/types" "^29.5.0" + "@jridgewell/trace-mapping" "^0.3.15" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" - jest-regex-util "^28.0.2" - jest-util "^28.1.0" + jest-haste-map "^29.5.0" + jest-regex-util "^29.4.3" + jest-util "^29.5.0" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" - write-file-atomic "^4.0.1" + write-file-atomic "^4.0.2" "@jest/types@^28.1.0": version "28.1.0" @@ -566,6 +592,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.5.0": + version "29.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593" + integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== + dependencies: + "@jest/schemas" "^29.4.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" @@ -583,6 +621,11 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + "@jridgewell/resolve-uri@^3.0.3": version "3.0.7" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" @@ -593,11 +636,24 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== +"@jridgewell/sourcemap-codec@1.4.14": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.13" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15": + version "0.3.18" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": version "0.3.13" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" @@ -648,19 +704,24 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" integrity sha512-AFBVi/iT4g20DHoujvMH1aEDn8fGJh4xsRGCP6d8RpLPMqsNPvW01Jcn0QysXTsg++/xj25NmJsGyH9xug/wKg== -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== +"@sinclair/typebox@^0.25.16": + version "0.25.24" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" + integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== + +"@sinonjs/commons@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" + integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^9.1.1": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== +"@sinonjs/fake-timers@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" + integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^2.0.0" "@types/async-lock@^1.3.0": version "1.3.0" @@ -1206,15 +1267,15 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -babel-jest@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.0.tgz#95a67f8e2e7c0042e7b3ad3951b8af41a533b5ea" - integrity sha512-zNKk0yhDZ6QUwfxh9k07GII6siNGMJWVUU49gmFj5gfdqDKLqa2RArXOF2CODp4Dr7dLxN2cvAV+667dGJ4b4w== +babel-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" + integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== dependencies: - "@jest/transform" "^28.1.0" + "@jest/transform" "^29.5.0" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^28.0.2" + babel-preset-jest "^29.5.0" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" @@ -1230,10 +1291,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.0.2.tgz#9307d03a633be6fc4b1a6bc5c3a87e22bd01dd3b" - integrity sha512-Kizhn/ZL+68ZQHxSnHyuvJv8IchXD62KQxV77TBDV/xoBFBOfgRAk97GNs6hXdTTCiVES9nB2I6+7MXXrk5llQ== +babel-plugin-jest-hoist@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz#a97db437936f441ec196990c9738d4b88538618a" + integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -1258,12 +1319,12 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.0.2.tgz#d8210fe4e46c1017e9fa13d7794b166e93aa9f89" - integrity sha512-sYzXIdgIXXroJTFeB3S6sNDWtlJ2dllCdTEsnZ65ACrMojj3hVNFRmnJ1HZtomGi+Be7aqpY/HJ92fr8OhKVkQ== +babel-preset-jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz#57bc8cc88097af7ff6a5ab59d1cd29d52a5916e2" + integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== dependencies: - babel-plugin-jest-hoist "^28.0.2" + babel-plugin-jest-hoist "^29.5.0" babel-preset-current-node-syntax "^1.0.0" babel-runtime@^6.26.0: @@ -1608,13 +1669,18 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1740,6 +1806,11 @@ diff-sequences@^28.0.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.0.2.tgz#40f8d4ffa081acbd8902ba35c798458d0ff1af41" integrity sha512-YtEoNynLDFCRznv/XDalsKGSZDoj0U5kLnXvY0JSq3nBboRrZXjD81+eSiwi+nzcZDwedMmcowcxNwwgFW23mQ== +diff-sequences@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" + integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1844,10 +1915,10 @@ electron-to-chromium@^1.4.118: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.141.tgz#4dd9119e8a99f1c83c51dfcf1bed79ea541f08d6" integrity sha512-mfBcbqc0qc6RlxrsIgLG2wCqkiPAjEezHxGTu7p3dHHFOurH4EjS9rFZndX5axC8264rI1Pcbw8uQP39oZckeA== -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" @@ -2217,6 +2288,17 @@ expect@*, expect@^28.1.0: jest-message-util "^28.1.0" jest-util "^28.1.0" +expect@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7" + integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== + dependencies: + "@jest/expect-utils" "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" + express@^4.18.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" @@ -2285,7 +2367,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3028,82 +3110,83 @@ istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.0.2.tgz#7d7810660a5bd043af9e9cfbe4d58adb05e91531" - integrity sha512-QX9u+5I2s54ZnGoMEjiM2WeBvJR2J7w/8ZUmH2um/WLAuGAYFQcsVXY9+1YL6k0H/AGUdH8pXUAv6erDqEsvIA== +jest-changed-files@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" + integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== dependencies: execa "^5.0.0" - throat "^6.0.1" + p-limit "^3.1.0" -jest-circus@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.0.tgz#e229f590911bd54d60efaf076f7acd9360296dae" - integrity sha512-rNYfqfLC0L0zQKRKsg4n4J+W1A2fbyGH7Ss/kDIocp9KXD9iaL111glsLu7+Z7FHuZxwzInMDXq+N1ZIBkI/TQ== +jest-circus@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.5.0.tgz#b5926989449e75bff0d59944bae083c9d7fb7317" + integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== dependencies: - "@jest/environment" "^28.1.0" - "@jest/expect" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/environment" "^29.5.0" + "@jest/expect" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" dedent "^0.7.0" is-generator-fn "^2.0.0" - jest-each "^28.1.0" - jest-matcher-utils "^28.1.0" - jest-message-util "^28.1.0" - jest-runtime "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" - pretty-format "^28.1.0" + jest-each "^29.5.0" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-runtime "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" + p-limit "^3.1.0" + pretty-format "^29.5.0" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" - throat "^6.0.1" -jest-cli@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.0.tgz#cd1d8adb9630102d5ba04a22895f63decdd7ac1f" - integrity sha512-fDJRt6WPRriHrBsvvgb93OxgajHHsJbk4jZxiPqmZbMDRcHskfJBBfTyjFko0jjfprP544hOktdSi9HVgl4VUQ== +jest-cli@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.5.0.tgz#b34c20a6d35968f3ee47a7437ff8e53e086b4a67" + integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== dependencies: - "@jest/core" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/core" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-config "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.0.tgz#fca22ca0760e746fe1ce1f9406f6b307ab818501" - integrity sha512-aOV80E9LeWrmflp7hfZNn/zGA4QKv/xsn2w8QCBP0t0+YqObuCWTSgNbHJ0j9YsTuCO08ZR/wsvlxqqHX20iUA== +jest-config@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.5.0.tgz#3cc972faec8c8aaea9ae158c694541b79f3748da" + integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^28.1.0" - "@jest/types" "^28.1.0" - babel-jest "^28.1.0" + "@jest/test-sequencer" "^29.5.0" + "@jest/types" "^29.5.0" + babel-jest "^29.5.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^28.1.0" - jest-environment-node "^28.1.0" - jest-get-type "^28.0.2" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-runner "^28.1.0" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-circus "^29.5.0" + jest-environment-node "^29.5.0" + jest-get-type "^29.4.3" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-runner "^29.5.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^28.1.0" + pretty-format "^29.5.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -3127,35 +3210,45 @@ jest-diff@^28.1.0: jest-get-type "^28.0.2" pretty-format "^28.1.0" -jest-docblock@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.0.2.tgz#3cab8abea53275c9d670cdca814fc89fba1298c2" - integrity sha512-FH10WWw5NxLoeSdQlJwu+MTiv60aXV/t8KEwIRGEv74WARE1cXIqh1vGdy2CraHuWOOrnzTWj/azQKqW4fO7xg== +jest-diff@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63" + integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== dependencies: - detect-newline "^3.0.0" + chalk "^4.0.0" + diff-sequences "^29.4.3" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" -jest-each@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.0.tgz#54ae66d6a0a5b1913e9a87588d26c2687c39458b" - integrity sha512-a/XX02xF5NTspceMpHujmOexvJ4GftpYXqr6HhhmKmExtMXsyIN/fvanQlt/BcgFoRKN4OCXxLQKth9/n6OPFg== +jest-docblock@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.4.3.tgz#90505aa89514a1c7dceeac1123df79e414636ea8" + integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== dependencies: - "@jest/types" "^28.1.0" - chalk "^4.0.0" - jest-get-type "^28.0.2" - jest-util "^28.1.0" - pretty-format "^28.1.0" + detect-newline "^3.0.0" -jest-environment-node@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.0.tgz#6ed2150aa31babba0c488c5b4f4d813a585c68e6" - integrity sha512-gBLZNiyrPw9CSMlTXF1yJhaBgWDPVvH0Pq6bOEwGMXaYNzhzhw2kA/OijNF8egbCgDS0/veRv97249x2CX+udQ== +jest-each@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" + integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== dependencies: - "@jest/environment" "^28.1.0" - "@jest/fake-timers" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/types" "^29.5.0" + chalk "^4.0.0" + jest-get-type "^29.4.3" + jest-util "^29.5.0" + pretty-format "^29.5.0" + +jest-environment-node@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.5.0.tgz#f17219d0f0cc0e68e0727c58b792c040e332c967" + integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== + dependencies: + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" - jest-mock "^28.1.0" - jest-util "^28.1.0" + jest-mock "^29.5.0" + jest-util "^29.5.0" jest-get-type@^27.5.1: version "27.5.1" @@ -3167,32 +3260,37 @@ jest-get-type@^28.0.2: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== -jest-haste-map@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.0.tgz#6c1ee2daf1c20a3e03dbd8e5b35c4d73d2349cf0" - integrity sha512-xyZ9sXV8PtKi6NCrJlmq53PyNVHzxmcfXNVvIRHpHmh1j/HChC4pwKgyjj7Z9us19JMw8PpQTJsFWOsIfT93Dw== +jest-get-type@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5" + integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== + +jest-haste-map@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.5.0.tgz#69bd67dc9012d6e2723f20a945099e972b2e94de" + integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^29.5.0" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^28.0.2" - jest-util "^28.1.0" - jest-worker "^28.1.0" + jest-regex-util "^29.4.3" + jest-util "^29.5.0" + jest-worker "^29.5.0" micromatch "^4.0.4" - walker "^1.0.7" + walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.0.tgz#b65167776a8787443214d6f3f54935a4c73c8a45" - integrity sha512-uIJDQbxwEL2AMMs2xjhZl2hw8s77c3wrPaQ9v6tXJLGaaQ+4QrNJH5vuw7hA7w/uGT/iJ42a83opAqxGHeyRIA== +jest-leak-detector@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz#cf4bdea9615c72bac4a3a7ba7e7930f9c0610c8c" + integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== dependencies: - jest-get-type "^28.0.2" - pretty-format "^28.1.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" jest-matcher-utils@^27.0.0: version "27.5.1" @@ -3214,6 +3312,16 @@ jest-matcher-utils@^28.1.0: jest-get-type "^28.0.2" pretty-format "^28.1.0" +jest-matcher-utils@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5" + integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== + dependencies: + chalk "^4.0.0" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + pretty-format "^29.5.0" + jest-message-util@^28.1.0: version "28.1.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.0.tgz#7e8f0b9049e948e7b94c2a52731166774ba7d0af" @@ -3229,132 +3337,148 @@ jest-message-util@^28.1.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.0.tgz#ccc7cc12a9b330b3182db0c651edc90d163ff73e" - integrity sha512-H7BrhggNn77WhdL7O1apG0Q/iwl0Bdd5E1ydhCJzL3oBLh/UYxAwR3EJLsBZ9XA3ZU4PA3UNw4tQjduBTCTmLw== +jest-message-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e" + integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== dependencies: - "@jest/types" "^28.1.0" + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.5.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.5.0" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed" + integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== + dependencies: + "@jest/types" "^29.5.0" "@types/node" "*" + jest-util "^29.5.0" jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^28.0.2: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== +jest-regex-util@^29.4.3: + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.4.3.tgz#a42616141e0cae052cfa32c169945d00c0aa0bb8" + integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== -jest-resolve-dependencies@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.0.tgz#167becb8bee6e20b5ef4a3a728ec67aef6b0b79b" - integrity sha512-Ue1VYoSZquPwEvng7Uefw8RmZR+me/1kr30H2jMINjGeHgeO/JgrR6wxj2ofkJ7KSAA11W3cOrhNCbj5Dqqd9g== +jest-resolve-dependencies@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz#f0ea29955996f49788bf70996052aa98e7befee4" + integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== dependencies: - jest-regex-util "^28.0.2" - jest-snapshot "^28.1.0" + jest-regex-util "^29.4.3" + jest-snapshot "^29.5.0" -jest-resolve@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.0.tgz#b1f32748a6cee7d1779c7ef639c0a87078de3d35" - integrity sha512-vvfN7+tPNnnhDvISuzD1P+CRVP8cK0FHXRwPAcdDaQv4zgvwvag2n55/h5VjYcM5UJG7L4TwE5tZlzcI0X2Lhw== +jest-resolve@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.5.0.tgz#b053cc95ad1d5f6327f0ac8aae9f98795475ecdc" + integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" + jest-haste-map "^29.5.0" jest-pnp-resolver "^1.2.2" - jest-util "^28.1.0" - jest-validate "^28.1.0" + jest-util "^29.5.0" + jest-validate "^29.5.0" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.0.tgz#aefe2a1e618a69baa0b24a50edc54fdd7e728eaa" - integrity sha512-FBpmuh1HB2dsLklAlRdOxNTTHKFR6G1Qmd80pVDvwbZXTriqjWqjei5DKFC1UlM732KjYcE6yuCdiF0WUCOS2w== +jest-runner@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.5.0.tgz#6a57c282eb0ef749778d444c1d758c6a7693b6f8" + integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== dependencies: - "@jest/console" "^28.1.0" - "@jest/environment" "^28.1.0" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/console" "^29.5.0" + "@jest/environment" "^29.5.0" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" chalk "^4.0.0" - emittery "^0.10.2" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^28.0.2" - jest-environment-node "^28.1.0" - jest-haste-map "^28.1.0" - jest-leak-detector "^28.1.0" - jest-message-util "^28.1.0" - jest-resolve "^28.1.0" - jest-runtime "^28.1.0" - jest-util "^28.1.0" - jest-watcher "^28.1.0" - jest-worker "^28.1.0" + jest-docblock "^29.4.3" + jest-environment-node "^29.5.0" + jest-haste-map "^29.5.0" + jest-leak-detector "^29.5.0" + jest-message-util "^29.5.0" + jest-resolve "^29.5.0" + jest-runtime "^29.5.0" + jest-util "^29.5.0" + jest-watcher "^29.5.0" + jest-worker "^29.5.0" + p-limit "^3.1.0" source-map-support "0.5.13" - throat "^6.0.1" -jest-runtime@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.0.tgz#4847dcb2a4eb4b0f9eaf41306897e51fb1665631" - integrity sha512-wNYDiwhdH/TV3agaIyVF0lsJ33MhyujOe+lNTUiolqKt8pchy1Hq4+tDMGbtD5P/oNLA3zYrpx73T9dMTOCAcg== - dependencies: - "@jest/environment" "^28.1.0" - "@jest/fake-timers" "^28.1.0" - "@jest/globals" "^28.1.0" - "@jest/source-map" "^28.0.2" - "@jest/test-result" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" +jest-runtime@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.5.0.tgz#c83f943ee0c1da7eb91fa181b0811ebd59b03420" + integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== + dependencies: + "@jest/environment" "^29.5.0" + "@jest/fake-timers" "^29.5.0" + "@jest/globals" "^29.5.0" + "@jest/source-map" "^29.4.3" + "@jest/test-result" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" + "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^28.1.0" - jest-message-util "^28.1.0" - jest-mock "^28.1.0" - jest-regex-util "^28.0.2" - jest-resolve "^28.1.0" - jest-snapshot "^28.1.0" - jest-util "^28.1.0" + jest-haste-map "^29.5.0" + jest-message-util "^29.5.0" + jest-mock "^29.5.0" + jest-regex-util "^29.4.3" + jest-resolve "^29.5.0" + jest-snapshot "^29.5.0" + jest-util "^29.5.0" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.0.tgz#4b74fa8816707dd10fe9d551c2c258e5a67b53b6" - integrity sha512-ex49M2ZrZsUyQLpLGxQtDbahvgBjlLPgklkqGM0hq/F7W/f8DyqZxVHjdy19QKBm4O93eDp+H5S23EiTbbUmHw== +jest-snapshot@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.5.0.tgz#c9c1ce0331e5b63cd444e2f95a55a73b84b1e8ce" + integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^28.1.0" - "@jest/transform" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/expect-utils" "^29.5.0" + "@jest/transform" "^29.5.0" + "@jest/types" "^29.5.0" "@types/babel__traverse" "^7.0.6" "@types/prettier" "^2.1.5" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^28.1.0" + expect "^29.5.0" graceful-fs "^4.2.9" - jest-diff "^28.1.0" - jest-get-type "^28.0.2" - jest-haste-map "^28.1.0" - jest-matcher-utils "^28.1.0" - jest-message-util "^28.1.0" - jest-util "^28.1.0" + jest-diff "^29.5.0" + jest-get-type "^29.4.3" + jest-matcher-utils "^29.5.0" + jest-message-util "^29.5.0" + jest-util "^29.5.0" natural-compare "^1.4.0" - pretty-format "^28.1.0" + pretty-format "^29.5.0" semver "^7.3.5" -jest-util@^28.0.0, jest-util@^28.1.0: +jest-util@^28.1.0: version "28.1.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.0.tgz#d54eb83ad77e1dd441408738c5a5043642823be5" integrity sha512-qYdCKD77k4Hwkose2YBEqQk7PzUf/NSE+rutzceduFveQREeH6b+89Dc9+wjX9dAwHcgdx4yedGA3FQlU/qCTA== @@ -3366,49 +3490,63 @@ jest-util@^28.0.0, jest-util@^28.1.0: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.0.tgz#8a6821f48432aba9f830c26e28226ad77b9a0e18" - integrity sha512-Lly7CJYih3vQBfjLeANGgBSBJ7pEa18cxpQfQEq2go2xyEzehnHfQTjoUia8xUv4x4J80XKFIDwJJThXtRFQXQ== +jest-util@^29.0.0, jest-util@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f" + integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== dependencies: - "@jest/types" "^28.1.0" + "@jest/types" "^29.5.0" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.5.0.tgz#8e5a8f36178d40e47138dc00866a5f3bd9916ffc" + integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== + dependencies: + "@jest/types" "^29.5.0" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^28.0.2" + jest-get-type "^29.4.3" leven "^3.1.0" - pretty-format "^28.1.0" + pretty-format "^29.5.0" -jest-watcher@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.0.tgz#aaa7b4164a4e77eeb5f7d7b25ede5e7b4e9c9aaf" - integrity sha512-tNHMtfLE8Njcr2IRS+5rXYA4BhU90gAOwI9frTGOqd+jX0P/Au/JfRSNqsf5nUTcWdbVYuLxS1KjnzILSoR5hA== +jest-watcher@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.5.0.tgz#cf7f0f949828ba65ddbbb45c743a382a4d911363" + integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== dependencies: - "@jest/test-result" "^28.1.0" - "@jest/types" "^28.1.0" + "@jest/test-result" "^29.5.0" + "@jest/types" "^29.5.0" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.0" + emittery "^0.13.1" + jest-util "^29.5.0" string-length "^4.0.1" -jest-worker@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.0.tgz#ced54757a035e87591e1208253a6e3aac1a855e5" - integrity sha512-ZHwM6mNwaWBR52Snff8ZvsCTqQsvhCxP/bT1I6T6DAnb6ygkshsyLQIMxFwHpYxht0HOoqt23JlC01viI7T03A== +jest-worker@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.5.0.tgz#bdaefb06811bd3384d93f009755014d8acb4615d" + integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== dependencies: "@types/node" "*" + jest-util "^29.5.0" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^28.1.0: - version "28.1.0" - resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.0.tgz#f420e41c8f2395b9a30445a97189ebb57593d831" - integrity sha512-TZR+tHxopPhzw3c3560IJXZWLNHgpcz1Zh0w5A65vynLGNcg/5pZ+VildAd7+XGOu6jd58XMY/HNn0IkZIXVXg== +jest@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" + integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== dependencies: - "@jest/core" "^28.1.0" + "@jest/core" "^29.5.0" + "@jest/types" "^29.5.0" import-local "^3.0.2" - jest-cli "^28.1.0" + jest-cli "^29.5.0" js-sdsl@^4.1.4: version "4.4.0" @@ -3515,6 +3653,11 @@ json5@^2.2.1: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -4002,7 +4145,7 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -4158,6 +4301,15 @@ pretty-format@^28.1.0: ansi-styles "^5.0.0" react-is "^18.0.0" +pretty-format@^29.5.0: + version "29.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a" + integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== + dependencies: + "@jest/schemas" "^29.4.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -4315,6 +4467,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +pure-rand@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== + qs@6.10.3: version "6.10.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" @@ -4513,10 +4670,10 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve.exports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" - integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== +resolve.exports@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" + integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.1.6, resolve@^1.20.0: version "1.22.0" @@ -4867,7 +5024,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -4881,14 +5038,6 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -4899,14 +5048,6 @@ taffydb@2.6.2: resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -4921,11 +5062,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -throat@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" - integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== - tmp@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" @@ -4973,19 +5109,19 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -ts-jest@^28.0.3: - version "28.0.3" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.3.tgz#d1c47f167e56eef3989bb51afaf7fc1c87a04c52" - integrity sha512-HzgbEDQ2KgVtDmpXToqAcKTyGHdHsG23i/iUjfxji92G5eT09S1m9UHZd7csF0Bfgh9txM4JzwHnv7r1waFPlw== +ts-jest@^29.1.0: + version "29.1.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.0.tgz#4a9db4104a49b76d2b368ea775b6c9535c603891" + integrity sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" - jest-util "^28.0.0" - json5 "^2.2.1" + jest-util "^29.0.0" + json5 "^2.2.3" lodash.memoize "4.x" make-error "1.x" semver "7.x" - yargs-parser "^20.x" + yargs-parser "^21.0.1" ts-map@^1.0.3: version "1.0.3" @@ -5151,6 +5287,15 @@ v8-to-istanbul@^9.0.0: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" +v8-to-istanbul@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -5201,7 +5346,7 @@ vue2-ace-editor@^0.0.15: dependencies: brace "^0.11.0" -walker@^1.0.7: +walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -5275,10 +5420,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" - integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" signal-exit "^3.0.7" @@ -5303,7 +5448,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^20.2.2, yargs-parser@^20.2.9, yargs-parser@^20.x: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== @@ -5313,6 +5458,11 @@ yargs-parser@^21.0.0: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== +yargs-parser@^21.0.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" From 11f39627ced1d836abbc3cf8c13fe4f31861c78c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 26 Apr 2023 23:40:33 +0900 Subject: [PATCH 024/123] Update rust-sdk crypto dependency (#14) Fixes #13 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 94d4786b..642c61d5 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.4", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.6", "@types/express": "^4.17.13", "another-json": "^0.2.0", "async-lock": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index 1e098456..cf338c11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -662,10 +662,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.4": - version "0.1.0-beta.4" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.4.tgz#80456b2e2cc731982f0d3c6aece80cefa1ebb797" - integrity sha512-XjCp/tG3LRMxMj/MMZfypD5BtW3J1B6oXY2Og8Ed0SyU4uWdglalMwrBUKlDotJr0/Q/2OTspGjD+ytAzCspyw== +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.6": + version "0.1.0-beta.6" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.6.tgz#0ecae51103ee3c107af0d6d0738f33eb7cc9857e" + integrity sha512-JXyrHuCVMydUGgSetWsfqbbvHj3aUMOX5TUghlMtLFromyEu7wIsNgYt7PjJ+k3WdF4GVABRy4P6GNjaEMy2uA== dependencies: https-proxy-agent "^5.0.1" node-downloader-helper "^2.1.5" From 888c97d45fd9a2e5b5fb5df5857b94bf3a1feeb1 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 5 Jun 2023 11:40:08 +0100 Subject: [PATCH 025/123] Shortcircuit sync loop if we've requested to stop syncing --- src/MatrixClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 47dc6b72..d0324571 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -721,6 +721,10 @@ export class MatrixClient extends EventEmitter { await Promise.resolve(this.storage.setSyncToken(token)); } } catch (e) { + // If we've requested to stop syncing, don't bother checking the error. + if (this.stopSyncing) { + return; + } LogService.error("MatrixClientLite", "Error handling sync " + extractRequestError(e)); const backoffTime = SYNC_BACKOFF_MIN_MS + Math.random() * (SYNC_BACKOFF_MAX_MS - SYNC_BACKOFF_MIN_MS); LogService.info("MatrixClientLite", `Backing off for ${backoffTime}ms`); From 9df413ef8b46b425f0743b5a612e1aefd8d8d3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tadeusz=20So=C5=9Bnierz?= Date: Mon, 5 Jun 2023 21:15:40 +0200 Subject: [PATCH 026/123] Reduce our reliance on an up-to-date joined rooms cache (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reduce our reliance on an up-to-date joined rooms cache * Remove joined room caching entirely * Remove now-unused variables * Undo accidental API breakage in getJoinedRooms() * Deprecate ensureJoined() --------- Co-authored-by: Tadeusz Sośnierz --- src/appservice/Appservice.ts | 2 - src/appservice/Intent.ts | 39 +--- test/appservice/AppserviceTest.ts | 94 +-------- test/appservice/IntentTest.ts | 331 ------------------------------ 4 files changed, 9 insertions(+), 457 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 539d68c1..36f9356f 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -631,9 +631,7 @@ export class Appservice extends EventEmitter { const botDomain = new UserID(this.botUserId).domain; if (domain !== botDomain) return; // can't be impersonated, so don't try - // Update the target intent's joined rooms (fixes transition errors with the cache, like join->kick->join) const intent = this.getIntentForUserId(event['state_key']); - await intent.refreshJoinedRooms(); const targetMembership = event["content"]["membership"]; if (targetMembership === "join") { diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 961d1752..eb3f3787 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -34,7 +34,6 @@ export class Intent { private client: MatrixClient; private unstableApisInstance: UnstableAppserviceApis; - private knownJoinedRooms: string[] = []; private cryptoSetupPromise: Promise; /** @@ -169,10 +168,9 @@ export class Intent { } // Now set up crypto - await this.client.crypto.prepare(await this.refreshJoinedRooms()); + await this.client.crypto.prepare(await this.getJoinedRooms()); this.appservice.on("room.event", (roomId, event) => { - if (!this.knownJoinedRooms.includes(roomId)) return; this.client.crypto.onRoomEvent(roomId, event); }); @@ -186,16 +184,14 @@ export class Intent { } /** - * Gets the joined rooms for the intent. Note that by working around - * the intent to join rooms may yield inaccurate results. + * Gets the joined rooms for the intent. * @returns {Promise} Resolves to an array of room IDs where * the intent is joined. */ @timedIntentFunctionCall() public async getJoinedRooms(): Promise { await this.ensureRegistered(); - if (this.knownJoinedRooms.length === 0) await this.refreshJoinedRooms(); - return this.knownJoinedRooms.map(r => r); // clone + return await this.client.getJoinedRooms(); } /** @@ -207,10 +203,7 @@ export class Intent { @timedIntentFunctionCall() public async leaveRoom(roomId: string, reason?: string): Promise { await this.ensureRegistered(); - return this.client.leaveRoom(roomId, reason).then(async () => { - // Recalculate joined rooms now that we've left a room - await this.refreshJoinedRooms(); - }); + return this.client.leaveRoom(roomId, reason); } /** @@ -221,11 +214,7 @@ export class Intent { @timedIntentFunctionCall() public async joinRoom(roomIdOrAlias: string): Promise { await this.ensureRegistered(); - return this.client.joinRoom(roomIdOrAlias).then(async roomId => { - // Recalculate joined rooms now that we've joined a room - await this.refreshJoinedRooms(); - return roomId; - }); + return this.client.joinRoom(roomIdOrAlias); } /** @@ -267,35 +256,23 @@ export class Intent { * Ensures the user is joined to the given room * @param {string} roomId The room ID to join * @returns {Promise} Resolves when complete + * @deprecated Use `joinRoom()` instead */ @timedIntentFunctionCall() public async ensureJoined(roomId: string) { - if (this.knownJoinedRooms.indexOf(roomId) !== -1) { - return; - } - - await this.refreshJoinedRooms(); - - if (this.knownJoinedRooms.indexOf(roomId) !== -1) { - return; - } - const returnedRoomId = await this.client.joinRoom(roomId); - if (!this.knownJoinedRooms.includes(returnedRoomId)) { - this.knownJoinedRooms.push(returnedRoomId); - } return returnedRoomId; } /** * Refreshes which rooms the user is joined to, potentially saving time on * calls like ensureJoined() + * @deprecated There is no longer a joined rooms cache, use `getJoinedRooms()` instead * @returns {Promise} Resolves to the joined room IDs for the user. */ @timedIntentFunctionCall() public async refreshJoinedRooms(): Promise { - this.knownJoinedRooms = await this.client.getJoinedRooms(); - return this.knownJoinedRooms.map(r => r); // clone + return await this.getJoinedRooms(); } /** diff --git a/test/appservice/AppserviceTest.ts b/test/appservice/AppserviceTest.ts index b6d1506c..e71d0f89 100644 --- a/test/appservice/AppserviceTest.ts +++ b/test/appservice/AppserviceTest.ts @@ -1752,7 +1752,7 @@ describe('Appservice', () => { }; const intent = appservice.getIntentForSuffix("test"); - intent.refreshJoinedRooms = () => Promise.resolve([]); + intent.getJoinedRooms = () => Promise.resolve([]); await appservice.begin(); @@ -1857,98 +1857,6 @@ describe('Appservice', () => { } }); - it('should refresh membership information of intents when actions are performed against them', async () => { - const port = await getPort(); - const hsToken = "s3cret_token"; - const appservice = new Appservice({ - port: port, - bindAddress: '', - homeserverName: 'example.org', - homeserverUrl: 'https://localhost', - registration: { - as_token: "", - hs_token: hsToken, - sender_localpart: "_bot_", - namespaces: { - users: [{ exclusive: true, regex: "@_prefix_.*:.+" }], - rooms: [], - aliases: [], - }, - }, - }); - appservice.botIntent.ensureRegistered = () => { - return null; - }; - - await appservice.begin(); - - try { - const intent = appservice.getIntentForSuffix("test"); - const refreshSpy = simple.stub().callFn(() => Promise.resolve([])); - intent.refreshJoinedRooms = refreshSpy; - - // polyfill the dummy user too - const intent2 = appservice.getIntentForSuffix("test___WRONGUSER"); - intent2.refreshJoinedRooms = () => Promise.resolve([]); - - const joinTxn = { - events: [ - { - type: "m.room.member", - room_id: "!AAA:example.org", - content: { membership: "join" }, - state_key: "@_prefix_test:example.org", - sender: "@_prefix_test:example.org", - }, - { - type: "m.room.member", - room_id: "!AAA:example.org", - content: { membership: "join" }, - state_key: "@_prefix_test___WRONGUSER:example.org", - sender: "@_prefix_test:example.org", - }, - ], - }; - const kickTxn = { - events: [ - { - type: "m.room.member", - room_id: "!AAA:example.org", - content: { membership: "leave" }, - state_key: "@_prefix_test:example.org", - sender: "@someone_else:example.org", - }, - { - type: "m.room.member", - room_id: "!AAA:example.org", - content: { membership: "leave" }, - state_key: "@_prefix_test___WRONGUSER:example.org", - sender: "@someone_else:example.org", - }, - ], - }; - - // eslint-disable-next-line no-inner-declarations - async function doCall(route: string, opts: any = {}) { - const res = await requestPromise({ - uri: `http://localhost:${port}${route}`, - method: "PUT", - qs: { access_token: hsToken }, - ...opts, - }); - expect(res).toMatchObject({}); - - expect(refreshSpy.callCount).toBe(1); - refreshSpy.callCount = 0; - } - - await doCall("/transactions/1", { json: joinTxn }); - await doCall("/_matrix/app/v1/transactions/2", { json: kickTxn }); - } finally { - appservice.stop(); - } - }); - it('should handle room upgrade events in transactions', async () => { const port = await getPort(); const hsToken = "s3cret_token"; diff --git a/test/appservice/IntentTest.ts b/test/appservice/IntentTest.ts index 66b2ddfc..8fa35ff2 100644 --- a/test/appservice/IntentTest.ts +++ b/test/appservice/IntentTest.ts @@ -3,7 +3,6 @@ import HttpBackend from 'matrix-mock-request'; import * as tmp from "tmp"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { expectArrayEquals } from "../TestUtils"; import { Appservice, IAppserviceCryptoStorageProvider, @@ -268,280 +267,12 @@ describe('Intent', () => { }); }); - describe('getJoinedRooms', () => { - it('should fetch rooms if none are cached', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomsPartA = ['!a:example.org', '!b:example.org']; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const registeredSpy = simple.mock(intent, "ensureRegistered").callFn(() => { - return Promise.resolve(); - }); - - const getJoinedSpy = simple.stub().callFn(() => { - return Promise.resolve(roomsPartA); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - - const joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals(roomsPartA, joinedRooms); - expect(registeredSpy.callCount).toBe(1); - expect(getJoinedSpy.callCount).toBe(1); - }); - - it('should cache rooms on join', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomId = "!test:example.org"; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const registeredSpy = simple.mock(intent, "ensureRegistered").callFn(() => { - return Promise.resolve(); - }); - - const getJoinedSpy = simple.stub().callFn(() => { - if (getJoinedSpy.callCount === 1) return Promise.resolve([]); - return Promise.resolve([roomId]); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - - const joinSpy = simple.stub().callFn((rid) => { - expect(rid).toBe(roomId); - return Promise.resolve(rid); - }); - intent.underlyingClient.joinRoom = joinSpy; - - let joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([], joinedRooms); - expect(registeredSpy.callCount).toBe(1); - expect(getJoinedSpy.callCount).toBe(1); - expect(joinSpy.callCount).toBe(0); - - await intent.joinRoom(roomId); - - joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([roomId], joinedRooms); - expect(registeredSpy.callCount).toBe(3); - expect(getJoinedSpy.callCount).toBe(2); - expect(joinSpy.callCount).toBe(1); - }); - - it('should cache rooms on leave', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomId = "!test:example.org"; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const registeredSpy = simple.mock(intent, "ensureRegistered").callFn(() => { - return Promise.resolve(); - }); - - const getJoinedSpy = simple.stub().callFn(() => { - if (getJoinedSpy.callCount > 1) return Promise.resolve([]); - return Promise.resolve([roomId]); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - - const leaveSpy = simple.stub().callFn((rid) => { - expect(rid).toBe(roomId); - return Promise.resolve(rid); - }); - intent.underlyingClient.leaveRoom = leaveSpy; - - let joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([roomId], joinedRooms); - expect(registeredSpy.callCount).toBe(1); - expect(getJoinedSpy.callCount).toBe(1); - expect(leaveSpy.callCount).toBe(0); - - await intent.leaveRoom(roomId); - - joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([], joinedRooms); - expect(registeredSpy.callCount).toBe(3); - expect(getJoinedSpy.callCount).toBe(3); - expect(leaveSpy.callCount).toBe(1); - }); - - it('should cache rooms on ensureJoined', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomId = "!test:example.org"; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const registeredSpy = simple.mock(intent, "ensureRegistered").callFn(() => { - return Promise.resolve(); - }); - - const getJoinedSpy = simple.stub().callFn(() => { - if (getJoinedSpy.callCount <= 2) return Promise.resolve([]); - return Promise.resolve([roomId]); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - - const joinSpy = simple.stub().callFn((rid) => { - expect(rid).toBe(roomId); - return Promise.resolve(rid); - }); - intent.underlyingClient.joinRoom = joinSpy; - - let joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([], joinedRooms); - expect(registeredSpy.callCount).toBe(1); - expect(getJoinedSpy.callCount).toBe(1); - expect(joinSpy.callCount).toBe(0); - - await intent.ensureJoined(roomId); - - joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([roomId], joinedRooms); - expect(registeredSpy.callCount).toBe(2); - expect(getJoinedSpy.callCount).toBe(2); - expect(joinSpy.callCount).toBe(1); - - // Duplicate just to prove it caches it - await intent.ensureJoined(roomId); - - joinedRooms = await intent.getJoinedRooms(); - expectArrayEquals([roomId], joinedRooms); - expect(registeredSpy.callCount).toBe(3); - expect(getJoinedSpy.callCount).toBe(2); - expect(joinSpy.callCount).toBe(1); - }); - }); - - describe('refreshJoinedRooms', () => { - it('should overwrite any previously known joined rooms', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomsPartA = ['!a:example.org', '!b:example.org']; - const roomsPartB = ['!c:example.org', '!d:example.org']; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - // We have to do private access to ensure that the intent actually overwrites - // its cache. - const getJoinedRooms = () => (intent).knownJoinedRooms; - const setJoinedRooms = (rooms) => (intent).knownJoinedRooms = rooms; - - const getJoinedSpy = simple.stub().callFn(() => { - return Promise.resolve(roomsPartB); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - - // Do a quick assert to prove that our private access hooks work - expectArrayEquals([], getJoinedRooms()); - setJoinedRooms(roomsPartA); - expectArrayEquals(roomsPartA, getJoinedRooms()); - - const result = await intent.refreshJoinedRooms(); - expect(getJoinedSpy.callCount).toBe(1); - expectArrayEquals(roomsPartB, result); - expectArrayEquals(roomsPartB, getJoinedRooms()); - }); - }); - describe('ensureJoined', () => { - it('should fetch the rooms the user is joined to', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const roomIds = ["!a:example.org", "!b:example.org"]; - const targetRoomId = "!a:example.org"; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const getJoinedSpy = simple.stub().callFn(() => { - return Promise.resolve(roomIds); - }); - const joinSpy = simple.stub().callFn((rid) => { - expect(rid).toEqual(targetRoomId); - return Promise.resolve("!joined:example.org"); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - intent.underlyingClient.joinRoom = joinSpy; - - await intent.ensureJoined(targetRoomId); - expect(getJoinedSpy.callCount).toBe(1); - expect(joinSpy.callCount).toBe(0); - }); - it('should attempt to join rooms a user is not in', async () => { const userId = "@someone:example.org"; const botUserId = "@bot:example.org"; const asToken = "s3cret"; const hsUrl = "https://localhost"; - const roomIds = ["!a:example.org", "!b:example.org"]; const targetRoomId = "!c:example.org"; const appservice = { botUserId: botUserId }; const storage = new MemoryStorageProvider(); @@ -555,18 +286,13 @@ describe('Intent', () => { const intent = new Intent(options, userId, appservice); - const getJoinedSpy = simple.stub().callFn(() => { - return Promise.resolve(roomIds); - }); const joinSpy = simple.stub().callFn((rid) => { expect(rid).toEqual(targetRoomId); return Promise.resolve("!joined:example.org"); }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; intent.underlyingClient.joinRoom = joinSpy; await intent.ensureJoined(targetRoomId); - expect(getJoinedSpy.callCount).toBe(1); expect(joinSpy.callCount).toBe(1); }); @@ -575,7 +301,6 @@ describe('Intent', () => { const botUserId = "@bot:example.org"; const asToken = "s3cret"; const hsUrl = "https://localhost"; - const roomIds = ["!a:example.org", "!b:example.org"]; const targetRoomId = "!c:example.org"; const appservice = { botUserId: botUserId }; const storage = new MemoryStorageProvider(); @@ -589,14 +314,10 @@ describe('Intent', () => { const intent = new Intent(options, userId, appservice); - const getJoinedSpy = simple.stub().callFn(() => { - return Promise.resolve(roomIds); - }); const joinSpy = simple.stub().callFn((rid) => { expect(rid).toEqual(targetRoomId); throw new Error("Simulated failure"); }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; intent.underlyingClient.joinRoom = joinSpy; try { @@ -607,49 +328,8 @@ describe('Intent', () => { } catch (e) { expect(e.message).toEqual("Simulated failure"); } - expect(getJoinedSpy.callCount).toBe(1); expect(joinSpy.callCount).toBe(1); }); - - it('should proxy failure for getting joined rooms', async () => { - const userId = "@someone:example.org"; - const botUserId = "@bot:example.org"; - const asToken = "s3cret"; - const hsUrl = "https://localhost"; - const targetRoomId = "!c:example.org"; - const appservice = { botUserId: botUserId }; - const storage = new MemoryStorageProvider(); - const options = { - homeserverUrl: hsUrl, - storage: storage, - registration: { - as_token: asToken, - }, - }; - - const intent = new Intent(options, userId, appservice); - - const getJoinedSpy = simple.stub().callFn(() => { - throw new Error("Simulated failure"); - }); - const joinSpy = simple.stub().callFn((rid) => { - expect(rid).toEqual(targetRoomId); - return Promise.resolve("!joined:example.org"); - }); - intent.underlyingClient.getJoinedRooms = getJoinedSpy; - intent.underlyingClient.joinRoom = joinSpy; - - try { - await intent.ensureJoined(targetRoomId); - - // noinspection ExceptionCaughtLocallyJS - throw new Error("Request completed when it should have failed"); - } catch (e) { - expect(e.message).toEqual("Simulated failure"); - } - expect(getJoinedSpy.callCount).toBe(1); - expect(joinSpy.callCount).toBe(0); - }); }); describe('ensureRegisteredAndJoined', () => { @@ -969,23 +649,17 @@ describe('Intent', () => { expect(rid).toEqual(targetRoomId); return {}; }); - const refreshJoinedRoomsSpy = simple.stub().callFn(() => { - return Promise.resolve([]); - }); - const joinRoomSpy = simple.stub().callFn((rid) => { expect(rid).toEqual(targetRoomId); return Promise.resolve(targetRoomId); }); intent.underlyingClient.joinRoom = joinRoomSpy; - intent.refreshJoinedRooms = refreshJoinedRoomsSpy; const result = await intent.joinRoom(targetRoomId); expect(result).toEqual(targetRoomId); expect(joinRoomSpy.callCount).toBe(1); expect(registeredSpy.callCount).toBe(1); expect(joinSpy.callCount).toBe(0); - expect(refreshJoinedRoomsSpy.callCount).toBe(1); }); it('should proxy errors upwards', async () => { @@ -1060,22 +734,17 @@ describe('Intent', () => { expect(rid).toEqual(targetRoomId); return {}; }); - const refreshJoinedRoomsSpy = simple.stub().callFn(() => { - return Promise.resolve([]); - }); const leaveRoomSpy = simple.stub().callFn((rid) => { expect(rid).toEqual(targetRoomId); return Promise.resolve(targetRoomId); }); intent.underlyingClient.leaveRoom = leaveRoomSpy; - intent.refreshJoinedRooms = refreshJoinedRoomsSpy; await intent.leaveRoom(targetRoomId); expect(leaveRoomSpy.callCount).toBe(1); expect(registeredSpy.callCount).toBe(1); expect(joinSpy.callCount).toBe(0); - expect(refreshJoinedRoomsSpy.callCount).toBe(1); }); it('should proxy errors upwards', async () => { From 79026712f70ac7beee8ef36bb3b0c6e7a94eeb86 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 29 Jun 2023 13:49:15 +0100 Subject: [PATCH 027/123] Update src/MatrixClient.ts Co-authored-by: Andrew Ferrazzutti --- src/MatrixClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index d0324571..281fe444 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -723,6 +723,7 @@ export class MatrixClient extends EventEmitter { } catch (e) { // If we've requested to stop syncing, don't bother checking the error. if (this.stopSyncing) { + LogService.info("MatrixClientLite", "Client stop requested - cancelling sync"); return; } LogService.error("MatrixClientLite", "Error handling sync " + extractRequestError(e)); From 5617a1fa618ea64f8ca7bca139f5742c6c7cc62e Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 29 Jun 2023 14:00:53 +0100 Subject: [PATCH 028/123] Refactor transaction handler. --- src/appservice/Appservice.ts | 405 ++++++++++++++++++----------------- 1 file changed, 206 insertions(+), 199 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 36f9356f..bfddc20d 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -236,7 +236,7 @@ export class Appservice extends EventEmitter { private appServer: any; private intentsCache: LRU; private eventProcessors: { [eventType: string]: IPreprocessor[] } = {}; - private pendingTransactions: { [txnId: string]: Promise } = {}; + private pendingTransactions = new Map>(); /** * Creates a new application service. @@ -655,248 +655,255 @@ export class Appservice extends EventEmitter { return providedToken === this.registration.hs_token; } - private async onTransaction(req: express.Request, res: express.Response): Promise { - if (!this.isAuthed(req)) { - res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" }); - return; - } - - if (typeof (req.body) !== "object") { - res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" }); - return; - } - - if (!req.body["events"] || !Array.isArray(req.body["events"])) { - res.status(400).json({ errcode: "BAD_REQUEST", error: "Invalid JSON: expected events" }); - return; - } - - const txnId = req.params["txnId"]; + private async handleTransaction(body: Record) { + // Process all the crypto stuff first to ensure that future transactions (if not this one) + // will decrypt successfully. We start with EDUs because we need structures to put counts + // and such into in a later stage, and EDUs are independent of crypto. - if (await Promise.resolve(this.storage.isTransactionCompleted(txnId))) { - res.status(200).json({}); - return; + const byUserId: { + [userId: string]: { + counts: Record; + toDevice: any[]; + unusedFallbacks: OTKAlgorithm[]; + }; + } = {}; + + const orderedEdus = []; + if (Array.isArray(body["de.sorunome.msc2409.to_device"])) { + orderedEdus.push(...body["de.sorunome.msc2409.to_device"].map(e => ({ + ...e, + unsigned: { + ...e['unsigned'], + [EDU_ANNOTATION_KEY]: EduAnnotation.ToDevice, + }, + }))); } - - if (this.pendingTransactions[txnId]) { - try { - await this.pendingTransactions[txnId]; - res.status(200).json({}); - } catch (e) { - LogService.error("Appservice", e); - res.status(500).json({}); - } - return; + if (Array.isArray(body["de.sorunome.msc2409.ephemeral"])) { + orderedEdus.push(...body["de.sorunome.msc2409.ephemeral"].map(e => ({ + ...e, + unsigned: { + ...e['unsigned'], + [EDU_ANNOTATION_KEY]: EduAnnotation.Ephemeral, + }, + }))); } + for (let event of orderedEdus) { + if (event['edu_type']) event['type'] = event['edu_type']; // handle property change during MSC2409's course - LogService.info("Appservice", "Processing transaction " + txnId); - // eslint-disable-next-line no-async-promise-executor - this.pendingTransactions[txnId] = new Promise(async (resolve) => { - // Process all the crypto stuff first to ensure that future transactions (if not this one) - // will decrypt successfully. We start with EDUs because we need structures to put counts - // and such into in a later stage, and EDUs are independent of crypto. - - const byUserId: { - [userId: string]: { - counts: Record; - toDevice: any[]; - unusedFallbacks: OTKAlgorithm[]; - }; - } = {}; - - const orderedEdus = []; - if (Array.isArray(req.body["de.sorunome.msc2409.to_device"])) { - orderedEdus.push(...req.body["de.sorunome.msc2409.to_device"].map(e => ({ - ...e, - unsigned: { - ...e['unsigned'], - [EDU_ANNOTATION_KEY]: EduAnnotation.ToDevice, - }, - }))); - } - if (Array.isArray(req.body["de.sorunome.msc2409.ephemeral"])) { - orderedEdus.push(...req.body["de.sorunome.msc2409.ephemeral"].map(e => ({ - ...e, - unsigned: { - ...e['unsigned'], - [EDU_ANNOTATION_KEY]: EduAnnotation.Ephemeral, - }, - }))); - } - for (let event of orderedEdus) { - if (event['edu_type']) event['type'] = event['edu_type']; // handle property change during MSC2409's course + LogService.info("Appservice", `Processing ${event['unsigned'][EDU_ANNOTATION_KEY]} event of type ${event["type"]}`); + event = await this.processEphemeralEvent(event); - LogService.info("Appservice", `Processing ${event['unsigned'][EDU_ANNOTATION_KEY]} event of type ${event["type"]}`); - event = await this.processEphemeralEvent(event); + // These events aren't tied to rooms, so just emit them generically + this.emit("ephemeral.event", event); - // These events aren't tied to rooms, so just emit them generically - this.emit("ephemeral.event", event); + if (this.cryptoStorage && (event["type"] === "m.room.encrypted" || event.unsigned?.[EDU_ANNOTATION_KEY] === EduAnnotation.ToDevice)) { + const toUser = event["to_user_id"]; + const intent = this.getIntentForUserId(toUser); + await intent.enableEncryption(); - if (this.cryptoStorage && (event["type"] === "m.room.encrypted" || event.unsigned?.[EDU_ANNOTATION_KEY] === EduAnnotation.ToDevice)) { - const toUser = event["to_user_id"]; - const intent = this.getIntentForUserId(toUser); - await intent.enableEncryption(); - - if (!byUserId[toUser]) byUserId[toUser] = { counts: null, toDevice: null, unusedFallbacks: null }; - if (!byUserId[toUser].toDevice) byUserId[toUser].toDevice = []; - byUserId[toUser].toDevice.push(event); - } + if (!byUserId[toUser]) byUserId[toUser] = { counts: null, toDevice: null, unusedFallbacks: null }; + if (!byUserId[toUser].toDevice) byUserId[toUser].toDevice = []; + byUserId[toUser].toDevice.push(event); } + } - const deviceLists: { changed: string[], removed: string[] } = req.body["org.matrix.msc3202.device_lists"] ?? { - changed: [], - removed: [], - }; + const deviceLists = body["org.matrix.msc3202.device_lists"] as { changed: string[], removed: string[] } ?? { + changed: [], + removed: [], + }; - if (!deviceLists.changed) deviceLists.changed = []; - if (!deviceLists.removed) deviceLists.removed = []; + if (!deviceLists.changed) deviceLists.changed = []; + if (!deviceLists.removed) deviceLists.removed = []; - if (deviceLists.changed.length || deviceLists.removed.length) { - this.emit("device_lists", deviceLists); - } + if (deviceLists.changed.length || deviceLists.removed.length) { + this.emit("device_lists", deviceLists); + } - let otks = req.body["org.matrix.msc3202.device_one_time_keys_count"]; - const otks2 = req.body["org.matrix.msc3202.device_one_time_key_counts"]; - if (otks2 && !otks) { - LogService.warn( - "Appservice", - "Your homeserver is using an outdated field (device_one_time_key_counts) to talk to this appservice. " + - "If you're using Synapse, please upgrade to 1.73.0 or higher.", - ); - otks = otks2; - } - if (otks) { - this.emit("otk.counts", otks); - } - if (otks && this.cryptoStorage) { - for (const userId of Object.keys(otks)) { - const intent = this.getIntentForUserId(userId); - await intent.enableEncryption(); - const otksForUser = otks[userId][intent.underlyingClient.crypto.clientDeviceId]; - if (otksForUser) { - if (!byUserId[userId]) { - byUserId[userId] = { - counts: null, - toDevice: null, - unusedFallbacks: null, - }; - } - byUserId[userId].counts = otksForUser; + let otks = body["org.matrix.msc3202.device_one_time_keys_count"]; + const otks2 = body["org.matrix.msc3202.device_one_time_key_counts"]; + if (otks2 && !otks) { + LogService.warn( + "Appservice", + "Your homeserver is using an outdated field (device_one_time_key_counts) to talk to this appservice. " + + "If you're using Synapse, please upgrade to 1.73.0 or higher.", + ); + otks = otks2; + } + if (otks) { + this.emit("otk.counts", otks); + } + if (otks && this.cryptoStorage) { + for (const userId of Object.keys(otks)) { + const intent = this.getIntentForUserId(userId); + await intent.enableEncryption(); + const otksForUser = otks[userId][intent.underlyingClient.crypto.clientDeviceId]; + if (otksForUser) { + if (!byUserId[userId]) { + byUserId[userId] = { + counts: null, + toDevice: null, + unusedFallbacks: null, + }; } + byUserId[userId].counts = otksForUser; } } + } - const fallbacks = req.body["org.matrix.msc3202.device_unused_fallback_key_types"]; - if (fallbacks) { - this.emit("otk.unused_fallback_keys", fallbacks); - } - if (fallbacks && this.cryptoStorage) { - for (const userId of Object.keys(fallbacks)) { - const intent = this.getIntentForUserId(userId); - await intent.enableEncryption(); - const fallbacksForUser = fallbacks[userId][intent.underlyingClient.crypto.clientDeviceId]; - if (Array.isArray(fallbacksForUser) && !fallbacksForUser.includes(OTKAlgorithm.Signed)) { - if (!byUserId[userId]) { - byUserId[userId] = { - counts: null, - toDevice: null, - unusedFallbacks: null, - }; - } - byUserId[userId].unusedFallbacks = fallbacksForUser; + const fallbacks = body["org.matrix.msc3202.device_unused_fallback_key_types"]; + if (fallbacks) { + this.emit("otk.unused_fallback_keys", fallbacks); + } + if (fallbacks && this.cryptoStorage) { + for (const userId of Object.keys(fallbacks)) { + const intent = this.getIntentForUserId(userId); + await intent.enableEncryption(); + const fallbacksForUser = fallbacks[userId][intent.underlyingClient.crypto.clientDeviceId]; + if (Array.isArray(fallbacksForUser) && !fallbacksForUser.includes(OTKAlgorithm.Signed)) { + if (!byUserId[userId]) { + byUserId[userId] = { + counts: null, + toDevice: null, + unusedFallbacks: null, + }; } + byUserId[userId].unusedFallbacks = fallbacksForUser; } } + } - if (this.cryptoStorage) { - for (const userId of Object.keys(byUserId)) { - const intent = this.getIntentForUserId(userId); - await intent.enableEncryption(); - const info = byUserId[userId]; - const userStorage = this.storage.storageForUser(userId); + if (this.cryptoStorage) { + for (const userId of Object.keys(byUserId)) { + const intent = this.getIntentForUserId(userId); + await intent.enableEncryption(); + const info = byUserId[userId]; + const userStorage = this.storage.storageForUser(userId); - if (!info.toDevice) info.toDevice = []; - if (!info.unusedFallbacks) info.unusedFallbacks = JSON.parse(await userStorage.readValue("last_unused_fallbacks") || "[]"); - if (!info.counts) info.counts = JSON.parse(await userStorage.readValue("last_counts") || "{}"); + if (!info.toDevice) info.toDevice = []; + if (!info.unusedFallbacks) info.unusedFallbacks = JSON.parse(await userStorage.readValue("last_unused_fallbacks") || "[]"); + if (!info.counts) info.counts = JSON.parse(await userStorage.readValue("last_counts") || "{}"); - LogService.info("Appservice", `Updating crypto state for ${userId}`); - await intent.underlyingClient.crypto.updateSyncData(info.toDevice, info.counts, info.unusedFallbacks, deviceLists.changed, deviceLists.removed); - } + LogService.info("Appservice", `Updating crypto state for ${userId}`); + await intent.underlyingClient.crypto.updateSyncData(info.toDevice, info.counts, info.unusedFallbacks, deviceLists.changed, deviceLists.removed); } + } - for (let event of req.body["events"]) { - LogService.info("Appservice", `Processing event of type ${event["type"]}`); - event = await this.processEvent(event); - if (event['type'] === 'm.room.encrypted') { - this.emit("room.encrypted_event", event["room_id"], event); - if (this.cryptoStorage) { + for (let event of body.events as any[]) { + LogService.info("Appservice", `Processing event of type ${event["type"]}`); + event = await this.processEvent(event); + if (event['type'] === 'm.room.encrypted') { + this.emit("room.encrypted_event", event["room_id"], event); + if (this.cryptoStorage) { + try { + const encrypted = new EncryptedRoomEvent(event); + const roomId = event['room_id']; try { - const encrypted = new EncryptedRoomEvent(event); - const roomId = event['room_id']; + event = (await this.botClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; + event = await this.processEvent(event); + this.emit("room.decrypted_event", roomId, event); + + // For logging purposes: show that the event was decrypted + LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); + } catch (e1) { + LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${event['event_id']} - trying other intents`); + + let tryUserId: string; try { - event = (await this.botClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; + // TODO: This could be more efficient + const userIdsInRoom = await this.botClient.getJoinedRoomMembers(roomId); + tryUserId = userIdsInRoom.find(u => this.isNamespacedUser(u)); + } catch (e) { + LogService.error("Appservice", "Failed to get members of room - cannot decrypt message"); + } + + if (tryUserId) { + const intent = this.getIntentForUserId(tryUserId); + + event = (await intent.underlyingClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; event = await this.processEvent(event); this.emit("room.decrypted_event", roomId, event); // For logging purposes: show that the event was decrypted LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); - } catch (e1) { - LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${event['event_id']} - trying other intents`); - - let tryUserId: string; - try { - // TODO: This could be more efficient - const userIdsInRoom = await this.botClient.getJoinedRoomMembers(roomId); - tryUserId = userIdsInRoom.find(u => this.isNamespacedUser(u)); - } catch (e) { - LogService.error("Appservice", "Failed to get members of room - cannot decrypt message"); - } - - if (tryUserId) { - const intent = this.getIntentForUserId(tryUserId); - - event = (await intent.underlyingClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; - event = await this.processEvent(event); - this.emit("room.decrypted_event", roomId, event); - - // For logging purposes: show that the event was decrypted - LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); - } else { - // noinspection ExceptionCaughtLocallyJS - throw e1; - } + } else { + // noinspection ExceptionCaughtLocallyJS + throw e1; } - } catch (e) { - LogService.error("Appservice", `Decryption error on ${event['room_id']} ${event['event_id']}`, e); - this.emit("room.failed_decryption", event['room_id'], event, e); } + } catch (e) { + LogService.error("Appservice", `Decryption error on ${event['room_id']} ${event['event_id']}`, e); + this.emit("room.failed_decryption", event['room_id'], event, e); } } - this.emit("room.event", event["room_id"], event); - if (event['type'] === 'm.room.message') { - this.emit("room.message", event["room_id"], event); - } - if (event['type'] === 'm.room.member' && this.isNamespacedUser(event['state_key'])) { - await this.processMembershipEvent(event); - } - if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') { - this.emit("room.archived", event['room_id'], event); - } - if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content'] && event['content']['predecessor']) { - this.emit("room.upgraded", event['room_id'], event); - } } + this.emit("room.event", event["room_id"], event); + if (event['type'] === 'm.room.message') { + this.emit("room.message", event["room_id"], event); + } + if (event['type'] === 'm.room.member' && this.isNamespacedUser(event['state_key'])) { + await this.processMembershipEvent(event); + } + if (event['type'] === 'm.room.tombstone' && event['state_key'] === '') { + this.emit("room.archived", event['room_id'], event); + } + if (event['type'] === 'm.room.create' && event['state_key'] === '' && event['content'] && event['content']['predecessor']) { + this.emit("room.upgraded", event['room_id'], event); + } + } + } - resolve(); - }); + private async onTransaction(req: express.Request, res: express.Response): Promise { + if (!this.isAuthed(req)) { + res.status(401).json({ errcode: "AUTH_FAILED", error: "Authentication failed" }); + return; + } + + if (typeof (req.body) !== "object") { + res.status(400).json({ errcode: "BAD_REQUEST", error: "Expected JSON" }); + return; + } + + if (!req.body["events"] || !Array.isArray(req.body["events"])) { + res.status(400).json({ errcode: "BAD_REQUEST", error: "Invalid JSON: expected events" }); + return; + } + + const txnId = req.params["txnId"]; + + try { + if (await this.storage.isTransactionCompleted(txnId)) { + res.status(200).json({}); + } + } catch (e) { + LogService.error("Appservice", e); + res.status(500).json({}); + } + + if (this.pendingTransactions.has(txnId)) { + // The homeserver has retried a transaction while we're still handling it. + try { + await this.pendingTransactions.get(txnId); + res.status(200).json({}); + } catch (e) { + LogService.error("Appservice", e); + res.status(500).json({}); + } + return; + } + + LogService.info("Appservice", `Processing transaction ${txnId}`); + const txnHandler = this.handleTransaction(req.body); + this.pendingTransactions.set(txnId, txnHandler); try { - await this.pendingTransactions[txnId]; + await txnHandler; await Promise.resolve(this.storage.setTransactionCompleted(txnId)); res.status(200).json({}); } catch (e) { LogService.error("Appservice", e); res.status(500).json({}); + } finally { + this.pendingTransactions.delete(txnId); } } From fbec61f6bd6c8df76cef195f9620cbf02084c14f Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 29 Jun 2023 09:50:27 -0400 Subject: [PATCH 029/123] Add scripts to aid PRs & releases (#18) --- package.json | 3 +++ scripts/prepare-patch-branch | 40 +++++++++++++++++++++++++++++ scripts/tag-release | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100755 scripts/prepare-patch-branch create mode 100755 scripts/tag-release diff --git a/package.json b/package.json index 642c61d5..80764c15 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "url": "https://github.com/vector-im/matrix-bot-sdk/issues" }, "homepage": "https://github.com/vector-im/matrix-bot-sdk#readme", + "publishConfig": { + "access": "public" + }, "scripts": { "prepublishOnly": "yarn build", "docs": "jsdoc -c jsdoc.json -P package.json -u docs/tutorials", diff --git a/scripts/prepare-patch-branch b/scripts/prepare-patch-branch new file mode 100755 index 00000000..07e9e675 --- /dev/null +++ b/scripts/prepare-patch-branch @@ -0,0 +1,40 @@ +#!/bin/bash +set -e + +if [[ $# -lt 1 ]]; then + echo 'Please provide a title for your patch branch' >&2 + exit 1 +fi +PATCH_TITLE=$1 + +for REMOTE in $(git remote); do + URL=$(git remote get-url $REMOTE) + if [[ $URL =~ "turt2live" ]]; then + UPSTREAM_REPO=$REMOTE + elif [[ $URL =~ "vector-im" ]]; then + FORK_REPO=$REMOTE + fi +done + +function echoAndDo { + echo "$*" + $* +} + +if [[ -z $UPSTREAM_REPO ]]; then + echo -n 'Adding remote for upstream repo: ' + UPSTREAM_REPO=turt2live + echoAndDo git remote add $UPSTREAM_REPO git@github.com:turt2live/matrix-bot-sdk.git +fi + +if [[ -z $FORK_REPO ]]; then + echo -n 'Adding remote for fork repo: ' + FORK_REPO=vector-im + echoAndDo git remote add $FORK_REPO git@github.com:vector-im/matrix-bot-sdk.git +fi + +git fetch $UPSTREAM_REPO +git fetch $FORK_REPO +git checkout -b $PATCH_TITLE $(git merge-base $UPSTREAM_REPO/main $FORK_REPO/element-main) + +echo "Branch '$PATCH_TITLE' is now ready. Push changes to this branch when preparing a PR, and aim to merge it to both upstream and the fork." diff --git a/scripts/tag-release b/scripts/tag-release new file mode 100755 index 00000000..c9161c63 --- /dev/null +++ b/scripts/tag-release @@ -0,0 +1,49 @@ +#!/bin/bash +set -e + +if [[ -n $(git status --porcelain) ]]; then + echo 'Working dir is dirty, aborting' >&2 + exit 1 +fi + +git fetch --all +git checkout element-release +git reset --hard element-main + +PREID=element + +# The latest upstream release tag reachable from the current commit +PREV_UPST_TAG=$(git log --decorate=short --decorate-refs=refs/tags/ --simplify-by-decoration --oneline | awk '/ \(tag: / && !/beta|element/ {sub(/)$/, "", $3); print $3; exit}') + +# The commit hash of the retrieved tag (not of the tag itself) +PREV_UPST_TAG_HASH=$(git rev-parse ${PREV_UPST_TAG}~0) + +# The immediate child commit of the release commit, +# to consider the 'Revert version back to "develop"' commits +PREV_UPST_NXT_HASH=$(git rev-list ${PREV_UPST_TAG}..main | tail -n 1) + +# Check if the current branch is a direct merge of the previous upstream release +for MERGE_PARENT in $(git show -s | awk '/^Merge: / {print $2; print $3; exit}'); do + if [[ $PREV_UPST_TAG_HASH =~ ^$MERGE_PARENT || $PREV_UPST_NXT_HASH =~ ^$MERGE_PARENT ]]; then + RELEASE_MERGE=1 + break + fi +done + +if [[ $RELEASE_MERGE -eq 1 ]]; then + THIS_TAG="${PREV_UPST_TAG}-${PREID}" + THIS_VER=${THIS_TAG#v} +else + THIS_VER=$(npx semver --preid ${PREID} -i prerelease ${PREV_UPST_TAG#v}) + while [[ -n $(git tag -l "v${THIS_VER}") ]]; do + THIS_VER=$(npx semver --preid ${PREID} -i prerelease $THIS_VER) + done + THIS_TAG="v${THIS_VER}" +fi + +sed -i 's/\("version": "\).*\("\)/\1'$THIS_VER'\2/' package.json +git add package.json +git commit -m $THIS_TAG +git tag -sm $THIS_TAG{,} + +echo "Tag '$THIS_TAG' is now ready and may be pushed" From 813cc8026b74fa0c25202b8b748c5cd3f6c89d63 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 29 Jun 2023 12:44:38 -0400 Subject: [PATCH 030/123] Correct the output folder for the Docs workflow (#21) --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 103510a3..b4b29a62 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,4 +17,4 @@ jobs: uses: JamesIves/github-pages-deploy-action@v4 with: branch: gh-pages - folder: .jsdoc/matrix-bot-sdk/develop + folder: .jsdoc/@vector-im/matrix-bot-sdk/develop From f985cd4aa46c0cfe3d0b003460db153722e29e9a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 29 Jun 2023 15:16:41 -0400 Subject: [PATCH 031/123] Use remote default branches in helper scripts --- scripts/fetch-remotes | 33 +++++++++++++++++++++++++++++++++ scripts/prepare-patch-branch | 30 ++---------------------------- scripts/tag-release | 7 ++++--- 3 files changed, 39 insertions(+), 31 deletions(-) create mode 100755 scripts/fetch-remotes diff --git a/scripts/fetch-remotes b/scripts/fetch-remotes new file mode 100755 index 00000000..734cf0a0 --- /dev/null +++ b/scripts/fetch-remotes @@ -0,0 +1,33 @@ +#!/bin/bash +set -e + +for REMOTE in $(git remote); do + URL=$(git remote get-url $REMOTE) + if [[ $URL =~ "turt2live" ]]; then + UPSTREAM_REPO=$REMOTE + elif [[ $URL =~ "vector-im" ]]; then + FORK_REPO=$REMOTE + fi +done + +function echoAndDo { + echo "$*" + $* +} + +if [[ -z $UPSTREAM_REPO ]]; then + echo -n 'Adding remote for upstream repo: ' + UPSTREAM_REPO=turt2live + echoAndDo git remote add $UPSTREAM_REPO git@github.com:turt2live/matrix-bot-sdk.git +fi + +if [[ -z $FORK_REPO ]]; then + echo -n 'Adding remote for fork repo: ' + FORK_REPO=vector-im + echoAndDo git remote add $FORK_REPO git@github.com:vector-im/matrix-bot-sdk.git +fi + +for REPO in $UPSTREAM_REPO $FORK_REPO; do + git fetch $REPO >/dev/null + git remote set-head $REPO -a >/dev/null +done diff --git a/scripts/prepare-patch-branch b/scripts/prepare-patch-branch index 07e9e675..fd360c1e 100755 --- a/scripts/prepare-patch-branch +++ b/scripts/prepare-patch-branch @@ -7,34 +7,8 @@ if [[ $# -lt 1 ]]; then fi PATCH_TITLE=$1 -for REMOTE in $(git remote); do - URL=$(git remote get-url $REMOTE) - if [[ $URL =~ "turt2live" ]]; then - UPSTREAM_REPO=$REMOTE - elif [[ $URL =~ "vector-im" ]]; then - FORK_REPO=$REMOTE - fi -done +. $(dirname $0)/fetch-remotes -function echoAndDo { - echo "$*" - $* -} - -if [[ -z $UPSTREAM_REPO ]]; then - echo -n 'Adding remote for upstream repo: ' - UPSTREAM_REPO=turt2live - echoAndDo git remote add $UPSTREAM_REPO git@github.com:turt2live/matrix-bot-sdk.git -fi - -if [[ -z $FORK_REPO ]]; then - echo -n 'Adding remote for fork repo: ' - FORK_REPO=vector-im - echoAndDo git remote add $FORK_REPO git@github.com:vector-im/matrix-bot-sdk.git -fi - -git fetch $UPSTREAM_REPO -git fetch $FORK_REPO -git checkout -b $PATCH_TITLE $(git merge-base $UPSTREAM_REPO/main $FORK_REPO/element-main) +git checkout -b $PATCH_TITLE $(git merge-base $UPSTREAM_REPO $FORK_REPO) echo "Branch '$PATCH_TITLE' is now ready. Push changes to this branch when preparing a PR, and aim to merge it to both upstream and the fork." diff --git a/scripts/tag-release b/scripts/tag-release index c9161c63..03741ab9 100755 --- a/scripts/tag-release +++ b/scripts/tag-release @@ -6,9 +6,10 @@ if [[ -n $(git status --porcelain) ]]; then exit 1 fi -git fetch --all +. $(dirname $0)/fetch-remotes + git checkout element-release -git reset --hard element-main +git reset --hard $FORK_REPO PREID=element @@ -20,7 +21,7 @@ PREV_UPST_TAG_HASH=$(git rev-parse ${PREV_UPST_TAG}~0) # The immediate child commit of the release commit, # to consider the 'Revert version back to "develop"' commits -PREV_UPST_NXT_HASH=$(git rev-list ${PREV_UPST_TAG}..main | tail -n 1) +PREV_UPST_NXT_HASH=$(git rev-list ${PREV_UPST_TAG}..${UPSTREAM_REPO} | tail -n 1) # Check if the current branch is a direct merge of the previous upstream release for MERGE_PARENT in $(git show -s | awk '/^Merge: / {print $2; print $3; exit}'); do From a0c209ee78a43d5f783cf2a623ee9cb8e432a21c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 3 Jul 2023 09:47:40 +0100 Subject: [PATCH 032/123] Fix races --- src/appservice/Appservice.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index bfddc20d..d9dd8c0c 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -655,10 +655,14 @@ export class Appservice extends EventEmitter { return providedToken === this.registration.hs_token; } - private async handleTransaction(body: Record) { + private async handleTransaction(txnId: string, body: Record) { // Process all the crypto stuff first to ensure that future transactions (if not this one) // will decrypt successfully. We start with EDUs because we need structures to put counts // and such into in a later stage, and EDUs are independent of crypto. + if (await this.storage.isTransactionCompleted(txnId)) { + // Duplicate. + return; + } const byUserId: { [userId: string]: { @@ -868,16 +872,7 @@ export class Appservice extends EventEmitter { return; } - const txnId = req.params["txnId"]; - - try { - if (await this.storage.isTransactionCompleted(txnId)) { - res.status(200).json({}); - } - } catch (e) { - LogService.error("Appservice", e); - res.status(500).json({}); - } + const { txnId } = req.params; if (this.pendingTransactions.has(txnId)) { // The homeserver has retried a transaction while we're still handling it. @@ -892,12 +887,18 @@ export class Appservice extends EventEmitter { } LogService.info("Appservice", `Processing transaction ${txnId}`); - const txnHandler = this.handleTransaction(req.body); + const txnHandler = this.handleTransaction(txnId, req.body); this.pendingTransactions.set(txnId, txnHandler); try { await txnHandler; - await Promise.resolve(this.storage.setTransactionCompleted(txnId)); + try { + await this.storage.setTransactionCompleted(txnId); + } catch (ex) { + // Not fatal for the transaction since we *did* process it, but we should + // warn loudly. + LogService.warn("Appservice", "Failed to store completed transaction", ex); + } res.status(200).json({}); } catch (e) { LogService.error("Appservice", e); From 8c7c2ae9bf5daa7055b6166df2842b52b52fbc48 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 13 Jul 2023 01:04:03 -0400 Subject: [PATCH 033/123] Remove Sled crypto store, use SQLite by default on account of it being removed from the crypto-sdk --- src/storage/RustSdkCryptoStorageProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index fc4be490..7f2432b9 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -26,7 +26,7 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { */ public constructor( public readonly storagePath: string, - public readonly storageType: RustSdkCryptoStoreType = RustSdkCryptoStoreType.Sled, + public readonly storageType: RustSdkCryptoStoreType = RustSdkCryptoStoreType.Sqlite, ) { this.storagePath = path.resolve(this.storagePath); mkdirp.sync(storagePath); @@ -69,7 +69,7 @@ export class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorage * @param baseStoragePath The *directory* to persist database details to. * @param storageType The storage type to use. Must be supported by the rust-sdk. */ - public constructor(private baseStoragePath: string, storageType: RustSdkCryptoStoreType = RustSdkCryptoStoreType.Sled) { + public constructor(private baseStoragePath: string, storageType: RustSdkCryptoStoreType = RustSdkCryptoStoreType.Sqlite) { super(path.join(baseStoragePath, "_default"), storageType); } From 8e306a854905962a411323be3cb09777c31a6321 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 17 Jul 2023 09:21:10 -0400 Subject: [PATCH 034/123] Remove Sled crypto store from examples & tests --- examples/bot.ts | 2 +- examples/encryption_appservice.ts | 2 +- examples/encryption_bot.ts | 2 +- test/MatrixClientTest.ts | 7 +++---- test/TestUtils.ts | 2 +- test/appservice/IntentTest.ts | 2 +- 6 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/bot.ts b/examples/bot.ts index a1ac99c1..b958df9b 100644 --- a/examples/bot.ts +++ b/examples/bot.ts @@ -27,7 +27,7 @@ const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN'; const storage = new SimpleFsStorageProvider("./examples/storage/bot.json"); -const crypto = new RustSdkCryptoStorageProvider("./examples/storage/bot_sled", StoreType.Sled); +const crypto = new RustSdkCryptoStorageProvider("./examples/storage/bot_sqlite", StoreType.Sqlite); const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); AutojoinRoomsMixin.setupOnClient(client); diff --git a/examples/encryption_appservice.ts b/examples/encryption_appservice.ts index 90c8f3f6..4621897f 100644 --- a/examples/encryption_appservice.ts +++ b/examples/encryption_appservice.ts @@ -31,7 +31,7 @@ try { const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const storage = new SimpleFsStorageProvider("./examples/storage/encryption_appservice.json"); -const crypto = new RustSdkAppserviceCryptoStorageProvider("./examples/storage/encryption_appservice_sled", StoreType.Sled); +const crypto = new RustSdkAppserviceCryptoStorageProvider("./examples/storage/encryption_appservice_sqlite", StoreType.Sqlite); const worksImage = fs.readFileSync("./examples/static/it-works.png"); const registration: IAppserviceRegistration = { diff --git a/examples/encryption_bot.ts b/examples/encryption_bot.ts index 011b1565..4dda4c56 100644 --- a/examples/encryption_bot.ts +++ b/examples/encryption_bot.ts @@ -29,7 +29,7 @@ const dmTarget = creds?.['dmTarget'] ?? "@admin:localhost"; const homeserverUrl = creds?.['homeserverUrl'] ?? "http://localhost:8008"; const accessToken = creds?.['accessToken'] ?? 'YOUR_TOKEN'; const storage = new SimpleFsStorageProvider("./examples/storage/encryption_bot.json"); -const crypto = new RustSdkCryptoStorageProvider("./examples/storage/encryption_bot_sled", StoreType.Sled); +const crypto = new RustSdkCryptoStorageProvider("./examples/storage/encryption_bot_sqlite", StoreType.Sqlite); const worksImage = fs.readFileSync("./examples/static/it-works.png"); const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 6abdf93e..21bee16e 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1,6 +1,5 @@ import * as tmp from "tmp"; import * as simple from "simple-mock"; -import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { EventKind, @@ -48,13 +47,13 @@ describe('MatrixClient', () => { expect(client.accessToken).toEqual(accessToken); }); - it('should create a crypto client when requested', () => { + it('should create a crypto client when requested', () => testCryptoStores(async (cryptoStoreType) => { const homeserverUrl = "https://example.org"; const accessToken = "example_token"; - const client = new MatrixClient(homeserverUrl, accessToken, null, new RustSdkCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled)); + const client = new MatrixClient(homeserverUrl, accessToken, null, new RustSdkCryptoStorageProvider(tmp.dirSync().name, cryptoStoreType)); expect(client.crypto).toBeDefined(); - }); + })); it('should NOT create a crypto client when requested', () => { const homeserverUrl = "https://example.org"; diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 7d6f9f8d..8666c83d 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -47,7 +47,7 @@ export function createTestClient( return { http, hsUrl, accessToken, client }; } -const CRYPTO_STORE_TYPES = [StoreType.Sled, StoreType.Sqlite]; +const CRYPTO_STORE_TYPES = [StoreType.Sqlite]; export async function testCryptoStores(fn: (StoreType) => Promise): Promise { for (const st of CRYPTO_STORE_TYPES) { diff --git a/test/appservice/IntentTest.ts b/test/appservice/IntentTest.ts index 07713238..fadb0e78 100644 --- a/test/appservice/IntentTest.ts +++ b/test/appservice/IntentTest.ts @@ -1137,7 +1137,7 @@ describe('Intent', () => { beforeEach(() => { storage = new MemoryStorageProvider(); - cryptoStorage = new RustSdkAppserviceCryptoStorageProvider(tmp.dirSync().name, StoreType.Sled); + cryptoStorage = new RustSdkAppserviceCryptoStorageProvider(tmp.dirSync().name, StoreType.Sqlite); options = { homeserverUrl: hsUrl, storage: storage, From 6eb9572705453cb814db5d04831e982b7887ab25 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 18 Jul 2023 01:49:54 -0400 Subject: [PATCH 035/123] Fix operator precedence & typings --- test/TestUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 8666c83d..6b4b851f 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -40,14 +40,14 @@ export function createTestClient( const http = new HttpBackend(); const hsUrl = "https://localhost"; const accessToken = "s3cret"; - const client = new MatrixClient(hsUrl, accessToken, storage, cryptoStoreType !== undefined ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, cryptoStoreType) : null); + const client = new MatrixClient(hsUrl, accessToken, storage, (cryptoStoreType !== undefined) ? new RustSdkCryptoStorageProvider(tmp.dirSync().name, cryptoStoreType) : null); (client).userId = userId; // private member access setRequestFn(http.requestFn); return { http, hsUrl, accessToken, client }; } -const CRYPTO_STORE_TYPES = [StoreType.Sqlite]; +const CRYPTO_STORE_TYPES: StoreType[] = [StoreType.Sqlite]; export async function testCryptoStores(fn: (StoreType) => Promise): Promise { for (const st of CRYPTO_STORE_TYPES) { From 73a38c1219361f4cca7fadaea119fed000178bc5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 1 Aug 2023 16:06:03 -0400 Subject: [PATCH 036/123] Update rust-sdk bindings --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 24e93dbf..678fd8cf 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.6", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.9", "@types/express": "^4.17.13", "another-json": "^0.2.0", "async-lock": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index f96ba4ad..56f4d508 100644 --- a/yarn.lock +++ b/yarn.lock @@ -584,10 +584,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.6": - version "0.1.0-beta.6" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.6.tgz#0ecae51103ee3c107af0d6d0738f33eb7cc9857e" - integrity sha512-JXyrHuCVMydUGgSetWsfqbbvHj3aUMOX5TUghlMtLFromyEu7wIsNgYt7PjJ+k3WdF4GVABRy4P6GNjaEMy2uA== +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.9": + version "0.1.0-beta.9" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.9.tgz#dc21f3b0f4b35b73befc64a257b8e519afbcbed0" + integrity sha512-ee2YlBoXPLgp1aav9MqREJKvWJfURn9Jcs46FyWT4NXEl37KQDNC8CWWnqgqsHkLfBxxSxfq9kMA/mWQZF7QJw== dependencies: https-proxy-agent "^5.0.1" node-downloader-helper "^2.1.5" From fef0d543b7dcbc81f855342e6e0dbbf7f582fd0c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 19 Jul 2023 09:02:34 -0400 Subject: [PATCH 037/123] Pull to-device event list out of returned tuple OlmMachine.receiveSyncChanges returns an array of [device messages, room key changes], so emit "to_device.decrypted" with that instead of the entire array. Fixes regression introduced by #287. --- src/e2ee/CryptoClient.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 74ddfb50..0f6d8f63 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -168,11 +168,13 @@ export class CryptoClient { leftDeviceLists.map(u => new UserId(u))); await this.engine.lock.acquire(SYNC_LOCK_NAME, async () => { - const syncResp = await this.engine.machine.receiveSyncChanges(deviceMessages, deviceLists, otkCounts, unusedFallbackKeyAlgs); - const decryptedToDeviceMessages = JSON.parse(syncResp); - if (Array.isArray(decryptedToDeviceMessages)) { - for (const msg of decryptedToDeviceMessages) { - this.client.emit("to_device.decrypted", msg); + const syncResp = JSON.parse(await this.engine.machine.receiveSyncChanges(deviceMessages, deviceLists, otkCounts, unusedFallbackKeyAlgs)); + if (Array.isArray(syncResp)) { + const decryptedToDeviceMessages = syncResp[0]; + if (Array.isArray(decryptedToDeviceMessages)) { + for (const msg of decryptedToDeviceMessages as IToDeviceMessage[]) { + this.client.emit("to_device.decrypted", msg); + } } } From 974771b4ed9272c841638f93d032b23d2689da1b Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 19 Jul 2023 10:23:08 -0400 Subject: [PATCH 038/123] Log when OlmMachine returns unexpected value Also condense the validity checks on the returned value --- src/e2ee/CryptoClient.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 0f6d8f63..a70aae4d 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -169,13 +169,12 @@ export class CryptoClient { await this.engine.lock.acquire(SYNC_LOCK_NAME, async () => { const syncResp = JSON.parse(await this.engine.machine.receiveSyncChanges(deviceMessages, deviceLists, otkCounts, unusedFallbackKeyAlgs)); - if (Array.isArray(syncResp)) { - const decryptedToDeviceMessages = syncResp[0]; - if (Array.isArray(decryptedToDeviceMessages)) { - for (const msg of decryptedToDeviceMessages as IToDeviceMessage[]) { - this.client.emit("to_device.decrypted", msg); - } + if (Array.isArray(syncResp) && syncResp.length === 2 && Array.isArray(syncResp[0])) { + for (const msg of syncResp[0] as IToDeviceMessage[]) { + this.client.emit("to_device.decrypted", msg); } + } else { + LogService.error("CryptoClient", "OlmMachine.receiveSyncChanges did not return an expected value of [to-device events, room key changes]"); } await this.engine.run(); From d87f7d8687e36148a735a57d5d55ef2cac5ee0f7 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 2 Aug 2023 17:06:59 -0400 Subject: [PATCH 039/123] Add crypto test for to-device messages --- test/MatrixClientTest.ts | 3 +- test/encryption/CryptoClientTest.ts | 67 ++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 21bee16e..e1764e09 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1400,8 +1400,7 @@ describe('MatrixClient', () => { describe('processSync', () => { interface ProcessSyncClient { userId: string; - - processSync(raw: any): Promise; + processSync(raw: any): MatrixClient["processSync"]; } it('should process non-room account data', async () => { diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index bab445f6..f472d57d 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -1,7 +1,7 @@ import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; -import { EncryptedFile, MatrixClient, MembershipEvent, OTKAlgorithm, RoomEncryptionAlgorithm } from "../../src"; +import { EncryptedFile, EncryptionAlgorithm, IOlmEncrypted, IToDeviceMessage, MatrixClient, MembershipEvent, OTKAlgorithm, RoomEncryptionAlgorithm } from "../../src"; import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; export function bindNullEngine(http: HttpBackend) { @@ -85,6 +85,71 @@ describe('CryptoClient', () => { })); }); + describe('processSync', () => { + /** + * Helper class to be able to call {@link MatrixClient#processSync}, which is otherwise private. + */ + interface ProcessSyncClient { + processSync: MatrixClient["processSync"]; + } + + it('should process encrypted to-device messages', () => testCryptoStores(async (cryptoStoreType) => { + const userId = "@alice:example.org"; + const { client, http } = createTestClient(null, userId, cryptoStoreType); + const psClient = (client); + + await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); + + const toDeviceMessage: IToDeviceMessage = { + type: "m.room.encrypted", + sender: userId, + content: { + algorithm: EncryptionAlgorithm.OlmV1Curve25519AesSha2, + sender_key: "sender_curve25519_key", + ciphertext: { + ["device_curve25519_key"]: { + type: 0, + body: "encrypted_payload_base_64", + }, + }, + }, + }; + const sync = { + to_device: { events: [toDeviceMessage] }, + device_unused_fallback_key_types: [OTKAlgorithm.Signed], + device_one_time_keys_count: { + [OTKAlgorithm.Signed]: 12, + [OTKAlgorithm.Unsigned]: 14, + }, + device_lists: { + changed: ["@bob:example.org"], + left: ["@charlie:example.org"], + }, + }; + + const toDeviceSpy = simple.stub().callFn((ev) => { + for (const prop in toDeviceMessage) { + expect(ev).toHaveProperty(prop); + } + }); + client.on("to_device.decrypted", toDeviceSpy); + + bindNullEngine(http); + await Promise.all([ + client.crypto.prepare([]), + http.flushAllExpected(), + ]); + + bindNullEngine(http); + await Promise.all([ + psClient.processSync(sync), + http.flushAllExpected(), + ]); + + expect(toDeviceSpy.callCount).toBe(1); + })); + }); + describe('isRoomEncrypted', () => { it('should fail when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; From ceae46055a6f9c19a08f4a2b0914a58f22bd1dfb Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 7 Jul 2023 02:18:11 -0400 Subject: [PATCH 040/123] Add support for key backups --- src/MatrixClient.ts | 75 ++++++++++++++++++++++++++++++++++++++++ src/e2ee/CryptoClient.ts | 18 ++++++++++ src/e2ee/RustEngine.ts | 30 +++++++++++++++- src/models/KeyBackup.ts | 36 +++++++++++++++++++ 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/models/KeyBackup.ts diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 47dc6b72..9a71e7d6 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -44,6 +44,8 @@ import { DMs } from "./DMs"; import { ServerVersions } from "./models/ServerVersions"; import { RoomCreateOptions } from "./models/CreateRoom"; import { PresenceState } from './models/events/PresenceEvent'; +import { IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUpdate, IKeyBackupVersion, KeyBackupVersion } from "./models/KeyBackup"; +import { MatrixError } from "./models/MatrixError"; const SYNC_BACKOFF_MIN_MS = 5000; const SYNC_BACKOFF_MAX_MS = 15000; @@ -1962,6 +1964,79 @@ export class MatrixClient extends EventEmitter { }); } + /** + * Get information about the latest room key backup version. + * @returns {Promise} Resolves to the retrieved key backup info, + * or null if there is no existing backup. + */ + public async getKeyBackupVersion(): Promise { + try { + return await this.doRequest("GET", "/_matrix/client/v3/room_keys/version"); + } catch (e) { + if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") { + return null; + } else { + throw e; + } + } + } + + /** + * Create a new room key backup. + * @param {IKeyBackupInfo} info The properties of the key backup to create. + * @returns {Promise} Resolves to the version id of the new backup. + */ + public async createKeyBackupVersion(info: IKeyBackupInfo): Promise { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + + const data = { + ...info, + signatures: this.crypto.sign(info), + }; + return await this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data); + } + + /** + * Update an existing room key backup. + * @param {KeyBackupVersion} version The key backup version to update. + * @param {IKeyBackupInfoUpdate} info The properties of the key backup to be applied. + * @returns {Promise} Resolves when complete. + */ + public async updateKeyBackupVersion(version: KeyBackupVersion, info: IKeyBackupInfoUpdate): Promise { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + + const data = { + ...info, + signatures: this.crypto.sign(info), + }; + await this.doRequest("PUT", `/_matrix/client/v3/room_keys/version/${version}`, null, data); + } + + /** + * Enable backing up of room keys. + * @param {IKeyBackupInfoRetrieved} info The configuration for key backup behaviour, + * as returned by {@link getKeyBackupVersion}. + * @returns {Promise} Resolves when complete. + */ + public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } + + this.crypto.enableKeyBackup(info); + } + + /** + * Disable backing up of room keys. + */ + public disableKeyBackup(): void { + this.crypto?.disableKeyBackup(); + } + /** * Get relations for a given event. * @param {string} roomId The room ID to for the given event. diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index a70aae4d..a5113412 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -26,6 +26,7 @@ import { EncryptedFile } from "../models/events/MessageEvent"; import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStorageProvider"; import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; +import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -285,4 +286,21 @@ export class CryptoClient { const decrypted = Attachment.decrypt(encrypted); return Buffer.from(decrypted); } + + /** + * Enable backing up of room keys. + * @param {IKeyBackupInfoRetrieved} info The configuration for key backup behaviour, + * as returned by {@link MatrixClient#getKeyBackupVersion}. + * @returns {Promise} Resolves when complete. + */ + public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { + await this.engine.enableKeyBackup(info); + } + + /** + * Disable backing up of room keys. + */ + public disableKeyBackup(): void { + this.engine.disableKeyBackup(); + } } diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index c9eae9c8..d1b3bd83 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -10,6 +10,7 @@ import { KeysUploadRequest, KeysQueryRequest, ToDeviceRequest, + KeysBackupRequest, } from "@matrix-org/matrix-sdk-crypto-nodejs"; import * as AsyncLock from "async-lock"; @@ -17,6 +18,7 @@ import { MatrixClient } from "../MatrixClient"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; import { EncryptionAlgorithm } from "../models/Crypto"; import { EncryptionEvent } from "../models/events/EncryptionEvent"; +import { ICurve25519AuthData, IKeyBackupInfoRetrieved, KeyBackupVersion } from "../models/KeyBackup"; /** * @internal @@ -29,6 +31,9 @@ export const SYNC_LOCK_NAME = "sync"; export class RustEngine { public readonly lock = new AsyncLock(); + private keyBackupVersion: KeyBackupVersion|undefined; + // TODO: keyBackupPromise that can be awaited by others + public constructor(public readonly machine: OlmMachine, private client: MatrixClient) { } @@ -59,7 +64,8 @@ export class RustEngine { case RequestType.SignatureUpload: throw new Error("Bindings error: Backup feature not possible"); case RequestType.KeysBackup: - throw new Error("Bindings error: Backup feature not possible"); + await this.processKeysBackupRequest(request); + break; default: throw new Error("Bindings error: Unrecognized request type: " + request.type); } @@ -128,6 +134,17 @@ export class RustEngine { }); } + public async enableKeyBackup(info: IKeyBackupInfoRetrieved) { + this.keyBackupVersion = info.version; + // TODO Error with message if the key backup uses an unsupported auth_data type + await this.machine.enableBackupV1((info.auth_data as ICurve25519AuthData).public_key, info.version); + } + + public async disableKeyBackup() { + this.keyBackupVersion = undefined; + await this.machine.disableBackup(); + } + private async processKeysClaimRequest(request: KeysClaimRequest) { const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body)); await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp)); @@ -154,4 +171,15 @@ export class RustEngine { const resp = await this.client.sendToDevices(type, messages); await this.machine.markRequestAsSent(id, RequestType.ToDevice, JSON.stringify(resp)); } + + private async processKeysBackupRequest(request: KeysBackupRequest) { + let resp: Awaited>; + try { + resp = await this.client.doRequest("PUT", "/_matrix/client/v3/room_keys/keys", { version: this.keyBackupVersion }, JSON.parse(request.body)); + } catch (e) { + this.client.emit("crypto.failed_backup", e); + return; + } + await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp)); + } } diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts new file mode 100644 index 00000000..a8c8eb61 --- /dev/null +++ b/src/models/KeyBackup.ts @@ -0,0 +1,36 @@ +import { Signatures } from "./Crypto"; + +/** + * The kinds of key backup encryption algorithms allowed by the spec. + * @category Models + */ +export enum KeyBackupEncryptionAlgorithm { + MegolmBackupV1Curve25519AesSha2 = "m.megolm_backup.v1.curve25519-aes-sha2", +} + +/** + * Information about a server-side key backup. + */ +export interface IKeyBackupInfo { + algorithm: string | KeyBackupEncryptionAlgorithm; + auth_data: object; +} + +export type KeyBackupVersion = string; + +export interface IKeyBackupVersion { + version: KeyBackupVersion; +} + +export interface IKeyBackupInfoRetrieved extends IKeyBackupInfo, IKeyBackupVersion { + count: number; + etag: string; +} + +export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial; + +export interface ICurve25519AuthDataUnsigned { + public_key: string; +} + +export type ICurve25519AuthData = ICurve25519AuthDataUnsigned & Signatures; From bfe6c372b61f1e3cfacb279988679ff6a335580d Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 19 Jul 2023 10:47:07 -0400 Subject: [PATCH 041/123] Back up keys when receiving/creating them --- src/e2ee/CryptoClient.ts | 8 ++++++++ src/e2ee/RustEngine.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index a5113412..1e592ac7 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -294,6 +294,7 @@ export class CryptoClient { * @returns {Promise} Resolves when complete. */ public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { + this.client.on("to_device.decrypted", this.onToDeviceMessage); await this.engine.enableKeyBackup(info); } @@ -302,5 +303,12 @@ export class CryptoClient { */ public disableKeyBackup(): void { this.engine.disableKeyBackup(); + this.client.removeListener("to_device.decrypted", this.onToDeviceMessage); } + + private readonly onToDeviceMessage = (msg: IToDeviceMessage): void => { + if (msg.type === "m.room_key") { + this.engine.machine.backupRoomKeys(); + } + }; } diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index d1b3bd83..d18371db 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -123,6 +123,9 @@ export class RustEngine { const keysClaim = await this.machine.getMissingSessions(members); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); + if (this.keyBackupVersion !== undefined) { + await this.machine.backupRoomKeys(); + } } }); From 64463f46b9d4139e3c7b3131489964db8d64597d Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 25 Jul 2023 03:20:24 -0400 Subject: [PATCH 042/123] Handle immediate outgoing backup requests --- src/e2ee/CryptoClient.ts | 3 ++- src/e2ee/RustEngine.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 1e592ac7..d9e410f7 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -296,6 +296,7 @@ export class CryptoClient { public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { this.client.on("to_device.decrypted", this.onToDeviceMessage); await this.engine.enableKeyBackup(info); + this.engine.backupRoomKeys(); } /** @@ -308,7 +309,7 @@ export class CryptoClient { private readonly onToDeviceMessage = (msg: IToDeviceMessage): void => { if (msg.type === "m.room_key") { - this.engine.machine.backupRoomKeys(); + this.engine.backupRoomKeys(); } }; } diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index d18371db..48734040 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -148,6 +148,13 @@ export class RustEngine { await this.machine.disableBackup(); } + public async backupRoomKeys() { + const request = await this.machine.backupRoomKeys(); + if (request) { + await this.processKeysBackupRequest(request); + } + } + private async processKeysClaimRequest(request: KeysClaimRequest) { const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body)); await this.machine.markRequestAsSent(request.id, request.type, JSON.stringify(resp)); From e735cb533a6f8a93c3246e23af4cc1cb58c8fed2 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 3 Aug 2023 16:47:10 -0400 Subject: [PATCH 043/123] Queue async key backup operations --- src/e2ee/RustEngine.ts | 58 +++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 48734040..802f5232 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -32,7 +32,8 @@ export class RustEngine { public readonly lock = new AsyncLock(); private keyBackupVersion: KeyBackupVersion|undefined; - // TODO: keyBackupPromise that can be awaited by others + private keyBackupWaiter = Promise.resolve(); + private isBackupEnabled = false; public constructor(public readonly machine: OlmMachine, private client: MatrixClient) { } @@ -123,9 +124,7 @@ export class RustEngine { const keysClaim = await this.machine.getMissingSessions(members); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); - if (this.keyBackupVersion !== undefined) { - await this.machine.backupRoomKeys(); - } + this.backupRoomKeysIfEnabled(); } }); @@ -137,23 +136,55 @@ export class RustEngine { }); } - public async enableKeyBackup(info: IKeyBackupInfoRetrieved) { - this.keyBackupVersion = info.version; - // TODO Error with message if the key backup uses an unsupported auth_data type - await this.machine.enableBackupV1((info.auth_data as ICurve25519AuthData).public_key, info.version); + public enableKeyBackup(info: IKeyBackupInfoRetrieved) { + this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { + if (this.isBackupEnabled) { + await this.actuallyDisableKeyBackup(); + } + // TODO Error with message if the key backup uses an unsupported auth_data type + await this.machine.enableBackupV1((info.auth_data as ICurve25519AuthData).public_key, info.version); + this.keyBackupVersion = info.version; + this.isBackupEnabled = true; + }); + return this.keyBackupWaiter; } - public async disableKeyBackup() { - this.keyBackupVersion = undefined; - await this.machine.disableBackup(); + public disableKeyBackup() { + this.keyBackupWaiter = this.keyBackupWaiter.then(this.actuallyDisableKeyBackup); + return this.keyBackupWaiter; } + private readonly actuallyDisableKeyBackup = async () => { + await this.machine.disableBackup(); + this.keyBackupVersion = undefined; + this.isBackupEnabled = false; + }; + public async backupRoomKeys() { + this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { + if (!this.isBackupEnabled) { + throw new Error("Key backup error: attempted to create a backup before having enabled backups"); + } + await this.actuallyBackupRoomKeys(); + }); + return this.keyBackupWaiter; + } + + private async backupRoomKeysIfEnabled() { + this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { + if (this.isBackupEnabled) { + await this.actuallyBackupRoomKeys(); + } + }); + return this.keyBackupWaiter; + } + + private readonly actuallyBackupRoomKeys = async () => { const request = await this.machine.backupRoomKeys(); if (request) { await this.processKeysBackupRequest(request); } - } + }; private async processKeysClaimRequest(request: KeysClaimRequest) { const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body)); @@ -185,6 +216,9 @@ export class RustEngine { private async processKeysBackupRequest(request: KeysBackupRequest) { let resp: Awaited>; try { + if (!this.keyBackupVersion) { + throw new Error("Key backup version missing"); + } resp = await this.client.doRequest("PUT", "/_matrix/client/v3/room_keys/keys", { version: this.keyBackupVersion }, JSON.parse(request.body)); } catch (e) { this.client.emit("crypto.failed_backup", e); From 744944090a2dd8c4eca71a47960e061224938677 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 4 Aug 2023 09:52:43 -0400 Subject: [PATCH 044/123] Fix/document awaits --- src/MatrixClient.ts | 16 ++++++++-------- src/e2ee/CryptoClient.ts | 4 ++-- src/e2ee/RustEngine.ts | 9 +++++---- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 9a71e7d6..68a1d91e 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1986,7 +1986,7 @@ export class MatrixClient extends EventEmitter { * @param {IKeyBackupInfo} info The properties of the key backup to create. * @returns {Promise} Resolves to the version id of the new backup. */ - public async createKeyBackupVersion(info: IKeyBackupInfo): Promise { + public createKeyBackupVersion(info: IKeyBackupInfo): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } @@ -1995,7 +1995,7 @@ export class MatrixClient extends EventEmitter { ...info, signatures: this.crypto.sign(info), }; - return await this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data); + return this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data); } /** @@ -2004,7 +2004,7 @@ export class MatrixClient extends EventEmitter { * @param {IKeyBackupInfoUpdate} info The properties of the key backup to be applied. * @returns {Promise} Resolves when complete. */ - public async updateKeyBackupVersion(version: KeyBackupVersion, info: IKeyBackupInfoUpdate): Promise { + public updateKeyBackupVersion(version: KeyBackupVersion, info: IKeyBackupInfoUpdate): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } @@ -2013,7 +2013,7 @@ export class MatrixClient extends EventEmitter { ...info, signatures: this.crypto.sign(info), }; - await this.doRequest("PUT", `/_matrix/client/v3/room_keys/version/${version}`, null, data); + return this.doRequest("PUT", `/_matrix/client/v3/room_keys/version/${version}`, null, data); } /** @@ -2022,19 +2022,19 @@ export class MatrixClient extends EventEmitter { * as returned by {@link getKeyBackupVersion}. * @returns {Promise} Resolves when complete. */ - public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { + public enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } - this.crypto.enableKeyBackup(info); + return this.crypto.enableKeyBackup(info); } /** * Disable backing up of room keys. */ - public disableKeyBackup(): void { - this.crypto?.disableKeyBackup(); + public disableKeyBackup(): Promise { + return this.crypto?.disableKeyBackup(); } /** diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index d9e410f7..6cd1bec0 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -302,8 +302,8 @@ export class CryptoClient { /** * Disable backing up of room keys. */ - public disableKeyBackup(): void { - this.engine.disableKeyBackup(); + public async disableKeyBackup(): Promise { + await this.engine.disableKeyBackup(); this.client.removeListener("to_device.decrypted", this.onToDeviceMessage); } diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 802f5232..69c9576e 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -124,6 +124,7 @@ export class RustEngine { const keysClaim = await this.machine.getMissingSessions(members); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); + // Back up keys asynchronously this.backupRoomKeysIfEnabled(); } }); @@ -136,7 +137,7 @@ export class RustEngine { }); } - public enableKeyBackup(info: IKeyBackupInfoRetrieved) { + public enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { if (this.isBackupEnabled) { await this.actuallyDisableKeyBackup(); @@ -149,7 +150,7 @@ export class RustEngine { return this.keyBackupWaiter; } - public disableKeyBackup() { + public disableKeyBackup(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(this.actuallyDisableKeyBackup); return this.keyBackupWaiter; } @@ -160,7 +161,7 @@ export class RustEngine { this.isBackupEnabled = false; }; - public async backupRoomKeys() { + public backupRoomKeys(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { if (!this.isBackupEnabled) { throw new Error("Key backup error: attempted to create a backup before having enabled backups"); @@ -170,7 +171,7 @@ export class RustEngine { return this.keyBackupWaiter; } - private async backupRoomKeysIfEnabled() { + private backupRoomKeysIfEnabled(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { if (this.isBackupEnabled) { await this.actuallyBackupRoomKeys(); From cc221fb678ee3f4c629c397c0fe8d338b8f3225a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 4 Aug 2023 10:18:28 -0400 Subject: [PATCH 045/123] Apply review recommendations --- src/e2ee/RustEngine.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 69c9576e..135085e6 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -125,7 +125,7 @@ export class RustEngine { if (keysClaim) { await this.processKeysClaimRequest(keysClaim); // Back up keys asynchronously - this.backupRoomKeysIfEnabled(); + void this.backupRoomKeysIfEnabled(); } }); @@ -140,6 +140,7 @@ export class RustEngine { public enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { if (this.isBackupEnabled) { + // Finish any pending backups before changing the backup version/pubkey await this.actuallyDisableKeyBackup(); } // TODO Error with message if the key backup uses an unsupported auth_data type @@ -151,15 +152,17 @@ export class RustEngine { } public disableKeyBackup(): Promise { - this.keyBackupWaiter = this.keyBackupWaiter.then(this.actuallyDisableKeyBackup); + this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { + await this.actuallyDisableKeyBackup(); + }); return this.keyBackupWaiter; } - private readonly actuallyDisableKeyBackup = async () => { + private async actuallyDisableKeyBackup(): Promise { await this.machine.disableBackup(); this.keyBackupVersion = undefined; this.isBackupEnabled = false; - }; + } public backupRoomKeys(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { @@ -180,12 +183,12 @@ export class RustEngine { return this.keyBackupWaiter; } - private readonly actuallyBackupRoomKeys = async () => { + private async actuallyBackupRoomKeys(): Promise { const request = await this.machine.backupRoomKeys(); if (request) { await this.processKeysBackupRequest(request); } - }; + } private async processKeysClaimRequest(request: KeysClaimRequest) { const resp = await this.client.doRequest("POST", "/_matrix/client/v3/keys/claim", null, JSON.parse(request.body)); From ae61b8fcf7cf0f69a93ceca12e004022808d6388 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 4 Aug 2023 17:21:43 -0400 Subject: [PATCH 046/123] Put auth_data signature in correct place Also add/tweak some utility types to help with this --- src/MatrixClient.ts | 14 +++++++++----- src/helpers/Types.ts | 5 +++++ src/models/Crypto.ts | 14 ++++++++++---- src/models/KeyBackup.ts | 29 +++++++++++++++++++---------- 4 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 src/helpers/Types.ts diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 68a1d91e..50bf7d66 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -44,7 +44,7 @@ import { DMs } from "./DMs"; import { ServerVersions } from "./models/ServerVersions"; import { RoomCreateOptions } from "./models/CreateRoom"; import { PresenceState } from './models/events/PresenceEvent'; -import { IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUpdate, IKeyBackupVersion, KeyBackupVersion } from "./models/KeyBackup"; +import { IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, IKeyBackupInfoUpdate, IKeyBackupVersion, KeyBackupVersion } from "./models/KeyBackup"; import { MatrixError } from "./models/MatrixError"; const SYNC_BACKOFF_MIN_MS = 5000; @@ -1983,17 +1983,21 @@ export class MatrixClient extends EventEmitter { /** * Create a new room key backup. - * @param {IKeyBackupInfo} info The properties of the key backup to create. + * @param {IKeyBackupInfoUnsigned} info The properties of the key backup to create, + * with its auth_data left unsigned. * @returns {Promise} Resolves to the version id of the new backup. */ - public createKeyBackupVersion(info: IKeyBackupInfo): Promise { + public async signAndCreateKeyBackupVersion(info: IKeyBackupInfoUnsigned): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } - const data = { + const data: IKeyBackupInfo = { ...info, - signatures: this.crypto.sign(info), + auth_data: { + ...info.auth_data, + signatures: await this.crypto.sign(info), + } }; return this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data); } diff --git a/src/helpers/Types.ts b/src/helpers/Types.ts new file mode 100644 index 00000000..14a717b5 --- /dev/null +++ b/src/helpers/Types.ts @@ -0,0 +1,5 @@ +export type Json = string | number | boolean | null | undefined | Json[] | { [key: string]: Json }; + +export interface IJsonType { + [key: string]: Json; +} diff --git a/src/models/Crypto.ts b/src/models/Crypto.ts index 9269a95f..af945641 100644 --- a/src/models/Crypto.ts +++ b/src/models/Crypto.ts @@ -23,13 +23,20 @@ export interface Signatures { }; } +/** + * Interface that can be extended by + * any object that needs a signature. + */ +export interface Signed { + signatures: Signatures; +} + /** * A signed_curve25519 one time key. * @category Models */ -export interface SignedCurve25519OTK { +export interface SignedCurve25519OTK extends Signed { key: string; - signatures: Signatures; fallback?: boolean; } @@ -89,12 +96,11 @@ export type DeviceKeyLabel, string>; - signatures: Signatures; unsigned?: { [k: string]: any; device_display_name?: string; diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index a8c8eb61..3b946257 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -1,4 +1,5 @@ -import { Signatures } from "./Crypto"; +import { IJsonType } from "../helpers/Types"; +import { Signed } from "./Crypto"; /** * The kinds of key backup encryption algorithms allowed by the spec. @@ -8,14 +9,28 @@ export enum KeyBackupEncryptionAlgorithm { MegolmBackupV1Curve25519AesSha2 = "m.megolm_backup.v1.curve25519-aes-sha2", } +export interface ICurve25519AuthDataUnsigned { + public_key: string; +} +export type ICurve25519AuthData = ICurve25519AuthDataUnsigned & Signed; + /** - * Information about a server-side key backup. + * Information about a server-side key backup, + * with its auth_data left unsigned. */ -export interface IKeyBackupInfo { +export interface IKeyBackupInfoUnsigned { algorithm: string | KeyBackupEncryptionAlgorithm; - auth_data: object; + auth_data: IJsonType | ICurve25519AuthDataUnsigned; } +/** + * Information about a server-side key backup, + * with its auth_data signed by the entity that created it. + */ +export type IKeyBackupInfo = IKeyBackupInfoUnsigned & { + auth_data: Signed & IKeyBackupInfoUnsigned["auth_data"]; +}; + export type KeyBackupVersion = string; export interface IKeyBackupVersion { @@ -28,9 +43,3 @@ export interface IKeyBackupInfoRetrieved extends IKeyBackupInfo, IKeyBackupVersi } export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial; - -export interface ICurve25519AuthDataUnsigned { - public_key: string; -} - -export type ICurve25519AuthData = ICurve25519AuthDataUnsigned & Signatures; From bcee62bab06562dae2f0d2dde8f332ffd6466054 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 4 Aug 2023 17:24:46 -0400 Subject: [PATCH 047/123] Add key backup test --- test/encryption/KeyBackupTest.ts | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 test/encryption/KeyBackupTest.ts diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts new file mode 100644 index 00000000..04a83d36 --- /dev/null +++ b/test/encryption/KeyBackupTest.ts @@ -0,0 +1,91 @@ +import * as simple from "simple-mock"; +import HttpBackend from 'matrix-mock-request'; + +import { ICurve25519AuthData, ICurve25519AuthDataUnsigned, IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, KeyBackupEncryptionAlgorithm } from "../../src/models/KeyBackup"; +import { MatrixClient, MatrixError, OTKAlgorithm, UnpaddedBase64 } from "../../src"; +import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; +import { bindNullEngine } from "./CryptoClientTest"; + +describe('KeyBackups', () => { + const userId = "@alice:example.org"; + let client: MatrixClient; + let http: HttpBackend; + + const prepareCrypto = async () => { + bindNullEngine(http); + await Promise.all([ + client.crypto.prepare([]), + http.flushAllExpected(), + ]); + }; + + beforeEach(() => testCryptoStores(async (cryptoStoreType) => { + const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); + client = mclient; + http = mhttp; + + await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); + })); + + it('should retrieve a missing backup version', () => testCryptoStores(async (cryptoStoreType) => { + http.when("GET", "/room_keys/version").respond(400, (path, obj) => { + return { + errcode: "M_NOT_FOUND", + error: "No current backup version", + }; + }); + + await Promise.all([ + http.flushAllExpected(), + (async () => { + const keyBackupInfo = await client.getKeyBackupVersion(); + expect(keyBackupInfo).toBeNull(); + })(), + ]); + })); + + it('should create and retrieve a backup version', () => testCryptoStores(async (cryptoStoreType) => { + await prepareCrypto(); + + const authDataUnsigned: ICurve25519AuthDataUnsigned = { + public_key: UnpaddedBase64.encodeString("pubkey"), + }; + + const keyBackupInfoToPost: IKeyBackupInfoUnsigned = { + algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, + auth_data: authDataUnsigned, + }; + + let keyBackupInfoOnServer: IKeyBackupInfoRetrieved|undefined; + + http.when("POST", "/room_keys/version").respond(200, (path, obj: IKeyBackupInfo) => { + expect(obj.auth_data.signatures[userId]).toHaveProperty(`ed25519:${TEST_DEVICE_ID}`); + + keyBackupInfoOnServer = { + ...obj, + version: "1", + count: 0, + etag: "etag0", + }; + return keyBackupInfoOnServer.version; + }); + + http.when("GET", "/room_keys/version").respond(200, (path, obj) => { + expect(keyBackupInfoOnServer).toBeDefined(); + expect(keyBackupInfoOnServer.version).toBe("1"); + + return keyBackupInfoOnServer; + }); + + await Promise.all([ + http.flushAllExpected(), + (async () => { + const keyBackupVersion = await client.signAndCreateKeyBackupVersion(keyBackupInfoToPost); + expect(keyBackupVersion).toStrictEqual(keyBackupInfoOnServer.version); + + const keyBackupInfoRetrieved = await client.getKeyBackupVersion(); + expect(keyBackupInfoRetrieved).toStrictEqual(keyBackupInfoOnServer); + })(), + ]); + })); +}); From 1af9ee9e9c9fbbc3ffe00b29ef28c1d15aeef8b5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 4 Aug 2023 17:27:41 -0400 Subject: [PATCH 048/123] Satisfy linter --- src/MatrixClient.ts | 2 +- test/encryption/KeyBackupTest.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 50bf7d66..63639fb6 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1997,7 +1997,7 @@ export class MatrixClient extends EventEmitter { auth_data: { ...info.auth_data, signatures: await this.crypto.sign(info), - } + }, }; return this.doRequest("POST", "/_matrix/client/v3/room_keys/version", null, data); } diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index 04a83d36..fba391cb 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -1,8 +1,7 @@ -import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; -import { ICurve25519AuthData, ICurve25519AuthDataUnsigned, IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, KeyBackupEncryptionAlgorithm } from "../../src/models/KeyBackup"; -import { MatrixClient, MatrixError, OTKAlgorithm, UnpaddedBase64 } from "../../src"; +import { ICurve25519AuthDataUnsigned, IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, KeyBackupEncryptionAlgorithm } from "../../src/models/KeyBackup"; +import { MatrixClient, UnpaddedBase64 } from "../../src"; import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; import { bindNullEngine } from "./CryptoClientTest"; From f96ac1938ea9d37e75ecc2396ef231f549e6bfa0 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 10 Aug 2023 01:27:49 -0400 Subject: [PATCH 049/123] Add tests and fixes --- src/MatrixClient.ts | 17 +--- src/e2ee/CryptoClient.ts | 2 + src/e2ee/RustEngine.ts | 4 +- src/models/KeyBackup.ts | 4 +- test/TestUtils.ts | 41 +++++++- test/encryption/CryptoClientTest.ts | 20 +--- test/encryption/KeyBackupTest.ts | 147 ++++++++++++++++++++++++++-- test/encryption/RoomTrackerTest.ts | 3 +- 8 files changed, 190 insertions(+), 48 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 63639fb6..be25d7c5 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1987,11 +1987,8 @@ export class MatrixClient extends EventEmitter { * with its auth_data left unsigned. * @returns {Promise} Resolves to the version id of the new backup. */ + @requiresCrypto() public async signAndCreateKeyBackupVersion(info: IKeyBackupInfoUnsigned): Promise { - if (!this.crypto) { - throw new Error("End-to-end encryption disabled"); - } - const data: IKeyBackupInfo = { ...info, auth_data: { @@ -2008,11 +2005,8 @@ export class MatrixClient extends EventEmitter { * @param {IKeyBackupInfoUpdate} info The properties of the key backup to be applied. * @returns {Promise} Resolves when complete. */ + @requiresCrypto() public updateKeyBackupVersion(version: KeyBackupVersion, info: IKeyBackupInfoUpdate): Promise { - if (!this.crypto) { - throw new Error("End-to-end encryption disabled"); - } - const data = { ...info, signatures: this.crypto.sign(info), @@ -2026,11 +2020,8 @@ export class MatrixClient extends EventEmitter { * as returned by {@link getKeyBackupVersion}. * @returns {Promise} Resolves when complete. */ + @requiresCrypto() public enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { - if (!this.crypto) { - throw new Error("End-to-end encryption disabled"); - } - return this.crypto.enableKeyBackup(info); } @@ -2038,7 +2029,7 @@ export class MatrixClient extends EventEmitter { * Disable backing up of room keys. */ public disableKeyBackup(): Promise { - return this.crypto?.disableKeyBackup(); + return this.crypto?.disableKeyBackup() ?? Promise.resolve(); } /** diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 6cd1bec0..97fd9467 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -293,6 +293,7 @@ export class CryptoClient { * as returned by {@link MatrixClient#getKeyBackupVersion}. * @returns {Promise} Resolves when complete. */ + @requiresReady() public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { this.client.on("to_device.decrypted", this.onToDeviceMessage); await this.engine.enableKeyBackup(info); @@ -302,6 +303,7 @@ export class CryptoClient { /** * Disable backing up of room keys. */ + @requiresReady() public async disableKeyBackup(): Promise { await this.engine.disableKeyBackup(); this.client.removeListener("to_device.decrypted", this.onToDeviceMessage); diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 135085e6..5e74c2ae 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -124,8 +124,6 @@ export class RustEngine { const keysClaim = await this.machine.getMissingSessions(members); if (keysClaim) { await this.processKeysClaimRequest(keysClaim); - // Back up keys asynchronously - void this.backupRoomKeysIfEnabled(); } }); @@ -134,6 +132,8 @@ export class RustEngine { for (const req of requests) { await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages); } + // Back up keys asynchronously + void this.backupRoomKeysIfEnabled(); }); } diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index 3b946257..9a83609b 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -37,9 +37,11 @@ export interface IKeyBackupVersion { version: KeyBackupVersion; } -export interface IKeyBackupInfoRetrieved extends IKeyBackupInfo, IKeyBackupVersion { +export interface IKeyBackupUpdateResponse { count: number; etag: string; } +export type IKeyBackupInfoRetrieved = IKeyBackupInfo & IKeyBackupVersion & IKeyBackupUpdateResponse; + export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial; diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 6b4b851f..f340e63e 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -2,7 +2,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { IStorageProvider, MatrixClient, RustSdkCryptoStorageProvider, setRequestFn } from "../src"; +import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -54,3 +54,42 @@ export async function testCryptoStores(fn: (StoreType) => Promise): Promis await fn(st); } } + +export function bindNullEngine(http: HttpBackend) { + http.when("POST", "/keys/upload").respond(200, (path, obj) => { + expect(obj).toMatchObject({ + + }); + return { + one_time_key_counts: { + // Enough to trick the OlmMachine into thinking it has enough keys + [OTKAlgorithm.Signed]: 1000, + }, + }; + }); + // Some oddity with the rust-sdk bindings during setup + bindNullQuery(http); +} + +export function bindNullQuery(http: HttpBackend) { + http.when("POST", "/keys/query").respond(200, (path, obj) => { + return {}; + }); +} + +/** + * Generate a string that can be used as a curve25519 public key. + * @returns A 32-byte string comprised of Unpadded Base64 characters. + */ +export function generateCurve25519PublicKey() { + return UnpaddedBase64.encodeString(generateAZString(32)); +} + +/** + * Generate an arbitrary string with characters in the range A-Z. + * @param length The length of the string to generate. + * @returns The generated string. + */ +function generateAZString(length: number) { + return String.fromCharCode(...Array.from({ length }, () => Math.floor(65 + Math.random()*25))); +} diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index f472d57d..60d7fe84 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -2,25 +2,7 @@ import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; import { EncryptedFile, EncryptionAlgorithm, IOlmEncrypted, IToDeviceMessage, MatrixClient, MembershipEvent, OTKAlgorithm, RoomEncryptionAlgorithm } from "../../src"; -import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; - -export function bindNullEngine(http: HttpBackend) { - http.when("POST", "/keys/upload").respond(200, (path, obj) => { - expect(obj).toMatchObject({ - - }); - return { - one_time_key_counts: { - // Enough to trick the OlmMachine into thinking it has enough keys - [OTKAlgorithm.Signed]: 1000, - }, - }; - }); - // Some oddity with the rust-sdk bindings during setup - http.when("POST", "/keys/query").respond(200, (path, obj) => { - return {}; - }); -} +import { bindNullEngine, createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; describe('CryptoClient', () => { it('should not have a device ID or be ready until prepared', () => testCryptoStores(async (cryptoStoreType) => { diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index fba391cb..b44e191c 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -1,12 +1,19 @@ import HttpBackend from 'matrix-mock-request'; -import { ICurve25519AuthDataUnsigned, IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, KeyBackupEncryptionAlgorithm } from "../../src/models/KeyBackup"; -import { MatrixClient, UnpaddedBase64 } from "../../src"; -import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; -import { bindNullEngine } from "./CryptoClientTest"; +import { + ICurve25519AuthDataUnsigned, + IKeyBackupInfo, + IKeyBackupInfoRetrieved, + IKeyBackupInfoUnsigned, + IKeyBackupUpdateResponse, + KeyBackupEncryptionAlgorithm, +} from "../../src/models/KeyBackup"; +import { EncryptionAlgorithm, ICryptoRoomInformation, MatrixClient, RoomTracker } from "../../src"; +import { bindNullEngine, createTestClient, testCryptoStores, TEST_DEVICE_ID, generateCurve25519PublicKey, bindNullQuery } from "../TestUtils"; + +const USER_ID = "@alice:example.org"; describe('KeyBackups', () => { - const userId = "@alice:example.org"; let client: MatrixClient; let http: HttpBackend; @@ -19,7 +26,7 @@ describe('KeyBackups', () => { }; beforeEach(() => testCryptoStores(async (cryptoStoreType) => { - const { client: mclient, http: mhttp } = createTestClient(null, userId, cryptoStoreType); + const { client: mclient, http: mhttp } = createTestClient(null, USER_ID, cryptoStoreType); client = mclient; http = mhttp; @@ -35,19 +42,35 @@ describe('KeyBackups', () => { }); await Promise.all([ - http.flushAllExpected(), (async () => { const keyBackupInfo = await client.getKeyBackupVersion(); expect(keyBackupInfo).toBeNull(); })(), + http.flushAllExpected(), ]); })); + it('should fail to create a backup version when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { + try { + await client.signAndCreateKeyBackupVersion({ + algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, + auth_data: { + public_key: "fake_key", + }, + }); + + // noinspection ExceptionCaughtLocallyJS + throw new Error("Failed to fail"); + } catch (e) { + expect(e.message).toEqual("End-to-end encryption has not initialized"); + } + })); + it('should create and retrieve a backup version', () => testCryptoStores(async (cryptoStoreType) => { await prepareCrypto(); const authDataUnsigned: ICurve25519AuthDataUnsigned = { - public_key: UnpaddedBase64.encodeString("pubkey"), + public_key: generateCurve25519PublicKey(), }; const keyBackupInfoToPost: IKeyBackupInfoUnsigned = { @@ -58,7 +81,7 @@ describe('KeyBackups', () => { let keyBackupInfoOnServer: IKeyBackupInfoRetrieved|undefined; http.when("POST", "/room_keys/version").respond(200, (path, obj: IKeyBackupInfo) => { - expect(obj.auth_data.signatures[userId]).toHaveProperty(`ed25519:${TEST_DEVICE_ID}`); + expect(obj.auth_data.signatures[USER_ID]).toHaveProperty(`ed25519:${TEST_DEVICE_ID}`); keyBackupInfoOnServer = { ...obj, @@ -77,7 +100,6 @@ describe('KeyBackups', () => { }); await Promise.all([ - http.flushAllExpected(), (async () => { const keyBackupVersion = await client.signAndCreateKeyBackupVersion(keyBackupInfoToPost); expect(keyBackupVersion).toStrictEqual(keyBackupInfoOnServer.version); @@ -85,6 +107,111 @@ describe('KeyBackups', () => { const keyBackupInfoRetrieved = await client.getKeyBackupVersion(); expect(keyBackupInfoRetrieved).toStrictEqual(keyBackupInfoOnServer); })(), + http.flushAllExpected(), ]); })); + + it('should fail to back up keys when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { + try { + await client.crypto.enableKeyBackup({ + algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, + auth_data: { + public_key: "fake_key", + signatures: {}, + }, + version: "1", + count: 0, + etag: "etag0", + }); + + // noinspection ExceptionCaughtLocallyJS + throw new Error("Failed to fail"); + } catch (e) { + expect(e.message).toEqual("End-to-end encryption has not initialized"); + } + })); + + it('correctly backs up keys', () => testCryptoStores(async (cryptoStoreType) => { + await prepareCrypto(); + + // --- Generate a room key by preparing encryption for that room + + const roomId = "!a:example.org"; + client.getJoinedRoomMembers = async () => [USER_ID]; + client.crypto.isRoomEncrypted = async () => true; + + const roomCryptoConfig: ICryptoRoomInformation = { + algorithm: EncryptionAlgorithm.MegolmV1AesSha2, + rotation_period_msgs: 1, + }; + ((client.crypto as any).roomTracker as RoomTracker).getRoomCryptoConfig = async () => roomCryptoConfig; + + const encryptRoomEvent = async () => { + bindNullQuery(http); + const encryptPromise = client.crypto.encryptRoomEvent(roomId, "m.room.message", "my message"); + await http.flushAllExpected({ timeout: 10000 }); + + // This is because encryptRoomEvent calls "/keys/query" after encrypting too. + bindNullQuery(http); + await Promise.all([ + encryptPromise, + http.flushAllExpected({ timeout: 10000 }), + ]); + }; + + await encryptRoomEvent(); + + // --- Back up the generated room key by enabling backups + + const authDataUnsigned: ICurve25519AuthDataUnsigned = { + public_key: generateCurve25519PublicKey(), + }; + const keyBackupInfo: IKeyBackupInfoRetrieved = { + algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, + auth_data: { + ...authDataUnsigned, + signatures: await client.crypto.sign(authDataUnsigned), + }, + version: "1", + count: 0, + etag: "etag0", + }; + + const knownSessions: Set = new Set(); + let etagCount = 0; + + const expectToPutRoomKey = () => { + http.when("PUT", "/room_keys/keys").respond(200, (path, obj: Record): IKeyBackupUpdateResponse => { + const sessions = obj?.rooms[roomId]?.sessions; + expect(sessions).toBeDefined(); + + Object.keys(sessions).forEach(session => { knownSessions.add(session); }); + return { + count: knownSessions.size, + etag: `etag${++etagCount}`, + }; + }); + }; + + expectToPutRoomKey(); + await Promise.all([ + client.enableKeyBackup(keyBackupInfo), + http.flushAllExpected(), + ]); + expect(knownSessions.size).toStrictEqual(1); + + // --- Back up a new room key by generating one while backups are enabled + + expectToPutRoomKey(); + await encryptRoomEvent(); + expect(knownSessions.size).toStrictEqual(2); + + // --- Back up a room key received via a to-device message + // TODO: use updateSyncData to send an *encrypted* "m.room_key" event. + + // --- Should not time out due to a mistake in the promise queue + await client.disableKeyBackup(); + }), + // Use longer timeout to give more time for encryption + 30000); }); diff --git a/test/encryption/RoomTrackerTest.ts b/test/encryption/RoomTrackerTest.ts index 4ce0a00c..3c4c16a1 100644 --- a/test/encryption/RoomTrackerTest.ts +++ b/test/encryption/RoomTrackerTest.ts @@ -1,8 +1,7 @@ import * as simple from "simple-mock"; import { EncryptionEventContent, MatrixClient, RoomEncryptionAlgorithm, RoomTracker } from "../../src"; -import { createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; -import { bindNullEngine } from "./CryptoClientTest"; +import { bindNullEngine, createTestClient, testCryptoStores, TEST_DEVICE_ID } from "../TestUtils"; function prepareQueueSpies( client: MatrixClient, From 746e4a7c4e9fc4cd96558228b358f79a417d544a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 10 Aug 2023 09:37:43 -0400 Subject: [PATCH 050/123] Error when enabling backups with unknown algorithm --- src/e2ee/RustEngine.ts | 13 ++++++++++--- test/encryption/KeyBackupTest.ts | 31 +++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 5e74c2ae..4fb77840 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -18,7 +18,7 @@ import { MatrixClient } from "../MatrixClient"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; import { EncryptionAlgorithm } from "../models/Crypto"; import { EncryptionEvent } from "../models/events/EncryptionEvent"; -import { ICurve25519AuthData, IKeyBackupInfoRetrieved, KeyBackupVersion } from "../models/KeyBackup"; +import { ICurve25519AuthData, IKeyBackupInfoRetrieved, KeyBackupEncryptionAlgorithm, KeyBackupVersion } from "../models/KeyBackup"; /** * @internal @@ -143,8 +143,15 @@ export class RustEngine { // Finish any pending backups before changing the backup version/pubkey await this.actuallyDisableKeyBackup(); } - // TODO Error with message if the key backup uses an unsupported auth_data type - await this.machine.enableBackupV1((info.auth_data as ICurve25519AuthData).public_key, info.version); + let publicKey: string; + switch (info.algorithm) { + case KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2: + publicKey = (info.auth_data as ICurve25519AuthData).public_key; + break; + default: + throw new Error("Key backup error: cannot enable backups with unsupported backup algorithm " + info.algorithm); + } + await this.machine.enableBackupV1(publicKey, info.version); this.keyBackupVersion = info.version; this.isBackupEnabled = true; }); diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index b44e191c..c2638963 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -73,7 +73,7 @@ describe('KeyBackups', () => { public_key: generateCurve25519PublicKey(), }; - const keyBackupInfoToPost: IKeyBackupInfoUnsigned = { + const keyBackupInfo: IKeyBackupInfoUnsigned = { algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, auth_data: authDataUnsigned, }; @@ -101,7 +101,7 @@ describe('KeyBackups', () => { await Promise.all([ (async () => { - const keyBackupVersion = await client.signAndCreateKeyBackupVersion(keyBackupInfoToPost); + const keyBackupVersion = await client.signAndCreateKeyBackupVersion(keyBackupInfo); expect(keyBackupVersion).toStrictEqual(keyBackupInfoOnServer.version); const keyBackupInfoRetrieved = await client.getKeyBackupVersion(); @@ -111,9 +111,9 @@ describe('KeyBackups', () => { ]); })); - it('should fail to back up keys when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { + it('should fail to enable backups when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { try { - await client.crypto.enableKeyBackup({ + await client.enableKeyBackup({ algorithm: KeyBackupEncryptionAlgorithm.MegolmBackupV1Curve25519AesSha2, auth_data: { public_key: "fake_key", @@ -131,6 +131,29 @@ describe('KeyBackups', () => { } })); + it('should fail to enable backups with an unsupported algorithm', () => testCryptoStores(async (cryptoStoreType) => { + await prepareCrypto(); + + const algorithm = "bogocrypt"; + + try { + await client.enableKeyBackup({ + algorithm, + auth_data: { + signatures: {}, + }, + version: "0", + count: 0, + etag: "zz", + }); + + // noinspection ExceptionCaughtLocallyJS + throw new Error("Failed to fail"); + } catch (e) { + expect(e.message).toEqual("Key backup error: cannot enable backups with unsupported backup algorithm " + algorithm); + } + })); + it('correctly backs up keys', () => testCryptoStores(async (cryptoStoreType) => { await prepareCrypto(); From c249cf58718b7e3c8aca160bbe7e94b09226e0cc Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 11 Aug 2023 09:48:55 -0400 Subject: [PATCH 051/123] Tweak key backup auth data type to fix docs --- src/models/KeyBackup.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index 9a83609b..a68495c7 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -14,13 +14,15 @@ export interface ICurve25519AuthDataUnsigned { } export type ICurve25519AuthData = ICurve25519AuthDataUnsigned & Signed; +export type IKeyBackupAuthData = IJsonType | ICurve25519AuthDataUnsigned; + /** * Information about a server-side key backup, * with its auth_data left unsigned. */ export interface IKeyBackupInfoUnsigned { algorithm: string | KeyBackupEncryptionAlgorithm; - auth_data: IJsonType | ICurve25519AuthDataUnsigned; + auth_data: IKeyBackupAuthData; } /** @@ -28,7 +30,7 @@ export interface IKeyBackupInfoUnsigned { * with its auth_data signed by the entity that created it. */ export type IKeyBackupInfo = IKeyBackupInfoUnsigned & { - auth_data: Signed & IKeyBackupInfoUnsigned["auth_data"]; + auth_data: Signed & IKeyBackupAuthData; }; export type KeyBackupVersion = string; From ac1fe4499e447618aca400741c839e9a75abb232 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 10 Aug 2023 11:50:43 -0400 Subject: [PATCH 052/123] Get type checking from OlmMachine.shareRoomKey Update the rust-sdk bindings to have access to type checking in the return value of OlmMachine.shareRoomKey, which now returns an array of ToDeviceRequest objects instead of a JSON encoding of the whole array. --- package.json | 2 +- src/e2ee/RustEngine.ts | 13 ++++--------- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 678fd8cf..273b76c8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.9", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.10", "@types/express": "^4.17.13", "another-json": "^0.2.0", "async-lock": "^1.3.2", diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index c9eae9c8..63293081 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -121,9 +121,9 @@ export class RustEngine { }); await this.lock.acquire(roomId, async () => { - const requests = JSON.parse(await this.machine.shareRoomKey(new RoomId(roomId), members, settings)); + const requests = await this.machine.shareRoomKey(new RoomId(roomId), members, settings); for (const req of requests) { - await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages); + await this.processToDeviceRequest(req); } }); } @@ -146,12 +146,7 @@ export class RustEngine { } private async processToDeviceRequest(request: ToDeviceRequest) { - const req = JSON.parse(request.body); - await this.actuallyProcessToDeviceRequest(req.txn_id, req.event_type, req.messages); - } - - private async actuallyProcessToDeviceRequest(id: string, type: string, messages: Record>) { - const resp = await this.client.sendToDevices(type, messages); - await this.machine.markRequestAsSent(id, RequestType.ToDevice, JSON.stringify(resp)); + const resp = await this.client.sendToDevices(request.eventType, JSON.parse(request.body).messages); + await this.machine.markRequestAsSent(request.txnId, RequestType.ToDevice, JSON.stringify(resp)); } } diff --git a/yarn.lock b/yarn.lock index 56f4d508..090f6316 100644 --- a/yarn.lock +++ b/yarn.lock @@ -584,10 +584,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.9": - version "0.1.0-beta.9" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.9.tgz#dc21f3b0f4b35b73befc64a257b8e519afbcbed0" - integrity sha512-ee2YlBoXPLgp1aav9MqREJKvWJfURn9Jcs46FyWT4NXEl37KQDNC8CWWnqgqsHkLfBxxSxfq9kMA/mWQZF7QJw== +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.10": + version "0.1.0-beta.10" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.10.tgz#52290c76ac997001b615c9fb78b70e36b6a4501f" + integrity sha512-AiSHgpHw75sJ1k9uqWk74Wps74XM+M7LsQZrLFlZh/nv9fhOk7JvRZlQczDK9qhD0Umt84PRcOumgT5bXbA/lw== dependencies: https-proxy-agent "^5.0.1" node-downloader-helper "^2.1.5" From de0872ddf2bf046ebf12fe6ab9e6d0057ecb2969 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Sep 2023 18:05:08 +0100 Subject: [PATCH 053/123] Add support for exportRoomKeysForSession --- src/e2ee/CryptoClient.ts | 10 ++++++++++ src/e2ee/RustEngine.ts | 6 +++++- src/models/KeyBackup.ts | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 97fd9467..90f1856c 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -135,6 +135,16 @@ export class CryptoClient { } } + /** + * Exports a set of keys for a given session. + * @param roomId The room ID for the session. + * @param sessionId The session ID. + * @returns An array of session keys. + */ + public async exportRoomKeysForSession(roomId: string, sessionId: string) { + return this.engine.exportRoomKeysForSession(roomId, sessionId); + } + /** * Checks if a room is encrypted. * @param {string} roomId The room ID to check. diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 14349da6..724fdc79 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -19,7 +19,7 @@ import { extractRequestError, LogService } from "../logging/LogService"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; import { EncryptionAlgorithm } from "../models/Crypto"; import { EncryptionEvent } from "../models/events/EncryptionEvent"; -import { ICurve25519AuthData, IKeyBackupInfoRetrieved, KeyBackupEncryptionAlgorithm, KeyBackupVersion } from "../models/KeyBackup"; +import { ICurve25519AuthData, IKeyBackupInfoRetrieved, IOlmSessionExport, KeyBackupEncryptionAlgorithm, KeyBackupVersion } from "../models/KeyBackup"; import { Membership } from "../models/events/MembershipEvent"; /** @@ -196,6 +196,10 @@ export class RustEngine { return this.keyBackupWaiter; } + public async exportRoomKeysForSession(roomId: string, sessionId: string): Promise { + return JSON.parse(await this.machine.exportRoomKeysForSession(roomId, sessionId)) as IOlmSessionExport[]; + } + private backupRoomKeysIfEnabled(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { if (this.isBackupEnabled) { diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index a68495c7..cc1c32c4 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -47,3 +47,15 @@ export interface IKeyBackupUpdateResponse { export type IKeyBackupInfoRetrieved = IKeyBackupInfo & IKeyBackupVersion & IKeyBackupUpdateResponse; export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial; + +export interface IOlmSessionExport { + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": string, + "sender_key": string, + "session_id": string, + "session_key": string, + "sender_claimed_keys":{ + "ed25519": string + }, + "forwarding_curve25519_key_chain": unknown[], +} \ No newline at end of file From 187363e0551049e7b7d6982541cd812967b0059f Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 6 Sep 2023 01:01:05 +0100 Subject: [PATCH 054/123] Update dependency --- package.json | 2 +- yarn.lock | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 1b911a57..505ab0c1 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.10", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.11", "@types/express": "^4.17.13", "another-json": "^0.2.0", "async-lock": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index bd127161..acb9022d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -662,10 +662,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.10": - version "0.1.0-beta.10" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.10.tgz#52290c76ac997001b615c9fb78b70e36b6a4501f" - integrity sha512-AiSHgpHw75sJ1k9uqWk74Wps74XM+M7LsQZrLFlZh/nv9fhOk7JvRZlQczDK9qhD0Umt84PRcOumgT5bXbA/lw== +"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.11": + version "0.1.0-beta.11" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.11.tgz#537cd7a7bbce1d9745b812a5a7ffa9a5944e146c" + integrity sha512-z5adcQo4o0UAry4zs6JHGxbTDlYTUMKUfpOpigmso65ETBDumbeTSQCWRw8UeUV7aCAyVoHARqDTol9SrauEFA== dependencies: https-proxy-agent "^5.0.1" node-downloader-helper "^2.1.5" @@ -1089,13 +1089,6 @@ agent-base@6: dependencies: debug "4" -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" From 3988fd35eaced74922e925ec1703ac2a2f79d838 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 08:25:54 -0400 Subject: [PATCH 055/123] Nitpick: fix inconsistent indent in package.json so that "yarn add" won't have to change the indentation again --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 505ab0c1..60582ac9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "homepage": "https://github.com/vector-im/matrix-bot-sdk#readme", "publishConfig": { - "access": "public" + "access": "public" }, "scripts": { "prepublishOnly": "yarn build", From 7fdb6e2819e9aba4e8f9dc60f5155abd43915264 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 08:27:36 -0400 Subject: [PATCH 056/123] Rename & refactor session data export type --- src/e2ee/CryptoClient.ts | 2 +- src/e2ee/RustEngine.ts | 6 +++--- src/models/KeyBackup.ts | 21 +++++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 90f1856c..43455db8 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -138,7 +138,7 @@ export class CryptoClient { /** * Exports a set of keys for a given session. * @param roomId The room ID for the session. - * @param sessionId The session ID. + * @param sessionId The session ID. * @returns An array of session keys. */ public async exportRoomKeysForSession(roomId: string, sessionId: string) { diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 724fdc79..ff64bef5 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -19,7 +19,7 @@ import { extractRequestError, LogService } from "../logging/LogService"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; import { EncryptionAlgorithm } from "../models/Crypto"; import { EncryptionEvent } from "../models/events/EncryptionEvent"; -import { ICurve25519AuthData, IKeyBackupInfoRetrieved, IOlmSessionExport, KeyBackupEncryptionAlgorithm, KeyBackupVersion } from "../models/KeyBackup"; +import { ICurve25519AuthData, IKeyBackupInfoRetrieved, IMegolmSessionDataExport, KeyBackupEncryptionAlgorithm, KeyBackupVersion } from "../models/KeyBackup"; import { Membership } from "../models/events/MembershipEvent"; /** @@ -196,8 +196,8 @@ export class RustEngine { return this.keyBackupWaiter; } - public async exportRoomKeysForSession(roomId: string, sessionId: string): Promise { - return JSON.parse(await this.machine.exportRoomKeysForSession(roomId, sessionId)) as IOlmSessionExport[]; + public async exportRoomKeysForSession(roomId: string, sessionId: string): Promise { + return JSON.parse(await this.machine.exportRoomKeysForSession(roomId, sessionId)) as IMegolmSessionDataExport[]; } private backupRoomKeysIfEnabled(): Promise { diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index cc1c32c4..29fa52a1 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -1,5 +1,6 @@ import { IJsonType } from "../helpers/Types"; import { Signed } from "./Crypto"; +import { RoomEncryptionAlgorithm } from "./events/EncryptionEvent"; /** * The kinds of key backup encryption algorithms allowed by the spec. @@ -48,14 +49,14 @@ export type IKeyBackupInfoRetrieved = IKeyBackupInfo & IKeyBackupVersion & IKeyB export type IKeyBackupInfoUpdate = IKeyBackupInfo & Partial; -export interface IOlmSessionExport { - "algorithm": "m.megolm.v1.aes-sha2", - "room_id": string, - "sender_key": string, - "session_id": string, - "session_key": string, - "sender_claimed_keys":{ - "ed25519": string - }, - "forwarding_curve25519_key_chain": unknown[], +export interface IMegolmSessionDataExport { + algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2; + room_id: string; + sender_key: string; + session_id: string; + session_key: string; + sender_claimed_keys: { + [algorithm: string]: string; + }; + forwarding_curve25519_key_chain: string[]; } \ No newline at end of file From b945461255cf79c62aadd88dc5e233e9fdcca7dc Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 08:34:03 -0400 Subject: [PATCH 057/123] Satisfy linter with trailing newline --- src/models/KeyBackup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/KeyBackup.ts b/src/models/KeyBackup.ts index 29fa52a1..812f034e 100644 --- a/src/models/KeyBackup.ts +++ b/src/models/KeyBackup.ts @@ -59,4 +59,4 @@ export interface IMegolmSessionDataExport { [algorithm: string]: string; }; forwarding_curve25519_key_chain: string[]; -} \ No newline at end of file +} From 4b59dc06ba1bb3bc7ed363fdb8930fa0d4a4236c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 09:34:50 -0400 Subject: [PATCH 058/123] Catch when CryptoClient can't find room members CryptoClient#onRoomEvent may throw when looking up the members of a room with a client that isn't in the room, which can happen if appservice namespaces are broad & the appservice receives events for rooms that some of its managed clients are not members of. --- src/e2ee/CryptoClient.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 74ddfb50..4a756aa4 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -108,7 +108,7 @@ export class CryptoClient { * @param roomId The room ID. * @param event The event. */ - public async onRoomEvent(roomId: string, event: any) { + public async onRoomEvent(roomId: string, event: any): Promise { await this.roomTracker.onRoomEvent(roomId, event); if (typeof event['state_key'] !== 'string') return; if (event['type'] === 'm.room.member') { @@ -116,8 +116,10 @@ export class CryptoClient { if (membership.effectiveMembership !== 'join' && membership.effectiveMembership !== 'invite') return; await this.engine.addTrackedUsers([membership.membershipFor]); } else if (event['type'] === 'm.room.encryption') { - const members = await this.client.getRoomMembers(roomId, null, ['join', 'invite']); - await this.engine.addTrackedUsers(members.map(e => e.membershipFor)); + return this.client.getRoomMembers(roomId, null, ['join', 'invite']).then( + members => this.engine.addTrackedUsers(members.map(e => e.membershipFor)), + e => void LogService.error("CryptoClient", `Error getting members of room ${roomId}:`, e), + ); } } From b5fbd7f357e4a46f6b5947d4880a560d752f258b Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 09:50:11 -0400 Subject: [PATCH 059/123] Put exportRoomKeysForSession on MatrixClient This also allows it to be guarded with a check on whether the client's crypto had been set up. --- src/MatrixClient.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 1c7b8d8b..bcc8b1b4 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -2037,6 +2037,17 @@ export class MatrixClient extends EventEmitter { return this.crypto?.disableKeyBackup() ?? Promise.resolve(); } + /** + * Exports a set of keys for a given session. + * @param roomId The room ID for the session. + * @param sessionId The session ID. + * @returns An array of session keys. + */ + @requiresCrypto() + public exportRoomKeysForSession(roomId: string, sessionId: string) { + return this.crypto.exportRoomKeysForSession(roomId, sessionId); + } + /** * Get relations for a given event. * @param {string} roomId The room ID to for the given event. From bd9fa96c5fd655646eac4a0fd81bdb28f92d5b27 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 10:13:38 -0400 Subject: [PATCH 060/123] Test room key exports --- test/encryption/KeyBackupTest.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index 27f5c971..a7f2f076 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -8,7 +8,7 @@ import { IKeyBackupUpdateResponse, KeyBackupEncryptionAlgorithm, } from "../../src/models/KeyBackup"; -import { EncryptionAlgorithm, ICryptoRoomInformation, MatrixClient, MembershipEvent, RoomTracker } from "../../src"; +import { RoomEncryptionAlgorithm, ICryptoRoomInformation, MatrixClient, MembershipEvent, RoomTracker } from "../../src"; import { bindNullEngine, createTestClient, testCryptoStores, TEST_DEVICE_ID, generateCurve25519PublicKey, bindNullQuery } from "../TestUtils"; const USER_ID = "@alice:example.org"; @@ -171,7 +171,7 @@ describe('KeyBackups', () => { client.crypto.isRoomEncrypted = async () => true; const roomCryptoConfig: ICryptoRoomInformation = { - algorithm: EncryptionAlgorithm.MegolmV1AesSha2, + algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2, rotation_period_msgs: 1, }; ((client.crypto as any).roomTracker as RoomTracker).getRoomCryptoConfig = async () => roomCryptoConfig; @@ -239,6 +239,23 @@ describe('KeyBackups', () => { // --- Back up a room key received via a to-device message // TODO: use updateSyncData to send an *encrypted* "m.room_key" event. + // --- Export a room key + // TODO: consider moving this to a test dedicated to key exports + + for (const session of knownSessions) { + const roomKeys = await client.exportRoomKeysForSession(roomId, session); + expect(roomKeys).toHaveLength(roomCryptoConfig.rotation_period_msgs); + for (const roomKey of roomKeys) { + expect(roomKey.algorithm).toStrictEqual(RoomEncryptionAlgorithm.MegolmV1AesSha2); + expect(roomKey.room_id).toStrictEqual(roomId); + expect(roomKey.sender_key).toBeTruthy(); + expect(roomKey.session_id).toStrictEqual(session); + expect(roomKey.session_key).toBeTruthy(); + expect(roomKey.sender_claimed_keys).toBeTruthy(); + expect(roomKey.forwarding_curve25519_key_chain).toBeTruthy(); + } + } + // --- Should not time out due to a mistake in the promise queue await client.disableKeyBackup(); }), From ee779574fd2028a5227af2bdfdd6013d82bc7906 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 15:17:39 -0400 Subject: [PATCH 061/123] Emit an event when an Intent is created This allows responding to when a transaction requires an appservice user that the process has not yet initialized. --- src/appservice/Appservice.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 4a9aa625..27ee5bbb 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -451,6 +451,7 @@ export class Appservice extends EventEmitter { let intent: Intent = this.intentsCache.get(userId); if (!intent) { intent = new Intent(this.options, userId, this); + this.emit("intent.new", intent); this.intentsCache.set(userId, intent); if (this.options.intentOptions.encryption) { intent.enableEncryption().catch(e => { From dab204a2e0a610d5cdc6b31a38db7e309fca7a84 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 18:23:59 -0400 Subject: [PATCH 062/123] Warn instead of error because: - the failure will print its own logs - the point of catching this is that it's _not_ an error --- src/e2ee/CryptoClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 4a756aa4..70f2134f 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -118,7 +118,7 @@ export class CryptoClient { } else if (event['type'] === 'm.room.encryption') { return this.client.getRoomMembers(roomId, null, ['join', 'invite']).then( members => this.engine.addTrackedUsers(members.map(e => e.membershipFor)), - e => void LogService.error("CryptoClient", `Error getting members of room ${roomId}:`, e), + e => void LogService.warn("CryptoClient", `Error getting members of room ${roomId}:`, e), ); } } From 8fbe5e0f949be0b7dba8cafe07f717f30bc8ef16 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 18:52:46 -0400 Subject: [PATCH 063/123] Don't print error object in warning because the failure already logs the error --- src/e2ee/CryptoClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 70f2134f..66f5ff13 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -118,7 +118,7 @@ export class CryptoClient { } else if (event['type'] === 'm.room.encryption') { return this.client.getRoomMembers(roomId, null, ['join', 'invite']).then( members => this.engine.addTrackedUsers(members.map(e => e.membershipFor)), - e => void LogService.warn("CryptoClient", `Error getting members of room ${roomId}:`, e), + e => void LogService.warn("CryptoClient", `Unable to get members of room ${roomId}`), ); } } From 8dab018143f60b1a806af4c69d5c4660033bba53 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 6 Sep 2023 14:55:36 -0400 Subject: [PATCH 064/123] Prevent setting multiple room key backup listeners If key backups are enabled on a client that already has them enabled, don't re-add the listener for to-device room key messages. --- src/e2ee/CryptoClient.ts | 10 +++-- src/e2ee/RustEngine.ts | 16 +++++--- test/encryption/KeyBackupTest.ts | 68 +++++++++++++++++++++++++------- 3 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 43455db8..34d56b8b 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -301,13 +301,17 @@ export class CryptoClient { * Enable backing up of room keys. * @param {IKeyBackupInfoRetrieved} info The configuration for key backup behaviour, * as returned by {@link MatrixClient#getKeyBackupVersion}. - * @returns {Promise} Resolves when complete. + * @returns {Promise} Resolves once backups have been enabled. */ @requiresReady() public async enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { - this.client.on("to_device.decrypted", this.onToDeviceMessage); + if (!this.engine.isBackupEnabled()) { + // Only add the listener if we didn't add it already + this.client.on("to_device.decrypted", this.onToDeviceMessage); + } await this.engine.enableKeyBackup(info); - this.engine.backupRoomKeys(); + // Back up any pending keys now, but asynchronously + void this.engine.backupRoomKeys(); } /** diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index ff64bef5..8239b79c 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -35,7 +35,11 @@ export class RustEngine { private keyBackupVersion: KeyBackupVersion|undefined; private keyBackupWaiter = Promise.resolve(); - private isBackupEnabled = false; + + private backupEnabled = false; + public isBackupEnabled() { + return this.backupEnabled; + } public constructor(public readonly machine: OlmMachine, private client: MatrixClient) { } @@ -154,7 +158,7 @@ export class RustEngine { public enableKeyBackup(info: IKeyBackupInfoRetrieved): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { - if (this.isBackupEnabled) { + if (this.backupEnabled) { // Finish any pending backups before changing the backup version/pubkey await this.actuallyDisableKeyBackup(); } @@ -168,7 +172,7 @@ export class RustEngine { } await this.machine.enableBackupV1(publicKey, info.version); this.keyBackupVersion = info.version; - this.isBackupEnabled = true; + this.backupEnabled = true; }); return this.keyBackupWaiter; } @@ -183,12 +187,12 @@ export class RustEngine { private async actuallyDisableKeyBackup(): Promise { await this.machine.disableBackup(); this.keyBackupVersion = undefined; - this.isBackupEnabled = false; + this.backupEnabled = false; } public backupRoomKeys(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { - if (!this.isBackupEnabled) { + if (!this.backupEnabled) { throw new Error("Key backup error: attempted to create a backup before having enabled backups"); } await this.actuallyBackupRoomKeys(); @@ -202,7 +206,7 @@ export class RustEngine { private backupRoomKeysIfEnabled(): Promise { this.keyBackupWaiter = this.keyBackupWaiter.then(async () => { - if (this.isBackupEnabled) { + if (this.backupEnabled) { await this.actuallyBackupRoomKeys(); } }); diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index a7f2f076..186a530f 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -1,3 +1,4 @@ +import * as simple from "simple-mock"; import HttpBackend from 'matrix-mock-request'; import { @@ -8,7 +9,7 @@ import { IKeyBackupUpdateResponse, KeyBackupEncryptionAlgorithm, } from "../../src/models/KeyBackup"; -import { RoomEncryptionAlgorithm, ICryptoRoomInformation, MatrixClient, MembershipEvent, RoomTracker } from "../../src"; +import { ICryptoRoomInformation, IToDeviceMessage, MatrixClient, MembershipEvent, RoomEncryptionAlgorithm, RoomTracker } from "../../src"; import { bindNullEngine, createTestClient, testCryptoStores, TEST_DEVICE_ID, generateCurve25519PublicKey, bindNullQuery } from "../TestUtils"; const USER_ID = "@alice:example.org"; @@ -208,19 +209,22 @@ describe('KeyBackups', () => { }; const knownSessions: Set = new Set(); + let expectedSessions = 0; let etagCount = 0; + const onBackupRequest = (path, obj: Record): IKeyBackupUpdateResponse => { + const sessions = obj?.rooms[roomId]?.sessions; + expect(sessions).toBeDefined(); + + Object.keys(sessions).forEach(session => { knownSessions.add(session); }); + return { + count: knownSessions.size, + etag: `etag${++etagCount}`, + }; + }; + const expectToPutRoomKey = () => { - http.when("PUT", "/room_keys/keys").respond(200, (path, obj: Record): IKeyBackupUpdateResponse => { - const sessions = obj?.rooms[roomId]?.sessions; - expect(sessions).toBeDefined(); - - Object.keys(sessions).forEach(session => { knownSessions.add(session); }); - return { - count: knownSessions.size, - etag: `etag${++etagCount}`, - }; - }); + http.when("PUT", "/room_keys/keys").respond(200, onBackupRequest); }; expectToPutRoomKey(); @@ -228,16 +232,52 @@ describe('KeyBackups', () => { client.enableKeyBackup(keyBackupInfo), http.flushAllExpected(), ]); - expect(knownSessions.size).toStrictEqual(1); + expect(knownSessions.size).toBe(++expectedSessions); + + // --- Test that it's safe to re-enable backups + + // Re-enabling backups replays all existing keys, so expect another request to be made + expectToPutRoomKey(); + await Promise.all([ + client.enableKeyBackup(keyBackupInfo), + http.flushAllExpected(), + ]); + // No new session expected this time + expect(knownSessions.size).toBe(expectedSessions); // --- Back up a new room key by generating one while backups are enabled expectToPutRoomKey(); await encryptRoomEvent(); - expect(knownSessions.size).toStrictEqual(2); + expect(knownSessions.size).toBe(++expectedSessions); // --- Back up a room key received via a to-device message - // TODO: use updateSyncData to send an *encrypted* "m.room_key" event. + + const onRoomKeySpy = simple.mock((client.crypto as any).engine, "backupRoomKeys"); + + // TODO: Encrypt this so that it will actually be included in the backup. + // Until then, no backup request or new session are expected. + const toDeviceMessage: IToDeviceMessage = { + type: "m.room_key", + sender: USER_ID, + content: { + algorithm: RoomEncryptionAlgorithm.MegolmV1AesSha2, + room_id: roomId, + session_id: "abc", + session_key: "def", + }, + }; + + bindNullEngine(http); + await Promise.all([ + client.crypto.updateSyncData( + [toDeviceMessage], + {}, [], [], [], + ), + http.flushAllExpected(), + ]); + expect(knownSessions.size).toBe(expectedSessions); + expect(onRoomKeySpy.callCount).toBe(1); // --- Export a room key // TODO: consider moving this to a test dedicated to key exports From f03e778343afa26ef174eacc13410a85c7b7c020 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 27 Sep 2023 14:38:29 -0400 Subject: [PATCH 065/123] Emit "intent.new" after caching the new Intent Otherwise, if a listener of that event were to trigger a lookup of the same Intent, the lookup wouldn't see the event in the Intent cache, and thus cause a new Intent to be created & new event to be emitted. --- src/appservice/Appservice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 27ee5bbb..dc2a8ec2 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -451,8 +451,8 @@ export class Appservice extends EventEmitter { let intent: Intent = this.intentsCache.get(userId); if (!intent) { intent = new Intent(this.options, userId, this); - this.emit("intent.new", intent); this.intentsCache.set(userId, intent); + this.emit("intent.new", intent); if (this.options.intentOptions.encryption) { intent.enableEncryption().catch(e => { LogService.error("Appservice", `Failed to set up crypto on intent ${userId}`, e); From a5dcf62b646592ef0d9be4292fd06504d8a0b40e Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 27 Sep 2023 15:39:16 -0400 Subject: [PATCH 066/123] Add test --- test/appservice/AppserviceTest.ts | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/appservice/AppserviceTest.ts b/test/appservice/AppserviceTest.ts index b6d1506c..ab85b590 100644 --- a/test/appservice/AppserviceTest.ts +++ b/test/appservice/AppserviceTest.ts @@ -385,6 +385,47 @@ describe('Appservice', () => { expect(intent.userId).toEqual(userId); }); + it('should emit an event for a created intent', async () => { + const appservice = new Appservice({ + port: 0, + bindAddress: '', + homeserverName: 'example.org', + homeserverUrl: 'https://localhost', + registration: { + as_token: "", + hs_token: "", + sender_localpart: "_bot_", + namespaces: { + users: [{ exclusive: true, regex: "@_prefix_.*:.+" }], + rooms: [], + aliases: [], + }, + }, + }); + + let newIntent: Intent | undefined; + const intentSpy = simple.stub().callFn(intent => { + expect(intent).toBeInstanceOf(Intent); + newIntent = intent; + const sameIntent = appservice.getIntentForUserId(newIntent.userId); + expect(newIntent).toBe(sameIntent); + }); + appservice.on("intent.new", intentSpy); + + [ + "@alice:example.org", + "@_prefix_testing:example.org", + "@_bot_:example.org", + "@test_prefix_:example.org", + ].forEach((userId, index) => { + const intent = appservice.getIntentForUserId(userId); + expect(intentSpy.callCount).toBe(index+1); + expect(intent).toBeDefined(); + expect(intent.userId).toEqual(userId); + expect(intent).toBe(newIntent); + }); + }); + it('should return a user ID for any namespaced localpart', async () => { const appservice = new Appservice({ port: 0, From 686a499e118eda4ab560777fbe89e35753265e84 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 22 Nov 2023 14:06:15 +0000 Subject: [PATCH 067/123] Add getEventNearestToTimestamp --- src/MatrixClient.ts | 12 ++++++++++++ src/SynapseAdminApis.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index f9ca9e44..86f740a3 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -996,6 +996,18 @@ export class MatrixClient extends EventEmitter { }; } + /** + * Get the nearest event to a given timestamp, either forwards or backwards. + * @param roomId The room ID to get the context in. + * @param ts The event ID to get the context of. + * @param dir The maximum number of events to return on either side of the event. + * @returns The ID and origin server timestamp of the event. + */ + @timedMatrixClientFunctionCall() + public async getEventNearestToTimestamp(roomId: string, ts: number, dir: "f"|"b"): Promise<{event_id: string, origin_server_ts: number}> { + return await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/timestamp_to_event", { ts, dir }); + } + /** * Gets the profile for a given user * @param {string} userId the user ID to lookup diff --git a/src/SynapseAdminApis.ts b/src/SynapseAdminApis.ts index 84bcdecd..fc4b8ebd 100644 --- a/src/SynapseAdminApis.ts +++ b/src/SynapseAdminApis.ts @@ -479,4 +479,16 @@ export class SynapseAdminApis { public async makeRoomAdmin(roomId: string, userId?: string): Promise { return this.client.doRequest("POST", `/_synapse/admin/v1/rooms/${encodeURIComponent(roomId)}/make_room_admin`, {}, { user_id: userId }); } + + /** + * Get the nearest event to a given timestamp, either forwards or backwards. You do not + * need to be joined to the room to retrieve this information. + * @param roomId The room ID to get the context in. + * @param ts The event ID to get the context of. + * @param dir The maximum number of events to return on either side of the event. + * @returns The ID and origin server timestamp of the event. + */ + public async getEventNearestToTimestamp(roomId: string, ts: number, dir: "f"|"b"): Promise<{event_id: string, origin_server_ts: number}> { + return await this.client.doRequest("GET", "/_synapse/admin/v1/rooms/" + encodeURIComponent(roomId) + "/timestamp_to_event", { ts, dir }); + } } From 9c6835e03a6d7fdb5508bea853b8d3e7fc25bfd5 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 22 Nov 2023 14:06:18 +0000 Subject: [PATCH 068/123] Add tests for getEventNearestToTimestamp --- test/MatrixClientTest.ts | 28 ++++++++++++++++++++++++++++ test/SynapseAdminApisTest.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 6abdf93e..a8c4a373 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -2759,6 +2759,34 @@ describe('MatrixClient', () => { }); }); + describe('getEventNearestToTimestamp', () => { + it('should use the right endpoint', async () => { + const { client, http, hsUrl } = createTestClient(); + const roomId = "!abc123:example.org"; + const dir = "f"; + const timestamp = 1234; + + const eventId = "$def456:example.org"; + const originServerTs = 4567; + + http.when("GET", "/_matrix/client/v3/rooms").respond(200, (path, _content, req) => { + expect(path).toEqual(`${hsUrl}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); + expect(req.queryParams['dir']).toEqual(dir); + expect(req.queryParams['ts']).toEqual(timestamp.toString()); + + return { + event_id: eventId, + origin_server_ts: originServerTs, + }; + }); + + const [result] = await Promise.all([client.getEventNearestToTimestamp(roomId, timestamp, dir), http.flushAllExpected()]); + expect(result).toBeDefined(); + expect(result.event_id).toEqual(eventId); + expect(result.origin_server_ts).toMatchObject(originServerTs); + }); + }); + describe('getUserProfile', () => { it('should call the right endpoint', async () => { const { client, http, hsUrl } = createTestClient(); diff --git a/test/SynapseAdminApisTest.ts b/test/SynapseAdminApisTest.ts index b9d9f5f6..b0e62cf1 100644 --- a/test/SynapseAdminApisTest.ts +++ b/test/SynapseAdminApisTest.ts @@ -531,5 +531,33 @@ describe('SynapseAdminApis', () => { await Promise.all([client.makeRoomAdmin(roomId, userId), http.flushAllExpected()]); }); }); + + describe('getEventNearestToTimestamp', () => { + it('should use the right endpoint', async () => { + const { client, http, hsUrl } = createTestSynapseAdminClient(); + const roomId = "!abc123:example.org"; + const dir = "f"; + const timestamp = 1234; + + const eventId = "$def456:example.org"; + const originServerTs = 4567; + + http.when("GET", "/_synapse/admin/v1/rooms").respond(200, (path, _content, req) => { + expect(path).toEqual(`${hsUrl}/_synapse/admin/v1/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); + expect(req.queryParams['dir']).toEqual(dir); + expect(req.queryParams['ts']).toEqual(timestamp.toString()); + + return { + event_id: eventId, + origin_server_ts: originServerTs, + }; + }); + + const [result] = await Promise.all([client.getEventNearestToTimestamp(roomId, timestamp, dir), http.flushAllExpected()]); + expect(result).toBeDefined(); + expect(result.event_id).toEqual(eventId); + expect(result.origin_server_ts).toMatchObject(originServerTs); + }); + }); }); }); From 06e66df6d3a6b9011de6df3ae5be10299683ffc6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 22 Nov 2023 14:19:32 +0000 Subject: [PATCH 069/123] v1 --- src/MatrixClient.ts | 2 +- test/MatrixClientTest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 86f740a3..808377bd 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1005,7 +1005,7 @@ export class MatrixClient extends EventEmitter { */ @timedMatrixClientFunctionCall() public async getEventNearestToTimestamp(roomId: string, ts: number, dir: "f"|"b"): Promise<{event_id: string, origin_server_ts: number}> { - return await this.doRequest("GET", "/_matrix/client/v3/rooms/" + encodeURIComponent(roomId) + "/timestamp_to_event", { ts, dir }); + return await this.doRequest("GET", "/_matrix/client/v1/rooms/" + encodeURIComponent(roomId) + "/timestamp_to_event", { ts, dir }); } /** diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index a8c4a373..53b419b6 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -2770,7 +2770,7 @@ describe('MatrixClient', () => { const originServerTs = 4567; http.when("GET", "/_matrix/client/v3/rooms").respond(200, (path, _content, req) => { - expect(path).toEqual(`${hsUrl}/_matrix/client/v3/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); + expect(path).toEqual(`${hsUrl}/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); expect(req.queryParams['dir']).toEqual(dir); expect(req.queryParams['ts']).toEqual(timestamp.toString()); From cf050597c17438761d5e0c9b30e78cb8d1b250b6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 23 Nov 2023 12:49:01 +0000 Subject: [PATCH 070/123] Fix test --- test/MatrixClientTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 53b419b6..cadf7fba 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -2772,7 +2772,7 @@ describe('MatrixClient', () => { http.when("GET", "/_matrix/client/v3/rooms").respond(200, (path, _content, req) => { expect(path).toEqual(`${hsUrl}/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); expect(req.queryParams['dir']).toEqual(dir); - expect(req.queryParams['ts']).toEqual(timestamp.toString()); + expect(req.queryParams['ts']).toEqual(timestamp); return { event_id: eventId, From 703e625dc783cb2d738c08dd0ff0690b7f82c546 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 23 Nov 2023 16:42:09 +0000 Subject: [PATCH 071/123] Fix tests --- test/MatrixClientTest.ts | 4 ++-- test/SynapseAdminApisTest.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index cadf7fba..c71e7d20 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -2769,7 +2769,7 @@ describe('MatrixClient', () => { const eventId = "$def456:example.org"; const originServerTs = 4567; - http.when("GET", "/_matrix/client/v3/rooms").respond(200, (path, _content, req) => { + http.when("GET", "/_matrix/client/v1/rooms").respond(200, (path, _content, req) => { expect(path).toEqual(`${hsUrl}/_matrix/client/v1/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); expect(req.queryParams['dir']).toEqual(dir); expect(req.queryParams['ts']).toEqual(timestamp); @@ -2783,7 +2783,7 @@ describe('MatrixClient', () => { const [result] = await Promise.all([client.getEventNearestToTimestamp(roomId, timestamp, dir), http.flushAllExpected()]); expect(result).toBeDefined(); expect(result.event_id).toEqual(eventId); - expect(result.origin_server_ts).toMatchObject(originServerTs); + expect(result.origin_server_ts).toEqual(originServerTs); }); }); diff --git a/test/SynapseAdminApisTest.ts b/test/SynapseAdminApisTest.ts index b0e62cf1..1dcd8541 100644 --- a/test/SynapseAdminApisTest.ts +++ b/test/SynapseAdminApisTest.ts @@ -545,7 +545,7 @@ describe('SynapseAdminApis', () => { http.when("GET", "/_synapse/admin/v1/rooms").respond(200, (path, _content, req) => { expect(path).toEqual(`${hsUrl}/_synapse/admin/v1/rooms/${encodeURIComponent(roomId)}/timestamp_to_event`); expect(req.queryParams['dir']).toEqual(dir); - expect(req.queryParams['ts']).toEqual(timestamp.toString()); + expect(req.queryParams['ts']).toEqual(timestamp); return { event_id: eventId, @@ -556,7 +556,7 @@ describe('SynapseAdminApis', () => { const [result] = await Promise.all([client.getEventNearestToTimestamp(roomId, timestamp, dir), http.flushAllExpected()]); expect(result).toBeDefined(); expect(result.event_id).toEqual(eventId); - expect(result.origin_server_ts).toMatchObject(originServerTs); + expect(result.origin_server_ts).toEqual(originServerTs); }); }); }); From 471842d4f1571284f77529889d5c3ea283fe06e7 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 18:15:11 +0000 Subject: [PATCH 072/123] Batch tracked users setup --- src/e2ee/RustEngine.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 8239b79c..03f5afaf 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -33,6 +33,9 @@ export const SYNC_LOCK_NAME = "sync"; export class RustEngine { public readonly lock = new AsyncLock(); + public readonly trackedUsersToAdd = new Set(); + public addTrackedUsersPromise: Promise|undefined; + private keyBackupVersion: KeyBackupVersion|undefined; private keyBackupWaiter = Promise.resolve(); @@ -80,8 +83,18 @@ export class RustEngine { } public async addTrackedUsers(userIds: string[]) { - await this.lock.acquire(SYNC_LOCK_NAME, async () => { - const uids = userIds.map(u => new UserId(u)); + // Add the new set of users to the pool + userIds.forEach(uId => this.trackedUsersToAdd.add(uId)); + if (this.addTrackedUsersPromise) { + // If we have a pending promise, don't create another lock requirement. + return; + } + return this.addTrackedUsersPromise = this.lock.acquire(SYNC_LOCK_NAME, async () => { + // Immediately clear this promise so that a new promise is queued up. + this.addTrackedUsersPromise = undefined; + const uids = [...this.trackedUsersToAdd].map(u => new UserId(u)); + // Clear the existing pool + this.trackedUsersToAdd.clear(); await this.machine.updateTrackedUsers(uids); const keysClaim = await this.machine.getMissingSessions(uids); From a8a9242fd9b2331fac2b916ad13865fa24b9b394 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 10:47:48 +0000 Subject: [PATCH 073/123] Ensure we cache a room when it's not encrypted. --- src/e2ee/RoomTracker.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/e2ee/RoomTracker.ts b/src/e2ee/RoomTracker.ts index 98ed5bda..352f4bca 100644 --- a/src/e2ee/RoomTracker.ts +++ b/src/e2ee/RoomTracker.ts @@ -1,4 +1,5 @@ import { MatrixClient } from "../MatrixClient"; +import { MatrixError } from "../models/MatrixError"; import { EncryptionEventContent } from "../models/events/EncryptionEvent"; import { ICryptoRoomInformation } from "./ICryptoRoomInformation"; @@ -61,7 +62,13 @@ export class RoomTracker { encEvent = await this.client.getRoomStateEvent(roomId, "m.room.encryption", ""); encEvent.algorithm = encEvent.algorithm ?? 'UNKNOWN'; } catch (e) { - return; // failure == no encryption + if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") { + // No encryption + encEvent = {}; + } else { + // Unexpected failure, do not store. + return; + } } // Pick out the history visibility setting too From 2fdc594fef55c3996edef0597af5593356b4beaf Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 10:56:59 +0000 Subject: [PATCH 074/123] Cache rooms that are not encrypted. --- src/e2ee/RoomTracker.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/e2ee/RoomTracker.ts b/src/e2ee/RoomTracker.ts index 352f4bca..b4e635ac 100644 --- a/src/e2ee/RoomTracker.ts +++ b/src/e2ee/RoomTracker.ts @@ -63,12 +63,9 @@ export class RoomTracker { encEvent.algorithm = encEvent.algorithm ?? 'UNKNOWN'; } catch (e) { if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") { - // No encryption encEvent = {}; - } else { - // Unexpected failure, do not store. - return; } + return; // Other failures should not be cached. } // Pick out the history visibility setting too From 0ee885efb4c2229700fe12d4dbb8a69dccd568c7 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 10:57:12 +0000 Subject: [PATCH 075/123] Recheck encrypted status on each m.room.encryption --- src/e2ee/CryptoClient.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index be9d1ecf..5acec0d5 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -117,6 +117,8 @@ export class CryptoClient { if (membership.effectiveMembership !== 'join' && membership.effectiveMembership !== 'invite') return; await this.engine.addTrackedUsers([membership.membershipFor]); } else if (event['type'] === 'm.room.encryption') { + // Update encryption status of room. + await this.roomTracker.queueRoomCheck(roomId); return this.client.getRoomMembers(roomId, null, ['join', 'invite']).then( members => this.engine.addTrackedUsers(members.map(e => e.membershipFor)), e => void LogService.warn("CryptoClient", `Unable to get members of room ${roomId}`), From b924ef0bc70ce223ab9f9768fc8cb3abaf621f93 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 14:52:19 +0000 Subject: [PATCH 076/123] Don't check all joined rooms on startup --- src/MatrixClient.ts | 2 +- src/appservice/Intent.ts | 2 +- src/e2ee/CryptoClient.ts | 4 +--- src/e2ee/RoomTracker.ts | 10 ---------- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 5370a411..ddba1827 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -656,7 +656,7 @@ export class MatrixClient extends EventEmitter { if (this.crypto) { LogService.debug("MatrixClientLite", "Preparing end-to-end encryption"); - await this.crypto.prepare(this.lastJoinedRoomIds); + await this.crypto.prepare(); LogService.info("MatrixClientLite", "End-to-end encryption enabled"); } diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index eb3f3787..61a9917e 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -168,7 +168,7 @@ export class Intent { } // Now set up crypto - await this.client.crypto.prepare(await this.getJoinedRooms()); + await this.client.crypto.prepare(); this.appservice.on("room.event", (roomId, event) => { this.client.crypto.onRoomEvent(roomId, event); diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 5acec0d5..789cce29 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -68,9 +68,7 @@ export class CryptoClient { * Prepares the crypto client for usage. * @param {string[]} roomIds The room IDs the MatrixClient is joined to. */ - public async prepare(roomIds: string[]) { - await this.roomTracker.prepare(roomIds); - + public async prepare() { if (this.ready) return; // stop re-preparing here const storedDeviceId = await this.client.cryptoStore.getDeviceId(); diff --git a/src/e2ee/RoomTracker.ts b/src/e2ee/RoomTracker.ts index b4e635ac..5d6dfbb4 100644 --- a/src/e2ee/RoomTracker.ts +++ b/src/e2ee/RoomTracker.ts @@ -34,16 +34,6 @@ export class RoomTracker { } } - /** - * Prepares the room tracker to track the given rooms. - * @param {string[]} roomIds The room IDs to track. This should be the joined rooms set. - */ - public async prepare(roomIds: string[]) { - for (const roomId of roomIds) { - await this.queueRoomCheck(roomId); - } - } - /** * Queues a room check for the tracker. If the room needs an update to the store, an * update will be made. From 5c059a1f5d1d7dcc035d879367b10dc528b80458 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 24 Nov 2023 16:37:32 +0000 Subject: [PATCH 077/123] Fix test --- examples/encryption_bot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/encryption_bot.ts b/examples/encryption_bot.ts index 4dda4c56..98bae196 100644 --- a/examples/encryption_bot.ts +++ b/examples/encryption_bot.ts @@ -35,9 +35,9 @@ const worksImage = fs.readFileSync("./examples/static/it-works.png"); const client = new MatrixClient(homeserverUrl, accessToken, storage, crypto); (async function() { - let encryptedRoomId: string; + let encryptedRoomId: string|undefined = undefined; const joinedRooms = await client.getJoinedRooms(); - await client.crypto.prepare(joinedRooms); // init crypto because we're doing things before the client is started + await client.crypto.prepare(); // init crypto because we're doing things before the client is started for (const roomId of joinedRooms) { if (await client.crypto.isRoomEncrypted(roomId)) { encryptedRoomId = roomId; From bf1159e88833024c5dba0a5350a984d39f5913d9 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 27 Nov 2023 17:20:45 +0000 Subject: [PATCH 078/123] Fix tests --- src/e2ee/CryptoClient.ts | 2 -- test/encryption/CryptoClientTest.ts | 43 ++++++++++++----------------- test/encryption/KeyBackupTest.ts | 2 +- test/encryption/RoomTrackerTest.ts | 22 ++------------- 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 789cce29..110fa090 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -115,8 +115,6 @@ export class CryptoClient { if (membership.effectiveMembership !== 'join' && membership.effectiveMembership !== 'invite') return; await this.engine.addTrackedUsers([membership.membershipFor]); } else if (event['type'] === 'm.room.encryption') { - // Update encryption status of room. - await this.roomTracker.queueRoomCheck(roomId); return this.client.getRoomMembers(roomId, null, ['join', 'invite']).then( members => this.engine.addTrackedUsers(members.map(e => e.membershipFor)), e => void LogService.warn("CryptoClient", `Unable to get members of room ${roomId}`), diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index 60d7fe84..bd055fc1 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -17,7 +17,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -28,24 +28,17 @@ describe('CryptoClient', () => { describe('prepare', () => { it('should prepare the room tracker', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; - const roomIds = ["!a:example.org", "!b:example.org"]; const { client, http } = createTestClient(null, userId, cryptoStoreType); client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); - const prepareSpy = simple.stub().callFn((rids: string[]) => { - expect(rids).toBe(roomIds); - return Promise.resolve(); - }); - - (client.crypto).roomTracker.prepare = prepareSpy; // private member access - bindNullEngine(http); - await Promise.all([ - client.crypto.prepare(roomIds), + // Prepare first + await Promise.all([, + client.crypto.prepare(), http.flushAllExpected(), ]); - expect(prepareSpy.callCount).toEqual(1); + expect(client.crypto.isReady).toBe(true); })); it('should use a stored device ID', () => testCryptoStores(async (cryptoStoreType) => { @@ -59,7 +52,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); expect(whoamiSpy.callCount).toEqual(0); @@ -118,7 +111,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -138,7 +131,7 @@ describe('CryptoClient', () => { const { client } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); - // await client.crypto.prepare([]); // deliberately commented + // await client.crypto.prepare(); // deliberately commented try { await client.crypto.isRoomEncrypted("!new:example.org"); @@ -159,7 +152,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -176,7 +169,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -193,7 +186,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -210,7 +203,7 @@ describe('CryptoClient', () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -248,7 +241,7 @@ describe('CryptoClient', () => { it('should sign the object while retaining signatures without mutation', async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -305,7 +298,7 @@ describe('CryptoClient', () => { it('should fail in unencrypted rooms', async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -381,7 +374,7 @@ describe('CryptoClient', () => { it('should encrypt media', async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -467,7 +460,7 @@ describe('CryptoClient', () => { it('should be symmetrical', async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -492,7 +485,7 @@ describe('CryptoClient', () => { it('should decrypt', async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -522,7 +515,7 @@ describe('CryptoClient', () => { await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); })); diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index 186a530f..100a8d8e 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -21,7 +21,7 @@ describe('KeyBackups', () => { const prepareCrypto = async () => { bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); }; diff --git a/test/encryption/RoomTrackerTest.ts b/test/encryption/RoomTrackerTest.ts index 3c4c16a1..0968544f 100644 --- a/test/encryption/RoomTrackerTest.ts +++ b/test/encryption/RoomTrackerTest.ts @@ -44,7 +44,7 @@ describe('RoomTracker', () => { await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); (client.crypto as any).engine.addTrackedUsers = () => Promise.resolve(); @@ -72,7 +72,7 @@ describe('RoomTracker', () => { await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); bindNullEngine(http); await Promise.all([ - client.crypto.prepare([]), + client.crypto.prepare(), http.flushAllExpected(), ]); @@ -103,24 +103,6 @@ describe('RoomTracker', () => { expect(queueSpy.callCount).toEqual(1); })); - describe('prepare', () => { - it('should queue updates for rooms', async () => { - const roomIds = ["!a:example.org", "!b:example.org"]; - - const { client } = createTestClient(); - - const queueSpy = simple.stub().callFn((rid: string) => { - expect(rid).toEqual(roomIds[queueSpy.callCount - 1]); - return Promise.resolve(); - }); - - const tracker = new RoomTracker(client); - tracker.queueRoomCheck = queueSpy; - await tracker.prepare(roomIds); - expect(queueSpy.callCount).toEqual(2); - }); - }); - describe('queueRoomCheck', () => { it('should store unknown rooms', () => testCryptoStores(async (cryptoStoreType) => { const roomId = "!b:example.org"; From be459b06a94d0eea9a1409f324c53d1c448fbfc3 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 27 Nov 2023 17:24:11 +0000 Subject: [PATCH 079/123] Comma --- test/encryption/CryptoClientTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index bd055fc1..f097be90 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -34,7 +34,7 @@ describe('CryptoClient', () => { bindNullEngine(http); // Prepare first - await Promise.all([, + await Promise.all([ client.crypto.prepare(), http.flushAllExpected(), ]); From e84fee9840b88e8d61711fa029bae5ded6fbaa5f Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Nov 2023 10:06:37 +0000 Subject: [PATCH 080/123] Rewrite logic to determine correct client to decrypt event/. --- src/appservice/Appservice.ts | 105 ++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index ceee434f..2d2a90aa 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -238,6 +238,11 @@ export class Appservice extends EventEmitter { private eventProcessors: { [eventType: string]: IPreprocessor[] } = {}; private pendingTransactions = new Map>(); + /** + * A cache of intents for the purposes of decrypting rooms + */ + private cryptoClientForRoomId: LRU.LRUCache; + /** * Creates a new application service. * @param {IAppserviceOptions} options The options for the application service. @@ -256,6 +261,11 @@ export class Appservice extends EventEmitter { ttl: options.intentOptions.maxAgeMs, }); + this.cryptoClientForRoomId = new LRU.LRUCache({ + max: options.intentOptions.maxCached, + ttl: options.intentOptions.maxAgeMs, + }); + this.registration = options.registration; // If protocol is not defined, define an empty array. @@ -658,6 +668,63 @@ export class Appservice extends EventEmitter { return providedToken === this.registration.hs_token; } + private async decryptAppserivceEvent(roomId: string, encrypted: EncryptedRoomEvent): ReturnType { + const existingClient = this.cryptoClientForRoomId.get(roomId); + const decryptFn = async (client) => { + let event = (await client.crypto.decryptRoomEvent(encrypted, roomId)).raw; + event = await this.processEvent(event); + this.cryptoClientForRoomId.set(roomId, client); + // For logging purposes: show that the event was decrypted + LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); + return event; + }; + // 1. Try cached client + if (existingClient) { + try { + return await decryptFn(existingClient); + } catch (existingClientError) { + LogService.warn("Appservice", `Cached client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); + } + } + this.cryptoClientForRoomId.delete(roomId); + // 2. Try the bot client + if (this.botClient.crypto?.isReady) { + try { + return await decryptFn(existingClient); + } catch (ex) { + LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); + } + } + + const userIdsInRoom = (await this.botClient.getJoinedRoomMembers(roomId)).filter(u => this.isNamespacedUser(u)); + // 3. Try existing clients with crypto enabled. + for (const intentCacheEntry of this.intentsCache.entries()) { + const [userId, intent] = intentCacheEntry as [string, Intent]; + if (!userIdsInRoom.includes(userId)) { + // Not in this room. + continue; + } + // Is this client crypto enabled? + if (!intent.underlyingClient.crypto?.isReady) { + continue; + } + try { + return await decryptFn(existingClient); + } catch (existingClientError) { + LogService.warn("Appservice", `Existing encrypted client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); + } + } + + // 4. Try to enable crypto on any client to decrypt it. + const userInRoom = this.intentsCache.find((_intent, userId) => userIdsInRoom.includes(userId)); + await userInRoom.enableEncryption(); + try { + return await decryptFn(existingClient); + } catch (existingClientError) { + throw new Error("Unable to decrypt event", { cause: existingClient }); + } + } + private async handleTransaction(txnId: string, body: Record) { // Process all the crypto stuff first to ensure that future transactions (if not this one) // will decrypt successfully. We start with EDUs because we need structures to put counts @@ -804,39 +871,11 @@ export class Appservice extends EventEmitter { try { const encrypted = new EncryptedRoomEvent(event); const roomId = event['room_id']; - try { - event = (await this.botClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; - event = await this.processEvent(event); - this.emit("room.decrypted_event", roomId, event); - - // For logging purposes: show that the event was decrypted - LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); - } catch (e1) { - LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${event['event_id']} - trying other intents`); - - let tryUserId: string; - try { - // TODO: This could be more efficient - const userIdsInRoom = await this.botClient.getJoinedRoomMembers(roomId); - tryUserId = userIdsInRoom.find(u => this.isNamespacedUser(u)); - } catch (e) { - LogService.error("Appservice", "Failed to get members of room - cannot decrypt message"); - } - - if (tryUserId) { - const intent = this.getIntentForUserId(tryUserId); - - event = (await intent.underlyingClient.crypto.decryptRoomEvent(encrypted, roomId)).raw; - event = await this.processEvent(event); - this.emit("room.decrypted_event", roomId, event); - - // For logging purposes: show that the event was decrypted - LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); - } else { - // noinspection ExceptionCaughtLocallyJS - throw e1; - } - } + event = await this.decryptAppserivceEvent(roomId, encrypted); + this.emit("room.decrypted_event", roomId, event); + + // For logging purposes: show that the event was decrypted + LogService.info("Appservice", `Processing decrypted event of type ${event["type"]}`); } catch (e) { LogService.error("Appservice", `Decryption error on ${event['room_id']} ${event['event_id']}`, e); this.emit("room.failed_decryption", event['room_id'], event, e); From 42fc7e8ebc519e3fef455405614bf650ba5933df Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Nov 2023 10:06:42 +0000 Subject: [PATCH 081/123] Use ES2022 for Error causes --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index b3e34a85..72a7c3c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "emitDecoratorMetadata": true, "module": "commonjs", "moduleResolution": "node", - "target": "es2020", + "target": "ES2022", "noImplicitAny": false, "sourceMap": true, "outDir": "./lib", From ed4ba9268b243b42866ffe51f3e0c3366e9aeb62 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Nov 2023 10:16:11 +0000 Subject: [PATCH 082/123] Fix release version --- tsconfig-release.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig-release.json b/tsconfig-release.json index 9c46a11a..2eef97bf 100644 --- a/tsconfig-release.json +++ b/tsconfig-release.json @@ -4,7 +4,7 @@ "emitDecoratorMetadata": true, "module": "commonjs", "moduleResolution": "node", - "target": "es2020", + "target": "es2022", "noImplicitAny": false, "sourceMap": true, "outDir": "./lib", From 15643f8187adf3a7ba26a935587cc277e6486e1c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Nov 2023 10:17:39 +0000 Subject: [PATCH 083/123] And examples too --- tsconfig-examples.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig-examples.json b/tsconfig-examples.json index f8f7df60..ffc89cbf 100644 --- a/tsconfig-examples.json +++ b/tsconfig-examples.json @@ -4,7 +4,7 @@ "emitDecoratorMetadata": true, "module": "commonjs", "moduleResolution": "node", - "target": "es2015", + "target": "es2022", "noImplicitAny": false, "sourceMap": false, "outDir": "./lib", From 81e682445abb3e3cec31a014324f652caa7609d4 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 29 Nov 2023 11:38:02 +0000 Subject: [PATCH 084/123] Fix obvious bs --- src/appservice/Appservice.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 2d2a90aa..06dc5d72 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -670,7 +670,7 @@ export class Appservice extends EventEmitter { private async decryptAppserivceEvent(roomId: string, encrypted: EncryptedRoomEvent): ReturnType { const existingClient = this.cryptoClientForRoomId.get(roomId); - const decryptFn = async (client) => { + const decryptFn = async (client: MatrixClient) => { let event = (await client.crypto.decryptRoomEvent(encrypted, roomId)).raw; event = await this.processEvent(event); this.cryptoClientForRoomId.set(roomId, client); @@ -682,7 +682,8 @@ export class Appservice extends EventEmitter { if (existingClient) { try { return await decryptFn(existingClient); - } catch (existingClientError) { + } catch (error) { + LogService.debug("Appservice", `Failed to decrypt via cached client ${await existingClient.getUserId()}`, error); LogService.warn("Appservice", `Cached client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); } } @@ -690,8 +691,9 @@ export class Appservice extends EventEmitter { // 2. Try the bot client if (this.botClient.crypto?.isReady) { try { - return await decryptFn(existingClient); - } catch (ex) { + return await decryptFn(this.botClient); + } catch (error) { + LogService.debug("Appservice", `Failed to decrypt via bot client`, error); LogService.warn("Appservice", `Bot client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); } } @@ -709,19 +711,24 @@ export class Appservice extends EventEmitter { continue; } try { - return await decryptFn(existingClient); - } catch (existingClientError) { + return await decryptFn(intent.underlyingClient); + } catch (error) { + LogService.debug("Appservice", `Failed to decrypt via ${userId}`, error); LogService.warn("Appservice", `Existing encrypted client was not able to decrypt ${roomId} ${encrypted.eventId} - trying other intents`); } } // 4. Try to enable crypto on any client to decrypt it. const userInRoom = this.intentsCache.find((_intent, userId) => userIdsInRoom.includes(userId)); + if (!userInRoom) { + throw Error('No users in room, cannot decrypt'); + } await userInRoom.enableEncryption(); try { - return await decryptFn(existingClient); - } catch (existingClientError) { - throw new Error("Unable to decrypt event", { cause: existingClient }); + return await decryptFn(userInRoom.underlyingClient); + } catch (error) { + LogService.debug("Appservice", `Failed to decrypt via random user ${userInRoom.userId}`, error); + throw new Error("Unable to decrypt event", { cause: error }); } } From 7e050c7336a62cbad51353d82b1d75dac7828c29 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 29 Nov 2023 16:07:40 +0000 Subject: [PATCH 085/123] Fix branch --- src/e2ee/RoomTracker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/e2ee/RoomTracker.ts b/src/e2ee/RoomTracker.ts index 5d6dfbb4..9b3c0e82 100644 --- a/src/e2ee/RoomTracker.ts +++ b/src/e2ee/RoomTracker.ts @@ -54,8 +54,9 @@ export class RoomTracker { } catch (e) { if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") { encEvent = {}; + } else { + return; // Other failures should not be cached. } - return; // Other failures should not be cached. } // Pick out the history visibility setting too From f656aba3c61f2ba81faff8c07926c62b4b2c209d Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:36:00 +0000 Subject: [PATCH 086/123] Save an iteration --- src/e2ee/RustEngine.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 03f5afaf..60e18843 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -92,7 +92,11 @@ export class RustEngine { return this.addTrackedUsersPromise = this.lock.acquire(SYNC_LOCK_NAME, async () => { // Immediately clear this promise so that a new promise is queued up. this.addTrackedUsersPromise = undefined; - const uids = [...this.trackedUsersToAdd].map(u => new UserId(u)); + const uids = Array(this.trackedUsersToAdd.size); + let idx = 0; + for (const u of this.trackedUsersToAdd.values()) { + uids[idx++] = new UserId(u); + } // Clear the existing pool this.trackedUsersToAdd.clear(); await this.machine.updateTrackedUsers(uids); From e95019df733c10f7a1e30c1f9ba310ea6254b93b Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:36:12 +0000 Subject: [PATCH 087/123] Add new --- src/e2ee/RustEngine.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/e2ee/RustEngine.ts b/src/e2ee/RustEngine.ts index 60e18843..d9466f78 100644 --- a/src/e2ee/RustEngine.ts +++ b/src/e2ee/RustEngine.ts @@ -92,7 +92,7 @@ export class RustEngine { return this.addTrackedUsersPromise = this.lock.acquire(SYNC_LOCK_NAME, async () => { // Immediately clear this promise so that a new promise is queued up. this.addTrackedUsersPromise = undefined; - const uids = Array(this.trackedUsersToAdd.size); + const uids = new Array(this.trackedUsersToAdd.size); let idx = 0; for (const u of this.trackedUsersToAdd.values()) { uids[idx++] = new UserId(u); From 5a0dd1e92cd5df29be516be33a93d4ff9f916455 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:38:39 +0000 Subject: [PATCH 088/123] Ensure we check if the room is encrypted first. --- src/appservice/Appservice.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 06dc5d72..79a68d0d 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -671,6 +671,10 @@ export class Appservice extends EventEmitter { private async decryptAppserivceEvent(roomId: string, encrypted: EncryptedRoomEvent): ReturnType { const existingClient = this.cryptoClientForRoomId.get(roomId); const decryptFn = async (client: MatrixClient) => { + // Also fetches state in order to decrypt room. We should throw if the client is confused. + if (!await client.crypto.isRoomEncrypted(roomId)) { + throw new Error("Client detected that the room is not encrypted.") + } let event = (await client.crypto.decryptRoomEvent(encrypted, roomId)).raw; event = await this.processEvent(event); this.cryptoClientForRoomId.set(roomId, client); From c852f25e663a5f9bc25636b2b075366563689713 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:42:02 +0000 Subject: [PATCH 089/123] Tidy up --- src/appservice/Appservice.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index 79a68d0d..ea35843b 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -668,7 +668,7 @@ export class Appservice extends EventEmitter { return providedToken === this.registration.hs_token; } - private async decryptAppserivceEvent(roomId: string, encrypted: EncryptedRoomEvent): ReturnType { + private async decryptAppserviceEvent(roomId: string, encrypted: EncryptedRoomEvent): ReturnType { const existingClient = this.cryptoClientForRoomId.get(roomId); const decryptFn = async (client: MatrixClient) => { // Also fetches state in order to decrypt room. We should throw if the client is confused. @@ -723,12 +723,13 @@ export class Appservice extends EventEmitter { } // 4. Try to enable crypto on any client to decrypt it. - const userInRoom = this.intentsCache.find((_intent, userId) => userIdsInRoom.includes(userId)); + // We deliberately do not enable crypto on every client for performance reasons. + const userInRoom = this.intentsCache.find((intent, userId) => !intent.underlyingClient.crypto?.isReady && userIdsInRoom.includes(userId)); if (!userInRoom) { throw Error('No users in room, cannot decrypt'); } - await userInRoom.enableEncryption(); try { + await userInRoom.enableEncryption(); return await decryptFn(userInRoom.underlyingClient); } catch (error) { LogService.debug("Appservice", `Failed to decrypt via random user ${userInRoom.userId}`, error); @@ -882,7 +883,7 @@ export class Appservice extends EventEmitter { try { const encrypted = new EncryptedRoomEvent(event); const roomId = event['room_id']; - event = await this.decryptAppserivceEvent(roomId, encrypted); + event = await this.decryptAppserviceEvent(roomId, encrypted); this.emit("room.decrypted_event", roomId, event); // For logging purposes: show that the event was decrypted From 93441476262b0faddd438030d4dd36e7f0da5394 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:52:26 +0000 Subject: [PATCH 090/123] Lint --- src/appservice/Appservice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index ea35843b..fa162818 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -673,7 +673,7 @@ export class Appservice extends EventEmitter { const decryptFn = async (client: MatrixClient) => { // Also fetches state in order to decrypt room. We should throw if the client is confused. if (!await client.crypto.isRoomEncrypted(roomId)) { - throw new Error("Client detected that the room is not encrypted.") + throw new Error("Client detected that the room is not encrypted."); } let event = (await client.crypto.decryptRoomEvent(encrypted, roomId)).raw; event = await this.processEvent(event); From db39b0a016755a26262ac1755b833b3890120a68 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 11:18:08 +0000 Subject: [PATCH 091/123] 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2a7ce49e..9d4ce93d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "develop", + "version": "0.7.0", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 124a70c68680da903270ce219a5ad49de886bc93 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 12:50:11 +0000 Subject: [PATCH 092/123] Allow specifing a specific device ID for impersonation. --- src/appservice/Intent.ts | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 61a9917e..318e73ca 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -97,10 +97,12 @@ export class Intent { /** * Sets up crypto on the client if it hasn't already been set up. + * @param providedDeviceId Optional device ID. If given, this will used instead of trying to + * masquerade as the first non-key enabled device. * @returns {Promise} Resolves when complete. */ @timedIntentFunctionCall() - public async enableEncryption(): Promise { + public async enableEncryption(providedDeviceId?: string): Promise { if (!this.cryptoSetupPromise) { // eslint-disable-next-line no-async-promise-executor this.cryptoSetupPromise = new Promise(async (resolve, reject) => { @@ -116,23 +118,32 @@ export class Intent { throw new Error("Failed to create crypto store"); } - // Try to impersonate a device ID - const ownDevices = await this.client.getOwnDevices(); let deviceId = await cryptoStore.getDeviceId(); - if (!deviceId || !ownDevices.some(d => d.device_id === deviceId)) { - const deviceKeys = await this.client.getUserDevices([this.userId]); - const userDeviceKeys = deviceKeys.device_keys[this.userId]; - if (userDeviceKeys) { - // We really should be validating signatures here, but we're actively looking - // for devices without keys to impersonate, so it should be fine. In theory, - // those devices won't even be present but we're cautious. - const devicesWithKeys = Array.from(Object.entries(userDeviceKeys)) - .filter(d => d[0] === d[1].device_id && !!d[1].keys?.[`${DeviceKeyAlgorithm.Curve25519}:${d[1].device_id}`]) - .map(t => t[0]); // grab device ID from tuple - deviceId = ownDevices.find(d => !devicesWithKeys.includes(d.device_id))?.device_id; + if (!providedDeviceId) { + // Try to impersonate a device ID + const ownDevices = await this.client.getOwnDevices(); + let deviceId = await cryptoStore.getDeviceId(); + if (!deviceId || !ownDevices.some(d => d.device_id === deviceId)) { + const deviceKeys = await this.client.getUserDevices([this.userId]); + const userDeviceKeys = deviceKeys.device_keys[this.userId]; + if (userDeviceKeys) { + // We really should be validating signatures here, but we're actively looking + // for devices without keys to impersonate, so it should be fine. In theory, + // those devices won't even be present but we're cautious. + const devicesWithKeys = Array.from(Object.entries(userDeviceKeys)) + .filter(d => d[0] === d[1].device_id && !!d[1].keys?.[`${DeviceKeyAlgorithm.Curve25519}:${d[1].device_id}`]) + .map(t => t[0]); // grab device ID from tuple + deviceId = ownDevices.find(d => !devicesWithKeys.includes(d.device_id))?.device_id; + } } + } else { + if (deviceId && deviceId !== providedDeviceId) { + throw Error(`Storage already configured with an existing device ${deviceId}, cannot use provided device ${providedDeviceId}`); + } + deviceId = providedDeviceId; } let prepared = false; + if (deviceId) { this.makeClient(true); this.client.impersonateUserId(this.userId, deviceId); From 6ab2f498af96bf313a76c1313c7ebc20f295fb31 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:08:31 +0000 Subject: [PATCH 093/123] Warn if the device changes./ --- src/appservice/Intent.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 318e73ca..a5beeef9 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -145,6 +145,11 @@ export class Intent { let prepared = false; if (deviceId) { + const cryptoStore = this.cryptoStorage?.storageForUser(this.userId); + const existingDeviceId = await cryptoStore.getDeviceId(); + if (existingDeviceId && existingDeviceId !== deviceId) { + LogService.warn("Intent", `Device ID has changed for user ${this.userId} from ${existingDeviceId} to ${deviceId}`); + } this.makeClient(true); this.client.impersonateUserId(this.userId, deviceId); From 9014b6b0ea0c2bd954489444b3b403e998f0a331 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:15:26 +0000 Subject: [PATCH 094/123] Clear storage if the device changes. --- src/e2ee/CryptoClient.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 110fa090..2ef4ace1 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -27,6 +27,7 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; +import { rm } from "fs/promises"; /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -72,10 +73,14 @@ export class CryptoClient { if (this.ready) return; // stop re-preparing here const storedDeviceId = await this.client.cryptoStore.getDeviceId(); - if (storedDeviceId) { + const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); + if (storedDeviceId === deviceId) { this.deviceId = storedDeviceId; + } else if (storedDeviceId && storedDeviceId !== deviceId) { + LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); + // Clear storage for old device. + await rm(this.storage.storagePath, { recursive: true }); } else { - const deviceId = (await this.client.getWhoAmI())['device_id']; if (!deviceId) { throw new Error("Encryption not possible: server not revealing device ID"); } @@ -86,7 +91,7 @@ export class CryptoClient { LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); const machine = await OlmMachine.initialize( - new UserId(await this.client.getUserId()), + new UserId(userId), new DeviceId(this.deviceId), this.storage.storagePath, "", this.storage.storageType, From 277d3d8ce4de5e2059c43dee0b7611ceb15d403e Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:17:11 +0000 Subject: [PATCH 095/123] Cleanup --- src/e2ee/CryptoClient.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 2ef4ace1..363349c1 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -74,21 +74,21 @@ export class CryptoClient { const storedDeviceId = await this.client.cryptoStore.getDeviceId(); const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); - if (storedDeviceId === deviceId) { - this.deviceId = storedDeviceId; - } else if (storedDeviceId && storedDeviceId !== deviceId) { + + if (!deviceId) { + throw new Error("Encryption not possible: server not revealing device ID"); + } + + if (storedDeviceId && storedDeviceId !== deviceId) { LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); // Clear storage for old device. await rm(this.storage.storagePath, { recursive: true }); - } else { - if (!deviceId) { - throw new Error("Encryption not possible: server not revealing device ID"); - } - this.deviceId = deviceId; - await this.client.cryptoStore.setDeviceId(this.deviceId); } - LogService.debug("CryptoClient", "Starting with device ID:", this.deviceId); + this.deviceId = deviceId; + await this.client.cryptoStore.setDeviceId(this.deviceId); + + LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); const machine = await OlmMachine.initialize( new UserId(userId), From 78d48dc562909db0f6e192229ffc4ac453f20db0 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:18:03 +0000 Subject: [PATCH 096/123] Lint --- src/e2ee/CryptoClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 363349c1..39be4713 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -7,6 +7,7 @@ import { Attachment, EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; +import { rm } from "fs/promises"; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -27,7 +28,6 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; -import { rm } from "fs/promises"; /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -73,7 +73,7 @@ export class CryptoClient { if (this.ready) return; // stop re-preparing here const storedDeviceId = await this.client.cryptoStore.getDeviceId(); - const { user_id: userId, device_id: deviceId} = (await this.client.getWhoAmI()); + const { user_id: userId, device_id: deviceId } = (await this.client.getWhoAmI()); if (!deviceId) { throw new Error("Encryption not possible: server not revealing device ID"); From e25be55b62d873e28bb7887694ec7378204e3fef Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 13:22:20 +0000 Subject: [PATCH 097/123] Warn properly --- src/appservice/Intent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index a5beeef9..38c7d1ce 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -138,7 +138,7 @@ export class Intent { } } else { if (deviceId && deviceId !== providedDeviceId) { - throw Error(`Storage already configured with an existing device ${deviceId}, cannot use provided device ${providedDeviceId}`); + LogService.warn(`Storage already configured with an existing device ${deviceId}. Old storage will be cleared.`); } deviceId = providedDeviceId; } From e5dcc5a698b26a9e47df6e34abf48ae88b8c3abd Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:09:44 +0000 Subject: [PATCH 098/123] Make tests happy --- src/e2ee/CryptoClient.ts | 14 ++++++++++++-- test/TestUtils.ts | 5 ++++- test/encryption/CryptoClientTest.ts | 10 ++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 39be4713..f229cfb7 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -28,6 +28,7 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; +import path = require("path"); /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -82,11 +83,20 @@ export class CryptoClient { if (storedDeviceId && storedDeviceId !== deviceId) { LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); // Clear storage for old device. - await rm(this.storage.storagePath, { recursive: true }); + try { + await rm(path.join(this.storage.storagePath, "matrix-sdk-crypto.sqlite3")); + } catch (ex) { + if (ex.code !== 'ENOENT') { + throw ex; + } + } } + if (storedDeviceId !== deviceId) { + this.client.cryptoStore.setDeviceId(deviceId); + } this.deviceId = deviceId; - await this.client.cryptoStore.setDeviceId(this.deviceId); + LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); diff --git a/test/TestUtils.ts b/test/TestUtils.ts index f340e63e..02730902 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,7 +1,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; - +import * as simple from "simple-mock"; import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -44,6 +44,9 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); + // Ensure we always respond to a well-known + client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + return { http, hsUrl, accessToken, client }; } diff --git a/test/encryption/CryptoClientTest.ts b/test/encryption/CryptoClientTest.ts index f097be90..e5d17322 100644 --- a/test/encryption/CryptoClientTest.ts +++ b/test/encryption/CryptoClientTest.ts @@ -8,7 +8,6 @@ describe('CryptoClient', () => { it('should not have a device ID or be ready until prepared', () => testCryptoStores(async (cryptoStoreType) => { const userId = "@alice:example.org"; const { client, http } = createTestClient(null, userId, cryptoStoreType); - client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); expect(client.crypto).toBeDefined(); @@ -46,8 +45,9 @@ describe('CryptoClient', () => { const { client, http } = createTestClient(null, userId, cryptoStoreType); await client.cryptoStore.setDeviceId(TEST_DEVICE_ID); + const CORRECT_DEVICE = "new_device"; - const whoamiSpy = simple.stub().callFn(() => Promise.resolve({ user_id: userId, device_id: "wrong" })); + const whoamiSpy = simple.stub().callFn(() => Promise.resolve({ user_id: userId, device_id: CORRECT_DEVICE })); client.getWhoAmI = whoamiSpy; bindNullEngine(http); @@ -55,8 +55,10 @@ describe('CryptoClient', () => { client.crypto.prepare(), http.flushAllExpected(), ]); - expect(whoamiSpy.callCount).toEqual(0); - expect(client.crypto.clientDeviceId).toEqual(TEST_DEVICE_ID); + // This should be called to check + expect(whoamiSpy.callCount).toEqual(1); + expect(client.crypto.clientDeviceId).toEqual(CORRECT_DEVICE); + expect(await client.cryptoStore.getDeviceId()).toEqual(CORRECT_DEVICE); })); }); From 34f86955c1c2a079bfc14c5982d706521c4b8a60 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:16:39 +0000 Subject: [PATCH 099/123] Tidy more --- src/e2ee/CryptoClient.ts | 3 +-- test/MatrixClientTest.ts | 4 ++-- test/TestUtils.ts | 9 ++++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index f229cfb7..3483ba0f 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -8,6 +8,7 @@ import { EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; import { rm } from "fs/promises"; +import * as path from 'path'; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -28,7 +29,6 @@ import { RustSdkCryptoStorageProvider } from "../storage/RustSdkCryptoStoragePro import { RustEngine, SYNC_LOCK_NAME } from "./RustEngine"; import { MembershipEvent } from "../models/events/MembershipEvent"; import { IKeyBackupInfoRetrieved } from "../models/KeyBackup"; -import path = require("path"); /** * Manages encryption for a MatrixClient. Get an instance from a MatrixClient directly @@ -97,7 +97,6 @@ export class CryptoClient { } this.deviceId = deviceId; - LogService.debug("CryptoClient", `Starting ${userId} with device ID:`, this.deviceId); const machine = await OlmMachine.initialize( diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 33343809..99c3c404 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -1043,7 +1043,7 @@ describe('MatrixClient', () => { }); it('should request the user ID if it is not known', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: false }); const userId = "@example:example.org"; const response = { @@ -1061,7 +1061,7 @@ describe('MatrixClient', () => { describe('getWhoAmI', () => { it('should call the right endpoint', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: false }); const response = { user_id: "@user:example.org", diff --git a/test/TestUtils.ts b/test/TestUtils.ts index 02730902..e8d2cfa3 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -1,7 +1,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import * as simple from "simple-mock"; + import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -31,6 +31,7 @@ export function createTestClient( storage: IStorageProvider = null, userId: string = null, cryptoStoreType?: StoreType, + opts = { handleWhoAmI: true }, ): { client: MatrixClient; http: HttpBackend; @@ -44,8 +45,10 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); - // Ensure we always respond to a well-known - client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + if (opts.handleWhoAmI) { + // Ensure we always respond to a whoami + client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID }); + } return { http, hsUrl, accessToken, client }; } From 959e603986ee08e47e64702f127ec62cd5b1c23c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 19 Dec 2023 18:26:36 +0000 Subject: [PATCH 100/123] Fix another test --- test/SynapseAdminApisTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/SynapseAdminApisTest.ts b/test/SynapseAdminApisTest.ts index 1dcd8541..ee3b266a 100644 --- a/test/SynapseAdminApisTest.ts +++ b/test/SynapseAdminApisTest.ts @@ -24,7 +24,7 @@ export function createTestSynapseAdminClient( hsUrl: string; accessToken: string; } { - const result = createTestClient(storage); + const result = createTestClient(storage, undefined, undefined, { handleWhoAmI: false }); const mxClient = result.client; const client = new SynapseAdminApis(mxClient); From 3d82d8315693af7414b9f870d654d84d3dc9388d Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:21:14 +0000 Subject: [PATCH 101/123] Refactor to instead create a new storage per device, rather than deleting old storage. --- src/e2ee/CryptoClient.ts | 16 ++--------- src/storage/IAppserviceStorageProvider.ts | 4 +-- src/storage/RustSdkCryptoStorageProvider.ts | 30 +++++++++++++++++++-- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index 3483ba0f..be8b789d 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -7,8 +7,6 @@ import { Attachment, EncryptedAttachment, } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { rm } from "fs/promises"; -import * as path from 'path'; import { MatrixClient } from "../MatrixClient"; import { LogService } from "../logging/LogService"; @@ -80,17 +78,7 @@ export class CryptoClient { throw new Error("Encryption not possible: server not revealing device ID"); } - if (storedDeviceId && storedDeviceId !== deviceId) { - LogService.warn("CryptoClient", `Device ID for ${userId} has changed from ${storedDeviceId} to ${deviceId}`); - // Clear storage for old device. - try { - await rm(path.join(this.storage.storagePath, "matrix-sdk-crypto.sqlite3")); - } catch (ex) { - if (ex.code !== 'ENOENT') { - throw ex; - } - } - } + const storagePath = await this.storage.getMachineStoragePath(deviceId); if (storedDeviceId !== deviceId) { this.client.cryptoStore.setDeviceId(deviceId); @@ -102,7 +90,7 @@ export class CryptoClient { const machine = await OlmMachine.initialize( new UserId(userId), new DeviceId(this.deviceId), - this.storage.storagePath, "", + storagePath, "", this.storage.storageType, ); this.engine = new RustEngine(machine, this.client); diff --git a/src/storage/IAppserviceStorageProvider.ts b/src/storage/IAppserviceStorageProvider.ts index da9eb2c0..aca046bb 100644 --- a/src/storage/IAppserviceStorageProvider.ts +++ b/src/storage/IAppserviceStorageProvider.ts @@ -47,8 +47,8 @@ export interface IAppserviceStorageProvider { export interface IAppserviceCryptoStorageProvider { /** * Gets a storage provider to use for the given user ID. - * @param {string} userId The user ID. - * @returns {ICryptoStorageProvider} The storage provider. + * @param userId The user ID. + * @returns The storage provider. */ storageForUser(userId: string): ICryptoStorageProvider; } diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 200845f5..f955bede 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -2,6 +2,8 @@ import * as lowdb from "lowdb"; import * as FileSync from "lowdb/adapters/FileSync"; import * as mkdirp from "mkdirp"; import * as path from "path"; +import { stat, rename, mkdir } from "fs/promises"; +import { PathLike } from "fs"; import * as sha512 from "hash.js/lib/hash/sha/512"; import * as sha256 from "hash.js/lib/hash/sha/256"; import { StoreType as RustSdkCryptoStoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; @@ -12,6 +14,10 @@ import { ICryptoRoomInformation } from "../e2ee/ICryptoRoomInformation"; export { RustSdkCryptoStoreType }; +async function doesFileExist(path: PathLike) { + return stat(path).then(() => true).catch(() => false); +} + /** * A crypto storage provider for the file-based rust-sdk store. * @category Storage providers @@ -40,6 +46,26 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { }); } + public async getMachineStoragePath(deviceId: string): Promise { + const newPath = path.join(this.storagePath, sha256().update(deviceId).digest('hex')); + if (await doesFileExist(newPath)) { + // Already exists, short circuit. + return newPath; + } // else: If the path does NOT exist we might need to perform a migration. + + const legacyFilePath = path.join(this.storagePath, 'matrix-sdk-crypto.sqlite3'); + // XXX: Slightly gross cross-dependency file name expectations. + if (await doesFileExist(legacyFilePath) === false) { + // No machine files at all, we can skip. + return newPath; + } + + // We need to move the file. + await mkdir(newPath); + await rename(legacyFilePath, path.join(newPath, 'matrix-sdk-crypto.sqlite3')); + return newPath; + } + public async getDeviceId(): Promise { return this.db.get('deviceId').value(); } @@ -75,7 +101,7 @@ export class RustSdkAppserviceCryptoStorageProvider extends RustSdkCryptoStorage public storageForUser(userId: string): ICryptoStorageProvider { // sha256 because sha512 is a bit big for some operating systems - const key = sha256().update(userId).digest('hex'); - return new RustSdkCryptoStorageProvider(path.join(this.baseStoragePath, key), this.storageType); + const storagePath = path.join(this.baseStoragePath, sha256().update(userId).digest('hex')); + return new RustSdkCryptoStorageProvider(storagePath, this.storageType); } } From 5672a5a4b74acbca3be3c8f3e027bac3656b7168 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:34:11 +0000 Subject: [PATCH 102/123] Log the migration --- src/storage/RustSdkCryptoStorageProvider.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index f955bede..9e041e2f 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -11,6 +11,7 @@ import { StoreType as RustSdkCryptoStoreType } from "@matrix-org/matrix-sdk-cryp import { ICryptoStorageProvider } from "./ICryptoStorageProvider"; import { IAppserviceCryptoStorageProvider } from "./IAppserviceStorageProvider"; import { ICryptoRoomInformation } from "../e2ee/ICryptoRoomInformation"; +import { LogService } from "../logging/LogService"; export { RustSdkCryptoStoreType }; @@ -59,10 +60,12 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { // No machine files at all, we can skip. return newPath; } - + const legacyDeviceId = await this.getDeviceId(); // We need to move the file. - await mkdir(newPath); - await rename(legacyFilePath, path.join(newPath, 'matrix-sdk-crypto.sqlite3')); + const previousDevicePath = path.join(this.storagePath, sha256().update(legacyDeviceId).digest('hex')); + LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); + await mkdir(previousDevicePath); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')); return newPath; } From 1c031efd444aba4f7d1dd0443ee4338ed7d0c808 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:36:28 +0000 Subject: [PATCH 103/123] Move all files but allow it to fail --- src/storage/RustSdkCryptoStorageProvider.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 9e041e2f..434d12bf 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -65,7 +65,16 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { const previousDevicePath = path.join(this.storagePath, sha256().update(legacyDeviceId).digest('hex')); LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); await mkdir(previousDevicePath); - await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex) + ); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-shm')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex) + ); + await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-wal')).catch((ex) => + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex) + ); + return newPath; } From 583b1cd0aecd02fb18ce0155012d482a9d3b35b5 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Dec 2023 12:37:38 +0000 Subject: [PATCH 104/123] trailing commas --- src/storage/RustSdkCryptoStorageProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/storage/RustSdkCryptoStorageProvider.ts b/src/storage/RustSdkCryptoStorageProvider.ts index 434d12bf..ce67976b 100644 --- a/src/storage/RustSdkCryptoStorageProvider.ts +++ b/src/storage/RustSdkCryptoStorageProvider.ts @@ -66,13 +66,13 @@ export class RustSdkCryptoStorageProvider implements ICryptoStorageProvider { LogService.warn("RustSdkCryptoStorageProvider", `Migrating path for SDK database for legacy device ${legacyDeviceId}`); await mkdir(previousDevicePath); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3`, ex), ); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-shm')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-shm`, ex), ); await rename(legacyFilePath, path.join(previousDevicePath, 'matrix-sdk-crypto.sqlite3-wal')).catch((ex) => - LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex) + LogService.warn("RustSdkCryptoStorageProvider", `Could not migrate matrix-sdk-crypto.sqlite3-wal`, ex), ); return newPath; From 28a69beb985092934f8af6062c95d9fd7575a4f1 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 5 Jan 2024 16:29:06 +0000 Subject: [PATCH 105/123] Update epxress to 4.17.21 Prevents type errors --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 95b66d93..2c61c6f7 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ ], "dependencies": { "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.6", - "@types/express": "^4.17.20", + "@types/express": "^4.17.21", "another-json": "^0.2.0", "async-lock": "^1.4.0", "chalk": "4", diff --git a/yarn.lock b/yarn.lock index e68b6468..4afd41f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -804,10 +804,10 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.20": - version "4.17.20" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.20.tgz#e7c9b40276d29e38a4e3564d7a3d65911e2aa433" - integrity sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw== +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" From 9e74b791c93a840ff2a91f5ab29df6447075851e Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 5 Jan 2024 16:32:45 +0000 Subject: [PATCH 106/123] 0.7.0-element.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4615a7fc..db63848d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.0", + "version": "0.7.0-element.0", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 3a0fa2616a9267941e54942488d77e901a027dcc Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 23 Jan 2024 09:37:07 +0000 Subject: [PATCH 107/123] Fix error --- src/appservice/Intent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index 38c7d1ce..44d7eb44 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -138,7 +138,7 @@ export class Intent { } } else { if (deviceId && deviceId !== providedDeviceId) { - LogService.warn(`Storage already configured with an existing device ${deviceId}. Old storage will be cleared.`); + LogService.warn("Intent", `Storage already configured with an existing device ${deviceId}. Old storage will be cleared.`); } deviceId = providedDeviceId; } From 0ff14e945ec92503412548645d8c8b0b0b52c585 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Fri, 9 Feb 2024 13:41:20 +0000 Subject: [PATCH 108/123] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index db63848d..f1168c0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.0-element.0", + "version": "0.7.0-element.1", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From a98ae4f6ed0285f32ca1175722b43c29a12bdf41 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 11 Jun 2024 16:15:58 +0100 Subject: [PATCH 109/123] Update matrix-sdk-crypto-nodejs to 0.2.0-beta.1 --- package.json | 5 +++-- yarn.lock | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f1168c0e..616c1183 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "tsconfig.json" ], "dependencies": { - "@matrix-org/matrix-sdk-crypto-nodejs": "0.1.0-beta.11", + "@matrix-org/matrix-sdk-crypto-nodejs": "0.2.0-beta.1", "@types/express": "^4.17.21", "another-json": "^0.2.0", "async-lock": "^1.4.0", @@ -110,5 +110,6 @@ "testMatch": [ "/test/**/*Test.ts" ] - } + }, + "packageManager": "yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71" } diff --git a/yarn.lock b/yarn.lock index 093247ad..ea115257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -654,10 +654,10 @@ dependencies: lodash "^4.17.21" -"@matrix-org/matrix-sdk-crypto-nodejs@0.1.0-beta.11": - version "0.1.0-beta.11" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.11.tgz#537cd7a7bbce1d9745b812a5a7ffa9a5944e146c" - integrity sha512-z5adcQo4o0UAry4zs6JHGxbTDlYTUMKUfpOpigmso65ETBDumbeTSQCWRw8UeUV7aCAyVoHARqDTol9SrauEFA== +"@matrix-org/matrix-sdk-crypto-nodejs@0.2.0-beta.1": + version "0.2.0-beta.1" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.2.0-beta.1.tgz#b696707ccfa944cfed3c96cf7e54799b0f1e3329" + integrity sha512-CgbOKORfD6dvYgQTPhfN73H1RbQknrFkMnRRwCIJMt15iL2AF1gEowgbrlGhkbG6gNng4CgPnKs1iHKCRrhvmA== dependencies: https-proxy-agent "^5.0.1" node-downloader-helper "^2.1.5" From 724e2030904a1e69d40654f3bd2b9db2449532d2 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 11 Jun 2024 17:17:46 +0100 Subject: [PATCH 110/123] Adjust URLs for backup test and timeouts --- test/TestUtils.ts | 4 ++-- test/encryption/KeyBackupTest.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/TestUtils.ts b/test/TestUtils.ts index e8d2cfa3..df748d64 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -62,7 +62,7 @@ export async function testCryptoStores(fn: (StoreType) => Promise): Promis } export function bindNullEngine(http: HttpBackend) { - http.when("POST", "/keys/upload").respond(200, (path, obj) => { + http.when("POST", "/_matrix/client/v3/keys/upload").respond(200, (path, obj) => { expect(obj).toMatchObject({ }); @@ -78,7 +78,7 @@ export function bindNullEngine(http: HttpBackend) { } export function bindNullQuery(http: HttpBackend) { - http.when("POST", "/keys/query").respond(200, (path, obj) => { + http.when("POST", "/_matrix/client/v3/keys/query").respond(200, (path, obj) => { return {}; }); } diff --git a/test/encryption/KeyBackupTest.ts b/test/encryption/KeyBackupTest.ts index 100a8d8e..210cb971 100644 --- a/test/encryption/KeyBackupTest.ts +++ b/test/encryption/KeyBackupTest.ts @@ -22,7 +22,7 @@ describe('KeyBackups', () => { bindNullEngine(http); await Promise.all([ client.crypto.prepare(), - http.flushAllExpected(), + http.flushAllExpected({ timeout: 5000 }), ]); }; @@ -81,7 +81,7 @@ describe('KeyBackups', () => { let keyBackupInfoOnServer: IKeyBackupInfoRetrieved|undefined; - http.when("POST", "/room_keys/version").respond(200, (path, obj: IKeyBackupInfo) => { + http.when("POST", "/_matrix/client/v3/room_keys/version").respond(200, (path, obj: IKeyBackupInfo) => { expect(obj.auth_data.signatures[USER_ID]).toHaveProperty(`ed25519:${TEST_DEVICE_ID}`); keyBackupInfoOnServer = { @@ -93,7 +93,7 @@ describe('KeyBackups', () => { return keyBackupInfoOnServer.version; }); - http.when("GET", "/room_keys/version").respond(200, (path, obj) => { + http.when("GET", "/_matrix/client/v3/room_keys/version").respond(200, (path, obj) => { expect(keyBackupInfoOnServer).toBeDefined(); expect(keyBackupInfoOnServer.version).toBe("1"); @@ -110,7 +110,7 @@ describe('KeyBackups', () => { })(), http.flushAllExpected(), ]); - })); + }), 10000); it('should fail to enable backups when the crypto has not been prepared', () => testCryptoStores(async (cryptoStoreType) => { try { @@ -179,7 +179,7 @@ describe('KeyBackups', () => { const encryptRoomEvent = async () => { bindNullQuery(http); - const encryptPromise = client.crypto.encryptRoomEvent(roomId, "m.room.message", "my message"); + const encryptPromise = client.crypto.encryptRoomEvent(roomId, "m.room.message", { body: "my message" }); await http.flushAllExpected({ timeout: 10000 }); // This is because encryptRoomEvent calls "/keys/query" after encrypting too. @@ -224,7 +224,7 @@ describe('KeyBackups', () => { }; const expectToPutRoomKey = () => { - http.when("PUT", "/room_keys/keys").respond(200, onBackupRequest); + http.when("PUT", "/_matrix/client/v3/room_keys/keys").respond(200, onBackupRequest); }; expectToPutRoomKey(); From 34b96a75994d37504a2fca44c15bcbbd08ddb2f0 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 12 Jun 2024 09:36:27 +0100 Subject: [PATCH 111/123] drop packageManager --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 616c1183..59864d80 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,5 @@ "testMatch": [ "/test/**/*Test.ts" ] - }, - "packageManager": "yarn@1.22.19+sha512.ff4579ab459bb25aa7c0ff75b62acebe576f6084b36aa842971cf250a5d8c6cd3bc9420b22ce63c7f93a0857bc6ef29291db39c3e7a23aab5adfd5a4dd6c5d71" + } } From c944bf40ff5eeb96df11ef7db7da0c972d56d182 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Mar 2024 10:06:55 +0000 Subject: [PATCH 112/123] Add Retry-After header parsing. --- src/http.ts | 2 +- src/models/MatrixError.ts | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/http.ts b/src/http.ts index c715405d..20a3b82b 100644 --- a/src/http.ts +++ b/src/http.ts @@ -107,7 +107,7 @@ export async function doHttpRequest( if (typeof (errBody) === "object" && 'errcode' in errBody) { const redactedBody = respIsBuffer ? '' : redactObjectForLogging(errBody); LogService.error("MatrixHttpClient", "(REQ-" + requestId + ")", redactedBody); - throw new MatrixError(errBody, response.statusCode); + throw new MatrixError(errBody, response.statusCode, response.headers); } // Don't log the body unless we're in debug mode. They can be large. diff --git a/src/models/MatrixError.ts b/src/models/MatrixError.ts index c346f020..8e814255 100644 --- a/src/models/MatrixError.ts +++ b/src/models/MatrixError.ts @@ -1,8 +1,26 @@ +import { LogService } from "../logging/LogService"; + /** * Represents an HTTP error from the Matrix server. * @category Error handling */ export class MatrixError extends Error { + /** + * Parse a Retry-After header into a number of milliseconds. + * @see https://www.rfc-editor.org/rfc/rfc9110#field.retry-after + * @param header The value of a Retry-After header. + * @throws If the date could not be parsed. + */ + static parseRetryAfterHeader(header: string): number { + // First try to parse as seconds + const retryAfterSeconds = parseInt(header, 10); + if (!Number.isNaN(retryAfterSeconds)) { + return retryAfterSeconds * 1000; + } + const retryAfterDate = new Date(header); + return retryAfterDate.getTime() - Date.now(); + } + /** * The Matrix error code */ @@ -23,11 +41,23 @@ export class MatrixError extends Error { * @param body The error body. * @param statusCode The HTTP status code. */ - constructor(public readonly body: { errcode: string, error: string, retry_after_ms?: number }, public readonly statusCode: number) { + constructor(public readonly body: { errcode: string, error: string, retry_after_ms?: number }, public readonly statusCode: number, headers: Record) { super(); this.errcode = body.errcode; this.error = body.error; - this.retryAfterMs = body.retry_after_ms; + const retryAfterHeader = headers['retry-after']; + if (this.statusCode === 429 && retryAfterHeader) { + try { + this.retryAfterMs = MatrixError.parseRetryAfterHeader(retryAfterHeader); + } catch (ex) { + // Could not parse...skip handling for now. + LogService.warn("MatrixError", "Could not parse Retry-After header from request.", ex); + } + } + // Fall back to the deprecated retry_after_ms property. + if (!this.retryAfterMs && body.retry_after_ms) { + this.retryAfterMs = body.retry_after_ms; + } } /** From 9380c2b854e9ba0a6908eaa10d19ba063a7247c6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Wed, 20 Mar 2024 10:21:07 +0000 Subject: [PATCH 113/123] Add comprehensive tests. --- test/models/MatrixErrorTest.ts | 39 ++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/models/MatrixErrorTest.ts diff --git a/test/models/MatrixErrorTest.ts b/test/models/MatrixErrorTest.ts new file mode 100644 index 00000000..adef19d5 --- /dev/null +++ b/test/models/MatrixErrorTest.ts @@ -0,0 +1,39 @@ +import { MatrixError } from "../../src"; + +describe("MatrixError", () => { + it("should construct a basic MatrixError", () => { + const err = new MatrixError({ 'errcode': 'M_TEST', "error": 'Test fail' }, 500, {}); + expect(err.message).toBe('M_TEST: Test fail'); + expect(err.retryAfterMs).toBeUndefined(); + }); + it("should handle a 429 without a retry duration", () => { + const err = new MatrixError({ 'errcode': 'M_TEST', "error": 'Test fail' }, 429, {}); + expect(err.message).toBe('M_TEST: Test fail'); + expect(err.retryAfterMs).toBeUndefined(); + }); + it("should handle a 429 with a Retry-After header (time)", () => { + // Should ignore the deprecated field. + const err = new MatrixError({ 'errcode': 'M_TEST', "error": 'Test fail', "retry_after_ms": 5 }, 429, { + 'retry-after': '10', + }); + expect(err.message).toBe('M_TEST: Test fail'); + expect(err.retryAfterMs).toEqual(10000); + }); + it("should handle a 429 with a Retry-After header (date)", () => { + jest + .spyOn(global.Date, 'now') + .mockImplementationOnce(() => new Date('Wed, 20 Mar 2024 10:18:16 UTC').valueOf()); + + // Should ignore the deprecated field. + const err = new MatrixError({ 'errcode': 'M_TEST', "error": 'Test fail', "retry_after_ms": 5 }, 429, { + 'retry-after': 'Wed, 20 Mar 2024 10:18:26 UTC', + }); + expect(err.message).toBe('M_TEST: Test fail'); + expect(err.retryAfterMs).toEqual(10000); + }); + it("should handle a 429 with a Retry-After header (date)", () => { + const err = new MatrixError({ 'errcode': 'M_TEST', "error": 'Test fail', "retry_after_ms": 5 }, 429, {}); + expect(err.message).toBe('M_TEST: Test fail'); + expect(err.retryAfterMs).toEqual(5); + }); +}); From 9bfc71f3f3b0608d23e4e51ad12b2c73a6d656cb Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 13 Jun 2024 08:39:02 +0100 Subject: [PATCH 114/123] Add packageManager declaration --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 59864d80..d8898cfa 100644 --- a/package.json +++ b/package.json @@ -110,5 +110,6 @@ "testMatch": [ "/test/**/*Test.ts" ] - } + }, + "packageManager": "yarn@1.22.19" } From e4e7e6555b363caebfa579eacf71d7208b5fe2cf Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 13 Jun 2024 08:39:42 +0100 Subject: [PATCH 115/123] v0.7.1-element.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8898cfa..92b16610 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.0-element.1", + "version": "0.7.1-element.0", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 816d1a3d09700021333ff73b1f4e8bd07ba3beb6 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 8 Jul 2024 14:18:24 +0100 Subject: [PATCH 116/123] 0.7.1-element.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 92b16610..c903dce4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.1-element.0", + "version": "0.7.1-element.1", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From 798f2d36ffa5c351f54d34349093bda4d33c9b22 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 11 Jul 2024 14:19:18 +0100 Subject: [PATCH 117/123] Add support for some content scanner endpoints. (#45) * Add support for some content scanner endpoints. * fix tests * fix empty media id being allowed --- src/MatrixClient.ts | 43 +++++++++++++------- src/MatrixContentScannerClient.ts | 66 +++++++++++++++++++++++++++++++ src/e2ee/CryptoClient.ts | 4 +- src/http.ts | 17 +++++++- src/index.ts | 2 + src/models/MXCUrl.ts | 22 +++++++++++ 6 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 src/MatrixContentScannerClient.ts create mode 100644 src/models/MXCUrl.ts diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index ddba1827..84db1ff6 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -21,7 +21,7 @@ import { PowerLevelBounds } from "./models/PowerLevelBounds"; import { EventKind } from "./models/events/EventKind"; import { IdentityClient } from "./identity/IdentityClient"; import { OpenIDConnectToken } from "./models/OpenIDConnect"; -import { doHttpRequest } from "./http"; +import { doHttpRequest, DoHttpRequestOpts } from "./http"; import { Space, SpaceCreateOptions } from "./models/Spaces"; import { PowerLevelAction } from "./models/PowerLevelAction"; import { CryptoClient } from "./e2ee/CryptoClient"; @@ -46,6 +46,8 @@ import { RoomCreateOptions } from "./models/CreateRoom"; import { PresenceState } from './models/events/PresenceEvent'; import { IKeyBackupInfo, IKeyBackupInfoRetrieved, IKeyBackupInfoUnsigned, IKeyBackupInfoUpdate, IKeyBackupVersion, KeyBackupVersion } from "./models/KeyBackup"; import { MatrixError } from "./models/MatrixError"; +import { MXCUrl } from "./models/MXCUrl"; +import { MatrixContentScannerClient } from "./MatrixContentScannerClient"; const SYNC_BACKOFF_MIN_MS = 5000; const SYNC_BACKOFF_MAX_MS = 15000; @@ -79,6 +81,13 @@ export class MatrixClient extends EventEmitter { */ public readonly crypto: CryptoClient; + /** + * The Content Scanner API instance for this client. This is set if `opts.enableContentScanner` + * is true. The `downloadContent` and `crypto.decryptMedia` methods automatically go via + * the content scanner when this is set. + */ + public readonly contentScannerInstance?: MatrixContentScannerClient; + /** * The DM manager instance for this client. */ @@ -94,7 +103,7 @@ export class MatrixClient extends EventEmitter { private filterId = 0; private stopSyncing = false; private metricsInstance: Metrics = new Metrics(); - private unstableApisInstance = new UnstableApis(this); + private readonly unstableApisInstance = new UnstableApis(this); private cachedVersions: ServerVersions; private versionsLastFetched = 0; @@ -118,6 +127,7 @@ export class MatrixClient extends EventEmitter { public readonly accessToken: string, private storage: IStorageProvider = null, public readonly cryptoStore: ICryptoStorageProvider = null, + opts: { enableContentScanner?: boolean } = {}, ) { super(); @@ -149,6 +159,10 @@ export class MatrixClient extends EventEmitter { if (!this.storage) this.storage = new MemoryStorageProvider(); this.dms = new DMs(this); + + if (opts.enableContentScanner) { + this.contentScannerInstance = new MatrixContentScannerClient(this); + } } /** @@ -1587,11 +1601,8 @@ export class MatrixClient extends EventEmitter { * @returns {string} The HTTP URL for the content. */ public mxcToHttp(mxc: string): string { - if (!mxc.startsWith("mxc://")) throw new Error("Not a MXC URI"); - const parts = mxc.substring("mxc://".length).split('/'); - const originHomeserver = parts[0]; - const mediaId = parts.slice(1, parts.length).join('/'); - return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(originHomeserver)}/${encodeURIComponent(mediaId)}`; + const { domain, mediaId } = MXCUrl.parse(mxc); + return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; } /** @@ -1633,13 +1644,11 @@ export class MatrixClient extends EventEmitter { * @returns {Promise<{data: Buffer, contentType: string}>} Resolves to the downloaded content. */ public async downloadContent(mxcUrl: string, allowRemote = true): Promise<{ data: Buffer, contentType: string }> { - if (!mxcUrl.toLowerCase().startsWith("mxc://")) { - throw Error("'mxcUrl' does not begin with mxc://"); + if (this.contentScannerInstance) { + return this.contentScannerInstance.downloadContent(mxcUrl, allowRemote); } - const urlParts = mxcUrl.substr("mxc://".length).split("/"); - const domain = encodeURIComponent(urlParts[0]); - const mediaId = encodeURIComponent(urlParts[1].split("/")[0]); - const path = `/_matrix/media/v3/download/${domain}/${mediaId}`; + const { domain, mediaId } = MXCUrl.parse(mxcUrl); + const path = `/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true); return { data: res.body, @@ -2095,7 +2104,8 @@ export class MatrixClient extends EventEmitter { * @returns {Promise} Resolves to the response (body), rejected if a non-2xx status code was returned. */ @timedMatrixClientFunctionCall() - public doRequest(method, endpoint, qs = null, body = null, timeout = 60000, raw = false, contentType = "application/json", noEncoding = false): Promise { + public doRequest(method, endpoint, qs = null, body = null, timeout = 60000, raw = false, + contentType = "application/json", noEncoding = false, opts?: DoHttpRequestOpts): Promise { if (this.impersonatedUserId) { if (!qs) qs = { "user_id": this.impersonatedUserId }; else qs["user_id"] = this.impersonatedUserId; @@ -2108,7 +2118,10 @@ export class MatrixClient extends EventEmitter { if (this.accessToken) { headers["Authorization"] = `Bearer ${this.accessToken}`; } - return doHttpRequest(this.homeserverUrl, method, endpoint, qs, body, headers, timeout, raw, contentType, noEncoding); + return doHttpRequest( + this.homeserverUrl, method, endpoint, qs, body, headers, + timeout, raw, contentType, noEncoding, opts, + ); } } diff --git a/src/MatrixContentScannerClient.ts b/src/MatrixContentScannerClient.ts new file mode 100644 index 00000000..d1288c3c --- /dev/null +++ b/src/MatrixContentScannerClient.ts @@ -0,0 +1,66 @@ +import { EncryptedFile, MatrixClient } from "."; +import { MXCUrl } from "./models/MXCUrl"; + +export interface ContentScannerResult { + info: string; + clean: boolean; +} +export interface ContentScannerErrorResult { + info: string; + reason: string; +} + +export class MatrixContentScannerError extends Error { + constructor(public readonly body: ContentScannerErrorResult) { + super(`Encountered error scanning content (${body.reason}): ${body.info}`); + } +} + +const errorHandler = (_response, errBody) => { + return typeof (errBody) === "object" && 'reason' in errBody ? + new MatrixContentScannerError(errBody as ContentScannerErrorResult) : undefined; +}; + +/** + * API client for https://github.com/element-hq/matrix-content-scanner-python. + */ +export class MatrixContentScannerClient { + constructor(public readonly client: MatrixClient) { + + } + + public async scanContent(mxcUrl: string): Promise { + const { domain, mediaId } = MXCUrl.parse(mxcUrl); + const path = `/_matrix/media_proxy/unstable/scan/${domain}/${mediaId}`; + const res = await this.client.doRequest("GET", path, null, null, null, false, null, false, { errorHandler }); + return res; + } + + public async scanContentEncrypted(file: EncryptedFile): Promise { + // Sanity check. + MXCUrl.parse(file.url); + const path = `/_matrix/media_proxy/unstable/scan_encrypted`; + const res = await this.client.doRequest("POST", path, null, { file }, null, false, null, false, { errorHandler }); + return res; + } + + public async downloadContent(mxcUrl: string, allowRemote = true): ReturnType { + const { domain, mediaId } = MXCUrl.parse(mxcUrl); + const path = `/_matrix/media_proxy/unstable/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; + const res = await this.client.doRequest("GET", path, null, null, null, true, null, true, { errorHandler }); + return { + data: res.body, + contentType: res.headers["content-type"], + }; + } + + public async downloadEncryptedContent(file: EncryptedFile): Promise { + // Sanity check. + MXCUrl.parse(file.url); + const path = `/_matrix/media_proxy/unstable/download_encrypted`; + const res = await this.client.doRequest("POST", path, undefined, { + file, + }, null, true, null, true, { errorHandler }); + return res.data; + } +} diff --git a/src/e2ee/CryptoClient.ts b/src/e2ee/CryptoClient.ts index be8b789d..3b448215 100644 --- a/src/e2ee/CryptoClient.ts +++ b/src/e2ee/CryptoClient.ts @@ -290,7 +290,9 @@ export class CryptoClient { */ @requiresReady() public async decryptMedia(file: EncryptedFile): Promise { - const contents = (await this.client.downloadContent(file.url)).data; + const contents = this.client.contentScannerInstance ? + await this.client.contentScannerInstance.downloadEncryptedContent(file) : + (await this.client.downloadContent(file.url)).data; const encrypted = new EncryptedAttachment( contents, JSON.stringify(file), diff --git a/src/http.ts b/src/http.ts index 20a3b82b..9f023406 100644 --- a/src/http.ts +++ b/src/http.ts @@ -4,6 +4,15 @@ import { MatrixError } from "./models/MatrixError"; let lastRequestId = 0; +const defaultErrorHandler = (response, errBody) => { + return typeof (errBody) === "object" && 'errcode' in errBody ? + new MatrixError(errBody, response.statusCode, response.headers) : undefined; +}; + +export interface DoHttpRequestOpts { + errorHandler?: (response, body) => Error|undefined; +} + /** * Performs a web request to a server. * @category Unit testing @@ -30,6 +39,9 @@ export async function doHttpRequest( raw = false, contentType = "application/json", noEncoding = false, + opts: DoHttpRequestOpts = { + errorHandler: defaultErrorHandler, + }, ): Promise { if (!endpoint.startsWith('/')) { endpoint = '/' + endpoint; @@ -104,10 +116,11 @@ export async function doHttpRequest( // Check for errors. const errBody = response.body || resBody; - if (typeof (errBody) === "object" && 'errcode' in errBody) { + const handledError = opts.errorHandler(response, errBody); + if (handledError) { const redactedBody = respIsBuffer ? '' : redactObjectForLogging(errBody); LogService.error("MatrixHttpClient", "(REQ-" + requestId + ")", redactedBody); - throw new MatrixError(errBody, response.statusCode, response.headers); + throw handledError; } // Don't log the body unless we're in debug mode. They can be large. diff --git a/src/index.ts b/src/index.ts index 56afd3d0..3d5ab340 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,6 +55,7 @@ export * from "./models/PowerLevelAction"; export * from "./models/ServerVersions"; export * from "./models/MatrixError"; export * from "./models/CreateRoom"; +export * from "./models/MXCUrl"; // Unstable models export * from "./models/unstable/MediaInfo"; @@ -112,6 +113,7 @@ export * from "./request"; export * from "./PantalaimonClient"; export * from "./SynchronousMatrixClient"; export * from "./SynapseAdminApis"; +export * from "./MatrixContentScannerClient"; export * from "./simple-validation"; export * from "./b64"; export * from "./http"; diff --git a/src/models/MXCUrl.ts b/src/models/MXCUrl.ts new file mode 100644 index 00000000..b774ea8c --- /dev/null +++ b/src/models/MXCUrl.ts @@ -0,0 +1,22 @@ +export class MXCUrl { + static parse(mxcUrl: string): MXCUrl { + if (!mxcUrl?.toLowerCase()?.startsWith("mxc://")) { + throw Error("Not a MXC URI"); + } + const [domain, ...mediaIdParts] = mxcUrl.slice("mxc://".length).split("/"); + if (!domain) { + throw Error("missing domain component"); + } + const mediaId = mediaIdParts?.join('/') ?? undefined; + if (!mediaId) { + throw Error("missing mediaId component"); + } + return new MXCUrl(domain, mediaId); + } + + constructor(public domain: string, public mediaId: string) { } + + public toString() { + return `mxc://${this.domain}/${this.mediaId}`; + } +} From 634acd637e1ff105c831ec83770a02a20018ccb9 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 29 Jul 2024 20:42:15 +0100 Subject: [PATCH 118/123] Add option to view/edit locked state of users (#47) Add option to lock users --- src/SynapseAdminApis.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SynapseAdminApis.ts b/src/SynapseAdminApis.ts index fc4b8ebd..9c907af9 100644 --- a/src/SynapseAdminApis.ts +++ b/src/SynapseAdminApis.ts @@ -41,6 +41,11 @@ export interface SynapseUser { * Whether or not the user is deactivated. */ deactivated?: boolean; + + /** + * Whether or not the user can log in. Available since Synapse v1.93.0 + */ + locked?: boolean; } /** From 8e2d32d4768f6ac07d1ce3be79c1dde370940810 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 Jul 2024 10:34:58 +0100 Subject: [PATCH 119/123] Passthrough scanner config to appservice intents. --- src/MatrixClient.ts | 5 ++--- src/MatrixContentScannerClient.ts | 2 +- src/appservice/Appservice.ts | 6 ++++++ src/appservice/Intent.ts | 5 ++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index 84db1ff6..bd084d09 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -152,8 +152,6 @@ export class MatrixClient extends EventEmitter { this.crypto.onRoomJoin(roomId); }); LogService.debug("MatrixClientLite", "End-to-end encryption client created"); - } else { - // LogService.trace("MatrixClientLite", "Not setting up encryption"); } if (!this.storage) this.storage = new MemoryStorageProvider(); @@ -1641,11 +1639,12 @@ export class MatrixClient extends EventEmitter { * @param {string} allowRemote Indicates to the server that it should not attempt to fetch the * media if it is deemed remote. This is to prevent routing loops where the server contacts itself. * Defaults to true if not provided. + * This is IGNORED if the content scanner is configured, as the API has no compatible option. * @returns {Promise<{data: Buffer, contentType: string}>} Resolves to the downloaded content. */ public async downloadContent(mxcUrl: string, allowRemote = true): Promise<{ data: Buffer, contentType: string }> { if (this.contentScannerInstance) { - return this.contentScannerInstance.downloadContent(mxcUrl, allowRemote); + return this.contentScannerInstance.downloadContent(mxcUrl); } const { domain, mediaId } = MXCUrl.parse(mxcUrl); const path = `/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; diff --git a/src/MatrixContentScannerClient.ts b/src/MatrixContentScannerClient.ts index d1288c3c..e5a90c66 100644 --- a/src/MatrixContentScannerClient.ts +++ b/src/MatrixContentScannerClient.ts @@ -44,7 +44,7 @@ export class MatrixContentScannerClient { return res; } - public async downloadContent(mxcUrl: string, allowRemote = true): ReturnType { + public async downloadContent(mxcUrl: string): ReturnType { const { domain, mediaId } = MXCUrl.parse(mxcUrl); const path = `/_matrix/media_proxy/unstable/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; const res = await this.client.doRequest("GET", path, null, null, null, true, null, true, { errorHandler }); diff --git a/src/appservice/Appservice.ts b/src/appservice/Appservice.ts index fa162818..6f2d0ea2 100644 --- a/src/appservice/Appservice.ts +++ b/src/appservice/Appservice.ts @@ -209,6 +209,12 @@ export interface IAppserviceOptions { * Note that the appservice bot account is considered an intent. */ encryption?: boolean; + + /** + * Enable the content scanner API when creating new intents. This means + * that all media requests will be proxied through the scanner. + */ + enableContentScanner?: boolean; }; } diff --git a/src/appservice/Intent.ts b/src/appservice/Intent.ts index ab95bdb0..9d1e719d 100644 --- a/src/appservice/Intent.ts +++ b/src/appservice/Intent.ts @@ -61,7 +61,10 @@ export class Intent { throw new Error("Tried to set up client with crypto, but no persistent storage"); } } - this.client = new MatrixClient(this.options.homeserverUrl, accessToken ?? this.options.registration.as_token, storage, cryptoStore); + + this.client = new MatrixClient(this.options.homeserverUrl, accessToken ?? this.options.registration.as_token, storage, cryptoStore, { + enableContentScanner: this.options.intentOptions?.enableContentScanner, + }); this.client.metrics = new Metrics(this.appservice.metrics); // Metrics only go up by one parent this.unstableApisInstance = new UnstableAppserviceApis(this.client); if (this.impersonateUserId !== this.appservice.botUserId) { From 6b780ee974d0e063799e75e554b46227c66d1c90 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 Jul 2024 10:35:14 +0100 Subject: [PATCH 120/123] 0.7.1-element.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c903dce4..b2c25364 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.1-element.1", + "version": "0.7.1-element.2", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From aadd2285001f8c9b9c0bf44ff6a56c959f63a12c Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 15 Jul 2024 10:35:41 +0100 Subject: [PATCH 121/123] 0.7.1-element.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b2c25364..e432a844 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.1-element.2", + "version": "0.7.1-element.3", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From b3d2d64bcea6442e801c444b00fb91195d3f6f87 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 29 Jul 2024 20:43:32 +0100 Subject: [PATCH 122/123] 0.7.1-element.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e432a844..01fa545f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vector-im/matrix-bot-sdk", - "version": "0.7.1-element.3", + "version": "0.7.1-element.4", "description": "TypeScript/JavaScript SDK for Matrix bots and appservices", "repository": { "type": "git", From f1d25a9eed560c7463f776fef26536747971ca12 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 19 Sep 2024 13:26:29 +0100 Subject: [PATCH 123/123] Support authenticated media endpoints. --- src/MatrixClient.ts | 26 ++++++++++++++++++-------- test/MatrixClientTest.ts | 32 ++++++++++++++++---------------- test/TestUtils.ts | 18 ++++++++++++++++-- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/MatrixClient.ts b/src/MatrixClient.ts index bd084d09..0535abdd 100644 --- a/src/MatrixClient.ts +++ b/src/MatrixClient.ts @@ -1593,14 +1593,22 @@ export class MatrixClient extends EventEmitter { await this.sendStateEvent(roomId, "m.room.power_levels", "", currentLevels); } + private async getMediaEndpointPrefix() { + if (await this.doesServerSupportVersion('v1.11')) { + return `${this.homeserverUrl}/_matrix/client/v1/media`; + } + return `${this.homeserverUrl}/_matrix/media/v3`; + } + /** * Converts a MXC URI to an HTTP URL. * @param {string} mxc The MXC URI to convert * @returns {string} The HTTP URL for the content. */ - public mxcToHttp(mxc: string): string { + public async mxcToHttp(mxc: string): Promise { const { domain, mediaId } = MXCUrl.parse(mxc); - return `${this.homeserverUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; + const endpoint = await this.getMediaEndpointPrefix(); + return `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; } /** @@ -1611,9 +1619,9 @@ export class MatrixClient extends EventEmitter { * @param {"crop"|"scale"} method Whether to crop or scale (preserve aspect ratio) the content. * @returns {string} The HTTP URL for the downsized content. */ - public mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): string { - const downloadUri = this.mxcToHttp(mxc); - return downloadUri.replace("/_matrix/media/v3/download", "/_matrix/media/v3/thumbnail") + public async mxcToHttpThumbnail(mxc: string, width: number, height: number, method: "crop" | "scale"): Promise { + const downloadUri = await this.mxcToHttp(mxc); + return downloadUri.replace("/download", "/thumbnail") + `?width=${width}&height=${height}&method=${encodeURIComponent(method)}`; } @@ -1626,9 +1634,10 @@ export class MatrixClient extends EventEmitter { * @returns {Promise} resolves to the MXC URI of the content */ @timedMatrixClientFunctionCall() - public uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise { + public async uploadContent(data: Buffer, contentType = "application/octet-stream", filename: string = null): Promise { // TODO: Make doRequest take an object for options - return this.doRequest("POST", "/_matrix/media/v3/upload", { filename: filename }, data, 60000, false, contentType) + const endpoint = await this.getMediaEndpointPrefix(); + return this.doRequest("POST", `${endpoint}/upload`, { filename: filename }, data, 60000, false, contentType) .then(response => response["content_uri"]); } @@ -1646,8 +1655,9 @@ export class MatrixClient extends EventEmitter { if (this.contentScannerInstance) { return this.contentScannerInstance.downloadContent(mxcUrl); } + const endpoint = await this.getMediaEndpointPrefix(); const { domain, mediaId } = MXCUrl.parse(mxcUrl); - const path = `/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; + const path = `${endpoint}/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`; const res = await this.doRequest("GET", path, { allow_remote: allowRemote }, null, null, true, null, true); return { data: res.body, diff --git a/test/MatrixClientTest.ts b/test/MatrixClientTest.ts index 99c3c404..0edd5e3a 100644 --- a/test/MatrixClientTest.ts +++ b/test/MatrixClientTest.ts @@ -305,7 +305,7 @@ describe('MatrixClient', () => { describe('getServerVersions', () => { it('should call the right endpoint', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { unstable_features: { @@ -322,7 +322,7 @@ describe('MatrixClient', () => { }); it('should cache the response', async () => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { unstable_features: { @@ -358,7 +358,7 @@ describe('MatrixClient', () => { [{ "org.example.wrong": true }, "org.example.feature", false], [{ "org.example.wrong": false }, "org.example.feature", false], ])("should find that %p has %p as %p", async (versions, flag, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: ["v1.1"], @@ -378,7 +378,7 @@ describe('MatrixClient', () => { [["v1.2"], "v1.1", false], [["v1.1", "v1.2", "v1.3"], "v1.2", true], ])("should find that %p has %p as %p", async (versions, version, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: versions, @@ -397,7 +397,7 @@ describe('MatrixClient', () => { [["v1.3"], ["v1.1", "v1.2"], false], [["v1.1", "v1.2", "v1.3"], ["v1.2", "v1.3"], true], ])("should find that %p has %p as %p", async (versions, searchVersions, target) => { - const { client, http } = createTestClient(); + const { client, http } = createTestClient(undefined, undefined, undefined, { handleWhoAmI: true, precacheVersions: false }); const versionsResponse: ServerVersions = { versions: versions, @@ -5646,8 +5646,8 @@ describe('MatrixClient', () => { const mediaId = "testing/val"; const mxc = `mxc://${domain}/${mediaId}`; - const http = client.mxcToHttp(mxc); - expect(http).toBe(`${hsUrl}/_matrix/media/v3/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`); + const http = await client.mxcToHttp(mxc); + expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/download/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}`); }); it('should error for non-MXC URIs', async () => { @@ -5658,7 +5658,7 @@ describe('MatrixClient', () => { const mxc = `https://${domain}/${mediaId}`; try { - client.mxcToHttp(mxc); + await client.mxcToHttp(mxc); // noinspection ExceptionCaughtLocallyJS throw new Error("Expected an error and didn't get one"); @@ -5679,9 +5679,9 @@ describe('MatrixClient', () => { const method = "scale"; const mxc = `mxc://${domain}/${mediaId}`; - const http = client.mxcToHttpThumbnail(mxc, width, height, method); + const http = await client.mxcToHttpThumbnail(mxc, width, height, method); // eslint-disable-next-line max-len - expect(http).toBe(`${hsUrl}/_matrix/media/v3/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`); + expect(http).toBe(`${hsUrl}/_matrix/client/v1/media/thumbnail/${encodeURIComponent(domain)}/${encodeURIComponent(mediaId)}?width=${width}&height=${height}&method=${encodeURIComponent(method)}`); }); it('should error for non-MXC URIs', async () => { @@ -5695,7 +5695,7 @@ describe('MatrixClient', () => { const mxc = `https://${domain}/${mediaId}`; try { - client.mxcToHttpThumbnail(mxc, width, height, method); + await client.mxcToHttpThumbnail(mxc, width, height, method); // noinspection ExceptionCaughtLocallyJS throw new Error("Expected an error and didn't get one"); @@ -5717,7 +5717,7 @@ describe('MatrixClient', () => { Buffer.isBuffer = (i => i === data); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); expect(req.queryParams.filename).toEqual(filename); expect(req.headers["Content-Type"]).toEqual(contentType); @@ -5740,7 +5740,7 @@ describe('MatrixClient', () => { Buffer.isBuffer = (i => i === data); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); expect(req.queryParams.filename).toEqual(filename); expect(req.headers["Content-Type"]).toEqual(contentType); @@ -5761,8 +5761,8 @@ describe('MatrixClient', () => { // const fileContents = Buffer.from("12345"); // noinspection TypeScriptValidateJSTypes - http.when("GET", "/_matrix/media/v3/download/").respond(200, (path, _, req) => { - expect(path).toContain("/_matrix/media/v3/download/" + urlPart); + http.when("GET", "/_matrix/client/v1/media/download/").respond(200, (path, _, req) => { + expect(path).toContain("/_matrix/client/v1/media/download/" + urlPart); expect((req as any).opts.encoding).toEqual(null); // TODO: Honestly, I have no idea how to coerce the mock library to return headers or buffers, // so this is left as a fun activity. @@ -5798,7 +5798,7 @@ describe('MatrixClient', () => { }); // noinspection TypeScriptValidateJSTypes - http.when("POST", "/_matrix/media/v3/upload").respond(200, (path, content, req) => { + http.when("POST", "/_matrix/client/v1/media/upload").respond(200, (path, content, req) => { expect(content).toBeDefined(); // HACK: We know the mock library will return JSON expect(req.headers["Content-Type"]).toEqual("application/json"); diff --git a/test/TestUtils.ts b/test/TestUtils.ts index df748d64..833f0305 100644 --- a/test/TestUtils.ts +++ b/test/TestUtils.ts @@ -2,7 +2,7 @@ import * as tmp from "tmp"; import HttpBackend from "matrix-mock-request"; import { StoreType } from "@matrix-org/matrix-sdk-crypto-nodejs"; -import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, UnpaddedBase64, setRequestFn } from "../src"; +import { IStorageProvider, MatrixClient, OTKAlgorithm, RustSdkCryptoStorageProvider, ServerVersions, UnpaddedBase64, setRequestFn } from "../src"; export const TEST_DEVICE_ID = "TEST_DEVICE"; @@ -31,13 +31,18 @@ export function createTestClient( storage: IStorageProvider = null, userId: string = null, cryptoStoreType?: StoreType, - opts = { handleWhoAmI: true }, + opts?: Partial<{ handleWhoAmI: boolean, precacheVersions: boolean }>, ): { client: MatrixClient; http: HttpBackend; hsUrl: string; accessToken: string; } { + opts = { + handleWhoAmI: true, + precacheVersions: true, + ...opts, + }; const http = new HttpBackend(); const hsUrl = "https://localhost"; const accessToken = "s3cret"; @@ -45,6 +50,15 @@ export function createTestClient( (client).userId = userId; // private member access setRequestFn(http.requestFn); + // Force versions + if (opts.precacheVersions) { + (client).cachedVersions = { + unstable_features: { }, + versions: ["v1.11"], + } as ServerVersions; + (client).versionsLastFetched = Date.now(); + } + if (opts.handleWhoAmI) { // Ensure we always respond to a whoami client.getWhoAmI = () => Promise.resolve({ user_id: userId, device_id: TEST_DEVICE_ID });