From e592f60240fa143fff37d4eda0cd0f7a4ebbf4ac Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Oct 2022 12:49:21 +0100 Subject: [PATCH 01/11] Prepare changelog for v21.0.0-rc.1 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce41de1aa0a..7cd42c03234 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +Changes in [21.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0-rc.1) (2022-10-18) +============================================================================================================ + +## 🚨 BREAKING CHANGES + * Changes the `uploadContent` API, kills off `request` and `browser-request` in favour of `fetch`, removed callback support on a lot of the methods, adds a lot of tests. ([\#2719](https://github.com/matrix-org/matrix-js-sdk/pull/2719)). Fixes #2415 and #801. + * Remove deprecated `m.room.aliases` references ([\#2759](https://github.com/matrix-org/matrix-js-sdk/pull/2759)). Fixes vector-im/element-web#12680. + +## ✨ Features + * Remove node-specific crypto bits, use Node 16's WebCrypto ([\#2762](https://github.com/matrix-org/matrix-js-sdk/pull/2762)). Fixes #2760. + * Export types for MatrixEvent and Room emitted events, and make event handler map types stricter ([\#2750](https://github.com/matrix-org/matrix-js-sdk/pull/2750)). Contributed by @stas-demydiuk. + * Use even more stable calls to `/room_keys` ([\#2746](https://github.com/matrix-org/matrix-js-sdk/pull/2746)). + * Upgrade to Olm 3.2.13 which has been repackaged to support Node 18 ([\#2744](https://github.com/matrix-org/matrix-js-sdk/pull/2744)). + * Fix `power_level_content_override` type ([\#2741](https://github.com/matrix-org/matrix-js-sdk/pull/2741)). + * Add custom notification handling for MSC3401 call events ([\#2720](https://github.com/matrix-org/matrix-js-sdk/pull/2720)). + * Add support for unread thread notifications ([\#2726](https://github.com/matrix-org/matrix-js-sdk/pull/2726)). + * Load Thread List with server-side assistance (MSC3856) ([\#2602](https://github.com/matrix-org/matrix-js-sdk/pull/2602)). + * Use stable calls to `/room_keys` ([\#2729](https://github.com/matrix-org/matrix-js-sdk/pull/2729)). Fixes vector-im/element-web#22839. + +## 🐛 Bug Fixes + * Fix IdentityPrefix.V2 containing spurious `/api` ([\#2761](https://github.com/matrix-org/matrix-js-sdk/pull/2761)). Fixes vector-im/element-web#23505. + * Always send back an httpStatus property if one is known ([\#2753](https://github.com/matrix-org/matrix-js-sdk/pull/2753)). + * Check for AbortError, not any generic connection error, to avoid tightlooping ([\#2752](https://github.com/matrix-org/matrix-js-sdk/pull/2752)). + * Correct the dir parameter of MSC3715 ([\#2745](https://github.com/matrix-org/matrix-js-sdk/pull/2745)). Contributed by @dhenneke. + * Fix sync init when thread unread notif is not supported ([\#2739](https://github.com/matrix-org/matrix-js-sdk/pull/2739)). Fixes vector-im/element-web#23435. + * Use the correct sender key when checking shared secret ([\#2730](https://github.com/matrix-org/matrix-js-sdk/pull/2730)). Fixes vector-im/element-web#23374. + Changes in [20.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v20.1.0) (2022-10-11) ============================================================================================================ From fc1b03c0bf0ff71d23e1f575314b6fcb7cfc70e9 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Oct 2022 12:49:22 +0100 Subject: [PATCH 02/11] v21.0.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 710bb281d87..8ec8d5473c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "20.1.0", + "version": "21.0.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" @@ -32,7 +32,7 @@ "keywords": [ "matrix-org" ], - "main": "./src/index.ts", + "main": "./lib/index.js", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -129,5 +129,6 @@ "jestSonar": { "reportPath": "coverage", "sonar56x": true - } + }, + "typings": "./lib/index.d.ts" } From 63f4bf571ebdd3147842197e759e8cfd999a9135 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 18 Oct 2022 16:03:40 +0100 Subject: [PATCH 03/11] [Backport staging] Fix POST data not being passed for registerWithIdentityServer (#2770) Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- spec/integ/matrix-client-methods.spec.ts | 40 +++++++++++++++++++++++- src/client.ts | 16 +++++----- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/spec/integ/matrix-client-methods.spec.ts b/spec/integ/matrix-client-methods.spec.ts index 954df27fd1b..273491e70fe 100644 --- a/spec/integ/matrix-client-methods.spec.ts +++ b/spec/integ/matrix-client-methods.spec.ts @@ -1254,6 +1254,20 @@ describe("MatrixClient", function() { }); }); + describe("agreeToTerms", () => { + it("should send `user_accepts` via body of POST request", async () => { + const terms = ["https://vector.im/notice-1"]; + + httpBackend!.when("POST", "/terms").check(req => { + expect(req.data.user_accepts).toStrictEqual(terms); + }).respond(200, {}); + + const prom = client!.agreeToTerms(SERVICE_TYPES.IS, "https://vector.im", "at", terms); + await httpBackend!.flushAllExpected(); + await prom; + }); + }); + describe("publicRooms", () => { it("should use GET request if no server or filter is specified", () => { httpBackend!.when("GET", "/publicRooms").respond(200, {}); @@ -1263,7 +1277,7 @@ describe("MatrixClient", function() { it("should use GET request if only server is specified", () => { httpBackend!.when("GET", "/publicRooms").check(request => { - expect(request.queryParams.server).toBe("server1"); + expect(request.queryParams?.server).toBe("server1"); }).respond(200, {}); client!.publicRooms({ server: "server1" }); return httpBackend!.flushAllExpected(); @@ -1296,6 +1310,30 @@ describe("MatrixClient", function() { expect(client.http.opts.accessToken).toBe(token); }); }); + + describe("registerWithIdentityServer", () => { + it("should pass data to POST request", async () => { + const token = { + access_token: "access_token", + token_type: "Bearer", + matrix_server_name: "server_name", + expires_in: 12345, + }; + + httpBackend!.when("POST", "/account/register").check(req => { + expect(req.data).toStrictEqual(token); + }).respond(200, { + access_token: "at", + token: "tt", + }); + + const prom = client!.registerWithIdentityServer(token); + await httpBackend!.flushAllExpected(); + const resp = await prom; + expect(resp.access_token).toBe("at"); + expect(resp.token).toBe("tt"); + }); + }); }); function withThreadId(event: MatrixEvent, newThreadId: string): MatrixEvent { diff --git a/src/client.ts b/src/client.ts index fc049a42089..e501f7d20db 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8278,13 +8278,16 @@ export class MatrixClient extends TypedEventEmitter { // TODO: Types + public registerWithIdentityServer(hsOpenIdToken: IOpenIDToken): Promise<{ + access_token: string; + token: string; + }> { if (!this.idBaseUrl) { throw new Error("No identity server base URL set"); } const uri = this.http.getUrl("/account/register", undefined, IdentityPrefix.V2, this.idBaseUrl); - return this.http.requestOtherUrl(Method.Post, uri, null, hsOpenIdToken); + return this.http.requestOtherUrl(Method.Post, uri, hsOpenIdToken); } /** @@ -8751,15 +8754,14 @@ export class MatrixClient extends TypedEventEmitter { // TODO: Types + ): Promise<{}> { const url = this.termsUrlForService(serviceType, baseUrl); - utils.encodeParams({ - user_accepts: termsUrls, - }, url.searchParams); const headers = { Authorization: "Bearer " + accessToken, }; - return this.http.requestOtherUrl(Method.Post, url, null, { headers }); + return this.http.requestOtherUrl(Method.Post, url, { + user_accepts: termsUrls, + }, { headers }); } /** From 4ccc52da8e0703b5199a333ae40d23c0b4811d6f Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Mon, 24 Oct 2022 10:12:42 +0100 Subject: [PATCH 04/11] [Backport staging] Improve crypto init code and allow easier shimming (#2792) Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/crypto/crypto.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/crypto/crypto.ts b/src/crypto/crypto.ts index eb8de60e304..704754f0bd6 100644 --- a/src/crypto/crypto.ts +++ b/src/crypto/crypto.ts @@ -14,18 +14,37 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { logger } from "../logger"; + export let crypto = global.window?.crypto; export let subtleCrypto = global.window?.crypto?.subtle ?? global.window?.crypto?.webkitSubtle; export let TextEncoder = global.window?.TextEncoder; /* eslint-disable @typescript-eslint/no-var-requires */ if (!crypto) { - crypto = require("crypto").webcrypto; + try { + crypto = require("crypto").webcrypto; + } catch (e) { + logger.error("Failed to load webcrypto", e); + } } if (!subtleCrypto) { subtleCrypto = crypto?.subtle; } if (!TextEncoder) { - TextEncoder = require("util").TextEncoder; + try { + TextEncoder = require("util").TextEncoder; + } catch (e) { + logger.error("Failed to load TextEncoder util", e); + } } /* eslint-enable @typescript-eslint/no-var-requires */ + +export function setCrypto(_crypto: Crypto): void { + crypto = _crypto; + subtleCrypto = _crypto.subtle ?? _crypto.webkitSubtle; +} + +export function setTextEncoder(_TextEncoder: typeof TextEncoder): void { + TextEncoder = _TextEncoder; +} From 7772f855e6fbecbbb050621f2044a84969c15cce Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 Oct 2022 16:19:46 +0100 Subject: [PATCH 05/11] Prepare changelog for v21.0.0-rc.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd42c03234..02087fb3920 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +Changes in [21.0.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0-rc.2) (2022-10-24) +============================================================================================================ + + * Fix POST data not being passed for registerWithIdentityServer ([\#2769](https://github.com/matrix-org/matrix-js-sdk/pull/2769)). Fixes matrix-org/element-web-rageshakes#16206. + Changes in [21.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0-rc.1) (2022-10-18) ============================================================================================================ From e7ce1fb9e8e81774233fc2b91ce3196c15f7d27d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 Oct 2022 16:19:47 +0100 Subject: [PATCH 06/11] v21.0.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8ec8d5473c5..0652aaea006 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "21.0.0-rc.1", + "version": "21.0.0-rc.2", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From c8c7af0ae26ddb863b1d47866a2f62632a97d0db Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 25 Oct 2022 17:04:02 +0100 Subject: [PATCH 07/11] Prepare changelog for v21.0.0 --- CHANGELOG.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02087fb3920..d109edd1984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,5 @@ -Changes in [21.0.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0-rc.2) (2022-10-24) -============================================================================================================ - - * Fix POST data not being passed for registerWithIdentityServer ([\#2769](https://github.com/matrix-org/matrix-js-sdk/pull/2769)). Fixes matrix-org/element-web-rageshakes#16206. - -Changes in [21.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0-rc.1) (2022-10-18) -============================================================================================================ +Changes in [21.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v21.0.0) (2022-10-25) +================================================================================================== ## 🚨 BREAKING CHANGES * Changes the `uploadContent` API, kills off `request` and `browser-request` in favour of `fetch`, removed callback support on a lot of the methods, adds a lot of tests. ([\#2719](https://github.com/matrix-org/matrix-js-sdk/pull/2719)). Fixes #2415 and #801. @@ -22,6 +17,7 @@ Changes in [21.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/ta * Use stable calls to `/room_keys` ([\#2729](https://github.com/matrix-org/matrix-js-sdk/pull/2729)). Fixes vector-im/element-web#22839. ## 🐛 Bug Fixes + * Fix POST data not being passed for registerWithIdentityServer ([\#2769](https://github.com/matrix-org/matrix-js-sdk/pull/2769)). Fixes matrix-org/element-web-rageshakes#16206. * Fix IdentityPrefix.V2 containing spurious `/api` ([\#2761](https://github.com/matrix-org/matrix-js-sdk/pull/2761)). Fixes vector-im/element-web#23505. * Always send back an httpStatus property if one is known ([\#2753](https://github.com/matrix-org/matrix-js-sdk/pull/2753)). * Check for AbortError, not any generic connection error, to avoid tightlooping ([\#2752](https://github.com/matrix-org/matrix-js-sdk/pull/2752)). From 1842004db2c9a58896fe159d52d489cf6ee3fcf7 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 25 Oct 2022 17:04:02 +0100 Subject: [PATCH 08/11] v21.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0652aaea006..b269d7b14d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "21.0.0-rc.2", + "version": "21.0.0", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From 4b3e6939d6dbfb72c9637d18d6346796bc6f997f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 25 Oct 2022 17:05:31 +0100 Subject: [PATCH 09/11] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5719aa3f7a0..8d07426b0d3 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "keywords": [ "matrix-org" ], - "main": "./lib/index.js", + "main": "./src/index.ts", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -129,6 +129,5 @@ "jestSonar": { "reportPath": "coverage", "sonar56x": true - }, - "typings": "./lib/index.d.ts" + } } From 9f2f08dfd3de5cb28b5427ffdb3280573929abea Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 Oct 2022 18:31:40 +0100 Subject: [PATCH 10/11] Fix more typescript --strict violations (#2795) * Stash tsc fixes * Iterate * Iterate * Iterate * Fix tests * Iterate * Iterate * Iterate * Iterate * Add tests --- spec/integ/matrix-client-methods.spec.ts | 16 +- spec/test-utils/test-utils.ts | 2 +- spec/unit/crypto/CrossSigningInfo.spec.ts | 6 +- spec/unit/crypto/algorithms/megolm.spec.ts | 1 + spec/unit/crypto/secrets.spec.ts | 2 + .../verification/verification_request.spec.ts | 6 +- spec/unit/event-timeline-set.spec.ts | 14 +- spec/unit/event-timeline.spec.ts | 10 +- spec/unit/event.spec.ts | 65 ----- spec/unit/models/MSC3089Branch.spec.ts | 2 +- spec/unit/models/MSC3089TreeSpace.spec.ts | 24 +- spec/unit/models/beacon.spec.ts | 2 +- spec/unit/models/event.spec.ts | 47 +++- spec/unit/queueToDevice.spec.ts | 26 +- spec/unit/read-receipt.spec.ts | 8 +- spec/unit/realtime-callbacks.spec.ts | 18 +- spec/unit/relations.spec.ts | 10 +- spec/unit/room-state.spec.ts | 2 +- spec/unit/room.spec.ts | 217 +++++++++------ spec/unit/scheduler.spec.ts | 8 +- src/ReEmitter.ts | 2 +- src/client.ts | 247 +++++++++--------- src/content-helpers.ts | 5 +- src/crypto/CrossSigning.ts | 19 +- src/crypto/DeviceList.ts | 2 +- src/crypto/EncryptionSetup.ts | 34 +-- src/crypto/OlmDevice.ts | 4 +- src/crypto/OutgoingRoomKeyRequestManager.ts | 81 +++--- src/crypto/RoomList.ts | 10 +- src/crypto/SecretStorage.ts | 71 +++-- src/crypto/algorithms/base.ts | 18 +- src/crypto/algorithms/megolm.ts | 23 +- src/crypto/algorithms/olm.ts | 4 +- src/crypto/api.ts | 4 +- src/crypto/backup.ts | 8 +- src/crypto/deviceinfo.ts | 2 +- src/crypto/index.ts | 89 +++---- src/crypto/recoverykey.ts | 4 +- .../store/indexeddb-crypto-store-backend.ts | 2 +- src/crypto/verification/Base.ts | 4 +- src/crypto/verification/Error.ts | 2 +- src/crypto/verification/QRCode.ts | 4 +- src/crypto/verification/SAS.ts | 12 +- src/crypto/verification/request/Channel.ts | 4 +- .../verification/request/InRoomChannel.ts | 49 ++-- .../verification/request/ToDeviceChannel.ts | 34 ++- .../request/VerificationRequest.ts | 30 ++- src/filter-component.ts | 6 +- src/interactive-auth.ts | 30 +-- src/logger.ts | 2 +- src/models/MSC3089Branch.ts | 4 +- src/models/beacon.ts | 2 +- src/models/event-context.ts | 4 +- src/models/event-timeline-set.ts | 6 +- src/models/event-timeline.ts | 2 +- src/models/event.ts | 16 +- src/models/read-receipt.ts | 4 +- src/models/related-relations.ts | 2 +- src/models/relations-container.ts | 2 +- src/models/relations.ts | 8 +- src/models/room.ts | 124 ++++----- src/models/thread.ts | 6 +- src/pushprocessor.ts | 2 +- src/rendezvous/MSC3906Rendezvous.ts | 2 +- src/sliding-sync-sdk.ts | 4 +- src/store/index.ts | 5 +- src/store/indexeddb-backend.ts | 6 +- src/store/indexeddb-local-backend.ts | 6 +- src/store/indexeddb-remote-backend.ts | 6 +- src/store/indexeddb.ts | 5 +- src/store/memory.ts | 9 +- src/store/stub.ts | 7 +- src/sync.ts | 13 +- src/utils.ts | 15 +- src/webrtc/call.ts | 2 +- src/webrtc/callEventHandler.ts | 4 +- src/webrtc/mediaHandler.ts | 4 +- 77 files changed, 829 insertions(+), 733 deletions(-) delete mode 100644 spec/unit/event.spec.ts diff --git a/spec/integ/matrix-client-methods.spec.ts b/spec/integ/matrix-client-methods.spec.ts index 114d0114759..8e721193726 100644 --- a/spec/integ/matrix-client-methods.spec.ts +++ b/spec/integ/matrix-client-methods.spec.ts @@ -658,7 +658,7 @@ describe("MatrixClient", function() { // The vote event has been copied into the thread const eventRefWithThreadId = withThreadId( - eventPollResponseReference, eventPollStartThreadRoot.getId()); + eventPollResponseReference, eventPollStartThreadRoot.getId()!); expect(eventRefWithThreadId.threadRootId).toBeTruthy(); expect(threaded).toEqual([ @@ -695,7 +695,7 @@ describe("MatrixClient", function() { expect(threaded).toEqual([ eventPollStartThreadRoot, eventMessageInThread, - withThreadId(eventReaction, eventPollStartThreadRoot.getId()), + withThreadId(eventReaction, eventPollStartThreadRoot.getId()!), ]); }); @@ -725,7 +725,7 @@ describe("MatrixClient", function() { expect(threaded).toEqual([ eventPollStartThreadRoot, - withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()), + withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()!), eventMessageInThread, ]); }); @@ -757,7 +757,7 @@ describe("MatrixClient", function() { expect(threaded).toEqual([ eventPollStartThreadRoot, eventMessageInThread, - withThreadId(eventReaction, eventPollStartThreadRoot.getId()), + withThreadId(eventReaction, eventPollStartThreadRoot.getId()!), ]); }); @@ -813,7 +813,7 @@ describe("MatrixClient", function() { // Thread should contain only stuff that happened in the thread - no room state events expect(threaded).toEqual([ eventPollStartThreadRoot, - withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()), + withThreadId(eventPollResponseReference, eventPollStartThreadRoot.getId()!), eventMessageInThread, ]); }); @@ -1375,7 +1375,7 @@ const buildEventMessageInThread = (root: MatrixEvent) => new MatrixEvent({ "m.relates_to": { "event_id": root.getId(), "m.in_reply_to": { - "event_id": root.getId(), + "event_id": root.getId()!, }, "rel_type": "m.thread", }, @@ -1474,13 +1474,13 @@ const buildEventReply = (target: MatrixEvent) => new MatrixEvent({ "device_id": "XISFUZSKHH", "m.relates_to": { "m.in_reply_to": { - "event_id": target.getId(), + "event_id": target.getId()!, }, }, "sender_key": "i3N3CtG/CD2bGB8rA9fW6adLYSDvlUhf2iuU73L65Vg", "session_id": "Ja11R/KG6ua0wdk8zAzognrxjio1Gm/RK2Gn6lFL804", }, - "event_id": target.getId() + Math.random(), + "event_id": target.getId()! + Math.random(), "origin_server_ts": 1643815466378, "room_id": "!STrMRsukXHtqQdSeHa:matrix.org", "sender": "@andybalaam-test1:matrix.org", diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index 288e0355b74..00a4d656d19 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -307,7 +307,7 @@ export function mkReplyMessage( "rel_type": "m.in_reply_to", "event_id": opts.replyToMessage.getId(), "m.in_reply_to": { - "event_id": opts.replyToMessage.getId(), + "event_id": opts.replyToMessage.getId()!, }, }, }, diff --git a/spec/unit/crypto/CrossSigningInfo.spec.ts b/spec/unit/crypto/CrossSigningInfo.spec.ts index 4bf29e31ed3..f6b64cac440 100644 --- a/spec/unit/crypto/CrossSigningInfo.spec.ts +++ b/spec/unit/crypto/CrossSigningInfo.spec.ts @@ -222,9 +222,9 @@ describe.each([ ["IndexedDBCryptoStore", () => new IndexedDBCryptoStore(global.indexedDB, "tests")], ["LocalStorageCryptoStore", - () => new IndexedDBCryptoStore(undefined, "tests")], + () => new IndexedDBCryptoStore(undefined!, "tests")], ["MemoryCryptoStore", () => { - const store = new IndexedDBCryptoStore(undefined, "tests"); + const store = new IndexedDBCryptoStore(undefined!, "tests"); // @ts-ignore set private properties store._backend = new MemoryCryptoStore(); // @ts-ignore @@ -255,6 +255,6 @@ describe.each([ expect(nokey).toBeNull(); const key = await getCrossSigningKeyCache!("self_signing", ""); - expect(new Uint8Array(key)).toEqual(testKey); + expect(new Uint8Array(key!)).toEqual(testKey); }); }); diff --git a/spec/unit/crypto/algorithms/megolm.spec.ts b/spec/unit/crypto/algorithms/megolm.spec.ts index 6dec0ab1559..a1519d4ab61 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.ts +++ b/spec/unit/crypto/algorithms/megolm.spec.ts @@ -536,6 +536,7 @@ describe("MegolmDecryption", function() { "@bob:example.com", BOB_DEVICES, ); aliceClient.crypto!.deviceList.downloadKeys = async function(userIds) { + // @ts-ignore short-circuiting private method return this.getDevicesFromStore(userIds); }; diff --git a/spec/unit/crypto/secrets.spec.ts b/spec/unit/crypto/secrets.spec.ts index 94293697f1b..386df0d22b0 100644 --- a/spec/unit/crypto/secrets.spec.ts +++ b/spec/unit/crypto/secrets.spec.ts @@ -437,6 +437,7 @@ describe("Secrets", function() { return [keyId, secretStorageKeys[keyId]]; } } + return null; }, }, }, @@ -571,6 +572,7 @@ describe("Secrets", function() { return [keyId, secretStorageKeys[keyId]]; } } + return null; }, }, }, diff --git a/spec/unit/crypto/verification/verification_request.spec.ts b/spec/unit/crypto/verification/verification_request.spec.ts index 0b759efb993..913fec1def8 100644 --- a/spec/unit/crypto/verification/verification_request.spec.ts +++ b/spec/unit/crypto/verification/verification_request.spec.ts @@ -131,7 +131,11 @@ function makeRemoteEcho(event) { })); } -async function distributeEvent(ownRequest, theirRequest, event) { +async function distributeEvent( + ownRequest: VerificationRequest, + theirRequest: VerificationRequest, + event: MatrixEvent, +): Promise { await ownRequest.channel.handleEvent( makeRemoteEcho(event), ownRequest, diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index 5e2d904eb20..ce8fbf32dc4 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -45,7 +45,7 @@ describe('EventTimelineSet', () => { it('should return the related events', () => { eventTimelineSet.relations.aggregateChildEvent(messageEvent); const relations = eventTimelineSet.relations.getChildEventsForEvent( - messageEvent.getId(), + messageEvent.getId()!, "m.in_reply_to", EventType.RoomMessage, ); @@ -193,7 +193,7 @@ describe('EventTimelineSet', () => { it('should not return the related events', () => { eventTimelineSet.relations.aggregateChildEvent(messageEvent); const relations = eventTimelineSet.relations.getChildEventsForEvent( - messageEvent.getId(), + messageEvent.getId()!, "m.in_reply_to", EventType.RoomMessage, ); @@ -236,7 +236,7 @@ describe('EventTimelineSet', () => { "m.relates_to": { "event_id": root.getId(), "m.in_reply_to": { - "event_id": root.getId(), + "event_id": root.getId()!, }, "rel_type": "m.thread", }, @@ -278,14 +278,14 @@ describe('EventTimelineSet', () => { }); it("should return true if the timeline set is for a thread and the event is its thread root", () => { - const thread = new Thread(messageEvent.getId(), messageEvent, { room, client }); + const thread = new Thread(messageEvent.getId()!, messageEvent, { room, client }); const eventTimelineSet = new EventTimelineSet(room, {}, client, thread); messageEvent.setThread(thread); expect(eventTimelineSet.canContain(messageEvent)).toBeTruthy(); }); it("should return true if the timeline set is for a thread and the event is a response to it", () => { - const thread = new Thread(messageEvent.getId(), messageEvent, { room, client }); + const thread = new Thread(messageEvent.getId()!, messageEvent, { room, client }); const eventTimelineSet = new EventTimelineSet(room, {}, client, thread); messageEvent.setThread(thread); const event = mkThreadResponse(messageEvent); @@ -310,7 +310,7 @@ describe('EventTimelineSet', () => { content: { body: "test" }, event_id: "!test1:server", }); - eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId()); + eventTimelineSet.handleRemoteEcho(roomMessageEvent, "~!local-event-id:server", roomMessageEvent.getId()!); expect(eventTimelineSet.getLiveTimeline().getEvents()).toContain(roomMessageEvent); const roomFilteredEvent = new MatrixEvent({ @@ -318,7 +318,7 @@ describe('EventTimelineSet', () => { content: { body: "test" }, event_id: "!test2:server", }); - eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId()); + eventTimelineSet.handleRemoteEcho(roomFilteredEvent, "~!local-event-id:server", roomFilteredEvent.getId()!); expect(eventTimelineSet.getLiveTimeline().getEvents()).not.toContain(roomFilteredEvent); }); }); diff --git a/spec/unit/event-timeline.spec.ts b/spec/unit/event-timeline.spec.ts index d96472887eb..59f35dcce02 100644 --- a/spec/unit/event-timeline.spec.ts +++ b/spec/unit/event-timeline.spec.ts @@ -341,11 +341,11 @@ describe("EventTimeline", function() { timeline.addEvent(events[1], { toStartOfTimeline: false }); expect(timeline.getEvents().length).toEqual(2); - let ev = timeline.removeEvent(events[0].getId()); + let ev = timeline.removeEvent(events[0].getId()!); expect(ev).toBe(events[0]); expect(timeline.getEvents().length).toEqual(1); - ev = timeline.removeEvent(events[1].getId()); + ev = timeline.removeEvent(events[1].getId()!); expect(ev).toBe(events[1]); expect(timeline.getEvents().length).toEqual(0); }); @@ -357,11 +357,11 @@ describe("EventTimeline", function() { expect(timeline.getEvents().length).toEqual(3); expect(timeline.getBaseIndex()).toEqual(1); - timeline.removeEvent(events[2].getId()); + timeline.removeEvent(events[2].getId()!); expect(timeline.getEvents().length).toEqual(2); expect(timeline.getBaseIndex()).toEqual(1); - timeline.removeEvent(events[1].getId()); + timeline.removeEvent(events[1].getId()!); expect(timeline.getEvents().length).toEqual(1); expect(timeline.getBaseIndex()).toEqual(0); }); @@ -372,7 +372,7 @@ describe("EventTimeline", function() { it("should not make baseIndex assplode when removing the last event", function() { timeline.addEvent(events[0], { toStartOfTimeline: true }); - timeline.removeEvent(events[0].getId()); + timeline.removeEvent(events[0].getId()!); const initialIndex = timeline.getBaseIndex(); timeline.addEvent(events[1], { toStartOfTimeline: false }); timeline.addEvent(events[2], { toStartOfTimeline: false }); diff --git a/spec/unit/event.spec.ts b/spec/unit/event.spec.ts deleted file mode 100644 index 55ac4c40c50..00000000000 --- a/spec/unit/event.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* -Copyright 2017 New Vector Ltd -Copyright 2019, 2022 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { MatrixEvent } from "../../src/models/event"; - -describe("MatrixEvent", () => { - describe(".attemptDecryption", () => { - let encryptedEvent; - const eventId = 'test_encrypted_event'; - - beforeEach(() => { - encryptedEvent = new MatrixEvent({ - event_id: eventId, - type: 'm.room.encrypted', - content: { - ciphertext: 'secrets', - }, - }); - }); - - it('should retry decryption if a retry is queued', async () => { - const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, 'attemptDecryption'); - - const crypto = { - decryptEvent: jest.fn() - .mockImplementationOnce(() => { - // schedule a second decryption attempt while - // the first one is still running. - encryptedEvent.attemptDecryption(crypto); - - const error = new Error("nope"); - error.name = 'DecryptionError'; - return Promise.reject(error); - }) - .mockImplementationOnce(() => { - return Promise.resolve({ - clearEvent: { - type: 'm.room.message', - }, - }); - }), - }; - - await encryptedEvent.attemptDecryption(crypto); - - expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2); - expect(crypto.decryptEvent).toHaveBeenCalledTimes(2); - expect(encryptedEvent.getType()).toEqual('m.room.message'); - }); - }); -}); diff --git a/spec/unit/models/MSC3089Branch.spec.ts b/spec/unit/models/MSC3089Branch.spec.ts index 1daf1c07fde..6f8fdbe4e56 100644 --- a/spec/unit/models/MSC3089Branch.spec.ts +++ b/spec/unit/models/MSC3089Branch.spec.ts @@ -312,7 +312,7 @@ describe("MSC3089Branch", () => { } as MatrixEvent); const events = [await branch.getFileEvent(), await branch2.getFileEvent(), { - replacingEventId: (): string => null, + replacingEventId: (): string | undefined => undefined, getId: () => "$unknown", }]; staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline; diff --git a/spec/unit/models/MSC3089TreeSpace.spec.ts b/spec/unit/models/MSC3089TreeSpace.spec.ts index ef099fede78..4158ea8b9fe 100644 --- a/spec/unit/models/MSC3089TreeSpace.spec.ts +++ b/spec/unit/models/MSC3089TreeSpace.spec.ts @@ -135,7 +135,7 @@ describe("MSC3089TreeSpace", () => { // noinspection ExceptionCaughtLocallyJS throw new Error("Failed to fail"); } catch (e) { - expect(e.errcode).toEqual("M_FORBIDDEN"); + expect((e).errcode).toEqual("M_FORBIDDEN"); } expect(fn).toHaveBeenCalledTimes(1); @@ -513,7 +513,7 @@ describe("MSC3089TreeSpace", () => { function expectOrder(childRoomId: string, order: number) { const child = childTrees.find(c => c.roomId === childRoomId); expect(child).toBeDefined(); - expect(child.getOrder()).toEqual(order); + expect(child!.getOrder()).toEqual(order); } function makeMockChildRoom(roomId: string): Room { @@ -608,7 +608,7 @@ describe("MSC3089TreeSpace", () => { // noinspection ExceptionCaughtLocallyJS throw new Error("Failed to fail"); } catch (e) { - expect(e.message).toEqual("Cannot set order of top level spaces currently"); + expect((e).message).toEqual("Cannot set order of top level spaces currently"); } }); @@ -706,7 +706,7 @@ describe("MSC3089TreeSpace", () => { const treeA = childTrees.find(c => c.roomId === a); expect(treeA).toBeDefined(); - await treeA.setOrder(1); + await treeA!.setOrder(1); expect(clientSendStateFn).toHaveBeenCalledTimes(3); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -743,7 +743,7 @@ describe("MSC3089TreeSpace", () => { const treeA = childTrees.find(c => c.roomId === a); expect(treeA).toBeDefined(); - await treeA.setOrder(1); + await treeA!.setOrder(1); expect(clientSendStateFn).toHaveBeenCalledTimes(1); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -771,7 +771,7 @@ describe("MSC3089TreeSpace", () => { const treeA = childTrees.find(c => c.roomId === a); expect(treeA).toBeDefined(); - await treeA.setOrder(2); + await treeA!.setOrder(2); expect(clientSendStateFn).toHaveBeenCalledTimes(1); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -800,7 +800,7 @@ describe("MSC3089TreeSpace", () => { const treeB = childTrees.find(c => c.roomId === b); expect(treeB).toBeDefined(); - await treeB.setOrder(2); + await treeB!.setOrder(2); expect(clientSendStateFn).toHaveBeenCalledTimes(1); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -829,7 +829,7 @@ describe("MSC3089TreeSpace", () => { const treeC = childTrees.find(ch => ch.roomId === c); expect(treeC).toBeDefined(); - await treeC.setOrder(1); + await treeC!.setOrder(1); expect(clientSendStateFn).toHaveBeenCalledTimes(1); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -858,7 +858,7 @@ describe("MSC3089TreeSpace", () => { const treeB = childTrees.find(ch => ch.roomId === b); expect(treeB).toBeDefined(); - await treeB.setOrder(2); + await treeB!.setOrder(2); expect(clientSendStateFn).toHaveBeenCalledTimes(2); expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({ @@ -903,7 +903,7 @@ describe("MSC3089TreeSpace", () => { url: mxc, file: fileInfo, metadata: true, // additional content from test - [UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable + [UNSTABLE_MSC3089_LEAF.unstable!]: {}, // test to ensure we're definitely using unstable }); return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase @@ -965,7 +965,7 @@ describe("MSC3089TreeSpace", () => { expect(contents).toMatchObject({ ...content, "m.new_content": content, - [UNSTABLE_MSC3089_LEAF.unstable]: {}, // test to ensure we're definitely using unstable + [UNSTABLE_MSC3089_LEAF.unstable!]: {}, // test to ensure we're definitely using unstable }); return Promise.resolve({ event_id: fileEventId }); // eslint-disable-line camelcase @@ -1010,7 +1010,7 @@ describe("MSC3089TreeSpace", () => { const file = tree.getFile(fileEventId); expect(file).toBeDefined(); - expect(file.indexEvent).toBe(fileEvent); + expect(file!.indexEvent).toBe(fileEvent); }); it('should return falsy for unknown files', () => { diff --git a/spec/unit/models/beacon.spec.ts b/spec/unit/models/beacon.spec.ts index 3b2f75a062c..2806ea26cb2 100644 --- a/spec/unit/models/beacon.spec.ts +++ b/spec/unit/models/beacon.spec.ts @@ -263,7 +263,7 @@ describe('Beacon', () => { roomId, ); // less than the original event - oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts - 1000; + oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts! - 1000; beacon.update(oldUpdateEvent); // didnt update diff --git a/spec/unit/models/event.spec.ts b/spec/unit/models/event.spec.ts index 674fd3258f6..535e0f12db6 100644 --- a/spec/unit/models/event.spec.ts +++ b/spec/unit/models/event.spec.ts @@ -115,8 +115,53 @@ describe('MatrixEvent', () => { }); const prom = emitPromise(ev, MatrixEventEvent.VisibilityChange); - ev.applyVisibilityEvent({ visible: false, eventId: ev.getId(), reason: null }); + ev.applyVisibilityEvent({ visible: false, eventId: ev.getId()!, reason: null }); await prom; }); }); + + describe(".attemptDecryption", () => { + let encryptedEvent; + const eventId = 'test_encrypted_event'; + + beforeEach(() => { + encryptedEvent = new MatrixEvent({ + event_id: eventId, + type: 'm.room.encrypted', + content: { + ciphertext: 'secrets', + }, + }); + }); + + it('should retry decryption if a retry is queued', async () => { + const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, 'attemptDecryption'); + + const crypto = { + decryptEvent: jest.fn() + .mockImplementationOnce(() => { + // schedule a second decryption attempt while + // the first one is still running. + encryptedEvent.attemptDecryption(crypto); + + const error = new Error("nope"); + error.name = 'DecryptionError'; + return Promise.reject(error); + }) + .mockImplementationOnce(() => { + return Promise.resolve({ + clearEvent: { + type: 'm.room.message', + }, + }); + }), + }; + + await encryptedEvent.attemptDecryption(crypto); + + expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2); + expect(crypto.decryptEvent).toHaveBeenCalledTimes(2); + expect(encryptedEvent.getType()).toEqual('m.room.message'); + }); + }); }); diff --git a/spec/unit/queueToDevice.spec.ts b/spec/unit/queueToDevice.spec.ts index c5b1f8a29d2..4b3050a15c7 100644 --- a/spec/unit/queueToDevice.spec.ts +++ b/spec/unit/queueToDevice.spec.ts @@ -140,11 +140,11 @@ describe.each([ ], }); await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); - expect(httpBackend.flushSync(null, 1)).toEqual(1); + expect(httpBackend.flushSync(undefined, 1)).toEqual(1); await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); - expect(httpBackend.flushSync(null, 1)).toEqual(1); + expect(httpBackend.flushSync(undefined, 1)).toEqual(1); // flush, as per comment in first test await flushPromises(); @@ -164,7 +164,7 @@ describe.each([ ], }); await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); - expect(httpBackend.flushSync(null, 1)).toEqual(1); + expect(httpBackend.flushSync(undefined, 1)).toEqual(1); // Asserting that another request is never made is obviously // a bit tricky - we just flush the queue what should hopefully @@ -200,7 +200,7 @@ describe.each([ ], }); await flushAndRunTimersUntil(() => httpBackend.requests.length > 0); - expect(httpBackend.flushSync(null, 1)).toEqual(1); + expect(httpBackend.flushSync(undefined, 1)).toEqual(1); await flushPromises(); logger.info("Advancing clock to just before expected retry time..."); @@ -215,7 +215,7 @@ describe.each([ jest.advanceTimersByTime(2000); await flushPromises(); - expect(httpBackend.flushSync(null, 1)).toEqual(1); + expect(httpBackend.flushSync(undefined, 1)).toEqual(1); }); it("retries on retryImmediately()", async function() { @@ -223,7 +223,7 @@ describe.each([ versions: ["r0.0.1"], }); - await Promise.all([client.startClient(), httpBackend.flush(null, 1, 20)]); + await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]); httpBackend.when( "PUT", "/sendToDevice/org.example.foo/", @@ -239,13 +239,13 @@ describe.each([ FAKE_MSG, ], }); - expect(await httpBackend.flush(null, 1, 1)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1); await flushPromises(); client.retryImmediately(); // longer timeout here to try & avoid flakiness - expect(await httpBackend.flush(null, 1, 3000)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 3000)).toEqual(1); }); it("retries on when client is started", async function() { @@ -269,13 +269,13 @@ describe.each([ FAKE_MSG, ], }); - expect(await httpBackend.flush(null, 1, 1)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1); await flushPromises(); client.stopClient(); await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]); - expect(await httpBackend.flush(null, 1, 20)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1); }); it("retries when a message is retried", async function() { @@ -283,7 +283,7 @@ describe.each([ versions: ["r0.0.1"], }); - await Promise.all([client.startClient(), httpBackend.flush(null, 1, 20)]); + await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]); httpBackend.when( "PUT", "/sendToDevice/org.example.foo/", @@ -300,7 +300,7 @@ describe.each([ ], }); - expect(await httpBackend.flush(null, 1, 1)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1); await flushPromises(); const dummyEvent = new MatrixEvent({ @@ -311,7 +311,7 @@ describe.each([ } as unknown as Room; client.resendEvent(dummyEvent, mockRoom); - expect(await httpBackend.flush(null, 1, 20)).toEqual(1); + expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1); }); it("splits many messages into multiple HTTP requests", async function() { diff --git a/spec/unit/read-receipt.spec.ts b/spec/unit/read-receipt.spec.ts index 07acaa184dd..78f57ea7d3c 100644 --- a/spec/unit/read-receipt.spec.ts +++ b/spec/unit/read-receipt.spec.ts @@ -97,7 +97,7 @@ describe("Read receipt", () => { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { $roomId: ROOM_ID, $receiptType: ReceiptType.Read, - $eventId: threadEvent.getId(), + $eventId: threadEvent.getId()!, }), ).check((request) => { expect(request.data.thread_id).toEqual(THREAD_ID); @@ -115,7 +115,7 @@ describe("Read receipt", () => { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { $roomId: ROOM_ID, $receiptType: ReceiptType.Read, - $eventId: roomEvent.getId(), + $eventId: roomEvent.getId()!, }), ).check((request) => { expect(request.data.thread_id).toEqual(MAIN_ROOM_TIMELINE); @@ -133,7 +133,7 @@ describe("Read receipt", () => { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { $roomId: ROOM_ID, $receiptType: ReceiptType.Read, - $eventId: threadEvent.getId(), + $eventId: threadEvent.getId()!, }), ).check((request) => { expect(request.data.thread_id).toBeUndefined(); @@ -151,7 +151,7 @@ describe("Read receipt", () => { "POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", { $roomId: ROOM_ID, $receiptType: ReceiptType.Read, - $eventId: threadEvent.getId(), + $eventId: threadEvent.getId()!, }), ).check((request) => { expect(request.data).toEqual({}); diff --git a/spec/unit/realtime-callbacks.spec.ts b/spec/unit/realtime-callbacks.spec.ts index 9d3acf1ae83..dd0d605a0cc 100644 --- a/spec/unit/realtime-callbacks.spec.ts +++ b/spec/unit/realtime-callbacks.spec.ts @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + import * as callbacks from "../../src/realtime-callbacks"; let wallTime = 1234567890; @@ -37,7 +53,7 @@ describe("realtime-callbacks", function() { it("should set 'this' to the global object", function() { let passed = false; - const callback = function() { + const callback = function(this: typeof global) { expect(this).toBe(global); // eslint-disable-line @typescript-eslint/no-invalid-this expect(this.console).toBeTruthy(); // eslint-disable-line @typescript-eslint/no-invalid-this passed = true; diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts index 9826264fb62..022f8c0c503 100644 --- a/spec/unit/relations.spec.ts +++ b/spec/unit/relations.spec.ts @@ -22,7 +22,7 @@ import { TestClient } from "../TestClient"; describe("Relations", function() { it("should deduplicate annotations", function() { - const room = new Room("room123", null, null); + const room = new Room("room123", null!, null!); const relations = new Relations("m.annotation", "m.reaction", room); // Create an instance of an annotation @@ -99,7 +99,7 @@ describe("Relations", function() { // Add the target event first, then the relation event { - const room = new Room("room123", null, null); + const room = new Room("room123", null!, null!); const relationsCreated = new Promise(resolve => { targetEvent.once(MatrixEventEvent.RelationsCreated, resolve); }); @@ -113,7 +113,7 @@ describe("Relations", function() { // Add the relation event first, then the target event { - const room = new Room("room123", null, null); + const room = new Room("room123", null!, null!); const relationsCreated = new Promise(resolve => { targetEvent.once(MatrixEventEvent.RelationsCreated, resolve); }); @@ -127,7 +127,7 @@ describe("Relations", function() { }); it("should re-use Relations between all timeline sets in a room", async () => { - const room = new Room("room123", null, null); + const room = new Room("room123", null!, null!); const timelineSet1 = new EventTimelineSet(room); const timelineSet2 = new EventTimelineSet(room); expect(room.relations).toBe(timelineSet1.relations); @@ -136,7 +136,7 @@ describe("Relations", function() { it("should ignore m.replace for state events", async () => { const userId = "@bob:example.com"; - const room = new Room("room123", null, userId); + const room = new Room("room123", null!, userId); const relations = new Relations("m.replace", "m.room.topic", room); // Create an instance of a state event with rel_type m.replace diff --git a/spec/unit/room-state.spec.ts b/spec/unit/room-state.spec.ts index a990d62945f..5ee44f6116f 100644 --- a/spec/unit/room-state.spec.ts +++ b/spec/unit/room-state.spec.ts @@ -172,7 +172,7 @@ describe("RoomState", function() { state.on(RoomStateEvent.Members, function(ev, st, mem) { expect(ev).toEqual(memberEvents[emitCount]); expect(st).toEqual(state); - expect(mem).toEqual(state.getMember(ev.getSender())); + expect(mem).toEqual(state.getMember(ev.getSender()!)); emitCount += 1; }); state.setStateEvents(memberEvents); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index ae85ee0dfad..7f1c1b7db78 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -24,7 +24,7 @@ import { DuplicateStrategy, EventStatus, EventTimelineSet, - EventType, + EventType, IStateEventWithRoomId, JoinRule, MatrixEvent, MatrixEventEvent, @@ -66,7 +66,7 @@ describe("Room", function() { "body": "Reply :: " + Math.random(), "m.relates_to": { "m.in_reply_to": { - "event_id": target.getId(), + "event_id": target.getId()!, }, }, }, @@ -84,7 +84,7 @@ describe("Room", function() { }, "m.relates_to": { rel_type: RelationType.Replace, - event_id: target.getId(), + event_id: target.getId()!, }, }, }, room.client); @@ -97,9 +97,9 @@ describe("Room", function() { content: { "body": "Thread response :: " + Math.random(), "m.relates_to": { - "event_id": root.getId(), + "event_id": root.getId()!, "m.in_reply_to": { - "event_id": root.getId(), + "event_id": root.getId()!, }, "rel_type": "m.thread", }, @@ -114,7 +114,7 @@ describe("Room", function() { content: { "m.relates_to": { "rel_type": RelationType.Annotation, - "event_id": target.getId(), + "event_id": target.getId()!, "key": Math.random().toString(), }, }, @@ -125,7 +125,7 @@ describe("Room", function() { type: EventType.RoomRedaction, user: userA, room: roomId, - redacts: target.getId(), + redacts: target.getId()!, content: {}, }, room.client); @@ -722,13 +722,13 @@ describe("Room", function() { it("should handle events in the same timeline", function() { room.addLiveEvents(events); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId())) .toBeLessThan(0); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[2].getId()!, events[1].getId())) .toBeGreaterThan(0); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[1].getId())) .toEqual(0); }); @@ -741,10 +741,10 @@ describe("Room", function() { room.addEventsToTimeline([events[0]], false, oldTimeline); room.addLiveEvents([events[1]]); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId())) .toBeLessThan(0); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId())) .toBeGreaterThan(0); }); @@ -755,10 +755,10 @@ describe("Room", function() { room.addEventsToTimeline([events[0]], false, oldTimeline); room.addLiveEvents([events[1]]); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId())) .toBe(null); - expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId(), + expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[1].getId()!, events[0].getId())) .toBe(null); }); @@ -767,13 +767,13 @@ describe("Room", function() { room.addLiveEvents(events); expect(room.getUnfilteredTimelineSet() - .compareEventOrdering(events[0].getId(), "xxx")) + .compareEventOrdering(events[0].getId()!, "xxx")) .toBe(null); expect(room.getUnfilteredTimelineSet() .compareEventOrdering("xxx", events[0].getId())) .toBe(null); expect(room.getUnfilteredTimelineSet() - .compareEventOrdering(events[0].getId(), events[0].getId())) + .compareEventOrdering(events[0].getId()!, events[0].getId())) .toBe(0); }); }); @@ -1228,7 +1228,7 @@ describe("Room", function() { it("should store the receipt so it can be obtained via getReceiptsForEvent", function() { const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), ])); expect(room.getReceiptsForEvent(eventToAck)).toEqual([{ type: "m.read", @@ -1247,7 +1247,7 @@ describe("Room", function() { const ts = 13787898424; const receiptEvent = mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), ]); room.addReceipt(receiptEvent); @@ -1261,11 +1261,11 @@ describe("Room", function() { }); const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), ])); const ts2 = 13787899999; room.addReceipt(mkReceipt(roomId, [ - mkRecord(nextEventToAck.getId(), "m.read", userB, ts2), + mkRecord(nextEventToAck.getId()!, "m.read", userB, ts2), ])); expect(room.getReceiptsForEvent(eventToAck)).toEqual([]); expect(room.getReceiptsForEvent(nextEventToAck)).toEqual([{ @@ -1280,9 +1280,9 @@ describe("Room", function() { it("should persist multiple receipts for a single event ID", function() { const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), - mkRecord(eventToAck.getId(), "m.read", userC, ts), - mkRecord(eventToAck.getId(), "m.read", userD, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userC, ts), + mkRecord(eventToAck.getId()!, "m.read", userD, ts), ])); expect(room.getUsersReadUpTo(eventToAck)).toEqual( [userB, userC, userD], @@ -1300,9 +1300,9 @@ describe("Room", function() { }); const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), - mkRecord(eventTwo.getId(), "m.read", userC, ts), - mkRecord(eventThree.getId(), "m.read", userD, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), + mkRecord(eventTwo.getId()!, "m.read", userC, ts), + mkRecord(eventThree.getId()!, "m.read", userD, ts), ])); expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]); expect(room.getUsersReadUpTo(eventTwo)).toEqual([userC]); @@ -1311,9 +1311,9 @@ describe("Room", function() { it("should persist multiple receipts for a single user ID", function() { room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.delivered", userB, 13787898424), - mkRecord(eventToAck.getId(), "m.read", userB, 22222222), - mkRecord(eventToAck.getId(), "m.seen", userB, 33333333), + mkRecord(eventToAck.getId()!, "m.delivered", userB, 13787898424), + mkRecord(eventToAck.getId()!, "m.read", userB, 22222222), + mkRecord(eventToAck.getId()!, "m.seen", userB, 33333333), ])); expect(room.getReceiptsForEvent(eventToAck)).toEqual([ { @@ -1361,19 +1361,19 @@ describe("Room", function() { // check it initialises correctly room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[0].getId(), "m.read", userB, ts), + mkRecord(events[0].getId()!, "m.read", userB, ts), ])); expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId()); // 2>0, so it should move forward room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[2].getId(), "m.read", userB, ts), + mkRecord(events[2].getId()!, "m.read", userB, ts), ])); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); // 1<2, so it should stay put room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[1].getId(), "m.read", userB, ts), + mkRecord(events[1].getId()!, "m.read", userB, ts), ])); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); }); @@ -1399,13 +1399,13 @@ describe("Room", function() { // check it initialises correctly room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[0].getId(), "m.read", userB, ts), + mkRecord(events[0].getId()!, "m.read", userB, ts), ])); expect(room.getEventReadUpTo(userB)).toEqual(events[0].getId()); // 2>0, so it should move forward room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[2].getId(), "m.read", userB, ts), + mkRecord(events[2].getId()!, "m.read", userB, ts), ]), true); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); expect(room.getReceiptsForEvent(events[2])).toEqual([ @@ -1414,7 +1414,7 @@ describe("Room", function() { // 1<2, so it should stay put room.addReceipt(mkReceipt(roomId, [ - mkRecord(events[1].getId(), "m.read", userB, ts), + mkRecord(events[1].getId()!, "m.read", userB, ts), ])); expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); expect(room.getEventReadUpTo(userB, true)).toEqual(events[1].getId()); @@ -1428,7 +1428,7 @@ describe("Room", function() { it("should return user IDs read up to the given event", function() { const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), ])); expect(room.getUsersReadUpTo(eventToAck)).toEqual([userB]); }); @@ -1438,9 +1438,9 @@ describe("Room", function() { it("should acknowledge if an event has been read", function() { const ts = 13787898424; room.addReceipt(mkReceipt(roomId, [ - mkRecord(eventToAck.getId(), "m.read", userB, ts), + mkRecord(eventToAck.getId()!, "m.read", userB, ts), ])); - expect(room.hasUserReadEvent(userB, eventToAck.getId())).toEqual(true); + expect(room.hasUserReadEvent(userB, eventToAck.getId()!)).toEqual(true); }); it("return false for an unknown event", function() { expect(room.hasUserReadEvent(userB, "unknown_event")).toEqual(false); @@ -1556,7 +1556,7 @@ describe("Room", function() { user: userA, type: EventType.RoomRedaction, content: {}, - redacts: eventA.getId(), + redacts: eventA.getId()!, event: true, }); redactA.status = EventStatus.SENDING; @@ -1609,7 +1609,7 @@ describe("Room", function() { }); it("should remove cancelled events from the timeline", function() { - const room = new Room(roomId, null, userA); + const room = new Room(roomId, null!, userA); const eventA = utils.mkMessage({ room: roomId, user: userA, event: true, }); @@ -1643,7 +1643,7 @@ describe("Room", function() { }); describe("loadMembersIfNeeded", function() { - function createClientMock(serverResponse, storageResponse = null) { + function createClientMock(serverResponse, storageResponse: MatrixEvent[] | Error | null = null) { return { getEventMapper: function() { // events should already be MatrixEvents @@ -1664,7 +1664,7 @@ describe("Room", function() { }), store: { storageResponse, - storedMembers: null, + storedMembers: [] as IStateEventWithRoomId[] | null, getOutOfBandMembers: function() { if (this.storageResponse instanceof Error) { return Promise.reject(this.storageResponse); @@ -1693,11 +1693,11 @@ describe("Room", function() { it("should load members from server on first call", async function() { const client = createClientMock([memberEvent]); - const room = new Room(roomId, client as any, null, { lazyLoadMembers: true }); + const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true }); await room.loadMembersIfNeeded(); - const memberA = room.getMember("@user_a:bar"); + const memberA = room.getMember("@user_a:bar")!; expect(memberA.name).toEqual("User A"); - const storedMembers = client.store.storedMembers; + const storedMembers = client.store.storedMembers!; expect(storedMembers.length).toEqual(1); expect(storedMembers[0].event_id).toEqual(memberEvent.getId()); }); @@ -1711,17 +1711,17 @@ describe("Room", function() { name: "Ms A", }); const client = createClientMock([memberEvent2], [memberEvent]); - const room = new Room(roomId, client as any, null, { lazyLoadMembers: true }); + const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true }); await room.loadMembersIfNeeded(); - const memberA = room.getMember("@user_a:bar"); + const memberA = room.getMember("@user_a:bar")!; expect(memberA.name).toEqual("User A"); }); it("should allow retry on error", async function() { const client = createClientMock(new Error("server says no")); - const room = new Room(roomId, client as any, null, { lazyLoadMembers: true }); + const room = new Room(roomId, client as any, null!, { lazyLoadMembers: true }); let hasThrown = false; try { await room.loadMembersIfNeeded(); @@ -1740,27 +1740,68 @@ describe("Room", function() { describe("getMyMembership", function() { it("should return synced membership if membership isn't available yet", function() { - const room = new Room(roomId, null, userA); + const room = new Room(roomId, null!, userA); room.updateMyMembership(JoinRule.Invite); expect(room.getMyMembership()).toEqual(JoinRule.Invite); }); - it("should emit a Room.myMembership event on a change", - function() { - const room = new Room(roomId, null, userA); - const events = []; - room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => { - events.push({ membership, oldMembership }); - }); - room.updateMyMembership(JoinRule.Invite); - expect(room.getMyMembership()).toEqual(JoinRule.Invite); - expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined }); - events.splice(0); //clear - room.updateMyMembership(JoinRule.Invite); - expect(events.length).toEqual(0); - room.updateMyMembership("join"); - expect(room.getMyMembership()).toEqual("join"); - expect(events[0]).toEqual({ membership: "join", oldMembership: "invite" }); + it("should emit a Room.myMembership event on a change", function() { + const room = new Room(roomId, null!, userA); + const events: { + membership: string; + oldMembership?: string; + }[] = []; + room.on(RoomEvent.MyMembership, (_room, membership, oldMembership) => { + events.push({ membership, oldMembership }); + }); + room.updateMyMembership(JoinRule.Invite); + expect(room.getMyMembership()).toEqual(JoinRule.Invite); + expect(events[0]).toEqual({ membership: "invite", oldMembership: undefined }); + events.splice(0); //clear + room.updateMyMembership(JoinRule.Invite); + expect(events.length).toEqual(0); + room.updateMyMembership("join"); + expect(room.getMyMembership()).toEqual("join"); + expect(events[0]).toEqual({ membership: "join", oldMembership: "invite" }); + }); + }); + + describe("getDMInviter", () => { + it("should delegate to RoomMember::getDMInviter if available", () => { + const room = new Room(roomId, null!, userA); + room.currentState.markOutOfBandMembersStarted(); + room.currentState.setOutOfBandMembers([ + new MatrixEvent({ + type: EventType.RoomMember, + state_key: userA, + sender: userB, + content: { + membership: "invite", + is_direct: true, + }, + }), + ]); + + expect(room.getDMInviter()).toBe(userB); + }); + + it("should fall back to summary heroes and return the first one", () => { + const room = new Room(roomId, null!, userA); + room.updateMyMembership("invite"); + room.setSummary({ + "m.heroes": [userA, userC], + "m.joined_member_count": 1, + "m.invited_member_count": 1, }); + + expect(room.getDMInviter()).toBe(userC); + }); + + it("should return undefined if we're not joined or invited to the room", () => { + const room = new Room(roomId, null!, userA); + expect(room.getDMInviter()).toBeUndefined(); + room.updateMyMembership("leave"); + expect(room.getDMInviter()).toBeUndefined(); + }); }); describe("guessDMUserId", function() { @@ -1789,6 +1830,36 @@ describe("Room", function() { }); }); + describe("getAvatarFallbackMember", () => { + it("should should return undefined if the room isn't a 1:1", () => { + const room = new Room(roomId, null!, userA); + room.currentState.setJoinedMemberCount(2); + room.currentState.setInvitedMemberCount(1); + expect(room.getAvatarFallbackMember()).toBeUndefined(); + }); + + it("should use summary heroes member if 1:1", () => { + const room = new Room(roomId, null!, userA); + room.currentState.markOutOfBandMembersStarted(); + room.currentState.setOutOfBandMembers([ + new MatrixEvent({ + type: EventType.RoomMember, + state_key: userD, + sender: userD, + content: { + membership: "join", + }, + }), + ]); + room.setSummary({ + "m.heroes": [userA, userD], + "m.joined_member_count": 1, + "m.invited_member_count": 1, + }); + expect(room.getAvatarFallbackMember()?.userId).toBe(userD); + }); + }); + describe("maySendMessage", function() { it("should return false if synced membership not join", function() { const room = new Room(roomId, { isRoomEncrypted: () => false } as any, userA); @@ -2118,7 +2189,7 @@ describe("Room", function() { }, }); - expect(() => room.createThread(rootEvent.getId(), rootEvent, [])).not.toThrow(); + expect(() => room.createThread(rootEvent.getId()!, rootEvent, [])).not.toThrow(); }); it("creating thread from edited event should not conflate old versions of the event", () => { @@ -2339,7 +2410,7 @@ describe("Room", function() { const threadReaction2 = mkReaction(threadRoot); const threadReaction2Redaction = mkRedaction(threadReaction2); - const roots = new Set([threadRoot.getId()]); + const roots = new Set([threadRoot.getId()!]); const events = [ randomMessage, threadRoot, @@ -2377,7 +2448,7 @@ describe("Room", function() { const threadReaction2 = mkReaction(threadResponse1); const threadReaction2Redaction = mkRedaction(threadReaction2); - const roots = new Set([threadRoot.getId()]); + const roots = new Set([threadRoot.getId()!]); const events = [threadRoot, threadResponse1, threadReaction1, threadReaction2, threadReaction2Redaction]; expect(room.eventShouldLiveIn(threadReaction1, events, roots).shouldLiveInRoom).toBeFalsy(); @@ -2399,7 +2470,7 @@ describe("Room", function() { const reaction2 = mkReaction(reply1); const reaction2Redaction = mkRedaction(reply1); - const roots = new Set([threadRoot.getId()]); + const roots = new Set([threadRoot.getId()!]); const events = [ threadRoot, threadResponse1, @@ -2425,7 +2496,7 @@ describe("Room", function() { const reply1 = mkReply(threadRoot); const reply2 = mkReply(reply1); - const roots = new Set([threadRoot.getId()]); + const roots = new Set([threadRoot.getId()!]); const events = [ threadRoot, threadResponse1, @@ -2459,7 +2530,7 @@ describe("Room", function() { expect(thread.rootEvent).toBe(threadRoot); const rootRelations = thread.timelineSet.relations.getChildEventsForEvent( - threadRoot.getId(), + threadRoot.getId()!, RelationType.Annotation, EventType.Reaction, )!.getSortedAnnotationsByKey(); @@ -2469,7 +2540,7 @@ describe("Room", function() { expect(rootRelations![0][1].has(rootReaction)).toBeTruthy(); const responseRelations = thread.timelineSet.relations.getChildEventsForEvent( - threadResponse.getId(), + threadResponse.getId()!, RelationType.Annotation, EventType.Reaction, )!.getSortedAnnotationsByKey(); @@ -2744,7 +2815,7 @@ describe("Room", function() { expect(pendingEvents[1].isBeingDecrypted()).toBeFalsy(); expect(pendingEvents[1].isEncrypted()).toBeTruthy(); for (const ev of pendingEvents) { - expect(room.getPendingEvent(ev.getId())).toBe(ev); + expect(room.getPendingEvent(ev.getId()!)).toBe(ev); } }); }); diff --git a/spec/unit/scheduler.spec.ts b/spec/unit/scheduler.spec.ts index 6a02111f403..59c2d0a1d45 100644 --- a/spec/unit/scheduler.spec.ts +++ b/spec/unit/scheduler.spec.ts @@ -160,10 +160,10 @@ describe("MatrixScheduler", function() { const eventD = utils.mkMessage({ user: "@b:bar", room: roomId, event: true }); const buckets = {}; - buckets[eventA.getId()] = "queue_A"; - buckets[eventD.getId()] = "queue_A"; - buckets[eventB.getId()] = "queue_B"; - buckets[eventC.getId()] = "queue_B"; + buckets[eventA.getId()!] = "queue_A"; + buckets[eventD.getId()!] = "queue_A"; + buckets[eventB.getId()!] = "queue_B"; + buckets[eventC.getId()!] = "queue_B"; retryFn = function() { return 0; diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index 91dbafd443b..6822e3d1e98 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -62,7 +62,7 @@ export class ReEmitter { if (!reEmittersByEvent) return; // We were never re-emitting these events in the first place for (const eventName of eventNames) { - source.off(eventName, reEmittersByEvent.get(eventName)); + source.off(eventName, reEmittersByEvent.get(eventName)!); reEmittersByEvent.delete(eventName); } diff --git a/src/client.ts b/src/client.ts index 40be762599c..0bc763e363e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -59,7 +59,11 @@ import { retryNetworkOperation, ClientPrefix, MediaPrefix, - IdentityPrefix, IHttpOpts, FileType, UploadResponse, + IdentityPrefix, + IHttpOpts, + FileType, + UploadResponse, + HTTPError, } from "./http-api"; import { Crypto, @@ -909,25 +913,25 @@ export class MatrixClient extends TypedEventEmitter(this); - public olmVersion: [number, number, number] = null; // populated after initCrypto + public olmVersion: [number, number, number] | null = null; // populated after initCrypto public usingExternalCrypto = false; public store: Store; public deviceId: string | null; - public credentials: { userId?: string }; + public credentials: { userId: string | null }; public pickleKey?: string; - public scheduler: MatrixScheduler; + public scheduler?: MatrixScheduler; public clientRunning = false; public timelineSupport = false; public urlPreviewCache: { [key: string]: Promise } = {}; - public identityServer: IIdentityServerProvider; + public identityServer?: IIdentityServerProvider; public http: MatrixHttpApi; // XXX: Intended private, used in code. public crypto?: Crypto; // XXX: Intended private, used in code. public cryptoCallbacks: ICryptoCallbacks; // XXX: Intended private, used in code. - public callEventHandler: CallEventHandler; // XXX: Intended private, used in code. + public callEventHandler?: CallEventHandler; // XXX: Intended private, used in code. public supportsCallTransfer = false; // XXX: Intended private, used in code. public forceTURN = false; // XXX: Intended private, used in code. public iceCandidatePoolSize = 0; // XXX: Intended private, used in code. - public idBaseUrl: string; + public idBaseUrl?: string; public baseUrl: string; // Note: these are all `protected` to let downstream consumers make mistakes if they want to. @@ -938,8 +942,8 @@ export class MatrixClient extends TypedEventEmitter, errorTs?: number}} = {}; protected notifTimelineSet: EventTimelineSet | null = null; - protected cryptoStore: CryptoStore; - protected verificationMethods: VerificationMethod[]; + protected cryptoStore?: CryptoStore; + protected verificationMethods?: VerificationMethod[]; protected fallbackICEServerAllowed = false; protected roomList: RoomList; protected syncApi?: SlidingSyncSdk | SyncApi; @@ -960,16 +964,16 @@ export class MatrixClient extends TypedEventEmitter; - public cachedCapabilities: { + public cachedCapabilities?: { capabilities: ICapabilities; expiration: number; }; - protected clientWellKnown: IClientWellKnown; - protected clientWellKnownPromise: Promise; + protected clientWellKnown?: IClientWellKnown; + protected clientWellKnownPromise?: Promise; protected turnServers: ITurnServer[] = []; protected turnServersExpiry = 0; - protected checkTurnServersIntervalID: ReturnType | null = null; - protected exportedOlmDeviceToImport: IExportedOlmDevice; + protected checkTurnServersIntervalID?: ReturnType; + protected exportedOlmDeviceToImport?: IExportedOlmDevice; protected txnCtr = 0; protected mediaHandler = new MediaHandler(this); protected pendingEventEncryption = new Map>(); @@ -1096,7 +1100,7 @@ export class MatrixClient extends TypedEventEmitter { + public async getDehydratedDevice(): Promise { try { return await this.http.authedRequest( Method.Get, @@ -1345,7 +1348,7 @@ export class MatrixClient extends TypedEventEmitter[] = []; promises.push(this.store.deleteAllData()); if (this.cryptoStore) { @@ -1465,7 +1468,7 @@ export class MatrixClient extends TypedEventEmitter { + return this.http.authedRequest<{ + capabilities?: ICapabilities; + }>(Method.Get, "/capabilities").catch((e: Error): void => { // We swallow errors because we need a default object anyhow logger.error(e); - }).then((r: { capabilities?: ICapabilities } = {}) => { + }).then((r = {}) => { const capabilities: ICapabilities = r["capabilities"] || {}; // If the capabilities missed the cache, cache it for a shorter amount @@ -1703,7 +1708,7 @@ export class MatrixClient extends TypedEventEmitter} */ - public async getEventSenderDeviceInfo(event: MatrixEvent): Promise { + public async getEventSenderDeviceInfo(event: MatrixEvent): Promise { if (!this.crypto) { return null; } @@ -2519,7 +2524,10 @@ export class MatrixClient extends TypedEventEmitter { - return event.cancelAndResendKeyRequest(this.crypto, this.getUserId()); + if (!this.crypto) { + throw new Error("End-to-End encryption disabled"); + } + return event.cancelAndResendKeyRequest(this.crypto, this.getUserId()!); } /** @@ -2857,10 +2865,10 @@ export class MatrixClient extends TypedEventEmitter { const privKey = await keyFromAuthData(backupInfo.auth_data, password); - return this.restoreKeyBackup( - privKey, targetRoomId, targetSessionId, backupInfo, opts, - ); + return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts); } /** @@ -3059,9 +3065,7 @@ export class MatrixClient extends TypedEventEmitter { + if (!this.crypto) { + throw new Error("End-to-end encryption disabled"); + } const privKey = await this.crypto.getSessionBackupPrivateKey(); if (!privKey) { throw new Error("Couldn't get key"); } - return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts); + return this.restoreKeyBackup(privKey, targetRoomId!, targetSessionId!, backupInfo, opts); } private async restoreKeyBackup( @@ -3179,7 +3186,7 @@ export class MatrixClient extends TypedEventEmitter { return privKey; }); @@ -3228,16 +3235,16 @@ export class MatrixClient extends TypedEventEmittere).data?.errcode === 'M_NOT_FOUND') { return null; } throw e; @@ -3527,9 +3534,9 @@ export class MatrixClient extends TypedEventEmitter(Method.Post, path, queryString, data); - const roomId = res['room_id']; + const roomId = res.room_id; const syncApi = new SyncApi(this, this.clientOpts); const room = syncApi.createRoom(roomId); if (opts.syncRoom) { @@ -3571,7 +3578,7 @@ export class MatrixClient extends TypedEventEmitter e.getId() === targetId); target?.once(MatrixEventEvent.LocalEventIdReplaced, () => { - localEvent.updateAssociatedId(target.getId()); + localEvent.updateAssociatedId(target.getId()!); }); } @@ -3882,10 +3889,10 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.pendingEventEncryption.has(event.getId())) { + if (!this.pendingEventEncryption.has(event.getId()!)) { // cancelled via MatrixClient::cancelPendingEvent cancelled = true; return; @@ -3951,7 +3958,7 @@ export class MatrixClient extends TypedEventEmitter 0) { + if (event.getStateKey() && event.getStateKey()!.length > 0) { pathTemplate = "/rooms/$roomId/state/$eventType/$stateKey"; } path = utils.encodeUri(pathTemplate, pathParams); } else if (event.isRedaction()) { const pathTemplate = `/rooms/$roomId/redact/$redactsEventId/$txnId`; - path = utils.encodeUri(pathTemplate, Object.assign({ - $redactsEventId: event.event.redacts, - }, pathParams)); + path = utils.encodeUri(pathTemplate, { + $redactsEventId: event.event.redacts!, + ...pathParams, + }); } else { path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams); } @@ -4386,7 +4397,7 @@ export class MatrixClient extends TypedEventEmitter(Method.Post, path, undefined, body || {}); const room = this.getRoom(event.getRoomId()); if (room && this.credentials.userId) { @@ -4510,9 +4521,9 @@ export class MatrixClient extends TypedEventEmitter { if (!event) return; - const eventId = event.getId(); + const eventId = event.getId()!; const room = this.getRoom(event.getRoomId()); - if (room && room.hasPendingEvent(eventId)) { + if (room?.hasPendingEvent(eventId)) { throw new Error(`Cannot set read receipt to a pending event (${eventId})`); } @@ -4545,23 +4556,23 @@ export class MatrixClient extends TypedEventEmitter { return this.leave(roomId).then(() => { - populationResults[roomId] = null; + delete populationResults[roomId]; }).catch((err) => { populationResults[roomId] = err; return null; // suppress error @@ -4925,7 +4936,7 @@ export class MatrixClient extends TypedEventEmitter; public setProfileInfo(info: "avatar_url" | "displayname", data: object): Promise<{}> { const path = utils.encodeUri("/profile/$userId/$info", { - $userId: this.credentials.userId, + $userId: this.credentials.userId!, $info: info, }); return this.http.authedRequest(Method.Put, path, undefined, data); @@ -4957,7 +4968,7 @@ export class MatrixClient extends TypedEventEmitter { const prom = await this.setProfileInfo("displayname", { displayname: name }); // XXX: synthesise a profile update for ourselves because Synapse is broken and won't - const user = this.getUser(this.getUserId()); + const user = this.getUser(this.getUserId()!); if (user) { user.displayName = name; user.emit(UserEvent.DisplayName, user.events.presence, user); @@ -4973,7 +4984,7 @@ export class MatrixClient extends TypedEventEmitter { const prom = await this.setProfileInfo("avatar_url", { avatar_url: url }); // XXX: synthesise a profile update for ourselves because Synapse is broken and won't - const user = this.getUser(this.getUserId()); + const user = this.getUser(this.getUserId()!); if (user) { user.avatarUrl = url; user.emit(UserEvent.AvatarUrl, user.events.presence, user); @@ -5227,7 +5238,7 @@ export class MatrixClient extends TypedEventEmitter this.processRoomEventsSearch(searchResults, res)) .finally(() => { - searchResults.pendingRequest = null; + searchResults.pendingRequest = undefined; }); searchResults.pendingRequest = promise; @@ -6127,7 +6138,7 @@ export class MatrixClient extends TypedEventEmitter { const path = utils.encodeUri("/user/$userId/filter", { - $userId: this.credentials.userId, + $userId: this.credentials.userId!, }); return this.http.authedRequest(Method.Post, path, undefined, content) .then((response) => { @@ -6264,7 +6275,7 @@ export class MatrixClient extends TypedEventEmitter { const path = utils.encodeUri("/user/$userId/openid/request_token", { - $userId: this.credentials.userId, + $userId: this.credentials.userId!, }); return this.http.authedRequest(Method.Post, path, undefined, {}); @@ -6284,7 +6295,7 @@ export class MatrixClient extends TypedEventEmitter { if (this.isInitialSyncComplete()) { - this.callEventHandler.start(); + this.callEventHandler?.start(); this.off(ClientEvent.Sync, this.startCallEventHandler); } }; @@ -6308,18 +6319,18 @@ export class MatrixClient extends TypedEventEmitter { + public async checkTurnServers(): Promise { if (!this.canSupportVoip) { return; } @@ -6349,15 +6360,15 @@ export class MatrixClient extends TypedEventEmittererr).httpStatus === 403) { // We got a 403, so there's no point in looping forever. logger.info("TURN access unavailable for this account: stopping credentials checks"); if (this.checkTurnServersIntervalID !== null) global.clearInterval(this.checkTurnServersIntervalID); - this.checkTurnServersIntervalID = null; - this.emit(ClientEvent.TurnServersError, err, true); // fatal + this.checkTurnServersIntervalID = undefined; + this.emit(ClientEvent.TurnServersError, err, true); // fatal } else { // otherwise, if we failed for whatever reason, try again the next time we're called. - this.emit(ClientEvent.TurnServersError, err, false); // non-fatal + this.emit(ClientEvent.TurnServersError, err, false); // non-fatal } } } @@ -6399,9 +6410,9 @@ export class MatrixClient extends TypedEventEmitter( Method.Get, path, undefined, undefined, { prefix: '' }, - ).then(r => r['admin']); // pull out the specific boolean we want + ).then(r => r.admin); // pull out the specific boolean we want } /** @@ -6441,12 +6452,15 @@ export class MatrixClient extends TypedEventEmitter { - return this.clientWellKnownPromise; + if (!this.clientRunning) { + throw new Error("Client is not running"); + } + return this.clientWellKnownPromise!; } /** @@ -6817,9 +6831,8 @@ export class MatrixClient extends TypedEventEmitter { const path = utils.encodeUri("/rooms/$roomId/read_markers", { $roomId: roomId, @@ -9021,8 +9034,8 @@ export function fixNotificationCountOnDecryption(cli: MatrixClient, event: Matri // TODO: Handle mentions received while the client is offline // See also https://github.com/vector-im/element-web/issues/9069 const hasReadEvent = isThreadEvent - ? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId()) - : room.hasUserReadEvent(cli.getUserId()!, event.getId()); + ? room.getThread(event.threadRootId)?.hasUserReadEvent(cli.getUserId()!, event.getId()!) + : room.hasUserReadEvent(cli.getUserId()!, event.getId()!); if (!hasReadEvent) { let newCount = currentCount; diff --git a/src/content-helpers.ts b/src/content-helpers.ts index c8c90dd6f0e..f6763fb8c55 100644 --- a/src/content-helpers.ts +++ b/src/content-helpers.ts @@ -139,8 +139,7 @@ export const getTextForLocationEvent = ( /** * Generates the content for a Location event * @param uri a geo:// uri for the location - * @param timestamp the timestamp when the location was correct (milliseconds since - * the UNIX epoch) + * @param timestamp the timestamp when the location was correct (milliseconds since the UNIX epoch) * @param description the (optional) label for this location on the map * @param assetType the (optional) asset type of this location e.g. "m.self" * @param text optional. A text for the location @@ -150,7 +149,7 @@ export const makeLocationContent = ( // to avoid a breaking change text: string | undefined, uri: string, - timestamp?: number, + timestamp: number, description?: string, assetType?: LocationAssetType, ): LegacyLocationEventContent & MLocationEventContent => { diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index 358e733e608..f958e64efe8 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -44,8 +44,8 @@ function publicKeyFromKeyInfo(keyInfo: ICrossSigningKey): string { } export interface ICacheCallbacks { - getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise; - storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise; + getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise; + storeCrossSigningKeyCache?(type: string, key?: Uint8Array): Promise; } export interface ICrossSigningInfo { @@ -169,7 +169,9 @@ export class CrossSigningInfo { * with, or null if it is not present or not encrypted with a trusted * key */ - public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise | null> { + public async isStoredInSecretStorage( + secretStorage: SecretStorage, + ): Promise | null> { // check what SSSS keys have encrypted the master key (if any) const stored = await secretStorage.isStored("m.cross_signing.master") || {}; // then check which of those SSSS keys have also encrypted the SSK and USK @@ -196,7 +198,7 @@ export class CrossSigningInfo { */ public static async storeInSecretStorage( keys: Map, - secretStorage: SecretStorage, + secretStorage: SecretStorage, ): Promise { for (const [type, privateKey] of keys) { const encodedKey = encodeBase64(privateKey); @@ -433,10 +435,9 @@ export class CrossSigningInfo { // if everything checks out, then save the keys if (keys.master) { this.keys.master = keys.master; - // if the master key is set, then the old self-signing and - // user-signing keys are obsolete - this.keys.self_signing = null; - this.keys.user_signing = null; + // if the master key is set, then the old self-signing and user-signing keys are obsolete + delete this.keys["self_signing"]; + delete this.keys["user_signing"]; } if (keys.self_signing) { this.keys.self_signing = keys.self_signing; @@ -723,7 +724,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O }, storeCrossSigningKeyCache: async function( type: keyof SecretStorePrivateKeys, - key: Uint8Array, + key?: Uint8Array, ): Promise { if (!(key instanceof Uint8Array)) { throw new Error( diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index bd6b106aea5..a17a3c32aac 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -252,7 +252,7 @@ export class DeviceList extends TypedEventEmitter} accountData pre-existing account data, will only be read, not written. @@ -162,14 +162,14 @@ export class EncryptionSetupBuilder { for (const type of ["master", "self_signing", "user_signing"]) { logger.log(`Cache ${type} cross-signing private key locally`); const privateKey = this.crossSigningCallbacks.privateKeys.get(type); - await cacheCallbacks.storeCrossSigningKeyCache(type, privateKey); + await cacheCallbacks.storeCrossSigningKeyCache?.(type, privateKey); } // store own cross-sign pubkeys as trusted await crypto.cryptoStore.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { crypto.cryptoStore.storeCrossSigningKeys( - txn, this.crossSigningKeys.keys); + txn, this.crossSigningKeys!.keys); }, ); } @@ -195,9 +195,9 @@ export class EncryptionSetupOperation { */ constructor( private readonly accountData: Map, - private readonly crossSigningKeys: ICrossSigningKeys, - private readonly keyBackupInfo: IKeyBackupInfo, - private readonly keySignatures: KeySignatures, + private readonly crossSigningKeys?: ICrossSigningKeys, + private readonly keyBackupInfo?: IKeyBackupInfo, + private readonly keySignatures?: KeySignatures, ) {} /** @@ -215,7 +215,7 @@ export class EncryptionSetupOperation { // We must only call `uploadDeviceSigningKeys` from inside this auth // helper to ensure we properly handle auth errors. - await this.crossSigningKeys.authUpload(authDict => { + await this.crossSigningKeys.authUpload?.(authDict => { return baseApis.uploadDeviceSigningKeys(authDict, keys as CrossSigningKeys); }); @@ -281,15 +281,15 @@ class AccountDataClientAdapter * @param {String} type * @return {Promise} the content of the account data */ - public getAccountDataFromServer(type: string): Promise { - return Promise.resolve(this.getAccountData(type)); + public getAccountDataFromServer(type: string): Promise { + return Promise.resolve(this.getAccountData(type) as T); } /** * @param {String} type * @return {Object} the content of the account data */ - public getAccountData(type: string): MatrixEvent { + public getAccountData(type: string): IContent | null { const modifiedValue = this.values.get(type); if (modifiedValue) { return modifiedValue; @@ -329,7 +329,7 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks { public readonly privateKeys = new Map(); // cache callbacks - public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise { + public getCrossSigningKeyCache(type: string, expectedPublicKey: string): Promise { return this.getCrossSigningKey(type, expectedPublicKey); } @@ -339,8 +339,8 @@ class CrossSigningCallbacks implements ICryptoCallbacks, ICacheCallbacks { } // non-cache callbacks - public getCrossSigningKey(type: string, expectedPubkey: string): Promise { - return Promise.resolve(this.privateKeys.get(type)); + public getCrossSigningKey(type: string, expectedPubkey: string): Promise { + return Promise.resolve(this.privateKeys.get(type) ?? null); } public saveCrossSigningKeys(privateKeys: Record) { diff --git a/src/crypto/OlmDevice.ts b/src/crypto/OlmDevice.ts index 2049a82de26..abb6962c1fd 100644 --- a/src/crypto/OlmDevice.ts +++ b/src/crypto/OlmDevice.ts @@ -680,7 +680,7 @@ export class OlmDevice { public async getSessionIdsForDevice(theirDeviceIdentityKey: string): Promise { const log = logger.withPrefix("[getSessionIdsForDevice]"); - if (this.sessionsInProgress[theirDeviceIdentityKey]) { + if (theirDeviceIdentityKey in this.sessionsInProgress) { log.debug(`Waiting for Olm session for ${theirDeviceIdentityKey} to be created`); try { await this.sessionsInProgress[theirDeviceIdentityKey]; @@ -770,7 +770,7 @@ export class OlmDevice { ): Promise<{ sessionId: string, lastReceivedMessageTs: number, hasReceivedMessage: boolean }[]> { log = log.withPrefix("[getSessionInfoForDevice]"); - if (this.sessionsInProgress[deviceIdentityKey] && !nowait) { + if (deviceIdentityKey in this.sessionsInProgress && !nowait) { log.debug(`Waiting for Olm session for ${deviceIdentityKey} to be created`); try { await this.sessionsInProgress[deviceIdentityKey]; diff --git a/src/crypto/OutgoingRoomKeyRequestManager.ts b/src/crypto/OutgoingRoomKeyRequestManager.ts index 013a6d08e68..ff534be38b5 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.ts +++ b/src/crypto/OutgoingRoomKeyRequestManager.ts @@ -75,10 +75,26 @@ export enum RoomKeyRequestState { CancellationPendingAndWillResend, } +interface RequestMessageBase { + requesting_device_id: string; + request_id: string; +} + +interface RequestMessageRequest extends RequestMessageBase { + action: "request"; + body: IRoomKeyRequestBody; +} + +interface RequestMessageCancellation extends RequestMessageBase { + action: "request_cancellation"; +} + +type RequestMessage = RequestMessageRequest | RequestMessageCancellation; + export class OutgoingRoomKeyRequestManager { // handle for the delayed call to sendOutgoingRoomKeyRequests. Non-null // if the callback has been set, or if it is still running. - private sendOutgoingRoomKeyRequestsTimer: ReturnType = null; + private sendOutgoingRoomKeyRequestsTimer?: ReturnType; // sanity check to ensure that we don't end up with two concurrent runs // of sendOutgoingRoomKeyRequests @@ -369,43 +385,42 @@ export class OutgoingRoomKeyRequestManager { // look for and send any queued requests. Runs itself recursively until // there are no more requests, or there is an error (in which case, the // timer will be restarted before the promise resolves). - private sendOutgoingRoomKeyRequests(): Promise { + private async sendOutgoingRoomKeyRequests(): Promise { if (!this.clientRunning) { - this.sendOutgoingRoomKeyRequestsTimer = null; - return Promise.resolve(); + this.sendOutgoingRoomKeyRequestsTimer = undefined; + return; } - return this.cryptoStore.getOutgoingRoomKeyRequestByState([ + const req = await this.cryptoStore.getOutgoingRoomKeyRequestByState([ RoomKeyRequestState.CancellationPending, RoomKeyRequestState.CancellationPendingAndWillResend, RoomKeyRequestState.Unsent, - ]).then((req: OutgoingRoomKeyRequest) => { - if (!req) { - this.sendOutgoingRoomKeyRequestsTimer = null; - return; - } + ]); - let prom; + if (!req) { + this.sendOutgoingRoomKeyRequestsTimer = undefined; + return; + } + + try { switch (req.state) { case RoomKeyRequestState.Unsent: - prom = this.sendOutgoingRoomKeyRequest(req); + await this.sendOutgoingRoomKeyRequest(req); break; case RoomKeyRequestState.CancellationPending: - prom = this.sendOutgoingRoomKeyRequestCancellation(req); + await this.sendOutgoingRoomKeyRequestCancellation(req); break; case RoomKeyRequestState.CancellationPendingAndWillResend: - prom = this.sendOutgoingRoomKeyRequestCancellation(req, true); + await this.sendOutgoingRoomKeyRequestCancellation(req, true); break; } - return prom.then(() => { - // go around the loop again - return this.sendOutgoingRoomKeyRequests(); - }).catch((e) => { - logger.error("Error sending room key request; will retry later.", e); - this.sendOutgoingRoomKeyRequestsTimer = null; - }); - }); + // go around the loop again + return this.sendOutgoingRoomKeyRequests(); + } catch (e) { + logger.error("Error sending room key request; will retry later.", e); + this.sendOutgoingRoomKeyRequestsTimer = undefined; + } } // given a RoomKeyRequest, send it and update the request record @@ -416,16 +431,14 @@ export class OutgoingRoomKeyRequestManager { `(id ${req.requestId})`, ); - const requestMessage = { + const requestMessage: RequestMessage = { action: "request", requesting_device_id: this.deviceId, request_id: req.requestId, body: req.requestBody, }; - return this.sendMessageToDevices( - requestMessage, req.recipients, req.requestTxnId || req.requestId, - ).then(() => { + return this.sendMessageToDevices(requestMessage, req.recipients, req.requestTxnId || req.requestId).then(() => { return this.cryptoStore.updateOutgoingRoomKeyRequest( req.requestId, RoomKeyRequestState.Unsent, { state: RoomKeyRequestState.Sent }, @@ -443,7 +456,7 @@ export class OutgoingRoomKeyRequestManager { `(cancellation id ${req.cancellationTxnId})`, ); - const requestMessage = { + const requestMessage: RequestMessage = { action: "request_cancellation", requesting_device_id: this.deviceId, request_id: req.requestId, @@ -467,7 +480,11 @@ export class OutgoingRoomKeyRequestManager { } // send a RoomKeyRequest to a list of recipients - private sendMessageToDevices(message, recipients, txnId: string): Promise<{}> { + private sendMessageToDevices( + message: RequestMessage, + recipients: IRoomKeyRequestRecipient[], + txnId?: string, + ): Promise<{}> { const contentMap: Record>> = {}; for (const recip of recipients) { if (!contentMap[recip.userId]) { @@ -480,15 +497,13 @@ export class OutgoingRoomKeyRequestManager { } } -function stringifyRequestBody(requestBody) { +function stringifyRequestBody(requestBody: IRoomKeyRequestBody): string { // we assume that the request is for megolm keys, which are identified by // room id and session id return requestBody.room_id + " / " + requestBody.session_id; } -function stringifyRecipientList(recipients) { - return '[' - + recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",") - + ']'; +function stringifyRecipientList(recipients: IRoomKeyRequestRecipient[]): string { + return `[${recipients.map((r) => `${r.userId}:${r.deviceId}`).join(",")}]`; } diff --git a/src/crypto/RoomList.ts b/src/crypto/RoomList.ts index 24315d4ecb7..f0e09b657a0 100644 --- a/src/crypto/RoomList.ts +++ b/src/crypto/RoomList.ts @@ -38,12 +38,12 @@ export class RoomList { // Object of roomId -> room e2e info object (body of the m.room.encryption event) private roomEncryption: Record = {}; - constructor(private readonly cryptoStore: CryptoStore) {} + constructor(private readonly cryptoStore?: CryptoStore) {} public async init(): Promise { - await this.cryptoStore.doTxn( + await this.cryptoStore!.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => { - this.cryptoStore.getEndToEndRooms(txn, (result) => { + this.cryptoStore!.getEndToEndRooms(txn, (result) => { this.roomEncryption = result; }); }, @@ -63,9 +63,9 @@ export class RoomList { // as it prevents the Crypto::setRoomEncryption from calling // this twice for consecutive m.room.encryption events this.roomEncryption[roomId] = roomInfo; - await this.cryptoStore.doTxn( + await this.cryptoStore!.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => { - this.cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn); + this.cryptoStore!.storeEndToEndRoom(roomId, roomInfo, txn); }, ); } diff --git a/src/crypto/SecretStorage.ts b/src/crypto/SecretStorage.ts index d0fb7f90e40..e724898bedb 100644 --- a/src/crypto/SecretStorage.ts +++ b/src/crypto/SecretStorage.ts @@ -19,10 +19,11 @@ import * as olmlib from './olmlib'; import { encodeBase64 } from './olmlib'; import { randomString } from '../randomstring'; import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from './aes'; -import { ClientEvent, ICryptoCallbacks, MatrixEvent } from '../matrix'; +import { ClientEvent, IContent, ICryptoCallbacks, MatrixEvent } from '../matrix'; import { ClientEventHandlerMap, MatrixClient } from "../client"; import { IAddSecretStorageKeyOpts, ISecretStorageKeyInfo } from './api'; import { TypedEventEmitter } from '../models/typed-event-emitter'; +import { defer, IDeferred } from "../utils"; export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2"; @@ -39,15 +40,14 @@ export interface ISecretRequest { export interface IAccountDataClient extends TypedEventEmitter { // Subset of MatrixClient (which also uses any for the event content) getAccountDataFromServer: (eventType: string) => Promise; - getAccountData: (eventType: string) => MatrixEvent; + getAccountData: (eventType: string) => IContent | null; setAccountData: (eventType: string, content: any) => Promise<{}>; } interface ISecretRequestInternal { name: string; devices: string[]; - resolve: (reason: string) => void; - reject: (error: Error) => void; + deferred: IDeferred; } interface IDecryptors { @@ -66,7 +66,7 @@ interface ISecretInfo { * Implements Secure Secret Storage and Sharing (MSC1946) * @module crypto/SecretStorage */ -export class SecretStorage { +export class SecretStorage { private requests = new Map(); // In it's pure javascript days, this was relying on some proper Javascript-style @@ -80,7 +80,7 @@ export class SecretStorage { constructor( private readonly accountDataAdapter: IAccountDataClient, private readonly cryptoCallbacks: ICryptoCallbacks, - private readonly baseApis?: MatrixClient, + private readonly baseApis: B, ) {} public async getDefaultKeyId(): Promise { @@ -129,13 +129,11 @@ export class SecretStorage { */ public async addKey( algorithm: string, - opts: IAddSecretStorageKeyOpts, + opts: IAddSecretStorageKeyOpts = {}, keyId?: string, ): Promise { const keyInfo = { algorithm } as ISecretStorageKeyInfo; - if (!opts) opts = {} as IAddSecretStorageKeyOpts; - if (opts.name) { keyInfo.name = opts.name; } @@ -376,21 +374,11 @@ export class SecretStorage { * @param {string} name the name of the secret to request * @param {string[]} devices the devices to request the secret from */ - public request(name: string, devices: string[]): ISecretRequest { + public request(this: SecretStorage, name: string, devices: string[]): ISecretRequest { const requestId = this.baseApis.makeTxnId(); - let resolve: (s: string) => void; - let reject: (e: Error) => void; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - this.requests.set(requestId, { - name, - devices, - resolve, - reject, - }); + const deferred = defer(); + this.requests.set(requestId, { name, devices, deferred }); const cancel = (reason: string) => { // send cancellation event @@ -404,12 +392,12 @@ export class SecretStorage { toDevice[device] = cancelData; } this.baseApis.sendToDevice("m.secret.request", { - [this.baseApis.getUserId()]: toDevice, + [this.baseApis.getUserId()!]: toDevice, }); // and reject the promise so that anyone waiting on it will be // notified - reject(new Error(reason || "Cancelled")); + deferred.reject(new Error(reason || "Cancelled")); }; // send request to devices @@ -425,22 +413,23 @@ export class SecretStorage { } logger.info(`Request secret ${name} from ${devices}, id ${requestId}`); this.baseApis.sendToDevice("m.secret.request", { - [this.baseApis.getUserId()]: toDevice, + [this.baseApis.getUserId()!]: toDevice, }); return { requestId, - promise, + promise: deferred.promise, cancel, }; } - public async onRequestReceived(event: MatrixEvent): Promise { + public async onRequestReceived(this: SecretStorage, event: MatrixEvent): Promise { const sender = event.getSender(); const content = event.getContent(); if (sender !== this.baseApis.getUserId() || !(content.name && content.action - && content.requesting_device_id && content.request_id)) { + && content.requesting_device_id && content.request_id) + ) { // ignore requests from anyone else, for now return; } @@ -498,25 +487,25 @@ export class SecretStorage { }; const encryptedContent = { algorithm: olmlib.OLM_ALGORITHM, - sender_key: this.baseApis.crypto.olmDevice.deviceCurve25519Key, + sender_key: this.baseApis.crypto!.olmDevice.deviceCurve25519Key, ciphertext: {}, }; await olmlib.ensureOlmSessionsForDevices( - this.baseApis.crypto.olmDevice, + this.baseApis.crypto!.olmDevice, this.baseApis, { [sender]: [ - this.baseApis.getStoredDevice(sender, deviceId), + this.baseApis.getStoredDevice(sender, deviceId)!, ], }, ); await olmlib.encryptMessageForDevice( encryptedContent.ciphertext, - this.baseApis.getUserId(), - this.baseApis.deviceId, - this.baseApis.crypto.olmDevice, + this.baseApis.getUserId()!, + this.baseApis.deviceId!, + this.baseApis.crypto!.olmDevice, sender, - this.baseApis.getStoredDevice(sender, deviceId), + this.baseApis.getStoredDevice(sender, deviceId)!, payload, ); const contentMap = { @@ -533,7 +522,7 @@ export class SecretStorage { } } - public onSecretReceived(event: MatrixEvent): void { + public onSecretReceived(this: SecretStorage, event: MatrixEvent): void { if (event.getSender() !== this.baseApis.getUserId()) { // we shouldn't be receiving secrets from anyone else, so ignore // because someone could be trying to send us bogus data @@ -547,7 +536,7 @@ export class SecretStorage { const content = event.getContent(); - const senderKeyUser = this.baseApis.crypto.deviceList.getUserByIdentityKey( + const senderKeyUser = this.baseApis.crypto!.deviceList.getUserByIdentityKey( olmlib.OLM_ALGORITHM, event.getSenderKey() || "", ); @@ -561,9 +550,9 @@ export class SecretStorage { if (requestControl) { // make sure that the device that sent it is one of the devices that // we requested from - const deviceInfo = this.baseApis.crypto.deviceList.getDeviceByIdentityKey( + const deviceInfo = this.baseApis.crypto!.deviceList.getDeviceByIdentityKey( olmlib.OLM_ALGORITHM, - event.getSenderKey(), + event.getSenderKey()!, ); if (!deviceInfo) { logger.log( @@ -578,7 +567,7 @@ export class SecretStorage { // unsure that the sender is trusted. In theory, this check is // unnecessary since we only accept secret shares from devices that // we requested from, but it doesn't hurt. - const deviceTrust = this.baseApis.crypto.checkDeviceInfoTrust(event.getSender(), deviceInfo); + const deviceTrust = this.baseApis.crypto!.checkDeviceInfoTrust(event.getSender()!, deviceInfo); if (!deviceTrust.isVerified()) { logger.log("secret share from unverified device"); return; @@ -588,7 +577,7 @@ export class SecretStorage { `Successfully received secret ${requestControl.name} ` + `from ${deviceInfo.deviceId}`, ); - requestControl.resolve(content.secret); + requestControl.deferred.resolve(content.secret); } } diff --git a/src/crypto/algorithms/base.ts b/src/crypto/algorithms/base.ts index 506a27ee810..190bfa437ab 100644 --- a/src/crypto/algorithms/base.ts +++ b/src/crypto/algorithms/base.ts @@ -36,7 +36,7 @@ import { IRoomEncryption } from "../RoomList"; */ export const ENCRYPTION_CLASSES = new Map EncryptionAlgorithm>(); -type DecryptionClassParams = Omit; +export type DecryptionClassParams

