From f10d39fee7a5953f85babe81998357d562f4443c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Thu, 8 Dec 2022 17:48:33 -0500 Subject: [PATCH 01/79] 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 02/79] 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 03/79] 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 04/79] 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 05/79] 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 06/79] 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 07/79] 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 08/79] 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 09/79] 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 10/79] 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 11/79] 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 12/79] 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 13/79] 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 14/79] 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 15/79] 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 16/79] 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 17/79] 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 18/79] 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 19/79] 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 20/79] 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 21/79] 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 22/79] 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 23/79] 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 24/79] 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 25/79] 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 26/79] 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 27/79] 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 28/79] 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 29/79] 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 30/79] 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 31/79] 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 32/79] 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 33/79] 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 34/79] 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 35/79] 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 36/79] 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 37/79] 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 38/79] 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 39/79] 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 40/79] 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 41/79] 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 42/79] 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 43/79] 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 44/79] 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 45/79] 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 46/79] 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 47/79] 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 48/79] 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 49/79] 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 50/79] 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 51/79] 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 52/79] 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 53/79] 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 54/79] 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 55/79] 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 56/79] 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 57/79] 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 58/79] 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 59/79] 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 60/79] 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 61/79] 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 62/79] 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 63/79] 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 64/79] 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 65/79] 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 66/79] 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 67/79] 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 68/79] 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 69/79] 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 70/79] 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 71/79] 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 e84fee9840b88e8d61711fa029bae5ded6fbaa5f Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 28 Nov 2023 10:06:37 +0000 Subject: [PATCH 72/79] 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 73/79] 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 74/79] 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 75/79] 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 76/79] 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 5a0dd1e92cd5df29be516be33a93d4ff9f916455 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Mon, 4 Dec 2023 10:38:39 +0000 Subject: [PATCH 77/79] 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 78/79] 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 79/79] 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);