= Omit; /** * map of registered encryption algorithm classes. Map from string to {@link @@ -52,7 +52,7 @@ export interface IParams { crypto: Crypto; olmDevice: OlmDevice; baseApis: MatrixClient; - roomId: string; + roomId?: string; config: IRoomEncryption & object; } @@ -76,7 +76,7 @@ export abstract class EncryptionAlgorithm { protected readonly crypto: Crypto; protected readonly olmDevice: OlmDevice; protected readonly baseApis: MatrixClient; - protected readonly roomId: string; + protected readonly roomId?: string; constructor(params: IParams) { this.userId = params.userId; @@ -148,7 +148,7 @@ export abstract class DecryptionAlgorithm { protected readonly crypto: Crypto; protected readonly olmDevice: OlmDevice; protected readonly baseApis: MatrixClient; - protected readonly roomId: string; + protected readonly roomId?: string; constructor(params: DecryptionClassParams) { this.userId = params.userId; @@ -296,11 +296,11 @@ export class UnknownDeviceError extends Error { * module:crypto/algorithms/base.DecryptionAlgorithm|DecryptionAlgorithm} * implementation */ -export function registerAlgorithm( +export function registerAlgorithm

( algorithm: string, - encryptor: new (params: IParams) => EncryptionAlgorithm, - decryptor: new (params: DecryptionClassParams) => DecryptionAlgorithm, + encryptor: new (params: P) => EncryptionAlgorithm, + decryptor: new (params: DecryptionClassParams

) => DecryptionAlgorithm, ): void { - ENCRYPTION_CLASSES.set(algorithm, encryptor); - DECRYPTION_CLASSES.set(algorithm, decryptor); + ENCRYPTION_CLASSES.set(algorithm, encryptor as new (params: IParams) => EncryptionAlgorithm); + DECRYPTION_CLASSES.set(algorithm, decryptor as new (params: DecryptionClassParams) => DecryptionAlgorithm); } diff --git a/src/crypto/algorithms/megolm.ts b/src/crypto/algorithms/megolm.ts index e91213185fa..170172a34cf 100644 --- a/src/crypto/algorithms/megolm.ts +++ b/src/crypto/algorithms/megolm.ts @@ -24,6 +24,7 @@ import { logger } from '../../logger'; import * as olmlib from "../olmlib"; import { DecryptionAlgorithm, + DecryptionClassParams, DecryptionError, EncryptionAlgorithm, IParams, @@ -251,8 +252,11 @@ class MegolmEncryption extends EncryptionAlgorithm { startTime: number; }; - constructor(params: IParams) { + protected readonly roomId: string; + + constructor(params: IParams & Required>) { super(params); + this.roomId = params.roomId; this.sessionRotationPeriodMsgs = params.config?.rotation_period_msgs ?? 100; this.sessionRotationPeriodMs = params.config?.rotation_period_ms ?? 7 * 24 * 3600 * 1000; @@ -1231,6 +1235,13 @@ class MegolmDecryption extends DecryptionAlgorithm { // this gets stubbed out by the unit tests. private olmlib = olmlib; + protected readonly roomId: string; + + constructor(params: DecryptionClassParams>>) { + super(params); + this.roomId = params.roomId; + } + /** * @inheritdoc * @@ -1264,7 +1275,7 @@ class MegolmDecryption extends DecryptionAlgorithm { try { res = await this.olmDevice.decryptGroupMessage( event.getRoomId()!, content.sender_key, content.session_id, content.ciphertext, - event.getId(), event.getTs(), + event.getId()!, event.getTs(), ); } catch (e) { if ((e).name === "DecryptionError") { @@ -1464,7 +1475,7 @@ class MegolmDecryption extends DecryptionAlgorithm { return; } const outgoingRequests = deviceInfo ? await this.crypto.cryptoStore.getOutgoingRoomKeyRequestsByTarget( - event.getSender(), deviceInfo.deviceId, [RoomKeyRequestState.Sent], + event.getSender()!, deviceInfo.deviceId, [RoomKeyRequestState.Sent], ) : []; const weRequested = outgoingRequests.some((req) => ( req.requestBody.room_id === content.room_id && req.requestBody.session_id === content.session_id @@ -1524,7 +1535,7 @@ class MegolmDecryption extends DecryptionAlgorithm { // that room later if (!room) { const parkedData = { - senderId: event.getSender(), + senderId: event.getSender()!, senderKey: content.sender_key, sessionId: content.session_id, sessionKey: content.session_key, @@ -1544,7 +1555,7 @@ class MegolmDecryption extends DecryptionAlgorithm { olmlib.OLM_ALGORITHM, senderKey, ) ?? undefined; - const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender(), sendingDevice); + const deviceTrust = this.crypto.checkDeviceInfoTrust(event.getSender()!, sendingDevice); if (fromUs && !deviceTrust.isVerified()) { return; @@ -1608,7 +1619,7 @@ class MegolmDecryption extends DecryptionAlgorithm { const senderKey = content.sender_key; if (content.code === "m.no_olm") { - const sender = event.getSender(); + const sender = event.getSender()!; logger.warn( `${sender}:${senderKey} was unable to establish an olm session with us`, ); diff --git a/src/crypto/algorithms/olm.ts b/src/crypto/algorithms/olm.ts index c85fdf9c89d..10bb98eccec 100644 --- a/src/crypto/algorithms/olm.ts +++ b/src/crypto/algorithms/olm.ts @@ -228,7 +228,7 @@ class OlmDecryption extends DecryptionAlgorithm { // assume that the device logged out. Some event handlers, such as // secret sharing, may be more strict and reject events that come from // unknown devices. - await this.crypto.deviceList.downloadKeys([event.getSender()], false); + await this.crypto.deviceList.downloadKeys([event.getSender()!], false); const senderKeyUser = this.crypto.deviceList.getUserByIdentityKey( olmlib.OLM_ALGORITHM, deviceKey, @@ -250,7 +250,7 @@ class OlmDecryption extends DecryptionAlgorithm { throw new DecryptionError( "OLM_FORWARDED_MESSAGE", "Message forwarded from " + payload.sender, { - reported_sender: event.getSender(), + reported_sender: event.getSender()!, }, ); } diff --git a/src/crypto/api.ts b/src/crypto/api.ts index bedc5e603cf..f6487ca913d 100644 --- a/src/crypto/api.ts +++ b/src/crypto/api.ts @@ -117,10 +117,10 @@ export interface IPassphraseInfo { } export interface IAddSecretStorageKeyOpts { - pubkey: string; + pubkey?: string; passphrase?: IPassphraseInfo; name?: string; - key: Uint8Array; + key?: Uint8Array; } export interface IImportOpts { diff --git a/src/crypto/backup.ts b/src/crypto/backup.ts index be5c9e0aa94..f57cfe29359 100644 --- a/src/crypto/backup.ts +++ b/src/crypto/backup.ts @@ -201,7 +201,7 @@ export class BackupManager { } const [privateKey, authData] = await Algorithm.prepare(key); - const recoveryKey = encodeRecoveryKey(privateKey); + const recoveryKey = encodeRecoveryKey(privateKey)!; return { algorithm: Algorithm.algorithmName, auth_data: authData, @@ -298,10 +298,10 @@ export class BackupManager { const now = new Date().getTime(); if ( - !this.sessionLastCheckAttemptedTime[targetSessionId] - || now - this.sessionLastCheckAttemptedTime[targetSessionId] > KEY_BACKUP_CHECK_RATE_LIMIT + !this.sessionLastCheckAttemptedTime[targetSessionId!] + || now - this.sessionLastCheckAttemptedTime[targetSessionId!] > KEY_BACKUP_CHECK_RATE_LIMIT ) { - this.sessionLastCheckAttemptedTime[targetSessionId] = now; + this.sessionLastCheckAttemptedTime[targetSessionId!] = now; await this.baseApis.restoreKeyBackupWithCache(targetRoomId, targetSessionId, this.backupInfo, {}); } } diff --git a/src/crypto/deviceinfo.ts b/src/crypto/deviceinfo.ts index 1c241a2b8cf..00ebf7c389f 100644 --- a/src/crypto/deviceinfo.ts +++ b/src/crypto/deviceinfo.ts @@ -87,7 +87,7 @@ export class DeviceInfo { BLOCKED: DeviceVerification.Blocked, }; - public algorithms: string[]; + public algorithms: string[] = []; public keys: Record = {}; public verified = DeviceVerification.Unverified; public known = false; diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 62258bd3491..0a13837e1ac 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -199,8 +199,8 @@ export interface IEventDecryptionResult { } export interface IRequestsMap { - getRequest(event: MatrixEvent): VerificationRequest; - getRequestByChannel(channel: IVerificationChannel): VerificationRequest; + getRequest(event: MatrixEvent): VerificationRequest | undefined; + getRequestByChannel(channel: IVerificationChannel): VerificationRequest | undefined; setRequest(event: MatrixEvent, request: VerificationRequest): void; setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void; } @@ -302,7 +302,7 @@ export class Crypto extends TypedEventEmitter> = {}; // roomId: Promise } = {}; // The timestamp of the last time we forced establishment // of a new session for each device, in milliseconds. @@ -588,7 +588,7 @@ export class Crypto extends TypedEventEmitter { - let request: Request; + let request: Request | undefined; if (transactionId) { request = this.toDeviceVerificationRequests.getRequestBySenderAndTxnId(userId, transactionId); if (!request) { @@ -2467,7 +2470,7 @@ export class Crypto extends TypedEventEmitter = {}; - ret.senderKey = event.getSenderKey(); + ret.senderKey = event.getSenderKey() ?? undefined; ret.algorithm = event.getWireContent().algorithm; if (!ret.senderKey || !ret.algorithm) { @@ -2488,7 +2491,7 @@ export class Crypto extends TypedEventEmitter { - this.roomDeviceTrackingState[roomId] = null; + delete this.roomDeviceTrackingState[roomId]; throw err; }); } @@ -2716,9 +2719,7 @@ export class Crypto extends TypedEventEmitter { if (s === null) return; - const sess = this.olmDevice.exportInboundGroupSession( - s.senderKey, s.sessionId, s.sessionData, - ); + const sess = this.olmDevice.exportInboundGroupSession(s.senderKey, s.sessionId, s.sessionData!); delete sess.first_known_index; sess.algorithm = olmlib.MEGOLM_ALGORITHM; exportedSessions.push(sess); @@ -2803,7 +2804,7 @@ export class Crypto extends TypedEventEmitter { - const roomId = event.getRoomId(); + const roomId = event.getRoomId()!; const content = event.getContent(); try { @@ -2978,8 +2979,7 @@ export class Crypto extends TypedEventEmitter { - this.deviceList.setSyncToken(syncData.nextSyncToken); + this.deviceList.setSyncToken(syncData.nextSyncToken ?? null); this.deviceList.saveIfDirty(); // we always track our own device list (for key backups etc) @@ -3075,7 +3075,7 @@ export class Crypto extends TypedEventEmitter { - const e2eUserIds = []; + const e2eUserIds: string[] = []; for (const room of this.getTrackedE2eRooms()) { const members = await room.getEncryptionTargetMembers(); for (const member of members) { @@ -3295,7 +3295,7 @@ export class Crypto extends TypedEventEmitter { + const createRequest = (event: MatrixEvent): VerificationRequest | undefined => { if (!ToDeviceChannel.canCreateRequest(ToDeviceChannel.getEventType(event))) { return; } @@ -3304,7 +3304,7 @@ export class Crypto extends TypedEventEmitter { - const channel = new InRoomChannel( - this.baseApis, - event.getRoomId(), - ); + const channel = new InRoomChannel(this.baseApis, event.getRoomId()!); return new VerificationRequest( channel, this.verificationMethods, this.baseApis); }; @@ -3350,15 +3347,15 @@ export class Crypto extends TypedEventEmitter VerificationRequest, + createRequest: (event: MatrixEvent) => VerificationRequest | undefined, isLiveEvent = true, ): Promise { // Wait for event to get its final ID with pendingEventOrdering: "chronological", since DM channels depend on it. if (event.isSending() && event.status != EventStatus.SENT) { - let eventIdListener; - let statusListener; + let eventIdListener: () => void; + let statusListener: () => void; try { - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { eventIdListener = resolve; statusListener = () => { if (event.status == EventStatus.CANCELLED) { @@ -3372,11 +3369,11 @@ export class Crypto extends TypedEventEmitter | undefined; let alg: DecryptionAlgorithm | undefined; - roomId = roomId || null; if (roomId) { decryptors = this.roomDecryptors.get(roomId); if (!decryptors) { @@ -3771,7 +3768,7 @@ export class Crypto extends TypedEventEmitter): string { +export function encodeRecoveryKey(key: ArrayLike): string | undefined { const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1); buf.set(OLM_RECOVERY_KEY_PREFIX, 0); buf.set(key, OLM_RECOVERY_KEY_PREFIX.length); @@ -32,7 +32,7 @@ export function encodeRecoveryKey(key: ArrayLike): string { buf[buf.length - 1] = parity; const base58key = bs58.encode(buf); - return base58key.match(/.{1,4}/g).join(" "); + return base58key.match(/.{1,4}/g)?.join(" "); } export function decodeRecoveryKey(recoveryKey: string): Uint8Array { diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index 56784189796..faeb5c829a6 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -678,7 +678,7 @@ export class Backend implements CryptoStore { senderCurve25519Key, sessionId, session: sessionData, }); addReq.onerror = (ev) => { - if (addReq.error.name === 'ConstraintError') { + if (addReq.error?.name === 'ConstraintError') { // This stops the error from triggering the txn's onerror ev.stopPropagation(); // ...and this stops it from aborting the transaction diff --git a/src/crypto/verification/Base.ts b/src/crypto/verification/Base.ts index 7c68eecf169..f2fc1d5f99a 100644 --- a/src/crypto/verification/Base.ts +++ b/src/crypto/verification/Base.ts @@ -34,7 +34,7 @@ import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter const timeoutException = new Error("Verification timed out"); export class SwitchStartEventError extends Error { - constructor(public readonly startEvent: MatrixEvent) { + constructor(public readonly startEvent: MatrixEvent | null) { super(); } } @@ -96,7 +96,7 @@ export class VerificationBase< public readonly baseApis: MatrixClient, public readonly userId: string, public readonly deviceId: string, - public startEvent: MatrixEvent, + public startEvent: MatrixEvent | null, public readonly request: VerificationRequest, ) { super(); diff --git a/src/crypto/verification/Error.ts b/src/crypto/verification/Error.ts index 0caae788a61..ca0fb20b575 100644 --- a/src/crypto/verification/Error.ts +++ b/src/crypto/verification/Error.ts @@ -23,7 +23,7 @@ limitations under the License. import { MatrixEvent } from "../../models/event"; import { EventType } from '../../@types/event'; -export function newVerificationError(code: string, reason: string, extraData: Record): MatrixEvent { +export function newVerificationError(code: string, reason: string, extraData?: Record): MatrixEvent { const content = Object.assign({}, { code, reason }, extraData); return new MatrixEvent({ type: EventType.KeyVerificationCancel, diff --git a/src/crypto/verification/QRCode.ts b/src/crypto/verification/QRCode.ts index 8bb3ca0d9bf..9ff9315a95f 100644 --- a/src/crypto/verification/QRCode.ts +++ b/src/crypto/verification/QRCode.ts @@ -147,7 +147,7 @@ interface IQrData { prefix: string; version: number; mode: Mode; - transactionId: string; + transactionId?: string; firstKeyB64: string; secondKeyB64: string; secretB64: string; @@ -250,7 +250,7 @@ export class QRCodeData { ): IQrData { const myUserId = client.getUserId()!; const transactionId = request.channel.transactionId; - const qrData = { + const qrData: IQrData = { prefix: BINARY_PREFIX, version: CODE_VERSION, mode, diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index 5d418da7964..a7119d899c6 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -233,10 +233,10 @@ type EventHandlerMap = { * @extends {module:crypto/verification/Base} */ export class SAS extends Base { - private waitingForAccept: boolean; - public ourSASPubKey: string; - public theirSASPubKey: string; - public sasEvent: ISasEvent; + private waitingForAccept?: boolean; + public ourSASPubKey?: string; + public theirSASPubKey?: string; + public sasEvent?: ISasEvent; // eslint-disable-next-line @typescript-eslint/naming-convention public static get NAME(): string { @@ -279,7 +279,7 @@ export class SAS extends Base { return false; } const content = event.getContent(); - return content && content.method === SAS.NAME && this.waitingForAccept; + return content?.method === SAS.NAME && !!this.waitingForAccept; } private async sendStart(): Promise> { @@ -400,7 +400,7 @@ export class SAS extends Base { private async doRespondVerification(): Promise { // as m.related_to is not included in the encrypted content in e2e rooms, // we need to make sure it is added - let content = this.channel.completedContentFromEvent(this.startEvent); + let content = this.channel.completedContentFromEvent(this.startEvent!); // Note: we intersect using our pre-made lists, rather than the sets, // so that the result will be in our order of preference. Then diff --git a/src/crypto/verification/request/Channel.ts b/src/crypto/verification/request/Channel.ts index 3bba7d82284..48415f977e1 100644 --- a/src/crypto/verification/request/Channel.ts +++ b/src/crypto/verification/request/Channel.ts @@ -19,10 +19,10 @@ import { VerificationRequest } from "./VerificationRequest"; export interface IVerificationChannel { request?: VerificationRequest; - readonly userId: string; + readonly userId?: string; readonly roomId?: string; readonly deviceId?: string; - readonly transactionId: string; + readonly transactionId?: string; readonly receiveStartFromOtherDevices?: boolean; getTimestamp(event: MatrixEvent): number; send(type: string, uncompletedContent: Record): Promise; diff --git a/src/crypto/verification/request/InRoomChannel.ts b/src/crypto/verification/request/InRoomChannel.ts index 67082ff835e..2a4fb8d8106 100644 --- a/src/crypto/verification/request/InRoomChannel.ts +++ b/src/crypto/verification/request/InRoomChannel.ts @@ -37,7 +37,7 @@ const M_RELATES_TO = "m.relates_to"; * Uses the event id of the initial m.key.verification.request event as a transaction id. */ export class InRoomChannel implements IVerificationChannel { - private requestEventId: string = null; + private requestEventId?: string; /** * @param {MatrixClient} client the matrix client, to send messages with and get current user & device from. @@ -47,7 +47,7 @@ export class InRoomChannel implements IVerificationChannel { constructor( private readonly client: MatrixClient, public readonly roomId: string, - public userId: string = null, + public userId?: string, ) { } @@ -56,11 +56,11 @@ export class InRoomChannel implements IVerificationChannel { } /** The transaction id generated/used by this verification channel */ - public get transactionId(): string { + public get transactionId(): string | undefined { return this.requestEventId; } - public static getOtherPartyUserId(event: MatrixEvent, client: MatrixClient): string { + public static getOtherPartyUserId(event: MatrixEvent, client: MatrixClient): string | undefined { const type = InRoomChannel.getEventType(event); if (type !== REQUEST_TYPE) { return; @@ -103,12 +103,12 @@ export class InRoomChannel implements IVerificationChannel { * @param {MatrixEvent} event the event * @returns {string} the transaction id */ - public static getTransactionId(event: MatrixEvent): string { + public static getTransactionId(event: MatrixEvent): string | undefined { if (InRoomChannel.getEventType(event) === REQUEST_TYPE) { return event.getId(); } else { const relation = event.getRelation(); - if (relation && relation.rel_type === M_REFERENCE) { + if (relation?.rel_type === M_REFERENCE) { return relation.event_id; } } @@ -184,10 +184,10 @@ export class InRoomChannel implements IVerificationChannel { * @param {boolean} isLiveEvent whether this is an even received through sync or not * @returns {Promise} a promise that resolves when any requests as an answer to the passed-in event are sent. */ - public handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise { + public async handleEvent(event: MatrixEvent, request: VerificationRequest, isLiveEvent = false): Promise { // prevent processing the same event multiple times, as under // some circumstances Room.timeline can get emitted twice for the same event - if (request.hasEventId(event.getId())) { + if (request.hasEventId(event.getId()!)) { return; } const type = InRoomChannel.getEventType(event); @@ -198,7 +198,7 @@ export class InRoomChannel implements IVerificationChannel { return; } // set userId if not set already - if (this.userId === null) { + if (!this.userId) { const userId = InRoomChannel.getOtherPartyUserId(event, this.client); if (userId) { this.userId = userId; @@ -207,14 +207,13 @@ export class InRoomChannel implements IVerificationChannel { // ignore events not sent by us or the other party const ownUserId = this.client.getUserId(); const sender = event.getSender(); - if (this.userId !== null) { + if (this.userId) { if (sender !== ownUserId && sender !== this.userId) { - logger.log(`InRoomChannel: ignoring verification event from ` + - `non-participating sender ${sender}`); + logger.log(`InRoomChannel: ignoring verification event from non-participating sender ${sender}`); return; } } - if (this.requestEventId === null) { + if (!this.requestEventId) { this.requestEventId = InRoomChannel.getTransactionId(event); } @@ -236,7 +235,7 @@ export class InRoomChannel implements IVerificationChannel { // ensure m.related_to is included in e2ee rooms // as the field is excluded from encryption const content = Object.assign({}, event.getContent()); - content[M_RELATES_TO] = event.getRelation(); + content[M_RELATES_TO] = event.getRelation()!; return content; } @@ -307,17 +306,17 @@ export class InRoomChannel implements IVerificationChannel { export class InRoomRequests implements IRequestsMap { private requestsByRoomId = new Map>(); - public getRequest(event: MatrixEvent): VerificationRequest { - const roomId = event.getRoomId(); - const txnId = InRoomChannel.getTransactionId(event); + public getRequest(event: MatrixEvent): VerificationRequest | undefined { + const roomId = event.getRoomId()!; + const txnId = InRoomChannel.getTransactionId(event)!; return this.getRequestByTxnId(roomId, txnId); } - public getRequestByChannel(channel: InRoomChannel): VerificationRequest { - return this.getRequestByTxnId(channel.roomId, channel.transactionId); + public getRequestByChannel(channel: InRoomChannel): VerificationRequest | undefined { + return this.getRequestByTxnId(channel.roomId, channel.transactionId!); } - private getRequestByTxnId(roomId: string, txnId: string): VerificationRequest { + private getRequestByTxnId(roomId: string, txnId: string): VerificationRequest | undefined { const requestsByTxnId = this.requestsByRoomId.get(roomId); if (requestsByTxnId) { return requestsByTxnId.get(txnId); @@ -325,11 +324,11 @@ export class InRoomRequests implements IRequestsMap { } public setRequest(event: MatrixEvent, request: VerificationRequest): void { - this.doSetRequest(event.getRoomId(), InRoomChannel.getTransactionId(event), request); + this.doSetRequest(event.getRoomId()!, InRoomChannel.getTransactionId(event)!, request); } public setRequestByChannel(channel: IVerificationChannel, request: VerificationRequest): void { - this.doSetRequest(channel.roomId, channel.transactionId, request); + this.doSetRequest(channel.roomId!, channel.transactionId!, request); } private doSetRequest(roomId: string, txnId: string, request: VerificationRequest): void { @@ -342,17 +341,17 @@ export class InRoomRequests implements IRequestsMap { } public removeRequest(event: MatrixEvent): void { - const roomId = event.getRoomId(); + const roomId = event.getRoomId()!; const requestsByTxnId = this.requestsByRoomId.get(roomId); if (requestsByTxnId) { - requestsByTxnId.delete(InRoomChannel.getTransactionId(event)); + requestsByTxnId.delete(InRoomChannel.getTransactionId(event)!); if (requestsByTxnId.size === 0) { this.requestsByRoomId.delete(roomId); } } } - public findRequestInProgress(roomId: string): VerificationRequest { + public findRequestInProgress(roomId: string): VerificationRequest | undefined { const requestsByTxnId = this.requestsByRoomId.get(roomId); if (requestsByTxnId) { for (const request of requestsByTxnId.values()) { diff --git a/src/crypto/verification/request/ToDeviceChannel.ts b/src/crypto/verification/request/ToDeviceChannel.ts index 61bd8bc34eb..11227291483 100644 --- a/src/crypto/verification/request/ToDeviceChannel.ts +++ b/src/crypto/verification/request/ToDeviceChannel.ts @@ -46,8 +46,8 @@ export class ToDeviceChannel implements IVerificationChannel { private readonly client: MatrixClient, public readonly userId: string, private readonly devices: string[], - public transactionId: string = null, - public deviceId: string = null, + public transactionId?: string, + public deviceId?: string, ) {} public isToDevices(devices: string[]): boolean { @@ -173,13 +173,11 @@ export class ToDeviceChannel implements IVerificationChannel { return this.sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]); } } - const wasStarted = request.phase === PHASE_STARTED || - request.phase === PHASE_READY; + const wasStarted = request.phase === PHASE_STARTED || request.phase === PHASE_READY; await request.handleEvent(event.getType(), event, isLiveEvent, false, false); - const isStarted = request.phase === PHASE_STARTED || - request.phase === PHASE_READY; + const isStarted = request.phase === PHASE_STARTED || request.phase === PHASE_READY; const isAcceptingEvent = type === START_TYPE || type === READY_TYPE; // the request has picked a ready or start event, tell the other devices about it @@ -256,16 +254,16 @@ export class ToDeviceChannel implements IVerificationChannel { if (type === REQUEST_TYPE || (type === CANCEL_TYPE && !this.deviceId)) { result = await this.sendToDevices(type, content, this.devices); } else { - result = await this.sendToDevices(type, content, [this.deviceId]); + result = await this.sendToDevices(type, content, [this.deviceId!]); } // the VerificationRequest state machine requires remote echos of the event // the client sends itself, so we fake this for to_device messages const remoteEchoEvent = new MatrixEvent({ - sender: this.client.getUserId(), + sender: this.client.getUserId()!, content, type, }); - await this.request.handleEvent( + await this.request!.handleEvent( type, remoteEchoEvent, /*isLiveEvent=*/true, @@ -298,18 +296,18 @@ export class ToDeviceChannel implements IVerificationChannel { export class ToDeviceRequests implements IRequestsMap { private requestsByUserId = new Map>(); - public getRequest(event: MatrixEvent): Request { + public getRequest(event: MatrixEvent): Request | undefined { return this.getRequestBySenderAndTxnId( - event.getSender(), + event.getSender()!, ToDeviceChannel.getTransactionId(event), ); } - public getRequestByChannel(channel: ToDeviceChannel): Request { - return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId); + public getRequestByChannel(channel: ToDeviceChannel): Request | undefined { + return this.getRequestBySenderAndTxnId(channel.userId, channel.transactionId!); } - public getRequestBySenderAndTxnId(sender: string, txnId: string): Request { + public getRequestBySenderAndTxnId(sender: string, txnId: string): Request | undefined { const requestsByTxnId = this.requestsByUserId.get(sender); if (requestsByTxnId) { return requestsByTxnId.get(txnId); @@ -317,11 +315,11 @@ export class ToDeviceRequests implements IRequestsMap { } public setRequest(event: MatrixEvent, request: Request): void { - this.setRequestBySenderAndTxnId(event.getSender(), ToDeviceChannel.getTransactionId(event), request); + this.setRequestBySenderAndTxnId(event.getSender()!, ToDeviceChannel.getTransactionId(event), request); } public setRequestByChannel(channel: ToDeviceChannel, request: Request): void { - this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId, request); + this.setRequestBySenderAndTxnId(channel.userId, channel.transactionId!, request); } public setRequestBySenderAndTxnId(sender: string, txnId: string, request: Request): void { @@ -334,7 +332,7 @@ export class ToDeviceRequests implements IRequestsMap { } public removeRequest(event: MatrixEvent): void { - const userId = event.getSender(); + const userId = event.getSender()!; const requestsByTxnId = this.requestsByUserId.get(userId); if (requestsByTxnId) { requestsByTxnId.delete(ToDeviceChannel.getTransactionId(event)); @@ -344,7 +342,7 @@ export class ToDeviceRequests implements IRequestsMap { } } - public findRequestInProgress(userId: string, devices: string[]): Request { + public findRequestInProgress(userId: string, devices: string[]): Request | undefined { const requestsByTxnId = this.requestsByUserId.get(userId); if (requestsByTxnId) { for (const request of requestsByTxnId.values()) { diff --git a/src/crypto/verification/request/VerificationRequest.ts b/src/crypto/verification/request/VerificationRequest.ts index 76bf6980231..2ff71d62c19 100644 --- a/src/crypto/verification/request/VerificationRequest.ts +++ b/src/crypto/verification/request/VerificationRequest.ts @@ -112,8 +112,8 @@ export class VerificationRequest< private requestReceivedAt: number | null = null; private commonMethods: VerificationMethod[] = []; - private _phase: Phase; - public _cancellingUserId: string; // Used in tests only + private _phase!: Phase; + public _cancellingUserId?: string; // Used in tests only private _verifier?: VerificationBase; constructor( @@ -357,7 +357,7 @@ export class VerificationRequest< /** The user id of the other party in this request */ public get otherUserId(): string { - return this.channel.userId; + return this.channel.userId!; } public get isSelfVerification(): boolean { @@ -372,7 +372,7 @@ export class VerificationRequest< const myCancel = this.eventsByUs.get(CANCEL_TYPE); const theirCancel = this.eventsByThem.get(CANCEL_TYPE); - if (myCancel && (!theirCancel || myCancel.getId() < theirCancel.getId())) { + if (myCancel && (!theirCancel || myCancel.getId()! < theirCancel.getId()!)) { return myCancel.getSender(); } if (theirCancel) { @@ -405,8 +405,8 @@ export class VerificationRequest< this.eventsByThem.get(REQUEST_TYPE) || this.eventsByThem.get(READY_TYPE) || this.eventsByThem.get(START_TYPE); - const theirFirstContent = theirFirstEvent.getContent(); - const fromDevice = theirFirstContent.from_device; + const theirFirstContent = theirFirstEvent?.getContent(); + const fromDevice = theirFirstContent?.from_device; return { userId: this.otherUserId, deviceId: fromDevice, @@ -559,7 +559,9 @@ export class VerificationRequest< const ourStartEvent = this.eventsByUs.get(START_TYPE); // any party can send .start after a .ready or unsent if (theirStartEvent && ourStartEvent) { - startEvent = theirStartEvent.getSender() < ourStartEvent.getSender() ? theirStartEvent : ourStartEvent; + startEvent = theirStartEvent.getSender()! < ourStartEvent.getSender()! + ? theirStartEvent + : ourStartEvent; } else { startEvent = theirStartEvent ? theirStartEvent : ourStartEvent; } @@ -595,7 +597,7 @@ export class VerificationRequest< // get common methods if (phase === PHASE_REQUESTED || phase === PHASE_READY) { if (!this.wasSentByOwnDevice(event)) { - const content = event.getContent<{ + const content = event!.getContent<{ methods: string[]; }>(); this.commonMethods = @@ -620,7 +622,7 @@ export class VerificationRequest< } // create verifier if (phase === PHASE_STARTED) { - const { method } = event.getContent(); + const { method } = event!.getContent(); if (!this._verifier && !this.observeOnly) { this._verifier = this.createVerifier(method, event); if (!this._verifier) { @@ -903,19 +905,19 @@ export class VerificationRequest< logger.warn("could not find verifier constructor for method", method); return; } - return new VerifierCtor(this.channel, this.client, userId, deviceId, startEvent, this); + return new VerifierCtor(this.channel, this.client, userId!, deviceId!, startEvent, this); } - private wasSentByOwnUser(event: MatrixEvent): boolean { - return event.getSender() === this.client.getUserId(); + private wasSentByOwnUser(event?: MatrixEvent): boolean { + return event?.getSender() === this.client.getUserId(); } // only for .request, .ready or .start - private wasSentByOwnDevice(event: MatrixEvent): boolean { + private wasSentByOwnDevice(event?: MatrixEvent): boolean { if (!this.wasSentByOwnUser(event)) { return false; } - const content = event.getContent(); + const content = event!.getContent(); if (!content || content.from_device !== this.client.getDeviceId()) { return false; } diff --git a/src/filter-component.ts b/src/filter-component.ts index 5e38238c698..fd90358b259 100644 --- a/src/filter-component.ts +++ b/src/filter-component.ts @@ -88,7 +88,7 @@ export class FilterComponent { // as sending a whole list of participants could be proven problematic in terms // of performance // This should be improved when bundled relationships solve that problem - const relationSenders = []; + const relationSenders: string[] = []; if (this.userId && bundledRelationships?.[THREAD_RELATION_TYPE.name]?.current_user_participated) { relationSenders.push(this.userId); } @@ -131,8 +131,8 @@ export class FilterComponent { * @return {boolean} true if the event fields match the filter */ private checkFields( - roomId: string, - sender: string, + roomId: string | undefined, + sender: string | undefined, eventType: string, containsUrl: boolean, relationTypes: Array, diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index dab48a94477..ef3bc8bd891 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -207,15 +207,15 @@ export class InteractiveAuth { private data: IAuthData; private emailSid?: string; private requestingEmailToken = false; - private attemptAuthDeferred: IDeferred = null; - private chosenFlow: IFlow = null; - private currentStage: string = null; + private attemptAuthDeferred: IDeferred | null = null; + private chosenFlow: IFlow | null = null; + private currentStage: string | null = null; private emailAttempt = 1; // if we are currently trying to submit an auth dict (which includes polling) // the promise the will resolve/reject when it completes - private submitPromise: Promise = null; + private submitPromise: Promise | null = null; constructor(opts: IOpts) { this.matrixClient = opts.matrixClient; @@ -229,7 +229,7 @@ export class InteractiveAuth { if (opts.sessionId) this.data.session = opts.sessionId; this.clientSecret = opts.clientSecret || this.matrixClient.generateClientSecret(); - this.emailSid = opts.emailSid ?? null; + this.emailSid = opts.emailSid; } /** @@ -286,7 +286,7 @@ export class InteractiveAuth { client_secret: this.clientSecret, }; if (await this.matrixClient.doesServerRequireIdServerParam()) { - const idServerParsedUrl = new URL(this.matrixClient.getIdentityServerUrl()); + const idServerParsedUrl = new URL(this.matrixClient.getIdentityServerUrl()!); creds.id_server = idServerParsedUrl.host; } authDict = { @@ -308,7 +308,7 @@ export class InteractiveAuth { * * @return {string} session id */ - public getSessionId(): string { + public getSessionId(): string | undefined { return this.data?.session; } @@ -332,7 +332,7 @@ export class InteractiveAuth { return this.data.params?.[loginType]; } - public getChosenFlow(): IFlow { + public getChosenFlow(): IFlow | null { return this.chosenFlow; } @@ -399,7 +399,7 @@ export class InteractiveAuth { * * @returns {string} The sid of the email auth session */ - public getEmailSid(): string { + public getEmailSid(): string | undefined { return this.emailSid; } @@ -457,7 +457,7 @@ export class InteractiveAuth { private async doRequest(auth: IAuthData, background = false): Promise { try { const result = await this.requestCallback(auth, background); - this.attemptAuthDeferred.resolve(result); + this.attemptAuthDeferred!.resolve(result); this.attemptAuthDeferred = null; } catch (error) { // sometimes UI auth errors don't come with flows @@ -491,12 +491,12 @@ export class InteractiveAuth { try { this.startNextAuthStage(); } catch (e) { - this.attemptAuthDeferred.reject(e); + this.attemptAuthDeferred!.reject(e); this.attemptAuthDeferred = null; return; } - if (!this.emailSid && this.chosenFlow.stages.includes(AuthType.Email)) { + if (!this.emailSid && this.chosenFlow?.stages.includes(AuthType.Email)) { try { await this.requestEmailToken(); // NB. promise is not resolved here - at some point, doRequest @@ -512,7 +512,7 @@ export class InteractiveAuth { // to do) or it could be a network failure. Either way, pass // the failure up as the user can't complete auth if we can't // send the email, for whatever reason. - this.attemptAuthDeferred.reject(e); + this.attemptAuthDeferred!.reject(e); this.attemptAuthDeferred = null; } } @@ -559,7 +559,7 @@ export class InteractiveAuth { * @return {string?} login type * @throws {NoAuthFlowFoundError} If no suitable authentication flow can be found */ - private chooseStage(): AuthType { + private chooseStage(): AuthType | undefined { if (this.chosenFlow === null) { this.chosenFlow = this.chooseFlow(); } @@ -625,7 +625,7 @@ export class InteractiveAuth { * @param {object} flow * @return {string} login type */ - private firstUncompletedStage(flow: IFlow): AuthType { + private firstUncompletedStage(flow: IFlow): AuthType | undefined { const completed = this.data.completed || []; for (let i = 0; i < flow.stages.length; ++i) { const stageType = flow.stages[i]; diff --git a/src/logger.ts b/src/logger.ts index 9723d6abb6b..f2fb821673f 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -35,7 +35,7 @@ const DEFAULT_NAMESPACE = "matrix"; // console methods at initialization time by a factory that looks up the console methods // when logging so we always get the current value of console methods. log.methodFactory = function(methodName, logLevel, loggerName) { - return function(...args) { + return function(this: PrefixedLogger, ...args) { /* eslint-disable @typescript-eslint/no-invalid-this */ if (this.prefix) { args.unshift(this.prefix); diff --git a/src/models/MSC3089Branch.ts b/src/models/MSC3089Branch.ts index a230a4f1a9c..e8c5af14acb 100644 --- a/src/models/MSC3089Branch.ts +++ b/src/models/MSC3089Branch.ts @@ -62,7 +62,7 @@ export class MSC3089Branch { } private get roomId(): string { - return this.indexEvent.getRoomId(); + return this.indexEvent.getRoomId()!; } /** @@ -223,7 +223,7 @@ export class MSC3089Branch { do { childEvent = timelineEvents.find(e => e.replacingEventId() === parentEvent.getId()); if (childEvent) { - const branch = this.directory.getFile(childEvent.getId()); + const branch = this.directory.getFile(childEvent.getId()!); if (branch) { fileHistory.push(branch); parentEvent = childEvent; diff --git a/src/models/beacon.ts b/src/models/beacon.ts index 584cae6491a..0ec955ffaa2 100644 --- a/src/models/beacon.ts +++ b/src/models/beacon.ts @@ -73,7 +73,7 @@ export class Beacon extends TypedEventEmitter$143350589368169JsLZx:localhost * */ - public getId(): string { + public getId(): string | undefined { return this.event.event_id; } @@ -400,7 +400,7 @@ export class MatrixEvent extends TypedEventEmitter@alice:matrix.org */ - public getSender(): string { + public getSender(): string | undefined { return this.event.sender || this.event.user_id; // v2 / v1 } @@ -521,7 +521,7 @@ export class MatrixEvent extends TypedEventEmitter [...c, ...p.getRelations()], []); + return this.relations.reduce((c, p) => [...c, ...p.getRelations()], []); } public on(ev: T, fn: Listener) { diff --git a/src/models/relations-container.ts b/src/models/relations-container.ts index e08b80cbdd0..edb2616107e 100644 --- a/src/models/relations-container.ts +++ b/src/models/relations-container.ts @@ -74,7 +74,7 @@ export class RelationsContainer { * @param {MatrixEvent} event The event to check as relation target. */ public aggregateParentEvent(event: MatrixEvent): void { - const relationsForEvent = this.relations.get(event.getId()); + const relationsForEvent = this.relations.get(event.getId()!); if (!relationsForEvent) return; for (const relationsWithRelType of relationsForEvent.values()) { diff --git a/src/models/relations.ts b/src/models/relations.ts index 070e5e46050..a384c9b7f26 100644 --- a/src/models/relations.ts +++ b/src/models/relations.ts @@ -76,7 +76,7 @@ export class Relations extends TypedEventEmitter { // read by megolm via getter; boolean value - null indicates "use global value" private blacklistUnverifiedDevices?: boolean; private selfMembership?: string; - private summaryHeroes: string[] = null; + private summaryHeroes: string[] | null = null; // flags to stop logspam about missing m.room.create events private getTypeWarning = false; private getVersionWarning = false; @@ -238,25 +240,25 @@ export class Room extends ReadReceipt { /** * The room summary. */ - public summary: RoomSummary = null; + public summary: RoomSummary | null = null; // legacy fields /** * The live event timeline for this room, with the oldest event at index 0. * Present for backwards compatibility - prefer getLiveTimeline().getEvents() */ - public timeline: MatrixEvent[]; + public timeline!: MatrixEvent[]; /** * oldState The state of the room at the time of the oldest * event in the live timeline. Present for backwards compatibility - * prefer getLiveTimeline().getState(EventTimeline.BACKWARDS). */ - public oldState: RoomState; + public oldState!: RoomState; /** * currentState The state of the room at the time of the * newest event in the timeline. Present for backwards compatibility - * prefer getLiveTimeline().getState(EventTimeline.FORWARDS). */ - public currentState: RoomState; + public currentState!: RoomState; public readonly relations = new RelationsContainer(this.client, this); /** @@ -592,7 +594,7 @@ export class Room extends ReadReceipt { * @throws If opts.pendingEventOrdering was not 'detached' */ public getPendingEvents(): MatrixEvent[] { - if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) { + if (!this.pendingEventList) { throw new Error( "Cannot call getPendingEvents with pendingEventOrdering == " + this.opts.pendingEventOrdering); @@ -608,7 +610,7 @@ export class Room extends ReadReceipt { * @return {boolean} True if an element was removed. */ public removePendingEvent(eventId: string): boolean { - if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) { + if (!this.pendingEventList) { throw new Error( "Cannot call removePendingEvent with pendingEventOrdering == " + this.opts.pendingEventOrdering); @@ -634,11 +636,7 @@ export class Room extends ReadReceipt { * @return {boolean} */ public hasPendingEvent(eventId: string): boolean { - if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) { - return false; - } - - return this.pendingEventList.some(event => event.getId() === eventId); + return this.pendingEventList?.some(event => event.getId() === eventId) ?? false; } /** @@ -648,11 +646,7 @@ export class Room extends ReadReceipt { * @return {MatrixEvent} */ public getPendingEvent(eventId: string): MatrixEvent | null { - if (this.opts.pendingEventOrdering !== PendingEventOrdering.Detached) { - return null; - } - - return this.pendingEventList.find(event => event.getId() === eventId) ?? null; + return this.pendingEventList?.find(event => event.getId() === eventId) ?? null; } /** @@ -693,17 +687,16 @@ export class Room extends ReadReceipt { * @return {string} user id of the inviter */ public getDMInviter(): string | undefined { - if (this.myUserId) { - const me = this.getMember(this.myUserId); - if (me) { - return me.getDMInviter(); - } + const me = this.getMember(this.myUserId); + if (me) { + return me.getDMInviter(); } + if (this.selfMembership === "invite") { // fall back to summary information const memberCount = this.getInvitedAndJoinedMemberCount(); - if (memberCount == 2 && this.summaryHeroes.length) { - return this.summaryHeroes[0]; + if (memberCount === 2) { + return this.summaryHeroes?.[0]; } } } @@ -720,11 +713,8 @@ export class Room extends ReadReceipt { return inviterId; } } - // remember, we're assuming this room is a DM, - // so returning the first member we find should be fine - const hasHeroes = Array.isArray(this.summaryHeroes) && - this.summaryHeroes.length; - if (hasHeroes) { + // Remember, we're assuming this room is a DM, so returning the first member we find should be fine + if (Array.isArray(this.summaryHeroes) && this.summaryHeroes.length) { return this.summaryHeroes[0]; } const members = this.currentState.getMembers(); @@ -743,10 +733,9 @@ export class Room extends ReadReceipt { if (memberCount > 2) { return; } - const hasHeroes = Array.isArray(this.summaryHeroes) && - this.summaryHeroes.length; + const hasHeroes = Array.isArray(this.summaryHeroes) && this.summaryHeroes.length; if (hasHeroes) { - const availableMember = this.summaryHeroes.map((userId) => { + const availableMember = this.summaryHeroes!.map((userId) => { return this.getMember(userId); }).find((member) => !!member); if (availableMember) { @@ -767,7 +756,7 @@ export class Room extends ReadReceipt { // if all else fails, try falling back to a user, // and create a one-off member for it if (hasHeroes) { - const availableUser = this.summaryHeroes.map((userId) => { + const availableUser = this.summaryHeroes!.map((userId) => { return this.client.getUser(userId); }).find((user) => !!user); if (availableUser) { @@ -934,7 +923,7 @@ export class Room extends ReadReceipt { // Get the main TimelineSet const timelineSet = this.getUnfilteredTimelineSet(); - let newTimeline: EventTimeline; + let newTimeline: Optional; // If there isn't any event in the timeline, let's go fetch the latest // event and construct a timeline from it. // @@ -965,7 +954,7 @@ export class Room extends ReadReceipt { // we reset everything. The `timelineSet` we pass in needs to be empty // in order for this function to call `/context` and generate a new // timeline. - newTimeline = await this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId()); + newTimeline = await this.client.getEventTimeline(timelineSet, mostRecentEventInTimeline.getId()!); } // If a racing `/sync` beat us to creating a new timeline, use that @@ -982,11 +971,11 @@ export class Room extends ReadReceipt { // of using the `/context` historical token (ex. `t12-13_0_0_0_0_0_0_0_0`) // so that it matches the next response from `/sync` and we can properly // continue the timeline. - newTimeline.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS); + newTimeline!.setPaginationToken(forwardPaginationToken, EventTimeline.FORWARDS); // Set our new fresh timeline as the live timeline to continue syncing // forwards and back paginating from. - timelineSet.setLiveTimeline(newTimeline); + timelineSet.setLiveTimeline(newTimeline!); // Fixup `this.oldstate` so that `scrollback` has the pagination tokens // available this.fixUpLegacyTimelineFields(); @@ -1020,7 +1009,8 @@ export class Room extends ReadReceipt { public resetLiveTimeline(backPaginationToken: string | null, forwardPaginationToken: string | null): void { for (let i = 0; i < this.timelineSets.length; i++) { this.timelineSets[i].resetLiveTimeline( - backPaginationToken, forwardPaginationToken, + backPaginationToken ?? undefined, + forwardPaginationToken ?? undefined, ); } @@ -1130,7 +1120,7 @@ export class Room extends ReadReceipt { * @return {?module:models/event-timeline~EventTimeline} timeline containing * the given event, or null if unknown */ - public getTimelineForEvent(eventId: string): EventTimeline { + public getTimelineForEvent(eventId: string): EventTimeline | null { const event = this.findEventById(eventId); const thread = this.findThreadForEvent(event); if (thread) { @@ -1712,8 +1702,8 @@ export class Room extends ReadReceipt { this.currentState, toStartOfTimeline, ); - if (!this.getThread(rootEvent.getId())) { - this.createThread(rootEvent.getId(), rootEvent, [], toStartOfTimeline); + if (!this.getThread(rootEvent.getId()!)) { + this.createThread(rootEvent.getId()!, rootEvent, [], toStartOfTimeline); } } } @@ -1757,14 +1747,14 @@ export class Room extends ReadReceipt { * is only meant as a short term patch */ const threadAMetadata = eventA - .getServerAggregatedRelation(THREAD_RELATION_TYPE.name); + .getServerAggregatedRelation(THREAD_RELATION_TYPE.name)!; const threadBMetadata = eventB - .getServerAggregatedRelation(THREAD_RELATION_TYPE.name); + .getServerAggregatedRelation(THREAD_RELATION_TYPE.name)!; return threadAMetadata.latest_event.origin_server_ts - threadBMetadata.latest_event.origin_server_ts; }); - let latestMyThreadsRootEvent: MatrixEvent; + let latestMyThreadsRootEvent: MatrixEvent | undefined; const roomState = this.getLiveTimeline().getState(EventTimeline.FORWARDS); for (const rootEvent of threadRoots) { this.threadsTimelineSets[0].addLiveEvent(rootEvent, { @@ -1870,7 +1860,7 @@ export class Room extends ReadReceipt { } // A thread root is always shown in both timelines - if (event.isThreadRoot || roots?.has(event.getId())) { + if (event.isThreadRoot || roots?.has(event.getId()!)) { return { shouldLiveInRoom: true, shouldLiveInThread: true, @@ -1887,7 +1877,7 @@ export class Room extends ReadReceipt { }; } - const parentEventId = event.getAssociatedId(); + const parentEventId = event.getAssociatedId()!; const parentEvent = this.findEventById(parentEventId) ?? events?.find(e => e.getId() === parentEventId); // Treat relations and redactions as extensions of their parents so evaluate parentEvent instead @@ -1896,7 +1886,7 @@ export class Room extends ReadReceipt { } // Edge case where we know the event is a relation but don't have the parentEvent - if (roots?.has(event.relationEventId)) { + if (roots?.has(event.relationEventId!)) { return { shouldLiveInRoom: true, shouldLiveInThread: true, @@ -1940,10 +1930,10 @@ export class Room extends ReadReceipt { const eventsByThread: { [threadId: string]: MatrixEvent[] } = {}; for (const event of events) { const { threadId, shouldLiveInThread } = this.eventShouldLiveIn(event); - if (shouldLiveInThread && !eventsByThread[threadId]) { - eventsByThread[threadId] = []; + if (shouldLiveInThread && !eventsByThread[threadId!]) { + eventsByThread[threadId!] = []; } - eventsByThread[threadId]?.push(event); + eventsByThread[threadId!]?.push(event); } Object.entries(eventsByThread).map(([threadId, threadEvents]) => ( @@ -1958,7 +1948,7 @@ export class Room extends ReadReceipt { toStartOfTimeline: boolean, ): Thread { if (rootEvent) { - const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId()); + const relatedEvents = this.relations.getAllChildEventsForEvent(rootEvent.getId()!); if (relatedEvents?.length) { // Include all relations of the root event, given it'll be visible in both timelines, // except `m.replace` as that will already be applied atop the event using `MatrixEvent::makeReplaced` @@ -2270,14 +2260,14 @@ export class Room extends ReadReceipt { * @private */ public handleRemoteEcho(remoteEvent: MatrixEvent, localEvent: MatrixEvent): void { - const oldEventId = localEvent.getId(); - const newEventId = remoteEvent.getId(); + const oldEventId = localEvent.getId()!; + const newEventId = remoteEvent.getId()!; const oldStatus = localEvent.status; logger.debug(`Got remote echo for event ${oldEventId} -> ${newEventId} old status ${oldStatus}`); // no longer pending - delete this.txnToEvent[remoteEvent.getUnsigned().transaction_id]; + delete this.txnToEvent[remoteEvent.getUnsigned().transaction_id!]; // if it's in the pending list, remove it if (this.pendingEventList) { @@ -2289,7 +2279,7 @@ export class Room extends ReadReceipt { localEvent.handleRemoteEcho(remoteEvent.event); const { shouldLiveInRoom, threadId } = this.eventShouldLiveIn(remoteEvent); - const thread = this.getThread(threadId); + const thread = threadId ? this.getThread(threadId) : null; thread?.timelineSet.handleRemoteEcho(localEvent, oldEventId, newEventId); if (shouldLiveInRoom) { @@ -2345,7 +2335,7 @@ export class Room extends ReadReceipt { remoteEvent.setUnsigned(unsigned); // the remote event is _already_ in the timeline, so we need to remove it so // we can convert the local event into the final event. - this.removeEvent(remoteEvent.getId()); + this.removeEvent(remoteEvent.getId()!); this.handleRemoteEcho(remoteEvent, event); } return; @@ -2353,17 +2343,15 @@ export class Room extends ReadReceipt { } const oldStatus = event.status; - const oldEventId = event.getId(); + const oldEventId = event.getId()!; if (!oldStatus) { - throw new Error("updatePendingEventStatus called on an event which is " + - "not a local echo."); + throw new Error("updatePendingEventStatus called on an event which is not a local echo."); } const allowed = ALLOWED_TRANSITIONS[oldStatus]; - if (!allowed || allowed.indexOf(newStatus) < 0) { - throw new Error("Invalid EventStatus transition " + oldStatus + "->" + - newStatus); + if (!allowed?.includes(newStatus)) { + throw new Error(`Invalid EventStatus transition ${oldStatus}->${newStatus}`); } event.setStatus(newStatus); @@ -2964,7 +2952,7 @@ export class Room extends ReadReceipt { }).map((m) => m.name); } - let oldName: string; + let oldName: string | undefined; if (leftNames.length) { oldName = this.roomNameGenerator({ type: RoomNameType.Generated, @@ -3060,8 +3048,8 @@ export class Room extends ReadReceipt { throw new Error("expected a visibility change event"); } const relation = event.getRelation(); - const originalEventId = relation.event_id; - const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId); + const originalEventId = relation?.event_id; + const visibilityEventsOnOriginalEvent = this.visibilityEvents.get(originalEventId!); if (!visibilityEventsOnOriginalEvent) { // No visibility changes on the original event. // In particular, this change event was not recorded, @@ -3079,13 +3067,13 @@ export class Room extends ReadReceipt { // If we removed the latest visibility change event, propagate changes. if (index === visibilityEventsOnOriginalEvent.length) { - const originalEvent = this.findEventById(originalEventId); + const originalEvent = this.findEventById(originalEventId!); if (!originalEvent) { return; } if (index === 0) { // We have just removed the only visibility change event. - this.visibilityEvents.delete(originalEventId); + this.visibilityEvents.delete(originalEventId!); originalEvent.applyVisibilityEvent(); } else { const newEvent = visibilityEventsOnOriginalEvent[visibilityEventsOnOriginalEvent.length - 1]; @@ -3110,7 +3098,7 @@ export class Room extends ReadReceipt { * change event. */ private applyPendingVisibilityEvents(event: MatrixEvent): void { - const visibilityEvents = this.visibilityEvents.get(event.getId()); + const visibilityEvents = this.visibilityEvents.get(event.getId()!); if (!visibilityEvents || visibilityEvents.length == 0) { // No pending visibility change in store. return; diff --git a/src/models/thread.ts b/src/models/thread.ts index e77e3f5b830..69b47004026 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -222,7 +222,7 @@ export class Thread extends ReadReceipt { } private addEventToTimeline(event: MatrixEvent, toStartOfTimeline: boolean): void { - if (!this.findEventById(event.getId())) { + if (!this.findEventById(event.getId()!)) { this.timelineSet.addEventToTimeline( event, this.liveTimeline, @@ -305,7 +305,7 @@ export class Thread extends ReadReceipt { this._currentUserParticipated = !!bundledRelationship.current_user_participated; const event = new MatrixEvent({ - room_id: this.rootEvent.getRoomId(), + room_id: this.room.roomId, ...bundledRelationship.latest_event, }); this.setEventMetadata(event); @@ -322,7 +322,7 @@ export class Thread extends ReadReceipt { private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise { return Promise.all(events.filter(e => e.isEncrypted()).map((event: MatrixEvent) => { if (event.isRelation()) return; // skip - relations don't get edits - return this.client.relations(this.roomId, event.getId(), RelationType.Replace, event.getType(), { + return this.client.relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), { limit: 1, }).then(relations => { if (relations.events.length) { diff --git a/src/pushprocessor.ts b/src/pushprocessor.ts index 048c24f6b01..2976417fdc1 100644 --- a/src/pushprocessor.ts +++ b/src/pushprocessor.ts @@ -302,7 +302,7 @@ export class PushProcessor { // Note that this should not be the current state of the room but the state at // the point the event is in the DAG. Unfortunately the js-sdk does not store // this. - return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()); + return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender()!); } private eventFulfillsRoomMemberCountCondition(cond: IRoomMemberCountCondition, ev: MatrixEvent): boolean { diff --git a/src/rendezvous/MSC3906Rendezvous.ts b/src/rendezvous/MSC3906Rendezvous.ts index d887c4be494..e275a3a0ad0 100644 --- a/src/rendezvous/MSC3906Rendezvous.ts +++ b/src/rendezvous/MSC3906Rendezvous.ts @@ -208,7 +208,7 @@ export class MSC3906Rendezvous { await this.send({ type: PayloadType.Finish, outcome: Outcome.Verified, - verifying_device_id: this.client.getDeviceId(), + verifying_device_id: this.client.getDeviceId()!, verifying_device_key: this.client.getDeviceEd25519Key()!, master_key: masterPublicKey, }); diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index 1c0bd63401e..5b02ca0c556 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -418,7 +418,7 @@ export class SlidingSyncSdk { // this room, then timeline_limit: 50). const knownEvents = new Set(); room.getLiveTimeline().getEvents().forEach((e) => { - knownEvents.add(e.getId()); + knownEvents.add(e.getId()!); }); // all unknown events BEFORE a known event must be scrollback e.g: // D E <-- what we know @@ -433,7 +433,7 @@ export class SlidingSyncSdk { let seenKnownEvent = false; for (let i = timelineEvents.length-1; i >= 0; i--) { const recvEvent = timelineEvents[i]; - if (knownEvents.has(recvEvent.getId())) { + if (knownEvents.has(recvEvent.getId()!)) { seenKnownEvent = true; continue; // don't include this event, it's a dupe } diff --git a/src/store/index.ts b/src/store/index.ts index c8df0346e30..bed7aa7a69c 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -24,6 +24,7 @@ import { IMinimalEvent, IRooms, ISyncResponse } from "../sync-accumulator"; import { IStartClientOpts } from "../client"; import { IStateEventWithRoomId } from "../@types/search"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; +import { EventEmitterEvents } from "../models/typed-event-emitter"; export interface ISavedSync { nextBatch: string; @@ -39,7 +40,7 @@ export interface IStore { // XXX: The indexeddb store exposes a non-standard emitter for the "degraded" event // for when it falls back to being a memory store due to errors. - on?: (event: string, handler: (...args: any[]) => void) => void; + on?: (event: EventEmitterEvents | "degraded", handler: (...args: any[]) => void) => void; /** @return {Promise} whether or not the database was newly created in this session. */ isNewlyCreated(): Promise; @@ -231,7 +232,7 @@ export interface IStore { clearOutOfBandMembers(roomId: string): Promise; - getClientOptions(): Promise; + getClientOptions(): Promise; storeClientOptions(options: IStartClientOpts): Promise; diff --git a/src/store/indexeddb-backend.ts b/src/store/indexeddb-backend.ts index 1e08c2e737e..5da164c555b 100644 --- a/src/store/indexeddb-backend.ts +++ b/src/store/indexeddb-backend.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { ISavedSync } from "./index"; -import { IEvent, IStartClientOpts, IStateEventWithRoomId, ISyncResponse } from "../matrix"; +import { IEvent, IStateEventWithRoomId, IStoredClientOpts, ISyncResponse } from "../matrix"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; export interface IIndexedDBBackend { @@ -30,8 +30,8 @@ export interface IIndexedDBBackend { setOutOfBandMembers(roomId: string, membershipEvents: IStateEventWithRoomId[]): Promise; clearOutOfBandMembers(roomId: string): Promise; getUserPresenceEvents(): Promise; - getClientOptions(): Promise; - storeClientOptions(options: IStartClientOpts): Promise; + getClientOptions(): Promise; + storeClientOptions(options: IStoredClientOpts): Promise; saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise; getOldestToDeviceBatch(): Promise; removeToDeviceBatch(id: number): Promise; diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index 007895ea288..a2b4448440c 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -18,7 +18,7 @@ import { IMinimalEvent, ISyncData, ISyncResponse, SyncAccumulator } from "../syn import * as utils from "../utils"; import * as IndexedDBHelpers from "../indexeddb-helpers"; import { logger } from '../logger'; -import { IStartClientOpts, IStateEventWithRoomId } from "../matrix"; +import { IStateEventWithRoomId, IStoredClientOpts } from "../matrix"; import { ISavedSync } from "./index"; import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; @@ -538,7 +538,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { }); } - public getClientOptions(): Promise { + public getClientOptions(): Promise { return Promise.resolve().then(() => { const txn = this.db!.transaction(["client_options"], "readonly"); const store = txn.objectStore("client_options"); @@ -548,7 +548,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { }); } - public async storeClientOptions(options: IStartClientOpts): Promise { + public async storeClientOptions(options: IStoredClientOpts): Promise { const txn = this.db!.transaction(["client_options"], "readwrite"); const store = txn.objectStore("client_options"); store.put({ diff --git a/src/store/indexeddb-remote-backend.ts b/src/store/indexeddb-remote-backend.ts index 025b3755f42..39486fe2325 100644 --- a/src/store/indexeddb-remote-backend.ts +++ b/src/store/indexeddb-remote-backend.ts @@ -17,7 +17,7 @@ limitations under the License. import { logger } from "../logger"; import { defer, IDeferred } from "../utils"; import { ISavedSync } from "./index"; -import { IStartClientOpts } from "../client"; +import { IStoredClientOpts } from "../client"; import { IStateEventWithRoomId, ISyncResponse } from "../matrix"; import { IIndexedDBBackend, UserTuple } from "./indexeddb-backend"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; @@ -118,11 +118,11 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend { return this.doCmd('clearOutOfBandMembers', [roomId]); } - public getClientOptions(): Promise { + public getClientOptions(): Promise { return this.doCmd('getClientOptions'); } - public storeClientOptions(options: IStartClientOpts): Promise { + public storeClientOptions(options: IStoredClientOpts): Promise { return this.doCmd('storeClientOptions', [options]); } diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index bb5dc1f16b0..961e66fd3e0 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -28,6 +28,7 @@ import { ISyncResponse } from "../sync-accumulator"; import { TypedEventEmitter } from "../models/typed-event-emitter"; import { IStateEventWithRoomId } from "../@types/search"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; +import { IStoredClientOpts } from "../client"; /** * This is an internal module. See {@link IndexedDBStore} for the public class. @@ -269,11 +270,11 @@ export class IndexedDBStore extends MemoryStore { return this.backend.clearOutOfBandMembers(roomId); }, "clearOutOfBandMembers"); - public getClientOptions = this.degradable((): Promise => { + public getClientOptions = this.degradable((): Promise => { return this.backend.getClientOptions(); }, "getClientOptions"); - public storeClientOptions = this.degradable((options: object): Promise => { + public storeClientOptions = this.degradable((options: IStoredClientOpts): Promise => { super.storeClientOptions(options); return this.backend.storeClientOptions(options); }, "storeClientOptions"); diff --git a/src/store/memory.ts b/src/store/memory.ts index 4c0fb7c08da..24b79fccbd6 100644 --- a/src/store/memory.ts +++ b/src/store/memory.ts @@ -31,6 +31,7 @@ import { RoomSummary } from "../models/room-summary"; import { ISyncResponse } from "../sync-accumulator"; import { IStateEventWithRoomId } from "../@types/search"; import { IndexedToDeviceBatch, ToDeviceBatchWithTxnId } from "../models/ToDeviceMessage"; +import { IStoredClientOpts } from "../client"; function isValidFilterId(filterId?: string | number | null): boolean { const isValidStr = typeof filterId === "string" && @@ -64,7 +65,7 @@ export class MemoryStore implements IStore { protected readonly localStorage?: Storage; private oobMembers: Record = {}; // roomId: [member events] private pendingEvents: { [roomId: string]: Partial[] } = {}; - private clientOptions = {}; + private clientOptions?: IStoredClientOpts; private pendingToDeviceBatches: IndexedToDeviceBatch[] = []; private nextToDeviceBatchId = 0; @@ -169,7 +170,7 @@ export class MemoryStore implements IStore { */ public getRoomSummaries(): RoomSummary[] { return Object.values(this.rooms).map(function(room) { - return room.summary; + return room.summary!; }); } @@ -412,11 +413,11 @@ export class MemoryStore implements IStore { return Promise.resolve(); } - public getClientOptions(): Promise { + public getClientOptions(): Promise { return Promise.resolve(this.clientOptions); } - public storeClientOptions(options: object): Promise { + public storeClientOptions(options: IStoredClientOpts): Promise { this.clientOptions = Object.assign({}, options); return Promise.resolve(); } diff --git a/src/store/stub.ts b/src/store/stub.ts index 7f4b8c07088..64a35d513eb 100644 --- a/src/store/stub.ts +++ b/src/store/stub.ts @@ -29,6 +29,7 @@ import { RoomSummary } from "../models/room-summary"; import { ISyncResponse } from "../sync-accumulator"; import { IStateEventWithRoomId } from "../@types/search"; import { IndexedToDeviceBatch, ToDeviceBatch } from "../models/ToDeviceMessage"; +import { IStoredClientOpts } from "../client"; /** * Construct a stub store. This does no-ops on most store methods. @@ -256,11 +257,11 @@ export class StubStore implements IStore { return Promise.resolve(); } - public getClientOptions(): Promise { - return Promise.resolve({}); + public getClientOptions(): Promise { + return Promise.resolve(undefined); } - public storeClientOptions(options: object): Promise { + public storeClientOptions(options: IStoredClientOpts): Promise { return Promise.resolve(); } diff --git a/src/sync.ts b/src/sync.ts index f655d247646..658b502db91 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1081,11 +1081,11 @@ export class SyncApi { if (Array.isArray(data.presence?.events)) { data.presence!.events.map(client.getEventMapper()).forEach( function(presenceEvent) { - let user = client.store.getUser(presenceEvent.getSender()); + let user = client.store.getUser(presenceEvent.getSender()!); if (user) { user.setPresenceEvent(presenceEvent); } else { - user = createNewUser(client, presenceEvent.getSender()); + user = createNewUser(client, presenceEvent.getSender()!); user.setPresenceEvent(presenceEvent); client.store.storeUser(user); } @@ -1097,7 +1097,7 @@ export class SyncApi { if (Array.isArray(data.account_data?.events)) { const events = data.account_data.events.map(client.getEventMapper()); const prevEventsMap = events.reduce((m, c) => { - m[c.getType()] = client.store.getAccountData(c.getType()); + m[c.getType()!] = client.store.getAccountData(c.getType()); return m; }, {}); client.store.storeAccountDataEvents(events); @@ -1111,7 +1111,7 @@ export class SyncApi { const rules = accountDataEvent.getContent(); client.pushRules = PushProcessor.rewriteDefaultRules(rules); } - const prevEvent = prevEventsMap[accountDataEvent.getType()]; + const prevEvent = prevEventsMap[accountDataEvent.getType()!]; client.emit(ClientEvent.AccountData, accountDataEvent, prevEvent); return accountDataEvent; }, @@ -1330,10 +1330,9 @@ export class SyncApi { // will stop us linking the empty timeline into the chain). // for (let i = events.length - 1; i >= 0; i--) { - const eventId = events[i].getId(); + const eventId = events[i].getId()!; if (room.getTimelineForEvent(eventId)) { - debuglog("Already have event " + eventId + " in limited " + - "sync - not resetting"); + debuglog(`Already have event ${eventId} in limited sync - not resetting`); limited = false; // we might still be missing some of the events before i; diff --git a/src/utils.ts b/src/utils.ts index 1e396d2de5d..237d1279e2b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -355,7 +355,9 @@ export function globToRegexp(glob: string, extended = false): string { const replacements: ([RegExp, string | ((substring: string, ...args: any[]) => string) ])[] = [ [/\\\*/g, '.*'], [/\?/g, '.'], - !extended && [ + ]; + if (!extended) { + replacements.push([ /\\\[(!|)(.*)\\]/g, (_match: string, neg: string, pat: string) => [ '[', @@ -363,8 +365,8 @@ export function globToRegexp(glob: string, extended = false): string { pat.replace(/\\-/, '-'), ']', ].join(''), - ], - ]; + ]); + } return replacements.reduce( // https://github.com/microsoft/TypeScript/issues/30134 (pat, args) => args ? pat.replace(args[0], args[1] as any) : pat, @@ -372,8 +374,11 @@ export function globToRegexp(glob: string, extended = false): string { ); } -export function ensureNoTrailingSlash(url: string): string { - if (url && url.endsWith("/")) { +export function ensureNoTrailingSlash(url: string): string; +export function ensureNoTrailingSlash(url: undefined): undefined; +export function ensureNoTrailingSlash(url?: string): string | undefined; +export function ensureNoTrailingSlash(url?: string): string | undefined { + if (url?.endsWith("/")) { return url.slice(0, -1); } else { return url; diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 2b0a975fb6f..b495dfec659 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -2134,7 +2134,7 @@ export class MatrixCall extends TypedEventEmitter = new Map(); - for (const call of this.client.callEventHandler.calls.values()) { + for (const call of this.client.callEventHandler!.calls.values()) { callMediaStreamParams.set(call.callId, { audio: call.hasLocalUserMediaAudioTrack, video: call.hasLocalUserMediaVideoTrack, }); } - for (const call of this.client.callEventHandler.calls.values()) { + for (const call of this.client.callEventHandler!.calls.values()) { if (call.state === CallState.Ended || !callMediaStreamParams.has(call.callId)) continue; const { audio, video } = callMediaStreamParams.get(call.callId)!; From 0f1012278a37d91c04bbda6ffa8c45cf80188977 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 26 Oct 2022 12:01:53 +0100 Subject: [PATCH 11/11] Fix types --- spec/unit/embedded.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/unit/embedded.spec.ts b/spec/unit/embedded.spec.ts index 3a144215c6a..436909be73c 100644 --- a/spec/unit/embedded.spec.ts +++ b/spec/unit/embedded.spec.ts @@ -28,6 +28,7 @@ import { WidgetApiToWidgetAction, MatrixCapabilities, ITurnServer, + IRoomEvent, } from "matrix-widget-api"; import { createRoomWidgetClient, MsgType } from "../../src/matrix"; @@ -184,7 +185,7 @@ describe("RoomWidgetClient", () => { it("backfills", async () => { widgetApi.readStateEvents.mockImplementation(async (eventType, limit, stateKey) => eventType === "org.example.foo" && (limit ?? Infinity) > 0 && stateKey === "bar" - ? [event] + ? [event as IRoomEvent] : [], );