From 9f619be08d9ae5d49bc4920332b920edc13e106d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 14:40:31 +0100 Subject: [PATCH 01/94] Update peter-evans/create-pull-request digest to 284f54f (#3331) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/upgrade_dependencies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upgrade_dependencies.yml b/.github/workflows/upgrade_dependencies.yml index 4db0b797db9..e86e7694a7a 100644 --- a/.github/workflows/upgrade_dependencies.yml +++ b/.github/workflows/upgrade_dependencies.yml @@ -20,7 +20,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 # v5 + uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 # v5 with: token: ${{ secrets.ELEMENT_BOT_TOKEN }} branch: actions/upgrade-deps From ca9263fa647e4cfc581a137c389ea646bcf71ce6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 14:56:42 +0100 Subject: [PATCH 02/94] Update JS-DevTools/npm-publish action to v2 (#3336) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/release-npm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index daa9cb589bc..714105481db 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -24,7 +24,7 @@ jobs: - name: 🚀 Publish to npm id: npm-publish - uses: JS-DevTools/npm-publish@0f451a94170d1699fd50710966d48fb26194d939 # v1 + uses: JS-DevTools/npm-publish@0be441d808570461daedc3fb178405dbcac54de0 # v2 with: token: ${{ secrets.NPM_TOKEN }} access: public From be742e811c2b9ad7d03e45551f4e5da7d3fdfee7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 13:58:33 +0000 Subject: [PATCH 03/94] Update dependency @types/jest to v29.5.1 (#3333) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index a3d837ff1c1..d8656d60dce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1741,9 +1741,9 @@ "@types/istanbul-lib-report" "*" "@types/jest@^29.0.0": - version "29.5.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.0.tgz#337b90bbcfe42158f39c2fb5619ad044bbb518ac" - integrity sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg== + version "29.5.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.1.tgz#83c818aa9a87da27d6da85d3378e5a34d2f31a47" + integrity sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1772,7 +1772,12 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== -"@types/node@*", "@types/node@18": +"@types/node@*": + version "18.16.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" + integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== + +"@types/node@18": version "18.15.11" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== From 0de73a0b3ec712afc3d0fa6858fca7b738bd0d3f Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 2 May 2023 17:53:36 +0200 Subject: [PATCH 04/94] Update `@matrix-org/matrix-sdk-crypto-js` to `^0.1.0-alpha.8` (#3337) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9ad447372e9..ed99e1ffc88 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ ], "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.7", + "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.8", "another-json": "^0.2.0", "bs58": "^5.0.0", "content-type": "^1.0.4", diff --git a/yarn.lock b/yarn.lock index d8656d60dce..8a4df3d3ed2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1419,10 +1419,10 @@ dependencies: lodash "^4.17.21" -"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.7": - version "0.1.0-alpha.7" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.7.tgz#136375b84fd8a7e698f70fc969f668e541a61313" - integrity sha512-sQEG9cSfNji5NYBf5h7j5IxYVO0dwtAKoetaVyR+LhIXz/Su7zyEE3EwlAWAeJOFdAV/vZ5LTNyh39xADuNlTg== +"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.8": + version "0.1.0-alpha.8" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz#18dd8e7fb56602d2999d8a502b49e902a2bb3782" + integrity sha512-hdmbbGXKrN6JNo3wdBaR5Zs3lXlzllT3U43ViNTlabB3nKkOZQnEAN/Isv+4EQSgz1+8897veI9Q8sqlQX22oA== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" From ee2f1cdfd4dea1b437b5067c448cbf3058a1a26e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 2 May 2023 16:57:53 +0100 Subject: [PATCH 05/94] Accumulate receipts for the main thread and unthreaded separately. (#3339) Fixes matrix-org/element-web#24629 --- spec/unit/receipt-accumulator.spec.ts | 44 +++++++++++++++++++++++++++ src/receipt-accumulator.ts | 4 +-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/spec/unit/receipt-accumulator.spec.ts b/spec/unit/receipt-accumulator.spec.ts index 5c9e779de00..6e2c71cea83 100644 --- a/spec/unit/receipt-accumulator.spec.ts +++ b/spec/unit/receipt-accumulator.spec.ts @@ -129,6 +129,50 @@ describe("ReceiptAccumulator", function () { ]), ); }); + + it("Keeps main thread receipts even when an unthreaded receipt came later", () => { + const acc = new ReceiptAccumulator(); + + // Given receipts for the special thread "main" and also unthreaded + // receipts (which have no thread id). + const receipt1 = newReceipt("$event1", ReceiptType.Read, "@alice:localhost", 1, "main"); + const receipt2 = newReceipt("$event2", ReceiptType.Read, "@alice:localhost", 2); + + // When we collect them + acc.consumeEphemeralEvents([receipt1, receipt2]); + const newEvent = acc.buildAccumulatedReceiptEvent(roomId); + + // We preserve both: thread:main and unthreaded receipts are different + // things, with different meanings. + expect(newEvent).toEqual( + newMultiReceipt([ + ["$event1", ReceiptType.Read, "@alice:localhost", 1, "main"], + ["$event2", ReceiptType.Read, "@alice:localhost", 2, undefined], + ]), + ); + }); + + it("Keeps unthreaded receipts even when a main thread receipt came later", () => { + const acc = new ReceiptAccumulator(); + + // Given receipts for the special thread "main" and also unthreaded + // receipts (which have no thread id). + const receipt1 = newReceipt("$event1", ReceiptType.Read, "@alice:localhost", 1); + const receipt2 = newReceipt("$event2", ReceiptType.Read, "@alice:localhost", 2, "main"); + + // When we collect them + acc.consumeEphemeralEvents([receipt1, receipt2]); + const newEvent = acc.buildAccumulatedReceiptEvent(roomId); + + // We preserve both: thread:main and unthreaded receipts are different + // things, with different meanings. + expect(newEvent).toEqual( + newMultiReceipt([ + ["$event1", ReceiptType.Read, "@alice:localhost", 1, undefined], + ["$event2", ReceiptType.Read, "@alice:localhost", 2, "main"], + ]), + ); + }); }); const newReceipt = ( diff --git a/src/receipt-accumulator.ts b/src/receipt-accumulator.ts index ce7230f0ca1..f1037233239 100644 --- a/src/receipt-accumulator.ts +++ b/src/receipt-accumulator.ts @@ -18,7 +18,7 @@ import { IMinimalEvent } from "./sync-accumulator"; import { EventType } from "./@types/event"; import { isSupportedReceiptType, MapWithDefault, recursiveMapToObject } from "./utils"; import { IContent } from "./models/event"; -import { MAIN_ROOM_TIMELINE, ReceiptContent, ReceiptType } from "./@types/read_receipts"; +import { ReceiptContent, ReceiptType } from "./@types/read_receipts"; interface AccumulatedReceipt { data: IMinimalEvent; @@ -118,7 +118,7 @@ export class ReceiptAccumulator { eventId, }; - if (!data.thread_id || data.thread_id === MAIN_ROOM_TIMELINE) { + if (!data.thread_id) { this.setUnthreaded(userId, receipt); } else { this.setThreaded(data.thread_id, userId, receipt); From f3772cdf829a052054037e6cca11d161bbf8b9fb Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 3 May 2023 15:10:02 +0200 Subject: [PATCH 06/94] Avoid upload a new fallback key at every `/sync` (#3338) --- src/rust-crypto/rust-crypto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index e516e199fd3..0d5b4a4f30e 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -319,7 +319,7 @@ export class RustCrypto implements CryptoBackend { private async receiveSyncChanges({ events, oneTimeKeysCounts = new Map(), - unusedFallbackKeys = new Set(), + unusedFallbackKeys, devices = new RustSdkCryptoJs.DeviceLists(), }: { events?: IToDeviceEvent[]; From 8ff8685ae5a930a43e204dc7e1aa9b8da338ade3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 May 2023 09:43:38 +1200 Subject: [PATCH 07/94] Update dependency rimraf to v5 (#3298) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 141 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 108 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index ed99e1ffc88..51cae279011 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "jest-mock": "^29.0.0", "matrix-mock-request": "^2.5.0", "prettier": "2.8.8", - "rimraf": "^4.0.0", + "rimraf": "^5.0.0", "terser": "^5.5.1", "ts-node": "^10.9.1", "tsify": "^5.0.2", diff --git a/yarn.lock b/yarn.lock index 8a4df3d3ed2..34f55a60045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1120,6 +1120,18 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1577,6 +1589,11 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@pkgr/utils@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" @@ -2105,6 +2122,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-sequence-parser@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-sequence-parser/-/ansi-sequence-parser-1.1.0.tgz#4d790f31236ac20366b23b3916b789e1bde39aed" @@ -2129,6 +2151,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansicolors@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" @@ -3287,6 +3314,11 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: dependencies: readable-stream "^2.0.2" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + electron-to-chromium@^1.4.284: version "1.4.356" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.356.tgz#b75a8a8c31d571f6024310cc980a08cd6c15a8c5" @@ -3315,6 +3347,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + enhanced-resolve@^5.12.0: version "5.12.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" @@ -3948,6 +3985,14 @@ foreground-child@^2.0.0: cross-spawn "^7.0.0" signal-exit "^3.0.2" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -4067,6 +4112,17 @@ glob-to-regexp@^0.4.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@^10.0.0: + version "10.2.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.2.tgz#ce2468727de7e035e8ecf684669dc74d0526ab75" + integrity sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ== + dependencies: + foreground-child "^3.1.0" + jackspeak "^2.0.3" + minimatch "^9.0.0" + minipass "^5.0.0" + path-scurry "^1.7.0" + glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -4079,16 +4135,6 @@ glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^9.2.0: - version "9.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" - integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - glob@~3.2.7: version "3.2.11" resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" @@ -4682,6 +4728,15 @@ istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^2.0.3: + version "2.1.5" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.5.tgz#9a6741037b58257dc92eb28e9c8f54d33a1c09ba" + integrity sha512-NeK3mbF9vwNS3SjhzlEfO6WREJqoKtCwLoUPoUVtGJrpecxN3ZxlDuF22MzNSbOk/AA/VFWi+nFMV89xkXh2og== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jest-changed-files@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.5.0.tgz#e88786dca8bf2aa899ec4af7644e16d9dcf9b23e" @@ -5577,13 +5632,6 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatc dependencies: brace-expansion "^1.1.7" -minimatch@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" - integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== - dependencies: - brace-expansion "^2.0.1" - minimatch@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56" @@ -5601,11 +5649,6 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^4.2.4: - version "4.2.7" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.7.tgz#14c6fc0dcab54d9c4dd64b2b7032fef04efec218" - integrity sha512-ScVIgqHcXRMyfflqHmEW0bm8z8rb5McHyOY3ewX9JBgZaR77G7nxq9L/mtV96/QbAAwtbCAHVVLzD1kkyfFQEw== - minipass@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" @@ -5959,10 +6002,10 @@ path-platform@~0.11.15: resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" integrity sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg== -path-scurry@^1.6.1: - version "1.6.4" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.6.4.tgz#020a9449e5382a4acb684f9c7e1283bc5695de66" - integrity sha512-Qp/9IHkdNiXJ3/Kon++At2nVpnhRiPq/aSvQN+H3U1WZbvNRK0RIQK/o4HMqPoXjpuGJUEWpHSs6Mnjxqh3TQg== +path-scurry@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.7.0.tgz#99c741a2cfbce782294a39994d63748b5a24f6db" + integrity sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg== dependencies: lru-cache "^9.0.0" minipass "^5.0.0" @@ -6558,12 +6601,12 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^4.0.0: - version "4.4.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" - integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== +rimraf@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.0.tgz#5bda14e410d7e4dd522154891395802ce032c2cb" + integrity sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g== dependencies: - glob "^9.2.0" + glob "^10.0.0" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" @@ -6720,6 +6763,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.1.tgz#96a61033896120ec9335d96851d902cc98f0ba2a" + integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -6851,7 +6899,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6860,6 +6908,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.trim@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" @@ -6906,13 +6963,20 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -7718,7 +7782,7 @@ wordwrap@0.0.2: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q== -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7727,6 +7791,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 8ab2e104713ac45dc8cc3bd96d2565d2552ce5d5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 5 May 2023 09:13:07 +0100 Subject: [PATCH 08/94] Update types to match spec (#3330) --- src/client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.ts b/src/client.ts index d02fa74fdc6..fe6e9d8c53b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -635,7 +635,7 @@ interface IJoinRequestBody { interface ITagMetadata { [key: string]: any; - order: number; + order?: number; } interface IMessagesResponse { @@ -4185,7 +4185,7 @@ export class MatrixClient extends TypedEventEmitter { + public setRoomTag(roomId: string, tagName: string, metadata: ITagMetadata = {}): Promise<{}> { const path = utils.encodeUri("/user/$userId/rooms/$roomId/tags/$tag", { $userId: this.credentials.userId!, $roomId: roomId, From 1ec7670f6a7747367c05236286839662b5fe72bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 May 2023 04:47:30 +0200 Subject: [PATCH 09/94] Update babel monorepo (#3332) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 366 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 204 insertions(+), 162 deletions(-) diff --git a/yarn.lock b/yarn.lock index 34f55a60045..571d3837f4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,9 +36,9 @@ "@jridgewell/trace-mapping" "^0.3.9" "@babel/cli@^7.12.10": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.21.0.tgz#1868eb70e9824b427fc607610cce8e9e7889e7e1" - integrity sha512-xi7CxyS8XjSyiwUGCfwf+brtJxjW1/ZTcBUkP10xawIEXLX5HzLn+3aXkgxozcP2UhRhtKTmQurw9Uaes7jZrA== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.21.5.tgz#a685a5b50b785f2edfbf6e042c1265c653547d9d" + integrity sha512-TOKytQ9uQW9c4np8F+P7ZfPINy5Kv+pizDIUwSVH8X5zHgYHV4AA8HE5LA450xXeu4jEfmUckTYvv1I4S26M/g== dependencies: "@jridgewell/trace-mapping" "^0.3.17" commander "^4.0.1" @@ -58,26 +58,26 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f" - integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": + version "7.21.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" + integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== "@babel/core@^7.0.0", "@babel/core@^7.11.6", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.4.tgz#c6dc73242507b8e2a27fd13a9c1814f9fa34a659" - integrity sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" + integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helpers" "^7.21.5" + "@babel/parser" "^7.21.8" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.4" - "@babel/types" "^7.21.4" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -85,9 +85,9 @@ semver "^6.3.0" "@babel/eslint-parser@^7.12.10": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz#d79e822050f2de65d7f368a076846e7184234af7" - integrity sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.21.8.tgz#59fb6fc4f3b017ab86987c076226ceef7b2b2ef2" + integrity sha512-HLhI+2q+BP3sf78mFUZNCGc10KEmoUqtUT1OCdMZsN+qr4qFeLUod62/zAnF3jNQstwyasDkZnVXwfK2Bml7MQ== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" @@ -100,7 +100,7 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.21.4", "@babel/generator@^7.7.2": +"@babel/generator@^7.12.11", "@babel/generator@^7.7.2": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== @@ -110,6 +110,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.21.4", "@babel/generator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" + integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== + dependencies: + "@babel/types" "^7.21.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -118,45 +128,46 @@ "@babel/types" "^7.18.6" "@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" - integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.21.5.tgz#817f73b6c59726ab39f6ba18c234268a519e5abb" + integrity sha512-uNrjKztPLkUk7bpCNC0jEKDJzzkvel/W+HguzbN8krA+LPfC1CEobJEvAvGka2A/M+ViOqXdcRL0GqPUJSjx9g== dependencies: - "@babel/helper-explode-assignable-expression" "^7.18.6" - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.5" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.4": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656" - integrity sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== dependencies: - "@babel/compat-data" "^7.21.4" + "@babel/compat-data" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz#3a017163dc3c2ba7deb9a7950849a9586ea24c18" - integrity sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz#205b26330258625ef8869672ebca1e0dee5a0f02" + integrity sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" - "@babel/helper-member-expression-to-functions" "^7.21.0" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.20.7" + "@babel/helper-replace-supers" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/helper-split-export-declaration" "^7.18.6" + semver "^6.3.0" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5" - integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA== + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.8.tgz#a7886f61c2e29e21fd4aaeaf1e473deba6b571dc" + integrity sha512-zGuSdedkFtsFHGbexAvNuipg1hbtitDLo2XE8/uf6Y9sOQV1xsYX/2pNbtedp/X0eU1pIt+kGvaqHCowkRbS5g== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.3.1" + semver "^6.3.0" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -170,17 +181,10 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-explode-assignable-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" - integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" + integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== "@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0": version "7.21.0" @@ -197,12 +201,12 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-member-expression-to-functions@^7.20.7", "@babel/helper-member-expression-to-functions@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz#319c6a940431a133897148515877d2f3269c3ba5" - integrity sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q== +"@babel/helper-member-expression-to-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz#3b1a009af932e586af77c1030fba9ee0bde396c0" + integrity sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.21.5" "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" @@ -211,19 +215,19 @@ dependencies: "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" + integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-simple-access" "^7.21.5" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -232,10 +236,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -247,24 +251,24 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" - integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz#a6ad005ba1c7d9bc2973dfde05a1bba7065dde3c" + integrity sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.20.7" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-member-expression-to-functions" "^7.21.5" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.20.7" - "@babel/types" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== +"@babel/helper-simple-access@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" + integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" @@ -280,10 +284,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" + integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" @@ -305,14 +309,14 @@ "@babel/traverse" "^7.20.5" "@babel/types" "^7.20.5" -"@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" + integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/highlight@^7.18.6": version "7.18.6" @@ -323,11 +327,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.2.3", "@babel/parser@^7.20.7", "@babel/parser@^7.21.4": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.2.3": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== +"@babel/parser@^7.20.7", "@babel/parser@^7.21.4", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -522,7 +531,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -606,12 +615,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-arrow-functions@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz#bea332b0e8b2dab3dafe55a163d8227531ab0551" - integrity sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ== +"@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-async-to-generator@^7.20.7": version "7.20.7" @@ -651,12 +660,12 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz#704cc2fd155d1c996551db8276d55b9d46e4d0aa" - integrity sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ== +"@babel/plugin-transform-computed-properties@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" + integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/template" "^7.20.7" "@babel/plugin-transform-destructuring@^7.21.3": @@ -689,12 +698,12 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz#964108c9988de1a60b4be2354a7d7e245f36e86e" - integrity sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ== +"@babel/plugin-transform-for-of@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" + integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -727,14 +736,14 @@ "@babel/helper-module-transforms" "^7.20.11" "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-commonjs@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz#6ff5070e71e3192ef2b7e39820a06fb78e3058e7" - integrity sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA== +"@babel/plugin-transform-modules-commonjs@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" + integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== dependencies: - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-simple-access" "^7.21.5" "@babel/plugin-transform-modules-systemjs@^7.20.11": version "7.20.11" @@ -791,12 +800,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.20.5": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" - integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ== +"@babel/plugin-transform-regenerator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" + integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": @@ -864,12 +873,12 @@ "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-unicode-escapes@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" + integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -880,13 +889,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/preset-env@^7.12.11": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.4.tgz#a952482e634a8dd8271a3fe5459a16eb10739c58" - integrity sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" + integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== dependencies: - "@babel/compat-data" "^7.21.4" - "@babel/helper-compilation-targets" "^7.21.4" - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/compat-data" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" @@ -911,6 +920,7 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -920,22 +930,22 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.20.7" + "@babel/plugin-transform-arrow-functions" "^7.21.5" "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" "@babel/plugin-transform-block-scoping" "^7.21.0" "@babel/plugin-transform-classes" "^7.21.0" - "@babel/plugin-transform-computed-properties" "^7.20.7" + "@babel/plugin-transform-computed-properties" "^7.21.5" "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.21.0" + "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" "@babel/plugin-transform-modules-amd" "^7.20.11" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-modules-systemjs" "^7.20.11" "@babel/plugin-transform-modules-umd" "^7.18.6" "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" @@ -943,17 +953,17 @@ "@babel/plugin-transform-object-super" "^7.18.6" "@babel/plugin-transform-parameters" "^7.21.3" "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.20.5" + "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" "@babel/plugin-transform-spread" "^7.20.7" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-escapes" "^7.21.5" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.21.4" + "@babel/types" "^7.21.5" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -972,14 +982,14 @@ esutils "^2.0.2" "@babel/preset-typescript@^7.12.7": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.4.tgz#b913ac8e6aa8932e47c21b01b4368d8aa239a529" - integrity sha512-sMLNWY37TCdRH/bJ6ZeeOH1nPuanED7Ai9Y/vH31IPqalioJ6ZNFUWONsakhv4r4n+I6gm5lmoE0olkgib/j/A== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" + integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-syntax-jsx" "^7.21.4" - "@babel/plugin-transform-modules-commonjs" "^7.21.2" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" "@babel/plugin-transform-typescript" "^7.21.3" "@babel/register@^7.12.10": @@ -999,9 +1009,9 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" - integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" + integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== dependencies: regenerator-runtime "^0.13.11" @@ -1014,7 +1024,7 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.1.6", "@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.1.6", "@babel/traverse@^7.7.2": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== @@ -1030,7 +1040,23 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" + integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== + dependencies: + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-function-name" "^7.21.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.21.5" + "@babel/types" "^7.21.5" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": version "7.21.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== @@ -1039,6 +1065,15 @@ "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" +"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.4.4": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== + dependencies: + "@babel/helper-string-parser" "^7.21.5" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2738,9 +2773,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001449: - version "1.0.30001477" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001477.tgz#a2ffb2276258233034bbb869d4558b02658a511e" - integrity sha512-lZim4iUHhGcy5p+Ri/G7m84hJwncj+Kz7S5aD4hoQfslKZJgt0tHc/hafVbqHC5bbhHb+mrW2JOUHkI5KH7toQ== + version "1.0.30001482" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz#8b3fad73dc35b2674a5c96df2d4f9f1c561435de" + integrity sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ== center-align@^0.1.1: version "0.1.3" @@ -3000,9 +3035,9 @@ convert-source-map@~1.1.0: integrity sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg== core-js-compat@^3.25.1: - version "3.30.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.0.tgz#99aa2789f6ed2debfa1df3232784126ee97f4d80" - integrity sha512-P5A2h/9mRYZFIAP+5Ab8ns6083IyVpSclU74UNvbGVQ8VM7n3n3/g2yF3AkKQ9NXz2O+ioxLbEWKnDtgsFamhg== + version "3.30.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe" + integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw== dependencies: browserslist "^4.21.5" @@ -3320,9 +3355,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.284: - version "1.4.356" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.356.tgz#b75a8a8c31d571f6024310cc980a08cd6c15a8c5" - integrity sha512-nEftV1dRX3omlxAj42FwqRZT0i4xd2dIg39sog/CnCJeCcL1TRd2Uh0i9Oebgv8Ou0vzTPw++xc+Z20jzS2B6A== + version "1.4.385" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.385.tgz#1afd8d6280d510145148777b899ff481c65531ff" + integrity sha512-L9zlje9bIw0h+CwPQumiuVlfMcV4boxRjFIWDcLfFqTZNbkwOExBzfmswytHawObQX4OUhtNv8gIiB21kOurIg== elliptic@^6.5.3: version "6.5.4" @@ -4497,13 +4532,20 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.1.0, is-core-module@^2.11.0: +is-core-module@^2.1.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" +is-core-module@^2.11.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== + dependencies: + has "^1.0.3" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -7503,9 +7545,9 @@ universalify@^2.0.0: integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" From ee2b0204aa4ee09c5d5cfba524f20f4a553d192a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 16:58:36 +1200 Subject: [PATCH 10/94] Update dependency @types/node to v18.16.4 (#3335) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 571d3837f4e..e179e9e1f01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1830,9 +1830,9 @@ integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== "@types/node@18": - version "18.15.11" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f" - integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q== + version "18.16.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.5.tgz#bf64e42719dc2e74da24709a2e1c0b50a966120a" + integrity sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA== "@types/normalize-package-data@^2.4.0": version "2.4.1" From e82bae2c4d55141cb7d2d1307e165021e8d990e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 12:52:45 +0200 Subject: [PATCH 11/94] Update dependency @types/node to v18.16.6 (#3347) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index e179e9e1f01..bc809206349 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1830,9 +1830,9 @@ integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== "@types/node@18": - version "18.16.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.5.tgz#bf64e42719dc2e74da24709a2e1c0b50a966120a" - integrity sha512-seOA34WMo9KB+UA78qaJoCO20RJzZGVXQ5Sh6FWu0g/hfT44nKXnej3/tCQl7FL97idFpBhisLYCTB50S0EirA== + version "18.16.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.6.tgz#d0ffffe201b253989b17ea157ddabec677a4f4fe" + integrity sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -1855,9 +1855,9 @@ integrity sha512-k3Nw6iaLoJbTjjY1tFT4L4IHNnLGJ52YacJNlNi6yqo76EYN1DiSPtuzzB7XnorZgrreUzCvzHDLklopmFdm7g== "@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/stack-utils@^2.0.0": version "2.0.1" From 83cb52c89c341c47f43fe255ec405194c0c80165 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 14:11:30 +0100 Subject: [PATCH 12/94] Lock file maintenance (#3312) * Lock file maintenance * Fix types * Iterate --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- spec/integ/sliding-sync.spec.ts | 2 +- yarn.lock | 586 ++++++++++++++++---------------- 2 files changed, 295 insertions(+), 293 deletions(-) diff --git a/spec/integ/sliding-sync.spec.ts b/spec/integ/sliding-sync.spec.ts index 38094be450a..c37a7e69172 100644 --- a/spec/integ/sliding-sync.spec.ts +++ b/spec/integ/sliding-sync.spec.ts @@ -1698,7 +1698,7 @@ describe("SlidingSync", () => { }); function timeout(delayMs: number, reason: string): { promise: Promise; cancel: () => void } { - let timeoutId: NodeJS.Timeout; + let timeoutId: ReturnType; return { promise: new Promise((resolve, reject) => { timeoutId = setTimeout(() => { diff --git a/yarn.lock b/yarn.lock index bc809206349..664e71663d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -100,17 +100,7 @@ dependencies: eslint-rule-composer "^0.3.0" -"@babel/generator@^7.12.11", "@babel/generator@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc" - integrity sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA== - dependencies: - "@babel/types" "^7.21.4" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/generator@^7.21.4", "@babel/generator@^7.21.5": +"@babel/generator@^7.12.11", "@babel/generator@^7.21.5", "@babel/generator@^7.7.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== @@ -284,7 +274,7 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.21.5": +"@babel/helper-string-parser@^7.21.5": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== @@ -327,12 +317,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.2.3": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17" - integrity sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw== - -"@babel/parser@^7.20.7", "@babel/parser@^7.21.4", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.2.3", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": version "7.21.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== @@ -1024,23 +1009,7 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.1.6", "@babel/traverse@^7.7.2": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36" - integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== - dependencies: - "@babel/code-frame" "^7.21.4" - "@babel/generator" "^7.21.4" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.4" - "@babel/types" "^7.21.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5": +"@babel/traverse@^7.1.6", "@babel/traverse@^7.20.5", "@babel/traverse@^7.21.5", "@babel/traverse@^7.7.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== @@ -1056,16 +1025,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.21.4" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.4.tgz#2d5d6bb7908699b3b416409ffd3b5daa25b030d4" - integrity sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA== - dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" - to-fast-properties "^2.0.0" - -"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.2.0", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== @@ -1095,10 +1055,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@es-joy/jsdoccomment@~0.37.1": - version "0.37.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.37.1.tgz#fa32a41ba12097452693343e09ad4d26d157aedd" - integrity sha512-5vxWJ1gEkEF0yRd0O+uK6dHJf7adrxwQSX8PuRiPfFSAbNLnY0ZJfXaZucoz14Jj2N11xn2DnlEPwWRpYpvRjg== +"@es-joy/jsdoccomment@~0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.38.0.tgz#2e74f8d824b4a4ec831eaabd4c3548fb11eae5cd" + integrity sha512-TFac4Bnv0ZYNkEeDnOWHQhaS1elWlvOCQxH06iHeu5iffs+hCaLVIZJwF+FqksQi68R4i66Pu+4DfFGvble+Uw== dependencies: comment-parser "1.3.1" esquery "^1.5.0" @@ -1112,18 +1072,18 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0": - version "4.5.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.0.tgz#f6f729b02feee2c749f57e334b7a1b5f40a81724" - integrity sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== "@eslint/eslintrc@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" - integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.1" + espree "^9.5.2" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1473,6 +1433,7 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" + uid acd96c00a881d0f462e1f97a56c73742c8dbc984 resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984" "@microsoft/tsdoc-config@0.16.2": @@ -1630,16 +1591,16 @@ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@pkgr/utils@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.3.1.tgz#0a9b06ffddee364d6642b3cd562ca76f55b34a03" - integrity sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.0.tgz#b6373d2504aedaf2fc7cdf2d13ab1f48fa5f12d5" + integrity sha512-2OCURAmRtdlL8iUDTypMrrxfwe8frXTeXaxGsVOaYtc/wrUyk8Z/0OBetM7cdlsy7ZFWlMX72VogKeh+A4Xcjw== dependencies: cross-spawn "^7.0.3" + fast-glob "^3.2.12" is-glob "^4.0.3" - open "^8.4.0" + open "^9.1.0" picocolors "^1.0.0" - tiny-glob "^0.2.9" - tslib "^2.4.0" + tslib "^2.5.0" "@sinclair/typebox@^0.24.1": version "0.24.51" @@ -1722,9 +1683,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.3" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" - integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== + version "7.18.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.5.tgz#c107216842905afafd3b6e774f6f935da6f5db80" + integrity sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q== dependencies: "@babel/types" "^7.3.0" @@ -1825,9 +1786,9 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "18.16.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.3.tgz#6bda7819aae6ea0b386ebc5b24bdf602f1b42b01" - integrity sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q== + version "20.1.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.1.tgz#afc492e8dbe7f672dd3a13674823522b467a45ad" + integrity sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A== "@types/node@18": version "18.16.6" @@ -1892,14 +1853,14 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.45.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.0.tgz#c0e10eeb936debe5d1c3433cf36206a95befefd0" - integrity sha512-p0QgrEyrxAWBecR56gyn3wkG15TJdI//eetInP3zYRewDh0XS+DhB3VUAd3QqvziFsfaQIoIuZMxZRB7vXYaYw== + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4" + integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.0" - "@typescript-eslint/type-utils" "5.59.0" - "@typescript-eslint/utils" "5.59.0" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/type-utils" "5.59.5" + "@typescript-eslint/utils" "5.59.5" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1908,119 +1869,71 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.45.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.0.tgz#0ad7cd019346cc5d150363f64869eca10ca9977c" - integrity sha512-qK9TZ70eJtjojSUMrrEwA9ZDQ4N0e/AuoOIgXuNBorXYcBDk397D2r5MIe1B3cok/oCtdNC5j+lUUpVB+Dpb+w== + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981" + integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw== dependencies: - "@typescript-eslint/scope-manager" "5.59.0" - "@typescript-eslint/types" "5.59.0" - "@typescript-eslint/typescript-estree" "5.59.0" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/typescript-estree" "5.59.5" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.57.1": - version "5.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.57.1.tgz#5d28799c0fc8b501a29ba1749d827800ef22d710" - integrity sha512-N/RrBwEUKMIYxSKl0oDK5sFVHd6VI7p9K5MyUlVYAY6dyNb/wHUqndkTd3XhpGlXgnQsBkRZuu4f9kAHghvgPw== +"@typescript-eslint/scope-manager@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d" + integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A== dependencies: - "@typescript-eslint/types" "5.57.1" - "@typescript-eslint/visitor-keys" "5.57.1" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/visitor-keys" "5.59.5" -"@typescript-eslint/scope-manager@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.0.tgz#86501d7a17885710b6716a23be2e93fc54a4fe8c" - integrity sha512-tsoldKaMh7izN6BvkK6zRMINj4Z2d6gGhO2UsI8zGZY3XhLq1DndP3Ycjhi1JwdwPRwtLMW4EFPgpuKhbCGOvQ== +"@typescript-eslint/type-utils@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" + integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg== dependencies: - "@typescript-eslint/types" "5.59.0" - "@typescript-eslint/visitor-keys" "5.59.0" - -"@typescript-eslint/type-utils@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.0.tgz#8e8d1420fc2265989fa3a0d897bde37f3851e8c9" - integrity sha512-d/B6VSWnZwu70kcKQSCqjcXpVH+7ABKH8P1KNn4K7j5PXXuycZTPXF44Nui0TEm6rbWGi8kc78xRgOC4n7xFgA== - dependencies: - "@typescript-eslint/typescript-estree" "5.59.0" - "@typescript-eslint/utils" "5.59.0" + "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/utils" "5.59.5" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.57.1": - version "5.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.57.1.tgz#d9989c7a9025897ea6f0550b7036027f69e8a603" - integrity sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA== - -"@typescript-eslint/types@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" - integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== - -"@typescript-eslint/typescript-estree@5.57.1": - version "5.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.57.1.tgz#10d9643e503afc1ca4f5553d9bbe672ea4050b71" - integrity sha512-A2MZqD8gNT0qHKbk2wRspg7cHbCDCk2tcqt6ScCFLr5Ru8cn+TCfM786DjPhqwseiS+PrYwcXht5ztpEQ6TFTw== - dependencies: - "@typescript-eslint/types" "5.57.1" - "@typescript-eslint/visitor-keys" "5.57.1" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" +"@typescript-eslint/types@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" + integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== -"@typescript-eslint/typescript-estree@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz#8869156ee1dcfc5a95be3ed0e2809969ea28e965" - integrity sha512-sUNnktjmI8DyGzPdZ8dRwW741zopGxltGs/SAPgGL/AAgDpiLsCFLcMNSpbfXfmnNeHmK9h3wGmCkGRGAoUZAg== +"@typescript-eslint/typescript-estree@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" + integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg== dependencies: - "@typescript-eslint/types" "5.59.0" - "@typescript-eslint/visitor-keys" "5.59.0" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/visitor-keys" "5.59.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.0.tgz#063d066b3bc4850c18872649ed0da9ee72d833d5" - integrity sha512-GGLFd+86drlHSvPgN/el6dRQNYYGOvRSDVydsUaQluwIW3HvbXuxyuD5JETvBt/9qGYe+lOrDk6gRrWOHb/FvA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.0" - "@typescript-eslint/types" "5.59.0" - "@typescript-eslint/typescript-estree" "5.59.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/utils@^5.10.0": - version "5.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.57.1.tgz#0f97b0bbd88c2d5e2036869f26466be5f4c69475" - integrity sha512-kN6vzzf9NkEtawECqze6v99LtmDiUJCVpvieTFA1uL7/jDghiJGubGZ5csicYHU1Xoqb3oH/R5cN5df6W41Nfg== +"@typescript-eslint/utils@5.59.5", "@typescript-eslint/utils@^5.10.0": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" + integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.57.1" - "@typescript-eslint/types" "5.57.1" - "@typescript-eslint/typescript-estree" "5.57.1" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/typescript-estree" "5.59.5" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.57.1": - version "5.57.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.57.1.tgz#585e5fa42a9bbcd9065f334fd7c8a4ddfa7d905e" - integrity sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA== - dependencies: - "@typescript-eslint/types" "5.57.1" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@5.59.0": - version "5.59.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz#a59913f2bf0baeb61b5cfcb6135d3926c3854365" - integrity sha512-qZ3iXxQhanchCeaExlKPV3gDQFxMUmU35xfd5eCXB6+kUw1TUAbIy2n7QIrwz9s98DQLzNWyHp61fY0da4ZcbA== +"@typescript-eslint/visitor-keys@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b" + integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA== dependencies: - "@typescript-eslint/types" "5.59.0" + "@typescript-eslint/types" "5.59.5" eslint-visitor-keys "^3.3.0" JSONStream@^1.0.3: @@ -2037,9 +1950,9 @@ abab@^2.0.6: integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== ace-builds@^1.4.13: - version "1.16.0" - resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.16.0.tgz#b4d38f9566157510fc22ee183d4f96b978f4fcc8" - integrity sha512-EriMhoxdfhh0zKm7icSt8EXekODAOVsYh9fpnlru9ALwf0Iw7J7bpuqLjhi3QRxvVKR7P0teQdJwTvjVMcYHuw== + version "1.19.0" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.19.0.tgz#1fcc462bbb82f65dfe8668d6d1f3752b1a8cf1d6" + integrity sha512-7iRX9MsxyhMUsqfWpWrJVf7dmv0nQcidOQOhzfYLQnNELdVpaqXVWcewfQqEHP+M0RR2TNie0gqoxPSstUc8Ww== acorn-globals@^3.0.0: version "3.1.0" @@ -2478,6 +2391,11 @@ better-docs@^2.4.0-beta.9: vue-docgen-api "^3.26.0" vue2-ace-editor "^0.0.15" +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -2493,6 +2411,13 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2721,6 +2646,13 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" + c8@^7.6.0: version "7.13.0" resolved "https://registry.yarnpkg.com/c8/-/c8-7.13.0.tgz#a2a70a851278709df5a9247d62d7f3d4bcb5f2e4" @@ -2773,9 +2705,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001449: - version "1.0.30001482" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz#8b3fad73dc35b2674a5c96df2d4f9f1c561435de" - integrity sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ== + version "1.0.30001486" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz#56a08885228edf62cbe1ac8980f2b5dae159997e" + integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg== center-align@^0.1.1: version "0.1.3" @@ -3035,9 +2967,9 @@ convert-source-map@~1.1.0: integrity sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg== core-js-compat@^3.25.1: - version "3.30.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.1.tgz#961541e22db9c27fc48bfc13a3cafa8734171dfe" - integrity sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw== + version "3.30.2" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b" + integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA== dependencies: browserslist "^4.21.5" @@ -3047,9 +2979,9 @@ core-js@^2.4.0: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.0.0: - version "3.30.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.0.tgz#64ac6f83bc7a49fd42807327051701d4b1478dea" - integrity sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg== + version "3.30.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc" + integrity sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg== core-util-is@~1.0.0: version "1.0.3" @@ -3201,12 +3133,30 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" -define-properties@^1.1.3, define-properties@^1.1.4: +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== @@ -3355,9 +3305,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.284: - version "1.4.385" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.385.tgz#1afd8d6280d510145148777b899ff481c65531ff" - integrity sha512-L9zlje9bIw0h+CwPQumiuVlfMcV4boxRjFIWDcLfFqTZNbkwOExBzfmswytHawObQX4OUhtNv8gIiB21kOurIg== + version "1.4.387" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.387.tgz#9a93ef1bd01b5898436026401ea3cabb1dd17d7a" + integrity sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw== elliptic@^6.5.3: version "6.5.4" @@ -3388,17 +3338,17 @@ emoji-regex@^9.2.2: integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== enhanced-resolve@^5.12.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== + version "5.13.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" + integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" entities@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" - integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== error-ex@^1.2.0, error-ex@^1.3.1: version "1.3.2" @@ -3574,9 +3524,9 @@ eslint-import-resolver-typescript@^3.5.1: synckit "^0.8.5" eslint-module-utils@^2.7.4: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== dependencies: debug "^3.2.7" @@ -3609,11 +3559,11 @@ eslint-plugin-jest@^27.1.6: "@typescript-eslint/utils" "^5.10.0" eslint-plugin-jsdoc@^43.0.6: - version "43.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.1.1.tgz#fc72ba21597cc99b1a0dc988aebb9bb57d0ec492" - integrity sha512-J2kjjsJ5vBXSyNzqJhceeSGTAgVgZHcPSJKo3vD4tNjUdfky98rR2VfZUDsS1GKL6isyVa8GWvr+Az7Vyg2HXA== + version "43.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.2.0.tgz#9d0df2329100a6956635f26211d0723c3ff91f15" + integrity sha512-Hst7XUfqh28UmPD52oTXmjaRN3d0KrmOZdgtp4h9/VHUJD3Evoo82ZGXi1TtRDWgWhvqDIRI63O49H0eH7NrZQ== dependencies: - "@es-joy/jsdoccomment" "~0.37.1" + "@es-joy/jsdoccomment" "~0.38.0" are-docs-informative "^0.0.2" comment-parser "1.3.1" debug "^4.3.4" @@ -3636,9 +3586,9 @@ eslint-plugin-tsdoc@^0.2.17: "@microsoft/tsdoc-config" "0.16.2" eslint-plugin-unicorn@^46.0.0: - version "46.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-46.0.0.tgz#b5cdcc9465fd6e46ab7968b87dd4a43adc8d6031" - integrity sha512-j07WkC+PFZwk8J33LYp6JMoHa1lXc1u6R45pbSAipjpfpb7KIGr17VE2D685zCxR5VL4cjrl65kTJflziQWMDA== + version "46.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-46.0.1.tgz#222ff65b30b2d9ed6f90de908ceb6a05dd0514d9" + integrity sha512-setGhMTiLAddg1asdwjZ3hekIN5zLznNa5zll7pBPwFOka6greCKDQydfqy4fqyUhndi74wpDzClSQMEcmOaew== dependencies: "@babel/helper-validator-identifier" "^7.19.1" "@eslint-community/eslint-utils" "^4.1.2" @@ -3683,10 +3633,10 @@ eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== eslint@8.39.0: version "8.39.0" @@ -3734,14 +3684,14 @@ eslint@8.39.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" - integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== +espree@^9.5.1, espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" @@ -3822,6 +3772,21 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" + integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.1" + human-signals "^4.3.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^3.0.7" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -3878,7 +3843,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.11, fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -4076,7 +4041,7 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" -functions-have-names@^1.2.2: +functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== @@ -4110,7 +4075,7 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^6.0.0: +get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== @@ -4197,11 +4162,6 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globalyzer@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" - integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== - globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -4215,9 +4175,9 @@ globby@^11.1.0: slash "^3.0.0" globby@^13.1.3: - version "13.1.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" - integrity sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw== + version "13.1.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.4.tgz#2f91c116066bcec152465ba36e5caa4a13c01317" + integrity sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g== dependencies: dir-glob "^3.0.1" fast-glob "^3.2.11" @@ -4225,11 +4185,6 @@ globby@^13.1.3: merge2 "^1.4.1" slash "^4.0.0" -globrex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" - integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -4378,6 +4333,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" + integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== + iconv-lite@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -4532,14 +4492,7 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.1.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-core-module@^2.11.0: +is-core-module@^2.1.0, is-core-module@^2.11.0: version "2.12.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== @@ -4553,11 +4506,16 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" -is-docker@^2.0.0, is-docker@^2.1.1: +is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-expression@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-3.0.0.tgz#39acaa6be7fd1f3471dc42c7416e61c24317ac9f" @@ -4595,6 +4553,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-negative-zero@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" @@ -4659,6 +4624,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -4771,9 +4741,9 @@ istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: istanbul-lib-report "^3.0.0" jackspeak@^2.0.3: - version "2.1.5" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.5.tgz#9a6741037b58257dc92eb28e9c8f54d33a1c09ba" - integrity sha512-NeK3mbF9vwNS3SjhzlEfO6WREJqoKtCwLoUPoUVtGJrpecxN3ZxlDuF22MzNSbOk/AA/VFWi+nFMV89xkXh2og== + version "2.2.0" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.2.0.tgz#497cbaedc902ec3f31d5d61be804d2364ff9ddad" + integrity sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: @@ -5510,9 +5480,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" lru-cache@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.0.tgz#daece36a9fc332e93f8e75f3fcfd17900253567c" - integrity sha512-9AEKXzvOZc4BMacFnYiTOlDH/197LNnQIK9wZ6iMB5NXPzuv4bWR/Msv7iUMplkiMQ1qQL+KSv/JF1mZAB5Lrg== + version "9.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1" + integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A== lru-queue@^0.1.0: version "0.1.0" @@ -5571,9 +5541,9 @@ matrix-mock-request@^2.5.0: expect "^28.1.0" matrix-widget-api@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.3.1.tgz#e38f404c76bb15c113909505c1c1a5b4d781c2f5" - integrity sha512-+rN6vGvnXm+fn0uq9r2KWSL/aPtehD6ObC50jYmUcEfgo8CUpf9eUurmjbRlwZkWq3XHXFuKQBUCI9UzqWg37Q== + version "1.4.0" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-1.4.0.tgz#e426ec16a013897f3a4a9c2bff423f54ab0ba745" + integrity sha512-dw0dRylGQzDUoiaY/g5xx1tBbS7aoov31PRtFMAvG58/4uerYllV9Gfou7w+I1aglwB6hihTREzKltVjARWV6A== dependencies: "@types/events" "^3.0.0" events "^3.2.0" @@ -5644,6 +5614,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -5773,9 +5748,9 @@ node-dir@^0.1.10: minimatch "^3.0.2" node-fetch@^2.6.7: - version "2.6.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + version "2.6.10" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.10.tgz#4a52b637a6802092aa11a3bf73d19aac789fdce1" + integrity sha512-5YytjUVbwzjE/BX4N62vnPPkGNxlJPwdA9/ArUc4pcM6cYS4Hinuv4VazzwjMGgnWuiQqcemOanib/5PpcsGug== dependencies: whatwg-url "^5.0.0" @@ -5811,10 +5786,17 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" + integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== + dependencies: + path-key "^4.0.0" + nwsapi@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.3.tgz#00e04dfd5a4a751e5ec2fecdc75dfd2f0db820fa" - integrity sha512-jscxIO4/VKScHlbmFBdV1Z6LXnLO+ZR4VMtypudUdfwtKxUN3TQcNFIHLwKtrUbDyHN4/GycY9+oRGZ2XMXYPw== + version "2.2.4" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.4.tgz#fd59d5e904e8e1f03c25a7d5a15cfa16c714a1e5" + integrity sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g== object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" @@ -5869,13 +5851,21 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^8.4.0: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" is-wsl "^2.2.0" optionator@^0.8.1: @@ -6034,6 +6024,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.6, path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -6328,9 +6323,9 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.1.tgz#31207dddd15d43f299fdcdb2f572df65030c19af" - integrity sha512-t+x1zEHDjBwkDGY5v5ApnZ/utcd4XYDiJsaQQoptTXgUXX95sDg1elCdJghzicm7n2mbCBJ3uYWr6M22SO19rg== + version "6.0.2" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" + integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== querystring-es3@~0.2.0: version "0.2.1" @@ -6528,18 +6523,18 @@ regenerator-transform@^0.15.1: "@babel/runtime" "^7.8.4" regexp-tree@^0.1.24, regexp-tree@~0.1.1: - version "0.1.24" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.24.tgz#3d6fa238450a4d66e5bc9c4c14bb720e2196829d" - integrity sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw== + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + functions-have-names "^1.2.3" regexpu-core@^5.3.1: version "5.3.2" @@ -6658,6 +6653,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -6723,14 +6725,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.7, semver@^7.5.0: +semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.0: version "7.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== @@ -6777,9 +6772,9 @@ shell-quote@^1.6.1: integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== shiki@^0.14.1: - version "0.14.1" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.1.tgz#9fbe082d0a8aa2ad63df4fbf2ee11ec924aa7ee1" - integrity sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw== + version "0.14.2" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.14.2.tgz#d51440800b701392b31ce2336036058e338247a1" + integrity sha512-ltSZlSLOuSY0M0Y75KA+ieRaZ0Trf5Wl3gutE7jzLuIcWxLp5i/uEnLoQWNvgKXQ5OMpGkJnVMRLAuzjc0LJ2A== dependencies: ansi-sequence-parser "^1.1.0" jsonc-parser "^3.2.0" @@ -7041,6 +7036,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" @@ -7117,9 +7117,9 @@ tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== terser@^5.5.1: - version "5.17.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.1.tgz#948f10830454761e2eeedc6debe45c532c83fd69" - integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw== + version "5.17.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.2.tgz#06c9818ae998066234b985abeb57bb7bff29d449" + integrity sha512-1D1aGbOF1Mnayq5PvfMc0amAR1y5Z1nrZaGCvI5xsdEfZEVte8okonk02OiaK5fw5hG1GWuuVsakOnpZW8y25A== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -7181,13 +7181,10 @@ timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" -tiny-glob@^0.2.9: - version "0.2.9" - resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" - integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== - dependencies: - globalyzer "0.1.0" - globrex "^0.1.2" +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== tmpl@1.0.5: version "1.0.5" @@ -7313,7 +7310,7 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.4.0, tslib@^2.5.0: +tslib@^2.0.1, tslib@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== @@ -7417,9 +7414,9 @@ typedoc-plugin-versions@^0.2.3: semver "^7.3.7" typedoc@^0.24.0: - version "0.24.6" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.24.6.tgz#1f76fac27db9c6626b5b5c79f78f3c813b285827" - integrity sha512-c3y3h45xJv3qYwKDAwU6Cl+26CjT0ZvblHzfHJ+SjQDM4p1mZxtgHky4lhmG0+nNarRht8kADfZlbspJWdZarQ== + version "0.24.7" + resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.24.7.tgz#7eeb272a1894b3789acc1a94b3f2ae8e7330ee39" + integrity sha512-zzfKDFIZADA+XRIp2rMzLe9xZ6pt12yQOhCr7cD7/PBTjhPmMyMvGrkZ2lPNJitg3Hj1SeiYFNzCsSDrlpxpKw== dependencies: lunr "^2.3.9" marked "^4.3.0" @@ -7544,6 +7541,11 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + update-browserslist-db@^1.0.10: version "1.0.11" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" @@ -7931,9 +7933,9 @@ yargs@^16.2.0: yargs-parser "^20.2.2" yargs@^17.0.1, yargs@^17.3.1: - version "17.7.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967" - integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw== + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" From 49696cecbdf10319dd473d1abc757427fb0a66e9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 May 2023 14:24:34 +0100 Subject: [PATCH 13/94] Use WeakMap in ReEmitter to help enable garbage collection (#3344) --- src/ReEmitter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ReEmitter.ts b/src/ReEmitter.ts index 565e8ea702c..5d41ac0e455 100644 --- a/src/ReEmitter.ts +++ b/src/ReEmitter.ts @@ -25,7 +25,7 @@ export class ReEmitter { public constructor(private readonly target: EventEmitter) {} // Map from emitter to event name to re-emitter - private reEmitters = new Map void>>(); + private reEmitters = new WeakMap void>>(); public reEmit(source: EventEmitter, eventNames: string[]): void { let reEmittersByEvent = this.reEmitters.get(source); @@ -35,6 +35,8 @@ export class ReEmitter { } for (const eventName of eventNames) { + if (reEmittersByEvent.has(eventName)) continue; + // We include the source as the last argument for event handlers which may need it, // such as read receipt listeners on the client class which won't have the context // of the room. From cc065c2772aebf5ff3a76f3a05f69efba395491f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 9 May 2023 15:14:29 +0100 Subject: [PATCH 14/94] Resetting package fields for development --- package.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index d17bdd73f99..d2a7b3eaed9 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "keywords": [ "matrix-org" ], - "main": "./lib/index.js", - "browser": "./lib/browser-index.js", + "main": "./src/index.ts", + "browser": "./src/browser-index.ts", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.ts", "matrix_lib_main": "./lib/index.js", @@ -154,6 +154,5 @@ "no-rust-crypto": { "src/rust-crypto/index.ts$": "./src/rust-crypto/browserify-index.ts" } - }, - "typings": "./lib/index.d.ts" + } } From 73ca9c9ed28847454e13da358c581769e695ff42 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 10 May 2023 11:32:22 +0100 Subject: [PATCH 15/94] Revert "Update JS-DevTools/npm-publish action to v2 (#3336)" (#3350) Attempting to fix failure to publish to NPM due to an "invalid token" error https://github.com/matrix-org/matrix-js-sdk/actions/runs/4926808655/jobs/8819822367 . This reverts commit ca9263fa647e4cfc581a137c389ea646bcf71ce6. --- .github/workflows/release-npm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 714105481db..daa9cb589bc 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -24,7 +24,7 @@ jobs: - name: 🚀 Publish to npm id: npm-publish - uses: JS-DevTools/npm-publish@0be441d808570461daedc3fb178405dbcac54de0 # v2 + uses: JS-DevTools/npm-publish@0f451a94170d1699fd50710966d48fb26194d939 # v1 with: token: ${{ secrets.NPM_TOKEN }} access: public From 90e8336797480ff0e512d7c76ba6f420c2c3a2d5 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Thu, 11 May 2023 16:00:26 +0200 Subject: [PATCH 16/94] Do an ICE Restart if WebRTC become disconnected (#3341) * Do an ice restart if ICE disconnected - Waite two seconds after disconnected - Remove check for finish ICE gathering and try to add each local candidate. Avoid race in multible ICE gathering * Add tests for failed iceConnectionState * suppress type check in unit test * fix pr issues --- spec/test-utils/webrtc.ts | 2 ++ spec/unit/webrtc/call.spec.ts | 22 +++++++++++++++- src/webrtc/call.ts | 47 +++++++++++++++++++++++++++++------ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/spec/test-utils/webrtc.ts b/spec/test-utils/webrtc.ts index e50a8df45d7..2037767cc94 100644 --- a/spec/test-utils/webrtc.ts +++ b/spec/test-utils/webrtc.ts @@ -239,6 +239,8 @@ export class MockRTCPeerConnection { public triggerIncomingDataChannel(): void { this.onDataChannelListener?.({ channel: {} } as RTCDataChannelEvent); } + + public restartIce(): void {} } export class MockRTCRtpSender { diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index db84ee54004..8fb2f3be435 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -1652,12 +1652,18 @@ describe("Call", function () { beforeEach(async () => { jest.useFakeTimers(); jest.spyOn(call, "hangup"); - await fakeIncomingCall(client, call, "1"); mockPeerConn = call.peerConn as unknown as MockRTCPeerConnection; + mockPeerConn.iceConnectionState = "disconnected"; mockPeerConn.iceConnectionStateChangeListener!(); + jest.spyOn(mockPeerConn, "restartIce"); + }); + + it("should restart ICE gathering after being disconnected for 2 seconds", () => { + jest.advanceTimersByTime(3 * 1000); + expect(mockPeerConn.restartIce).toHaveBeenCalled(); }); it("should hang up after being disconnected for 30 seconds", () => { @@ -1665,6 +1671,20 @@ describe("Call", function () { expect(call.hangup).toHaveBeenCalledWith(CallErrorCode.IceFailed, false); }); + it("should restart ICE gathering once again after ICE being failed", () => { + mockPeerConn.iceConnectionState = "failed"; + mockPeerConn.iceConnectionStateChangeListener!(); + expect(mockPeerConn.restartIce).toHaveBeenCalled(); + }); + + it("should call hangup after ICE being failed and if there not exists a restartIce method", () => { + // @ts-ignore + mockPeerConn.restartIce = null; + mockPeerConn.iceConnectionState = "failed"; + mockPeerConn.iceConnectionStateChangeListener!(); + expect(call.hangup).toHaveBeenCalledWith(CallErrorCode.IceFailed, false); + }); + it("should not hangup if we've managed to re-connect", () => { mockPeerConn.iceConnectionState = "connected"; mockPeerConn.iceConnectionStateChangeListener!(); diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 641d4cb982e..df2a2258764 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -263,7 +263,8 @@ const CALL_TIMEOUT_MS = 60 * 1000; // ms const CALL_LENGTH_INTERVAL = 1000; // ms /** The time after which we end the call, if ICE got disconnected */ const ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms - +/** The time after which we try a ICE restart, if ICE got disconnected */ +const ICE_RECONNECTING_TIMEOUT = 2 * 1000; // ms export class CallError extends Error { public readonly code: string; @@ -382,6 +383,7 @@ export class MatrixCall extends TypedEventEmitter; + private iceReconnectionTimeOut?: ReturnType | undefined; private inviteTimeout?: ReturnType; private readonly removeTrackListeners = new Map void>(); @@ -965,6 +967,7 @@ export class MatrixCall extends TypedEventEmitter { if (event.candidate) { if (this.candidatesEnded) { - logger.warn( - `Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended - ignoring!`, - ); - return; + logger.warn(`Call ${this.callId} gotLocalIceCandidate() got candidate after candidates have ended!`); } logger.debug(`Call ${this.callId} got local ICE ${event.candidate.sdpMid} ${event.candidate.candidate}`); @@ -1817,7 +1817,10 @@ export class MatrixCall extends TypedEventEmitter void) | null) { this.candidatesEnded = false; + logger.debug( + `Call ${this.callId} onIceConnectionStateChanged() ice restart (state=${this.peerConn?.iceConnectionState})`, + ); this.peerConn!.restartIce(); } else { logger.info( @@ -2257,7 +2271,19 @@ export class MatrixCall extends TypedEventEmitter { + this.candidatesEnded = false; + this.iceReconnectionTimeOut = setTimeout((): void => { + logger.info( + `Call ${this.callId} onIceConnectionStateChanged() ICE restarting because of ICE disconnected, (state=${this.peerConn?.iceConnectionState}, conn=${this.peerConn?.connectionState})`, + ); + if (this.peerConn?.restartIce as (() => void) | null) { + this.candidatesEnded = false; + this.peerConn!.restartIce(); + } + this.iceReconnectionTimeOut = undefined; + }, ICE_RECONNECTING_TIMEOUT); + + this.iceDisconnectedTimeout = setTimeout((): void => { logger.info( `Call ${this.callId} onIceConnectionStateChanged() hanging up call (ICE disconnected for too long)`, ); @@ -2887,6 +2913,11 @@ export class MatrixCall extends TypedEventEmitter Date: Thu, 11 May 2023 19:41:58 +0100 Subject: [PATCH 17/94] Rename and move `crypto.IBootstrapCrossSigningOpts` (#3352) * Define `UIAuthCallback` type and use in `IBootstrapCrossSigningOpts` * Move `IBootstrapCrossSigningOpts` to `crypto-api` and rename * Replace uses of `IBootstrapCrossSigningOpts` ... with `BootstrapCrossSigningOpts` * Update src/crypto-api.ts --- spec/unit/crypto/cross-signing.spec.ts | 5 +++-- src/client.ts | 5 ++--- src/crypto-api.ts | 15 +++++++++++++++ src/crypto/EncryptionSetup.ts | 5 +++-- src/crypto/index.ts | 17 +++++------------ src/interactive-auth.ts | 11 +++++++++++ 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/spec/unit/crypto/cross-signing.spec.ts b/spec/unit/crypto/cross-signing.spec.ts index eeee27566d1..276c2feaf41 100644 --- a/spec/unit/crypto/cross-signing.spec.ts +++ b/spec/unit/crypto/cross-signing.spec.ts @@ -24,10 +24,11 @@ import * as olmlib from "../../../src/crypto/olmlib"; import { MatrixError } from "../../../src/http-api"; import { logger } from "../../../src/logger"; import { ICrossSigningKey, ICreateClientOpts, ISignedKey, MatrixClient } from "../../../src/client"; -import { CryptoEvent, IBootstrapCrossSigningOpts } from "../../../src/crypto"; +import { CryptoEvent } from "../../../src/crypto"; import { IDevice } from "../../../src/crypto/deviceinfo"; import { TestClient } from "../../TestClient"; import { resetCrossSigningKeys } from "./crypto-utils"; +import { BootstrapCrossSigningOpts } from "../../../src/crypto-api"; const PUSH_RULES_RESPONSE: Response = { method: "GET", @@ -146,7 +147,7 @@ describe("Cross Signing", function () { alice.uploadKeySignatures = async () => ({ failures: {} }); alice.setAccountData = async () => ({}); alice.getAccountDataFromServer = async (): Promise => ({} as T); - const authUploadDeviceSigningKeys: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"] = async (func) => { + const authUploadDeviceSigningKeys: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"] = async (func) => { await func({}); }; diff --git a/src/client.ts b/src/client.ts index fe6e9d8c53b..f1b90b850cb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -74,7 +74,6 @@ import { CryptoEventHandlerMap, fixBackupKey, ICryptoCallbacks, - IBootstrapCrossSigningOpts, ICheckOwnCrossSigningTrustOpts, isCryptoAvailable, VerificationMethod, @@ -205,7 +204,7 @@ import { LocalNotificationSettings } from "./@types/local_notifications"; import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature"; import { CryptoBackend } from "./common-crypto/CryptoBackend"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants"; -import { CryptoApi } from "./crypto-api"; +import { BootstrapCrossSigningOpts, CryptoApi } from "./crypto-api"; import { DeviceInfoMap } from "./crypto/DeviceList"; import { AddSecretStorageKeyOpts, @@ -2751,7 +2750,7 @@ export class MatrixClient extends TypedEventEmitter { + public bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise { if (!this.crypto) { throw new Error("End-to-end encryption disabled"); } diff --git a/src/crypto-api.ts b/src/crypto-api.ts index bf3213f22f8..22d91cc12f2 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -17,6 +17,7 @@ limitations under the License. import type { IMegolmSessionData } from "./@types/crypto"; import { Room } from "./models/room"; import { DeviceMap } from "./models/device"; +import { UIAuthCallback } from "./interactive-auth"; /** * Public interface to the cryptography parts of the js-sdk @@ -123,6 +124,20 @@ export interface CryptoApi { getDeviceVerificationStatus(userId: string, deviceId: string): Promise; } +/** + * Options object for `CryptoApi.bootstrapCrossSigning`. + */ +export interface BootstrapCrossSigningOpts { + /** Optional. Reset the cross-signing keys even if keys already exist. */ + setupNewCrossSigning?: boolean; + + /** + * An application callback to collect the authentication data for uploading the keys. If not given, the keys + * will not be uploaded to the server (which seems like a bad thing?). + */ + authUploadDeviceSigningKeys?: UIAuthCallback; +} + export class DeviceVerificationStatus { /** * True if this device has been signed by its owner (and that signature verified). diff --git a/src/crypto/EncryptionSetup.ts b/src/crypto/EncryptionSetup.ts index b09df261930..b8f52fcdcea 100644 --- a/src/crypto/EncryptionSetup.ts +++ b/src/crypto/EncryptionSetup.ts @@ -19,7 +19,7 @@ import { MatrixEvent } from "../models/event"; import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store"; import { Method, ClientPrefix } from "../http-api"; -import { Crypto, ICryptoCallbacks, IBootstrapCrossSigningOpts } from "./index"; +import { Crypto, ICryptoCallbacks } from "./index"; import { ClientEvent, ClientEventHandlerMap, @@ -31,9 +31,10 @@ import { import { IKeyBackupInfo } from "./keybackup"; import { TypedEventEmitter } from "../models/typed-event-emitter"; import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage"; +import { BootstrapCrossSigningOpts } from "../crypto-api"; interface ICrossSigningKeys { - authUpload: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"]; + authUpload: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"]; keys: Record<"master" | "self_signing" | "user_signing", ICrossSigningKey>; } diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 9554e617359..234bb2737ab 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -88,10 +88,13 @@ import { ServerSideSecretStorageImpl, } from "../secret-storage"; import { ISecretRequest } from "./SecretSharing"; -import { DeviceVerificationStatus } from "../crypto-api"; +import { BootstrapCrossSigningOpts, DeviceVerificationStatus } from "../crypto-api"; import { Device, DeviceMap } from "../models/device"; import { deviceInfoToDevice } from "./device-converter"; +/* re-exports for backwards compatibility */ +export type { BootstrapCrossSigningOpts as IBootstrapCrossSigningOpts } from "../crypto-api"; + const DeviceVerification = DeviceInfo.DeviceVerification; const defaultVerificationMethods = { @@ -127,16 +130,6 @@ interface IInitOpts { pickleKey?: string; } -export interface IBootstrapCrossSigningOpts { - /** Optional. Reset even if keys already exist. */ - setupNewCrossSigning?: boolean; - /** - * A function that makes the request requiring auth. Receives the auth data as an object. - * Can be called multiple times, first with an empty authDict, to obtain the flows. - */ - authUploadDeviceSigningKeys?(makeRequest: (authData: any) => Promise<{}>): Promise; -} - export interface ICryptoCallbacks extends SecretStorageCallbacks { getCrossSigningKey?: (keyType: string, pubKey: string) => Promise; saveCrossSigningKeys?: (keys: Record) => void; @@ -769,7 +762,7 @@ export class Crypto extends TypedEventEmitter { + }: BootstrapCrossSigningOpts = {}): Promise { logger.log("Bootstrapping cross-signing"); const delegateCryptoCallbacks = this.baseApis.cryptoCallbacks; diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index 7c78bd0f434..aac5ef53e8f 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -20,6 +20,7 @@ import { logger } from "./logger"; import { MatrixClient } from "./client"; import { defer, IDeferred } from "./utils"; import { MatrixError } from "./http-api"; +import { UIAResponse } from "./@types/uia"; const EMAIL_STAGE_TYPE = "m.login.email.identity"; const MSISDN_STAGE_TYPE = "m.login.msisdn"; @@ -118,6 +119,16 @@ export class NoAuthFlowFoundError extends Error { } } +/** + * The type of an application callback to perform the user-interactive bit of UIA. + * + * It is called with a single parameter, `makeRequest`, which is a function which takes the UIA parameters and + * makes the HTTP request. + * + * The generic parameter `T` is the type of the response of the endpoint, once it is eventually successful. + */ +export type UIAuthCallback = (makeRequest: (authData: IAuthDict) => Promise>) => Promise; + interface IOpts { /** * A matrix client to use for the auth process From 40f257915853f889809d3a1d4655acb8810af5c7 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 12 May 2023 10:38:33 +0100 Subject: [PATCH 18/94] Pass SecretStorage into RustCrypto (#3353) * Pass SecretStorage into RustCrypto * Update src/rust-crypto/rust-crypto.ts --- spec/unit/rust-crypto/rust-crypto.spec.ts | 45 ++++++++++++++++++++--- src/client.ts | 2 +- src/rust-crypto/index.ts | 15 +++++++- src/rust-crypto/rust-crypto.ts | 15 ++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index c31aa15f2fc..f6bc05adebb 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -27,6 +27,7 @@ import { mkEvent } from "../../test-utils/test-utils"; import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend"; import { IEventDecryptionResult } from "../../../src/@types/crypto"; import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; +import { ServerSideSecretStorage } from "../../../src/secret-storage"; afterEach(() => { // reset fake-indexeddb after each test, to make sure we don't leak connections @@ -44,7 +45,12 @@ describe("RustCrypto", () => { beforeEach(async () => { const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto(mockHttpApi, TEST_USER, TEST_DEVICE_ID)) as RustCrypto; + rustCrypto = (await initRustCrypto( + mockHttpApi, + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + )) as RustCrypto; }); it("should return a list", async () => { @@ -58,7 +64,12 @@ describe("RustCrypto", () => { beforeEach(async () => { const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto(mockHttpApi, TEST_USER, TEST_DEVICE_ID)) as RustCrypto; + rustCrypto = (await initRustCrypto( + mockHttpApi, + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + )) as RustCrypto; }); it("should pass through unencrypted to-device messages", async () => { @@ -141,7 +152,13 @@ describe("RustCrypto", () => { makeOutgoingRequest: jest.fn(), } as unknown as Mocked; - rustCrypto = new RustCrypto(olmMachine, {} as MatrixHttpApi, TEST_USER, TEST_DEVICE_ID); + rustCrypto = new RustCrypto( + olmMachine, + {} as MatrixHttpApi, + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + ); rustCrypto["outgoingRequestProcessor"] = outgoingRequestProcessor; }); @@ -207,7 +224,12 @@ describe("RustCrypto", () => { beforeEach(async () => { const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto(mockHttpApi, TEST_USER, TEST_DEVICE_ID)) as RustCrypto; + rustCrypto = (await initRustCrypto( + mockHttpApi, + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + )) as RustCrypto; }); it("should handle unencrypted events", () => { @@ -235,7 +257,12 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - rustCrypto = await initRustCrypto({} as MatrixClient["http"], TEST_USER, TEST_DEVICE_ID); + rustCrypto = await initRustCrypto( + {} as MatrixClient["http"], + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + ); }); it("should be true by default", () => { @@ -258,7 +285,13 @@ describe("RustCrypto", () => { olmMachine = { getDevice: jest.fn(), } as unknown as Mocked; - rustCrypto = new RustCrypto(olmMachine, {} as MatrixClient["http"], TEST_USER, TEST_DEVICE_ID); + rustCrypto = new RustCrypto( + olmMachine, + {} as MatrixClient["http"], + TEST_USER, + TEST_DEVICE_ID, + {} as ServerSideSecretStorage, + ); }); it("should call getDevice", async () => { diff --git a/src/client.ts b/src/client.ts index f1b90b850cb..016f7ffc77f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2227,7 +2227,7 @@ export class MatrixClient extends TypedEventEmitter, userId: string, deviceId: string, + secretStorage: ServerSideSecretStorage, ): Promise { // initialise the rust matrix-sdk-crypto-js, if it hasn't already been done await RustSdkCryptoJs.initAsync(); @@ -38,7 +49,7 @@ export async function initRustCrypto( // TODO: use the pickle key for the passphrase const olmMachine = await RustSdkCryptoJs.OlmMachine.initialize(u, d, RUST_SDK_STORE_PREFIX, "test pass"); - const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId); + const rustCrypto = new RustCrypto(olmMachine, http, userId, deviceId, secretStorage); await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) => rustCrypto.onRoomKeysUpdated(sessions), ); diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 0d5b4a4f30e..9f7ef7468f8 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -34,6 +34,7 @@ import { DeviceVerificationStatus } from "../crypto-api"; import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client"; import { Device, DeviceMap } from "../models/device"; +import { ServerSideSecretStorage } from "../secret-storage"; /** * An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto. @@ -56,10 +57,24 @@ export class RustCrypto implements CryptoBackend { private outgoingRequestProcessor: OutgoingRequestProcessor; public constructor( + /** The `OlmMachine` from the underlying rust crypto sdk. */ private readonly olmMachine: RustSdkCryptoJs.OlmMachine, + + /** + * Low-level HTTP interface: used to make outgoing requests required by the rust SDK. + * + * We expect it to set the access token, etc. + */ private readonly http: MatrixHttpApi, + + /** The local user's User ID. */ _userId: string, + + /** The local user's Device ID. */ _deviceId: string, + + /** Interface to server-side secret storage */ + _secretStorage: ServerSideSecretStorage, ) { this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http); this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor); From 63abd00ca7104cf9b37f673e42712711a4db26e2 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 12 May 2023 13:21:52 +0100 Subject: [PATCH 19/94] Element-R: Stub out `isCrossSigningReady` and `isSecretStorageReady` (#3354) * Stub implementation of `isCrossSigningReady` * Stub implementation of `isSecretStorageReady` * add tests to meet quality gate * factor out common * Remove accidentally-added file --- spec/unit/matrix-client.spec.ts | 42 +++++++++++++++ spec/unit/rust-crypto/rust-crypto.spec.ts | 62 +++++++++++------------ src/client.ts | 12 ++--- src/crypto-api.ts | 29 +++++++++++ src/rust-crypto/rust-crypto.ts | 14 +++++ 5 files changed, 122 insertions(+), 37 deletions(-) diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 635cd2ee5cd..3b5231d55d9 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -70,6 +70,7 @@ import { SyncState } from "../../src/sync"; import * as featureUtils from "../../src/feature"; import { StubStore } from "../../src/store/stub"; import { SecretStorageKeyDescriptionAesV1, ServerSideSecretStorageImpl } from "../../src/secret-storage"; +import { CryptoBackend } from "../../src/common-crypto/CryptoBackend"; jest.useFakeTimers(); @@ -2750,6 +2751,47 @@ describe("MatrixClient", function () { }); }); + // these wrappers are deprecated, but we need coverage of them to pass the quality gate + describe("Crypto wrappers", () => { + describe("exception if no crypto", () => { + it("isCrossSigningReady", () => { + expect(() => client.isCrossSigningReady()).toThrow("End-to-end encryption disabled"); + }); + + it("isSecretStorageReady", () => { + expect(() => client.isSecretStorageReady()).toThrow("End-to-end encryption disabled"); + }); + }); + + describe("defer to crypto backend", () => { + let mockCryptoBackend: Mocked; + + beforeEach(() => { + mockCryptoBackend = { + isCrossSigningReady: jest.fn(), + isSecretStorageReady: jest.fn(), + stop: jest.fn().mockResolvedValue(undefined), + } as unknown as Mocked; + client["cryptoBackend"] = mockCryptoBackend; + }); + + it("isCrossSigningReady", async () => { + const testResult = "test"; + mockCryptoBackend.isCrossSigningReady.mockResolvedValue(testResult as unknown as boolean); + expect(await client.isCrossSigningReady()).toBe(testResult); + expect(mockCryptoBackend.isCrossSigningReady).toHaveBeenCalledTimes(1); + }); + + it("isSecretStorageReady", async () => { + client["cryptoBackend"] = mockCryptoBackend; + const testResult = "test"; + mockCryptoBackend.isSecretStorageReady.mockResolvedValue(testResult as unknown as boolean); + expect(await client.isSecretStorageReady()).toBe(testResult); + expect(mockCryptoBackend.isSecretStorageReady).toHaveBeenCalledTimes(1); + }); + }); + }); + describe("paginateEventTimeline()", () => { describe("notifications timeline", () => { const unsafeNotification = { diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index f6bc05adebb..397b4df0322 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -22,7 +22,7 @@ import { Mocked } from "jest-mock"; import { RustCrypto } from "../../../src/rust-crypto/rust-crypto"; import { initRustCrypto } from "../../../src/rust-crypto"; -import { IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src"; +import { IHttpOpts, IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src"; import { mkEvent } from "../../test-utils/test-utils"; import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend"; import { IEventDecryptionResult } from "../../../src/@types/crypto"; @@ -36,21 +36,15 @@ afterEach(() => { indexedDB = new IDBFactory(); }); -describe("RustCrypto", () => { - const TEST_USER = "@alice:example.com"; - const TEST_DEVICE_ID = "TEST_DEVICE"; +const TEST_USER = "@alice:example.com"; +const TEST_DEVICE_ID = "TEST_DEVICE"; +describe("RustCrypto", () => { describe(".exportRoomKeys", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should return a list", async () => { @@ -63,13 +57,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should pass through unencrypted to-device messages", async () => { @@ -105,6 +93,16 @@ describe("RustCrypto", () => { }); }); + it("isCrossSigningReady", async () => { + const rustCrypto = await makeTestRustCrypto(); + await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false); + }); + + it("isSecretStorageReady", async () => { + const rustCrypto = await makeTestRustCrypto(); + await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false); + }); + describe("outgoing requests", () => { /** the RustCrypto implementation under test */ let rustCrypto: RustCrypto; @@ -223,13 +221,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - const mockHttpApi = {} as MatrixClient["http"]; - rustCrypto = (await initRustCrypto( - mockHttpApi, - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - )) as RustCrypto; + rustCrypto = await makeTestRustCrypto(); }); it("should handle unencrypted events", () => { @@ -257,12 +249,7 @@ describe("RustCrypto", () => { let rustCrypto: RustCrypto; beforeEach(async () => { - rustCrypto = await initRustCrypto( - {} as MatrixClient["http"], - TEST_USER, - TEST_DEVICE_ID, - {} as ServerSideSecretStorage, - ); + rustCrypto = await makeTestRustCrypto(); }); it("should be true by default", () => { @@ -315,3 +302,16 @@ describe("RustCrypto", () => { }); }); }); + +/** build a basic RustCrypto instance for testing + * + * just provides default arguments for initRustCrypto() + */ +async function makeTestRustCrypto( + http: MatrixHttpApi = {} as MatrixClient["http"], + userId: string = TEST_USER, + deviceId: string = TEST_DEVICE_ID, + secretStorage: ServerSideSecretStorage = {} as ServerSideSecretStorage, +): Promise { + return await initRustCrypto(http, userId, deviceId, secretStorage); +} diff --git a/src/client.ts b/src/client.ts index 016f7ffc77f..216f34d6b7b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2731,12 +2731,13 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.isCrossSigningReady(); + return this.cryptoBackend.isCrossSigningReady(); } /** @@ -2843,15 +2844,14 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.isSecretStorageReady(); + return this.cryptoBackend.isSecretStorageReady(); } /** diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 22d91cc12f2..e303d05837f 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -122,6 +122,35 @@ export interface CryptoApi { * @returns Verification status of the device, or `null` if the device is not known */ getDeviceVerificationStatus(userId: string, deviceId: string): Promise; + + /** + * Checks whether cross signing: + * - is enabled on this account and trusted by this device + * - has private keys either cached locally or stored in secret storage + * + * If this function returns false, bootstrapCrossSigning() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapCrossSigning() completes successfully, this function should + * return true. + * + * @returns True if cross-signing is ready to be used on this device + */ + isCrossSigningReady(): Promise; + + /** + * Checks whether secret storage: + * - is enabled on this account + * - is storing cross-signing private keys + * - is storing session backup key (if enabled) + * + * If this function returns false, bootstrapSecretStorage() can be used + * to fix things such that it returns true. That is to say, after + * bootstrapSecretStorage() completes successfully, this function should + * return true. + * + * @returns True if secret storage is ready to be used on this device + */ + isSecretStorageReady(): Promise; } /** diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 9f7ef7468f8..3f7b6f52e83 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -317,6 +317,20 @@ export class RustCrypto implements CryptoBackend { }); } + /** + * Implementation of {@link CryptoApi#isCrossSigningReady} + */ + public async isCrossSigningReady(): Promise { + return false; + } + + /** + * Implementation of {@link CryptoApi#isSecretStorageReady} + */ + public async isSecretStorageReady(): Promise { + return false; + } + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // SyncCryptoCallbacks implementation From 7ff44d4a502dc3d53e5a8c1f02d6c1b04bf40a77 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 12 May 2023 17:19:18 +0100 Subject: [PATCH 20/94] Integration test for `bootstrapCrossSigning` (#3355) * Stub implementation of bootstrapCrossSigning * Integration test for `bootstrapCrossSigning` --- spec/integ/cross-signing.spec.ts | 117 ++++++++++++++++++++++ spec/unit/matrix-client.spec.ts | 13 +++ spec/unit/rust-crypto/rust-crypto.spec.ts | 5 + src/client.ts | 8 +- src/crypto-api.ts | 16 +++ src/rust-crypto/rust-crypto.ts | 9 +- 6 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 spec/integ/cross-signing.spec.ts diff --git a/spec/integ/cross-signing.spec.ts b/spec/integ/cross-signing.spec.ts new file mode 100644 index 00000000000..0b20683091a --- /dev/null +++ b/spec/integ/cross-signing.spec.ts @@ -0,0 +1,117 @@ +/* +Copyright 2023 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 fetchMock from "fetch-mock-jest"; +import "fake-indexeddb/auto"; +import { IDBFactory } from "fake-indexeddb"; + +import { CRYPTO_BACKENDS, InitCrypto } from "../test-utils/test-utils"; +import { createClient, MatrixClient, UIAuthCallback } from "../../src"; + +afterEach(() => { + // reset fake-indexeddb after each test, to make sure we don't leak connections + // cf https://github.com/dumbmatter/fakeIndexedDB#wipingresetting-the-indexeddb-for-a-fresh-state + // eslint-disable-next-line no-global-assign + indexedDB = new IDBFactory(); +}); + +const TEST_USER_ID = "@alice:localhost"; +const TEST_DEVICE_ID = "xzcvb"; + +/** + * Integration tests for cross-signing functionality. + * + * These tests work by intercepting HTTP requests via fetch-mock rather than mocking out bits of the client, so as + * to provide the most effective integration tests possible. + */ +describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: string, initCrypto: InitCrypto) => { + // oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the + // Rust backend. Once we have full support in the rust sdk, it will go away. + const oldBackendOnly = backend === "rust-sdk" ? test.skip : test; + + let aliceClient: MatrixClient; + + beforeEach(async () => { + // anything that we don't have a specific matcher for silently returns a 404 + fetchMock.catch(404); + fetchMock.config.warnOnFallback = false; + + const homeserverUrl = "https://alice-server.com"; + aliceClient = createClient({ + baseUrl: homeserverUrl, + userId: TEST_USER_ID, + accessToken: "akjgkrgjs", + deviceId: TEST_DEVICE_ID, + }); + + await initCrypto(aliceClient); + }); + + afterEach(async () => { + await aliceClient.stopClient(); + fetchMock.mockReset(); + }); + + describe("bootstrapCrossSigning (before initialsync completes)", () => { + oldBackendOnly("publishes keys if none were yet published", async () => { + // have account_data requests return an empty object + fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {}); + + // we expect a request to upload signatures for our device ... + fetchMock.post({ url: "path:/_matrix/client/v3/keys/signatures/upload", name: "upload-sigs" }, {}); + + // ... and one to upload the cross-signing keys (with UIA) + fetchMock.post( + { url: "path:/_matrix/client/unstable/keys/device_signing/upload", name: "upload-keys" }, + {}, + ); + + // provide a UIA callback, so that the cross-signing keys are uploaded + const authDict = { type: "test" }; + const uiaCallback: UIAuthCallback = async (makeRequest) => { + await makeRequest(authDict); + }; + + // now bootstrap cross signing, and check it resolves successfully + await aliceClient.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: uiaCallback, + }); + + // check the cross-signing keys upload + expect(fetchMock.called("upload-keys")).toBeTruthy(); + const [, keysOpts] = fetchMock.lastCall("upload-keys")!; + const keysBody = JSON.parse(keysOpts!.body as string); + expect(keysBody.auth).toEqual(authDict); // check uia dict was passed + // there should be a key of each type + // master key is signed by the device + expect(keysBody).toHaveProperty(`master_key.signatures.[${TEST_USER_ID}].[ed25519:${TEST_DEVICE_ID}]`); + const masterKeyId = Object.keys(keysBody.master_key.keys)[0]; + // ssk and usk are signed by the master key + expect(keysBody).toHaveProperty(`self_signing_key.signatures.[${TEST_USER_ID}].[${masterKeyId}]`); + expect(keysBody).toHaveProperty(`user_signing_key.signatures.[${TEST_USER_ID}].[${masterKeyId}]`); + const sskId = Object.keys(keysBody.self_signing_key.keys)[0]; + + // check the publish call + expect(fetchMock.called("upload-sigs")).toBeTruthy(); + const [, sigsOpts] = fetchMock.lastCall("upload-sigs")!; + const body = JSON.parse(sigsOpts!.body as string); + // there should be a signature for our device, by our self-signing key. + expect(body).toHaveProperty( + `[${TEST_USER_ID}].[${TEST_DEVICE_ID}].signatures.[${TEST_USER_ID}].[${sskId}]`, + ); + }); + }); +}); diff --git a/spec/unit/matrix-client.spec.ts b/spec/unit/matrix-client.spec.ts index 3b5231d55d9..e77a3d5e8a1 100644 --- a/spec/unit/matrix-client.spec.ts +++ b/spec/unit/matrix-client.spec.ts @@ -2758,6 +2758,10 @@ describe("MatrixClient", function () { expect(() => client.isCrossSigningReady()).toThrow("End-to-end encryption disabled"); }); + it("bootstrapCrossSigning", () => { + expect(() => client.bootstrapCrossSigning({})).toThrow("End-to-end encryption disabled"); + }); + it("isSecretStorageReady", () => { expect(() => client.isSecretStorageReady()).toThrow("End-to-end encryption disabled"); }); @@ -2769,6 +2773,7 @@ describe("MatrixClient", function () { beforeEach(() => { mockCryptoBackend = { isCrossSigningReady: jest.fn(), + bootstrapCrossSigning: jest.fn(), isSecretStorageReady: jest.fn(), stop: jest.fn().mockResolvedValue(undefined), } as unknown as Mocked; @@ -2782,6 +2787,14 @@ describe("MatrixClient", function () { expect(mockCryptoBackend.isCrossSigningReady).toHaveBeenCalledTimes(1); }); + it("bootstrapCrossSigning", async () => { + const testOpts = {}; + mockCryptoBackend.bootstrapCrossSigning.mockResolvedValue(undefined); + await client.bootstrapCrossSigning(testOpts); + expect(mockCryptoBackend.bootstrapCrossSigning).toHaveBeenCalledTimes(1); + expect(mockCryptoBackend.bootstrapCrossSigning).toHaveBeenCalledWith(testOpts); + }); + it("isSecretStorageReady", async () => { client["cryptoBackend"] = mockCryptoBackend; const testResult = "test"; diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 397b4df0322..1297003b74f 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -98,6 +98,11 @@ describe("RustCrypto", () => { await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false); }); + it("bootstrapCrossSigning", async () => { + const rustCrypto = await makeTestRustCrypto(); + await rustCrypto.bootstrapCrossSigning({}); + }); + it("isSecretStorageReady", async () => { const rustCrypto = await makeTestRustCrypto(); await expect(rustCrypto.isSecretStorageReady()).resolves.toBe(false); diff --git a/src/client.ts b/src/client.ts index 216f34d6b7b..a037a5e4f55 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2747,15 +2747,15 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { + if (!this.cryptoBackend) { throw new Error("End-to-end encryption disabled"); } - return this.crypto.bootstrapCrossSigning(opts); + return this.cryptoBackend.bootstrapCrossSigning(opts); } /** diff --git a/src/crypto-api.ts b/src/crypto-api.ts index e303d05837f..338bfee4637 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -137,6 +137,22 @@ export interface CryptoApi { */ isCrossSigningReady(): Promise; + /** + * Bootstrap cross-signing by creating keys if needed. + * + * If everything is already set up, then no changes are made, so this is safe to run to ensure + * cross-signing is ready for use. + * + * This function: + * - creates new cross-signing keys if they are not found locally cached nor in + * secret storage (if it has been set up) + * - publishes the public keys to the server if they are not already published + * - stores the private keys in secret storage if secret storage is set up. + * + * @param opts - options object + */ + bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise; + /** * Checks whether secret storage: * - is enabled on this account diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index 3f7b6f52e83..d92b7a9ca74 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -30,7 +30,7 @@ import { RoomEncryptor } from "./RoomEncryptor"; import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor"; import { KeyClaimManager } from "./KeyClaimManager"; import { MapWithDefault } from "../utils"; -import { DeviceVerificationStatus } from "../crypto-api"; +import { BootstrapCrossSigningOpts, DeviceVerificationStatus } from "../crypto-api"; import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client"; import { Device, DeviceMap } from "../models/device"; @@ -324,6 +324,13 @@ export class RustCrypto implements CryptoBackend { return false; } + /** + * Implementation of {@link CryptoApi#boostrapCrossSigning} + */ + public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise { + logger.log("Cross-signing ready"); + } + /** * Implementation of {@link CryptoApi#isSecretStorageReady} */ From 9e586ab63486096a6932294c39b79693c697e1a0 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Fri, 12 May 2023 18:24:27 +0200 Subject: [PATCH 21/94] Audio concealment (#3349) * add audio concealment to stats report * audio concealment to summary * make ts linter happy * format and rename * fix and add tests * make it prettier! * we can make it even prettier ?! * review * fix tests * pretty * one empty line to ... * remove ratio in audio concealment (ratio is now done in the summary) * remove comment * fix test --- .../webrtc/stats/statsReportBuilder.spec.ts | 8 + .../webrtc/stats/statsReportGatherer.spec.ts | 36 ++- .../webrtc/stats/summaryStatsReporter.spec.ts | 260 ++++++++++++++++-- .../webrtc/stats/trackStatsReporter.spec.ts | 24 +- src/webrtc/stats/connectionStats.ts | 4 +- src/webrtc/stats/media/mediaTrackStats.ts | 25 +- src/webrtc/stats/statsReport.ts | 12 +- src/webrtc/stats/statsReportBuilder.ts | 20 +- src/webrtc/stats/statsReportGatherer.ts | 5 +- src/webrtc/stats/summaryStats.ts | 2 + src/webrtc/stats/summaryStatsReporter.ts | 58 ++-- src/webrtc/stats/trackStatsReporter.ts | 65 +++-- 12 files changed, 442 insertions(+), 77 deletions(-) diff --git a/spec/unit/webrtc/stats/statsReportBuilder.spec.ts b/spec/unit/webrtc/stats/statsReportBuilder.spec.ts index fd75c90546d..4b4faa1ca05 100644 --- a/spec/unit/webrtc/stats/statsReportBuilder.spec.ts +++ b/spec/unit/webrtc/stats/statsReportBuilder.spec.ts @@ -91,6 +91,13 @@ describe("StatsReportBuilder", () => { ["REMOTE_AUDIO_TRACK_ID", 0.1], ["REMOTE_VIDEO_TRACK_ID", 50], ]), + audioConcealment: new Map([ + ["REMOTE_AUDIO_TRACK_ID", { concealedAudio: 3000, totalAudioDuration: 3000 * 20 }], + ]), + totalAudioConcealment: { + concealedAudio: 3000, + totalAudioDuration: (1 / 0.05) * 3000, + }, }); }); }); @@ -104,6 +111,7 @@ describe("StatsReportBuilder", () => { remoteAudioTrack.setLoss({ packetsTotal: 20, packetsLost: 0, isDownloadStream: true }); remoteAudioTrack.setBitrate({ download: 4000, upload: 0 }); remoteAudioTrack.setJitter(0.1); + remoteAudioTrack.setAudioConcealment(3000, 3000 * 20); localVideoTrack.setCodec("v8"); localVideoTrack.setLoss({ packetsTotal: 30, packetsLost: 6, isDownloadStream: false }); diff --git a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts b/spec/unit/webrtc/stats/statsReportGatherer.spec.ts index aad12b4a36a..0eb847ce01d 100644 --- a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts +++ b/spec/unit/webrtc/stats/statsReportGatherer.spec.ts @@ -43,8 +43,22 @@ describe("StatsReportGatherer", () => { receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }); expect(collector.getActive()).toBeTruthy(); }); @@ -74,8 +88,22 @@ describe("StatsReportGatherer", () => { receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }); expect(getStats).toHaveBeenCalled(); expect(collector.getActive()).toBeFalsy(); diff --git a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts index 2692dbf03eb..117231ee2de 100644 --- a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts @@ -37,29 +37,85 @@ describe("SummaryStatsReporter", () => { receivedMedia: 10, receivedAudioMedia: 4, receivedVideoMedia: 6, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 100, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 13, receivedAudioMedia: 0, receivedVideoMedia: 13, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 5, + totalAudio: 100, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 10, + totalAudio: 100, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 15, receivedAudioMedia: 6, receivedVideoMedia: 9, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 100, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -69,6 +125,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 0.75, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0.0375, }); }); @@ -78,8 +135,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 10, receivedAudioMedia: 10, receivedVideoMedia: 0, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 1, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 1, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -89,6 +160,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); @@ -98,8 +170,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 10, receivedAudioMedia: 10, receivedVideoMedia: 0, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 2, muted: 1, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 2, + muted: 1, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -109,6 +195,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 0, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); @@ -118,8 +205,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 100, receivedAudioMedia: 0, receivedVideoMedia: 100, - audioTrackSummary: { count: 1, muted: 1, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 1, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -129,6 +230,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); @@ -138,29 +240,85 @@ describe("SummaryStatsReporter", () => { receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 20, maxPacketLoss: 5 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 20, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 2, maxPacketLoss: 5 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 2, maxPacketLoss: 5 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, }, { receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 2, maxPacketLoss: 5 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 40 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 40, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -170,6 +328,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 20, maxPacketLoss: 40, + percentageConcealedAudio: 0, }); }); @@ -179,8 +338,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 10, receivedAudioMedia: 0, receivedVideoMedia: 10, - audioTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -190,6 +363,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); @@ -199,8 +373,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 1, receivedAudioMedia: 22, receivedVideoMedia: 0, - audioTrackSummary: { count: 1, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -210,6 +398,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); @@ -219,8 +408,22 @@ describe("SummaryStatsReporter", () => { receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, - videoTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, }, ]; reporter.build(summary); @@ -230,6 +433,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + percentageConcealedAudio: 0, }); }); }); diff --git a/spec/unit/webrtc/stats/trackStatsReporter.spec.ts b/spec/unit/webrtc/stats/trackStatsReporter.spec.ts index 79b1ad680aa..0c22cdaf6a0 100644 --- a/spec/unit/webrtc/stats/trackStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/trackStatsReporter.spec.ts @@ -226,12 +226,16 @@ describe("TrackStatsReporter", () => { muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, videoTrackSummary: { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, }); }); @@ -245,12 +249,16 @@ describe("TrackStatsReporter", () => { muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, videoTrackSummary: { count: 3, muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, }); }); @@ -266,12 +274,16 @@ describe("TrackStatsReporter", () => { muted: 1, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, videoTrackSummary: { count: 3, muted: 1, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, }); }); @@ -287,17 +299,21 @@ describe("TrackStatsReporter", () => { muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, videoTrackSummary: { count: 3, muted: 0, maxJitter: 0, maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, }, }); }); - it("and returns summary and build max jitter and packet loss", async () => { + it("and returns summary and build max jitter, packet loss and audio conealment", async () => { const trackStatsList = buildMockTrackStatsList(); // video remote trackStatsList[1].setJitter(12); @@ -311,6 +327,8 @@ describe("TrackStatsReporter", () => { trackStatsList[5].setJitter(15); trackStatsList[2].setLoss({ packetsLost: 5, packetsTotal: 0, isDownloadStream: true }); trackStatsList[5].setLoss({ packetsLost: 0, packetsTotal: 0, isDownloadStream: true }); + trackStatsList[2].setAudioConcealment(220, 2000); + trackStatsList[5].setAudioConcealment(180, 2000); const summary = TrackStatsReporter.buildTrackSummary(trackStatsList); expect(summary).toEqual({ @@ -319,12 +337,16 @@ describe("TrackStatsReporter", () => { muted: 0, maxJitter: 15, maxPacketLoss: 5, + concealedAudio: 400, + totalAudio: 4000, }, videoTrackSummary: { count: 3, muted: 0, maxJitter: 66, maxPacketLoss: 55, + concealedAudio: 0, + totalAudio: 0, }, }); }); diff --git a/src/webrtc/stats/connectionStats.ts b/src/webrtc/stats/connectionStats.ts index dbde6e50327..ef1c36797ea 100644 --- a/src/webrtc/stats/connectionStats.ts +++ b/src/webrtc/stats/connectionStats.ts @@ -33,7 +33,7 @@ export interface ConnectionStatsBitrate extends Bitrate { video?: Bitrate; } -export interface PacketLoos { +export interface PacketLoss { total: number; download: number; upload: number; @@ -42,6 +42,6 @@ export interface PacketLoos { export class ConnectionStats { public bandwidth: ConnectionStatsBitrate = {} as ConnectionStatsBitrate; public bitrate: ConnectionStatsBitrate = {} as ConnectionStatsBitrate; - public packetLoss: PacketLoos = {} as PacketLoos; + public packetLoss: PacketLoss = {} as PacketLoss; public transport: TransportStats[] = []; } diff --git a/src/webrtc/stats/media/mediaTrackStats.ts b/src/webrtc/stats/media/mediaTrackStats.ts index 66475e19ce6..7835ceb8a65 100644 --- a/src/webrtc/stats/media/mediaTrackStats.ts +++ b/src/webrtc/stats/media/mediaTrackStats.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { AudioConcealment } from "../statsReport"; import { TrackId } from "./mediaTrackHandler"; export interface PacketLoss { @@ -32,7 +33,14 @@ export interface Bitrate { */ upload: number; } +export interface ConcealedAudio { + /** + * duration in ms + */ + duration: number; + ratio: number; +} export interface Resolution { width: number; height: number; @@ -44,6 +52,7 @@ export class MediaTrackStats { private loss: PacketLoss = { packetsTotal: 0, packetsLost: 0, isDownloadStream: false }; private bitrate: Bitrate = { download: 0, upload: 0 }; private resolution: Resolution = { width: -1, height: -1 }; + private audioConcealment: AudioConcealment = { concealedAudio: 0, totalAudioDuration: 0 }; private framerate = 0; private jitter = 0; private codec = ""; @@ -61,8 +70,8 @@ export class MediaTrackStats { return this.type; } - public setLoss(loos: PacketLoss): void { - this.loss = loos; + public setLoss(loss: PacketLoss): void { + this.loss = loss; } public getLoss(): PacketLoss { @@ -152,4 +161,16 @@ export class MediaTrackStats { public getJitter(): number { return this.jitter; } + + /** + * Audio concealment ration (conceled duration / total duration) + */ + public setAudioConcealment(concealedAudioDuration: number, totalAudioDuration: number): void { + this.audioConcealment.concealedAudio = concealedAudioDuration; + this.audioConcealment.totalAudioDuration = totalAudioDuration; + } + + public getAudioConcealment(): AudioConcealment { + return this.audioConcealment; + } } diff --git a/src/webrtc/stats/statsReport.ts b/src/webrtc/stats/statsReport.ts index 64bb6aba440..67fd3513bcc 100644 --- a/src/webrtc/stats/statsReport.ts +++ b/src/webrtc/stats/statsReport.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { ConnectionStatsBandwidth, ConnectionStatsBitrate, PacketLoos } from "./connectionStats"; +import { ConnectionStatsBandwidth, ConnectionStatsBitrate, PacketLoss } from "./connectionStats"; import { TransportStats } from "./transportStats"; import { Resolution } from "./media/mediaTrackStats"; @@ -34,7 +34,9 @@ export interface ByteSentStatsReport extends Map { export interface ConnectionStatsReport { bandwidth: ConnectionStatsBandwidth; bitrate: ConnectionStatsBitrate; - packetLoss: PacketLoos; + packetLoss: PacketLoss; + audioConcealment: Map; + totalAudioConcealment: AudioConcealment; resolution: ResolutionMap; framerate: FramerateMap; codec: CodecMap; @@ -42,6 +44,11 @@ export interface ConnectionStatsReport { transport: TransportStats[]; } +export interface AudioConcealment { + concealedAudio: number; + totalAudioDuration: number; +} + export interface ResolutionMap { local: Map; remote: Map; @@ -70,4 +77,5 @@ export interface SummaryStatsReport { percentageReceivedVideoMedia: number; maxJitter: number; maxPacketLoss: number; + percentageConcealedAudio: number; } diff --git a/src/webrtc/stats/statsReportBuilder.ts b/src/webrtc/stats/statsReportBuilder.ts index eeca4ed4074..566648fb0e9 100644 --- a/src/webrtc/stats/statsReportBuilder.ts +++ b/src/webrtc/stats/statsReportBuilder.ts @@ -13,7 +13,7 @@ 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 { CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from "./statsReport"; +import { AudioConcealment, CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from "./statsReport"; import { MediaTrackStats, Resolution } from "./media/mediaTrackStats"; export class StatsReportBuilder { @@ -38,12 +38,16 @@ export class StatsReportBuilder { const framerates: FramerateMap = { local: new Map(), remote: new Map() }; const codecs: CodecMap = { local: new Map(), remote: new Map() }; const jitter = new Map(); + const audioConcealment = new Map(); let audioBitrateDownload = 0; let audioBitrateUpload = 0; let videoBitrateDownload = 0; let videoBitrateUpload = 0; + let totalConcealedAudio = 0; + let totalAudioDuration = 0; + for (const [trackId, trackStats] of stats) { // process packet loss stats const loss = trackStats.getLoss(); @@ -58,6 +62,11 @@ export class StatsReportBuilder { // collect resolutions and framerates if (trackStats.kind === "audio") { + // process audio quality stats + const audioConcealmentForTrack = trackStats.getAudioConcealment(); + totalConcealedAudio += audioConcealmentForTrack.concealedAudio; + totalAudioDuration += audioConcealmentForTrack.totalAudioDuration; + audioBitrateDownload += trackStats.getBitrate().download; audioBitrateUpload += trackStats.getBitrate().upload; } else { @@ -70,6 +79,9 @@ export class StatsReportBuilder { codecs[trackStats.getType()].set(trackId, trackStats.getCodec()); if (trackStats.getType() === "remote") { jitter.set(trackId, trackStats.getJitter()); + if (trackStats.kind === "audio") { + audioConcealment.set(trackId, trackStats.getAudioConcealment()); + } } trackStats.resetBitrate(); @@ -98,6 +110,12 @@ export class StatsReportBuilder { download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download), upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload), }; + report.audioConcealment = audioConcealment; + report.totalAudioConcealment = { + concealedAudio: totalConcealedAudio, + totalAudioDuration, + }; + report.framerate = framerates; report.resolution = resolutions; report.codec = codecs; diff --git a/src/webrtc/stats/statsReportGatherer.ts b/src/webrtc/stats/statsReportGatherer.ts index 25f9b93c862..e5146122fb6 100644 --- a/src/webrtc/stats/statsReportGatherer.ts +++ b/src/webrtc/stats/statsReportGatherer.ts @@ -53,8 +53,8 @@ export class StatsReportGatherer { receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0 }, - videoTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0 }, + audioTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 }, + videoTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 }, } as SummaryStats; if (this.isActive) { const statsPromise = this.pc.getStats(); @@ -138,6 +138,7 @@ export class StatsReportGatherer { const ts = this.trackStats.findTransceiverByTrackId(trackStats.trackId); TrackStatsReporter.setTrackStatsState(trackStats, ts); TrackStatsReporter.buildJitter(trackStats, now); + TrackStatsReporter.buildAudioConcealment(trackStats, now); } else if (before) { byteSentStats.set(trackStats.trackId, StatsValueFormatter.getNonNegativeValue(now.bytesSent)); TrackStatsReporter.buildBitrateSend(trackStats, now, before); diff --git a/src/webrtc/stats/summaryStats.ts b/src/webrtc/stats/summaryStats.ts index f708a5bad09..e5591749db9 100644 --- a/src/webrtc/stats/summaryStats.ts +++ b/src/webrtc/stats/summaryStats.ts @@ -23,4 +23,6 @@ export interface TrackSummary { muted: number; maxJitter: number; maxPacketLoss: number; + concealedAudio: number; + totalAudio: number; } diff --git a/src/webrtc/stats/summaryStatsReporter.ts b/src/webrtc/stats/summaryStatsReporter.ts index 66f738b1ca5..8ad0c5591d3 100644 --- a/src/webrtc/stats/summaryStatsReporter.ts +++ b/src/webrtc/stats/summaryStatsReporter.ts @@ -14,59 +14,76 @@ import { StatsReportEmitter } from "./statsReportEmitter"; import { SummaryStats } from "./summaryStats"; import { SummaryStatsReport } from "./statsReport"; -interface ReceivedMedia { - audio: number; - video: number; - media: number; +interface SummaryCounter { + receivedAudio: number; + receivedVideo: number; + receivedMedia: number; + concealedAudio: number; + totalAudio: number; } export class SummaryStatsReporter { public constructor(private emitter: StatsReportEmitter) {} public build(summary: SummaryStats[]): void { - const entiretyTracksCount = summary.length; - if (entiretyTracksCount === 0) { + const summaryTotalCount = summary.length; + if (summaryTotalCount === 0) { return; } - const receivedCounter: ReceivedMedia = { audio: 0, video: 0, media: 0 }; + const summaryCounter: SummaryCounter = { + receivedAudio: 0, + receivedVideo: 0, + receivedMedia: 0, + concealedAudio: 0, + totalAudio: 0, + }; let maxJitter = 0; let maxPacketLoss = 0; - summary.forEach((stats) => { - this.countTrackListReceivedMedia(receivedCounter, stats); + this.countTrackListReceivedMedia(summaryCounter, stats); + this.countConcealedAudio(summaryCounter, stats); maxJitter = this.buildMaxJitter(maxJitter, stats); maxPacketLoss = this.buildMaxPacketLoss(maxPacketLoss, stats); }); - + const decimalPlaces = 5; const report = { - percentageReceivedMedia: Math.round((receivedCounter.media / entiretyTracksCount) * 100) / 100, - percentageReceivedVideoMedia: Math.round((receivedCounter.video / entiretyTracksCount) * 100) / 100, - percentageReceivedAudioMedia: Math.round((receivedCounter.audio / entiretyTracksCount) * 100) / 100, + percentageReceivedMedia: Number((summaryCounter.receivedMedia / summaryTotalCount).toFixed(decimalPlaces)), + percentageReceivedVideoMedia: Number( + (summaryCounter.receivedVideo / summaryTotalCount).toFixed(decimalPlaces), + ), + percentageReceivedAudioMedia: Number( + (summaryCounter.receivedAudio / summaryTotalCount).toFixed(decimalPlaces), + ), maxJitter, maxPacketLoss, + percentageConcealedAudio: Number( + summaryCounter.totalAudio > 0 + ? (summaryCounter.concealedAudio / summaryCounter.totalAudio).toFixed(decimalPlaces) + : 0, + ), } as SummaryStatsReport; this.emitter.emitSummaryStatsReport(report); } - private countTrackListReceivedMedia(counter: ReceivedMedia, stats: SummaryStats): void { + private countTrackListReceivedMedia(counter: SummaryCounter, stats: SummaryStats): void { let hasReceivedAudio = false; let hasReceivedVideo = false; if (stats.receivedAudioMedia > 0 || stats.audioTrackSummary.count === 0) { - counter.audio++; + counter.receivedAudio++; hasReceivedAudio = true; } if (stats.receivedVideoMedia > 0 || stats.videoTrackSummary.count === 0) { - counter.video++; + counter.receivedVideo++; hasReceivedVideo = true; } else { if (stats.videoTrackSummary.muted > 0 && stats.videoTrackSummary.muted === stats.videoTrackSummary.count) { - counter.video++; + counter.receivedVideo++; hasReceivedVideo = true; } } if (hasReceivedVideo && hasReceivedAudio) { - counter.media++; + counter.receivedMedia++; } } @@ -91,4 +108,9 @@ export class SummaryStatsReporter { } return maxPacketLoss; } + + private countConcealedAudio(summaryCounter: SummaryCounter, stats: SummaryStats): void { + summaryCounter.concealedAudio += stats.audioTrackSummary.concealedAudio; + summaryCounter.totalAudio += stats.audioTrackSummary.totalAudio; + } } diff --git a/src/webrtc/stats/trackStatsReporter.ts b/src/webrtc/stats/trackStatsReporter.ts index e243cb32603..75409f26afa 100644 --- a/src/webrtc/stats/trackStatsReporter.ts +++ b/src/webrtc/stats/trackStatsReporter.ts @@ -140,23 +140,44 @@ export class TrackStatsReporter { audioTrackSummary: TrackSummary; videoTrackSummary: TrackSummary; } { - const audioTrackSummary = { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }; - const videoTrackSummary = { count: 0, muted: 0, maxJitter: 0, maxPacketLoss: 0 }; - trackStatsList - .filter((t) => t.getType() === "remote") - .forEach((stats) => { - const trackSummary = stats.kind === "video" ? videoTrackSummary : audioTrackSummary; - trackSummary.count++; - if (stats.alive && stats.muted) { - trackSummary.muted++; - } - if (trackSummary.maxJitter < stats.getJitter()) { - trackSummary.maxJitter = stats.getJitter(); - } - if (trackSummary.maxPacketLoss < stats.getLoss().packetsLost) { - trackSummary.maxPacketLoss = stats.getLoss().packetsLost; - } - }); + const videoTrackSummary: TrackSummary = { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }; + const audioTrackSummary: TrackSummary = { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }; + + const remoteTrackList = trackStatsList.filter((t) => t.getType() === "remote"); + const audioTrackList = remoteTrackList.filter((t) => t.kind === "audio"); + + remoteTrackList.forEach((stats) => { + const trackSummary = stats.kind === "video" ? videoTrackSummary : audioTrackSummary; + trackSummary.count++; + if (stats.alive && stats.muted) { + trackSummary.muted++; + } + if (trackSummary.maxJitter < stats.getJitter()) { + trackSummary.maxJitter = stats.getJitter(); + } + if (trackSummary.maxPacketLoss < stats.getLoss().packetsLost) { + trackSummary.maxPacketLoss = stats.getLoss().packetsLost; + } + if (audioTrackList.length > 0) { + trackSummary.concealedAudio += stats.getAudioConcealment()?.concealedAudio; + trackSummary.totalAudio += stats.getAudioConcealment()?.totalAudioDuration; + } + }); + return { audioTrackSummary, videoTrackSummary }; } @@ -173,4 +194,14 @@ export class TrackStatsReporter { trackStats.setJitter(-1); } } + + public static buildAudioConcealment(trackStats: MediaTrackStats, statsReport: any): void { + if (statsReport.type !== "inbound-rtp") { + return; + } + const msPerSample = (1000 * statsReport?.totalSamplesDuration) / statsReport?.totalSamplesReceived; + const concealedAudioDuration = msPerSample * statsReport?.concealedSamples; + const totalAudioDuration = 1000 * statsReport?.totalSamplesDuration; + trackStats.setAudioConcealment(concealedAudioDuration, totalAudioDuration); + } } From aaae55736f5a1295aae249a391489514cab12b64 Mon Sep 17 00:00:00 2001 From: Robin Date: Sat, 13 May 2023 13:54:29 -0400 Subject: [PATCH 22/94] Keep measuring a call feed's volume after a stream replacement (#3361) --- src/webrtc/callFeed.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index 505cf56f843..07c906e6796 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -135,6 +135,8 @@ export class CallFeed extends TypedEventEmitter private updateStream(oldStream: MediaStream | null, newStream: MediaStream): void { if (newStream === oldStream) return; + const wasMeasuringVolumeActivity = this.measuringVolumeActivity; + if (oldStream) { oldStream.removeEventListener("addtrack", this.onAddTrack); this.measureVolumeActivity(false); @@ -145,6 +147,7 @@ export class CallFeed extends TypedEventEmitter if (this.hasAudioTrack) { this.initVolumeMeasuring(); + if (wasMeasuringVolumeActivity) this.measureVolumeActivity(true); } else { this.measureVolumeActivity(false); } From e6a3b0ebc035ca79fe55de1b3c3aaafc87fba30e Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Sun, 14 May 2023 18:58:38 +0200 Subject: [PATCH 23/94] Total summary count (#3351) * add audio concealment to stats report * audio concealment to summary * make ts linter happy * format and rename * fix and add tests * make it prettier! * we can make it even prettier ?! * review * fix tests * pretty * one empty line to ... * remove ratio in audio concealment (ratio is now done in the summary) * remove comment * fix test * add peer connections to summary report * tests --- spec/unit/webrtc/stats/summaryStatsReporter.spec.ts | 8 ++++++++ src/webrtc/stats/statsReport.ts | 1 + src/webrtc/stats/summaryStatsReporter.ts | 1 + 3 files changed, 10 insertions(+) diff --git a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts index 117231ee2de..22c7f60c3ad 100644 --- a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts @@ -125,6 +125,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 0.75, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 4, percentageConcealedAudio: 0.0375, }); }); @@ -160,6 +161,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); @@ -195,6 +197,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 0, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); @@ -230,6 +233,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); @@ -328,6 +332,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 20, maxPacketLoss: 40, + peerConnections: 4, percentageConcealedAudio: 0, }); }); @@ -363,6 +368,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); @@ -398,6 +404,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); @@ -433,6 +440,7 @@ describe("SummaryStatsReporter", () => { percentageReceivedVideoMedia: 1, maxJitter: 0, maxPacketLoss: 0, + peerConnections: 1, percentageConcealedAudio: 0, }); }); diff --git a/src/webrtc/stats/statsReport.ts b/src/webrtc/stats/statsReport.ts index 67fd3513bcc..cdfa751f464 100644 --- a/src/webrtc/stats/statsReport.ts +++ b/src/webrtc/stats/statsReport.ts @@ -78,4 +78,5 @@ export interface SummaryStatsReport { maxJitter: number; maxPacketLoss: number; percentageConcealedAudio: number; + peerConnections: number; } diff --git a/src/webrtc/stats/summaryStatsReporter.ts b/src/webrtc/stats/summaryStatsReporter.ts index 8ad0c5591d3..7fd594c5099 100644 --- a/src/webrtc/stats/summaryStatsReporter.ts +++ b/src/webrtc/stats/summaryStatsReporter.ts @@ -61,6 +61,7 @@ export class SummaryStatsReporter { ? (summaryCounter.concealedAudio / summaryCounter.totalAudio).toFixed(decimalPlaces) : 0, ), + peerConnections: summaryTotalCount, } as SummaryStatsReport; this.emitter.emitSummaryStatsReport(report); } From 6ef9f6c55e7bedc2d394f1cace83d5f5ae10b581 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 15 May 2023 09:10:44 +0100 Subject: [PATCH 24/94] Enable better tree shaking (#3356) --- src/content-repo.ts | 4 +-- .../store/indexeddb-crypto-store-backend.ts | 4 +-- src/crypto/store/memory-crypto-store.ts | 6 ++--- src/http-api/fetch.ts | 6 ++--- src/http-api/index.ts | 26 +++++++++---------- src/models/read-receipt.ts | 4 +-- src/models/room-member.ts | 14 +++++----- src/models/room-state.ts | 12 ++++----- src/models/room.ts | 6 ++--- src/scheduler.ts | 11 ++++---- src/sliding-sync-sdk.ts | 6 ++--- src/store/indexeddb-local-backend.ts | 20 +++++++------- src/sync.ts | 12 ++++----- src/webrtc/call.ts | 8 +++--- 14 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/content-repo.ts b/src/content-repo.ts index 257541296bb..d3130aa722a 100644 --- a/src/content-repo.ts +++ b/src/content-repo.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as utils from "./utils"; +import { encodeParams } from "./utils"; /** * Get the HTTP URL for an MXC URI. @@ -74,6 +74,6 @@ export function getHttpUriForMxc( serverAndMediaId = serverAndMediaId.slice(0, fragmentOffset); } - const urlParams = Object.keys(params).length === 0 ? "" : "?" + utils.encodeParams(params); + const urlParams = Object.keys(params).length === 0 ? "" : "?" + encodeParams(params); return baseUrl + prefix + serverAndMediaId + urlParams + fragment; } diff --git a/src/crypto/store/indexeddb-crypto-store-backend.ts b/src/crypto/store/indexeddb-crypto-store-backend.ts index 7827697ec8d..228418091f7 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.ts +++ b/src/crypto/store/indexeddb-crypto-store-backend.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { logger, PrefixedLogger } from "../../logger"; -import * as utils from "../../utils"; +import { deepCompare } from "../../utils"; import { CryptoStore, IDeviceData, @@ -158,7 +158,7 @@ export class Backend implements CryptoStore { const existing = cursor.value; - if (utils.deepCompare(existing.requestBody, requestBody)) { + if (deepCompare(existing.requestBody, requestBody)) { // got a match callback(existing); return; diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index d4e154d39d3..a78574850c5 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { logger } from "../../logger"; -import * as utils from "../../utils"; +import { deepCompare, promiseTry } from "../../utils"; import { CryptoStore, IDeviceData, @@ -90,7 +90,7 @@ export class MemoryCryptoStore implements CryptoStore { public getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): Promise { const requestBody = request.requestBody; - return utils.promiseTry(() => { + return promiseTry(() => { // first see if we already have an entry for this request. const existing = this._getOutgoingRoomKeyRequest(requestBody); @@ -138,7 +138,7 @@ export class MemoryCryptoStore implements CryptoStore { // eslint-disable-next-line @typescript-eslint/naming-convention private _getOutgoingRoomKeyRequest(requestBody: IRoomKeyRequestBody): OutgoingRoomKeyRequest | null { for (const existing of this.outgoingRoomKeyRequests) { - if (utils.deepCompare(existing.requestBody, requestBody)) { + if (deepCompare(existing.requestBody, requestBody)) { return existing; } } diff --git a/src/http-api/fetch.ts b/src/http-api/fetch.ts index e1c266c64d1..5b0f0a1bacc 100644 --- a/src/http-api/fetch.ts +++ b/src/http-api/fetch.ts @@ -18,7 +18,7 @@ limitations under the License. * This is an internal module. See {@link MatrixHttpApi} for the public class. */ -import * as utils from "../utils"; +import { checkObjectHasKeys, encodeParams } from "../utils"; import { TypedEventEmitter } from "../models/typed-event-emitter"; import { Method } from "./method"; import { ConnectionError, MatrixError } from "./errors"; @@ -45,7 +45,7 @@ export class FetchHttpApi { private eventEmitter: TypedEventEmitter, public readonly opts: O, ) { - utils.checkObjectHasKeys(opts, ["baseUrl", "prefix"]); + checkObjectHasKeys(opts, ["baseUrl", "prefix"]); opts.onlyData = !!opts.onlyData; opts.useAuthorizationHeader = opts.useAuthorizationHeader ?? true; } @@ -304,7 +304,7 @@ export class FetchHttpApi { public getUrl(path: string, queryParams?: QueryDict, prefix?: string, baseUrl?: string): URL { const url = new URL((baseUrl ?? this.opts.baseUrl) + (prefix ?? this.opts.prefix) + path); if (queryParams) { - utils.encodeParams(queryParams, url.searchParams); + encodeParams(queryParams, url.searchParams); } return url; } diff --git a/src/http-api/index.ts b/src/http-api/index.ts index c5e8e2a3a9a..1de1f847b27 100644 --- a/src/http-api/index.ts +++ b/src/http-api/index.ts @@ -17,7 +17,7 @@ limitations under the License. import { FetchHttpApi } from "./fetch"; import { FileType, IContentUri, IHttpOpts, Upload, UploadOpts, UploadResponse } from "./interface"; import { MediaPrefix } from "./prefix"; -import * as utils from "../utils"; +import { defer, QueryDict, removeElement } from "../utils"; import * as callbacks from "../realtime-callbacks"; import { Method } from "./method"; import { ConnectionError } from "./errors"; @@ -58,14 +58,14 @@ export class MatrixHttpApi extends FetchHttpApi { total: 0, abortController, } as Upload; - const defer = utils.defer(); + const deferred = defer(); if (global.XMLHttpRequest) { const xhr = new global.XMLHttpRequest(); const timeoutFn = function (): void { xhr.abort(); - defer.reject(new Error("Timeout")); + deferred.reject(new Error("Timeout")); }; // set an initial timeout of 30s; we'll advance it each time we get a progress notification @@ -84,16 +84,16 @@ export class MatrixHttpApi extends FetchHttpApi { } if (xhr.status >= 400) { - defer.reject(parseErrorResponse(xhr, xhr.responseText)); + deferred.reject(parseErrorResponse(xhr, xhr.responseText)); } else { - defer.resolve(JSON.parse(xhr.responseText)); + deferred.resolve(JSON.parse(xhr.responseText)); } } catch (err) { if ((err).name === "AbortError") { - defer.reject(err); + deferred.reject(err); return; } - defer.reject(new ConnectionError("request failed", err)); + deferred.reject(new ConnectionError("request failed", err)); } break; } @@ -131,7 +131,7 @@ export class MatrixHttpApi extends FetchHttpApi { xhr.abort(); }); } else { - const queryParams: utils.QueryDict = {}; + const queryParams: QueryDict = {}; if (includeFilename && fileName) { queryParams.filename = fileName; } @@ -146,16 +146,16 @@ export class MatrixHttpApi extends FetchHttpApi { .then((response) => { return this.opts.onlyData ? response : response.json(); }) - .then(defer.resolve, defer.reject); + .then(deferred.resolve, deferred.reject); } // remove the upload from the list on completion - upload.promise = defer.promise.finally(() => { - utils.removeElement(this.uploads, (elem) => elem === upload); + upload.promise = deferred.promise.finally(() => { + removeElement(this.uploads, (elem) => elem === upload); }); abortController.signal.addEventListener("abort", () => { - utils.removeElement(this.uploads, (elem) => elem === upload); - defer.reject(new DOMException("Aborted", "AbortError")); + removeElement(this.uploads, (elem) => elem === upload); + deferred.reject(new DOMException("Aborted", "AbortError")); }); this.uploads.push(upload); return upload.promise; diff --git a/src/models/read-receipt.ts b/src/models/read-receipt.ts index 5858fe5bb99..1699779150a 100644 --- a/src/models/read-receipt.ts +++ b/src/models/read-receipt.ts @@ -20,7 +20,7 @@ import { WrappedReceipt, } from "../@types/read_receipts"; import { ListenerMap, TypedEventEmitter } from "./typed-event-emitter"; -import * as utils from "../utils"; +import { isSupportedReceiptType } from "../utils"; import { MatrixEvent } from "./event"; import { EventType } from "../@types/event"; import { EventTimelineSet } from "./event-timeline-set"; @@ -267,7 +267,7 @@ export abstract class ReadReceipt< public getUsersReadUpTo(event: MatrixEvent): string[] { return this.getReceiptsForEvent(event) .filter(function (receipt) { - return utils.isSupportedReceiptType(receipt.type); + return isSupportedReceiptType(receipt.type); }) .map(function (receipt) { return receipt.userId; diff --git a/src/models/room-member.ts b/src/models/room-member.ts index 116a93b624b..e8fcfa60620 100644 --- a/src/models/room-member.ts +++ b/src/models/room-member.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { getHttpUriForMxc } from "../content-repo"; -import * as utils from "../utils"; +import { removeDirectionOverrideChars, removeHiddenChars } from "../utils"; import { User } from "./user"; import { MatrixEvent } from "./event"; import { RoomState } from "./room-state"; @@ -206,8 +206,8 @@ export class RoomMember extends TypedEventEmitter * @returns An array of user IDs or an empty array. */ public getUserIdsWithDisplayName(displayName: string): string[] { - return this.displayNameToUserIds.get(utils.removeHiddenChars(displayName)) ?? []; + return this.displayNameToUserIds.get(removeHiddenChars(displayName)) ?? []; } /** @@ -798,7 +798,7 @@ export class RoomState extends TypedEventEmitter } let requiredLevel = 50; - if (utils.isNumber(powerLevels[action])) { + if (isNumber(powerLevels[action])) { requiredLevel = powerLevels[action]!; } @@ -928,7 +928,7 @@ export class RoomState extends TypedEventEmitter powerLevelsEvent && powerLevelsEvent.getContent() && powerLevelsEvent.getContent().notifications && - utils.isNumber(powerLevelsEvent.getContent().notifications[notifLevelKey]) + isNumber(powerLevelsEvent.getContent().notifications[notifLevelKey]) ) { notifLevel = powerLevelsEvent.getContent().notifications[notifLevelKey]; } @@ -1058,7 +1058,7 @@ export class RoomState extends TypedEventEmitter // We clobber the user_id > name lookup but the name -> [user_id] lookup // means we need to remove that user ID from that array rather than nuking // the lot. - const strippedOldName = utils.removeHiddenChars(oldName); + const strippedOldName = removeHiddenChars(oldName); const existingUserIds = this.displayNameToUserIds.get(strippedOldName); if (existingUserIds) { @@ -1070,7 +1070,7 @@ export class RoomState extends TypedEventEmitter this.userIdsToDisplayNames[userId] = displayName; - const strippedDisplayname = displayName && utils.removeHiddenChars(displayName); + const strippedDisplayname = displayName && removeHiddenChars(displayName); // an empty stripped displayname (undefined/'') will be set to MXID in room-member.js if (strippedDisplayname) { const arr = this.displayNameToUserIds.get(strippedDisplayname) ?? []; diff --git a/src/models/room.ts b/src/models/room.ts index 439bd681998..e16375707f1 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -24,7 +24,7 @@ import { } from "./event-timeline-set"; import { Direction, EventTimeline } from "./event-timeline"; import { getHttpUriForMxc } from "../content-repo"; -import * as utils from "../utils"; +import { compare, removeElement } from "../utils"; import { normalize, noUnsafeEventProps } from "../utils"; import { IEvent, IThreadBundledRelationship, MatrixEvent, MatrixEventEvent, MatrixEventHandlerMap } from "./event"; import { EventStatus } from "./event-status"; @@ -733,7 +733,7 @@ export class Room extends ReadReceipt { ); } - const removed = utils.removeElement( + const removed = removeElement( this.pendingEventList, function (ev) { return ev.getId() == eventId; @@ -3267,7 +3267,7 @@ export class Room extends ReadReceipt { return true; }); // make sure members have stable order - otherMembers.sort((a, b) => utils.compare(a.userId, b.userId)); + otherMembers.sort((a, b) => compare(a.userId, b.userId)); // only 5 first members, immitate summaryHeroes otherMembers = otherMembers.slice(0, 5); otherNames = otherMembers.map((m) => m.name); diff --git a/src/scheduler.ts b/src/scheduler.ts index e41770249c6..41612f1c902 100644 --- a/src/scheduler.ts +++ b/src/scheduler.ts @@ -18,11 +18,10 @@ limitations under the License. * This is an internal module which manages queuing, scheduling and retrying * of requests. */ -import * as utils from "./utils"; import { logger } from "./logger"; import { MatrixEvent } from "./models/event"; import { EventType } from "./@types/event"; -import { IDeferred } from "./utils"; +import { defer, IDeferred, removeElement } from "./utils"; import { ConnectionError, MatrixError } from "./http-api"; import { ISendEventResponse } from "./@types/requests"; @@ -175,7 +174,7 @@ export class MatrixScheduler { return false; } let removed = false; - utils.removeElement(this.queues[name], (element) => { + removeElement(this.queues[name], (element) => { if (element.event.getId() === event.getId()) { // XXX we should probably reject the promise? // https://github.com/matrix-org/matrix-js-sdk/issues/496 @@ -214,15 +213,15 @@ export class MatrixScheduler { if (!this.queues[queueName]) { this.queues[queueName] = []; } - const defer = utils.defer(); + const deferred = defer(); this.queues[queueName].push({ event: event, - defer: defer, + defer: deferred, attempts: 0, }); debuglog("Queue algorithm dumped event %s into queue '%s'", event.getId(), queueName); this.startProcessingQueues(); - return defer.promise; + return deferred.promise; } private startProcessingQueues(): void { diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index e0aa627e273..b7d2223ffa9 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -17,7 +17,7 @@ limitations under the License. import type { SyncCryptoCallbacks } from "./common-crypto/CryptoBackend"; import { NotificationCountType, Room, RoomEvent } from "./models/room"; import { logger } from "./logger"; -import * as utils from "./utils"; +import { promiseMapSeries } from "./utils"; import { EventTimeline } from "./models/event-timeline"; import { ClientEvent, IStoredClientOpts, MatrixClient } from "./client"; import { @@ -726,8 +726,8 @@ export class SlidingSyncSdk { } }; - await utils.promiseMapSeries(stateEvents, processRoomEvent); - await utils.promiseMapSeries(timelineEvents, processRoomEvent); + await promiseMapSeries(stateEvents, processRoomEvent); + await promiseMapSeries(timelineEvents, processRoomEvent); ephemeralEvents.forEach(function (e) { client.emit(ClientEvent.Event, e); }); diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index 80fed44c5c0..a82de854e60 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -15,8 +15,8 @@ limitations under the License. */ import { IMinimalEvent, ISyncData, ISyncResponse, SyncAccumulator } from "../sync-accumulator"; -import * as utils from "../utils"; -import * as IndexedDBHelpers from "../indexeddb-helpers"; +import { deepCopy, promiseTry } from "../utils"; +import { exists as idbExists } from "../indexeddb-helpers"; import { logger } from "../logger"; import { IStateEventWithRoomId, IStoredClientOpts } from "../matrix"; import { ISavedSync } from "./index"; @@ -122,7 +122,7 @@ function reqAsCursorPromise(req: IDBRequest): Promise { export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { public static exists(indexedDB: IDBFactory, dbName: string): Promise { dbName = "matrix-js-sdk:" + (dbName || "default"); - return IndexedDBHelpers.exists(indexedDB, dbName); + return idbExists(indexedDB, dbName); } private readonly dbName: string; @@ -380,7 +380,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { if (copy) { // We must deep copy the stored data so that the /sync processing code doesn't // corrupt the internal state of the sync accumulator (it adds non-clonable keys) - return Promise.resolve(utils.deepCopy(data)); + return Promise.resolve(deepCopy(data)); } else { return Promise.resolve(data); } @@ -435,7 +435,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { */ private persistSyncData(nextBatch: string, roomsData: ISyncResponse["rooms"]): Promise { logger.log("Persisting sync data up to", nextBatch); - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["sync"], "readwrite"); const store = txn.objectStore("sync"); store.put({ @@ -456,7 +456,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { * @returns Promise which resolves if the events were persisted. */ private persistAccountData(accountData: IMinimalEvent[]): Promise { - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["accountData"], "readwrite"); const store = txn.objectStore("accountData"); for (const event of accountData) { @@ -475,7 +475,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { * @returns Promise which resolves if the users were persisted. */ private persistUserPresenceEvents(tuples: UserTuple[]): Promise { - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["users"], "readwrite"); const store = txn.objectStore("users"); for (const tuple of tuples) { @@ -495,7 +495,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { * @returns A list of presence events in their raw form. */ public getUserPresenceEvents(): Promise { - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["users"], "readonly"); const store = txn.objectStore("users"); return selectQuery(store, undefined, (cursor) => { @@ -510,7 +510,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { */ private loadAccountData(): Promise { logger.log(`LocalIndexedDBStoreBackend: loading account data...`); - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["accountData"], "readonly"); const store = txn.objectStore("accountData"); return selectQuery(store, undefined, (cursor) => { @@ -528,7 +528,7 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { */ private loadSyncData(): Promise { logger.log(`LocalIndexedDBStoreBackend: loading sync data...`); - return utils.promiseTry(() => { + return promiseTry(() => { const txn = this.db!.transaction(["sync"], "readonly"); const store = txn.objectStore("sync"); return selectQuery(store, undefined, (cursor) => { diff --git a/src/sync.ts b/src/sync.ts index 8bf0f8626fc..4800880bc4a 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -28,7 +28,7 @@ import { Optional } from "matrix-events-sdk"; import type { SyncCryptoCallbacks } from "./common-crypto/CryptoBackend"; import { User, UserEvent } from "./models/user"; import { NotificationCountType, Room, RoomEvent } from "./models/room"; -import * as utils from "./utils"; +import { promiseMapSeries, defer, deepCopy } from "./utils"; import { IDeferred, noUnsafeEventProps, unsafeProp } from "./utils"; import { Filter } from "./filter"; import { EventTimeline } from "./models/event-timeline"; @@ -414,7 +414,7 @@ export class SyncApi { // FIXME: Mostly duplicated from injectRoomEvents but not entirely // because "state" in this API is at the BEGINNING of the chunk - const oldStateEvents = utils.deepCopy(response.state).map(client.getEventMapper()); + const oldStateEvents = deepCopy(response.state).map(client.getEventMapper()); const stateEvents = response.state.map(client.getEventMapper()); const messages = response.messages.chunk.map(client.getEventMapper()); @@ -1247,7 +1247,7 @@ export class SyncApi { this.notifEvents = []; // Handle invites - await utils.promiseMapSeries(inviteRooms, async (inviteObj) => { + await promiseMapSeries(inviteRooms, async (inviteObj) => { const room = inviteObj.room; const stateEvents = this.mapSyncEventsFormat(inviteObj.invite_state, room); @@ -1288,7 +1288,7 @@ export class SyncApi { }); // Handle joins - await utils.promiseMapSeries(joinRooms, async (joinObj) => { + await promiseMapSeries(joinRooms, async (joinObj) => { const room = joinObj.room; const stateEvents = this.mapSyncEventsFormat(joinObj.state, room); // Prevent events from being decrypted ahead of time @@ -1471,7 +1471,7 @@ export class SyncApi { }); // Handle leaves (e.g. kicked rooms) - await utils.promiseMapSeries(leaveRooms, async (leaveObj) => { + await promiseMapSeries(leaveRooms, async (leaveObj) => { const room = leaveObj.room; const stateEvents = this.mapSyncEventsFormat(leaveObj.state, room); const events = this.mapSyncEventsFormat(leaveObj.timeline, room); @@ -1552,7 +1552,7 @@ export class SyncApi { this.pokeKeepAlive(); } if (!this.connectionReturnedDefer) { - this.connectionReturnedDefer = utils.defer(); + this.connectionReturnedDefer = defer(); } return this.connectionReturnedDefer.promise; } diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index df2a2258764..1e42c31432e 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -25,7 +25,7 @@ import { v4 as uuidv4 } from "uuid"; import { parse as parseSdp, write as writeSdp } from "sdp-transform"; import { logger } from "../logger"; -import * as utils from "../utils"; +import { checkObjectHasKeys, isNullOrUndefined, recursivelyAssign } from "../utils"; import { IContent, MatrixEvent } from "../models/event"; import { EventType, ToDeviceMessageId } from "../@types/event"; import { RoomMember } from "../models/room-member"; @@ -453,7 +453,7 @@ export class MatrixCall extends TypedEventEmitter Date: Mon, 15 May 2023 10:14:35 +0100 Subject: [PATCH 25/94] Fix typedoc release documentation deployment (#3358) * Prune typedoc docs before generating new ones * Only maintain 10 major versions * Switch to deploy mechanism which doesn't mangle symlinks * Convert absolute symlinks to relative --- .github/workflows/release.yml | 24 ++++++++++++++++------- package.json | 1 + yarn.lock | 36 +++++++++++++++++++++++++++++++---- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2af82c5776c..8bf9a6489fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,15 +25,25 @@ jobs: - name: 🔨 Install dependencies run: "yarn install --frozen-lockfile" - - name: 📖 Generate JSDoc - run: "yarn gendoc" + - name: 🔨 Install symlinks + run: | + sudo apt-get update + sudo apt-get install -y symlinks + + - name: 📖 Generate docs + run: | + yarn tpv --yes --out _docs --stale --major 10 + yarn gendoc + symlinks -rc _docs - name: 🚀 Deploy - uses: peaceiris/actions-gh-pages@373f7f263a76c20808c831209c920827a82a2847 # v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - keep_files: true - publish_dir: _docs + run: | + git config --global user.email "releases@riot.im" + git config --global user.name "RiotRobot" + git add . --all + git commit -m "Update docs" + git push + working-directory: _docs npm: name: Publish diff --git a/package.json b/package.json index d2a7b3eaed9..341ab850bc6 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "typedoc-plugin-mdn-links": "^3.0.3", "typedoc-plugin-missing-exports": "^2.0.0", "typedoc-plugin-versions": "^0.2.3", + "typedoc-plugin-versions-cli": "^0.1.12", "typescript": "^5.0.0" }, "@casualbot/jest-sonar-reporter": { diff --git a/yarn.lock b/yarn.lock index 664e71663d8..07a9e8e7010 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1433,7 +1433,6 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" - uid acd96c00a881d0f462e1f97a56c73742c8dbc984 resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984" "@microsoft/tsdoc-config@0.16.2": @@ -2223,6 +2222,11 @@ ast-types@^0.14.2: dependencies: tslib "^2.0.1" +async@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2717,7 +2721,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^2.0.0: +chalk@^2.0.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2804,6 +2808,14 @@ cli-color@^2.0.0: memoizee "^0.4.15" timers-ext "^0.1.7" +cli-diff@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-diff/-/cli-diff-1.0.0.tgz#c56f1fa17849a629bf07154a2bd199aefe742964" + integrity sha512-XOVrll4VMhxBv26WqV6OH9cWqRxBXthh3uZ3dtg+CLqB8m0R6QJiSoDIXQNXDAeo/FAkQ+kF9Ph8NhQskU3LpQ== + dependencies: + chalk "^2.4.1" + diff "^3.5.0" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -3226,6 +3238,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -6163,7 +6180,7 @@ promise@^7.0.1: dependencies: asap "~2.0.3" -prompts@^2.0.1: +prompts@^2.0.1, prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -7405,6 +7422,17 @@ typedoc-plugin-missing-exports@^2.0.0: resolved "https://registry.yarnpkg.com/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-2.0.0.tgz#9bdc4e30b0c7f24e9f1cb8890db4d01f608717c5" integrity sha512-t0QlKCm27/8DaheJkLo/gInSNjzBXgSciGhoLpL6sLyXZibm7SuwJtHvg4qXI2IjJfFBgW9mJvvszpoxMyB0TA== +typedoc-plugin-versions-cli@^0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/typedoc-plugin-versions-cli/-/typedoc-plugin-versions-cli-0.1.12.tgz#3e20c4e4078d8aec827dc3cc4686069d6e3c8ab2" + integrity sha512-tmGXo8T6gGW3hajMh+cZTRo50w6JJyOuCWBALGxZM0TOaRL4n0J3SanO2vFUrVd25QR/O5/1pdnTKW1ldcXmXg== + dependencies: + async "^3.2.4" + cli-diff "^1.0.0" + prompts "^2.4.2" + semver "^7.3.7" + yargs "^17.5.1" + typedoc-plugin-versions@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/typedoc-plugin-versions/-/typedoc-plugin-versions-0.2.3.tgz#2cae4d722e45c3d9ab1a7640349c970dfc880f86" @@ -7932,7 +7960,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.1, yargs@^17.3.1: +yargs@^17.0.1, yargs@^17.3.1, yargs@^17.5.1: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From af38021d282641d0ec0cb7e6d294f404f554bed9 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Mon, 15 May 2023 11:37:35 +0100 Subject: [PATCH 26/94] Deprecate device methods in MatrixClient (#3357) --- src/client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/client.ts b/src/client.ts index a037a5e4f55..41bd2fa5d77 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2294,6 +2294,8 @@ export class MatrixClient extends TypedEventEmitterdeviceId-\>`DeviceInfo` + * + * @deprecated Prefer {@link CryptoApi.getUserDeviceInfo} */ public downloadKeys(userIds: string[], forceDownload?: boolean): Promise { if (!this.crypto) { @@ -2308,6 +2310,7 @@ export class MatrixClient extends TypedEventEmitter Date: Mon, 15 May 2023 16:38:43 +0200 Subject: [PATCH 27/94] Check permission on mute mic, only if no audio track exists. (#3359) * check permission only if no audio track * fix linter issues * add missing tests for perfect negotiation pattern * add null case in unit tests for audio muting * fix issue with type MediaStream * force right type of mock methode * format code --- spec/unit/webrtc/call.spec.ts | 107 +++++++++++++++++++++++++++++ spec/unit/webrtc/groupCall.spec.ts | 33 ++++++++- src/webrtc/callFeed.ts | 2 +- src/webrtc/groupCall.ts | 60 ++++++++++------ 4 files changed, 177 insertions(+), 25 deletions(-) diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 8fb2f3be435..965f41eae43 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -25,6 +25,7 @@ import { CallType, CallState, CallParty, + CallDirection, } from "../../../src/webrtc/call"; import { MCallAnswer, @@ -1712,4 +1713,110 @@ describe("Call", function () { expect(onReplace).toHaveBeenCalled(); }); }); + describe("should handle glare in negotiation process", () => { + beforeEach(async () => { + // cut methods not want to test + call.hangup = () => null; + call.isLocalOnHold = () => true; + // @ts-ignore + call.updateRemoteSDPStreamMetadata = jest.fn(); + // @ts-ignore + call.getRidOfRTXCodecs = jest.fn(); + // @ts-ignore + call.createAnswer = jest.fn().mockResolvedValue({}); + // @ts-ignore + call.sendVoipEvent = jest.fn(); + }); + + it("and reject remote offer if not polite and have pending local offer", async () => { + // not polite user == CallDirection.Outbound + call.direction = CallDirection.Outbound; + // have already a local offer + // @ts-ignore + call.makingOffer = true; + const offerEvent = makeMockEvent("@test:foo", { + description: { + type: "offer", + sdp: DUMMY_SDP, + }, + }); + // @ts-ignore + call.peerConn = { + signalingState: "have-local-offer", + setRemoteDescription: jest.fn(), + }; + await call.onNegotiateReceived(offerEvent); + expect(call.peerConn?.setRemoteDescription).not.toHaveBeenCalled(); + }); + + it("and not reject remote offer if not polite and do have pending answer", async () => { + // not polite user == CallDirection.Outbound + call.direction = CallDirection.Outbound; + // have not a local offer + // @ts-ignore + call.makingOffer = false; + + // If we have a setRemoteDescription() answer operation pending, then + // we will be "stable" by the time the next setRemoteDescription() is + // executed, so we count this being readyForOffer when deciding whether to + // ignore the offer. + // @ts-ignore + call.isSettingRemoteAnswerPending = true; + const offerEvent = makeMockEvent("@test:foo", { + description: { + type: "offer", + sdp: DUMMY_SDP, + }, + }); + // @ts-ignore + call.peerConn = { + signalingState: "have-local-offer", + setRemoteDescription: jest.fn(), + }; + await call.onNegotiateReceived(offerEvent); + expect(call.peerConn?.setRemoteDescription).toHaveBeenCalled(); + }); + + it("and not reject remote offer if not polite and do not have pending local offer", async () => { + // not polite user == CallDirection.Outbound + call.direction = CallDirection.Outbound; + // have no local offer + // @ts-ignore + call.makingOffer = false; + const offerEvent = makeMockEvent("@test:foo", { + description: { + type: "offer", + sdp: DUMMY_SDP, + }, + }); + // @ts-ignore + call.peerConn = { + signalingState: "stable", + setRemoteDescription: jest.fn(), + }; + await call.onNegotiateReceived(offerEvent); + expect(call.peerConn?.setRemoteDescription).toHaveBeenCalled(); + }); + + it("and if polite do rollback pending local offer", async () => { + // polite user == CallDirection.Inbound + call.direction = CallDirection.Inbound; + // have already a local offer + // @ts-ignore + call.makingOffer = true; + const offerEvent = makeMockEvent("@test:foo", { + description: { + type: "offer", + sdp: DUMMY_SDP, + }, + }); + // @ts-ignore + call.peerConn = { + signalingState: "have-local-offer", + setRemoteDescription: jest.fn(), + }; + await call.onNegotiateReceived(offerEvent); + expect(call.peerConn?.setRemoteDescription).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index 4d839827b1b..d2023236749 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -517,8 +517,7 @@ describe("Group Call", function () { await groupCall.setMicrophoneMuted(false); expect(groupCall.isMicrophoneMuted()).toEqual(false); - jest.advanceTimersByTime(groupCall.pttMaxTransmitTime + 100); - + await jest.advanceTimersByTimeAsync(groupCall.pttMaxTransmitTime + 100); expect(groupCall.isMicrophoneMuted()).toEqual(true); }); @@ -585,7 +584,15 @@ describe("Group Call", function () { }); mockCall.sendMetadataUpdate = jest.fn().mockReturnValue(metadataUpdatePromise); + const getUserMediaStreamFlush = Promise.resolve("stream"); + // @ts-ignore + mockCall.cleint = { + getMediaHandler: { + getUserMediaStream: jest.fn().mockReturnValue(getUserMediaStreamFlush), + }, + }; const mutePromise = groupCall.setMicrophoneMuted(true); + await getUserMediaStreamFlush; // we should be muted at this point, before the metadata update has been sent expect(groupCall.isMicrophoneMuted()).toEqual(true); expect(mockCall.localUsermediaFeed.setAudioVideoMuted).toHaveBeenCalled(); @@ -892,14 +899,34 @@ describe("Group Call", function () { expect(await groupCall.setMicrophoneMuted(false)).toBe(false); }); - it("returns false when no permission for audio stream", async () => { + it("returns false when no permission for audio stream and localCallFeed do not have an audio track", async () => { const groupCall = await createAndEnterGroupCall(mockClient, room); + // @ts-ignore + jest.spyOn(groupCall.localCallFeed, "hasAudioTrack", "get").mockReturnValue(false); jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream").mockRejectedValueOnce( new Error("No Permission"), ); expect(await groupCall.setMicrophoneMuted(false)).toBe(false); }); + it("returns false when user media stream null", async () => { + const groupCall = await createAndEnterGroupCall(mockClient, room); + // @ts-ignore + jest.spyOn(groupCall.localCallFeed, "hasAudioTrack", "get").mockReturnValue(false); + // @ts-ignore + jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream").mockResolvedValue({} as MediaStream); + expect(await groupCall.setMicrophoneMuted(false)).toBe(false); + }); + + it("returns true when no permission for audio stream but localCallFeed has a audio track already", async () => { + const groupCall = await createAndEnterGroupCall(mockClient, room); + // @ts-ignore + jest.spyOn(groupCall.localCallFeed, "hasAudioTrack", "get").mockReturnValue(true); + jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream"); + expect(mockClient.getMediaHandler().getUserMediaStream).not.toHaveBeenCalled(); + expect(await groupCall.setMicrophoneMuted(false)).toBe(true); + }); + it("returns false when unmuting video with no video device", async () => { const groupCall = await createAndEnterGroupCall(mockClient, room); jest.spyOn(mockClient.getMediaHandler(), "hasVideoDevice").mockResolvedValue(false); diff --git a/src/webrtc/callFeed.ts b/src/webrtc/callFeed.ts index 07c906e6796..a9cf7a7a973 100644 --- a/src/webrtc/callFeed.ts +++ b/src/webrtc/callFeed.ts @@ -128,7 +128,7 @@ export class CallFeed extends TypedEventEmitter this.emit(CallFeedEvent.ConnectedChanged, this.connected); } - private get hasAudioTrack(): boolean { + public get hasAudioTrack(): boolean { return this.stream.getAudioTracks().length > 0; } diff --git a/src/webrtc/groupCall.ts b/src/webrtc/groupCall.ts index a6a629576f4..6d98586c92b 100644 --- a/src/webrtc/groupCall.ts +++ b/src/webrtc/groupCall.ts @@ -655,27 +655,9 @@ export class GroupCall extends TypedEventEmitter< `GroupCall ${this.groupCallId} setMicrophoneMuted() (streamId=${this.localCallFeed.stream.id}, muted=${muted})`, ); - // We needed this here to avoid an error in case user join a call without a device. - // I can not use .then .catch functions because linter :-( - try { - if (!muted) { - const stream = await this.client - .getMediaHandler() - .getUserMediaStream(true, !this.localCallFeed.isVideoMuted()); - if (stream === null) { - // if case permission denied to get a stream stop this here - /* istanbul ignore next */ - logger.log( - `GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`, - ); - return false; - } - } - } catch (e) { - /* istanbul ignore next */ - logger.log( - `GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`, - ); + const hasPermission = await this.checkAudioPermissionIfNecessary(muted); + + if (!hasPermission) { return false; } @@ -700,6 +682,42 @@ export class GroupCall extends TypedEventEmitter< return true; } + /** + * If we allow entering a call without a camera and without video, it can happen that the access rights to the + * devices have not yet been queried. If a stream does not yet have an audio track, we assume that the rights have + * not yet been checked. + * + * `this.client.getMediaHandler().getUserMediaStream` clones the current stream, so it only wanted to be called when + * not Audio Track exists. + * As such, this is a compromise, because, the access rights should always be queried before the call. + */ + private async checkAudioPermissionIfNecessary(muted: boolean): Promise { + // We needed this here to avoid an error in case user join a call without a device. + try { + if (!muted && this.localCallFeed && !this.localCallFeed.hasAudioTrack) { + const stream = await this.client + .getMediaHandler() + .getUserMediaStream(true, !this.localCallFeed.isVideoMuted()); + if (stream?.getTracks().length === 0) { + // if case permission denied to get a stream stop this here + /* istanbul ignore next */ + logger.log( + `GroupCall ${this.groupCallId} setMicrophoneMuted() no device to receive local stream, muted=${muted}`, + ); + return false; + } + } + } catch (e) { + /* istanbul ignore next */ + logger.log( + `GroupCall ${this.groupCallId} setMicrophoneMuted() no device or permission to receive local stream, muted=${muted}`, + ); + return false; + } + + return true; + } + /** * Sets the mute state of the local participants's video. * @param muted - Whether to mute the video From 72f3c360b62c469123deccddaeff21eb645c1e4e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Mon, 15 May 2023 18:46:33 +0100 Subject: [PATCH 28/94] Add `CryptoApi.getCrossSigningKeyId` (#3360) --- spec/unit/rust-crypto/rust-crypto.spec.ts | 5 +++++ src/client.ts | 5 ++--- src/crypto-api.ts | 17 +++++++++++++++++ src/crypto/api.ts | 7 +------ src/crypto/index.ts | 22 ++++++++++++++++------ src/rust-crypto/rust-crypto.ts | 9 +++++++++ 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index 1297003b74f..a0c9a47763f 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -98,6 +98,11 @@ describe("RustCrypto", () => { await expect(rustCrypto.isCrossSigningReady()).resolves.toBe(false); }); + it("getCrossSigningKeyId", async () => { + const rustCrypto = await makeTestRustCrypto(); + await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null); + }); + it("bootstrapCrossSigning", async () => { const rustCrypto = await makeTestRustCrypto(); await rustCrypto.bootstrapCrossSigning({}); diff --git a/src/client.ts b/src/client.ts index 41bd2fa5d77..20bb0935144 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2571,14 +2571,13 @@ export class MatrixClient extends TypedEventEmitter; + /** + * Get the ID of one of the user's cross-signing keys. + * + * @param type - The type of key to get the ID of. One of `CrossSigningKey.Master`, `CrossSigngingKey.SelfSigning`, + * or `CrossSigningKey.UserSigning`. Defaults to `CrossSigningKey.Master`. + * + * @returns If cross-signing has been initialised on this device, the ID of the given key. Otherwise, null + */ + getCrossSigningKeyId(type?: CrossSigningKey): Promise; + /** * Bootstrap cross-signing by creating keys if needed. * diff --git a/src/crypto/api.ts b/src/crypto/api.ts index 571c3df78d3..97ead425dab 100644 --- a/src/crypto/api.ts +++ b/src/crypto/api.ts @@ -19,6 +19,7 @@ import { IKeyBackupInfo } from "./keybackup"; import type { AddSecretStorageKeyOpts } from "../secret-storage"; /* re-exports for backwards compatibility. */ +export { CrossSigningKey } from "../crypto-api"; export type { AddSecretStorageKeyOpts as IAddSecretStorageKeyOpts, PassphraseInfo as IPassphraseInfo, @@ -27,12 +28,6 @@ export type { // TODO: Merge this with crypto.js once converted -export enum CrossSigningKey { - Master = "master", - SelfSigning = "self_signing", - UserSigning = "user_signing", -} - export interface IEncryptedEventInfo { /** * whether the event is encrypted (if not encrypted, some of the other properties may not be set) diff --git a/src/crypto/index.ts b/src/crypto/index.ts index 234bb2737ab..1e847152b14 100644 --- a/src/crypto/index.ts +++ b/src/crypto/index.ts @@ -35,7 +35,13 @@ import * as algorithms from "./algorithms"; import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning"; import { EncryptionSetupBuilder } from "./EncryptionSetup"; import { SecretStorage as LegacySecretStorage } from "./SecretStorage"; -import { ICreateSecretStorageOpts, IEncryptedEventInfo, IImportRoomKeysOpts, IRecoveryKey } from "./api"; +import { + CrossSigningKey, + ICreateSecretStorageOpts, + IEncryptedEventInfo, + IImportRoomKeysOpts, + IRecoveryKey, +} from "./api"; import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store"; import { VerificationBase } from "./verification/Base"; @@ -45,7 +51,7 @@ import { keyFromPassphrase } from "./key_passphrase"; import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey"; import { VerificationRequest } from "./verification/request/VerificationRequest"; import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel"; -import { ToDeviceChannel, ToDeviceRequests, Request } from "./verification/request/ToDeviceChannel"; +import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel"; import { IllegalMethod } from "./verification/IllegalMethod"; import { KeySignatureUploadError } from "../errors"; import { calculateKeyCheck, decryptAES, encryptAES } from "./aes"; @@ -54,7 +60,7 @@ import { BackupManager } from "./backup"; import { IStore } from "../store"; import { Room, RoomEvent } from "../models/room"; import { RoomMember, RoomMemberEvent } from "../models/room-member"; -import { EventStatus, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event"; +import { EventStatus, IContent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event"; import { ToDeviceBatch } from "../models/ToDeviceMessage"; import { ClientEvent, @@ -70,7 +76,6 @@ import { ISyncStateData } from "../sync"; import { CryptoStore } from "./store/base"; import { IVerificationChannel } from "./verification/request/Channel"; import { TypedEventEmitter } from "../models/typed-event-emitter"; -import { IContent } from "../models/event"; import { IDeviceLists, ISyncResponse, IToDeviceEvent } from "../sync-accumulator"; import { ISignatures } from "../@types/signed"; import { IMessage } from "./algorithms/olm"; @@ -80,11 +85,11 @@ import { MapWithDefault, recursiveMapToObject } from "../utils"; import { AccountDataClient, AddSecretStorageKeyOpts, + SECRET_STORAGE_ALGORITHM_V1_AES, + SecretStorageCallbacks, SecretStorageKeyDescription, SecretStorageKeyObject, SecretStorageKeyTuple, - SECRET_STORAGE_ALGORITHM_V1_AES, - SecretStorageCallbacks, ServerSideSecretStorageImpl, } from "../secret-storage"; import { ISecretRequest } from "./SecretSharing"; @@ -1415,6 +1420,11 @@ export class Crypto extends TypedEventEmitter { + return Promise.resolve(this.getCrossSigningId(type)); + } + + // old name, for backwards compatibility public getCrossSigningId(type: string): string | null { return this.crossSigningInfo.getId(type); } diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index d92b7a9ca74..d96c916e477 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -35,6 +35,7 @@ import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter" import { IDownloadKeyResult, IQueryKeysRequest } from "../client"; import { Device, DeviceMap } from "../models/device"; import { ServerSideSecretStorage } from "../secret-storage"; +import { CrossSigningKey } from "../crypto/api"; /** * An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto. @@ -324,6 +325,14 @@ export class RustCrypto implements CryptoBackend { return false; } + /** + * Implementation of {@link CryptoApi#getCrossSigningKeyId} + */ + public async getCrossSigningKeyId(type: CrossSigningKey = CrossSigningKey.Master): Promise { + // TODO + return null; + } + /** * Implementation of {@link CryptoApi#boostrapCrossSigning} */ From 43160094019adb6f62d4eb16f8d16069e89524c1 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Mon, 15 May 2023 20:14:05 +0100 Subject: [PATCH 29/94] Element-R: support for SigningKeysUploadRequest (#3365) * OutgoingRequestProcessor: support for SigningKeysUploadRequest * Tests * Bump matrix-org/matrix-sdk-crypto-js ... to pick up bug fixes for outgoing requests --- package.json | 2 +- .../OutgoingRequestProcessor.spec.ts | 43 ++++++++++++++++++- src/rust-crypto/OutgoingRequestProcessor.ts | 38 +++++++++++++++- yarn.lock | 8 ++-- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 341ab850bc6..02a5c4cee04 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ ], "dependencies": { "@babel/runtime": "^7.12.5", - "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.8", + "@matrix-org/matrix-sdk-crypto-js": "^0.1.0-alpha.9", "another-json": "^0.2.0", "bs58": "^5.0.0", "content-type": "^1.0.4", diff --git a/spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts b/spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts index 28d1e9b3508..bb1cdff6ab1 100644 --- a/spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts +++ b/spec/unit/rust-crypto/OutgoingRequestProcessor.spec.ts @@ -24,11 +24,12 @@ import { KeysUploadRequest, RoomMessageRequest, SignatureUploadRequest, + SigningKeysUploadRequest, ToDeviceRequest, } from "@matrix-org/matrix-sdk-crypto-js"; import { TypedEventEmitter } from "../../../src/models/typed-event-emitter"; -import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi } from "../../../src"; +import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi, UIAuthCallback } from "../../../src"; import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; describe("OutgoingRequestProcessor", () => { @@ -80,6 +81,12 @@ describe("OutgoingRequestProcessor", () => { "https://example.com/_matrix/client/v3/keys/signatures/upload", ], ["KeysBackupRequest", KeysBackupRequest, "PUT", "https://example.com/_matrix/client/v3/room_keys/keys"], + [ + "SigningKeysUploadRequest", + SigningKeysUploadRequest, + "POST", + "https://example.com/_matrix/client/v3/keys/device_signing/upload", + ], ]; test.each(tests)(`should handle %ss`, async (_, RequestClass, expectedMethod, expectedPath) => { @@ -171,6 +178,40 @@ describe("OutgoingRequestProcessor", () => { httpBackend.verifyNoOutstandingRequests(); }); + it("should handle SigningKeysUploadRequests with UIA", async () => { + // first, mock up a request as we might expect to receive it from the Rust layer ... + const testReq = { foo: "bar" }; + const outgoingRequest = new SigningKeysUploadRequest("1234", JSON.stringify(testReq)); + + // also create a UIA callback + const authCallback: UIAuthCallback = async (makeRequest) => { + return await makeRequest({ type: "test" }); + }; + + // ... then poke the request into the OutgoingRequestProcessor under test + const reqProm = processor.makeOutgoingRequest(outgoingRequest, authCallback); + + // Now: check that it makes a matching HTTP request ... + const testResponse = '{"result":1}'; + httpBackend + .when("POST", "/_matrix") + .check((req) => { + expect(req.path).toEqual("https://example.com/_matrix/client/v3/keys/device_signing/upload"); + expect(JSON.parse(req.rawData)).toEqual({ foo: "bar", auth: { type: "test" } }); + expect(req.headers["Accept"]).toEqual("application/json"); + expect(req.headers["Content-Type"]).toEqual("application/json"); + }) + .respond(200, testResponse, true); + + // ... and that it calls OlmMachine.markAsSent. + const markSentCallPromise = awaitCallToMarkAsSent(); + await httpBackend.flushAllExpected(); + + await Promise.all([reqProm, markSentCallPromise]); + expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse); + httpBackend.verifyNoOutstandingRequests(); + }); + it("does not explode with unknown requests", async () => { const outgoingRequest = { id: "5678", type: 987 }; const markSentCallPromise = awaitCallToMarkAsSent(); diff --git a/src/rust-crypto/OutgoingRequestProcessor.ts b/src/rust-crypto/OutgoingRequestProcessor.ts index 7ac9a2105db..4e98bce8d49 100644 --- a/src/rust-crypto/OutgoingRequestProcessor.ts +++ b/src/rust-crypto/OutgoingRequestProcessor.ts @@ -23,11 +23,14 @@ import { RoomMessageRequest, SignatureUploadRequest, ToDeviceRequest, + SigningKeysUploadRequest, } from "@matrix-org/matrix-sdk-crypto-js"; import { logger } from "../logger"; import { IHttpOpts, MatrixHttpApi, Method } from "../http-api"; import { QueryDict } from "../utils"; +import { IAuthDict, UIAuthCallback } from "../interactive-auth"; +import { UIAResponse } from "../@types/uia"; /** * Common interface for all the request types returned by `OlmMachine.outgoingRequests`. @@ -53,7 +56,7 @@ export class OutgoingRequestProcessor { private readonly http: MatrixHttpApi, ) {} - public async makeOutgoingRequest(msg: OutgoingRequest): Promise { + public async makeOutgoingRequest(msg: OutgoingRequest, uiaCallback?: UIAuthCallback): Promise { let resp: string; /* refer https://docs.rs/matrix-sdk-crypto/0.6.0/matrix_sdk_crypto/requests/enum.OutgoingRequests.html @@ -79,6 +82,14 @@ export class OutgoingRequestProcessor { `/_matrix/client/v3/room/${encodeURIComponent(msg.room_id)}/send/` + `${encodeURIComponent(msg.event_type)}/${encodeURIComponent(msg.txn_id)}`; resp = await this.rawJsonRequest(Method.Put, path, {}, msg.body); + } else if (msg instanceof SigningKeysUploadRequest) { + resp = await this.makeRequestWithUIA( + Method.Post, + "/_matrix/client/v3/keys/device_signing/upload", + {}, + msg.body, + uiaCallback, + ); } else { logger.warn("Unsupported outgoing message", Object.getPrototypeOf(msg)); resp = ""; @@ -89,6 +100,31 @@ export class OutgoingRequestProcessor { } } + private async makeRequestWithUIA( + method: Method, + path: string, + queryParams: QueryDict, + body: string, + uiaCallback: UIAuthCallback | undefined, + ): Promise { + if (!uiaCallback) { + return await this.rawJsonRequest(method, path, queryParams, body); + } + + const parsedBody = JSON.parse(body); + const makeRequest = async (auth: IAuthDict): Promise> => { + const newBody = { + ...parsedBody, + auth, + }; + const resp = await this.rawJsonRequest(method, path, queryParams, JSON.stringify(newBody)); + return JSON.parse(resp) as T; + }; + + const resp = await uiaCallback(makeRequest); + return JSON.stringify(resp); + } + private async rawJsonRequest(method: Method, path: string, queryParams: QueryDict, body: string): Promise { const opts = { // inhibit the JSON stringification and parsing within HttpApi. diff --git a/yarn.lock b/yarn.lock index 07a9e8e7010..7f091782348 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1426,10 +1426,10 @@ dependencies: lodash "^4.17.21" -"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.8": - version "0.1.0-alpha.8" - resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.8.tgz#18dd8e7fb56602d2999d8a502b49e902a2bb3782" - integrity sha512-hdmbbGXKrN6JNo3wdBaR5Zs3lXlzllT3U43ViNTlabB3nKkOZQnEAN/Isv+4EQSgz1+8897veI9Q8sqlQX22oA== +"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.9": + version "0.1.0-alpha.9" + resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.9.tgz#00bc266781502641a661858a5a521dd4d95275fc" + integrity sha512-g5cjpFwA9h0CbEGoAqNVI2QcyDsbI8FHoLo9+OXWHIezEKITsSv78mc5ilIwN+2YpmVlH0KNeQWTHw4vi0BMnw== "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" From 083b4cb17eca7ac94e9ce0b988387a6df2a3f866 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 May 2023 09:10:31 +0100 Subject: [PATCH 30/94] Prepare changelog for v25.1.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84bbdabc70..1e8005168f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [25.1.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.1.1) (2023-05-16) +================================================================================================== + +## 🐛 Bug Fixes + * Rebuild to fix packaging glitch in 25.1.0. Fixes #3363 + Changes in [25.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.1.0) (2023-05-09) ================================================================================================== From 077da23d084870b79c2014bb7e2ee8ec11810bf9 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 May 2023 09:10:34 +0100 Subject: [PATCH 31/94] v25.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index af1c9c71e1e..99dccfedfed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.1.0", + "version": "25.1.1", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From 18722d0031b13590d804ad82c73f97118fada902 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 16 May 2023 11:34:06 +0100 Subject: [PATCH 32/94] correctly accumulate sync summaries. if a sync summary for (say) invited_member_count goes from 1 to 0, it should be accumluated as 0, rather than 1. Should fix https://github.com/vector-im/element-web/issues/23345 --- spec/unit/sync-accumulator.spec.ts | 17 +++++++++++++++++ src/sync-accumulator.ts | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/spec/unit/sync-accumulator.spec.ts b/spec/unit/sync-accumulator.spec.ts index d434e0145ac..cad31d7a325 100644 --- a/spec/unit/sync-accumulator.spec.ts +++ b/spec/unit/sync-accumulator.spec.ts @@ -545,6 +545,23 @@ describe("SyncAccumulator", function () { expect(summary["m.heroes"]).toEqual(["@bob:bar"]); }); + it("should reset summary properties", function () { + sa.accumulate( + createSyncResponseWithSummary({ + "m.heroes": ["@alice:bar"], + "m.invited_member_count": 2, + }), + ); + sa.accumulate( + createSyncResponseWithSummary({ + "m.heroes": ["@alice:bar"], + "m.invited_member_count": 0, + }), + ); + const summary = sa.getJSON().roomsData.join["!foo:bar"].summary; + expect(summary["m.invited_member_count"]).toEqual(0); + }); + it("should return correctly adjusted age attributes", () => { const delta = 1000; const startingTs = 1000; diff --git a/src/sync-accumulator.ts b/src/sync-accumulator.ts index ae6ef13b79b..e25ace53039 100644 --- a/src/sync-accumulator.ts +++ b/src/sync-accumulator.ts @@ -399,9 +399,9 @@ export class SyncAccumulator { const acc = currentData._summary; const sum = data.summary; - acc[HEROES_KEY] = sum[HEROES_KEY] || acc[HEROES_KEY]; - acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] || acc[JOINED_COUNT_KEY]; - acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] || acc[INVITED_COUNT_KEY]; + acc[HEROES_KEY] = sum[HEROES_KEY] ?? acc[HEROES_KEY]; + acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] ?? acc[JOINED_COUNT_KEY]; + acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] ?? acc[INVITED_COUNT_KEY]; } // We purposefully do not persist m.typing events. From d459a91af3932c9c3326f2d555c0d36421776868 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 16 May 2023 11:41:35 +0100 Subject: [PATCH 33/94] correctly accumulate sync summaries. (#3366) if a sync summary for (say) invited_member_count goes from 1 to 0, it should be accumluated as 0, rather than 1. Should fix https://github.com/vector-im/element-web/issues/23345 --- spec/unit/sync-accumulator.spec.ts | 17 +++++++++++++++++ src/sync-accumulator.ts | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/spec/unit/sync-accumulator.spec.ts b/spec/unit/sync-accumulator.spec.ts index d434e0145ac..cad31d7a325 100644 --- a/spec/unit/sync-accumulator.spec.ts +++ b/spec/unit/sync-accumulator.spec.ts @@ -545,6 +545,23 @@ describe("SyncAccumulator", function () { expect(summary["m.heroes"]).toEqual(["@bob:bar"]); }); + it("should reset summary properties", function () { + sa.accumulate( + createSyncResponseWithSummary({ + "m.heroes": ["@alice:bar"], + "m.invited_member_count": 2, + }), + ); + sa.accumulate( + createSyncResponseWithSummary({ + "m.heroes": ["@alice:bar"], + "m.invited_member_count": 0, + }), + ); + const summary = sa.getJSON().roomsData.join["!foo:bar"].summary; + expect(summary["m.invited_member_count"]).toEqual(0); + }); + it("should return correctly adjusted age attributes", () => { const delta = 1000; const startingTs = 1000; diff --git a/src/sync-accumulator.ts b/src/sync-accumulator.ts index ae6ef13b79b..e25ace53039 100644 --- a/src/sync-accumulator.ts +++ b/src/sync-accumulator.ts @@ -399,9 +399,9 @@ export class SyncAccumulator { const acc = currentData._summary; const sum = data.summary; - acc[HEROES_KEY] = sum[HEROES_KEY] || acc[HEROES_KEY]; - acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] || acc[JOINED_COUNT_KEY]; - acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] || acc[INVITED_COUNT_KEY]; + acc[HEROES_KEY] = sum[HEROES_KEY] ?? acc[HEROES_KEY]; + acc[JOINED_COUNT_KEY] = sum[JOINED_COUNT_KEY] ?? acc[JOINED_COUNT_KEY]; + acc[INVITED_COUNT_KEY] = sum[INVITED_COUNT_KEY] ?? acc[INVITED_COUNT_KEY]; } // We purposefully do not persist m.typing events. From 0c5eb277e4ba2f16c6c8e46c8d5f0fd9dab306dc Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 16 May 2023 12:05:56 +0100 Subject: [PATCH 34/94] incorporate andy review --- spec/unit/sync-accumulator.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/unit/sync-accumulator.spec.ts b/spec/unit/sync-accumulator.spec.ts index cad31d7a325..e257aa960c3 100644 --- a/spec/unit/sync-accumulator.spec.ts +++ b/spec/unit/sync-accumulator.spec.ts @@ -545,7 +545,8 @@ describe("SyncAccumulator", function () { expect(summary["m.heroes"]).toEqual(["@bob:bar"]); }); - it("should reset summary properties", function () { + it("should correctly update summary properties to zero", function () { + // When we receive updates of a summary property, the last of which is 0 sa.accumulate( createSyncResponseWithSummary({ "m.heroes": ["@alice:bar"], @@ -559,6 +560,7 @@ describe("SyncAccumulator", function () { }), ); const summary = sa.getJSON().roomsData.join["!foo:bar"].summary; + // Then we give an answer of 0 expect(summary["m.invited_member_count"]).toEqual(0); }); From 52792ec89bc562b77e2e44d2464b7e286bc83e80 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 May 2023 12:52:58 +0100 Subject: [PATCH 35/94] Fix CI failure on develop due to force merged PR and prettier failure (#3369) --- spec/unit/sync-accumulator.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/sync-accumulator.spec.ts b/spec/unit/sync-accumulator.spec.ts index 6932176bb5c..e257aa960c3 100644 --- a/spec/unit/sync-accumulator.spec.ts +++ b/spec/unit/sync-accumulator.spec.ts @@ -546,7 +546,7 @@ describe("SyncAccumulator", function () { }); it("should correctly update summary properties to zero", function () { - // When we receive updates of a summary property, the last of which is 0 + // When we receive updates of a summary property, the last of which is 0 sa.accumulate( createSyncResponseWithSummary({ "m.heroes": ["@alice:bar"], From daf845d7bdb93bb807b7e67fb5d222bf7882b55e Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:36:40 +0100 Subject: [PATCH 36/94] Fix changelog_head.py script to be Python 3 compatible --- scripts/changelog_head.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/changelog_head.py b/scripts/changelog_head.py index c7c1c0aeaa5..dfe54b466d4 100755 --- a/scripts/changelog_head.py +++ b/scripts/changelog_head.py @@ -15,4 +15,4 @@ break found_first_header = True elif not re.match(r"^=+$", line) and len(line) > 0: - print line + print(line) From 0c417b7c32556f4d0e4c2adfbe5cb91912195dcd Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:37:17 +0100 Subject: [PATCH 37/94] Prepare changelog for v25.2.0-rc.1 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e8005168f1..c013129e338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +Changes in [25.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.1) (2023-05-16) +============================================================================================================ + +## 🦖 Deprecations + * Deprecate device methods in MatrixClient ([\#3357](https://github.com/matrix-org/matrix-js-sdk/pull/3357)). + +## ✨ Features + * Total summary count ([\#3351](https://github.com/matrix-org/matrix-js-sdk/pull/3351)). Contributed by @toger5. + * Audio concealment ([\#3349](https://github.com/matrix-org/matrix-js-sdk/pull/3349)). Contributed by @toger5. + +## 🐛 Bug Fixes + * Correctly accumulate sync summaries. ([\#3366](https://github.com/matrix-org/matrix-js-sdk/pull/3366)). Fixes vector-im/element-web#23345. + * Keep measuring a call feed's volume after a stream replacement ([\#3361](https://github.com/matrix-org/matrix-js-sdk/pull/3361)). Fixes vector-im/element-call#1051. + * Element-R: Avoid uploading a new fallback key at every `/sync` ([\#3338](https://github.com/matrix-org/matrix-js-sdk/pull/3338)). Fixes vector-im/element-web#25215. + * Accumulate receipts for the main thread and unthreaded separately ([\#3339](https://github.com/matrix-org/matrix-js-sdk/pull/3339)). Fixes vector-im/element-web#24629. + Changes in [25.1.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.1.1) (2023-05-16) ================================================================================================== From 7574dacdb3c0e65121315c9fa5d2b1235032c8f7 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:37:19 +0100 Subject: [PATCH 38/94] v25.2.0-rc.1 --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f4e7e085c68..f739dcf248a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.1.1", + "version": "25.2.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" @@ -32,8 +32,8 @@ "keywords": [ "matrix-org" ], - "main": "./src/index.ts", - "browser": "./src/browser-index.ts", + "main": "./lib/index.js", + "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.ts", "matrix_lib_main": "./lib/index.js", @@ -155,5 +155,6 @@ "no-rust-crypto": { "src/rust-crypto/index.ts$": "./src/rust-crypto/browserify-index.ts" } - } + }, + "typings": "./lib/index.d.ts" } From cb018dfc8063b2ec4845b49c406ec4e94303d466 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:58:05 +0100 Subject: [PATCH 39/94] Fix tsconfig-build.json --- tsconfig-build.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig-build.json b/tsconfig-build.json index 3108314a4d6..5437c65b2db 100644 --- a/tsconfig-build.json +++ b/tsconfig-build.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "forceConsistentCasingInFileNames": true, "declarationMap": true, "sourceMap": true, "noEmit": false, From 13ee0eb7f570e94726e3446865b5ae5b5517fe3e Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:58:33 +0100 Subject: [PATCH 40/94] Prepare changelog for v25.2.0-rc.2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c013129e338..d55e6bdaad3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Changes in [25.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.1) (2023-05-16) +Changes in [25.2.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.2) (2023-05-16) ============================================================================================================ ## 🦖 Deprecations From 4ca882fcd4325d7180f923ed2596684c3dd1a78a Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 13:58:35 +0100 Subject: [PATCH 41/94] v25.2.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f739dcf248a..a6d44d95b52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.2.0-rc.1", + "version": "25.2.0-rc.2", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From 78637a06898871857d0c81b34c5011bd2deee606 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 May 2023 14:15:27 +0100 Subject: [PATCH 42/94] Fix docs deployment --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8bf9a6489fa..0273854cf29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - name: 📖 Generate docs run: | - yarn tpv --yes --out _docs --stale --major 10 + yarn tpv purge --yes --out _docs --stale --major 10 yarn gendoc symlinks -rc _docs From fc02e550bd9c01d82cbc1bf34ea7c6ea2c34381f Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 14:17:02 +0100 Subject: [PATCH 43/94] Prepare changelog for v25.2.0-rc.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55e6bdaad3..c126333a6a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Changes in [25.2.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.2) (2023-05-16) +Changes in [25.2.0-rc.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.3) (2023-05-16) ============================================================================================================ ## 🦖 Deprecations From 21a10a2d14c9b1b7e4a12e56fd0e175e6b0ba895 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 14:17:04 +0100 Subject: [PATCH 44/94] v25.2.0-rc.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6d44d95b52..499901f7177 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.2.0-rc.2", + "version": "25.2.0-rc.3", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From f15d682938ffbd33381bbc315d9d6b21068d6846 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 14:22:02 +0100 Subject: [PATCH 45/94] Prepare changelog for v25.2.0-rc.4 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c126333a6a4..22c776ccad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Changes in [25.2.0-rc.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.3) (2023-05-16) +Changes in [25.2.0-rc.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.4) (2023-05-16) ============================================================================================================ ## 🦖 Deprecations From 2ec1fa660532bb72d998548b9dd2af617ebf06db Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Tue, 16 May 2023 14:22:04 +0100 Subject: [PATCH 46/94] v25.2.0-rc.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 499901f7177..0dbe23662d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.2.0-rc.3", + "version": "25.2.0-rc.4", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From 8d14d45272dc4b6c33269802d229e6d8feffbbad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:37:05 +0100 Subject: [PATCH 47/94] Update typescript-eslint monorepo to v5.59.6 (#3334) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 95 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7f091782348..7fbd8269d52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1852,14 +1852,14 @@ "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@^5.45.0": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4" - integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg== + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz#a350faef1baa1e961698240f922d8de1761a9e2b" + integrity sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/type-utils" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/type-utils" "5.59.6" + "@typescript-eslint/utils" "5.59.6" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -1868,13 +1868,13 @@ tsutils "^3.21.0" "@typescript-eslint/parser@^5.45.0": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981" - integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw== + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.6.tgz#bd36f71f5a529f828e20b627078d3ed6738dbb40" + integrity sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== dependencies: - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/typescript-estree" "5.59.6" debug "^4.3.4" "@typescript-eslint/scope-manager@5.59.5": @@ -1885,13 +1885,21 @@ "@typescript-eslint/types" "5.59.5" "@typescript-eslint/visitor-keys" "5.59.5" -"@typescript-eslint/type-utils@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" - integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg== +"@typescript-eslint/scope-manager@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" + integrity sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== dependencies: - "@typescript-eslint/typescript-estree" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/visitor-keys" "5.59.6" + +"@typescript-eslint/type-utils@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" + integrity sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== + dependencies: + "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/utils" "5.59.6" debug "^4.3.4" tsutils "^3.21.0" @@ -1900,6 +1908,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== +"@typescript-eslint/types@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" + integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== + "@typescript-eslint/typescript-estree@5.59.5": version "5.59.5" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" @@ -1913,7 +1926,34 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.5", "@typescript-eslint/utils@^5.10.0": +"@typescript-eslint/typescript-estree@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" + integrity sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== + dependencies: + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/visitor-keys" "5.59.6" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" + integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/typescript-estree" "5.59.6" + eslint-scope "^5.1.1" + semver "^7.3.7" + +"@typescript-eslint/utils@^5.10.0": version "5.59.5" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== @@ -1935,6 +1975,14 @@ "@typescript-eslint/types" "5.59.5" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" + integrity sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== + dependencies: + "@typescript-eslint/types" "5.59.6" + eslint-visitor-keys "^3.3.0" + JSONStream@^1.0.3: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -6742,13 +6790,20 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.0: +semver@^7.3.5, semver@^7.3.8, semver@^7.5.0: version "7.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" +semver@^7.3.7: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" From 789458e0d40bad168caa8a7c65895b4470dfac45 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 May 2023 16:05:55 +0100 Subject: [PATCH 48/94] Add methods to terminate idb worker (#3362) --- spec/unit/stores/indexeddb.spec.ts | 27 +++++++++++++++++++++++++++ src/store/index.ts | 5 +++++ src/store/indexeddb-backend.ts | 1 + src/store/indexeddb-local-backend.ts | 7 +++++++ src/store/indexeddb-remote-backend.ts | 7 +++++++ src/store/indexeddb.ts | 7 +++++++ src/store/memory.ts | 4 ++++ src/store/stub.ts | 4 ++++ 8 files changed, 62 insertions(+) diff --git a/spec/unit/stores/indexeddb.spec.ts b/spec/unit/stores/indexeddb.spec.ts index 544a3d75c5b..7f2bc25d458 100644 --- a/spec/unit/stores/indexeddb.spec.ts +++ b/spec/unit/stores/indexeddb.spec.ts @@ -254,4 +254,31 @@ describe("IndexedDBStore", () => { }); await expect(store.startup()).rejects.toThrow("Test"); }); + + it("remote worker should terminate upon destroy call", async () => { + const terminate = jest.fn(); + const worker = new (class MockWorker { + private onmessage!: (data: any) => void; + postMessage(data: any) { + this.onmessage({ + data: { + command: "cmd_success", + seq: data.seq, + result: [], + }, + }); + } + public terminate = terminate; + })() as unknown as Worker; + + const store = new IndexedDBStore({ + indexedDB: indexedDB, + dbName: "database", + localStorage, + workerFactory: () => worker, + }); + await store.startup(); + await expect(store.destroy()).resolves; + expect(terminate).toHaveBeenCalled(); + }); }); diff --git a/src/store/index.ts b/src/store/index.ts index 78741786ac2..ac0a344e852 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -245,4 +245,9 @@ export interface IStore { * Removes a specific batch of to-device messages from the queue */ removeToDeviceBatch(id: number): Promise; + + /** + * Stop the store and perform any appropriate cleanup + */ + destroy(): Promise; } diff --git a/src/store/indexeddb-backend.ts b/src/store/indexeddb-backend.ts index 008867dfc33..c93afb9e705 100644 --- a/src/store/indexeddb-backend.ts +++ b/src/store/indexeddb-backend.ts @@ -35,6 +35,7 @@ export interface IIndexedDBBackend { saveToDeviceBatches(batches: ToDeviceBatchWithTxnId[]): Promise; getOldestToDeviceBatch(): Promise; removeToDeviceBatch(id: number): Promise; + destroy(): Promise; } export type UserTuple = [userId: string, presenceEvent: Partial]; diff --git a/src/store/indexeddb-local-backend.ts b/src/store/indexeddb-local-backend.ts index a82de854e60..3bc5914066a 100644 --- a/src/store/indexeddb-local-backend.ts +++ b/src/store/indexeddb-local-backend.ts @@ -594,4 +594,11 @@ export class LocalIndexedDBStoreBackend implements IIndexedDBBackend { store.delete(id); await txnAsPromise(txn); } + + /* + * Close the database + */ + public async destroy(): Promise { + this.db?.close(); + } } diff --git a/src/store/indexeddb-remote-backend.ts b/src/store/indexeddb-remote-backend.ts index 7e2aa0ccbe9..32ff51efe71 100644 --- a/src/store/indexeddb-remote-backend.ts +++ b/src/store/indexeddb-remote-backend.ts @@ -200,4 +200,11 @@ export class RemoteIndexedDBStoreBackend implements IIndexedDBBackend { logger.warn("Unrecognised message from worker: ", msg); } }; + + /* + * Destroy the web worker + */ + public async destroy(): Promise { + this.worker?.terminate(); + } } diff --git a/src/store/indexeddb.ts b/src/store/indexeddb.ts index cc77bf9c80f..3ca9ea2dca4 100644 --- a/src/store/indexeddb.ts +++ b/src/store/indexeddb.ts @@ -151,6 +151,13 @@ export class IndexedDBStore extends MemoryStore { }); } + /* + * Close the database and destroy any associated workers + */ + public destroy(): Promise { + return this.backend.destroy(); + } + private onClose = (): void => { this.emitter.emit("closed"); }; diff --git a/src/store/memory.ts b/src/store/memory.ts index 091dcd4c8ca..8b560784622 100644 --- a/src/store/memory.ts +++ b/src/store/memory.ts @@ -435,4 +435,8 @@ export class MemoryStore implements IStore { this.pendingToDeviceBatches = this.pendingToDeviceBatches.filter((batch) => batch.id !== id); return Promise.resolve(); } + + public async destroy(): Promise { + // Nothing to do + } } diff --git a/src/store/stub.ts b/src/store/stub.ts index cc128540630..5ea91cf98cc 100644 --- a/src/store/stub.ts +++ b/src/store/stub.ts @@ -266,4 +266,8 @@ export class StubStore implements IStore { public async removeToDeviceBatch(id: number): Promise { return Promise.resolve(); } + + public async destroy(): Promise { + // Nothing to do + } } From d580f56c0b7827beeb6ffb5dc2e161e504a9332f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 14:51:21 +0000 Subject: [PATCH 49/94] Update all non-major dependencies (#3371) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index f4e7e085c68..a3ce12c8174 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "debug": "^4.3.4", "docdash": "^2.0.0", "domexception": "^4.0.0", - "eslint": "8.39.0", + "eslint": "8.40.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^8.5.0", "eslint-import-resolver-typescript": "^3.5.1", diff --git a/yarn.lock b/yarn.lock index 7fbd8269d52..be9bb8a91c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1076,7 +1076,7 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== -"@eslint/eslintrc@^2.0.2": +"@eslint/eslintrc@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== @@ -1091,10 +1091,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.39.0": - version "8.39.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" - integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== +"@eslint/js@8.40.0": + version "8.40.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" + integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== "@humanwhocodes/config-array@^0.11.8": version "0.11.8" @@ -3698,20 +3698,20 @@ eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0, eslint-visitor-keys@^3.4.1: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@8.39.0: - version "8.39.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" - integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== +eslint@8.40.0: + version "8.40.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4" + integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.39.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.40.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -3722,8 +3722,8 @@ eslint@8.39.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.0" - eslint-visitor-keys "^3.4.0" - espree "^9.5.1" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3749,7 +3749,7 @@ eslint@8.39.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.1, espree@^9.5.2: +espree@^9.5.2: version "9.5.2" resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== @@ -7189,9 +7189,9 @@ tapable@^2.2.0: integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== terser@^5.5.1: - version "5.17.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.2.tgz#06c9818ae998066234b985abeb57bb7bff29d449" - integrity sha512-1D1aGbOF1Mnayq5PvfMc0amAR1y5Z1nrZaGCvI5xsdEfZEVte8okonk02OiaK5fw5hG1GWuuVsakOnpZW8y25A== + version "5.17.4" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.4.tgz#b0c2d94897dfeba43213ed5f90ed117270a2c696" + integrity sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" From 32a5cc4728dfaa8ff564d298ac0a82d6a58c638d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:01:13 +0000 Subject: [PATCH 50/94] Update matrix-org/netlify-pr-preview action to v2 (#3374) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docs-pr-netlify.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-pr-netlify.yaml b/.github/workflows/docs-pr-netlify.yaml index bad72ef66c4..5891876fb0d 100644 --- a/.github/workflows/docs-pr-netlify.yaml +++ b/.github/workflows/docs-pr-netlify.yaml @@ -22,7 +22,7 @@ jobs: path: docs - name: 📤 Deploy to Netlify - uses: matrix-org/netlify-pr-preview@v1 + uses: matrix-org/netlify-pr-preview@v2 with: path: docs owner: ${{ github.event.workflow_run.head_repository.owner.login }} From 978e74824618da665f70940db715cbef55f18e02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:15:21 +0000 Subject: [PATCH 51/94] Update dependency eslint-plugin-jsdoc to v44 (#3372) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index a3ce12c8174..b0c67f316a0 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "eslint-import-resolver-typescript": "^3.5.1", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jest": "^27.1.6", - "eslint-plugin-jsdoc": "^43.0.6", + "eslint-plugin-jsdoc": "^44.0.0", "eslint-plugin-matrix-org": "^1.0.0", "eslint-plugin-tsdoc": "^0.2.17", "eslint-plugin-unicorn": "^46.0.0", diff --git a/yarn.lock b/yarn.lock index be9bb8a91c2..13874053585 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1055,10 +1055,10 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@es-joy/jsdoccomment@~0.38.0": - version "0.38.0" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.38.0.tgz#2e74f8d824b4a4ec831eaabd4c3548fb11eae5cd" - integrity sha512-TFac4Bnv0ZYNkEeDnOWHQhaS1elWlvOCQxH06iHeu5iffs+hCaLVIZJwF+FqksQi68R4i66Pu+4DfFGvble+Uw== +"@es-joy/jsdoccomment@~0.39.3": + version "0.39.4" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.39.4.tgz#6b8a62e9b3077027837728818d3c4389a898b392" + integrity sha512-Jvw915fjqQct445+yron7Dufix9A+m9j1fCJYlCo1FWlRvTxa3pjJelxdSTdaLWcTwRU6vbL+NYjO4YuNIS5Qg== dependencies: comment-parser "1.3.1" esquery "^1.5.0" @@ -3623,18 +3623,18 @@ eslint-plugin-jest@^27.1.6: dependencies: "@typescript-eslint/utils" "^5.10.0" -eslint-plugin-jsdoc@^43.0.6: - version "43.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.2.0.tgz#9d0df2329100a6956635f26211d0723c3ff91f15" - integrity sha512-Hst7XUfqh28UmPD52oTXmjaRN3d0KrmOZdgtp4h9/VHUJD3Evoo82ZGXi1TtRDWgWhvqDIRI63O49H0eH7NrZQ== +eslint-plugin-jsdoc@^44.0.0: + version "44.2.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-44.2.4.tgz#0bdc163771504ec7330414eda6a7dbae67156ddb" + integrity sha512-/EMMxCyRh1SywhCb66gAqoGX4Yv6Xzc4bsSkF1AiY2o2+bQmGMQ05QZ5+JjHbdFTPDZY9pfn+DsSNP0a5yQpIg== dependencies: - "@es-joy/jsdoccomment" "~0.38.0" + "@es-joy/jsdoccomment" "~0.39.3" are-docs-informative "^0.0.2" comment-parser "1.3.1" debug "^4.3.4" escape-string-regexp "^4.0.0" esquery "^1.5.0" - semver "^7.5.0" + semver "^7.5.1" spdx-expression-parse "^3.0.1" eslint-plugin-matrix-org@^1.0.0: @@ -6790,14 +6790,14 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.8, semver@^7.5.0: +semver@^7.3.5, semver@^7.3.8: version "7.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== dependencies: lru-cache "^6.0.0" -semver@^7.3.7: +semver@^7.3.7, semver@^7.5.1: version "7.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== From 8b0f1a0c59c055820f7820814df901fe53db44ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:19:43 +0000 Subject: [PATCH 52/94] Update dependency @types/node to v18.16.9 (#3370) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 13874053585..8bee2c3e5f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1790,9 +1790,9 @@ integrity sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A== "@types/node@18": - version "18.16.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.6.tgz#d0ffffe201b253989b17ea157ddabec677a4f4fe" - integrity sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q== + version "18.16.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.10.tgz#9b16d918f4f6fec6cae4af34283a91d555b81519" + integrity sha512-sMo3EngB6QkMBlB9rBe1lFdKSLqljyWPPWv6/FzSxh/IDlyVWSzE9RiF4eAuerQHybrWdqBgAGb03PM89qOasA== "@types/normalize-package-data@^2.4.0": version "2.4.1" From a769cf88f761f0e34240d0d6403b3594dd630434 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:23:34 +0000 Subject: [PATCH 53/94] Update dependency eslint-plugin-unicorn to v47 (#3373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 33 ++++++++++++++++++++------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index b0c67f316a0..5c7bae50f40 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "eslint-plugin-jsdoc": "^44.0.0", "eslint-plugin-matrix-org": "^1.0.0", "eslint-plugin-tsdoc": "^0.2.17", - "eslint-plugin-unicorn": "^46.0.0", + "eslint-plugin-unicorn": "^47.0.0", "exorcist": "^2.0.0", "fake-indexeddb": "^4.0.0", "fetch-mock-jest": "^1.5.1", diff --git a/yarn.lock b/yarn.lock index 8bee2c3e5f9..34d921a0a4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,7 +1064,7 @@ esquery "^1.5.0" jsdoc-type-pratt-parser "~4.0.0" -"@eslint-community/eslint-utils@^4.1.2", "@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== @@ -2813,7 +2813,7 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.3.2" -ci-info@^3.2.0, ci-info@^3.6.1: +ci-info@^3.2.0, ci-info@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== @@ -3650,24 +3650,24 @@ eslint-plugin-tsdoc@^0.2.17: "@microsoft/tsdoc" "0.14.2" "@microsoft/tsdoc-config" "0.16.2" -eslint-plugin-unicorn@^46.0.0: - version "46.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-46.0.1.tgz#222ff65b30b2d9ed6f90de908ceb6a05dd0514d9" - integrity sha512-setGhMTiLAddg1asdwjZ3hekIN5zLznNa5zll7pBPwFOka6greCKDQydfqy4fqyUhndi74wpDzClSQMEcmOaew== +eslint-plugin-unicorn@^47.0.0: + version "47.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-47.0.0.tgz#960e9d3789f656ba3e21982420793b069a911011" + integrity sha512-ivB3bKk7fDIeWOUmmMm9o3Ax9zbMz1Bsza/R2qm46ufw4T6VBFBaJIR1uN3pCKSmSXm8/9Nri8V+iUut1NhQGA== dependencies: "@babel/helper-validator-identifier" "^7.19.1" - "@eslint-community/eslint-utils" "^4.1.2" - ci-info "^3.6.1" + "@eslint-community/eslint-utils" "^4.4.0" + ci-info "^3.8.0" clean-regexp "^1.0.0" - esquery "^1.4.0" + esquery "^1.5.0" indent-string "^4.0.0" - is-builtin-module "^3.2.0" + is-builtin-module "^3.2.1" jsesc "^3.0.2" lodash "^4.17.21" pluralize "^8.0.0" read-pkg-up "^7.0.1" regexp-tree "^0.1.24" - regjsparser "^0.9.1" + regjsparser "^0.10.0" safe-regex "^2.1.1" semver "^7.3.8" strip-indent "^3.0.0" @@ -3763,7 +3763,7 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.0, esquery@^1.4.2, esquery@^1.5.0: +esquery@^1.4.2, esquery@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== @@ -4545,7 +4545,7 @@ is-buffer@^1.1.0, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-builtin-module@^3.2.0: +is-builtin-module@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== @@ -6613,6 +6613,13 @@ regexpu-core@^5.3.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" +regjsparser@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.10.0.tgz#b1ed26051736b436f22fdec1c8f72635f9f44892" + integrity sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA== + dependencies: + jsesc "~0.5.0" + regjsparser@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" From ece3ccb9582fadebd73d85cad8e431a67254f291 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 17:31:36 +0100 Subject: [PATCH 54/94] Lock file maintenance (#3375) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 159 ++++++++++++++++++------------------------------------ 1 file changed, 53 insertions(+), 106 deletions(-) diff --git a/yarn.lock b/yarn.lock index 34d921a0a4e..43d068eb91f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1433,6 +1433,7 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz": version "3.2.14" + uid acd96c00a881d0f462e1f97a56c73742c8dbc984 resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz#acd96c00a881d0f462e1f97a56c73742c8dbc984" "@microsoft/tsdoc-config@0.16.2": @@ -1611,19 +1612,19 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== +"@sinonjs/commons@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" + integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" - integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== + version "10.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.1.0.tgz#3595e42b3f0a7df80a9681cf58d8cb418eac1e99" + integrity sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw== dependencies: - "@sinonjs/commons" "^2.0.0" + "@sinonjs/commons" "^3.0.0" "@tootallnate/once@2": version "2.0.0" @@ -1646,9 +1647,9 @@ integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/babel-types@*", "@types/babel-types@^7.0.0": version "7.0.11" @@ -1785,9 +1786,9 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "20.1.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.1.tgz#afc492e8dbe7f672dd3a13674823522b467a45ad" - integrity sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A== + version "20.1.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.5.tgz#e94b604c67fc408f215fcbf3bd84d4743bf7f710" + integrity sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg== "@types/node@18": version "18.16.10" @@ -1877,14 +1878,6 @@ "@typescript-eslint/typescript-estree" "5.59.6" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d" - integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A== - dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" - "@typescript-eslint/scope-manager@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" @@ -1903,29 +1896,11 @@ debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" - integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== - "@typescript-eslint/types@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== -"@typescript-eslint/typescript-estree@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" - integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg== - dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" @@ -1939,7 +1914,7 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.6": +"@typescript-eslint/utils@5.59.6", "@typescript-eslint/utils@^5.10.0": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== @@ -1953,28 +1928,6 @@ eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/utils@^5.10.0": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" - integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b" - integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA== - dependencies: - "@typescript-eslint/types" "5.59.5" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" @@ -1997,9 +1950,9 @@ abab@^2.0.6: integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== ace-builds@^1.4.13: - version "1.19.0" - resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.19.0.tgz#1fcc462bbb82f65dfe8668d6d1f3752b1a8cf1d6" - integrity sha512-7iRX9MsxyhMUsqfWpWrJVf7dmv0nQcidOQOhzfYLQnNELdVpaqXVWcewfQqEHP+M0RR2TNie0gqoxPSstUc8Ww== + version "1.21.1" + resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.21.1.tgz#fb73589114725795babac7cedbbb74cd438c715b" + integrity sha512-GHHtz76BnQc5PcAAjJK6tPbuktSKQ+Vu5MDlu2+hDS4NtiofrttGBJHaxcD4741WZwsAO7Dk1I8w2a4n204T+Q== acorn-globals@^3.0.0: version "3.1.0" @@ -2757,9 +2710,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001449: - version "1.0.30001486" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz#56a08885228edf62cbe1ac8980f2b5dae159997e" - integrity sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg== + version "1.0.30001487" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz#d882d1a34d89c11aea53b8cdc791931bdab5fe1b" + integrity sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA== center-align@^0.1.1: version "0.1.3" @@ -3370,9 +3323,9 @@ eastasianwidth@^0.2.0: integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== electron-to-chromium@^1.4.284: - version "1.4.387" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.387.tgz#9a93ef1bd01b5898436026401ea3cabb1dd17d7a" - integrity sha512-tutLf+alr1/0YqJwKPdstVvDLmxmLb5xNyDLNS0RZmenHcEYk9qKfpKDCVZEKJ00JVbnayJm1MZAbYhYDFpcOw== + version "1.4.396" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.396.tgz#3d3664eb58d86376fbe2fece3705f68ca197205c" + integrity sha512-pqKTdqp/c5vsrc0xUPYXTDBo9ixZuGY8es4ZOjjd6HD6bFYbu5QA09VoW3fkY4LF1T0zYk86lN6bZnNlBuOpdQ== elliptic@^6.5.3: version "6.5.4" @@ -3403,9 +3356,9 @@ emoji-regex@^9.2.2: integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== enhanced-resolve@^5.12.0: - version "5.13.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" - integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== + version "5.14.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz#0b6c676c8a3266c99fa281e4433a706f5c0c61c4" + integrity sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4127,12 +4080,13 @@ get-caller-file@^2.0.5: integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" + has-proto "^1.0.1" has-symbols "^1.0.3" get-package-type@^0.1.0: @@ -4178,14 +4132,14 @@ glob-to-regexp@^0.4.0: integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== glob@^10.0.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.2.tgz#ce2468727de7e035e8ecf684669dc74d0526ab75" - integrity sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ== + version "10.2.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.4.tgz#f5bf7ddb080e3e9039b148a9e2aef3d5ebfc0a25" + integrity sha512-fDboBse/sl1oXSLhIp0FcCJgzW9KmhC/q8ULTKC82zc+DL3TL7FNb8qlt5qqXN53MsKEUSIcb+7DLmEygOE5Yw== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" minimatch "^9.0.0" - minipass "^5.0.0" + minipass "^5.0.0 || ^6.0.0" path-scurry "^1.7.0" glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: @@ -5544,7 +5498,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^9.0.0: +lru-cache@^9.1.1: version "9.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1" integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A== @@ -5731,10 +5685,10 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.0": + version "6.0.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.1.tgz#315417c259cb32a1b2fc530c0e7f55c901a60a6d" + integrity sha512-Tenl5QPpgozlOGBiveNYHg2f6y+VpxsXRoIHFUVJuSmTonXRAE6q9b8Mp/O46762/2AlW4ye4Nkyvx0fgWDKbw== mkdirp-classic@^0.5.2: version "0.5.3" @@ -5813,9 +5767,9 @@ node-dir@^0.1.10: minimatch "^3.0.2" node-fetch@^2.6.7: - version "2.6.10" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.10.tgz#4a52b637a6802092aa11a3bf73d19aac789fdce1" - integrity sha512-5YytjUVbwzjE/BX4N62vnPPkGNxlJPwdA9/ArUc4pcM6cYS4Hinuv4VazzwjMGgnWuiQqcemOanib/5PpcsGug== + version "2.6.11" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.11.tgz#cde7fc71deef3131ef80a738919f999e6edfff25" + integrity sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w== dependencies: whatwg-url "^5.0.0" @@ -6105,12 +6059,12 @@ path-platform@~0.11.15: integrity sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg== path-scurry@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.7.0.tgz#99c741a2cfbce782294a39994d63748b5a24f6db" - integrity sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg== + version "1.9.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.9.1.tgz#838566bb22e38feaf80ecd49ae06cd12acd782ee" + integrity sha512-UgmoiySyjFxP6tscZDgWGEAgsW5ok8W3F5CJDnnH2pozwSTGE6eH7vwTotMwATWA2r5xqdkKdxYPkwlJjAI/3g== dependencies: - lru-cache "^9.0.0" - minipass "^5.0.0" + lru-cache "^9.1.1" + minipass "^5.0.0 || ^6.0.0" path-to-regexp@^2.2.1: version "2.4.0" @@ -6797,14 +6751,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.5, semver@^7.3.8: - version "7.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" - integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.7, semver@^7.5.1: +semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1: version "7.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== @@ -6880,9 +6827,9 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== signal-exit@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.1.tgz#96a61033896120ec9335d96851d902cc98f0ba2a" - integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw== + version "4.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" + integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== simple-concat@^1.0.0: version "1.0.1" From bb5bccbf7844b82b7e91fdd2c29676c2e25a33b8 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Tue, 16 May 2023 21:30:32 +0100 Subject: [PATCH 55/94] Element-R: initial implementation of `bootstrapCrossSigning` (#3368) * Working `bootstrapCrossSigning` for rust * Remove unused `oldBackendOnly` * update tests * another test --- spec/integ/cross-signing.spec.ts | 12 +-- .../rust-crypto/CrossSigningIdentity.spec.ts | 79 ++++++++++++++ spec/unit/rust-crypto/rust-crypto.spec.ts | 8 +- src/rust-crypto/CrossSigningIdentity.ts | 101 ++++++++++++++++++ src/rust-crypto/rust-crypto.ts | 5 +- 5 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 spec/unit/rust-crypto/CrossSigningIdentity.spec.ts create mode 100644 src/rust-crypto/CrossSigningIdentity.ts diff --git a/spec/integ/cross-signing.spec.ts b/spec/integ/cross-signing.spec.ts index 0b20683091a..ba227d3f89c 100644 --- a/spec/integ/cross-signing.spec.ts +++ b/spec/integ/cross-signing.spec.ts @@ -38,10 +38,6 @@ const TEST_DEVICE_ID = "xzcvb"; * to provide the most effective integration tests possible. */ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: string, initCrypto: InitCrypto) => { - // oldBackendOnly is an alternative to `it` or `test` which will skip the test if we are running against the - // Rust backend. Once we have full support in the rust sdk, it will go away. - const oldBackendOnly = backend === "rust-sdk" ? test.skip : test; - let aliceClient: MatrixClient; beforeEach(async () => { @@ -66,7 +62,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s }); describe("bootstrapCrossSigning (before initialsync completes)", () => { - oldBackendOnly("publishes keys if none were yet published", async () => { + it("publishes keys if none were yet published", async () => { // have account_data requests return an empty object fetchMock.get("express:/_matrix/client/r0/user/:userId/account_data/:type", {}); @@ -75,7 +71,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("cross-signing (%s)", (backend: s // ... and one to upload the cross-signing keys (with UIA) fetchMock.post( - { url: "path:/_matrix/client/unstable/keys/device_signing/upload", name: "upload-keys" }, + // legacy crypto uses /unstable/; /v3/ is correct + { + url: new RegExp("/_matrix/client/(unstable|v3)/keys/device_signing/upload"), + name: "upload-keys", + }, {}, ); diff --git a/spec/unit/rust-crypto/CrossSigningIdentity.spec.ts b/spec/unit/rust-crypto/CrossSigningIdentity.spec.ts new file mode 100644 index 00000000000..d0fb8bd36be --- /dev/null +++ b/spec/unit/rust-crypto/CrossSigningIdentity.spec.ts @@ -0,0 +1,79 @@ +/* +Copyright 2023 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 { Mocked } from "jest-mock"; +import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js"; + +import { CrossSigningIdentity } from "../../../src/rust-crypto/CrossSigningIdentity"; +import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor"; + +describe("CrossSigningIdentity", () => { + describe("bootstrapCrossSigning", () => { + /** the CrossSigningIdentity implementation under test */ + let crossSigning: CrossSigningIdentity; + + /** a mocked-up OlmMachine which crossSigning is connected to */ + let olmMachine: Mocked; + + /** A mock OutgoingRequestProcessor which crossSigning is connected to */ + let outgoingRequestProcessor: Mocked; + + beforeEach(async () => { + await RustSdkCryptoJs.initAsync(); + + olmMachine = { + crossSigningStatus: jest.fn(), + bootstrapCrossSigning: jest.fn(), + close: jest.fn(), + } as unknown as Mocked; + + outgoingRequestProcessor = { + makeOutgoingRequest: jest.fn(), + } as unknown as Mocked; + + crossSigning = new CrossSigningIdentity(olmMachine, outgoingRequestProcessor); + }); + + it("should do nothing if keys are present on-device and in secret storage", async () => { + olmMachine.crossSigningStatus.mockResolvedValue({ + hasMaster: true, + hasSelfSigning: true, + hasUserSigning: true, + }); + // TODO: secret storage + await crossSigning.bootstrapCrossSigning({}); + expect(olmMachine.bootstrapCrossSigning).not.toHaveBeenCalled(); + expect(outgoingRequestProcessor.makeOutgoingRequest).not.toHaveBeenCalled(); + }); + + it("should call bootstrapCrossSigning if a reset is forced", async () => { + olmMachine.bootstrapCrossSigning.mockResolvedValue([]); + await crossSigning.bootstrapCrossSigning({ setupNewCrossSigning: true }); + expect(olmMachine.bootstrapCrossSigning).toHaveBeenCalledWith(true); + }); + + it("should call bootstrapCrossSigning if we need new keys", async () => { + olmMachine.crossSigningStatus.mockResolvedValue({ + hasMaster: false, + hasSelfSigning: false, + hasUserSigning: false, + }); + olmMachine.bootstrapCrossSigning.mockResolvedValue([]); + await crossSigning.bootstrapCrossSigning({}); + expect(olmMachine.bootstrapCrossSigning).toHaveBeenCalledWith(true); + }); + }); +}); diff --git a/spec/unit/rust-crypto/rust-crypto.spec.ts b/spec/unit/rust-crypto/rust-crypto.spec.ts index a0c9a47763f..71df210ad97 100644 --- a/spec/unit/rust-crypto/rust-crypto.spec.ts +++ b/spec/unit/rust-crypto/rust-crypto.spec.ts @@ -103,9 +103,15 @@ describe("RustCrypto", () => { await expect(rustCrypto.getCrossSigningKeyId()).resolves.toBe(null); }); - it("bootstrapCrossSigning", async () => { + it("bootstrapCrossSigning delegates to CrossSigningIdentity", async () => { const rustCrypto = await makeTestRustCrypto(); + const mockCrossSigningIdentity = { + bootstrapCrossSigning: jest.fn().mockResolvedValue(undefined), + }; + // @ts-ignore private property + rustCrypto.crossSigningIdentity = mockCrossSigningIdentity; await rustCrypto.bootstrapCrossSigning({}); + expect(mockCrossSigningIdentity.bootstrapCrossSigning).toHaveBeenCalledWith({}); }); it("isSecretStorageReady", async () => { diff --git a/src/rust-crypto/CrossSigningIdentity.ts b/src/rust-crypto/CrossSigningIdentity.ts new file mode 100644 index 00000000000..27eb623100a --- /dev/null +++ b/src/rust-crypto/CrossSigningIdentity.ts @@ -0,0 +1,101 @@ +/* +Copyright 2023 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 { OlmMachine, CrossSigningStatus } from "@matrix-org/matrix-sdk-crypto-js"; + +import { BootstrapCrossSigningOpts } from "../crypto-api"; +import { logger } from "../logger"; +import { OutgoingRequest, OutgoingRequestProcessor } from "./OutgoingRequestProcessor"; +import { UIAuthCallback } from "../interactive-auth"; + +/** Manages the cross-signing keys for our own user. + */ +export class CrossSigningIdentity { + public constructor( + private readonly olmMachine: OlmMachine, + private readonly outgoingRequestProcessor: OutgoingRequestProcessor, + ) {} + + /** + * Initialise our cross-signing keys by creating new keys if they do not exist, and uploading to the server + */ + public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise { + if (opts.setupNewCrossSigning) { + await this.resetCrossSigning(opts.authUploadDeviceSigningKeys); + return; + } + + const olmDeviceStatus: CrossSigningStatus = await this.olmMachine.crossSigningStatus(); + const privateKeysInSecretStorage = false; // TODO + const olmDeviceHasKeys = + olmDeviceStatus.hasMaster && olmDeviceStatus.hasUserSigning && olmDeviceStatus.hasSelfSigning; + + // Log all relevant state for easier parsing of debug logs. + logger.log("bootStrapCrossSigning: starting", { + setupNewCrossSigning: opts.setupNewCrossSigning, + olmDeviceHasMaster: olmDeviceStatus.hasMaster, + olmDeviceHasUserSigning: olmDeviceStatus.hasUserSigning, + olmDeviceHasSelfSigning: olmDeviceStatus.hasSelfSigning, + privateKeysInSecretStorage, + }); + + if (!olmDeviceHasKeys && !privateKeysInSecretStorage) { + logger.log( + "bootStrapCrossSigning: Cross-signing private keys not found locally or in secret storage, creating new keys", + ); + await this.resetCrossSigning(opts.authUploadDeviceSigningKeys); + } else if (olmDeviceHasKeys) { + logger.log("bootStrapCrossSigning: Olm device has private keys: exporting to secret storage"); + await this.exportCrossSigningKeysToStorage(); + } else if (privateKeysInSecretStorage) { + logger.log( + "bootStrapCrossSigning: Cross-signing private keys not found locally, but they are available " + + "in secret storage, reading storage and caching locally", + ); + throw new Error("TODO"); + } + + // TODO: we might previously have bootstrapped cross-signing but not completed uploading the keys to the + // server -- in which case we should call OlmDevice.bootstrap_cross_signing. How do we know? + logger.log("bootStrapCrossSigning: complete"); + } + + /** Reset our cross-signing keys + * + * This method will: + * * Tell the OlmMachine to create new keys + * * Upload the new public keys and the device signature to the server + * * Upload the private keys to SSSS, if it is set up + */ + private async resetCrossSigning(authUploadDeviceSigningKeys?: UIAuthCallback): Promise { + const outgoingRequests: Array = await this.olmMachine.bootstrapCrossSigning(true); + + logger.log("bootStrapCrossSigning: publishing keys to server"); + for (const req of outgoingRequests) { + await this.outgoingRequestProcessor.makeOutgoingRequest(req, authUploadDeviceSigningKeys); + } + await this.exportCrossSigningKeysToStorage(); + } + + /** + * Extract the cross-signing keys from the olm machine and save them to secret storage, if it is configured + * + * (If secret storage is *not* configured, we assume that the export will happen when it is set up) + */ + private async exportCrossSigningKeysToStorage(): Promise { + // TODO + } +} diff --git a/src/rust-crypto/rust-crypto.ts b/src/rust-crypto/rust-crypto.ts index d96c916e477..acfb82dd68f 100644 --- a/src/rust-crypto/rust-crypto.ts +++ b/src/rust-crypto/rust-crypto.ts @@ -36,6 +36,7 @@ import { IDownloadKeyResult, IQueryKeysRequest } from "../client"; import { Device, DeviceMap } from "../models/device"; import { ServerSideSecretStorage } from "../secret-storage"; import { CrossSigningKey } from "../crypto/api"; +import { CrossSigningIdentity } from "./CrossSigningIdentity"; /** * An implementation of {@link CryptoBackend} using the Rust matrix-sdk-crypto. @@ -56,6 +57,7 @@ export class RustCrypto implements CryptoBackend { private eventDecryptor: EventDecryptor; private keyClaimManager: KeyClaimManager; private outgoingRequestProcessor: OutgoingRequestProcessor; + private crossSigningIdentity: CrossSigningIdentity; public constructor( /** The `OlmMachine` from the underlying rust crypto sdk. */ @@ -80,6 +82,7 @@ export class RustCrypto implements CryptoBackend { this.outgoingRequestProcessor = new OutgoingRequestProcessor(olmMachine, http); this.keyClaimManager = new KeyClaimManager(olmMachine, this.outgoingRequestProcessor); this.eventDecryptor = new EventDecryptor(olmMachine); + this.crossSigningIdentity = new CrossSigningIdentity(olmMachine, this.outgoingRequestProcessor); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -337,7 +340,7 @@ export class RustCrypto implements CryptoBackend { * Implementation of {@link CryptoApi#boostrapCrossSigning} */ public async bootstrapCrossSigning(opts: BootstrapCrossSigningOpts): Promise { - logger.log("Cross-signing ready"); + await this.crossSigningIdentity.bootstrapCrossSigning(opts); } /** From a7b1dcaf9514b2e424a387e266c6f383a5909927 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Wed, 17 May 2023 17:40:33 +0200 Subject: [PATCH 56/94] Filter all summary stats which collect the first time webrtc metrics. (#3377) * remove comment * Filter all summery stats which collect the first time webrtc stats * Fix lint issues * Fix lint second issues --- spec/unit/webrtc/stats/groupCallStats.spec.ts | 19 ++- .../webrtc/stats/statsReportGatherer.spec.ts | 38 ++++- .../webrtc/stats/summaryStatsReporter.spec.ts | 144 ++++++++++++++++++ src/webrtc/stats/statsReportGatherer.ts | 3 +- src/webrtc/stats/summaryStats.ts | 2 + src/webrtc/stats/summaryStatsReporter.ts | 6 +- 6 files changed, 206 insertions(+), 6 deletions(-) diff --git a/spec/unit/webrtc/stats/groupCallStats.spec.ts b/spec/unit/webrtc/stats/groupCallStats.spec.ts index f0b6c436546..672e79d6863 100644 --- a/spec/unit/webrtc/stats/groupCallStats.spec.ts +++ b/spec/unit/webrtc/stats/groupCallStats.spec.ts @@ -92,11 +92,26 @@ describe("GroupCallStats", () => { const collector = stats.getStatsReportGatherer("CALL_ID"); stats.reports.emitSummaryStatsReport = jest.fn(); const summaryStats = { + isFirstCollection: true, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, - audioTrackSummary: { count: 0, muted: 0 }, - videoTrackSummary: { count: 0, muted: 0 }, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, } as SummaryStats; let processStatsSpy; if (collector) { diff --git a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts b/spec/unit/webrtc/stats/statsReportGatherer.spec.ts index 0eb847ce01d..fdd2ffd502d 100644 --- a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts +++ b/spec/unit/webrtc/stats/statsReportGatherer.spec.ts @@ -40,6 +40,7 @@ describe("StatsReportGatherer", () => { const actual = await collector.processStats("GROUP_CALL_ID", "LOCAL_USER_ID"); expect(getStats).toHaveBeenCalled(); expect(actual).toEqual({ + isFirstCollection: true, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, @@ -79,12 +80,13 @@ describe("StatsReportGatherer", () => { expect(collector.getActive()).toBeFalsy(); }); - it("if active an RTCStatsReport not a promise the collector becomes inactive", async () => { + it("if active and getStats returns not an RTCStatsReport inside a promise the collector fails and becomes inactive", async () => { const getStats = jest.spyOn(rtcSpy, "getStats"); // @ts-ignore getStats.mockReturnValue({}); const actual = await collector.processStats("GROUP_CALL_ID", "LOCAL_USER_ID"); expect(actual).toEqual({ + isFirstCollection: true, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, @@ -108,5 +110,39 @@ describe("StatsReportGatherer", () => { expect(getStats).toHaveBeenCalled(); expect(collector.getActive()).toBeFalsy(); }); + + it("if active and the collector runs not the first time the Summery Stats is marked as not fits collection", async () => { + const getStats = jest.spyOn(rtcSpy, "getStats"); + // @ts-ignore + collector.previousStatsReport = {} as RTCStatsReport; + const report = {} as RTCStatsReport; + report.forEach = jest.fn().mockReturnValue([]); + getStats.mockResolvedValue(report); + const actual = await collector.processStats("GROUP_CALL_ID", "LOCAL_USER_ID"); + expect(getStats).toHaveBeenCalled(); + expect(actual).toEqual({ + isFirstCollection: false, + receivedMedia: 0, + receivedAudioMedia: 0, + receivedVideoMedia: 0, + audioTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 0, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + }); + expect(collector.getActive()).toBeTruthy(); + }); }); }); diff --git a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts index 22c7f60c3ad..e448c3b1604 100644 --- a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts @@ -30,10 +30,38 @@ describe("SummaryStatsReporter", () => { reporter.build([]); expect(emitter.emitSummaryStatsReport).not.toHaveBeenCalled(); }); + it("should do nothing if a summary stats element collection the is first time", async () => { + reporter.build([ + { + isFirstCollection: true, + receivedMedia: 10, + receivedAudioMedia: 4, + receivedVideoMedia: 6, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 100, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + }, + ]); + expect(emitter.emitSummaryStatsReport).not.toHaveBeenCalled(); + }); it("should trigger new summary report", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 10, receivedAudioMedia: 4, receivedVideoMedia: 6, @@ -55,6 +83,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 13, receivedAudioMedia: 0, receivedVideoMedia: 13, @@ -76,6 +105,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, @@ -97,6 +127,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 15, receivedAudioMedia: 6, receivedVideoMedia: 9, @@ -133,6 +164,7 @@ describe("SummaryStatsReporter", () => { it("as received video Media, although video was not received, but because video muted", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 10, receivedAudioMedia: 10, receivedVideoMedia: 0, @@ -169,6 +201,7 @@ describe("SummaryStatsReporter", () => { it("as received no video Media, because only on video was muted", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 10, receivedAudioMedia: 10, receivedVideoMedia: 0, @@ -205,6 +238,7 @@ describe("SummaryStatsReporter", () => { it("as received no audio Media, although audio not received and audio muted", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 100, receivedAudioMedia: 0, receivedVideoMedia: 100, @@ -241,6 +275,7 @@ describe("SummaryStatsReporter", () => { it("should find max jitter and max packet loss", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, @@ -262,6 +297,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, @@ -283,6 +319,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, @@ -304,6 +341,7 @@ describe("SummaryStatsReporter", () => { }, }, { + isFirstCollection: false, receivedMedia: 1, receivedAudioMedia: 1, receivedVideoMedia: 1, @@ -340,6 +378,7 @@ describe("SummaryStatsReporter", () => { it("as received video Media, if no audio track received should count as received Media", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 10, receivedAudioMedia: 0, receivedVideoMedia: 10, @@ -376,6 +415,7 @@ describe("SummaryStatsReporter", () => { it("as received audio Media, if no video track received should count as received Media", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 1, receivedAudioMedia: 22, receivedVideoMedia: 0, @@ -412,6 +452,7 @@ describe("SummaryStatsReporter", () => { it("as received no media at all, as received Media", async () => { const summary = [ { + isFirstCollection: false, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, @@ -444,5 +485,108 @@ describe("SummaryStatsReporter", () => { percentageConcealedAudio: 0, }); }); + + it("should filter the first time summery stats", async () => { + const summary = [ + { + isFirstCollection: false, + receivedMedia: 1, + receivedAudioMedia: 1, + receivedVideoMedia: 1, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + }, + { + isFirstCollection: true, + receivedMedia: 1, + receivedAudioMedia: 1, + receivedVideoMedia: 1, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 20, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 0, + concealedAudio: 0, + totalAudio: 0, + }, + }, + { + isFirstCollection: false, + receivedMedia: 1, + receivedAudioMedia: 1, + receivedVideoMedia: 1, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + }, + { + isFirstCollection: false, + receivedMedia: 1, + receivedAudioMedia: 1, + receivedVideoMedia: 1, + audioTrackSummary: { + count: 1, + muted: 0, + maxJitter: 2, + maxPacketLoss: 5, + concealedAudio: 0, + totalAudio: 0, + }, + videoTrackSummary: { + count: 1, + muted: 0, + maxJitter: 0, + maxPacketLoss: 40, + concealedAudio: 0, + totalAudio: 0, + }, + }, + ]; + reporter.build(summary); + expect(emitter.emitSummaryStatsReport).toHaveBeenCalledWith({ + percentageReceivedMedia: 1, + percentageReceivedAudioMedia: 1, + percentageReceivedVideoMedia: 1, + maxJitter: 2, + maxPacketLoss: 40, + peerConnections: 3, + percentageConcealedAudio: 0, + }); + }); }); }); diff --git a/src/webrtc/stats/statsReportGatherer.ts b/src/webrtc/stats/statsReportGatherer.ts index e5146122fb6..485f35ecffd 100644 --- a/src/webrtc/stats/statsReportGatherer.ts +++ b/src/webrtc/stats/statsReportGatherer.ts @@ -35,8 +35,6 @@ export class StatsReportGatherer { private readonly trackStats: MediaTrackStatsHandler; - // private readonly ssrcToMid = { local: new Map(), remote: new Map() }; - public constructor( public readonly callId: string, public readonly remoteUserId: string, @@ -50,6 +48,7 @@ export class StatsReportGatherer { public async processStats(groupCallId: string, localUserId: string): Promise { const summary = { + isFirstCollection: this.previousStatsReport === undefined, receivedMedia: 0, receivedAudioMedia: 0, receivedVideoMedia: 0, diff --git a/src/webrtc/stats/summaryStats.ts b/src/webrtc/stats/summaryStats.ts index e5591749db9..e8189be3a58 100644 --- a/src/webrtc/stats/summaryStats.ts +++ b/src/webrtc/stats/summaryStats.ts @@ -16,6 +16,8 @@ export interface SummaryStats { receivedVideoMedia: number; audioTrackSummary: TrackSummary; videoTrackSummary: TrackSummary; + + isFirstCollection: Boolean; } export interface TrackSummary { diff --git a/src/webrtc/stats/summaryStatsReporter.ts b/src/webrtc/stats/summaryStatsReporter.ts index 7fd594c5099..79cf20dcffc 100644 --- a/src/webrtc/stats/summaryStatsReporter.ts +++ b/src/webrtc/stats/summaryStatsReporter.ts @@ -25,7 +25,11 @@ interface SummaryCounter { export class SummaryStatsReporter { public constructor(private emitter: StatsReportEmitter) {} - public build(summary: SummaryStats[]): void { + public build(allSummary: SummaryStats[]): void { + // Filter all stats which collect the first time webrtc stats. + // Because stats based on time interval and the first collection of a summery stats has no previous + // webrtcStats as basement all the calculation are 0. We don't want track the 0 stats. + const summary = allSummary.filter((s) => !s.isFirstCollection); const summaryTotalCount = summary.length; if (summaryTotalCount === 0) { return; From 0fa9528a3ff7d688f5e418eac3aa42e6214bff32 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Thu, 18 May 2023 13:22:51 +0100 Subject: [PATCH 57/94] Comments for typed-event-emitter classes (#3380) * Comments for typed-event-emitter classes * Export `TypedEventEmitter` ... to stop tsdoc complaining. --- src/matrix.ts | 1 + src/models/typed-event-emitter.ts | 118 +++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/src/matrix.ts b/src/matrix.ts index a60376df6a4..2034665748b 100644 --- a/src/matrix.ts +++ b/src/matrix.ts @@ -37,6 +37,7 @@ export * from "./models/event-timeline-set"; export * from "./models/poll"; export * from "./models/room-member"; export * from "./models/room-state"; +export * from "./models/typed-event-emitter"; export * from "./models/user"; export * from "./models/device"; export * from "./scheduler"; diff --git a/src/models/typed-event-emitter.ts b/src/models/typed-event-emitter.ts index c359df5d18f..7eac48b962b 100644 --- a/src/models/typed-event-emitter.ts +++ b/src/models/typed-event-emitter.ts @@ -17,6 +17,7 @@ limitations under the License. // eslint-disable-next-line no-restricted-imports import { EventEmitter } from "events"; +/** Events emitted by EventEmitter itself */ export enum EventEmitterEvents { NewListener = "newListener", RemoveListener = "removeListener", @@ -24,10 +25,22 @@ export enum EventEmitterEvents { } type AnyListener = (...args: any) => any; + +/** Base class for types mapping from event name to the type of listeners to that event */ export type ListenerMap = { [eventName in E]: AnyListener }; + type EventEmitterEventListener = (eventName: string, listener: AnyListener) => void; type EventEmitterErrorListener = (error: Error) => void; +/** + * The expected type of a listener function for a particular event. + * + * Type parameters: + * * `E` - List of all events emitted by the `TypedEventEmitter`. Normally an enum type. + * * `A` - A type providing mappings from event names to listener types. + * * `T` - The name of the actual event that this listener is for. Normally one of the types in `E` or + * {@link EventEmitterEvents}. + */ export type Listener, T extends E | EventEmitterEvents> = T extends E ? A[T] : T extends EventEmitterEvents @@ -40,12 +53,20 @@ export type Listener, T extends E | E * This makes it much easier for us to distinguish between events, as we now need * to properly type this, so that our events are not stringly-based and prone * to silly typos. + * + * Type parameters: + * * `Events` - List of all events emitted by this `TypedEventEmitter`. Normally an enum type. + * * `Arguments` - A {@link ListenerMap} type providing mappings from event names to listener types. + * * `SuperclassArguments` - TODO: not really sure. Alternative listener mappings, I think? But only honoured for `.emit`? */ export class TypedEventEmitter< Events extends string, Arguments extends ListenerMap, SuperclassArguments extends ListenerMap = Arguments, > extends EventEmitter { + /** + * Alias for {@link TypedEventEmitter#on}. + */ public addListener( event: T, listener: Listener, @@ -53,32 +74,97 @@ export class TypedEventEmitter< return super.addListener(event, listener); } + /** + * Synchronously calls each of the listeners registered for the event named + * `event`, in the order they were registered, passing the supplied arguments + * to each. + * + * @param event - The name of the event to emit + * @param args - Arguments to pass to the listener + * @returns `true` if the event had listeners, `false` otherwise. + */ public emit(event: T, ...args: Parameters): boolean; public emit(event: T, ...args: Parameters): boolean; public emit(event: T, ...args: any[]): boolean { return super.emit(event, ...args); } + /** + * Returns the number of listeners listening to the event named `event`. + * + * @param event - The name of the event being listened for + */ public listenerCount(event: Events | EventEmitterEvents): number { return super.listenerCount(event); } - public listeners(event: Events | EventEmitterEvents): ReturnType { + /** + * Returns a copy of the array of listeners for the event named `event`. + */ + public listeners(event: Events | EventEmitterEvents): Function[] { return super.listeners(event); } + /** + * Alias for {@link TypedEventEmitter#removeListener} + */ public off(event: T, listener: Listener): this { return super.off(event, listener); } + /** + * Adds the `listener` function to the end of the listeners array for the + * event named `event`. + * + * No checks are made to see if the `listener` has already been added. Multiple calls + * passing the same combination of `event` and `listener` will result in the `listener` + * being added, and called, multiple times. + * + * By default, event listeners are invoked in the order they are added. The + * {@link TypedEventEmitter#prependListener} method can be used as an alternative to add the + * event listener to the beginning of the listeners array. + * + * @param event - The name of the event. + * @param listener - The callback function + * + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public on(event: T, listener: Listener): this { return super.on(event, listener); } + /** + * Adds a **one-time** `listener` function for the event named `event`. The + * next time `event` is triggered, this listener is removed and then invoked. + * + * Returns a reference to the `EventEmitter`, so that calls can be chained. + * + * By default, event listeners are invoked in the order they are added. + * The {@link TypedEventEmitter#prependOnceListener} method can be used as an alternative to add the + * event listener to the beginning of the listeners array. + * + * @param event - The name of the event. + * @param listener - The callback function + * + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public once(event: T, listener: Listener): this { return super.once(event, listener); } + /** + * Adds the `listener` function to the _beginning_ of the listeners array for the + * event named `event`. + * + * No checks are made to see if the `listener` has already been added. Multiple calls + * passing the same combination of `event` and `listener` will result in the `listener` + * being added, and called, multiple times. + * + * @param event - The name of the event. + * @param listener - The callback function + * + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public prependListener( event: T, listener: Listener, @@ -86,6 +172,15 @@ export class TypedEventEmitter< return super.prependListener(event, listener); } + /** + * Adds a **one-time**`listener` function for the event named `event` to the _beginning_ of the listeners array. + * The next time `event` is triggered, this listener is removed, and then invoked. + * + * @param event - The name of the event. + * @param listener - The callback function + * + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public prependOnceListener( event: T, listener: Listener, @@ -93,10 +188,25 @@ export class TypedEventEmitter< return super.prependOnceListener(event, listener); } + /** + * Removes all listeners, or those of the specified `event`. + * + * It is bad practice to remove listeners added elsewhere in the code, + * particularly when the `EventEmitter` instance was created by some other + * component or module (e.g. sockets or file streams). + * + * @param event - The name of the event. If undefined, all listeners everywhere are removed. + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public removeAllListeners(event?: Events | EventEmitterEvents): this { return super.removeAllListeners(event); } + /** + * Removes the specified `listener` from the listener array for the event named `event`. + * + * @returns a reference to the `EventEmitter`, so that calls can be chained. + */ public removeListener( event: T, listener: Listener, @@ -104,7 +214,11 @@ export class TypedEventEmitter< return super.removeListener(event, listener); } - public rawListeners(event: Events | EventEmitterEvents): ReturnType { + /** + * Returns a copy of the array of listeners for the event named `eventName`, + * including any wrappers (such as those created by `.once()`). + */ + public rawListeners(event: Events | EventEmitterEvents): Function[] { return super.rawListeners(event); } } From b7b1129478c2e1f9250f5050f134142929f36287 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Thu, 18 May 2023 14:47:24 +0100 Subject: [PATCH 58/94] Fix bug with pendingEventOrdering: "chronological" (#3382) If we don't do this, then we end up trying to match the MAC on our own `m.key.verification.mac`, which of course fails. --- src/crypto/verification/request/InRoomChannel.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/crypto/verification/request/InRoomChannel.ts b/src/crypto/verification/request/InRoomChannel.ts index ff11bf192bb..ae8ced5ffe2 100644 --- a/src/crypto/verification/request/InRoomChannel.ts +++ b/src/crypto/verification/request/InRoomChannel.ts @@ -208,10 +208,17 @@ export class InRoomChannel implements IVerificationChannel { this.requestEventId = InRoomChannel.getTransactionId(event); } + // With pendingEventOrdering: "chronological", we will see events that have been sent but not yet reflected + // back via /sync. These are "local echoes" and are identifiable by their txnId + const isLocalEcho = !!event.getTxnId(); + + // Alternatively, we may see an event that we sent that is reflected back via /sync. These are "remote echoes" + // and have a transaction ID in the "unsigned" data const isRemoteEcho = !!event.getUnsigned().transaction_id; + const isSentByUs = event.getSender() === this.client.getUserId(); - return request.handleEvent(type, event, isLiveEvent, isRemoteEcho, isSentByUs); + return request.handleEvent(type, event, isLiveEvent, isLocalEcho || isRemoteEcho, isSentByUs); } /** From 488390365a12f3d3a853de2ae49b0d1a14c5b9d4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 19 May 2023 11:35:19 +0100 Subject: [PATCH 59/94] Add typedoc-plugin-coverage (#3379) * Add typedoc-plugin-coverage * Specify coverageLabel --- package.json | 1 + typedoc.json | 8 +++++++- yarn.lock | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c7bae50f40..c62dfb93df7 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "ts-node": "^10.9.1", "tsify": "^5.0.2", "typedoc": "^0.24.0", + "typedoc-plugin-coverage": "^2.1.0", "typedoc-plugin-mdn-links": "^3.0.3", "typedoc-plugin-missing-exports": "^2.0.0", "typedoc-plugin-versions": "^0.2.3", diff --git a/typedoc.json b/typedoc.json index ca02e27c2a0..d0217d6625f 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,3 +1,9 @@ { - "plugin": ["typedoc-plugin-mdn-links", "typedoc-plugin-missing-exports", "typedoc-plugin-versions"] + "plugin": [ + "typedoc-plugin-mdn-links", + "typedoc-plugin-missing-exports", + "typedoc-plugin-versions", + "typedoc-plugin-coverage" + ], + "coverageLabel": "TypeDoc" } diff --git a/yarn.lock b/yarn.lock index 43d068eb91f..8d013ba0dc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7421,6 +7421,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== +typedoc-plugin-coverage@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/typedoc-plugin-coverage/-/typedoc-plugin-coverage-2.1.0.tgz#619bf10853c5851c47dc17585e14385647bbb754" + integrity sha512-d0Lc/aOPRAMnfABCW2cQqCQdzLUzadeq62r4DBrSchcfzx1X8nOvhXK/n4mVAO4wQQUchTm2ZGAzTtiAc2nl0A== + typedoc-plugin-mdn-links@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-3.0.3.tgz#da8d1a9750d57333e6c21717b38bfc13d4058de2" From cd26ba67d4ac8688d10058a8e5de571de8c5268c Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Fri, 19 May 2023 12:34:26 +0100 Subject: [PATCH 60/94] Attempt a potential workaround for stuck notifs (#3384) * Attempt a potential workaround for stuck notifs * Remove TODOs * Fix backwards logic about server support for MSC3981 in fetchEditsWhereNeeded * Check for lack of MSC3981 server support before calling insertEventIntoTimeline * If no parent event is found, insert purely based on timestamp * Mark temporary methods as internal --- src/models/event-timeline-set.ts | 88 ++++++++++++++++++++++++++++++++ src/models/event-timeline.ts | 39 ++++++++++++++ src/models/thread.ts | 39 +++++++++++++- 3 files changed, 164 insertions(+), 2 deletions(-) diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 5cb04997e8b..dfe9694c2ed 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -756,6 +756,94 @@ export class EventTimelineSet extends TypedEventEmitter event.getTs()) { + // We found an event later than ours, so insert before that. + break; + } + } + // If we got to the end of the loop, insertIndex points at the end of + // the list. + + const eventId = event.getId()!; + timeline.insertEvent(event, insertIndex, roomState); + this._eventIdToTimeline.set(eventId, timeline); + + this.relations.aggregateParentEvent(event); + this.relations.aggregateChildEvent(event, this); + + const data: IRoomTimelineData = { + timeline: timeline, + liveEvent: timeline == this.liveTimeline, + }; + this.emit(RoomEvent.Timeline, event, this.room, false, false, data); + } + /** * Replaces event with ID oldEventId with one with newEventId, if oldEventId is * recognised. Otherwise, add to the live timeline. Used to handle remote echos. diff --git a/src/models/event-timeline.ts b/src/models/event-timeline.ts index d1ba3210365..67be25fbc9c 100644 --- a/src/models/event-timeline.ts +++ b/src/models/event-timeline.ts @@ -427,6 +427,45 @@ export class EventTimeline { } } + /** + * Insert a new event into the timeline, and update the state. + * + * TEMPORARY: until we have recursive relations, we need this function + * to exist to allow us to insert events in timeline order, which is our + * best guess for Sync Order. + * This is a copy of addEvent above, modified to allow inserting an event at + * a specific index. + * + * @internal + */ + public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void { + const timelineSet = this.getTimelineSet(); + + if (timelineSet.room) { + EventTimeline.setEventMetadata(event, roomState, false); + + // modify state but only on unfiltered timelineSets + if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) { + roomState.setStateEvents([event], {}); + // it is possible that the act of setting the state event means we + // can set more metadata (specifically sender/target props), so try + // it again if the prop wasn't previously set. It may also mean that + // the sender/target is updated (if the event set was a room member event) + // so we want to use the *updated* member (new avatar/name) instead. + // + // However, we do NOT want to do this on member events if we're going + // back in time, else we'll set the .sender value for BEFORE the given + // member event, whereas we want to set the .sender value for the ACTUAL + // member event itself. + if (!event.sender || event.getType() === EventType.RoomMember) { + EventTimeline.setEventMetadata(event, roomState!, false); + } + } + } + + this.events.splice(insertIndex, 0, event); // insert element + } + /** * Remove an event from the timeline * diff --git a/src/models/thread.ts b/src/models/thread.ts index c31499b2b34..f2420842dfc 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -236,6 +236,33 @@ export class Thread extends ReadReceipt { } } + /** + * TEMPORARY. Only call this when MSC3981 is not available, and we have some + * late-arriving events to insert, because we recursively found them as part + * of populating a thread. When we have MSC3981 we won't need it, because + * they will all be supplied by the homeserver in one request, and they will + * already be in the right order in that response. + * This is a copy of addEventToTimeline above, modified to call + * insertEventIntoTimeline so this event is inserted into our best guess of + * the right place based on timestamp. (We should be using Sync Order but we + * don't have it.) + * + * @internal + */ + public insertEventIntoTimeline(event: MatrixEvent): void { + const eventId = event.getId(); + if (!eventId) { + return; + } + if (this.findEventById(eventId)) { + return; + } + this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState); + + // As far as we know, timeline should always be the same as events + this.timeline = this.events; + } + public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void { events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false)); this.updateThreadMetadata(); @@ -281,7 +308,14 @@ export class Thread extends ReadReceipt { */ this.replayEvents?.push(event); } else { - this.addEventToTimeline(event, toStartOfTimeline); + const recursionSupport = + this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; + + if (recursionSupport === ServerSupport.Unsupported) { + this.insertEventIntoTimeline(event); + } else { + this.addEventToTimeline(event, toStartOfTimeline); + } } // Apply annotations and replace relations to the relations of the timeline only this.timelineSet.relations?.aggregateParentEvent(event); @@ -460,7 +494,7 @@ export class Thread extends ReadReceipt { // XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084 private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise { const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; - if (recursionSupport !== ServerSupport.Unsupported) { + if (recursionSupport === ServerSupport.Unsupported) { return Promise.all( events .filter((e) => e.isEncrypted()) @@ -473,6 +507,7 @@ export class Thread extends ReadReceipt { .then((relations) => { if (relations.events.length) { event.makeReplaced(relations.events[0]); + this.insertEventIntoTimeline(event); } }) .catch((e) => { From b5d544df68979785c7ff5afa9c620050033c67f9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 19 May 2023 13:16:29 +0100 Subject: [PATCH 61/94] Update sonarqube.yml (#3376) --- .github/workflows/sonarqube.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index c68535bd82a..d5f3dc77ba5 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -11,7 +11,7 @@ jobs: # This is a workaround for https://github.com/SonarSource/SonarJS/issues/578 prepare: name: Prepare - if: github.event.workflow_run.event != 'merge_group' + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group' runs-on: ubuntu-latest outputs: reportPaths: ${{ steps.extra_args.outputs.reportPaths }} @@ -36,7 +36,7 @@ jobs: sonarqube: name: 🩻 SonarQube - if: github.event.workflow_run.event != 'merge_group' + if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event != 'merge_group' needs: prepare uses: matrix-org/matrix-js-sdk/.github/workflows/sonarcloud.yml@develop secrets: From 3f48a954d82552a1449c80ed06a13407f779c456 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 19 May 2023 13:43:16 +0100 Subject: [PATCH 62/94] Move crypto classes into a separate namespace (#3385) * Move crypto classes into a separate namespace * Add in re-exports for backwards compatibility * Update src/matrix.ts --- src/client.ts | 4 ++-- src/crypto-api.ts | 4 ++-- src/crypto/index.ts | 12 ++++++------ src/matrix.ts | 22 +++++++++++++++++++++- src/models/device.ts | 2 +- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/client.ts b/src/client.ts index 20bb0935144..820b36386cd 100644 --- a/src/client.ts +++ b/src/client.ts @@ -2577,7 +2577,7 @@ export class MatrixClient extends TypedEventEmitter new MemoryCryptoStore(); diff --git a/src/models/device.ts b/src/models/device.ts index 6f63e2e0634..0a451fd5a8d 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -27,7 +27,7 @@ export type DeviceMap = Map>; type DeviceParameters = Pick & Partial; /** - * Information on a user's device, as returned by {@link CryptoApi.getUserDeviceInfo}. + * Information on a user's device, as returned by {@link Crypto.CryptoApi.getUserDeviceInfo}. */ export class Device { /** id of the device */ From b22fc1f9d93f42fcdedd5f827cb8dbd62ad2367b Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Fri, 19 May 2023 15:57:55 +0100 Subject: [PATCH 63/94] [Backport staging] Attempt a potential workaround for stuck notifs (#3387) Co-authored-by: Andy Balaam --- src/models/event-timeline-set.ts | 88 ++++++++++++++++++++++++++++++++ src/models/event-timeline.ts | 39 ++++++++++++++ src/models/thread.ts | 39 +++++++++++++- 3 files changed, 164 insertions(+), 2 deletions(-) diff --git a/src/models/event-timeline-set.ts b/src/models/event-timeline-set.ts index 5cb04997e8b..dfe9694c2ed 100644 --- a/src/models/event-timeline-set.ts +++ b/src/models/event-timeline-set.ts @@ -756,6 +756,94 @@ export class EventTimelineSet extends TypedEventEmitter event.getTs()) { + // We found an event later than ours, so insert before that. + break; + } + } + // If we got to the end of the loop, insertIndex points at the end of + // the list. + + const eventId = event.getId()!; + timeline.insertEvent(event, insertIndex, roomState); + this._eventIdToTimeline.set(eventId, timeline); + + this.relations.aggregateParentEvent(event); + this.relations.aggregateChildEvent(event, this); + + const data: IRoomTimelineData = { + timeline: timeline, + liveEvent: timeline == this.liveTimeline, + }; + this.emit(RoomEvent.Timeline, event, this.room, false, false, data); + } + /** * Replaces event with ID oldEventId with one with newEventId, if oldEventId is * recognised. Otherwise, add to the live timeline. Used to handle remote echos. diff --git a/src/models/event-timeline.ts b/src/models/event-timeline.ts index d1ba3210365..67be25fbc9c 100644 --- a/src/models/event-timeline.ts +++ b/src/models/event-timeline.ts @@ -427,6 +427,45 @@ export class EventTimeline { } } + /** + * Insert a new event into the timeline, and update the state. + * + * TEMPORARY: until we have recursive relations, we need this function + * to exist to allow us to insert events in timeline order, which is our + * best guess for Sync Order. + * This is a copy of addEvent above, modified to allow inserting an event at + * a specific index. + * + * @internal + */ + public insertEvent(event: MatrixEvent, insertIndex: number, roomState: RoomState): void { + const timelineSet = this.getTimelineSet(); + + if (timelineSet.room) { + EventTimeline.setEventMetadata(event, roomState, false); + + // modify state but only on unfiltered timelineSets + if (event.isState() && timelineSet.room.getUnfilteredTimelineSet() === timelineSet) { + roomState.setStateEvents([event], {}); + // it is possible that the act of setting the state event means we + // can set more metadata (specifically sender/target props), so try + // it again if the prop wasn't previously set. It may also mean that + // the sender/target is updated (if the event set was a room member event) + // so we want to use the *updated* member (new avatar/name) instead. + // + // However, we do NOT want to do this on member events if we're going + // back in time, else we'll set the .sender value for BEFORE the given + // member event, whereas we want to set the .sender value for the ACTUAL + // member event itself. + if (!event.sender || event.getType() === EventType.RoomMember) { + EventTimeline.setEventMetadata(event, roomState!, false); + } + } + } + + this.events.splice(insertIndex, 0, event); // insert element + } + /** * Remove an event from the timeline * diff --git a/src/models/thread.ts b/src/models/thread.ts index c31499b2b34..f2420842dfc 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -236,6 +236,33 @@ export class Thread extends ReadReceipt { } } + /** + * TEMPORARY. Only call this when MSC3981 is not available, and we have some + * late-arriving events to insert, because we recursively found them as part + * of populating a thread. When we have MSC3981 we won't need it, because + * they will all be supplied by the homeserver in one request, and they will + * already be in the right order in that response. + * This is a copy of addEventToTimeline above, modified to call + * insertEventIntoTimeline so this event is inserted into our best guess of + * the right place based on timestamp. (We should be using Sync Order but we + * don't have it.) + * + * @internal + */ + public insertEventIntoTimeline(event: MatrixEvent): void { + const eventId = event.getId(); + if (!eventId) { + return; + } + if (this.findEventById(eventId)) { + return; + } + this.timelineSet.insertEventIntoTimeline(event, this.liveTimeline, this.roomState); + + // As far as we know, timeline should always be the same as events + this.timeline = this.events; + } + public addEvents(events: MatrixEvent[], toStartOfTimeline: boolean): void { events.forEach((ev) => this.addEvent(ev, toStartOfTimeline, false)); this.updateThreadMetadata(); @@ -281,7 +308,14 @@ export class Thread extends ReadReceipt { */ this.replayEvents?.push(event); } else { - this.addEventToTimeline(event, toStartOfTimeline); + const recursionSupport = + this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; + + if (recursionSupport === ServerSupport.Unsupported) { + this.insertEventIntoTimeline(event); + } else { + this.addEventToTimeline(event, toStartOfTimeline); + } } // Apply annotations and replace relations to the relations of the timeline only this.timelineSet.relations?.aggregateParentEvent(event); @@ -460,7 +494,7 @@ export class Thread extends ReadReceipt { // XXX: Workaround for https://github.com/matrix-org/matrix-spec-proposals/pull/2676/files#r827240084 private async fetchEditsWhereNeeded(...events: MatrixEvent[]): Promise { const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; - if (recursionSupport !== ServerSupport.Unsupported) { + if (recursionSupport === ServerSupport.Unsupported) { return Promise.all( events .filter((e) => e.isEncrypted()) @@ -473,6 +507,7 @@ export class Thread extends ReadReceipt { .then((relations) => { if (relations.events.length) { event.makeReplaced(relations.events[0]); + this.insertEventIntoTimeline(event); } }) .catch((e) => { From 5973c667266e2235e9c5901c76573b8fb0e02982 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 19 May 2023 16:33:19 +0100 Subject: [PATCH 64/94] Make sonar happier about our code & tests (#3388) --- spec/integ/crypto.spec.ts | 2 +- spec/olm-loader.ts | 2 +- spec/unit/crypto.spec.ts | 35 ++------------ spec/unit/crypto/secrets.spec.ts | 17 ++----- spec/unit/crypto/verification/sas.spec.ts | 8 ++-- spec/unit/room.spec.ts | 8 +--- spec/unit/webrtc/call.spec.ts | 9 +--- spec/unit/webrtc/groupCall.spec.ts | 5 +- src/autodiscovery.ts | 6 +-- src/client.ts | 48 +++++++++---------- src/crypto/CrossSigning.ts | 4 +- src/crypto/DeviceList.ts | 2 +- src/crypto/store/memory-crypto-store.ts | 3 +- .../verification/request/InRoomChannel.ts | 4 +- src/embedded.ts | 3 +- 15 files changed, 50 insertions(+), 106 deletions(-) diff --git a/spec/integ/crypto.spec.ts b/spec/integ/crypto.spec.ts index 4e70bdd6e9f..951e853f90e 100644 --- a/spec/integ/crypto.spec.ts +++ b/spec/integ/crypto.spec.ts @@ -1111,7 +1111,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string, } catch (e) { expect((e as any).name).toEqual("UnknownDeviceError"); expect([...(e as any).devices.keys()]).toEqual([aliceClient.getUserId()!]); - expect((e as any).devices.get(aliceClient.getUserId()!).has("DEVICE_ID")); + expect((e as any).devices.get(aliceClient.getUserId()!).has("DEVICE_ID")).toBeTruthy(); } // mark the device as known, and resend. diff --git a/spec/olm-loader.ts b/spec/olm-loader.ts index a139a48672b..29817176bd4 100644 --- a/spec/olm-loader.ts +++ b/spec/olm-loader.ts @@ -23,5 +23,5 @@ try { global.Olm = require("@matrix-org/olm"); logger.log("loaded libolm"); } catch (e) { - logger.warn("unable to run crypto tests: libolm not available"); + logger.warn("unable to run crypto tests: libolm not available", e); } diff --git a/spec/unit/crypto.spec.ts b/spec/unit/crypto.spec.ts index e62bb2eac1e..b96c0668656 100644 --- a/spec/unit/crypto.spec.ts +++ b/spec/unit/crypto.spec.ts @@ -381,12 +381,7 @@ describe("Crypto", function () { event.senderCurve25519Key = null; // @ts-ignore private properties event.claimedEd25519Key = null; - try { - await bobClient.crypto!.decryptEvent(event); - } catch (e) { - // we expect this to fail because we don't have the - // decryption keys yet - } + await expect(bobClient.crypto!.decryptEvent(event)).rejects.toBeTruthy(); }), ); @@ -617,12 +612,7 @@ describe("Crypto", function () { event.senderCurve25519Key = null; // @ts-ignore private properties event.claimedEd25519Key = null; - try { - await secondAliceClient.crypto!.decryptEvent(event); - } catch (e) { - // we expect this to fail because we don't have the - // decryption keys yet - } + await expect(secondAliceClient.crypto!.decryptEvent(event)).rejects.toBeTruthy(); }), ); @@ -725,12 +715,7 @@ describe("Crypto", function () { event.senderCurve25519Key = null; // @ts-ignore private properties event.claimedEd25519Key = null; - try { - await bobClient.crypto!.decryptEvent(event); - } catch (e) { - // we expect this to fail because we don't have the - // decryption keys yet - } + await expect(bobClient.crypto!.decryptEvent(event)).rejects.toBeTruthy(); }), ); @@ -805,12 +790,7 @@ describe("Crypto", function () { event.senderCurve25519Key = null; // @ts-ignore private properties event.claimedEd25519Key = null; - try { - await bobClient.crypto!.decryptEvent(event); - } catch (e) { - // we expect this to fail because we don't have the - // decryption keys yet - } + await expect(bobClient.crypto!.decryptEvent(event)).rejects.toBeTruthy(); }), ); @@ -897,12 +877,7 @@ describe("Crypto", function () { event.senderCurve25519Key = null; // @ts-ignore private properties event.claimedEd25519Key = null; - try { - await bobClient.crypto!.decryptEvent(event); - } catch (e) { - // we expect this to fail because we don't have the - // decryption keys yet - } + await expect(bobClient.crypto!.decryptEvent(event)).rejects.toBeTruthy(); }), ); diff --git a/spec/unit/crypto/secrets.spec.ts b/spec/unit/crypto/secrets.spec.ts index 91b8deb1cb6..3e95ed05e21 100644 --- a/spec/unit/crypto/secrets.spec.ts +++ b/spec/unit/crypto/secrets.spec.ts @@ -148,22 +148,14 @@ describe("Secrets", function () { it("should throw if given a key that doesn't exist", async function () { const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" }); - try { - await alice.storeSecret("foo", "bar", ["this secret does not exist"]); - // should be able to use expect(...).toThrow() but mocha still fails - // the test even when it throws for reasons I have no inclination to debug - expect(true).toBeFalsy(); - } catch (e) {} + await expect(alice.storeSecret("foo", "bar", ["this secret does not exist"])).rejects.toBeTruthy(); alice.stopClient(); }); it("should refuse to encrypt with zero keys", async function () { const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" }); - try { - await alice.storeSecret("foo", "bar", []); - expect(true).toBeFalsy(); - } catch (e) {} + await expect(alice.storeSecret("foo", "bar", [])).rejects.toBeTruthy(); alice.stopClient(); }); @@ -214,10 +206,7 @@ describe("Secrets", function () { it("should refuse to encrypt if no keys given and no default key", async function () { const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" }); - try { - await alice.storeSecret("foo", "bar"); - expect(true).toBeFalsy(); - } catch (e) {} + await expect(alice.storeSecret("foo", "bar")).rejects.toBeTruthy(); alice.stopClient(); }); diff --git a/spec/unit/crypto/verification/sas.spec.ts b/spec/unit/crypto/verification/sas.spec.ts index 1ea2c322285..ba73ced6a3e 100644 --- a/spec/unit/crypto/verification/sas.spec.ts +++ b/spec/unit/crypto/verification/sas.spec.ts @@ -144,7 +144,7 @@ describe("SAS verification", function () { expect(e.sas).toEqual(aliceSasEvent.sas); e.confirm(); aliceSasEvent.confirm(); - } catch (error) { + } catch { e.mismatch(); aliceSasEvent.mismatch(); } @@ -169,7 +169,7 @@ describe("SAS verification", function () { expect(e.sas).toEqual(bobSasEvent.sas); e.confirm(); bobSasEvent.confirm(); - } catch (error) { + } catch { e.mismatch(); bobSasEvent.mismatch(); } @@ -519,7 +519,7 @@ describe("SAS verification", function () { expect(e.sas).toEqual(aliceSasEvent.sas); e.confirm(); aliceSasEvent.confirm(); - } catch (error) { + } catch { e.mismatch(); aliceSasEvent.mismatch(); } @@ -543,7 +543,7 @@ describe("SAS verification", function () { expect(e.sas).toEqual(bobSasEvent.sas); e.confirm(); bobSasEvent.confirm(); - } catch (error) { + } catch { e.mismatch(); bobSasEvent.mismatch(); } diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 8caca90bee8..2445ee4fd1f 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -1932,13 +1932,7 @@ describe("Room", function () { 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 }); - let hasThrown = false; - try { - await room.loadMembersIfNeeded(); - } catch (err) { - hasThrown = true; - } - expect(hasThrown).toEqual(true); + await expect(room.loadMembersIfNeeded()).rejects.toBeTruthy(); client.members.mockReturnValue({ chunk: [memberEvent] }); await room.loadMembersIfNeeded(); diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index 965f41eae43..5a3b384539f 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -1054,14 +1054,7 @@ describe("Call", function () { mockSendEvent.mockReset(); - let caught = false; - try { - call.reject(); - } catch (e) { - caught = true; - } - - expect(caught).toEqual(true); + expect(() => call.reject()).toThrow(); expect(client.client.sendEvent).not.toHaveBeenCalled(); call.hangup(CallErrorCode.UserHangup, true); diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index d2023236749..876b658e2cb 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -186,10 +186,7 @@ describe("Group Call", function () { it("sets state to local call feed uninitialized when getUserMedia() fails", async () => { jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream").mockRejectedValue("Error"); - try { - await groupCall.initLocalCallFeed(); - } catch (e) {} - + await expect(groupCall.initLocalCallFeed()).rejects.toBeTruthy(); expect(groupCall.state).toBe(GroupCallState.LocalCallFeedUninitialized); }); diff --git a/src/autodiscovery.ts b/src/autodiscovery.ts index f4a34159613..5a7294b29ab 100644 --- a/src/autodiscovery.ts +++ b/src/autodiscovery.ts @@ -142,7 +142,7 @@ export class AutoDiscovery { }, }; - if (!wellknown || !wellknown["m.homeserver"]) { + if (!wellknown?.["m.homeserver"]) { logger.error("No m.homeserver key in config"); clientConfig["m.homeserver"].state = AutoDiscovery.FAIL_PROMPT; @@ -171,7 +171,7 @@ export class AutoDiscovery { // Step 3: Make sure the homeserver URL points to a homeserver. const hsVersions = await this.fetchWellKnownObject(`${hsUrl}/_matrix/client/versions`); - if (!hsVersions || !hsVersions.raw?.["versions"]) { + if (!hsVersions?.raw?.["versions"]) { logger.error("Invalid /versions response"); clientConfig["m.homeserver"].error = AutoDiscovery.ERROR_INVALID_HOMESERVER; @@ -345,7 +345,7 @@ export class AutoDiscovery { const response = await this.fetchWellKnownObject(`https://${domain}/.well-known/matrix/client`); if (!response) return {}; - return response.raw || {}; + return response.raw ?? {}; } /** diff --git a/src/client.ts b/src/client.ts index 820b36386cd..f34996686e7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -36,7 +36,11 @@ import { StubStore } from "./store/stub"; import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call"; import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter"; import { CallEventHandlerEvent, CallEventHandler, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler"; -import { GroupCallEventHandlerEvent, GroupCallEventHandlerEventHandlerMap } from "./webrtc/groupCallEventHandler"; +import { + GroupCallEventHandler, + GroupCallEventHandlerEvent, + GroupCallEventHandlerEventHandlerMap, +} from "./webrtc/groupCallEventHandler"; import * as utils from "./utils"; import { replaceParam, QueryDict, sleep, noUnsafeEventProps, safeSet } from "./utils"; import { Direction, EventTimeline } from "./models/event-timeline"; @@ -180,7 +184,6 @@ import { IThreepid } from "./@types/threepids"; import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base"; import { GroupCall, IGroupCallDataChannelOptions, GroupCallIntent, GroupCallType } from "./webrtc/groupCall"; import { MediaHandler } from "./webrtc/mediaHandler"; -import { GroupCallEventHandler } from "./webrtc/groupCallEventHandler"; import { LoginTokenPostResponse, ILoginFlowsResponse, IRefreshTokenResponse, SSOAction } from "./@types/auth"; import { TypedEventEmitter } from "./models/typed-event-emitter"; import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts"; @@ -4087,27 +4090,23 @@ export class MatrixClient extends TypedEventEmitter(Method.Post, path, queryString, data); + const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias }); + const res = await this.http.authedRequest<{ room_id: string }>(Method.Post, path, queryString, data); - const roomId = res.room_id; - const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); - const room = syncApi.createRoom(roomId); - if (opts.syncRoom) { - // v2 will do this for us - // return syncApi.syncRoom(room); - } - return room; - } catch (e) { - throw e; // rethrow for reject + const roomId = res.room_id; + const syncApi = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions()); + const syncRoom = syncApi.createRoom(roomId); + if (opts.syncRoom) { + // v2 will do this for us + // return syncApi.syncRoom(room); } + return syncRoom; } /** @@ -4689,7 +4688,7 @@ export class MatrixClient extends TypedEventEmitter { const room = this.getRoom(roomId); - if (room && room.hasPendingEvent(rmEventId)) { + if (room?.hasPendingEvent(rmEventId)) { throw new Error(`Cannot set read marker to a pending event (${rmEventId})`); } @@ -5058,9 +5057,8 @@ export class MatrixClient extends TypedEventEmitter( diff --git a/src/crypto/CrossSigning.ts b/src/crypto/CrossSigning.ts index a77682e4cf0..e71967cb2a5 100644 --- a/src/crypto/CrossSigning.ts +++ b/src/crypto/CrossSigning.ts @@ -688,7 +688,7 @@ export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: O _expectedPublicKey: string, ): Promise { const key = await new Promise((resolve) => { - return store.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { + store.doTxn("readonly", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { store.getSecretStorePrivateKey(txn, resolve, type); }); }); @@ -790,7 +790,7 @@ export async function requestKeysDuringVerification( })(); // We call getCrossSigningKey() for its side-effects - return Promise.race([ + Promise.race([ Promise.all([ crossSigning.getCrossSigningKey("master"), crossSigning.getCrossSigningKey("self_signing"), diff --git a/src/crypto/DeviceList.ts b/src/crypto/DeviceList.ts index a1ff0ebf144..8ad3831893a 100644 --- a/src/crypto/DeviceList.ts +++ b/src/crypto/DeviceList.ts @@ -116,7 +116,7 @@ export class DeviceList extends TypedEventEmitter { await this.cryptoStore.doTxn("readonly", [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => { this.cryptoStore.getEndToEndDeviceData(txn, (deviceData) => { - this.hasFetched = Boolean(deviceData && deviceData.devices); + this.hasFetched = Boolean(deviceData?.devices); this.devices = deviceData ? deviceData.devices : {}; this.crossSigningInfo = deviceData ? deviceData.crossSigningInfo || {} : {}; this.deviceTrackingStatus = deviceData ? deviceData.trackingStatus : {}; diff --git a/src/crypto/store/memory-crypto-store.ts b/src/crypto/store/memory-crypto-store.ts index a78574850c5..3e264a42926 100644 --- a/src/crypto/store/memory-crypto-store.ts +++ b/src/crypto/store/memory-crypto-store.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { logger } from "../../logger"; -import { deepCompare, promiseTry } from "../../utils"; +import { safeSet, deepCompare, promiseTry } from "../../utils"; import { CryptoStore, IDeviceData, @@ -33,7 +33,6 @@ import { ICrossSigningKey } from "../../client"; import { IOlmDevice } from "../algorithms/megolm"; import { IRoomEncryption } from "../RoomList"; import { InboundGroupSessionData } from "../OlmDevice"; -import { safeSet } from "../../utils"; /** * Internal module. in-memory storage for e2e. diff --git a/src/crypto/verification/request/InRoomChannel.ts b/src/crypto/verification/request/InRoomChannel.ts index ae8ced5ffe2..d66032bf8f1 100644 --- a/src/crypto/verification/request/InRoomChannel.ts +++ b/src/crypto/verification/request/InRoomChannel.ts @@ -125,7 +125,7 @@ export class InRoomChannel implements IVerificationChannel { // part of a verification request, so be noisy when rejecting something if (type === REQUEST_TYPE) { if (!content || typeof content.to !== "string" || !content.to.length) { - logger.log("InRoomChannel: validateEvent: " + "no valid to " + (content && content.to)); + logger.log("InRoomChannel: validateEvent: " + "no valid to " + content.to); return false; } @@ -134,7 +134,7 @@ export class InRoomChannel implements IVerificationChannel { logger.log( "InRoomChannel: validateEvent: " + `not directed to or sent by me: ${event.getSender()}` + - `, ${content && content.to}`, + `, ${content.to}`, ); return false; } diff --git a/src/embedded.ts b/src/embedded.ts index a08b79a2f5c..c2c30105a42 100644 --- a/src/embedded.ts +++ b/src/embedded.ts @@ -25,14 +25,13 @@ import { ISendEventFromWidgetResponseData, } from "matrix-widget-api"; -import { IEvent, IContent, EventStatus } from "./models/event"; +import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event"; import { ISendEventResponse } from "./@types/requests"; import { EventType } from "./@types/event"; import { logger } from "./logger"; import { MatrixClient, ClientEvent, IMatrixClientCreateOpts, IStartClientOpts, SendToDeviceContentMap } from "./client"; import { SyncApi, SyncState } from "./sync"; import { SlidingSyncSdk } from "./sliding-sync-sdk"; -import { MatrixEvent } from "./models/event"; import { User } from "./models/user"; import { Room } from "./models/room"; import { ToDeviceBatch, ToDevicePayload } from "./models/ToDeviceMessage"; From fba3cf27565acc8829fcf2ec66c50ab8fd018987 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Fri, 19 May 2023 16:35:32 +0100 Subject: [PATCH 65/94] Prepare changelog for v25.2.0-rc.5 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c776ccad4..0d15a4cf7b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [25.2.0-rc.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.5) (2023-05-19) +============================================================================================================ + +## 🐛 Bug Fixes + * Attempt a potential workaround for stuck notifs ([\#3384](https://github.com/matrix-org/matrix-js-sdk/pull/3384)). Fixes vector-im/element-web#25406. Contributed by @andybalaam. + Changes in [25.2.0-rc.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.4) (2023-05-16) ============================================================================================================ From ff07cb642f4d2b113f680b997a6bbe4a770e1899 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Fri, 19 May 2023 16:35:34 +0100 Subject: [PATCH 66/94] v25.2.0-rc.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0dbe23662d8..0d2138e754e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.2.0-rc.4", + "version": "25.2.0-rc.5", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From b62fac9dadb845f517bf49494ffe6966501352e2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 19 May 2023 16:39:02 +0100 Subject: [PATCH 67/94] Update client.ts --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index f34996686e7..f35fb94c201 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4688,7 +4688,7 @@ export class MatrixClient extends TypedEventEmitter Date: Mon, 22 May 2023 12:02:10 +0100 Subject: [PATCH 68/94] Combine `QrCodeEvent`, `SasEvent` and `VerificationEvent` (#3386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move IReciprocateQr to `crypto-api` and rename * Move ISasEvent to `crypto-api`, and rename ... and add some ✨comments✨ * Combine QrCodeEvent, SasEvent and VerificationEvent together ... as a precursor to extracting a single `Verifier` interface for `SAS` and `ReciprocateQRCode`. `enum`s are slightly magical things that have both a type and a value, so we have to re-export their backwards-compatibility fudges twice. * Update src/crypto/verification/Base.ts --- src/crypto-api.ts | 2 + src/crypto-api/verification.ts | 112 ++++++++++++++++++++++++++++++ src/crypto/verification/Base.ts | 21 ++++-- src/crypto/verification/QRCode.ts | 23 +++--- src/crypto/verification/SAS.ts | 43 +++++------- 5 files changed, 153 insertions(+), 48 deletions(-) create mode 100644 src/crypto-api/verification.ts diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 5af016674fb..50d82bc4c7e 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -260,3 +260,5 @@ export class DeviceVerificationStatus { return this.localVerified || (this.trustCrossSignedDevices && this.crossSigningVerified); } } + +export * from "./crypto-api/verification"; diff --git a/src/crypto-api/verification.ts b/src/crypto-api/verification.ts new file mode 100644 index 00000000000..b78c713eeb2 --- /dev/null +++ b/src/crypto-api/verification.ts @@ -0,0 +1,112 @@ +/* +Copyright 2023 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 "../models/event"; + +/** Events emitted by `Verifier`. */ +export enum VerifierEvent { + /** + * The verification has been cancelled, by us or the other side. + * + * The payload is either an {@link Error}, or an (incoming or outgoing) {@link MatrixEvent}, depending on + * unspecified reasons. + */ + Cancel = "cancel", + + /** + * SAS data has been exchanged and should be displayed to the user. + * + * The payload is the {@link ShowQrCodeCallbacks} object. + */ + ShowSas = "show_sas", + + /** + * QR code data should be displayed to the user. + * + * The payload is the {@link ShowQrCodeCallbacks} object. + */ + ShowReciprocateQr = "show_reciprocate_qr", +} + +/** Listener type map for {@link VerifierEvent}s. */ +export type VerifierEventHandlerMap = { + [VerifierEvent.Cancel]: (e: Error | MatrixEvent) => void; + [VerifierEvent.ShowSas]: (sas: ShowSasCallbacks) => void; + [VerifierEvent.ShowReciprocateQr]: (qr: ShowQrCodeCallbacks) => void; +}; + +/** + * Callbacks for user actions while a QR code is displayed. + * + * This is exposed as the payload of a `VerifierEvent.ShowReciprocateQr` event, or can be retrieved directly from the + * verifier as `reciprocateQREvent`. + */ +export interface ShowQrCodeCallbacks { + /** The user confirms that the verification data matches */ + confirm(): void; + + /** Cancel the verification flow */ + cancel(): void; +} + +/** + * Callbacks for user actions while a SAS is displayed. + * + * This is exposed as the payload of a `VerifierEvent.ShowSas` event, or directly from the verifier as `sasEvent`. + */ +export interface ShowSasCallbacks { + /** The generated SAS to be shown to the user */ + sas: GeneratedSas; + + /** Function to call if the user confirms that the SAS matches. + * + * @returns A Promise that completes once the m.key.verification.mac is queued. + */ + confirm(): Promise; + + /** + * Function to call if the user finds the SAS does not match. + * + * Sends an `m.key.verification.cancel` event with a `m.mismatched_sas` error code. + */ + mismatch(): void; + + /** Cancel the verification flow */ + cancel(): void; +} + +/** A generated SAS to be shown to the user, in alternative formats */ +export interface GeneratedSas { + /** + * The SAS as three numbers between 0 and 8191. + * + * Only populated if the `decimal` SAS method was negotiated. + */ + decimal?: [number, number, number]; + + /** + * The SAS as seven emojis. + * + * Only populated if the `emoji` SAS method was negotiated. + */ + emoji?: EmojiMapping[]; +} + +/** + * An emoji for the generated SAS. A tuple `[emoji, name]` where `emoji` is the emoji itself and `name` is the + * English name. + */ +export type EmojiMapping = [emoji: string, name: string]; diff --git a/src/crypto/verification/Base.ts b/src/crypto/verification/Base.ts index 89c700c231c..89bead3aa3e 100644 --- a/src/crypto/verification/Base.ts +++ b/src/crypto/verification/Base.ts @@ -28,7 +28,8 @@ import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossS import { IVerificationChannel } from "./request/Channel"; import { MatrixClient } from "../../client"; import { VerificationRequest } from "./request/VerificationRequest"; -import { ListenerMap, TypedEventEmitter } from "../../models/typed-event-emitter"; +import { TypedEventEmitter } from "../../models/typed-event-emitter"; +import { VerifierEvent, VerifierEventHandlerMap } from "../../crypto-api/verification"; const timeoutException = new Error("Verification timed out"); @@ -40,18 +41,24 @@ export class SwitchStartEventError extends Error { export type KeyVerifier = (keyId: string, device: DeviceInfo, keyInfo: string) => void; -export enum VerificationEvent { - Cancel = "cancel", -} +/** @deprecated use VerifierEvent */ +export type VerificationEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const VerificationEvent = VerifierEvent; +/** @deprecated use VerifierEventHandlerMap */ export type VerificationEventHandlerMap = { [VerificationEvent.Cancel]: (e: Error | MatrixEvent) => void; }; +// The type parameters of VerificationBase are no longer used, but we need some placeholders to maintain +// backwards compatibility with applications that reference the class. export class VerificationBase< - Events extends string, - Arguments extends ListenerMap, -> extends TypedEventEmitter { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Events extends string = VerifierEvent, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Arguments = VerifierEventHandlerMap, +> extends TypedEventEmitter { private cancelled = false; private _done = false; private promise: Promise | null = null; diff --git a/src/crypto/verification/QRCode.ts b/src/crypto/verification/QRCode.ts index bfb532e4223..38feade12db 100644 --- a/src/crypto/verification/QRCode.ts +++ b/src/crypto/verification/QRCode.ts @@ -18,7 +18,7 @@ limitations under the License. * QR code key verification. */ -import { VerificationBase as Base, VerificationEventHandlerMap } from "./Base"; +import { VerificationBase as Base } from "./Base"; import { newKeyMismatchError, newUserCancelledError } from "./Error"; import { decodeBase64, encodeUnpaddedBase64 } from "../olmlib"; import { logger } from "../../logger"; @@ -26,25 +26,18 @@ import { VerificationRequest } from "./request/VerificationRequest"; import { MatrixClient } from "../../client"; import { IVerificationChannel } from "./request/Channel"; import { MatrixEvent } from "../../models/event"; +import { ShowQrCodeCallbacks, VerifierEvent } from "../../crypto-api/verification"; export const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1"; export const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1"; -interface IReciprocateQr { - confirm(): void; - cancel(): void; -} - -export enum QrCodeEvent { - ShowReciprocateQr = "show_reciprocate_qr", -} - -type EventHandlerMap = { - [QrCodeEvent.ShowReciprocateQr]: (qr: IReciprocateQr) => void; -} & VerificationEventHandlerMap; +/** @deprecated use VerifierEvent */ +export type QrCodeEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const QrCodeEvent = VerifierEvent; -export class ReciprocateQRCode extends Base { - public reciprocateQREvent?: IReciprocateQr; +export class ReciprocateQRCode extends Base { + public reciprocateQREvent?: ShowQrCodeCallbacks; public static factory( channel: IVerificationChannel, diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index a8d237d2da1..e8feaddca47 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -21,7 +21,7 @@ limitations under the License. import anotherjson from "another-json"; import { Utility, SAS as OlmSAS } from "@matrix-org/olm"; -import { VerificationBase as Base, SwitchStartEventError, VerificationEventHandlerMap } from "./Base"; +import { VerificationBase as Base, SwitchStartEventError } from "./Base"; import { errorFactory, newInvalidMessageError, @@ -33,6 +33,14 @@ import { logger } from "../../logger"; import { IContent, MatrixEvent } from "../../models/event"; import { generateDecimalSas } from "./SASDecimal"; import { EventType } from "../../@types/event"; +import { EmojiMapping, GeneratedSas, ShowSasCallbacks, VerifierEvent } from "../../crypto-api/verification"; + +// backwards-compatibility exports +export { + ShowSasCallbacks as ISasEvent, + GeneratedSas as IGeneratedSas, + EmojiMapping, +} from "../../crypto-api/verification"; const START_TYPE = EventType.KeyVerificationStart; @@ -44,8 +52,6 @@ const newMismatchedSASError = errorFactory("m.mismatched_sas", "Mismatched short const newMismatchedCommitmentError = errorFactory("m.mismatched_commitment", "Mismatched commitment"); -type EmojiMapping = [emoji: string, name: string]; - const emojiMapping: EmojiMapping[] = [ ["🐶", "dog"], // 0 ["🐱", "cat"], // 1 @@ -133,20 +139,8 @@ const sasGenerators = { emoji: generateEmojiSas, } as const; -export interface IGeneratedSas { - decimal?: [number, number, number]; - emoji?: EmojiMapping[]; -} - -export interface ISasEvent { - sas: IGeneratedSas; - confirm(): Promise; - cancel(): void; - mismatch(): void; -} - -function generateSas(sasBytes: Uint8Array, methods: string[]): IGeneratedSas { - const sas: IGeneratedSas = {}; +function generateSas(sasBytes: Uint8Array, methods: string[]): GeneratedSas { + const sas: GeneratedSas = {}; for (const method of methods) { if (method in sasGenerators) { // @ts-ignore - ts doesn't like us mixing types like this @@ -220,19 +214,16 @@ function intersection(anArray: T[], aSet: Set): T[] { return Array.isArray(anArray) ? anArray.filter((x) => aSet.has(x)) : []; } -export enum SasEvent { - ShowSas = "show_sas", -} - -type EventHandlerMap = { - [SasEvent.ShowSas]: (sas: ISasEvent) => void; -} & VerificationEventHandlerMap; +/** @deprecated use VerifierEvent */ +export type SasEvent = VerifierEvent; +/** @deprecated use VerifierEvent */ +export const SasEvent = VerifierEvent; -export class SAS extends Base { +export class SAS extends Base { private waitingForAccept?: boolean; public ourSASPubKey?: string; public theirSASPubKey?: string; - public sasEvent?: ISasEvent; + public sasEvent?: ShowSasCallbacks; // eslint-disable-next-line @typescript-eslint/naming-convention public static get NAME(): string { From 063d69eff15025080e60c0d41f27af26fe1557df Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Tue, 23 May 2023 22:34:18 +0200 Subject: [PATCH 69/94] Fix mark as unread button (#3393) * Fix mark as unread button * Revert to prefer the last event from the main timeline * Refactor room test * Fix type * Improve docs * Insert events to the end of the timeline * Improve test doc --- spec/test-utils/thread.ts | 22 +++++++- spec/unit/room.spec.ts | 116 ++++++++++++++++++++++---------------- src/models/room.ts | 2 +- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/spec/test-utils/thread.ts b/spec/test-utils/thread.ts index 6232f20d70c..ebca5720fe4 100644 --- a/spec/test-utils/thread.ts +++ b/spec/test-utils/thread.ts @@ -115,6 +115,26 @@ type MakeThreadProps = { ts?: number; }; +type MakeThreadResult = { + /** + * Thread model + */ + thread: Thread; + /** + * Thread root event + */ + rootEvent: MatrixEvent; + /** + * Events added to the thread + */ + events: MatrixEvent[]; +}; + +/** + * Starts a new thread in a room by creating a message as thread root. + * Also creates a Thread model and adds it to the room. + * Does not insert the messages into a timeline. + */ export const mkThread = ({ room, client, @@ -122,7 +142,7 @@ export const mkThread = ({ participantUserIds, length = 2, ts = 1, -}: MakeThreadProps): { thread: Thread; rootEvent: MatrixEvent; events: MatrixEvent[] } => { +}: MakeThreadProps): MakeThreadResult => { const { rootEvent, events } = makeThreadEvents({ roomId: room.roomId, authorId, diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 2445ee4fd1f..9f5479031cd 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -51,7 +51,7 @@ import { TestClient } from "../TestClient"; import { ReceiptType, WrappedReceipt } from "../../src/@types/read_receipts"; import { FeatureSupport, Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../src/models/thread"; import { Crypto } from "../../src/crypto"; -import { mkThread } from "../test-utils/thread"; +import * as threadUtils from "../test-utils/thread"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../test-utils/client"; import { logger } from "../../src/logger"; import { IMessageOpts } from "../test-utils/test-utils"; @@ -168,30 +168,45 @@ describe("Room", function () { room.client, ); - const addRoomMainAndThreadMessages = ( - room: Room, - tsMain?: number, - tsThread?: number, - ): { mainEvent?: MatrixEvent; threadEvent?: MatrixEvent } => { - const result: { mainEvent?: MatrixEvent; threadEvent?: MatrixEvent } = {}; - - if (tsMain) { - result.mainEvent = mkMessage({ ts: tsMain }); - room.addLiveEvents([result.mainEvent]); - } + /** + * @see threadUtils.mkThread + */ + const mkThread = ( + opts: Partial[0]>, + ): ReturnType => { + return threadUtils.mkThread({ + room, + client: new TestClient().client, + authorId: "@bob:example.org", + participantUserIds: ["@bob:example.org"], + ...opts, + }); + }; - if (tsThread) { - const { rootEvent, thread } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); - result.threadEvent = mkThreadResponse(rootEvent, { ts: tsThread }); - thread.liveTimeline.addEvent(result.threadEvent, { toStartOfTimeline: true }); - } + /** + * Creates a message and adds it to the end of the main live timeline. + * + * @param room - Room to add the message to + * @param timestamp - Timestamp of the message + * @return The message event + */ + const mkMessageInRoom = (room: Room, timestamp: number) => { + const message = mkMessage({ ts: timestamp }); + room.addLiveEvents([message]); + return message; + }; - return result; + /** + * Creates a message in a thread and adds it to the end of the thread live timeline. + * + * @param thread - Thread to add the message to + * @param timestamp - Timestamp of the message + * @returns The thread message event + */ + const mkMessageInThread = (thread: Thread, timestamp: number) => { + const message = mkThreadResponse(thread.rootEvent!, { ts: timestamp }); + thread.liveTimeline.addEvent(message, { toStartOfTimeline: false }); + return message; }; const addRoomThreads = ( @@ -202,24 +217,14 @@ describe("Room", function () { const result: { thread1?: Thread; thread2?: Thread } = {}; if (thread1EventTs !== null) { - const { rootEvent: thread1RootEvent, thread: thread1 } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { rootEvent: thread1RootEvent, thread: thread1 } = mkThread({ room }); const thread1Event = mkThreadResponse(thread1RootEvent, { ts: thread1EventTs }); thread1.liveTimeline.addEvent(thread1Event, { toStartOfTimeline: true }); result.thread1 = thread1; } if (thread2EventTs !== null) { - const { rootEvent: thread2RootEvent, thread: thread2 } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { rootEvent: thread2RootEvent, thread: thread2 } = mkThread({ room }); const thread2Event = mkThreadResponse(thread2RootEvent, { ts: thread2EventTs }); thread2.liveTimeline.addEvent(thread2Event, { toStartOfTimeline: true }); result.thread2 = thread2; @@ -2504,12 +2509,7 @@ describe("Room", function () { }); it("returns the same model when creating a thread twice", () => { - const { thread, rootEvent } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { thread, rootEvent } = mkThread({ room }); expect(thread).toBeInstanceOf(Thread); @@ -3534,32 +3534,50 @@ describe("Room", function () { }); describe("getLastLiveEvent", () => { - let lastEventInMainTimeline: MatrixEvent; - let lastEventInThread: MatrixEvent; - it("when there are no events, it should return undefined", () => { expect(room.getLastLiveEvent()).toBeUndefined(); }); it("when there is only an event in the main timeline and there are no threads, it should return the last event from the main timeline", () => { - lastEventInMainTimeline = addRoomMainAndThreadMessages(room, 23).mainEvent!; - room.addLiveEvents([lastEventInMainTimeline]); + const lastEventInMainTimeline = mkMessageInRoom(room, 23); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); + /** + * This should normally not happen. The test exists only for the sake of completeness. + * No event is added to the room's live timeline here. + */ it("when there is no event in the room live timeline but in a thread, it should return the last event from the thread", () => { - lastEventInThread = addRoomMainAndThreadMessages(room, undefined, 42).threadEvent!; + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 42); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); describe("when there are events in both, the main timeline and threads", () => { it("and the last event is in a thread, it should return the last event from the thread", () => { - lastEventInThread = addRoomMainAndThreadMessages(room, 23, 42).threadEvent!; + mkMessageInRoom(room, 23); + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 42); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); it("and the last event is in the main timeline, it should return the last event from the main timeline", () => { - lastEventInMainTimeline = addRoomMainAndThreadMessages(room, 42, 23).mainEvent!; + const lastEventInMainTimeline = mkMessageInRoom(room, 42); + const { thread } = mkThread({ room, length: 0 }); + mkMessageInThread(thread, 23); + expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); + }); + + it("and both events have the same timestamp, it should return the last event from the thread", () => { + mkMessageInRoom(room, 23); + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 23); + expect(room.getLastLiveEvent()).toBe(lastEventInThread); + }); + + it("and there is a thread without any messages, it should return the last event from the main timeline", () => { + const lastEventInMainTimeline = mkMessageInRoom(room, 23); + mkThread({ room, length: 0 }); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); }); diff --git a/src/models/room.ts b/src/models/room.ts index e16375707f1..f56d30629e4 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -809,7 +809,7 @@ export class Room extends ReadReceipt { const lastThreadEvent = lastThread.events[lastThread.events.length - 1]; - return (lastRoomEvent?.getTs() ?? 0) > (lastThreadEvent.getTs() ?? 0) ? lastRoomEvent : lastThreadEvent; + return (lastRoomEvent?.getTs() ?? 0) > (lastThreadEvent?.getTs() ?? 0) ? lastRoomEvent : lastThreadEvent; } /** From a7d503a8ac9b7a60d58ebc657fe0ae77db414e08 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Wed, 24 May 2023 09:02:18 +0100 Subject: [PATCH 70/94] [Backport staging] Fix mark as unread button (#3401) Co-authored-by: Michael Weimann --- spec/test-utils/thread.ts | 22 +++++++- spec/unit/room.spec.ts | 116 ++++++++++++++++++++++---------------- src/models/room.ts | 2 +- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/spec/test-utils/thread.ts b/spec/test-utils/thread.ts index 6232f20d70c..ebca5720fe4 100644 --- a/spec/test-utils/thread.ts +++ b/spec/test-utils/thread.ts @@ -115,6 +115,26 @@ type MakeThreadProps = { ts?: number; }; +type MakeThreadResult = { + /** + * Thread model + */ + thread: Thread; + /** + * Thread root event + */ + rootEvent: MatrixEvent; + /** + * Events added to the thread + */ + events: MatrixEvent[]; +}; + +/** + * Starts a new thread in a room by creating a message as thread root. + * Also creates a Thread model and adds it to the room. + * Does not insert the messages into a timeline. + */ export const mkThread = ({ room, client, @@ -122,7 +142,7 @@ export const mkThread = ({ participantUserIds, length = 2, ts = 1, -}: MakeThreadProps): { thread: Thread; rootEvent: MatrixEvent; events: MatrixEvent[] } => { +}: MakeThreadProps): MakeThreadResult => { const { rootEvent, events } = makeThreadEvents({ roomId: room.roomId, authorId, diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 8caca90bee8..3dcbacbb70b 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -51,7 +51,7 @@ import { TestClient } from "../TestClient"; import { ReceiptType, WrappedReceipt } from "../../src/@types/read_receipts"; import { FeatureSupport, Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../src/models/thread"; import { Crypto } from "../../src/crypto"; -import { mkThread } from "../test-utils/thread"; +import * as threadUtils from "../test-utils/thread"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../test-utils/client"; import { logger } from "../../src/logger"; import { IMessageOpts } from "../test-utils/test-utils"; @@ -168,30 +168,45 @@ describe("Room", function () { room.client, ); - const addRoomMainAndThreadMessages = ( - room: Room, - tsMain?: number, - tsThread?: number, - ): { mainEvent?: MatrixEvent; threadEvent?: MatrixEvent } => { - const result: { mainEvent?: MatrixEvent; threadEvent?: MatrixEvent } = {}; - - if (tsMain) { - result.mainEvent = mkMessage({ ts: tsMain }); - room.addLiveEvents([result.mainEvent]); - } + /** + * @see threadUtils.mkThread + */ + const mkThread = ( + opts: Partial[0]>, + ): ReturnType => { + return threadUtils.mkThread({ + room, + client: new TestClient().client, + authorId: "@bob:example.org", + participantUserIds: ["@bob:example.org"], + ...opts, + }); + }; - if (tsThread) { - const { rootEvent, thread } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); - result.threadEvent = mkThreadResponse(rootEvent, { ts: tsThread }); - thread.liveTimeline.addEvent(result.threadEvent, { toStartOfTimeline: true }); - } + /** + * Creates a message and adds it to the end of the main live timeline. + * + * @param room - Room to add the message to + * @param timestamp - Timestamp of the message + * @return The message event + */ + const mkMessageInRoom = (room: Room, timestamp: number) => { + const message = mkMessage({ ts: timestamp }); + room.addLiveEvents([message]); + return message; + }; - return result; + /** + * Creates a message in a thread and adds it to the end of the thread live timeline. + * + * @param thread - Thread to add the message to + * @param timestamp - Timestamp of the message + * @returns The thread message event + */ + const mkMessageInThread = (thread: Thread, timestamp: number) => { + const message = mkThreadResponse(thread.rootEvent!, { ts: timestamp }); + thread.liveTimeline.addEvent(message, { toStartOfTimeline: false }); + return message; }; const addRoomThreads = ( @@ -202,24 +217,14 @@ describe("Room", function () { const result: { thread1?: Thread; thread2?: Thread } = {}; if (thread1EventTs !== null) { - const { rootEvent: thread1RootEvent, thread: thread1 } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { rootEvent: thread1RootEvent, thread: thread1 } = mkThread({ room }); const thread1Event = mkThreadResponse(thread1RootEvent, { ts: thread1EventTs }); thread1.liveTimeline.addEvent(thread1Event, { toStartOfTimeline: true }); result.thread1 = thread1; } if (thread2EventTs !== null) { - const { rootEvent: thread2RootEvent, thread: thread2 } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { rootEvent: thread2RootEvent, thread: thread2 } = mkThread({ room }); const thread2Event = mkThreadResponse(thread2RootEvent, { ts: thread2EventTs }); thread2.liveTimeline.addEvent(thread2Event, { toStartOfTimeline: true }); result.thread2 = thread2; @@ -2510,12 +2515,7 @@ describe("Room", function () { }); it("returns the same model when creating a thread twice", () => { - const { thread, rootEvent } = mkThread({ - room, - client: new TestClient().client, - authorId: "@bob:example.org", - participantUserIds: ["@bob:example.org"], - }); + const { thread, rootEvent } = mkThread({ room }); expect(thread).toBeInstanceOf(Thread); @@ -3540,32 +3540,50 @@ describe("Room", function () { }); describe("getLastLiveEvent", () => { - let lastEventInMainTimeline: MatrixEvent; - let lastEventInThread: MatrixEvent; - it("when there are no events, it should return undefined", () => { expect(room.getLastLiveEvent()).toBeUndefined(); }); it("when there is only an event in the main timeline and there are no threads, it should return the last event from the main timeline", () => { - lastEventInMainTimeline = addRoomMainAndThreadMessages(room, 23).mainEvent!; - room.addLiveEvents([lastEventInMainTimeline]); + const lastEventInMainTimeline = mkMessageInRoom(room, 23); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); + /** + * This should normally not happen. The test exists only for the sake of completeness. + * No event is added to the room's live timeline here. + */ it("when there is no event in the room live timeline but in a thread, it should return the last event from the thread", () => { - lastEventInThread = addRoomMainAndThreadMessages(room, undefined, 42).threadEvent!; + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 42); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); describe("when there are events in both, the main timeline and threads", () => { it("and the last event is in a thread, it should return the last event from the thread", () => { - lastEventInThread = addRoomMainAndThreadMessages(room, 23, 42).threadEvent!; + mkMessageInRoom(room, 23); + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 42); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); it("and the last event is in the main timeline, it should return the last event from the main timeline", () => { - lastEventInMainTimeline = addRoomMainAndThreadMessages(room, 42, 23).mainEvent!; + const lastEventInMainTimeline = mkMessageInRoom(room, 42); + const { thread } = mkThread({ room, length: 0 }); + mkMessageInThread(thread, 23); + expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); + }); + + it("and both events have the same timestamp, it should return the last event from the thread", () => { + mkMessageInRoom(room, 23); + const { thread } = mkThread({ room, length: 0 }); + const lastEventInThread = mkMessageInThread(thread, 23); + expect(room.getLastLiveEvent()).toBe(lastEventInThread); + }); + + it("and there is a thread without any messages, it should return the last event from the main timeline", () => { + const lastEventInMainTimeline = mkMessageInRoom(room, 23); + mkThread({ room, length: 0 }); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); }); diff --git a/src/models/room.ts b/src/models/room.ts index e16375707f1..f56d30629e4 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -809,7 +809,7 @@ export class Room extends ReadReceipt { const lastThreadEvent = lastThread.events[lastThread.events.length - 1]; - return (lastRoomEvent?.getTs() ?? 0) > (lastThreadEvent.getTs() ?? 0) ? lastRoomEvent : lastThreadEvent; + return (lastRoomEvent?.getTs() ?? 0) > (lastThreadEvent?.getTs() ?? 0) ? lastRoomEvent : lastThreadEvent; } /** From 4732098731311172e8339dfd64432579dd2b1239 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 24 May 2023 11:29:57 +0100 Subject: [PATCH 71/94] Test for inserting events into the timeline in timestamp order (#3391) * Move mkReaction into test-utils so it can be used by other code * Basic test for inserting messages into the thread timeline --- spec/test-utils/test-utils.ts | 42 ++++++++++++++++++++++++- spec/unit/models/thread.spec.ts | 54 +++++++++++++++++++++++++++++++-- spec/unit/room.spec.ts | 38 ++++++----------------- 3 files changed, 103 insertions(+), 31 deletions(-) diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index d164272d91b..ff41b35960d 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -6,7 +6,7 @@ import "../olm-loader"; import { logger } from "../../src/logger"; import { IContent, IEvent, IEventRelation, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event"; -import { ClientEvent, EventType, IPusher, MatrixClient, MsgType } from "../../src"; +import { ClientEvent, EventType, IPusher, MatrixClient, MsgType, RelationType } from "../../src"; import { SyncState } from "../../src/sync"; import { eventMapperFor } from "../../src/event-mapper"; @@ -258,6 +258,9 @@ export interface IMessageOpts { * @param opts.user - The user ID for the event. * @param opts.msg - Optional. The content.body for the event. * @param opts.event - True to make a MatrixEvent. + * @param opts.relatesTo - An IEventRelation relating this to another event. + * @param opts.ts - The timestamp of the event. + * @param opts.event - True to make a MatrixEvent. * @param client - If passed along with opts.event=true will be used to set up re-emitters. * @returns The event */ @@ -297,6 +300,7 @@ interface IReplyMessageOpts extends IMessageOpts { * @param opts.room - The room ID for the event. * @param opts.user - The user ID for the event. * @param opts.msg - Optional. The content.body for the event. + * @param opts.ts - The timestamp of the event. * @param opts.replyToMessage - The replied message * @param opts.event - True to make a MatrixEvent. * @param client - If passed along with opts.event=true will be used to set up re-emitters. @@ -330,6 +334,42 @@ export function mkReplyMessage( return mkEvent(eventOpts, client); } +/** + * Create a reaction event. + * + * @param target - the event we are reacting to. + * @param client - the MatrixClient + * @param userId - the userId of the sender + * @param roomId - the id of the room we are in + * @param ts - The timestamp of the event. + * @returns The event + */ +export function mkReaction( + target: MatrixEvent, + client: MatrixClient, + userId: string, + roomId: string, + ts?: number, +): MatrixEvent { + return mkEvent( + { + event: true, + type: EventType.Reaction, + user: userId, + room: roomId, + content: { + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: target.getId()!, + key: Math.random().toString(), + }, + }, + ts, + }, + client, + ); +} + /** * A mock implementation of webstorage */ diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index d1e25c84274..bd17f00c8aa 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -14,17 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { mocked } from "jest-mock"; + import { MatrixClient, PendingEventOrdering } from "../../../src/client"; import { Room } from "../../../src/models/room"; -import { Thread, THREAD_RELATION_TYPE, ThreadEvent } from "../../../src/models/thread"; +import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../../../src/models/thread"; import { mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; -import { emitPromise, mkMessage, mock } from "../../test-utils/test-utils"; +import { emitPromise, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; import { Direction, EventStatus, MatrixEvent } from "../../../src"; import { ReceiptType } from "../../../src/@types/read_receipts"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client"; import { ReEmitter } from "../../../src/ReEmitter"; import { Feature, ServerSupport } from "../../../src/feature"; +import { eventMapperFor } from "../../../src/event-mapper"; describe("Thread", () => { describe("constructor", () => { @@ -424,4 +427,51 @@ describe("Thread", () => { expect(mock).toHaveBeenCalledWith("b1", "f1"); }); }); + + describe("insertEventIntoTimeline", () => { + it("Inserts a reply in timestamp order", () => { + // Assumption: no server side support because if we have it, events + // can only be added to the timeline after the thread has been + // initialised, and we are not properly initialising it here. + expect(Thread.hasServerSideSupport).toBe(FeatureSupport.None); + + const client = createClientWithEventMapper(); + const userId = "user1"; + const room = new Room("room1", client, userId); + + // Given a thread with a root plus 5 messages + const { thread, events } = mkThread({ + room, + client, + authorId: userId, + participantUserIds: ["@bob:hs", "@chia:hs", "@dv:hs"], + length: 6, + ts: 100, // Events will be at ts 100, 101, 102, 103, 104 and 105 + }); + + // When we insert a reply to the second thread message + const replyEvent = mkReaction(events[2], client, userId, room.roomId, 104); + thread.insertEventIntoTimeline(replyEvent); + + // Then the reply is inserted based on its timestamp + expect(thread.events.map((ev) => ev.getId())).toEqual([ + events[0].getId(), + events[1].getId(), + events[2].getId(), + events[3].getId(), + events[4].getId(), + replyEvent.getId(), + events[5].getId(), + ]); + }); + + function createClientWithEventMapper(): MatrixClient { + const client = mock(MatrixClient, "MatrixClient"); + client.reEmitter = mock(ReEmitter, "ReEmitter"); + client.canSupport = new Map(); + jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); + mocked(client.supportsThreads).mockReturnValue(true); + return client; + } + }); }); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 9f5479031cd..e168a5bf628 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -137,24 +137,6 @@ describe("Room", function () { room.client, ); - const mkReaction = (target: MatrixEvent) => - utils.mkEvent( - { - event: true, - type: EventType.Reaction, - user: userA, - room: roomId, - content: { - "m.relates_to": { - rel_type: RelationType.Annotation, - event_id: target.getId()!, - key: Math.random().toString(), - }, - }, - }, - room.client, - ); - const mkRedaction = (target: MatrixEvent) => utils.mkEvent( { @@ -2674,7 +2656,7 @@ describe("Room", function () { threadResponse1.localTimestamp += 1000; const threadResponse2 = mkThreadResponse(threadRoot); threadResponse2.localTimestamp += 2000; - const threadResponse2Reaction = mkReaction(threadResponse2); + const threadResponse2Reaction = utils.mkReaction(threadResponse2, room.client, userA, roomId); room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({ @@ -2714,7 +2696,7 @@ describe("Room", function () { threadResponse1.localTimestamp += 1000; const threadResponse2 = mkThreadResponse(threadRoot); threadResponse2.localTimestamp += 2000; - const threadResponse2Reaction = mkReaction(threadResponse2); + const threadResponse2Reaction = utils.mkReaction(threadResponse2, room.client, userA, roomId); room.client.fetchRoomEvent = (eventId: string) => Promise.resolve({ @@ -2862,8 +2844,8 @@ describe("Room", function () { const randomMessage = mkMessage(); const threadRoot = mkMessage(); const threadResponse1 = mkThreadResponse(threadRoot); - const threadReaction1 = mkReaction(threadRoot); - const threadReaction2 = mkReaction(threadRoot); + const threadReaction1 = utils.mkReaction(threadRoot, room.client, userA, roomId); + const threadReaction2 = utils.mkReaction(threadRoot, room.client, userA, roomId); const threadReaction2Redaction = mkRedaction(threadReaction2); const roots = new Set([threadRoot.getId()!]); @@ -2900,8 +2882,8 @@ describe("Room", function () { it("thread response and its relations&redactions should be only in thread timeline", () => { const threadRoot = mkMessage(); const threadResponse1 = mkThreadResponse(threadRoot); - const threadReaction1 = mkReaction(threadResponse1); - const threadReaction2 = mkReaction(threadResponse1); + const threadReaction1 = utils.mkReaction(threadResponse1, room.client, userA, roomId); + const threadReaction2 = utils.mkReaction(threadResponse1, room.client, userA, roomId); const threadReaction2Redaction = mkRedaction(threadReaction2); const roots = new Set([threadRoot.getId()!]); @@ -2922,8 +2904,8 @@ describe("Room", function () { const threadRoot = mkMessage(); const threadResponse1 = mkThreadResponse(threadRoot); const reply1 = mkReply(threadResponse1); - const reaction1 = mkReaction(reply1); - const reaction2 = mkReaction(reply1); + const reaction1 = utils.mkReaction(reply1, room.client, userA, roomId); + const reaction2 = utils.mkReaction(reply1, room.client, userA, roomId); const reaction2Redaction = mkRedaction(reply1); const roots = new Set([threadRoot.getId()!]); @@ -2957,9 +2939,9 @@ describe("Room", function () { it("should aggregate relations in thread event timeline set", async () => { Thread.setServerSideSupport(FeatureSupport.Stable); const threadRoot = mkMessage(); - const rootReaction = mkReaction(threadRoot); + const rootReaction = utils.mkReaction(threadRoot, room.client, userA, roomId); const threadResponse = mkThreadResponse(threadRoot); - const threadReaction = mkReaction(threadResponse); + const threadReaction = utils.mkReaction(threadResponse, room.client, userA, roomId); const events = [threadRoot, rootReaction, threadResponse, threadReaction]; From 729f924de1000e08dffb73aa2b522202ba07d375 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 24 May 2023 11:33:48 +0100 Subject: [PATCH 72/94] Prioritise entirely supported flows for UIA (#3402) * Prioritise entirely supported flows for UIA * Add tests * Fix types * Apply suggestions from code review Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> * Update src/interactive-auth.ts --------- Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> --- spec/unit/interactive-auth.spec.ts | 43 ++++++++++++++++++++++++++++++ src/interactive-auth.ts | 33 ++++++++++++++++++++--- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/spec/unit/interactive-auth.spec.ts b/spec/unit/interactive-auth.spec.ts index 48279530289..554d747352d 100644 --- a/spec/unit/interactive-auth.spec.ts +++ b/spec/unit/interactive-auth.spec.ts @@ -517,4 +517,47 @@ describe("InteractiveAuth", () => { expect(ia.getEmailSid()).toEqual(sid); }); }); + + it("should prioritise shorter flows", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + + const ia = new InteractiveAuth({ + matrixClient: getFakeClient(), + doRequest: doRequest, + stateUpdated: stateUpdated, + requestEmailToken: jest.fn(), + authData: { + session: "sessionId", + flows: [{ stages: [AuthType.Recaptcha, AuthType.Password] }, { stages: [AuthType.Password] }], + params: {}, + }, + }); + + // @ts-ignore + ia.chooseStage(); + expect(ia.getChosenFlow()?.stages).toEqual([AuthType.Password]); + }); + + it("should prioritise flows with entirely supported stages", async () => { + const doRequest = jest.fn(); + const stateUpdated = jest.fn(); + + const ia = new InteractiveAuth({ + matrixClient: getFakeClient(), + doRequest: doRequest, + stateUpdated: stateUpdated, + requestEmailToken: jest.fn(), + authData: { + session: "sessionId", + flows: [{ stages: ["com.devture.shared_secret_auth"] }, { stages: [AuthType.Password] }], + params: {}, + }, + supportedStages: [AuthType.Password], + }); + + // @ts-ignore + ia.chooseStage(); + expect(ia.getChosenFlow()?.stages).toEqual([AuthType.Password]); + }); }); diff --git a/src/interactive-auth.ts b/src/interactive-auth.ts index aac5ef53e8f..4d3f643f0d7 100644 --- a/src/interactive-auth.ts +++ b/src/interactive-auth.ts @@ -26,7 +26,7 @@ const EMAIL_STAGE_TYPE = "m.login.email.identity"; const MSISDN_STAGE_TYPE = "m.login.msisdn"; export interface UIAFlow { - stages: AuthType[]; + stages: Array; } export interface IInputs { @@ -156,6 +156,14 @@ interface IOpts { */ emailSid?: string; + /** + * If specified, will prefer flows which entirely consist of listed stages. + * These should normally be of type AuthTypes but can be string when supporting custom auth stages. + * + * This can be used to avoid needing the fallback mechanism. + */ + supportedStages?: Array; + /** * Called with the new auth dict to submit the request. * Also passes a second deprecated arg which is a flag set to true if this request is a background request. @@ -176,7 +184,7 @@ interface IOpts { * m.login.email.identity: * * emailSid: string, the sid of the active email auth session */ - stateUpdated(nextStage: AuthType, status: IStageStatus): void; + stateUpdated(nextStage: AuthType | string, status: IStageStatus): void; /** * A function that takes the email address (string), clientSecret (string), attempt number (int) and @@ -216,6 +224,7 @@ export class InteractiveAuth { private readonly busyChangedCallback?: IOpts["busyChanged"]; private readonly stateUpdatedCallback: IOpts["stateUpdated"]; private readonly requestEmailTokenCallback: IOpts["requestEmailToken"]; + private readonly supportedStages?: Set; private data: IAuthData; private emailSid?: string; @@ -243,6 +252,7 @@ export class InteractiveAuth { if (opts.sessionId) this.data.session = opts.sessionId; this.clientSecret = opts.clientSecret || this.matrixClient.generateClientSecret(); this.emailSid = opts.emailSid; + if (opts.supportedStages !== undefined) this.supportedStages = new Set(opts.supportedStages); } /** @@ -571,7 +581,7 @@ export class InteractiveAuth { * @returns login type * @throws {@link NoAuthFlowFoundError} If no suitable authentication flow can be found */ - private chooseStage(): AuthType | undefined { + private chooseStage(): AuthType | string | undefined { if (this.chosenFlow === null) { this.chosenFlow = this.chooseFlow(); } @@ -581,6 +591,17 @@ export class InteractiveAuth { return nextStage; } + // Returns a low number for flows we consider best. Counts increase for longer flows and even more so + // for flows which contain stages not listed in `supportedStages`. + private scoreFlow(flow: UIAFlow): number { + let score = flow.stages.length; + if (this.supportedStages !== undefined) { + // Add 10 points to the score for each unsupported stage in the flow. + score += flow.stages.filter((stage) => !this.supportedStages!.has(stage)).length * 10; + } + return score; + } + /** * Pick one of the flows from the returned list * If a flow using all of the inputs is found, it will @@ -603,6 +624,10 @@ export class InteractiveAuth { const haveEmail = Boolean(this.inputs.emailAddress) || Boolean(this.emailSid); const haveMsisdn = Boolean(this.inputs.phoneCountry) && Boolean(this.inputs.phoneNumber); + // Flows are not represented in a significant order, so we can choose any we support best + // Sort flows based on how many unsupported stages they contain ascending + flows.sort((a, b) => this.scoreFlow(a) - this.scoreFlow(b)); + for (const flow of flows) { let flowHasEmail = false; let flowHasMsisdn = false; @@ -633,7 +658,7 @@ export class InteractiveAuth { * @internal * @returns login type */ - private firstUncompletedStage(flow: UIAFlow): AuthType | undefined { + private firstUncompletedStage(flow: UIAFlow): AuthType | string | undefined { const completed = this.data.completed || []; return flow.stages.find((stageType) => !completed.includes(stageType)); } From ed71cdeccd279244eabbb2ad7ca9404d17072e30 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 24 May 2023 05:48:22 -0500 Subject: [PATCH 73/94] Add more context for why threaded vs unthreaded read receipts (#3378) * Add more context for why threaded vs unthreaded read receipts See https://github.com/matrix-org/matrix-js-sdk/pull/3339#discussion_r1195364120 * Language updates from Andy See https://github.com/matrix-org/matrix-js-sdk/pull/3378#discussion_r1197622113 * Fix lints --- src/receipt-accumulator.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/receipt-accumulator.ts b/src/receipt-accumulator.ts index f1037233239..ded358ad9a1 100644 --- a/src/receipt-accumulator.ts +++ b/src/receipt-accumulator.ts @@ -118,6 +118,26 @@ export class ReceiptAccumulator { eventId, }; + // In a world that supports threads, read receipts normally have + // a `thread_id` which is either the thread they belong in or + // `MAIN_ROOM_TIMELINE`, so we normally use `setThreaded(...)` + // here. The `MAIN_ROOM_TIMELINE` is just treated as another + // thread. + // + // We still encounter read receipts that are "unthreaded" + // (missing the `thread_id` property). These come from clients + // that don't support threads, and from threaded clients that + // are doing a "Mark room as read" operation. Unthreaded + // receipts mark everything "before" them as read, in all + // threads, where "before" means in Sync Order i.e. the order + // the events were received from the homeserver in a sync. + // [Note: we have some bugs where we use timestamp order instead + // of Sync Order, because we don't correctly remember the Sync + // Order. See #3325.] + // + // Calling the wrong method will cause incorrect behavior like + // messages re-appearing as "new" when you already read them + // previously. if (!data.thread_id) { this.setUnthreaded(userId, receipt); } else { From d7bcdff29bc5979c35386758efc4b3af9979fb27 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 24 May 2023 11:54:10 +0100 Subject: [PATCH 74/94] Fix re-exports for SAS.ts (#3405) These three are only types, not objects we can export. Fixes warnings in EW (and probably some build failures for someone somewhere): ``` 2023-05-24 11:27:28.294 [element-js] WARNING in ../matrix-js-sdk/src/crypto/verification/SAS.ts 31:0-123 2023-05-24 11:27:28.294 [element-js] "export 'EmojiMapping' was not found in '../../crypto-api/verification' 2023-05-24 11:27:28.294 [element-js] 2023-05-24 11:27:28.294 [element-js] WARNING in ../matrix-js-sdk/src/crypto/verification/SAS.ts 31:0-123 2023-05-24 11:27:28.294 [element-js] "export 'GeneratedSas' (reexported as 'IGeneratedSas') was not found in '../../crypto-api/verification' 2023-05-24 11:27:28.294 [element-js] 2023-05-24 11:27:28.294 [element-js] WARNING in ../matrix-js-sdk/src/crypto/verification/SAS.ts 31:0-123 2023-05-24 11:27:28.294 [element-js] "export 'ShowSasCallbacks' (reexported as 'ISasEvent') was not found in '../../crypto-api/verification' ``` --- src/crypto/verification/SAS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/verification/SAS.ts b/src/crypto/verification/SAS.ts index e8feaddca47..61f096ca452 100644 --- a/src/crypto/verification/SAS.ts +++ b/src/crypto/verification/SAS.ts @@ -36,7 +36,7 @@ import { EventType } from "../../@types/event"; import { EmojiMapping, GeneratedSas, ShowSasCallbacks, VerifierEvent } from "../../crypto-api/verification"; // backwards-compatibility exports -export { +export type { ShowSasCallbacks as ISasEvent, GeneratedSas as IGeneratedSas, EmojiMapping, From a2d1dee0a1ae15180f092acc4cf0a816e5a5cf29 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 24 May 2023 17:02:40 +0100 Subject: [PATCH 75/94] Minor fixes to CryptoApi doc comments (#3406) Followups to #3287 and #3360: minor clarifications to the doc-comments --- src/crypto-api.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/crypto-api.ts b/src/crypto-api.ts index 50d82bc4c7e..1149b036d04 100644 --- a/src/crypto-api.ts +++ b/src/crypto-api.ts @@ -126,7 +126,8 @@ export interface CryptoApi { * @param userId - The ID of the user whose device is to be checked. * @param deviceId - The ID of the device to check * - * @returns Verification status of the device, or `null` if the device is not known + * @returns `null` if the device is unknown, or has not published any encryption keys (implying it does not support + * encryption); otherwise the verification status of the device. */ getDeviceVerificationStatus(userId: string, deviceId: string): Promise; @@ -147,7 +148,7 @@ export interface CryptoApi { /** * Get the ID of one of the user's cross-signing keys. * - * @param type - The type of key to get the ID of. One of `CrossSigningKey.Master`, `CrossSigngingKey.SelfSigning`, + * @param type - The type of key to get the ID of. One of `CrossSigningKey.Master`, `CrossSigningKey.SelfSigning`, * or `CrossSigningKey.UserSigning`. Defaults to `CrossSigningKey.Master`. * * @returns If cross-signing has been initialised on this device, the ID of the given key. Otherwise, null From c0577c29c42928770b0b6949a9e7d228f6d5242e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 12:44:56 +0100 Subject: [PATCH 76/94] Minor improvements to the code inserting events into timelines (#3389) * Remove unneeded pling * Comment clarifying why we bail out in insertEventIntoTimeline --- src/models/event-timeline.ts | 2 +- src/models/thread.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/event-timeline.ts b/src/models/event-timeline.ts index 67be25fbc9c..a20dac880e4 100644 --- a/src/models/event-timeline.ts +++ b/src/models/event-timeline.ts @@ -458,7 +458,7 @@ export class EventTimeline { // member event, whereas we want to set the .sender value for the ACTUAL // member event itself. if (!event.sender || event.getType() === EventType.RoomMember) { - EventTimeline.setEventMetadata(event, roomState!, false); + EventTimeline.setEventMetadata(event, roomState, false); } } } diff --git a/src/models/thread.ts b/src/models/thread.ts index f2420842dfc..05e847594af 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -254,6 +254,7 @@ export class Thread extends ReadReceipt { if (!eventId) { return; } + // If the event is already in this thread, bail out if (this.findEventById(eventId)) { return; } From d1bfdca0c9176df382419346fee225b43d9e8807 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 12:45:47 +0100 Subject: [PATCH 77/94] Isolate changes to Thread global into a single describe block (#3390) --- spec/unit/event-timeline-set.spec.ts | 144 +++++++++++++++------------ 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index a099aba0370..6f03e774553 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -206,85 +206,97 @@ describe("EventTimelineSet", () => { expect(liveTimeline.getEvents().length).toStrictEqual(0); }); - it("should allow edits to be added to thread timeline", async () => { - jest.spyOn(client, "supportsThreads").mockReturnValue(true); - jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); - Thread.hasServerSideSupport = FeatureSupport.Stable; + describe("When the server supports threads", () => { + let previousThreadHasServerSideSupport: FeatureSupport; - const sender = "@alice:matrix.org"; + beforeAll(() => { + previousThreadHasServerSideSupport = Thread.hasServerSideSupport; + Thread.hasServerSideSupport = FeatureSupport.Stable; + }); - const root = utils.mkEvent({ - event: true, - content: { - body: "Thread root", - }, - type: EventType.RoomMessage, - sender, + afterAll(() => { + Thread.hasServerSideSupport = previousThreadHasServerSideSupport; }); - room.addLiveEvents([root]); - const threadReply = utils.mkEvent({ - event: true, - content: { - "body": "Thread reply", - "m.relates_to": { - event_id: root.getId()!, - rel_type: RelationType.Thread, + it("should allow edits to be added to thread timeline", async () => { + jest.spyOn(client, "supportsThreads").mockReturnValue(true); + jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); + + const sender = "@alice:matrix.org"; + + const root = utils.mkEvent({ + event: true, + content: { + body: "Thread root", }, - }, - type: EventType.RoomMessage, - sender, - }); + type: EventType.RoomMessage, + sender, + }); + room.addLiveEvents([root]); - root.setUnsigned({ - "m.relations": { - [RelationType.Thread]: { - count: 1, - latest_event: { - content: threadReply.getContent(), - origin_server_ts: 5, - room_id: room.roomId, - sender, - type: EventType.RoomMessage, - event_id: threadReply.getId()!, - user_id: sender, - age: 1, + const threadReply = utils.mkEvent({ + event: true, + content: { + "body": "Thread reply", + "m.relates_to": { + event_id: root.getId()!, + rel_type: RelationType.Thread, }, - current_user_participated: true, }, - }, - }); + type: EventType.RoomMessage, + sender, + }); - const editToThreadReply = utils.mkEvent({ - event: true, - content: { - "body": " * edit", - "m.new_content": { - "body": "edit", - "msgtype": "m.text", - "org.matrix.msc1767.text": "edit", + root.setUnsigned({ + "m.relations": { + [RelationType.Thread]: { + count: 1, + latest_event: { + content: threadReply.getContent(), + origin_server_ts: 5, + room_id: room.roomId, + sender, + type: EventType.RoomMessage, + event_id: threadReply.getId()!, + user_id: sender, + age: 1, + }, + current_user_participated: true, + }, }, - "m.relates_to": { - event_id: threadReply.getId()!, - rel_type: RelationType.Replace, + }); + + const editToThreadReply = utils.mkEvent({ + event: true, + content: { + "body": " * edit", + "m.new_content": { + "body": "edit", + "msgtype": "m.text", + "org.matrix.msc1767.text": "edit", + }, + "m.relates_to": { + event_id: threadReply.getId()!, + rel_type: RelationType.Replace, + }, }, - }, - type: EventType.RoomMessage, - sender, - }); + type: EventType.RoomMessage, + sender, + }); - jest.spyOn(client, "paginateEventTimeline").mockImplementation(async () => { - thread.timelineSet.getLiveTimeline().addEvent(threadReply, { toStartOfTimeline: true }); - return true; - }); - jest.spyOn(client, "relations").mockResolvedValue({ - events: [], - }); + jest.spyOn(client, "paginateEventTimeline").mockImplementation(async () => { + thread.timelineSet.getLiveTimeline().addEvent(threadReply, { toStartOfTimeline: true }); + return true; + }); + jest.spyOn(client, "relations").mockResolvedValue({ + events: [], + }); - const thread = room.createThread(root.getId()!, root, [threadReply, editToThreadReply], false); - thread.once(RoomEvent.TimelineReset, () => { - const lastEvent = thread.timeline.at(-1)!; - expect(lastEvent.getContent().body).toBe(" * edit"); + const thread = room.createThread(root.getId()!, root, [threadReply, editToThreadReply], false); + thread.once(RoomEvent.TimelineReset, () => { + const lastEvent = thread.timeline.at(-1)!; + expect(lastEvent.getContent().body).toBe(" * edit"); + }); }); }); From 056aae823dcf9156d445879848c65c9f16cc4442 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 13:38:53 +0100 Subject: [PATCH 78/94] Fix an existing test for editing messages in threads (#3407) While attempting to test a new change, I discovered that the test "should allow edits to be added to thread timeline" did not actually fail if its assertions failed. Further, those assertions were incorrect. So this change fixes the test to create the thread, wait for it to be initialised, and then add events to it. This simplifies the flow and ensures the test fails if something unexpected happens. --- spec/unit/event-timeline-set.spec.ts | 34 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index 6f03e774553..83844fe8670 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -284,19 +284,27 @@ describe("EventTimelineSet", () => { sender, }); - jest.spyOn(client, "paginateEventTimeline").mockImplementation(async () => { - thread.timelineSet.getLiveTimeline().addEvent(threadReply, { toStartOfTimeline: true }); - return true; - }); - jest.spyOn(client, "relations").mockResolvedValue({ - events: [], - }); - - const thread = room.createThread(root.getId()!, root, [threadReply, editToThreadReply], false); - thread.once(RoomEvent.TimelineReset, () => { - const lastEvent = thread.timeline.at(-1)!; - expect(lastEvent.getContent().body).toBe(" * edit"); - }); + // Mock methods that call out to HTTP endpoints + jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); + jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); + jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); + + // Create a thread and wait for it to be initialised + const thread = room.createThread(root.getId()!, root, [], false); + await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); + + // When a message and an edit are added to the thread + await thread.addEvent(threadReply, false); + await thread.addEvent(editToThreadReply, false); + + // Then both events end up in the timeline + const lastEvent = thread.timeline.at(-1)!; + const secondLastEvent = thread.timeline.at(-2)!; + expect(lastEvent).toBe(editToThreadReply); + expect(secondLastEvent).toBe(threadReply); + + // And the first message has been edited + expect(secondLastEvent.getContent().body).toEqual("edit"); }); }); From 26736b6bb1234cc0b70958d4ff2afae8f07d6ee8 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 14:20:59 +0100 Subject: [PATCH 79/94] Move editing test into thread.spec.ts (#3408) * Fix an existing test for editing messages in threads While attempting to test a new change, I discovered that the test "should allow edits to be added to thread timeline" did not actually fail if its assertions failed. Further, those assertions were incorrect. So this change fixes the test to create the thread, wait for it to be initialised, and then add events to it. This simplifies the flow and ensures the test fails if something unexpected happens. * Move editing test into thread.spec.ts --- spec/unit/event-timeline-set.spec.ts | 107 +-------------------------- spec/unit/models/thread.spec.ts | 104 +++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 109 deletions(-) diff --git a/spec/unit/event-timeline-set.spec.ts b/spec/unit/event-timeline-set.spec.ts index 83844fe8670..7afee718967 100644 --- a/spec/unit/event-timeline-set.spec.ts +++ b/spec/unit/event-timeline-set.spec.ts @@ -24,13 +24,10 @@ import { MatrixClient, MatrixEvent, MatrixEventEvent, - RelationType, Room, - RoomEvent, } from "../../src"; -import { FeatureSupport, Thread } from "../../src/models/thread"; +import { Thread } from "../../src/models/thread"; import { ReEmitter } from "../../src/ReEmitter"; -import { eventMapperFor } from "../../src/event-mapper"; describe("EventTimelineSet", () => { const roomId = "!foo:bar"; @@ -206,108 +203,6 @@ describe("EventTimelineSet", () => { expect(liveTimeline.getEvents().length).toStrictEqual(0); }); - describe("When the server supports threads", () => { - let previousThreadHasServerSideSupport: FeatureSupport; - - beforeAll(() => { - previousThreadHasServerSideSupport = Thread.hasServerSideSupport; - Thread.hasServerSideSupport = FeatureSupport.Stable; - }); - - afterAll(() => { - Thread.hasServerSideSupport = previousThreadHasServerSideSupport; - }); - - it("should allow edits to be added to thread timeline", async () => { - jest.spyOn(client, "supportsThreads").mockReturnValue(true); - jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); - - const sender = "@alice:matrix.org"; - - const root = utils.mkEvent({ - event: true, - content: { - body: "Thread root", - }, - type: EventType.RoomMessage, - sender, - }); - room.addLiveEvents([root]); - - const threadReply = utils.mkEvent({ - event: true, - content: { - "body": "Thread reply", - "m.relates_to": { - event_id: root.getId()!, - rel_type: RelationType.Thread, - }, - }, - type: EventType.RoomMessage, - sender, - }); - - root.setUnsigned({ - "m.relations": { - [RelationType.Thread]: { - count: 1, - latest_event: { - content: threadReply.getContent(), - origin_server_ts: 5, - room_id: room.roomId, - sender, - type: EventType.RoomMessage, - event_id: threadReply.getId()!, - user_id: sender, - age: 1, - }, - current_user_participated: true, - }, - }, - }); - - const editToThreadReply = utils.mkEvent({ - event: true, - content: { - "body": " * edit", - "m.new_content": { - "body": "edit", - "msgtype": "m.text", - "org.matrix.msc1767.text": "edit", - }, - "m.relates_to": { - event_id: threadReply.getId()!, - rel_type: RelationType.Replace, - }, - }, - type: EventType.RoomMessage, - sender, - }); - - // Mock methods that call out to HTTP endpoints - jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); - jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); - jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); - - // Create a thread and wait for it to be initialised - const thread = room.createThread(root.getId()!, root, [], false); - await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); - - // When a message and an edit are added to the thread - await thread.addEvent(threadReply, false); - await thread.addEvent(editToThreadReply, false); - - // Then both events end up in the timeline - const lastEvent = thread.timeline.at(-1)!; - const secondLastEvent = thread.timeline.at(-2)!; - expect(lastEvent).toBe(editToThreadReply); - expect(secondLastEvent).toBe(threadReply); - - // And the first message has been edited - expect(secondLastEvent.getContent().body).toEqual("edit"); - }); - }); - describe("non-room timeline", () => { it("Adds event to timeline", () => { const nonRoomEventTimelineSet = new EventTimelineSet( diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index bd17f00c8aa..cd0340cb199 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -17,12 +17,12 @@ limitations under the License. import { mocked } from "jest-mock"; import { MatrixClient, PendingEventOrdering } from "../../../src/client"; -import { Room } from "../../../src/models/room"; +import { Room, RoomEvent } from "../../../src/models/room"; import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../../../src/models/thread"; import { mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; -import { emitPromise, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; -import { Direction, EventStatus, MatrixEvent } from "../../../src"; +import { emitPromise, mkEvent, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; +import { Direction, EventStatus, EventType, MatrixEvent, RelationType } from "../../../src"; import { ReceiptType } from "../../../src/@types/read_receipts"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client"; import { ReEmitter } from "../../../src/ReEmitter"; @@ -474,4 +474,102 @@ describe("Thread", () => { return client; } }); + + describe("Editing events", () => { + it("should allow edits to be added to thread timeline", async () => { + const roomId = "!foo:bar"; + const userA = "@alice:bar"; + const client = mock(MatrixClient, "MatrixClient"); + client.reEmitter = mock(ReEmitter, "ReEmitter"); + client.canSupport = new Map(); + const room = new Room(roomId, client, userA); + jest.spyOn(client, "supportsThreads").mockReturnValue(true); + jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); + Thread.hasServerSideSupport = FeatureSupport.Stable; + + const sender = "@alice:matrix.org"; + + const root = mkEvent({ + event: true, + content: { + body: "Thread root", + }, + type: EventType.RoomMessage, + sender, + }); + room.addLiveEvents([root]); + + const threadReply = mkEvent({ + event: true, + content: { + "body": "Thread reply", + "m.relates_to": { + event_id: root.getId()!, + rel_type: RelationType.Thread, + }, + }, + type: EventType.RoomMessage, + sender, + }); + + root.setUnsigned({ + "m.relations": { + [RelationType.Thread]: { + count: 1, + latest_event: { + content: threadReply.getContent(), + origin_server_ts: 5, + room_id: room.roomId, + sender, + type: EventType.RoomMessage, + event_id: threadReply.getId()!, + user_id: sender, + age: 1, + }, + current_user_participated: true, + }, + }, + }); + + const editToThreadReply = mkEvent({ + event: true, + content: { + "body": " * edit", + "m.new_content": { + "body": "edit", + "msgtype": "m.text", + "org.matrix.msc1767.text": "edit", + }, + "m.relates_to": { + event_id: threadReply.getId()!, + rel_type: RelationType.Replace, + }, + }, + type: EventType.RoomMessage, + sender, + }); + + // Mock methods that call out to HTTP endpoints + jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); + jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); + jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); + + // Create a thread and wait for it to be initialised + const thread = room.createThread(root.getId()!, root, [], false); + await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); + + // When a message and an edit are added to the thread + await thread.addEvent(threadReply, false); + await thread.addEvent(editToThreadReply, false); + + // Then both events end up in the timeline + const lastEvent = thread.timeline.at(-1)!; + const secondLastEvent = thread.timeline.at(-2)!; + expect(lastEvent).toBe(editToThreadReply); + expect(secondLastEvent).toBe(threadReply); + + // And the first message has been edited + expect(secondLastEvent.getContent().body).toEqual("edit"); + }); + }); }); From 7ed14dc74978f2fbd479524984ac498100c4721e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 14:59:14 +0100 Subject: [PATCH 80/94] Only add a local receipt if it's after an existing receipt (#3399) * Add a test for creating local echo receipts in threads * Only add local receipt if it's after existing receipt * Refactor local receipt tests to be shorter * Tests for local receipts where we DO have recursive relations support --- spec/unit/models/thread.spec.ts | 139 ++++++++++++++++++++++++++++++-- src/models/thread.ts | 24 +++++- 2 files changed, 156 insertions(+), 7 deletions(-) diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index cd0340cb199..9a36ca56163 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -19,7 +19,7 @@ import { mocked } from "jest-mock"; import { MatrixClient, PendingEventOrdering } from "../../../src/client"; import { Room, RoomEvent } from "../../../src/models/room"; import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../../../src/models/thread"; -import { mkThread } from "../../test-utils/thread"; +import { makeThreadEvent, mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; import { emitPromise, mkEvent, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; import { Direction, EventStatus, EventType, MatrixEvent, RelationType } from "../../../src"; @@ -429,7 +429,7 @@ describe("Thread", () => { }); describe("insertEventIntoTimeline", () => { - it("Inserts a reply in timestamp order", () => { + it("Inserts a reaction in timestamp order", () => { // Assumption: no server side support because if we have it, events // can only be added to the timeline after the thread has been // initialised, and we are not properly initialising it here. @@ -449,11 +449,11 @@ describe("Thread", () => { ts: 100, // Events will be at ts 100, 101, 102, 103, 104 and 105 }); - // When we insert a reply to the second thread message + // When we insert a reaction to the second thread message const replyEvent = mkReaction(events[2], client, userId, room.roomId, 104); thread.insertEventIntoTimeline(replyEvent); - // Then the reply is inserted based on its timestamp + // Then the reaction is inserted based on its timestamp expect(thread.events.map((ev) => ev.getId())).toEqual([ events[0].getId(), events[1].getId(), @@ -465,10 +465,137 @@ describe("Thread", () => { ]); }); - function createClientWithEventMapper(): MatrixClient { + describe("Without relations recursion support", () => { + it("Creates a local echo receipt for new events", async () => { + // Assumption: no server side support because if we have it, events + // can only be added to the timeline after the thread has been + // initialised, and we are not properly initialising it here. + expect(Thread.hasServerSideSupport).toBe(FeatureSupport.None); + + // Given a client without relations recursion support + const client = createClientWithEventMapper(); + + // And a thread with an added event (with later timestamp) + const userId = "user1"; + const { thread, message } = await createThreadAndEvent(client, 1, 100, userId); + + // Then a receipt was added to the thread + const receipt = thread.getReadReceiptForUserId(userId); + expect(receipt).toBeTruthy(); + expect(receipt?.eventId).toEqual(message.getId()); + expect(receipt?.data.ts).toEqual(100); + expect(receipt?.data.thread_id).toEqual(thread.id); + + // (And the receipt was synthetic) + expect(thread.getReadReceiptForUserId(userId, true)).toBeNull(); + }); + + it("Doesn't create a local echo receipt for events before an existing receipt", async () => { + // Assumption: no server side support because if we have it, events + // can only be added to the timeline after the thread has been + // initialised, and we are not properly initialising it here. + expect(Thread.hasServerSideSupport).toBe(FeatureSupport.None); + + // Given a client without relations recursion support + const client = createClientWithEventMapper(); + + // And a thread with an added event with a lower timestamp than its other events + const userId = "user1"; + const { thread } = await createThreadAndEvent(client, 200, 100, userId); + + // Then no receipt was added to the thread (the receipt is still + // for the thread root). This happens because since we have no + // recursive relations support, we know that sometimes events + // appear out of order, so we have to check their timestamps as + // a guess of the correct order. + expect(thread.getReadReceiptForUserId(userId)?.eventId).toEqual(thread.rootEvent?.getId()); + }); + }); + + describe("With relations recursion support", () => { + it("Creates a local echo receipt for new events", async () => { + // Assumption: no server side support because if we have it, events + // can only be added to the timeline after the thread has been + // initialised, and we are not properly initialising it here. + expect(Thread.hasServerSideSupport).toBe(FeatureSupport.None); + + // Given a client WITH relations recursion support + const client = createClientWithEventMapper( + new Map([[Feature.RelationsRecursion, ServerSupport.Stable]]), + ); + + // And a thread with an added event (with later timestamp) + const userId = "user1"; + const { thread, message } = await createThreadAndEvent(client, 1, 100, userId); + + // Then a receipt was added to the thread + const receipt = thread.getReadReceiptForUserId(userId); + expect(receipt?.eventId).toEqual(message.getId()); + }); + + it("Creates a local echo receipt even for events BEFORE an existing receipt", async () => { + // Assumption: no server side support because if we have it, events + // can only be added to the timeline after the thread has been + // initialised, and we are not properly initialising it here. + expect(Thread.hasServerSideSupport).toBe(FeatureSupport.None); + + // Given a client WITH relations recursion support + const client = createClientWithEventMapper( + new Map([[Feature.RelationsRecursion, ServerSupport.Stable]]), + ); + + // And a thread with an added event with a lower timestamp than its other events + const userId = "user1"; + const { thread, message } = await createThreadAndEvent(client, 200, 100, userId); + + // Then a receipt was added to the thread, because relations + // recursion is available, so we trust the server to have + // provided us with events in the right order. + const receipt = thread.getReadReceiptForUserId(userId); + expect(receipt?.eventId).toEqual(message.getId()); + }); + }); + + async function createThreadAndEvent( + client: MatrixClient, + rootTs: number, + eventTs: number, + userId: string, + ): Promise<{ thread: Thread; message: MatrixEvent }> { + const room = new Room("room1", client, userId); + + // Given a thread + const { thread } = mkThread({ + room, + client, + authorId: userId, + participantUserIds: [], + ts: rootTs, + }); + // Sanity: the current receipt is for the thread root + expect(thread.getReadReceiptForUserId(userId)?.eventId).toEqual(thread.rootEvent?.getId()); + + const awaitTimelineEvent = new Promise((res) => thread.on(RoomEvent.Timeline, () => res())); + + // When we add a message that is before the latest receipt + const message = makeThreadEvent({ + event: true, + rootEventId: thread.id, + replyToEventId: thread.id, + user: userId, + room: room.roomId, + ts: eventTs, + }); + await thread.addEvent(message, false, true); + await awaitTimelineEvent; + + return { thread, message }; + } + + function createClientWithEventMapper(canSupport: Map = new Map()): MatrixClient { const client = mock(MatrixClient, "MatrixClient"); client.reEmitter = mock(ReEmitter, "ReEmitter"); - client.canSupport = new Map(); + client.canSupport = canSupport; jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); mocked(client.supportsThreads).mockReturnValue(true); return client; diff --git a/src/models/thread.ts b/src/models/thread.ts index 05e847594af..c44f01c9547 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -203,11 +203,33 @@ export class Thread extends ReadReceipt { ): void => { // Add a synthesized receipt when paginating forward in the timeline if (!toStartOfTimeline) { - room!.addLocalEchoReceipt(event.getSender()!, event, ReceiptType.Read); + const sender = event.getSender(); + if (sender && room && this.shouldSendLocalEchoReceipt(sender, event)) { + room.addLocalEchoReceipt(sender, event, ReceiptType.Read); + } } this.onEcho(event, toStartOfTimeline ?? false); }; + private shouldSendLocalEchoReceipt(sender: string, event: MatrixEvent): boolean { + const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; + + if (recursionSupport === ServerSupport.Unsupported) { + // Normally we add a local receipt, but if we don't have + // recursion support, then events may arrive out of order, so we + // only create a receipt if it's after our existing receipt. + const oldReceiptEventId = this.getReadReceiptForUserId(sender)?.eventId; + if (oldReceiptEventId) { + const receiptEvent = this.findEventById(oldReceiptEventId); + if (receiptEvent && receiptEvent.getTs() > event.getTs()) { + return false; + } + } + } + + return true; + } + private onLocalEcho = (event: MatrixEvent): void => { this.onEcho(event, false); }; From 48b60bb8852ab2be19278bf22a5bf6aa2eaadddf Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 15:03:58 +0100 Subject: [PATCH 81/94] Simplify thread-editing test (#3409) * Fix an existing test for editing messages in threads While attempting to test a new change, I discovered that the test "should allow edits to be added to thread timeline" did not actually fail if its assertions failed. Further, those assertions were incorrect. So this change fixes the test to create the thread, wait for it to be initialised, and then add events to it. This simplifies the flow and ensures the test fails if something unexpected happens. * Move editing test into thread.spec.ts * Isolate Thread global modification in beforeAll() * Delete unneeded setUnsigned call * Use standard message-creation methods * Rename event variables * Rename sender->user * Remove unneeded variables * Extract distractions into functions --- spec/test-utils/test-utils.ts | 31 ++++++ spec/unit/models/thread.spec.ts | 167 +++++++++++++++----------------- 2 files changed, 110 insertions(+), 88 deletions(-) diff --git a/spec/test-utils/test-utils.ts b/spec/test-utils/test-utils.ts index ff41b35960d..dab7dfc9b85 100644 --- a/spec/test-utils/test-utils.ts +++ b/spec/test-utils/test-utils.ts @@ -370,6 +370,37 @@ export function mkReaction( ); } +export function mkEdit( + target: MatrixEvent, + client: MatrixClient, + userId: string, + roomId: string, + msg?: string, + ts?: number, +) { + msg = msg ?? `Edit of ${target.getId()}`; + return mkEvent( + { + event: true, + type: EventType.RoomMessage, + user: userId, + room: roomId, + content: { + "body": `* ${msg}`, + "m.new_content": { + body: msg, + }, + "m.relates_to": { + rel_type: RelationType.Replace, + event_id: target.getId()!, + }, + }, + ts, + }, + client, + ); +} + /** * A mock implementation of webstorage */ diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index 9a36ca56163..123de4d5e70 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -21,8 +21,8 @@ import { Room, RoomEvent } from "../../../src/models/room"; import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../../../src/models/thread"; import { makeThreadEvent, mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; -import { emitPromise, mkEvent, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; -import { Direction, EventStatus, EventType, MatrixEvent, RelationType } from "../../../src"; +import { emitPromise, mkEdit, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; +import { Direction, EventStatus, MatrixEvent } from "../../../src"; import { ReceiptType } from "../../../src/@types/read_receipts"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client"; import { ReEmitter } from "../../../src/ReEmitter"; @@ -603,100 +603,91 @@ describe("Thread", () => { }); describe("Editing events", () => { - it("should allow edits to be added to thread timeline", async () => { - const roomId = "!foo:bar"; - const userA = "@alice:bar"; - const client = mock(MatrixClient, "MatrixClient"); - client.reEmitter = mock(ReEmitter, "ReEmitter"); - client.canSupport = new Map(); - const room = new Room(roomId, client, userA); - jest.spyOn(client, "supportsThreads").mockReturnValue(true); - jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); - Thread.hasServerSideSupport = FeatureSupport.Stable; + describe("Given server support for threads", () => { + let previousThreadHasServerSideSupport: FeatureSupport; - const sender = "@alice:matrix.org"; - - const root = mkEvent({ - event: true, - content: { - body: "Thread root", - }, - type: EventType.RoomMessage, - sender, - }); - room.addLiveEvents([root]); - - const threadReply = mkEvent({ - event: true, - content: { - "body": "Thread reply", - "m.relates_to": { - event_id: root.getId()!, - rel_type: RelationType.Thread, - }, - }, - type: EventType.RoomMessage, - sender, - }); - - root.setUnsigned({ - "m.relations": { - [RelationType.Thread]: { - count: 1, - latest_event: { - content: threadReply.getContent(), - origin_server_ts: 5, - room_id: room.roomId, - sender, - type: EventType.RoomMessage, - event_id: threadReply.getId()!, - user_id: sender, - age: 1, - }, - current_user_participated: true, - }, - }, + beforeAll(() => { + previousThreadHasServerSideSupport = Thread.hasServerSideSupport; + Thread.hasServerSideSupport = FeatureSupport.Stable; }); - const editToThreadReply = mkEvent({ - event: true, - content: { - "body": " * edit", - "m.new_content": { - "body": "edit", - "msgtype": "m.text", - "org.matrix.msc1767.text": "edit", - }, - "m.relates_to": { - event_id: threadReply.getId()!, - rel_type: RelationType.Replace, - }, - }, - type: EventType.RoomMessage, - sender, + afterAll(() => { + Thread.hasServerSideSupport = previousThreadHasServerSideSupport; }); - // Mock methods that call out to HTTP endpoints - jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); - jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); - jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); - - // Create a thread and wait for it to be initialised - const thread = room.createThread(root.getId()!, root, [], false); - await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); + it("should allow edits to be added to thread timeline", async () => { + // Given a thread + const client = createClient(); + const user = "@alice:matrix.org"; + const room = "!room:z"; + const thread = await createThread(client, user, room); - // When a message and an edit are added to the thread - await thread.addEvent(threadReply, false); - await thread.addEvent(editToThreadReply, false); + // When a message and an edit are added to the thread + const messageToEdit = createThreadMessage(thread.id, user, room, "Thread reply"); + const editEvent = mkEdit(messageToEdit, client, user, room, "edit"); + await thread.addEvent(messageToEdit, false); + await thread.addEvent(editEvent, false); - // Then both events end up in the timeline - const lastEvent = thread.timeline.at(-1)!; - const secondLastEvent = thread.timeline.at(-2)!; - expect(lastEvent).toBe(editToThreadReply); - expect(secondLastEvent).toBe(threadReply); + // Then both events end up in the timeline + const lastEvent = thread.timeline.at(-1)!; + const secondLastEvent = thread.timeline.at(-2)!; + expect(lastEvent).toBe(editEvent); + expect(secondLastEvent).toBe(messageToEdit); - // And the first message has been edited - expect(secondLastEvent.getContent().body).toEqual("edit"); + // And the first message has been edited + expect(secondLastEvent.getContent().body).toEqual("edit"); + }); }); }); }); + +/** + * Create a message event that lives in a thread + */ +function createThreadMessage(threadId: string, user: string, room: string, msg: string): MatrixEvent { + return makeThreadEvent({ + event: true, + user, + room, + msg, + rootEventId: threadId, + replyToEventId: threadId, + }); +} + +/** + * Create a thread and wait for it to be properly initialised (so you can safely + * add events to it and expect them to appear in the timeline. + */ +async function createThread(client: MatrixClient, user: string, roomId: string): Promise { + const root = mkMessage({ event: true, user, room: roomId, msg: "Thread root" }); + + const room = new Room(roomId, client, "@roomcreator:x"); + room.addLiveEvents([root]); + + // Create the thread and wait for it to be initialised + const thread = room.createThread(root.getId()!, root, [], false); + await new Promise((res) => thread.once(RoomEvent.TimelineReset, () => res())); + + return thread; +} + +/** + * Create a MatrixClient that supports threads and has all the methods used when + * creating a thread that call out to HTTP endpoints mocked out. + */ +function createClient(): MatrixClient { + const client = mock(MatrixClient, "MatrixClient"); + client.reEmitter = mock(ReEmitter, "ReEmitter"); + client.canSupport = new Map(); + + jest.spyOn(client, "supportsThreads").mockReturnValue(true); + jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); + + // Mock methods that call out to HTTP endpoints + jest.spyOn(client, "paginateEventTimeline").mockResolvedValue(true); + jest.spyOn(client, "relations").mockResolvedValue({ events: [] }); + jest.spyOn(client, "fetchRoomEvent").mockResolvedValue({}); + + return client; +} From 711bf4710d38bff86cd5fea8e4505c857b7451ff Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 16:32:42 +0100 Subject: [PATCH 82/94] Test inserting edit events into the thread timeline "on demand" (#3410) * Fix an existing test for editing messages in threads While attempting to test a new change, I discovered that the test "should allow edits to be added to thread timeline" did not actually fail if its assertions failed. Further, those assertions were incorrect. So this change fixes the test to create the thread, wait for it to be initialised, and then add events to it. This simplifies the flow and ensures the test fails if something unexpected happens. * Move editing test into thread.spec.ts * Isolate Thread global modification in beforeAll() * Delete unneeded setUnsigned call * Use standard message-creation methods * Rename event variables * Rename sender->user * Remove unneeded variables * Extract distractions into functions * Fetch edits for thread messages Modifies fetchEditsWhereNeeded to allow edits of threaded messages. The code before prevented any relations from fetching edits, but of course events in threads are relations. We definitely want thread messages to get their edits fetched, and I assume this is working in the real code, probably because the event being looked at is some kind of eventmapped thing that doesn't have proper relations visible on it. In tests, if we don't make this change, we can't see edits getting fetched. * Add a test for fetching edits on demand in a thread This test demonstrates the current behaviour, which contains a bug - we don't actually add the right event to the timeline. --- spec/unit/models/thread.spec.ts | 51 +++++++++++++++++++++++++++++---- src/models/thread.ts | 3 +- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index 123de4d5e70..480e28f6438 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -22,7 +22,7 @@ import { Thread, THREAD_RELATION_TYPE, ThreadEvent, FeatureSupport } from "../.. import { makeThreadEvent, mkThread } from "../../test-utils/thread"; import { TestClient } from "../../TestClient"; import { emitPromise, mkEdit, mkMessage, mkReaction, mock } from "../../test-utils/test-utils"; -import { Direction, EventStatus, MatrixEvent } from "../../../src"; +import { Direction, EventStatus, EventType, MatrixEvent } from "../../../src"; import { ReceiptType } from "../../../src/@types/read_receipts"; import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../test-utils/client"; import { ReEmitter } from "../../../src/ReEmitter"; @@ -615,7 +615,7 @@ describe("Thread", () => { Thread.hasServerSideSupport = previousThreadHasServerSideSupport; }); - it("should allow edits to be added to thread timeline", async () => { + it("Adds edits from sync to the thread timeline and applies them", async () => { // Given a thread const client = createClient(); const user = "@alice:matrix.org"; @@ -637,6 +637,45 @@ describe("Thread", () => { // And the first message has been edited expect(secondLastEvent.getContent().body).toEqual("edit"); }); + + it("Adds edits fetched on demand to the thread timeline and applies them", async () => { + // Given we don't support recursive relations + const client = createClient(new Map([[Feature.RelationsRecursion, ServerSupport.Unsupported]])); + // And we have a thread + const user = "@alice:matrix.org"; + const room = "!room:z"; + const thread = await createThread(client, user, room); + + // When a message is added to the thread, and an edit to it is provided on demand + const messageToEdit = createThreadMessage(thread.id, user, room, "Thread reply"); + // (fetchEditsWhereNeeded only applies to encrypted messages for some reason) + messageToEdit.event.type = EventType.RoomMessageEncrypted; + const editEvent = mkEdit(messageToEdit, client, user, room, "edit"); + mocked(client.relations).mockImplementation(async (_roomId, eventId) => { + if (eventId === messageToEdit.getId()) { + return { events: [editEvent] }; + } else { + return { events: [] }; + } + }); + await thread.addEvent(messageToEdit, false); + + // THIS IS THE CORRECT BEHAVIOUR + // Then both events end up in the timeline + //const lastEvent = thread.timeline.at(-1)!; + //const secondLastEvent = thread.timeline.at(-2)!; + //expect(lastEvent).toBe(editEvent); + //expect(secondLastEvent).toBe(messageToEdit); + + //// And the first message has been edited + //expect(secondLastEvent.getContent().body).toEqual("edit"); + + // TODO: For now, we incorrecly DON'T add the event to the timeline + const lastEvent = thread.timeline.at(-1)!; + expect(lastEvent).toBe(messageToEdit); + // But the original is edited, as expected + expect(lastEvent.getContent().body).toEqual("edit"); + }); }); }); }); @@ -661,8 +700,10 @@ function createThreadMessage(threadId: string, user: string, room: string, msg: */ async function createThread(client: MatrixClient, user: string, roomId: string): Promise { const root = mkMessage({ event: true, user, room: roomId, msg: "Thread root" }); - const room = new Room(roomId, client, "@roomcreator:x"); + + // Ensure the root is in the room timeline + root.setThreadId(root.getId()); room.addLiveEvents([root]); // Create the thread and wait for it to be initialised @@ -676,10 +717,10 @@ async function createThread(client: MatrixClient, user: string, roomId: string): * Create a MatrixClient that supports threads and has all the methods used when * creating a thread that call out to HTTP endpoints mocked out. */ -function createClient(): MatrixClient { +function createClient(canSupport = new Map()): MatrixClient { const client = mock(MatrixClient, "MatrixClient"); client.reEmitter = mock(ReEmitter, "ReEmitter"); - client.canSupport = new Map(); + client.canSupport = canSupport; jest.spyOn(client, "supportsThreads").mockReturnValue(true); jest.spyOn(client, "getEventMapper").mockReturnValue(eventMapperFor(client, {})); diff --git a/src/models/thread.ts b/src/models/thread.ts index c44f01c9547..b04ea63dc34 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -522,7 +522,8 @@ export class Thread extends ReadReceipt { events .filter((e) => e.isEncrypted()) .map((event: MatrixEvent) => { - if (event.isRelation()) return; // skip - relations don't get edits + // The only type of relation that gets edits is a thread message. + if (event.getThread() === undefined && event.isRelation()) return; return this.client .relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), { limit: 1, From b5414ea914d7626e368bb1d3f6273cf8661f305e Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 May 2023 16:48:48 +0100 Subject: [PATCH 83/94] Fix bug where original event was inserted into timeline instead of the edit event (#3398) * Fix an existing test for editing messages in threads While attempting to test a new change, I discovered that the test "should allow edits to be added to thread timeline" did not actually fail if its assertions failed. Further, those assertions were incorrect. So this change fixes the test to create the thread, wait for it to be initialised, and then add events to it. This simplifies the flow and ensures the test fails if something unexpected happens. * Move editing test into thread.spec.ts * Isolate Thread global modification in beforeAll() * Delete unneeded setUnsigned call * Use standard message-creation methods * Rename event variables * Rename sender->user * Remove unneeded variables * Extract distractions into functions * Fetch edits for thread messages Modifies fetchEditsWhereNeeded to allow edits of threaded messages. The code before prevented any relations from fetching edits, but of course events in threads are relations. We definitely want thread messages to get their edits fetched, and I assume this is working in the real code, probably because the event being looked at is some kind of eventmapped thing that doesn't have proper relations visible on it. In tests, if we don't make this change, we can't see edits getting fetched. * Add a test for fetching edits on demand in a thread This test demonstrates the current behaviour, which contains a bug - we don't actually add the right event to the timeline. * Fix bug where original event was inserted into timeline instead of the edit event --- spec/unit/models/thread.spec.ts | 19 ++++++------------- src/models/thread.ts | 5 +++-- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index 480e28f6438..6aa1d5c862e 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -660,21 +660,14 @@ describe("Thread", () => { }); await thread.addEvent(messageToEdit, false); - // THIS IS THE CORRECT BEHAVIOUR // Then both events end up in the timeline - //const lastEvent = thread.timeline.at(-1)!; - //const secondLastEvent = thread.timeline.at(-2)!; - //expect(lastEvent).toBe(editEvent); - //expect(secondLastEvent).toBe(messageToEdit); - - //// And the first message has been edited - //expect(secondLastEvent.getContent().body).toEqual("edit"); - - // TODO: For now, we incorrecly DON'T add the event to the timeline const lastEvent = thread.timeline.at(-1)!; - expect(lastEvent).toBe(messageToEdit); - // But the original is edited, as expected - expect(lastEvent.getContent().body).toEqual("edit"); + const secondLastEvent = thread.timeline.at(-2)!; + expect(lastEvent).toBe(editEvent); + expect(secondLastEvent).toBe(messageToEdit); + + // And the first message has been edited + expect(secondLastEvent.getContent().body).toEqual("edit"); }); }); }); diff --git a/src/models/thread.ts b/src/models/thread.ts index b04ea63dc34..11727d9704e 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -530,8 +530,9 @@ export class Thread extends ReadReceipt { }) .then((relations) => { if (relations.events.length) { - event.makeReplaced(relations.events[0]); - this.insertEventIntoTimeline(event); + const editEvent = relations.events[0]; + event.makeReplaced(editEvent); + this.insertEventIntoTimeline(editEvent); } }) .catch((e) => { From 7ade461a4ce5728e8dbef690f860c2766fca5d48 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Fri, 26 May 2023 09:56:49 +0200 Subject: [PATCH 84/94] Refactor naming of webrtc stats reporter (#3404) * Refactor names in webrtc stats * Refactor summary stats reporter to gatherer * Add test for signal change * rename test --- ...pec.ts => callStatsReportGatherer.spec.ts} | 49 ++++++++++++++-- ...s => connectionStatsReportBuilder.spec.ts} | 4 +- .../stats/connectionStatsReporter.spec.ts | 10 ++-- spec/unit/webrtc/stats/groupCallStats.spec.ts | 4 +- ....ts => summaryStatsReportGatherer.spec.ts} | 8 +-- ...rter.spec.ts => trackStatsBuilder.spec.ts} | 50 ++++++++--------- ....spec.ts => transportStatsBuilder.spec.ts} | 12 ++-- ...rmatter.spec.ts => valueFormatter.spec.ts} | 14 ++--- ...Gatherer.ts => callStatsReportGatherer.ts} | 56 +++++++++---------- ...maryStats.ts => callStatsReportSummary.ts} | 2 +- ...sReporter.ts => connectionStatsBuilder.ts} | 2 +- ...der.ts => connectionStatsReportBuilder.ts} | 8 +-- src/webrtc/stats/groupCallStats.ts | 18 +++--- ...orter.ts => summaryStatsReportGatherer.ts} | 18 +++--- ...kStatsReporter.ts => trackStatsBuilder.ts} | 20 +++---- ...tsReporter.ts => transportStatsBuilder.ts} | 2 +- ...atsValueFormatter.ts => valueFormatter.ts} | 2 +- 17 files changed, 160 insertions(+), 119 deletions(-) rename spec/unit/webrtc/stats/{statsReportGatherer.spec.ts => callStatsReportGatherer.spec.ts} (75%) rename spec/unit/webrtc/stats/{statsReportBuilder.spec.ts => connectionStatsReportBuilder.spec.ts} (96%) rename spec/unit/webrtc/stats/{summaryStatsReporter.spec.ts => summaryStatsReportGatherer.spec.ts} (98%) rename spec/unit/webrtc/stats/{trackStatsReporter.spec.ts => trackStatsBuilder.spec.ts} (89%) rename spec/unit/webrtc/stats/{transportStatsReporter.spec.ts => transportStatsBuilder.spec.ts} (87%) rename spec/unit/webrtc/stats/{statsValueFormatter.spec.ts => valueFormatter.spec.ts} (58%) rename src/webrtc/stats/{statsReportGatherer.ts => callStatsReportGatherer.ts} (79%) rename src/webrtc/stats/{summaryStats.ts => callStatsReportSummary.ts} (95%) rename src/webrtc/stats/{connectionStatsReporter.ts => connectionStatsBuilder.ts} (96%) rename src/webrtc/stats/{statsReportBuilder.ts => connectionStatsReportBuilder.ts} (93%) rename src/webrtc/stats/{summaryStatsReporter.ts => summaryStatsReportGatherer.ts} (86%) rename src/webrtc/stats/{trackStatsReporter.ts => trackStatsBuilder.ts} (90%) rename src/webrtc/stats/{transportStatsReporter.ts => transportStatsBuilder.ts} (98%) rename src/webrtc/stats/{statsValueFormatter.ts => valueFormatter.ts} (96%) diff --git a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts b/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts similarity index 75% rename from spec/unit/webrtc/stats/statsReportGatherer.spec.ts rename to spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts index fdd2ffd502d..8f81cc17a8e 100644 --- a/spec/unit/webrtc/stats/statsReportGatherer.spec.ts +++ b/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts @@ -14,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { StatsReportGatherer } from "../../../../src/webrtc/stats/statsReportGatherer"; +import { CallStatsReportGatherer } from "../../../../src/webrtc/stats/callStatsReportGatherer"; import { StatsReportEmitter } from "../../../../src/webrtc/stats/statsReportEmitter"; +import { MediaSsrcHandler } from "../../../../src/webrtc/stats/media/mediaSsrcHandler"; const CALL_ID = "CALL_ID"; const USER_ID = "USER_ID"; -describe("StatsReportGatherer", () => { - let collector: StatsReportGatherer; +describe("CallStatsReportGatherer", () => { + let collector: CallStatsReportGatherer; let rtcSpy: RTCPeerConnection; let emitter: StatsReportEmitter; beforeEach(() => { rtcSpy = { getStats: () => new Promise(() => null) } as RTCPeerConnection; rtcSpy.addEventListener = jest.fn(); emitter = new StatsReportEmitter(); - collector = new StatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter); + collector = new CallStatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter); }); describe("on process stats", () => { @@ -145,4 +146,44 @@ describe("StatsReportGatherer", () => { expect(collector.getActive()).toBeTruthy(); }); }); + + describe("on signal state change event", () => { + let events: { [key: string]: any }; + beforeEach(() => { + events = []; + // Define the addEventListener method with a Jest mock function + rtcSpy.addEventListener = jest.fn((event: any, callback: any) => { + events[event] = callback; + }); + + collector = new CallStatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter); + }); + it("in case of stable, parse remote and local description", async () => { + // @ts-ignore + const mediaSsrcHandler = { + parse: jest.fn(), + ssrcToMid: jest.fn(), + findMidBySsrc: jest.fn(), + getSsrcToMidMap: jest.fn(), + } as MediaSsrcHandler; + + const remoteSDP = "sdp"; + const localSDP = "sdp"; + + // @ts-ignore + rtcSpy.signalingState = "stable"; + + // @ts-ignore + rtcSpy.currentRemoteDescription = { sdp: remoteSDP }; + // @ts-ignore + rtcSpy.currentLocalDescription = { sdp: localSDP }; + + // @ts-ignore + collector.trackStats.mediaSsrcHandler = mediaSsrcHandler; + + events["signalingstatechange"](); + expect(mediaSsrcHandler.parse).toHaveBeenCalledWith(remoteSDP, "remote"); + expect(mediaSsrcHandler.parse).toHaveBeenCalledWith(localSDP, "local"); + }); + }); }); diff --git a/spec/unit/webrtc/stats/statsReportBuilder.spec.ts b/spec/unit/webrtc/stats/connectionStatsReportBuilder.spec.ts similarity index 96% rename from spec/unit/webrtc/stats/statsReportBuilder.spec.ts rename to spec/unit/webrtc/stats/connectionStatsReportBuilder.spec.ts index 4b4faa1ca05..d8fb9a756f9 100644 --- a/spec/unit/webrtc/stats/statsReportBuilder.spec.ts +++ b/spec/unit/webrtc/stats/connectionStatsReportBuilder.spec.ts @@ -16,7 +16,7 @@ limitations under the License. import { TrackID } from "../../../../src/webrtc/stats/statsReport"; import { MediaTrackStats } from "../../../../src/webrtc/stats/media/mediaTrackStats"; -import { StatsReportBuilder } from "../../../../src/webrtc/stats/statsReportBuilder"; +import { ConnectionStatsReportBuilder } from "../../../../src/webrtc/stats/connectionStatsReportBuilder"; describe("StatsReportBuilder", () => { const LOCAL_VIDEO_TRACK_ID = "LOCAL_VIDEO_TRACK_ID"; @@ -39,7 +39,7 @@ describe("StatsReportBuilder", () => { describe("should build stats", () => { it("by media track stats.", async () => { - expect(StatsReportBuilder.build(stats)).toEqual({ + expect(ConnectionStatsReportBuilder.build(stats)).toEqual({ bitrate: { audio: { download: 4000, diff --git a/spec/unit/webrtc/stats/connectionStatsReporter.spec.ts b/spec/unit/webrtc/stats/connectionStatsReporter.spec.ts index 1c9b2123319..7356f2576ab 100644 --- a/spec/unit/webrtc/stats/connectionStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/connectionStatsReporter.spec.ts @@ -13,7 +13,7 @@ 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 { ConnectionStatsReporter } from "../../../../src/webrtc/stats/connectionStatsReporter"; +import { ConnectionStatsBuilder } from "../../../../src/webrtc/stats/connectionStatsBuilder"; describe("ConnectionStatsReporter", () => { describe("should on bandwidth stats", () => { @@ -22,11 +22,11 @@ describe("ConnectionStatsReporter", () => { availableIncomingBitrate: 1000, availableOutgoingBitrate: 2000, } as RTCIceCandidatePairStats; - expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 }); + expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 }); }); it("build empty bandwidth report if chromium starts attributes not available", () => { const stats = {} as RTCIceCandidatePairStats; - expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 }); + expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 }); }); }); @@ -36,11 +36,11 @@ describe("ConnectionStatsReporter", () => { availableIncomingBitrate: 1000, availableOutgoingBitrate: 2000, } as RTCIceCandidatePairStats; - expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 }); + expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 }); }); it("build empty bandwidth report if chromium starts attributes not available", () => { const stats = {} as RTCIceCandidatePairStats; - expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 }); + expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 }); }); }); }); diff --git a/spec/unit/webrtc/stats/groupCallStats.spec.ts b/spec/unit/webrtc/stats/groupCallStats.spec.ts index 672e79d6863..6aa45f307f9 100644 --- a/spec/unit/webrtc/stats/groupCallStats.spec.ts +++ b/spec/unit/webrtc/stats/groupCallStats.spec.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import { GroupCallStats } from "../../../../src/webrtc/stats/groupCallStats"; -import { SummaryStats } from "../../../../src/webrtc/stats/summaryStats"; +import { CallStatsReportSummary } from "../../../../src/webrtc/stats/callStatsReportSummary"; const GROUP_CALL_ID = "GROUP_ID"; const LOCAL_USER_ID = "LOCAL_USER_ID"; @@ -112,7 +112,7 @@ describe("GroupCallStats", () => { concealedAudio: 0, totalAudio: 0, }, - } as SummaryStats; + } as CallStatsReportSummary; let processStatsSpy; if (collector) { processStatsSpy = jest.spyOn(collector, "processStats").mockResolvedValue(summaryStats); diff --git a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts b/spec/unit/webrtc/stats/summaryStatsReportGatherer.spec.ts similarity index 98% rename from spec/unit/webrtc/stats/summaryStatsReporter.spec.ts rename to spec/unit/webrtc/stats/summaryStatsReportGatherer.spec.ts index e448c3b1604..eefff0d9b49 100644 --- a/spec/unit/webrtc/stats/summaryStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/summaryStatsReportGatherer.spec.ts @@ -13,16 +13,16 @@ 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 { SummaryStatsReporter } from "../../../../src/webrtc/stats/summaryStatsReporter"; +import { SummaryStatsReportGatherer } from "../../../../src/webrtc/stats/summaryStatsReportGatherer"; import { StatsReportEmitter } from "../../../../src/webrtc/stats/statsReportEmitter"; -describe("SummaryStatsReporter", () => { - let reporter: SummaryStatsReporter; +describe("SummaryStatsReportGatherer", () => { + let reporter: SummaryStatsReportGatherer; let emitter: StatsReportEmitter; beforeEach(() => { emitter = new StatsReportEmitter(); emitter.emitSummaryStatsReport = jest.fn(); - reporter = new SummaryStatsReporter(emitter); + reporter = new SummaryStatsReportGatherer(emitter); }); describe("build Summary Stats Report", () => { diff --git a/spec/unit/webrtc/stats/trackStatsReporter.spec.ts b/spec/unit/webrtc/stats/trackStatsBuilder.spec.ts similarity index 89% rename from spec/unit/webrtc/stats/trackStatsReporter.spec.ts rename to spec/unit/webrtc/stats/trackStatsBuilder.spec.ts index 0c22cdaf6a0..9bfe1169cdf 100644 --- a/spec/unit/webrtc/stats/trackStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/trackStatsBuilder.spec.ts @@ -13,20 +13,20 @@ 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 { TrackStatsReporter } from "../../../../src/webrtc/stats/trackStatsReporter"; +import { TrackStatsBuilder } from "../../../../src/webrtc/stats/trackStatsBuilder"; import { MediaTrackStats } from "../../../../src/webrtc/stats/media/mediaTrackStats"; -describe("TrackStatsReporter", () => { +describe("TrackStatsBuilder", () => { describe("should on frame and resolution stats", () => { it("creating empty frame and resolution report, if no data available.", async () => { const trackStats = new MediaTrackStats("1", "local", "video"); - TrackStatsReporter.buildFramerateResolution(trackStats, {}); + TrackStatsBuilder.buildFramerateResolution(trackStats, {}); expect(trackStats.getFramerate()).toEqual(0); expect(trackStats.getResolution()).toEqual({ width: -1, height: -1 }); }); it("creating empty frame and resolution report.", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildFramerateResolution(trackStats, { + TrackStatsBuilder.buildFramerateResolution(trackStats, { framesPerSecond: 22.2, frameHeight: 180, frameWidth: 360, @@ -39,7 +39,7 @@ describe("TrackStatsReporter", () => { describe("should on simulcast", () => { it("creating simulcast framerate.", async () => { const trackStats = new MediaTrackStats("1", "local", "video"); - TrackStatsReporter.calculateSimulcastFramerate( + TrackStatsBuilder.calculateSimulcastFramerate( trackStats, { framesSent: 100, @@ -58,7 +58,7 @@ describe("TrackStatsReporter", () => { describe("should on bytes received stats", () => { it("creating build bitrate received report.", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildBitrateReceived( + TrackStatsBuilder.buildBitrateReceived( trackStats, { bytesReceived: 2001000, @@ -73,7 +73,7 @@ describe("TrackStatsReporter", () => { describe("should on bytes send stats", () => { it("creating build bitrate send report.", async () => { const trackStats = new MediaTrackStats("1", "local", "video"); - TrackStatsReporter.buildBitrateSend( + TrackStatsBuilder.buildBitrateSend( trackStats, { bytesSent: 2001000, @@ -90,7 +90,7 @@ describe("TrackStatsReporter", () => { const trackStats = new MediaTrackStats("1", "remote", "video"); const remote = {} as RTCStatsReport; remote.get = jest.fn().mockReturnValue({ mimeType: "video/v8" }); - TrackStatsReporter.buildCodec(remote, trackStats, { codecId: "codecID" }); + TrackStatsBuilder.buildCodec(remote, trackStats, { codecId: "codecID" }); expect(trackStats.getCodec()).toEqual("v8"); }); }); @@ -98,7 +98,7 @@ describe("TrackStatsReporter", () => { describe("should on package lost stats", () => { it("creating build package lost on send report.", async () => { const trackStats = new MediaTrackStats("1", "local", "video"); - TrackStatsReporter.buildPacketsLost( + TrackStatsBuilder.buildPacketsLost( trackStats, { type: "outbound-rtp", @@ -114,7 +114,7 @@ describe("TrackStatsReporter", () => { }); it("creating build package lost on received report.", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildPacketsLost( + TrackStatsBuilder.buildPacketsLost( trackStats, { type: "inbound-rtp", @@ -133,7 +133,7 @@ describe("TrackStatsReporter", () => { describe("should set state of a TrackStats", () => { it("to not alive if Transceiver undefined", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.setTrackStatsState(trackStats, undefined); + TrackStatsBuilder.setTrackStatsState(trackStats, undefined); expect(trackStats.alive).toBeFalsy(); }); @@ -145,7 +145,7 @@ describe("TrackStatsReporter", () => { } as RTCRtpSender, } as RTCRtpTransceiver; - TrackStatsReporter.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); expect(trackStats.alive).toBeFalsy(); }); @@ -162,7 +162,7 @@ describe("TrackStatsReporter", () => { } as RTCRtpReceiver, } as RTCRtpTransceiver; - TrackStatsReporter.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); expect(trackStats.alive).toBeTruthy(); }); @@ -179,7 +179,7 @@ describe("TrackStatsReporter", () => { } as RTCRtpSender, } as RTCRtpTransceiver; - TrackStatsReporter.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); expect(trackStats.alive).toBeTruthy(); }); @@ -195,7 +195,7 @@ describe("TrackStatsReporter", () => { } as RTCRtpReceiver, } as RTCRtpTransceiver; - TrackStatsReporter.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); expect(trackStats.alive).toBeFalsy(); }); @@ -211,7 +211,7 @@ describe("TrackStatsReporter", () => { } as RTCRtpReceiver, } as RTCRtpTransceiver; - TrackStatsReporter.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); expect(trackStats.alive).toBeTruthy(); expect(trackStats.muted).toBeTruthy(); }); @@ -219,7 +219,7 @@ describe("TrackStatsReporter", () => { describe("should build Track Summary", () => { it("and returns empty summary if stats list empty", async () => { - const summary = TrackStatsReporter.buildTrackSummary([]); + const summary = TrackStatsBuilder.buildTrackSummary([]); expect(summary).toEqual({ audioTrackSummary: { count: 0, @@ -242,7 +242,7 @@ describe("TrackStatsReporter", () => { it("and returns summary if stats list not empty and ignore local summery", async () => { const trackStatsList = buildMockTrackStatsList(); - const summary = TrackStatsReporter.buildTrackSummary(trackStatsList); + const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList); expect(summary).toEqual({ audioTrackSummary: { count: 2, @@ -267,7 +267,7 @@ describe("TrackStatsReporter", () => { const trackStatsList = buildMockTrackStatsList(); trackStatsList[1].muted = true; trackStatsList[5].muted = true; - const summary = TrackStatsReporter.buildTrackSummary(trackStatsList); + const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList); expect(summary).toEqual({ audioTrackSummary: { count: 2, @@ -292,7 +292,7 @@ describe("TrackStatsReporter", () => { const trackStatsList = buildMockTrackStatsList(); trackStatsList[1].muted = true; trackStatsList[1].alive = false; - const summary = TrackStatsReporter.buildTrackSummary(trackStatsList); + const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList); expect(summary).toEqual({ audioTrackSummary: { count: 2, @@ -330,7 +330,7 @@ describe("TrackStatsReporter", () => { trackStatsList[2].setAudioConcealment(220, 2000); trackStatsList[5].setAudioConcealment(180, 2000); - const summary = TrackStatsReporter.buildTrackSummary(trackStatsList); + const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList); expect(summary).toEqual({ audioTrackSummary: { count: 2, @@ -355,25 +355,25 @@ describe("TrackStatsReporter", () => { describe("should build jitter value in Track Stats", () => { it("and returns track stats without jitter if report not 'inbound-rtp'", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildJitter(trackStats, { jitter: 0.01 }); + TrackStatsBuilder.buildJitter(trackStats, { jitter: 0.01 }); expect(trackStats.getJitter()).toEqual(0); }); it("and returns track stats with jitter", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp", jitter: 0.01 }); + TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp", jitter: 0.01 }); expect(trackStats.getJitter()).toEqual(10); }); it("and returns negative jitter if stats has no jitter value", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp" }); + TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp" }); expect(trackStats.getJitter()).toEqual(-1); }); it("and returns jitter as number", async () => { const trackStats = new MediaTrackStats("1", "remote", "video"); - TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp", jitter: "0.5" }); + TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp", jitter: "0.5" }); expect(trackStats.getJitter()).toEqual(500); }); }); diff --git a/spec/unit/webrtc/stats/transportStatsReporter.spec.ts b/spec/unit/webrtc/stats/transportStatsBuilder.spec.ts similarity index 87% rename from spec/unit/webrtc/stats/transportStatsReporter.spec.ts rename to spec/unit/webrtc/stats/transportStatsBuilder.spec.ts index bd3288b15ae..17283f31e90 100644 --- a/spec/unit/webrtc/stats/transportStatsReporter.spec.ts +++ b/spec/unit/webrtc/stats/transportStatsBuilder.spec.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { TransportStatsReporter } from "../../../../src/webrtc/stats/transportStatsReporter"; +import { TransportStatsBuilder } from "../../../../src/webrtc/stats/transportStatsBuilder"; import { TransportStats } from "../../../../src/webrtc/stats/transportStats"; describe("TransportStatsReporter", () => { @@ -35,7 +35,7 @@ describe("TransportStatsReporter", () => { it("build new transport stats if all properties there", () => { const { report, stats } = mockStatsReport(isFocus, 0); const conferenceStatsTransport: TransportStats[] = []; - const transportStats = TransportStatsReporter.buildReport(report, stats, conferenceStatsTransport, isFocus); + const transportStats = TransportStatsBuilder.buildReport(report, stats, conferenceStatsTransport, isFocus); expect(transportStats).toEqual([ { ip: `${remoteIC.ip + 0}:${remoteIC.port}`, @@ -54,8 +54,8 @@ describe("TransportStatsReporter", () => { const mock1 = mockStatsReport(isFocus, 0); const mock2 = mockStatsReport(isFocus, 1); let transportStats: TransportStats[] = []; - transportStats = TransportStatsReporter.buildReport(mock1.report, mock1.stats, transportStats, isFocus); - transportStats = TransportStatsReporter.buildReport(mock2.report, mock2.stats, transportStats, isFocus); + transportStats = TransportStatsBuilder.buildReport(mock1.report, mock1.stats, transportStats, isFocus); + transportStats = TransportStatsBuilder.buildReport(mock2.report, mock2.stats, transportStats, isFocus); expect(transportStats).toEqual([ { ip: `${remoteIC.ip + 0}:${remoteIC.port}`, @@ -84,8 +84,8 @@ describe("TransportStatsReporter", () => { const mock1 = mockStatsReport(isFocus, 0); const mock2 = mockStatsReport(isFocus, 0); let transportStats: TransportStats[] = []; - transportStats = TransportStatsReporter.buildReport(mock1.report, mock1.stats, transportStats, isFocus); - transportStats = TransportStatsReporter.buildReport(mock2.report, mock2.stats, transportStats, isFocus); + transportStats = TransportStatsBuilder.buildReport(mock1.report, mock1.stats, transportStats, isFocus); + transportStats = TransportStatsBuilder.buildReport(mock2.report, mock2.stats, transportStats, isFocus); expect(transportStats).toEqual([ { ip: `${remoteIC.ip + 0}:${remoteIC.port}`, diff --git a/spec/unit/webrtc/stats/statsValueFormatter.spec.ts b/spec/unit/webrtc/stats/valueFormatter.spec.ts similarity index 58% rename from spec/unit/webrtc/stats/statsValueFormatter.spec.ts rename to spec/unit/webrtc/stats/valueFormatter.spec.ts index 1ce563e91d6..5009960b195 100644 --- a/spec/unit/webrtc/stats/statsValueFormatter.spec.ts +++ b/spec/unit/webrtc/stats/valueFormatter.spec.ts @@ -13,16 +13,16 @@ 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 { StatsValueFormatter } from "../../../../src/webrtc/stats/statsValueFormatter"; +import { ValueFormatter } from "../../../../src/webrtc/stats/valueFormatter"; -describe("StatsValueFormatter", () => { +describe("ValueFormatter", () => { describe("on get non negative values", () => { it("formatter shod return number", async () => { - expect(StatsValueFormatter.getNonNegativeValue("2")).toEqual(2); - expect(StatsValueFormatter.getNonNegativeValue(0)).toEqual(0); - expect(StatsValueFormatter.getNonNegativeValue("-2")).toEqual(0); - expect(StatsValueFormatter.getNonNegativeValue("")).toEqual(0); - expect(StatsValueFormatter.getNonNegativeValue(NaN)).toEqual(0); + expect(ValueFormatter.getNonNegativeValue("2")).toEqual(2); + expect(ValueFormatter.getNonNegativeValue(0)).toEqual(0); + expect(ValueFormatter.getNonNegativeValue("-2")).toEqual(0); + expect(ValueFormatter.getNonNegativeValue("")).toEqual(0); + expect(ValueFormatter.getNonNegativeValue(NaN)).toEqual(0); }); }); }); diff --git a/src/webrtc/stats/statsReportGatherer.ts b/src/webrtc/stats/callStatsReportGatherer.ts similarity index 79% rename from src/webrtc/stats/statsReportGatherer.ts rename to src/webrtc/stats/callStatsReportGatherer.ts index 485f35ecffd..ad1e079c3d9 100644 --- a/src/webrtc/stats/statsReportGatherer.ts +++ b/src/webrtc/stats/callStatsReportGatherer.ts @@ -17,17 +17,17 @@ limitations under the License. import { ConnectionStats } from "./connectionStats"; import { StatsReportEmitter } from "./statsReportEmitter"; import { ByteSend, ByteSentStatsReport, TrackID } from "./statsReport"; -import { ConnectionStatsReporter } from "./connectionStatsReporter"; -import { TransportStatsReporter } from "./transportStatsReporter"; +import { ConnectionStatsBuilder } from "./connectionStatsBuilder"; +import { TransportStatsBuilder } from "./transportStatsBuilder"; import { MediaSsrcHandler } from "./media/mediaSsrcHandler"; import { MediaTrackHandler } from "./media/mediaTrackHandler"; import { MediaTrackStatsHandler } from "./media/mediaTrackStatsHandler"; -import { TrackStatsReporter } from "./trackStatsReporter"; -import { StatsReportBuilder } from "./statsReportBuilder"; -import { StatsValueFormatter } from "./statsValueFormatter"; -import { SummaryStats } from "./summaryStats"; +import { TrackStatsBuilder } from "./trackStatsBuilder"; +import { ConnectionStatsReportBuilder } from "./connectionStatsReportBuilder"; +import { ValueFormatter } from "./valueFormatter"; +import { CallStatsReportSummary } from "./callStatsReportSummary"; -export class StatsReportGatherer { +export class CallStatsReportGatherer { private isActive = true; private previousStatsReport: RTCStatsReport | undefined; private currentStatsReport: RTCStatsReport | undefined; @@ -46,7 +46,7 @@ export class StatsReportGatherer { this.trackStats = new MediaTrackStatsHandler(new MediaSsrcHandler(), new MediaTrackHandler(pc)); } - public async processStats(groupCallId: string, localUserId: string): Promise { + public async processStats(groupCallId: string, localUserId: string): Promise { const summary = { isFirstCollection: this.previousStatsReport === undefined, receivedMedia: 0, @@ -54,7 +54,7 @@ export class StatsReportGatherer { receivedVideoMedia: 0, audioTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 }, videoTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 }, - } as SummaryStats; + } as CallStatsReportSummary; if (this.isActive) { const statsPromise = this.pc.getStats(); if (typeof statsPromise?.then === "function") { @@ -73,7 +73,7 @@ export class StatsReportGatherer { summary.receivedMedia = this.connectionStats.bitrate.download; summary.receivedAudioMedia = this.connectionStats.bitrate.audio?.download || 0; summary.receivedVideoMedia = this.connectionStats.bitrate.video?.download || 0; - const trackSummary = TrackStatsReporter.buildTrackSummary( + const trackSummary = TrackStatsBuilder.buildTrackSummary( Array.from(this.trackStats.getTrack2stats().values()), ); return { @@ -93,14 +93,14 @@ export class StatsReportGatherer { } private processStatsReport(groupCallId: string, localUserId: string): void { - const byteSentStats: ByteSentStatsReport = new Map(); + const byteSentStatsReport: ByteSentStatsReport = new Map(); this.currentStatsReport?.forEach((now) => { const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null; // RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict* if (now.type === "candidate-pair" && now.nominated && now.state === "succeeded") { - this.connectionStats.bandwidth = ConnectionStatsReporter.buildBandwidthReport(now); - this.connectionStats.transport = TransportStatsReporter.buildReport( + this.connectionStats.bandwidth = ConnectionStatsBuilder.buildBandwidthReport(now); + this.connectionStats.transport = TransportStatsBuilder.buildReport( this.currentStatsReport, now, this.connectionStats.transport, @@ -121,7 +121,7 @@ export class StatsReportGatherer { } if (before) { - TrackStatsReporter.buildPacketsLost(trackStats, now, before); + TrackStatsBuilder.buildPacketsLost(trackStats, now, before); } // Get the resolution and framerate for only remote video sources here. For the local video sources, @@ -130,26 +130,26 @@ export class StatsReportGatherer { // more calculations needed to determine what is the highest resolution stream sent by the client if the // 'outbound-rtp' stats are used. if (now.type === "inbound-rtp") { - TrackStatsReporter.buildFramerateResolution(trackStats, now); + TrackStatsBuilder.buildFramerateResolution(trackStats, now); if (before) { - TrackStatsReporter.buildBitrateReceived(trackStats, now, before); + TrackStatsBuilder.buildBitrateReceived(trackStats, now, before); } const ts = this.trackStats.findTransceiverByTrackId(trackStats.trackId); - TrackStatsReporter.setTrackStatsState(trackStats, ts); - TrackStatsReporter.buildJitter(trackStats, now); - TrackStatsReporter.buildAudioConcealment(trackStats, now); + TrackStatsBuilder.setTrackStatsState(trackStats, ts); + TrackStatsBuilder.buildJitter(trackStats, now); + TrackStatsBuilder.buildAudioConcealment(trackStats, now); } else if (before) { - byteSentStats.set(trackStats.trackId, StatsValueFormatter.getNonNegativeValue(now.bytesSent)); - TrackStatsReporter.buildBitrateSend(trackStats, now, before); + byteSentStatsReport.set(trackStats.trackId, ValueFormatter.getNonNegativeValue(now.bytesSent)); + TrackStatsBuilder.buildBitrateSend(trackStats, now, before); } - TrackStatsReporter.buildCodec(this.currentStatsReport, trackStats, now); + TrackStatsBuilder.buildCodec(this.currentStatsReport, trackStats, now); } else if (now.type === "track" && now.kind === "video" && !now.remoteSource) { const trackStats = this.trackStats.findLocalVideoTrackStats(now); if (!trackStats) { return; } - TrackStatsReporter.buildFramerateResolution(trackStats, now); - TrackStatsReporter.calculateSimulcastFramerate( + TrackStatsBuilder.buildFramerateResolution(trackStats, now); + TrackStatsBuilder.calculateSimulcastFramerate( trackStats, now, before, @@ -158,8 +158,8 @@ export class StatsReportGatherer { } }); - this.emitter.emitByteSendReport(byteSentStats); - this.processAndEmitReport(); + this.emitter.emitByteSendReport(byteSentStatsReport); + this.processAndEmitConnectionStatsReport(); } public setActive(isActive: boolean): void { @@ -174,8 +174,8 @@ export class StatsReportGatherer { this.isActive = false; } - private processAndEmitReport(): void { - const report = StatsReportBuilder.build(this.trackStats.getTrack2stats()); + private processAndEmitConnectionStatsReport(): void { + const report = ConnectionStatsReportBuilder.build(this.trackStats.getTrack2stats()); this.connectionStats.bandwidth = report.bandwidth; this.connectionStats.bitrate = report.bitrate; diff --git a/src/webrtc/stats/summaryStats.ts b/src/webrtc/stats/callStatsReportSummary.ts similarity index 95% rename from src/webrtc/stats/summaryStats.ts rename to src/webrtc/stats/callStatsReportSummary.ts index e8189be3a58..fc62ae3b3f1 100644 --- a/src/webrtc/stats/summaryStats.ts +++ b/src/webrtc/stats/callStatsReportSummary.ts @@ -10,7 +10,7 @@ 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. */ -export interface SummaryStats { +export interface CallStatsReportSummary { receivedMedia: number; receivedAudioMedia: number; receivedVideoMedia: number; diff --git a/src/webrtc/stats/connectionStatsReporter.ts b/src/webrtc/stats/connectionStatsBuilder.ts similarity index 96% rename from src/webrtc/stats/connectionStatsReporter.ts rename to src/webrtc/stats/connectionStatsBuilder.ts index c43b9b40c19..f954fed49f7 100644 --- a/src/webrtc/stats/connectionStatsReporter.ts +++ b/src/webrtc/stats/connectionStatsBuilder.ts @@ -15,7 +15,7 @@ limitations under the License. */ import { Bitrate } from "./media/mediaTrackStats"; -export class ConnectionStatsReporter { +export class ConnectionStatsBuilder { public static buildBandwidthReport(now: RTCIceCandidatePairStats): Bitrate { const availableIncomingBitrate = now.availableIncomingBitrate; const availableOutgoingBitrate = now.availableOutgoingBitrate; diff --git a/src/webrtc/stats/statsReportBuilder.ts b/src/webrtc/stats/connectionStatsReportBuilder.ts similarity index 93% rename from src/webrtc/stats/statsReportBuilder.ts rename to src/webrtc/stats/connectionStatsReportBuilder.ts index 566648fb0e9..5ed99d59bae 100644 --- a/src/webrtc/stats/statsReportBuilder.ts +++ b/src/webrtc/stats/connectionStatsReportBuilder.ts @@ -16,7 +16,7 @@ limitations under the License. import { AudioConcealment, CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from "./statsReport"; import { MediaTrackStats, Resolution } from "./media/mediaTrackStats"; -export class StatsReportBuilder { +export class ConnectionStatsReportBuilder { public static build(stats: Map): ConnectionStatsReport { const report = {} as ConnectionStatsReport; @@ -103,12 +103,12 @@ export class StatsReportBuilder { }; report.packetLoss = { - total: StatsReportBuilder.calculatePacketLoss( + total: ConnectionStatsReportBuilder.calculatePacketLoss( lostPackets.download + lostPackets.upload, totalPackets.download + totalPackets.upload, ), - download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download), - upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload), + download: ConnectionStatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download), + upload: ConnectionStatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload), }; report.audioConcealment = audioConcealment; report.totalAudioConcealment = { diff --git a/src/webrtc/stats/groupCallStats.ts b/src/webrtc/stats/groupCallStats.ts index 898424846fb..054d51f8154 100644 --- a/src/webrtc/stats/groupCallStats.ts +++ b/src/webrtc/stats/groupCallStats.ts @@ -13,16 +13,16 @@ 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 { StatsReportGatherer } from "./statsReportGatherer"; +import { CallStatsReportGatherer } from "./callStatsReportGatherer"; import { StatsReportEmitter } from "./statsReportEmitter"; -import { SummaryStats } from "./summaryStats"; -import { SummaryStatsReporter } from "./summaryStatsReporter"; +import { CallStatsReportSummary } from "./callStatsReportSummary"; +import { SummaryStatsReportGatherer } from "./summaryStatsReportGatherer"; export class GroupCallStats { private timer: undefined | ReturnType; - private readonly gatherers: Map = new Map(); + private readonly gatherers: Map = new Map(); public readonly reports = new StatsReportEmitter(); - private readonly summaryStatsReporter = new SummaryStatsReporter(this.reports); + private readonly summaryStatsReportGatherer = new SummaryStatsReportGatherer(this.reports); public constructor(private groupCallId: string, private userId: string, private interval: number = 10000) {} @@ -49,7 +49,7 @@ export class GroupCallStats { if (this.hasStatsReportGatherer(callId)) { return false; } - this.gatherers.set(callId, new StatsReportGatherer(callId, userId, peerConnection, this.reports)); + this.gatherers.set(callId, new CallStatsReportGatherer(callId, userId, peerConnection, this.reports)); return true; } @@ -57,17 +57,17 @@ export class GroupCallStats { return this.gatherers.delete(callId); } - public getStatsReportGatherer(callId: string): StatsReportGatherer | undefined { + public getStatsReportGatherer(callId: string): CallStatsReportGatherer | undefined { return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined; } private processStats(): void { - const summary: Promise[] = []; + const summary: Promise[] = []; this.gatherers.forEach((c) => { summary.push(c.processStats(this.groupCallId, this.userId)); }); - Promise.all(summary).then((s: Awaited[]) => this.summaryStatsReporter.build(s)); + Promise.all(summary).then((s: Awaited[]) => this.summaryStatsReportGatherer.build(s)); } public setInterval(interval: number): void { diff --git a/src/webrtc/stats/summaryStatsReporter.ts b/src/webrtc/stats/summaryStatsReportGatherer.ts similarity index 86% rename from src/webrtc/stats/summaryStatsReporter.ts rename to src/webrtc/stats/summaryStatsReportGatherer.ts index 79cf20dcffc..87601dcac7a 100644 --- a/src/webrtc/stats/summaryStatsReporter.ts +++ b/src/webrtc/stats/summaryStatsReportGatherer.ts @@ -11,10 +11,10 @@ See the License for the specific language governing permissions and limitations under the License. */ import { StatsReportEmitter } from "./statsReportEmitter"; -import { SummaryStats } from "./summaryStats"; +import { CallStatsReportSummary } from "./callStatsReportSummary"; import { SummaryStatsReport } from "./statsReport"; -interface SummaryCounter { +interface CallStatsReportSummaryCounter { receivedAudio: number; receivedVideo: number; receivedMedia: number; @@ -22,10 +22,10 @@ interface SummaryCounter { totalAudio: number; } -export class SummaryStatsReporter { +export class SummaryStatsReportGatherer { public constructor(private emitter: StatsReportEmitter) {} - public build(allSummary: SummaryStats[]): void { + public build(allSummary: CallStatsReportSummary[]): void { // Filter all stats which collect the first time webrtc stats. // Because stats based on time interval and the first collection of a summery stats has no previous // webrtcStats as basement all the calculation are 0. We don't want track the 0 stats. @@ -34,7 +34,7 @@ export class SummaryStatsReporter { if (summaryTotalCount === 0) { return; } - const summaryCounter: SummaryCounter = { + const summaryCounter: CallStatsReportSummaryCounter = { receivedAudio: 0, receivedVideo: 0, receivedMedia: 0, @@ -70,7 +70,7 @@ export class SummaryStatsReporter { this.emitter.emitSummaryStatsReport(report); } - private countTrackListReceivedMedia(counter: SummaryCounter, stats: SummaryStats): void { + private countTrackListReceivedMedia(counter: CallStatsReportSummaryCounter, stats: CallStatsReportSummary): void { let hasReceivedAudio = false; let hasReceivedVideo = false; if (stats.receivedAudioMedia > 0 || stats.audioTrackSummary.count === 0) { @@ -92,7 +92,7 @@ export class SummaryStatsReporter { } } - private buildMaxJitter(maxJitter: number, stats: SummaryStats): number { + private buildMaxJitter(maxJitter: number, stats: CallStatsReportSummary): number { if (maxJitter < stats.videoTrackSummary.maxJitter) { maxJitter = stats.videoTrackSummary.maxJitter; } @@ -103,7 +103,7 @@ export class SummaryStatsReporter { return maxJitter; } - private buildMaxPacketLoss(maxPacketLoss: number, stats: SummaryStats): number { + private buildMaxPacketLoss(maxPacketLoss: number, stats: CallStatsReportSummary): number { if (maxPacketLoss < stats.videoTrackSummary.maxPacketLoss) { maxPacketLoss = stats.videoTrackSummary.maxPacketLoss; } @@ -114,7 +114,7 @@ export class SummaryStatsReporter { return maxPacketLoss; } - private countConcealedAudio(summaryCounter: SummaryCounter, stats: SummaryStats): void { + private countConcealedAudio(summaryCounter: CallStatsReportSummaryCounter, stats: CallStatsReportSummary): void { summaryCounter.concealedAudio += stats.audioTrackSummary.concealedAudio; summaryCounter.totalAudio += stats.audioTrackSummary.totalAudio; } diff --git a/src/webrtc/stats/trackStatsReporter.ts b/src/webrtc/stats/trackStatsBuilder.ts similarity index 90% rename from src/webrtc/stats/trackStatsReporter.ts rename to src/webrtc/stats/trackStatsBuilder.ts index 75409f26afa..c670fddbb47 100644 --- a/src/webrtc/stats/trackStatsReporter.ts +++ b/src/webrtc/stats/trackStatsBuilder.ts @@ -1,8 +1,8 @@ import { MediaTrackStats } from "./media/mediaTrackStats"; -import { StatsValueFormatter } from "./statsValueFormatter"; -import { TrackSummary } from "./summaryStats"; +import { ValueFormatter } from "./valueFormatter"; +import { TrackSummary } from "./callStatsReportSummary"; -export class TrackStatsReporter { +export class TrackStatsBuilder { public static buildFramerateResolution(trackStats: MediaTrackStats, now: any): void { const resolution = { height: now.frameHeight, @@ -56,7 +56,7 @@ export class TrackStatsReporter { public static buildBitrateReceived(trackStats: MediaTrackStats, now: any, before: any): void { trackStats.setBitrate({ - download: TrackStatsReporter.calculateBitrate( + download: TrackStatsBuilder.calculateBitrate( now.bytesReceived, before.bytesReceived, now.timestamp, @@ -81,11 +81,11 @@ export class TrackStatsReporter { packetsNow = 0; } - const packetsBefore = StatsValueFormatter.getNonNegativeValue(before[key]); + const packetsBefore = ValueFormatter.getNonNegativeValue(before[key]); const packetsDiff = Math.max(0, packetsNow - packetsBefore); - const packetsLostNow = StatsValueFormatter.getNonNegativeValue(now.packetsLost); - const packetsLostBefore = StatsValueFormatter.getNonNegativeValue(before.packetsLost); + const packetsLostNow = ValueFormatter.getNonNegativeValue(now.packetsLost); + const packetsLostBefore = ValueFormatter.getNonNegativeValue(before.packetsLost); const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore); trackStats.setLoss({ @@ -101,8 +101,8 @@ export class TrackStatsReporter { nowTimestamp: number, beforeTimestamp: number, ): number { - const bytesNow = StatsValueFormatter.getNonNegativeValue(bytesNowAny); - const bytesBefore = StatsValueFormatter.getNonNegativeValue(bytesBeforeAny); + const bytesNow = ValueFormatter.getNonNegativeValue(bytesNowAny); + const bytesBefore = ValueFormatter.getNonNegativeValue(bytesBeforeAny); const bytesProcessed = Math.max(0, bytesNow - bytesBefore); const timeMs = nowTimestamp - beforeTimestamp; @@ -188,7 +188,7 @@ export class TrackStatsReporter { const jitterStr = statsReport?.jitter; if (jitterStr !== undefined) { - const jitter = StatsValueFormatter.getNonNegativeValue(jitterStr); + const jitter = ValueFormatter.getNonNegativeValue(jitterStr); trackStats.setJitter(Math.round(jitter * 1000)); } else { trackStats.setJitter(-1); diff --git a/src/webrtc/stats/transportStatsReporter.ts b/src/webrtc/stats/transportStatsBuilder.ts similarity index 98% rename from src/webrtc/stats/transportStatsReporter.ts rename to src/webrtc/stats/transportStatsBuilder.ts index d419a73972b..12ed7b0cbe4 100644 --- a/src/webrtc/stats/transportStatsReporter.ts +++ b/src/webrtc/stats/transportStatsBuilder.ts @@ -1,6 +1,6 @@ import { TransportStats } from "./transportStats"; -export class TransportStatsReporter { +export class TransportStatsBuilder { public static buildReport( report: RTCStatsReport | undefined, now: RTCIceCandidatePairStats, diff --git a/src/webrtc/stats/statsValueFormatter.ts b/src/webrtc/stats/valueFormatter.ts similarity index 96% rename from src/webrtc/stats/statsValueFormatter.ts rename to src/webrtc/stats/valueFormatter.ts index c658fa66504..bf75ce29f65 100644 --- a/src/webrtc/stats/statsValueFormatter.ts +++ b/src/webrtc/stats/valueFormatter.ts @@ -10,7 +10,7 @@ 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. */ -export class StatsValueFormatter { +export class ValueFormatter { public static getNonNegativeValue(imput: any): number { let value = imput; From b29e1e9d21b5c0afcfaf985d104a9e2e87e85c15 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Fri, 26 May 2023 09:29:35 +0100 Subject: [PATCH 85/94] Adjust fetchEditsWhereNeeded to use a clearer filter and async function (#3411) * Make a clear and explicit filter on which events are considered for fetchEventsWhereNeeded * Convert the logic in fetchEventsWhereNeeded to an async function --- src/models/thread.ts | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/models/thread.ts b/src/models/thread.ts index 11727d9704e..3179860819d 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -519,26 +519,26 @@ export class Thread extends ReadReceipt { const recursionSupport = this.client.canSupport.get(Feature.RelationsRecursion) ?? ServerSupport.Unsupported; if (recursionSupport === ServerSupport.Unsupported) { return Promise.all( - events - .filter((e) => e.isEncrypted()) - .map((event: MatrixEvent) => { - // The only type of relation that gets edits is a thread message. - if (event.getThread() === undefined && event.isRelation()) return; - return this.client - .relations(this.roomId, event.getId()!, RelationType.Replace, event.getType(), { + events.filter(isAnEncryptedThreadMessage).map(async (event: MatrixEvent) => { + try { + const relations = await this.client.relations( + this.roomId, + event.getId()!, + RelationType.Replace, + event.getType(), + { limit: 1, - }) - .then((relations) => { - if (relations.events.length) { - const editEvent = relations.events[0]; - event.makeReplaced(editEvent); - this.insertEventIntoTimeline(editEvent); - } - }) - .catch((e) => { - logger.error("Failed to load edits for encrypted thread event", e); - }); - }), + }, + ); + if (relations.events.length) { + const editEvent = relations.events[0]; + event.makeReplaced(editEvent); + this.insertEventIntoTimeline(editEvent); + } + } catch (e) { + logger.error("Failed to load edits for encrypted thread event", e); + } + }), ); } } @@ -708,6 +708,16 @@ export class Thread extends ReadReceipt { } } +/** + * Decide whether an event deserves to have its potential edits fetched. + * + * @returns true if this event is encrypted and is a message that is part of a + * thread - either inside it, or a root. + */ +function isAnEncryptedThreadMessage(event: MatrixEvent): boolean { + return event.isEncrypted() && (event.isRelation(THREAD_RELATION_TYPE.name) || event.isThreadRoot); +} + export const FILTER_RELATED_BY_SENDERS = new ServerControlledNamespacedValue( "related_by_senders", "io.element.relation_senders", From 56c5375bbddeb8555d7b6e51b3f2a9374fcfc902 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 31 May 2023 10:05:55 +0100 Subject: [PATCH 86/94] Remove spec non-compliant extended glob format (#3423) * Remove spec non-compliant extended glob format * Simplify * Remove tests for non spec compliant behaviour * Remove stale rules --- spec/unit/pushprocessor.spec.ts | 78 --------------------------------- spec/unit/utils.spec.ts | 17 +++++++ src/models/invites-ignorer.ts | 2 +- src/utils.ts | 29 ++++-------- 4 files changed, 26 insertions(+), 100 deletions(-) diff --git a/spec/unit/pushprocessor.spec.ts b/spec/unit/pushprocessor.spec.ts index 36153e7deea..c381c5e6406 100644 --- a/spec/unit/pushprocessor.spec.ts +++ b/spec/unit/pushprocessor.spec.ts @@ -97,51 +97,6 @@ describe("NotificationService", function () { pattern: "foo*bar", rule_id: "foobar", }, - { - actions: [ - "notify", - { - set_tweak: "sound", - value: "default", - }, - { - set_tweak: "highlight", - }, - ], - enabled: true, - pattern: "p[io]ng", - rule_id: "pingpong", - }, - { - actions: [ - "notify", - { - set_tweak: "sound", - value: "default", - }, - { - set_tweak: "highlight", - }, - ], - enabled: true, - pattern: "I ate [0-9] pies", - rule_id: "pies", - }, - { - actions: [ - "notify", - { - set_tweak: "sound", - value: "default", - }, - { - set_tweak: "highlight", - }, - ], - enabled: true, - pattern: "b[!ai]ke", - rule_id: "bakebike", - }, ], override: [ { @@ -289,39 +244,6 @@ describe("NotificationService", function () { expect(actions.tweaks.highlight).toEqual(true); }); - // TODO: This is not spec compliant behaviour. - // - // See https://spec.matrix.org/v1.5/client-server-api/#conditions-1 which - // describes pattern should glob: - // - // 1. * matches 0 or more characters; - // 2. ? matches exactly one character - it("should bing on character group ([abc]) bing words.", function () { - testEvent.event.content!.body = "Ping!"; - let actions = pushProcessor.actionsForEvent(testEvent); - expect(actions.tweaks.highlight).toEqual(true); - testEvent.event.content!.body = "Pong!"; - actions = pushProcessor.actionsForEvent(testEvent); - expect(actions.tweaks.highlight).toEqual(true); - }); - - // TODO: This is not spec compliant behaviour. (See above.) - it("should bing on character range ([a-z]) bing words.", function () { - testEvent.event.content!.body = "I ate 6 pies"; - const actions = pushProcessor.actionsForEvent(testEvent); - expect(actions.tweaks.highlight).toEqual(true); - }); - - // TODO: This is not spec compliant behaviour. (See above.) - it("should bing on character negation ([!a]) bing words.", function () { - testEvent.event.content!.body = "boke"; - let actions = pushProcessor.actionsForEvent(testEvent); - expect(actions.tweaks.highlight).toEqual(true); - testEvent.event.content!.body = "bake"; - actions = pushProcessor.actionsForEvent(testEvent); - expect(actions.tweaks.highlight).toEqual(false); - }); - it("should not bing on room server ACL changes", function () { testEvent = utils.mkEvent({ type: EventType.RoomServerAcl, diff --git a/spec/unit/utils.spec.ts b/spec/unit/utils.spec.ts index 13fffb93cd0..5c2db9e9c88 100644 --- a/spec/unit/utils.spec.ts +++ b/spec/unit/utils.spec.ts @@ -30,6 +30,8 @@ import { sortEventsByLatestContentTimestamp, safeSet, MapWithDefault, + globToRegexp, + escapeRegExp, } from "../../src/utils"; import { logger } from "../../src/logger"; import { mkMessage } from "../test-utils/test-utils"; @@ -725,4 +727,19 @@ describe("utils", function () { await utils.immediate(); }); }); + + describe("escapeRegExp", () => { + it("should escape XYZ", () => { + expect(escapeRegExp("[FIT-Connect Zustelldienst \\(Testumgebung\\)]")).toMatchInlineSnapshot( + `"\\[FIT-Connect Zustelldienst \\\\\\(Testumgebung\\\\\\)\\]"`, + ); + }); + }); + + describe("globToRegexp", () => { + it("should not explode when given regexes as globs", () => { + const result = globToRegexp("[FIT-Connect Zustelldienst \\(Testumgebung\\)]"); + expect(result).toMatchInlineSnapshot(`"\\[FIT-Connect Zustelldienst \\\\\\(Testumgebung\\\\\\)\\]"`); + }); + }); }); diff --git a/src/models/invites-ignorer.ts b/src/models/invites-ignorer.ts index 173ba620d9b..bb18cf0797f 100644 --- a/src/models/invites-ignorer.ts +++ b/src/models/invites-ignorer.ts @@ -186,7 +186,7 @@ export class IgnoredInvites { } let regexp: RegExp; try { - regexp = new RegExp(globToRegexp(glob, false)); + regexp = new RegExp(globToRegexp(glob)); } catch (ex) { // Assume invalid event. continue; diff --git a/src/utils.ts b/src/utils.ts index e2b29bdd4d1..1dba236c9c5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -357,27 +357,14 @@ export function escapeRegExp(string: string): string { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } -export function globToRegexp(glob: string, extended = false): string { - // From - // https://github.com/matrix-org/synapse/blob/abbee6b29be80a77e05730707602f3bbfc3f38cb/synapse/push/__init__.py#L132 - // Because micromatch is about 130KB with dependencies, - // and minimatch is not much better. - const replacements: [RegExp, string | ((substring: string, ...args: any[]) => string)][] = [ - [/\\\*/g, ".*"], - [/\?/g, "."], - ]; - if (!extended) { - replacements.push([ - /\\\[(!|)(.*)\\]/g, - (_match: string, neg: string, pat: string): string => - ["[", neg ? "^" : "", 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), - escapeRegExp(glob), - ); +/** + * Converts Matrix glob-style string to a regular expression + * https://spec.matrix.org/v1.7/appendices/#glob-style-matching + * @param glob - Matrix glob-style string + * @returns regular expression + */ +export function globToRegexp(glob: string): string { + return escapeRegExp(glob).replace(/\\\*/g, ".*").replace(/\?/g, "."); } export function ensureNoTrailingSlash(url: string): string; From 648a1a09b192534bcde9e0a23c1f3c30541fc2e6 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 31 May 2023 08:15:49 -0600 Subject: [PATCH 87/94] Mark room version 10 as safe (#3425) As should have been done a year ago. --- src/models/room.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/room.ts b/src/models/room.ts index f56d30629e4..04967091e80 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -72,8 +72,8 @@ import { isPollEvent, Poll, PollEvent } from "./poll"; // room versions which are considered okay for people to run without being asked // to upgrade (ie: "stable"). Eventually, we should remove these when all homeservers // return an m.room_versions capability. -export const KNOWN_SAFE_ROOM_VERSION = "9"; -const SAFE_ROOM_VERSIONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; +export const KNOWN_SAFE_ROOM_VERSION = "10"; +const SAFE_ROOM_VERSIONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]; interface IOpts { /** From feb424b0a9433ef7a75dd9099b849461e07a7a7a Mon Sep 17 00:00:00 2001 From: sigmaSd Date: Thu, 1 Jun 2023 08:52:55 +0100 Subject: [PATCH 88/94] mention deno support in the README (#3417) * mention deno support in the README It seems to work fine, and I have a demo bot with it https://github.com/sigmaSd/deno-matrix-bot * Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9099f8ec44d..6d5aaa77662 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ client.publicRooms(function (err, data) { See below for how to include libolm to enable end-to-end-encryption. Please check [the Node.js terminal app](examples/node) for a more complex example. +You can also use the sdk with [Deno](https://deno.land/) (`import npm:matrix-js-sdk`) but its not officialy supported. + To start the client: ```javascript From 9c5c7ddb17f1a9edfe1d7145795ac0128d4ba0d5 Mon Sep 17 00:00:00 2001 From: Enrico Schwendig Date: Thu, 1 Jun 2023 17:07:44 +0200 Subject: [PATCH 89/94] Add remote user and call id in webrtc call stats (#3413) * Refactor names in webrtc stats * Refactor summary stats reporter to gatherer * Add call and opponent member id to call stats reports * Update opponent member when we know them * Add missing return type * remove async in test * mark new stats property as optional to avoid braking changes --- .../webrtc/stats/callStatsReportGatherer.spec.ts | 2 +- src/webrtc/call.ts | 7 ++++++- src/webrtc/stats/callStatsReportGatherer.ts | 12 ++++++++++-- src/webrtc/stats/groupCallStats.ts | 12 ++++++++++-- src/webrtc/stats/statsReport.ts | 4 ++++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts b/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts index 8f81cc17a8e..e6a364d6a8b 100644 --- a/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts +++ b/spec/unit/webrtc/stats/callStatsReportGatherer.spec.ts @@ -158,7 +158,7 @@ describe("CallStatsReportGatherer", () => { collector = new CallStatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter); }); - it("in case of stable, parse remote and local description", async () => { + it("in case of stable, parse remote and local description", () => { // @ts-ignore const mediaSsrcHandler = { parse: jest.fn(), diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1e42c31432e..875dd26996b 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -2848,7 +2848,9 @@ export class MatrixCall extends TypedEventEmitter { diff --git a/src/webrtc/stats/callStatsReportGatherer.ts b/src/webrtc/stats/callStatsReportGatherer.ts index ad1e079c3d9..fb3e50e6907 100644 --- a/src/webrtc/stats/callStatsReportGatherer.ts +++ b/src/webrtc/stats/callStatsReportGatherer.ts @@ -37,7 +37,7 @@ export class CallStatsReportGatherer { public constructor( public readonly callId: string, - public readonly remoteUserId: string, + private opponentMemberId: string, private readonly pc: RTCPeerConnection, private readonly emitter: StatsReportEmitter, private readonly isFocus = true, @@ -93,7 +93,9 @@ export class CallStatsReportGatherer { } private processStatsReport(groupCallId: string, localUserId: string): void { - const byteSentStatsReport: ByteSentStatsReport = new Map(); + const byteSentStatsReport: ByteSentStatsReport = new Map() as ByteSentStatsReport; + byteSentStatsReport.callId = this.callId; + byteSentStatsReport.opponentMemberId = this.opponentMemberId; this.currentStatsReport?.forEach((now) => { const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null; @@ -176,6 +178,8 @@ export class CallStatsReportGatherer { private processAndEmitConnectionStatsReport(): void { const report = ConnectionStatsReportBuilder.build(this.trackStats.getTrack2stats()); + report.callId = this.callId; + report.opponentMemberId = this.opponentMemberId; this.connectionStats.bandwidth = report.bandwidth; this.connectionStats.bitrate = report.bitrate; @@ -201,4 +205,8 @@ export class CallStatsReportGatherer { } } } + + public setOpponentMemberId(id: string): void { + this.opponentMemberId = id; + } } diff --git a/src/webrtc/stats/groupCallStats.ts b/src/webrtc/stats/groupCallStats.ts index 054d51f8154..40ee9797a44 100644 --- a/src/webrtc/stats/groupCallStats.ts +++ b/src/webrtc/stats/groupCallStats.ts @@ -45,11 +45,15 @@ export class GroupCallStats { return this.gatherers.has(callId); } - public addStatsReportGatherer(callId: string, userId: string, peerConnection: RTCPeerConnection): boolean { + public addStatsReportGatherer( + callId: string, + opponentMemberId: string, + peerConnection: RTCPeerConnection, + ): boolean { if (this.hasStatsReportGatherer(callId)) { return false; } - this.gatherers.set(callId, new CallStatsReportGatherer(callId, userId, peerConnection, this.reports)); + this.gatherers.set(callId, new CallStatsReportGatherer(callId, opponentMemberId, peerConnection, this.reports)); return true; } @@ -61,6 +65,10 @@ export class GroupCallStats { return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined; } + public updateOpponentMember(callId: string, opponentMember: string): void { + this.getStatsReportGatherer(callId)?.setOpponentMemberId(opponentMember); + } + private processStats(): void { const summary: Promise[] = []; this.gatherers.forEach((c) => { diff --git a/src/webrtc/stats/statsReport.ts b/src/webrtc/stats/statsReport.ts index cdfa751f464..750c26051bb 100644 --- a/src/webrtc/stats/statsReport.ts +++ b/src/webrtc/stats/statsReport.ts @@ -28,10 +28,14 @@ export type TrackID = string; export type ByteSend = number; export interface ByteSentStatsReport extends Map { + callId?: string; + opponentMemberId?: string; // is a map: `local trackID` => byte send } export interface ConnectionStatsReport { + callId?: string; + opponentMemberId?: string; bandwidth: ConnectionStatsBandwidth; bitrate: ConnectionStatsBitrate; packetLoss: PacketLoss; From 71f9b25db7bb1704aabf249dcba5d18d3aa94b08 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 1 Jun 2023 16:29:05 +0100 Subject: [PATCH 90/94] Ensure we do not add relations to the wrong timeline (#3427) * Do not assume that a relation lives in main timeline if we do not know its parent * For pagination, partition relations with unknown parents into a separate bucket And only add them to relation map, no timelines * Make addLiveEvents async and have it fetch parent events of unknown relations to not insert into the wrong timeline * Fix tests not awaiting addLIveEvents * Fix handling of thread roots in eventShouldLiveIn * Fix types * Fix tests * Fix import * Stash thread ID of relations in unsigned to be stashed in sync accumulator * Persist after processing * Revert "Persist after processing" This reverts commit 05ed6409b35f5e9bea3b699d0abcaac3d02588c5. * Update unsigned field name to match MSC4023 * Persist after processing to store thread id in unsigned sync accumulator * Add test * Fix replayEvents getting doubled up due to Thread::addEvents being called in createThread and separately * Fix test * Switch to using UnstableValue * Add comment * Iterate --- .../matrix-client-event-timeline.spec.ts | 15 +- spec/integ/matrix-client-syncing.spec.ts | 124 ++++++ ...matrix-client-unread-notifications.spec.ts | 2 +- spec/integ/sliding-sync-sdk.spec.ts | 152 ++++--- spec/unit/models/thread.spec.ts | 2 +- spec/unit/room.spec.ts | 390 +++++++++--------- src/@types/event.ts | 7 + src/client.ts | 10 +- src/models/event.ts | 9 +- src/models/room.ts | 95 ++++- src/sliding-sync-sdk.ts | 12 +- src/sync.ts | 21 +- 12 files changed, 517 insertions(+), 322 deletions(-) diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index bb4e9563431..680e408380b 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -1142,7 +1142,7 @@ describe("MatrixClient event timelines", function () { const prom = emitPromise(room, ThreadEvent.Update); // Assume we're seeing the reply while loading backlog - room.addLiveEvents([THREAD_REPLY2]); + await room.addLiveEvents([THREAD_REPLY2]); httpBackend .when( "GET", @@ -1156,7 +1156,7 @@ describe("MatrixClient event timelines", function () { }); await flushHttp(prom); // but while loading the metadata, a new reply has arrived - room.addLiveEvents([THREAD_REPLY3]); + await room.addLiveEvents([THREAD_REPLY3]); const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!; // then the events should still be all in the right order expect(thread.events.map((it) => it.getId())).toEqual([ @@ -1248,7 +1248,7 @@ describe("MatrixClient event timelines", function () { const prom = emitPromise(room, ThreadEvent.Update); // Assume we're seeing the reply while loading backlog - room.addLiveEvents([THREAD_REPLY2]); + await room.addLiveEvents([THREAD_REPLY2]); httpBackend .when( "GET", @@ -1267,7 +1267,7 @@ describe("MatrixClient event timelines", function () { }); await flushHttp(prom); // but while loading the metadata, a new reply has arrived - room.addLiveEvents([THREAD_REPLY3]); + await room.addLiveEvents([THREAD_REPLY3]); const thread = room.getThread(THREAD_ROOT_UPDATED.event_id!)!; // then the events should still be all in the right order expect(thread.events.map((it) => it.getId())).toEqual([ @@ -1572,7 +1572,7 @@ describe("MatrixClient event timelines", function () { respondToEvent(THREAD_ROOT_UPDATED); respondToEvent(THREAD_ROOT_UPDATED); respondToEvent(THREAD2_ROOT); - room.addLiveEvents([THREAD_REPLY2]); + await room.addLiveEvents([THREAD_REPLY2]); await httpBackend.flushAllExpected(); await prom; expect(thread.length).toBe(2); @@ -1937,11 +1937,6 @@ describe("MatrixClient event timelines", function () { .respond(200, function () { return THREAD_ROOT; }); - httpBackend - .when("GET", "/rooms/!foo%3Abar/event/" + encodeURIComponent(THREAD_ROOT.event_id!)) - .respond(200, function () { - return THREAD_ROOT; - }); httpBackend .when("GET", "/rooms/!foo%3Abar/context/" + encodeURIComponent(THREAD_ROOT.event_id!)) .respond(200, function () { diff --git a/spec/integ/matrix-client-syncing.spec.ts b/spec/integ/matrix-client-syncing.spec.ts index 86228037a46..baec3039ff4 100644 --- a/spec/integ/matrix-client-syncing.spec.ts +++ b/spec/integ/matrix-client-syncing.spec.ts @@ -36,11 +36,15 @@ import { NotificationCountType, IEphemeral, Room, + IndexedDBStore, + RelationType, } from "../../src"; import { ReceiptType } from "../../src/@types/read_receipts"; import { UNREAD_THREAD_NOTIFICATIONS } from "../../src/@types/sync"; import * as utils from "../test-utils/test-utils"; import { TestClient } from "../TestClient"; +import { emitPromise, mkEvent, mkMessage } from "../test-utils/test-utils"; +import { THREAD_RELATION_TYPE } from "../../src/models/thread"; describe("MatrixClient syncing", () => { const selfUserId = "@alice:localhost"; @@ -1867,4 +1871,124 @@ describe("MatrixClient syncing (IndexedDB version)", () => { idbClient.stopClient(); idbHttpBackend.stop(); }); + + it("should query server for which thread a 2nd order relation belongs to and stash in sync accumulator", async () => { + const roomId = "!room:example.org"; + + async function startClient(client: MatrixClient): Promise { + await Promise.all([ + idbClient.startClient({ + // Without this all events just go into the main timeline + threadSupport: true, + }), + idbHttpBackend.flushAllExpected(), + emitPromise(idbClient, ClientEvent.Room), + ]); + } + + function assertEventsExpected(client: MatrixClient): void { + const room = client.getRoom(roomId); + const mainTimelineEvents = room!.getLiveTimeline().getEvents(); + expect(mainTimelineEvents).toHaveLength(1); + expect(mainTimelineEvents[0].getContent().body).toEqual("Test"); + + const thread = room!.getThread("$someThreadId")!; + expect(thread.replayEvents).toHaveLength(1); + expect(thread.replayEvents![0].getRelation()!.key).toEqual("🪿"); + } + + let idbTestClient = new TestClient(selfUserId, "DEVICE", selfAccessToken, undefined, { + store: new IndexedDBStore({ + indexedDB: global.indexedDB, + dbName: "test", + }), + }); + let idbHttpBackend = idbTestClient.httpBackend; + let idbClient = idbTestClient.client; + await idbClient.store.startup(); + + idbHttpBackend.when("GET", "/versions").respond(200, { versions: ["v1.4"] }); + idbHttpBackend.when("GET", "/pushrules/").respond(200, {}); + idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + + const syncRoomSection = { + join: { + [roomId]: { + timeline: { + prev_batch: "foo", + events: [ + mkMessage({ + room: roomId, + user: selfUserId, + msg: "Test", + }), + mkEvent({ + room: roomId, + user: selfUserId, + content: { + "m.relates_to": { + rel_type: RelationType.Annotation, + event_id: "$someUnknownEvent", + key: "🪿", + }, + }, + type: "m.reaction", + }), + ], + }, + }, + }, + }; + idbHttpBackend.when("GET", "/sync").respond(200, { + ...syncData, + rooms: syncRoomSection, + }); + idbHttpBackend.when("GET", `/rooms/${encodeURIComponent(roomId)}/event/%24someUnknownEvent`).respond( + 200, + mkEvent({ + room: roomId, + user: selfUserId, + content: { + "body": "Thread response", + "m.relates_to": { + rel_type: THREAD_RELATION_TYPE.name, + event_id: "$someThreadId", + }, + }, + type: "m.room.message", + }), + ); + + await startClient(idbClient); + assertEventsExpected(idbClient); + + idbHttpBackend.verifyNoOutstandingExpectation(); + // Force sync accumulator to persist, reset client, assert it doesn't re-fetch event on next start-up + await idbClient.store.save(true); + await idbClient.stopClient(); + await idbClient.store.destroy(); + await idbHttpBackend.stop(); + + idbTestClient = new TestClient(selfUserId, "DEVICE", selfAccessToken, undefined, { + store: new IndexedDBStore({ + indexedDB: global.indexedDB, + dbName: "test", + }), + }); + idbHttpBackend = idbTestClient.httpBackend; + idbClient = idbTestClient.client; + await idbClient.store.startup(); + + idbHttpBackend.when("GET", "/versions").respond(200, { versions: ["v1.4"] }); + idbHttpBackend.when("GET", "/pushrules/").respond(200, {}); + idbHttpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); + idbHttpBackend.when("GET", "/sync").respond(200, syncData); + + await startClient(idbClient); + assertEventsExpected(idbClient); + + idbHttpBackend.verifyNoOutstandingExpectation(); + await idbClient.stopClient(); + await idbHttpBackend.stop(); + }); }); diff --git a/spec/integ/matrix-client-unread-notifications.spec.ts b/spec/integ/matrix-client-unread-notifications.spec.ts index 8274d7afaba..a4d3c2b8991 100644 --- a/spec/integ/matrix-client-unread-notifications.spec.ts +++ b/spec/integ/matrix-client-unread-notifications.spec.ts @@ -89,7 +89,7 @@ describe("MatrixClient syncing", () => { const thread = mkThread({ room, client: client!, authorId: selfUserId, participantUserIds: [selfUserId] }); const threadReply = thread.events.at(-1)!; - room.addLiveEvents([thread.rootEvent]); + await room.addLiveEvents([thread.rootEvent]); // Initialize read receipt datastructure before testing the reaction room.addReceiptToStructure(thread.rootEvent.getId()!, ReceiptType.Read, selfUserId, { ts: 1 }, false); diff --git a/spec/integ/sliding-sync-sdk.spec.ts b/spec/integ/sliding-sync-sdk.spec.ts index 6fdaeb119bd..dfec79e1583 100644 --- a/spec/integ/sliding-sync-sdk.spec.ts +++ b/spec/integ/sliding-sync-sdk.spec.ts @@ -42,6 +42,7 @@ import { SyncApiOptions, SyncState } from "../../src/sync"; import { IStoredClientOpts } from "../../src/client"; import { logger } from "../../src/logger"; import { emitPromise } from "../test-utils/test-utils"; +import { defer } from "../../src/utils"; describe("SlidingSyncSdk", () => { let client: MatrixClient | undefined; @@ -301,67 +302,57 @@ describe("SlidingSyncSdk", () => { }, }; - it("can be created with required_state and timeline", () => { + it("can be created with required_state and timeline", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomA); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.name).toEqual(data[roomA].name); - expect(gotRoom.getMyMembership()).toEqual("join"); - assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.name).toEqual(data[roomA].name); + expect(gotRoom!.getMyMembership()).toEqual("join"); + assertTimelineEvents(gotRoom!.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline); }); - it("can be created with timeline only", () => { + it("can be created with timeline only", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomB); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.name).toEqual(data[roomB].name); - expect(gotRoom.getMyMembership()).toEqual("join"); - assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.name).toEqual(data[roomB].name); + expect(gotRoom!.getMyMembership()).toEqual("join"); + assertTimelineEvents(gotRoom!.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline); }); - it("can be created with a highlight_count", () => { + it("can be created with a highlight_count", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomC); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual( + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual( data[roomC].highlight_count, ); }); - it("can be created with a notification_count", () => { + it("can be created with a notification_count", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomD); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Total)).toEqual( + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.getUnreadNotificationCount(NotificationCountType.Total)).toEqual( data[roomD].notification_count, ); }); - it("can be created with an invited/joined_count", () => { + it("can be created with an invited/joined_count", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomG); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.getInvitedMemberCount()).toEqual(data[roomG].invited_count); - expect(gotRoom.getJoinedMemberCount()).toEqual(data[roomG].joined_count); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.getInvitedMemberCount()).toEqual(data[roomG].invited_count); + expect(gotRoom!.getJoinedMemberCount()).toEqual(data[roomG].joined_count); }); - it("can be created with live events", () => { - let seenLiveEvent = false; + it("can be created with live events", async () => { + const seenLiveEventDeferred = defer(); const listener = ( ev: MatrixEvent, room?: Room, @@ -371,43 +362,37 @@ describe("SlidingSyncSdk", () => { ) => { if (timelineData?.liveEvent) { assertTimelineEvents([ev], data[roomH].timeline.slice(-1)); - seenLiveEvent = true; + seenLiveEventDeferred.resolve(true); } }; client!.on(RoomEvent.Timeline, listener); mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomH, data[roomH]); + await emitPromise(client!, ClientEvent.Room); client!.off(RoomEvent.Timeline, listener); const gotRoom = client!.getRoom(roomH); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.name).toEqual(data[roomH].name); - expect(gotRoom.getMyMembership()).toEqual("join"); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.name).toEqual(data[roomH].name); + expect(gotRoom!.getMyMembership()).toEqual("join"); // check the entire timeline is correct - assertTimelineEvents(gotRoom.getLiveTimeline().getEvents(), data[roomH].timeline); - expect(seenLiveEvent).toBe(true); + assertTimelineEvents(gotRoom!.getLiveTimeline().getEvents(), data[roomH].timeline); + await expect(seenLiveEventDeferred.promise).resolves.toBeTruthy(); }); - it("can be created with invite_state", () => { + it("can be created with invite_state", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomE); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.getMyMembership()).toEqual("invite"); - expect(gotRoom.currentState.getJoinRule()).toEqual(JoinRule.Invite); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.getMyMembership()).toEqual("invite"); + expect(gotRoom!.currentState.getJoinRule()).toEqual(JoinRule.Invite); }); - it("uses the 'name' field to caluclate the room name", () => { + it("uses the 'name' field to caluclate the room name", async () => { mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]); + await emitPromise(client!, ClientEvent.Room); const gotRoom = client!.getRoom(roomF); - expect(gotRoom).toBeDefined(); - if (gotRoom == null) { - return; - } - expect(gotRoom.name).toEqual(data[roomF].name); + expect(gotRoom).toBeTruthy(); + expect(gotRoom!.name).toEqual(data[roomF].name); }); describe("updating", () => { @@ -419,33 +404,33 @@ describe("SlidingSyncSdk", () => { name: data[roomA].name, }); const gotRoom = client!.getRoom(roomA); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } const newTimeline = data[roomA].timeline; newTimeline.push(newEvent); - assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-3), newTimeline); + assertTimelineEvents(gotRoom!.getLiveTimeline().getEvents().slice(-3), newTimeline); }); it("can update with a new required_state event", async () => { let gotRoom = client!.getRoom(roomB); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } - expect(gotRoom.getJoinRule()).toEqual(JoinRule.Invite); // default + expect(gotRoom!.getJoinRule()).toEqual(JoinRule.Invite); // default mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, { required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")], timeline: [], name: data[roomB].name, }); gotRoom = client!.getRoom(roomB); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } - expect(gotRoom.getJoinRule()).toEqual(JoinRule.Restricted); + expect(gotRoom!.getJoinRule()).toEqual(JoinRule.Restricted); }); it("can update with a new highlight_count", async () => { @@ -456,11 +441,11 @@ describe("SlidingSyncSdk", () => { highlight_count: 1, }); const gotRoom = client!.getRoom(roomC); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } - expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual(1); + expect(gotRoom!.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual(1); }); it("can update with a new notification_count", async () => { @@ -471,11 +456,11 @@ describe("SlidingSyncSdk", () => { notification_count: 1, }); const gotRoom = client!.getRoom(roomD); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } - expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Total)).toEqual(1); + expect(gotRoom!.getUnreadNotificationCount(NotificationCountType.Total)).toEqual(1); }); it("can update with a new joined_count", () => { @@ -486,11 +471,11 @@ describe("SlidingSyncSdk", () => { joined_count: 1, }); const gotRoom = client!.getRoom(roomG); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } - expect(gotRoom.getJoinedMemberCount()).toEqual(1); + expect(gotRoom!.getJoinedMemberCount()).toEqual(1); }); // Regression test for a bug which caused the timeline entries to be out-of-order @@ -512,7 +497,7 @@ describe("SlidingSyncSdk", () => { initial: true, // e.g requested via room subscription }); const gotRoom = client!.getRoom(roomA); - expect(gotRoom).toBeDefined(); + expect(gotRoom).toBeTruthy(); if (gotRoom == null) { return; } @@ -530,7 +515,7 @@ describe("SlidingSyncSdk", () => { ); // we expect the timeline now to be oldTimeline (so the old events are in fact old) - assertTimelineEvents(gotRoom.getLiveTimeline().getEvents(), oldTimeline); + assertTimelineEvents(gotRoom!.getLiveTimeline().getEvents(), oldTimeline); }); }); }); @@ -626,9 +611,9 @@ describe("SlidingSyncSdk", () => { await httpBackend!.flush("/profile", 1, 1000); await emitPromise(client!, RoomMemberEvent.Name); const room = client!.getRoom(roomId)!; - expect(room).toBeDefined(); + expect(room).toBeTruthy(); const inviteeMember = room.getMember(invitee)!; - expect(inviteeMember).toBeDefined(); + expect(inviteeMember).toBeTruthy(); expect(inviteeMember.getMxcAvatarUrl()).toEqual(inviteeProfile.avatar_url); expect(inviteeMember.name).toEqual(inviteeProfile.displayname); }); @@ -723,7 +708,7 @@ describe("SlidingSyncSdk", () => { ], }); globalData = client!.getAccountData(globalType)!; - expect(globalData).toBeDefined(); + expect(globalData).toBeTruthy(); expect(globalData.getContent()).toEqual(globalContent); }); @@ -744,6 +729,7 @@ describe("SlidingSyncSdk", () => { foo: "bar", }; const roomType = "test"; + await emitPromise(client!, ClientEvent.Room); ext.onResponse({ rooms: { [roomId]: [ @@ -755,9 +741,9 @@ describe("SlidingSyncSdk", () => { }, }); const room = client!.getRoom(roomId)!; - expect(room).toBeDefined(); + expect(room).toBeTruthy(); const event = room.getAccountData(roomType)!; - expect(event).toBeDefined(); + expect(event).toBeTruthy(); expect(event.getContent()).toEqual(roomContent); }); @@ -943,8 +929,9 @@ describe("SlidingSyncSdk", () => { ], initial: true, }); + await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; - expect(room).toBeDefined(); + expect(room).toBeTruthy(); expect(room.getMember(selfUserId)?.typing).toEqual(false); ext.onResponse({ rooms: { @@ -984,7 +971,7 @@ describe("SlidingSyncSdk", () => { initial: true, }); const room = client!.getRoom(roomId)!; - expect(room).toBeDefined(); + expect(room).toBeTruthy(); expect(room.getMember(selfUserId)?.typing).toEqual(false); ext.onResponse({ rooms: { @@ -1077,12 +1064,13 @@ describe("SlidingSyncSdk", () => { ], initial: true, }); + await emitPromise(client!, ClientEvent.Room); const room = client!.getRoom(roomId)!; - expect(room).toBeDefined(); + expect(room).toBeTruthy(); expect(room.getReadReceiptForUserId(alice, true)).toBeNull(); ext.onResponse(generateReceiptResponse(alice, roomId, lastEvent.event_id, "m.read", 1234567)); const receipt = room.getReadReceiptForUserId(alice); - expect(receipt).toBeDefined(); + expect(receipt).toBeTruthy(); expect(receipt?.eventId).toEqual(lastEvent.event_id); expect(receipt?.data.ts).toEqual(1234567); expect(receipt?.data.thread_id).toBeFalsy(); diff --git a/spec/unit/models/thread.spec.ts b/spec/unit/models/thread.spec.ts index 6aa1d5c862e..d8dd88809b4 100644 --- a/spec/unit/models/thread.spec.ts +++ b/spec/unit/models/thread.spec.ts @@ -697,7 +697,7 @@ async function createThread(client: MatrixClient, user: string, roomId: string): // Ensure the root is in the room timeline root.setThreadId(root.getId()); - room.addLiveEvents([root]); + await room.addLiveEvents([root]); // Create the thread and wait for it to be initialised const thread = room.createThread(root.getId()!, root, [], false); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index e168a5bf628..2ed44dffd7b 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -172,9 +172,9 @@ describe("Room", function () { * @param timestamp - Timestamp of the message * @return The message event */ - const mkMessageInRoom = (room: Room, timestamp: number) => { + const mkMessageInRoom = async (room: Room, timestamp: number) => { const message = mkMessage({ ts: timestamp }); - room.addLiveEvents([message]); + await room.addLiveEvents([message]); return message; }; @@ -319,23 +319,25 @@ describe("Room", function () { }), ]; - it("Make sure legacy overload passing options directly as parameters still works", () => { - expect(() => room.addLiveEvents(events, DuplicateStrategy.Replace, false)).not.toThrow(); - expect(() => room.addLiveEvents(events, DuplicateStrategy.Ignore, true)).not.toThrow(); - // @ts-ignore - expect(() => room.addLiveEvents(events, "shouldfailbecauseinvalidduplicatestrategy", false)).toThrow(); + it("Make sure legacy overload passing options directly as parameters still works", async () => { + await expect(room.addLiveEvents(events, DuplicateStrategy.Replace, false)).resolves.not.toThrow(); + await expect(room.addLiveEvents(events, DuplicateStrategy.Ignore, true)).resolves.not.toThrow(); + await expect( + // @ts-ignore + room.addLiveEvents(events, "shouldfailbecauseinvalidduplicatestrategy", false), + ).rejects.toThrow(); }); - it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", function () { - expect(function () { + it("should throw if duplicateStrategy isn't 'replace' or 'ignore'", async function () { + return expect( // @ts-ignore room.addLiveEvents(events, { duplicateStrategy: "foo", - }); - }).toThrow(); + }), + ).rejects.toThrow(); }); - it("should replace a timeline event if dupe strategy is 'replace'", function () { + it("should replace a timeline event if dupe strategy is 'replace'", async function () { // make a duplicate const dupe = utils.mkMessage({ room: roomId, @@ -344,15 +346,15 @@ describe("Room", function () { event: true, }); dupe.event.event_id = events[0].getId(); - room.addLiveEvents(events); + await room.addLiveEvents(events); expect(room.timeline[0]).toEqual(events[0]); - room.addLiveEvents([dupe], { + await room.addLiveEvents([dupe], { duplicateStrategy: DuplicateStrategy.Replace, }); expect(room.timeline[0]).toEqual(dupe); }); - it("should ignore a given dupe event if dupe strategy is 'ignore'", function () { + it("should ignore a given dupe event if dupe strategy is 'ignore'", async function () { // make a duplicate const dupe = utils.mkMessage({ room: roomId, @@ -361,16 +363,16 @@ describe("Room", function () { event: true, }); dupe.event.event_id = events[0].getId(); - room.addLiveEvents(events); + await room.addLiveEvents(events); expect(room.timeline[0]).toEqual(events[0]); // @ts-ignore - room.addLiveEvents([dupe], { + await room.addLiveEvents([dupe], { duplicateStrategy: "ignore", }); expect(room.timeline[0]).toEqual(events[0]); }); - it("should emit 'Room.timeline' events", function () { + it("should emit 'Room.timeline' events", async function () { let callCount = 0; room.on(RoomEvent.Timeline, function (event, emitRoom, toStart) { callCount += 1; @@ -379,11 +381,11 @@ describe("Room", function () { expect(emitRoom).toEqual(room); expect(toStart).toBeFalsy(); }); - room.addLiveEvents(events); + await room.addLiveEvents(events); expect(callCount).toEqual(2); }); - it("should call setStateEvents on the right RoomState with the right forwardLooking value for new events", function () { + it("should call setStateEvents on the right RoomState with the right forwardLooking value for new events", async function () { const events: MatrixEvent[] = [ utils.mkMembership({ room: roomId, @@ -402,7 +404,7 @@ describe("Room", function () { }, }), ]; - room.addLiveEvents(events); + await room.addLiveEvents(events); expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[0]], { timelineWasEmpty: false }); expect(room.currentState.setStateEvents).toHaveBeenCalledWith([events[1]], { timelineWasEmpty: false }); expect(events[0].forwardLooking).toBe(true); @@ -410,7 +412,7 @@ describe("Room", function () { expect(room.oldState.setStateEvents).not.toHaveBeenCalled(); }); - it("should synthesize read receipts for the senders of events", function () { + it("should synthesize read receipts for the senders of events", async function () { const sentinel = { userId: userA, membership: "join", @@ -422,11 +424,11 @@ describe("Room", function () { } return null; }); - room.addLiveEvents(events); + await room.addLiveEvents(events); expect(room.getEventReadUpTo(userA)).toEqual(events[1].getId()); }); - it("should emit Room.localEchoUpdated when a local echo is updated", function () { + it("should emit Room.localEchoUpdated when a local echo is updated", async function () { const localEvent = utils.mkMessage({ room: roomId, user: userA, @@ -457,7 +459,7 @@ describe("Room", function () { expect(stub.mock.calls[0][3]).toBeUndefined(); // then the remoteEvent - room.addLiveEvents([remoteEvent]); + await room.addLiveEvents([remoteEvent]); expect(room.timeline.length).toEqual(1); expect(stub).toHaveBeenCalledTimes(2); @@ -469,7 +471,7 @@ describe("Room", function () { expect(stub.mock.calls[1][3]).toBe(EventStatus.SENDING); }); - it("should be able to update local echo without a txn ID (/send then /sync)", function () { + it("should be able to update local echo without a txn ID (/send then /sync)", async function () { const eventJson = utils.mkMessage({ room: roomId, user: userA, @@ -495,14 +497,14 @@ describe("Room", function () { // then /sync returns the remoteEvent, it should de-dupe based on the event ID. const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson)); expect(remoteEvent.getTxnId()).toBeUndefined(); - room.addLiveEvents([remoteEvent]); + await room.addLiveEvents([remoteEvent]); // the duplicate strategy code should ensure we don't add a 2nd event to the live timeline expect(room.timeline.length).toEqual(1); // but without the event ID matching we will still have the local event in pending events expect(room.getEventForTxnId(txnId)).toBeUndefined(); }); - it("should be able to update local echo without a txn ID (/sync then /send)", function () { + it("should be able to update local echo without a txn ID (/sync then /send)", async function () { const eventJson = utils.mkMessage({ room: roomId, user: userA, @@ -525,7 +527,7 @@ describe("Room", function () { const realEventId = "$real-event-id"; const remoteEvent = new MatrixEvent(Object.assign({ event_id: realEventId }, eventJson)); expect(remoteEvent.getUnsigned().transaction_id).toBeUndefined(); - room.addLiveEvents([remoteEvent]); + await room.addLiveEvents([remoteEvent]); expect(room.timeline.length).toEqual(2); // impossible to de-dupe as no txn ID or matching event ID // then the /send request returns the real event ID. @@ -538,7 +540,7 @@ describe("Room", function () { expect(room.getEventForTxnId(txnId)).toBeUndefined(); }); - it("should correctly handle remote echoes from other devices", () => { + it("should correctly handle remote echoes from other devices", async () => { const remoteEvent = utils.mkMessage({ room: roomId, user: userA, @@ -547,7 +549,7 @@ describe("Room", function () { remoteEvent.event.unsigned = { transaction_id: "TXN_ID" }; // add the remoteEvent - room.addLiveEvents([remoteEvent]); + await room.addLiveEvents([remoteEvent]); expect(room.timeline.length).toEqual(1); }); }); @@ -612,7 +614,7 @@ describe("Room", function () { }); describe("event metadata handling", function () { - it("should set event.sender for new and old events", function () { + it("should set event.sender for new and old events", async function () { const sentinel = { userId: userA, membership: "join", @@ -650,13 +652,13 @@ describe("Room", function () { event: true, content: { name: "Old Room Name" }, }); - room.addLiveEvents([newEv]); + await room.addLiveEvents([newEv]); expect(newEv.sender).toEqual(sentinel); room.addEventsToTimeline([oldEv], true, room.getLiveTimeline()); expect(oldEv.sender).toEqual(oldSentinel); }); - it("should set event.target for new and old m.room.member events", function () { + it("should set event.target for new and old m.room.member events", async function () { const sentinel = { userId: userA, membership: "join", @@ -694,7 +696,7 @@ describe("Room", function () { skey: userA, event: true, }); - room.addLiveEvents([newEv]); + await room.addLiveEvents([newEv]); expect(newEv.target).toEqual(sentinel); room.addEventsToTimeline([oldEv], true, room.getLiveTimeline()); expect(oldEv.target).toEqual(oldSentinel); @@ -763,12 +765,12 @@ describe("Room", function () { ]; }); - it("should copy state from previous timeline", function () { - room.addLiveEvents([events[0], events[1]]); + it("should copy state from previous timeline", async function () { + await room.addLiveEvents([events[0], events[1]]); expect(room.getLiveTimeline().getEvents().length).toEqual(2); room.resetLiveTimeline("sometoken", "someothertoken"); - room.addLiveEvents([events[2]]); + await room.addLiveEvents([events[2]]); const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS); const newState = room.getLiveTimeline().getState(EventTimeline.FORWARDS); expect(room.getLiveTimeline().getEvents().length).toEqual(1); @@ -776,8 +778,8 @@ describe("Room", function () { expect(newState?.getStateEvents(EventType.RoomName, "")).toEqual(events[2]); }); - it("should reset the legacy timeline fields", function () { - room.addLiveEvents([events[0], events[1]]); + it("should reset the legacy timeline fields", async function () { + await room.addLiveEvents([events[0], events[1]]); expect(room.timeline.length).toEqual(2); const oldStateBeforeRunningReset = room.oldState; @@ -798,7 +800,7 @@ describe("Room", function () { room.resetLiveTimeline("sometoken", "someothertoken"); - room.addLiveEvents([events[2]]); + await room.addLiveEvents([events[2]]); const newLiveTimeline = room.getLiveTimeline(); expect(room.timeline).toEqual(newLiveTimeline.getEvents()); expect(room.oldState).toEqual(newLiveTimeline.getState(EventTimeline.BACKWARDS)); @@ -824,8 +826,8 @@ describe("Room", function () { expect(callCount).toEqual(1); }); - it("should " + (timelineSupport ? "remember" : "forget") + " old timelines", function () { - room.addLiveEvents([events[0]]); + it("should " + (timelineSupport ? "remember" : "forget") + " old timelines", async function () { + await room.addLiveEvents([events[0]]); expect(room.timeline.length).toEqual(1); const firstLiveTimeline = room.getLiveTimeline(); room.resetLiveTimeline("sometoken", "someothertoken"); @@ -868,8 +870,8 @@ describe("Room", function () { }), ]; - it("should handle events in the same timeline", function () { - room.addLiveEvents(events); + it("should handle events in the same timeline", async function () { + await room.addLiveEvents(events); expect( room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!), @@ -882,13 +884,13 @@ describe("Room", function () { ).toEqual(0); }); - it("should handle events in adjacent timelines", function () { + it("should handle events in adjacent timelines", async function () { const oldTimeline = room.addTimeline(); oldTimeline.setNeighbouringTimeline(room.getLiveTimeline(), Direction.Forward); room.getLiveTimeline().setNeighbouringTimeline(oldTimeline, Direction.Backward); room.addEventsToTimeline([events[0]], false, oldTimeline); - room.addLiveEvents([events[1]]); + await room.addLiveEvents([events[1]]); expect( room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!), @@ -898,11 +900,11 @@ describe("Room", function () { ).toBeGreaterThan(0); }); - it("should return null for events in non-adjacent timelines", function () { + it("should return null for events in non-adjacent timelines", async function () { const oldTimeline = room.addTimeline(); room.addEventsToTimeline([events[0]], false, oldTimeline); - room.addLiveEvents([events[1]]); + await room.addLiveEvents([events[1]]); expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, events[1].getId()!)).toBe( null, @@ -912,8 +914,8 @@ describe("Room", function () { ); }); - it("should return null for unknown events", function () { - room.addLiveEvents(events); + it("should return null for unknown events", async function () { + await room.addLiveEvents(events); expect(room.getUnfilteredTimelineSet().compareEventOrdering(events[0].getId()!, "xxx")).toBe(null); expect(room.getUnfilteredTimelineSet().compareEventOrdering("xxx", events[0].getId()!)).toBe(null); @@ -990,8 +992,8 @@ describe("Room", function () { }); describe("recalculate", function () { - const setJoinRule = function (rule: JoinRule) { - room.addLiveEvents([ + const setJoinRule = async function (rule: JoinRule) { + await room.addLiveEvents([ utils.mkEvent({ type: EventType.RoomJoinRules, room: roomId, @@ -1003,8 +1005,8 @@ describe("Room", function () { }), ]); }; - const setAltAliases = function (aliases: string[]) { - room.addLiveEvents([ + const setAltAliases = async function (aliases: string[]) { + await room.addLiveEvents([ utils.mkEvent({ type: EventType.RoomCanonicalAlias, room: roomId, @@ -1016,8 +1018,8 @@ describe("Room", function () { }), ]); }; - const setAlias = function (alias: string) { - room.addLiveEvents([ + const setAlias = async function (alias: string) { + await room.addLiveEvents([ utils.mkEvent({ type: EventType.RoomCanonicalAlias, room: roomId, @@ -1027,8 +1029,8 @@ describe("Room", function () { }), ]); }; - const setRoomName = function (name: string) { - room.addLiveEvents([ + const setRoomName = async function (name: string) { + await room.addLiveEvents([ utils.mkEvent({ type: EventType.RoomName, room: roomId, @@ -1040,14 +1042,14 @@ describe("Room", function () { }), ]); }; - const addMember = function (userId: string, state = "join", opts: any = {}) { + const addMember = async function (userId: string, state = "join", opts: any = {}) { opts.room = roomId; opts.mship = state; opts.user = opts.user || userId; opts.skey = userId; opts.event = true; const event = utils.mkMembership(opts); - room.addLiveEvents([event]); + await room.addLiveEvents([event]); return event; }; @@ -1059,10 +1061,10 @@ describe("Room", function () { describe("Room.recalculate => Stripped State Events", function () { it( "should set stripped state events as actual state events if the " + "room is an invite room", - function () { + async function () { const roomName = "flibble"; - const event = addMember(userA, "invite"); + const event = await addMember(userA, "invite"); event.event.unsigned = {}; event.event.unsigned.invite_room_state = [ { @@ -1080,8 +1082,8 @@ describe("Room", function () { }, ); - it("should not clobber state events if it isn't an invite room", function () { - const event = addMember(userA, "join"); + it("should not clobber state events if it isn't an invite room", async function () { + const event = await addMember(userA, "join"); const roomName = "flibble"; setRoomName(roomName); const roomNameToIgnore = "ignoreme"; @@ -1537,7 +1539,7 @@ describe("Room", function () { ]); }); - it("should prioritise the most recent event", function () { + it("should prioritise the most recent event", async function () { const events: MatrixEvent[] = [ utils.mkMessage({ room: roomId, @@ -1559,7 +1561,7 @@ describe("Room", function () { }), ]; - room.addLiveEvents(events); + await room.addLiveEvents(events); const ts = 13787898424; // check it initialises correctly @@ -1575,7 +1577,7 @@ describe("Room", function () { expect(room.getEventReadUpTo(userB)).toEqual(events[2].getId()); }); - it("should prioritise the most recent event even if it is synthetic", () => { + it("should prioritise the most recent event even if it is synthetic", async () => { const events: MatrixEvent[] = [ utils.mkMessage({ room: roomId, @@ -1597,7 +1599,7 @@ describe("Room", function () { }), ]; - room.addLiveEvents(events); + await room.addLiveEvents(events); const ts = 13787898424; // check it initialises correctly @@ -1673,66 +1675,72 @@ describe("Room", function () { }); describe("addPendingEvent", function () { - it("should add pending events to the pendingEventList if " + "pendingEventOrdering == 'detached'", function () { - const client = new TestClient("@alice:example.com", "alicedevice").client; - client.supportsThreads = () => true; - const room = new Room(roomId, client, userA, { - pendingEventOrdering: PendingEventOrdering.Detached, - }); - const eventA = utils.mkMessage({ - room: roomId, - user: userA, - msg: "remote 1", - event: true, - }); - const eventB = utils.mkMessage({ - room: roomId, - user: userA, - msg: "local 1", - event: true, - }); - eventB.status = EventStatus.SENDING; - const eventC = utils.mkMessage({ - room: roomId, - user: userA, - msg: "remote 2", - event: true, - }); - room.addLiveEvents([eventA]); - room.addPendingEvent(eventB, "TXN1"); - room.addLiveEvents([eventC]); - expect(room.timeline).toEqual([eventA, eventC]); - expect(room.getPendingEvents()).toEqual([eventB]); - }); + it( + "should add pending events to the pendingEventList if " + "pendingEventOrdering == 'detached'", + async function () { + const client = new TestClient("@alice:example.com", "alicedevice").client; + client.supportsThreads = () => true; + const room = new Room(roomId, client, userA, { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + const eventA = utils.mkMessage({ + room: roomId, + user: userA, + msg: "remote 1", + event: true, + }); + const eventB = utils.mkMessage({ + room: roomId, + user: userA, + msg: "local 1", + event: true, + }); + eventB.status = EventStatus.SENDING; + const eventC = utils.mkMessage({ + room: roomId, + user: userA, + msg: "remote 2", + event: true, + }); + await room.addLiveEvents([eventA]); + room.addPendingEvent(eventB, "TXN1"); + await room.addLiveEvents([eventC]); + expect(room.timeline).toEqual([eventA, eventC]); + expect(room.getPendingEvents()).toEqual([eventB]); + }, + ); - it("should add pending events to the timeline if " + "pendingEventOrdering == 'chronological'", function () { - const room = new Room(roomId, new TestClient(userA).client, userA, { - pendingEventOrdering: PendingEventOrdering.Chronological, - }); - const eventA = utils.mkMessage({ - room: roomId, - user: userA, - msg: "remote 1", - event: true, - }); - const eventB = utils.mkMessage({ - room: roomId, - user: userA, - msg: "local 1", - event: true, - }); - eventB.status = EventStatus.SENDING; - const eventC = utils.mkMessage({ - room: roomId, - user: userA, - msg: "remote 2", - event: true, - }); - room.addLiveEvents([eventA]); - room.addPendingEvent(eventB, "TXN1"); - room.addLiveEvents([eventC]); - expect(room.timeline).toEqual([eventA, eventB, eventC]); - }); + it( + "should add pending events to the timeline if " + "pendingEventOrdering == 'chronological'", + async function () { + const room = new Room(roomId, new TestClient(userA).client, userA, { + pendingEventOrdering: PendingEventOrdering.Chronological, + }); + const eventA = utils.mkMessage({ + room: roomId, + user: userA, + msg: "remote 1", + event: true, + }); + const eventB = utils.mkMessage({ + room: roomId, + user: userA, + msg: "local 1", + event: true, + }); + eventB.status = EventStatus.SENDING; + const eventC = utils.mkMessage({ + room: roomId, + user: userA, + msg: "remote 2", + event: true, + }); + await room.addLiveEvents([eventA]); + room.addPendingEvent(eventB, "TXN1"); + await room.addLiveEvents([eventC]); + expect(room.timeline).toEqual([eventA, eventB, eventC]); + }, + ); it("should apply redactions eagerly in the pending event list", () => { const client = new TestClient("@alice:example.com", "alicedevice").client; @@ -2004,9 +2012,9 @@ describe("Room", function () { }); expect(room.guessDMUserId()).toEqual(userB); }); - it("should return first member that isn't self", function () { + it("should return first member that isn't self", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userB, mship: "join", @@ -2070,9 +2078,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("Empty room"); }); - it("should return a display name if one other member is in the room", function () { + it("should return a display name if one other member is in the room", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2091,9 +2099,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return a display name if one other member is banned", function () { + it("should return a display name if one other member is banned", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2112,9 +2120,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("Empty room (was User B)"); }); - it("should return a display name if one other member is invited", function () { + it("should return a display name if one other member is invited", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2133,9 +2141,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return 'Empty room (was User B)' if User B left the room", function () { + it("should return 'Empty room (was User B)' if User B left the room", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2154,9 +2162,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("Empty room (was User B)"); }); - it("should return 'User B and User C' if in a room with two other users", function () { + it("should return 'User B and User C' if in a room with two other users", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2182,9 +2190,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B and User C"); }); - it("should return 'User B and 2 others' if in a room with three other users", function () { + it("should return 'User B and 2 others' if in a room with three other users", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2219,9 +2227,9 @@ describe("Room", function () { }); describe("io.element.functional_users", function () { - it("should return a display name (default behaviour) if no one is marked as a functional member", function () { + it("should return a display name (default behaviour) if no one is marked as a functional member", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2249,9 +2257,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return a display name (default behaviour) if service members is a number (invalid)", function () { + it("should return a display name (default behaviour) if service members is a number (invalid)", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2279,9 +2287,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return a display name (default behaviour) if service members is a string (invalid)", function () { + it("should return a display name (default behaviour) if service members is a string (invalid)", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2309,9 +2317,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return 'Empty room' if the only other member is a functional member", function () { + it("should return 'Empty room' if the only other member is a functional member", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2339,9 +2347,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("Empty room"); }); - it("should return 'User B' if User B is the only other member who isn't a functional member", function () { + it("should return 'User B' if User B is the only other member who isn't a functional member", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2377,9 +2385,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("User B"); }); - it("should return 'Empty room' if all other members are functional members", function () { + it("should return 'Empty room' if all other members are functional members", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2415,9 +2423,9 @@ describe("Room", function () { expect(room.getDefaultRoomName(userA)).toEqual("Empty room"); }); - it("should not break if an unjoined user is marked as a service user", function () { + it("should not break if an unjoined user is marked as a service user", async function () { const room = new Room(roomId, new TestClient(userA).client, userA); - room.addLiveEvents([ + await room.addLiveEvents([ utils.mkMembership({ user: userA, mship: "join", @@ -2548,7 +2556,7 @@ describe("Room", function () { }); let prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents([randomMessage, threadRoot, threadResponse]); + await room.addLiveEvents([randomMessage, threadRoot, threadResponse]); const thread: Thread = await prom; await emitPromise(room, ThreadEvent.Update); @@ -2575,7 +2583,7 @@ describe("Room", function () { }); prom = emitPromise(room, ThreadEvent.Update); - room.addLiveEvents([threadResponseEdit]); + await room.addLiveEvents([threadResponseEdit]); await prom; expect(thread.replyToEvent!.getContent().body).toBe(threadResponseEdit.getContent()["m.new_content"].body); }); @@ -2606,7 +2614,7 @@ describe("Room", function () { }); let prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents([threadRoot, threadResponse1, threadResponse2]); + await room.addLiveEvents([threadRoot, threadResponse1, threadResponse2]); const thread = await prom; await emitPromise(room, ThreadEvent.Update); @@ -2641,7 +2649,7 @@ describe("Room", function () { prom = emitPromise(thread, ThreadEvent.Update); const threadResponse1Redaction = mkRedaction(threadResponse1); - room.addLiveEvents([threadResponse1Redaction]); + await room.addLiveEvents([threadResponse1Redaction]); await prom; expect(thread).toHaveLength(1); expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId()); @@ -2674,7 +2682,7 @@ describe("Room", function () { }); const prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]); + await room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]); const thread = await prom; await emitPromise(room, ThreadEvent.Update); @@ -2682,7 +2690,7 @@ describe("Room", function () { expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId()); const threadResponse2ReactionRedaction = mkRedaction(threadResponse2Reaction); - room.addLiveEvents([threadResponse2ReactionRedaction]); + await room.addLiveEvents([threadResponse2ReactionRedaction]); expect(thread).toHaveLength(2); expect(thread.replyToEvent.getId()).toBe(threadResponse2.getId()); }); @@ -2714,7 +2722,7 @@ describe("Room", function () { }); let prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]); + await room.addLiveEvents([threadRoot, threadResponse1, threadResponse2, threadResponse2Reaction]); const thread = await prom; await emitPromise(room, ThreadEvent.Update); @@ -2723,7 +2731,7 @@ describe("Room", function () { prom = emitPromise(room, ThreadEvent.Update); const threadRootRedaction = mkRedaction(threadRoot); - room.addLiveEvents([threadRootRedaction]); + await room.addLiveEvents([threadRootRedaction]); await prom; expect(thread).toHaveLength(2); }); @@ -2776,12 +2784,12 @@ describe("Room", function () { }); let prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents([threadRoot, threadResponse1]); + await room.addLiveEvents([threadRoot, threadResponse1]); const thread: Thread = await prom; await emitPromise(room, ThreadEvent.Update); expect(thread.initialEventsFetched).toBeTruthy(); - room.addLiveEvents([threadResponse2]); + await room.addLiveEvents([threadResponse2]); expect(thread).toHaveLength(2); expect(thread.replyToEvent!.getId()).toBe(threadResponse2.getId()); @@ -2802,7 +2810,7 @@ describe("Room", function () { prom = emitPromise(room, ThreadEvent.Update); const threadResponse2Redaction = mkRedaction(threadResponse2); - room.addLiveEvents([threadResponse2Redaction]); + await room.addLiveEvents([threadResponse2Redaction]); await prom; await emitPromise(room, ThreadEvent.Update); expect(thread).toHaveLength(1); @@ -2826,7 +2834,7 @@ describe("Room", function () { prom = emitPromise(room, ThreadEvent.Delete); const prom2 = emitPromise(room, RoomEvent.Timeline); const threadResponse1Redaction = mkRedaction(threadResponse1); - room.addLiveEvents([threadResponse1Redaction]); + await room.addLiveEvents([threadResponse1Redaction]); await prom; await prom2; expect(thread).toHaveLength(0); @@ -2946,7 +2954,7 @@ describe("Room", function () { const events = [threadRoot, rootReaction, threadResponse, threadReaction]; const prom = emitPromise(room, ThreadEvent.New); - room.addLiveEvents(events); + await room.addLiveEvents(events); const thread = await prom; expect(thread).toBe(threadRoot.getThread()); expect(thread.rootEvent).toBe(threadRoot); @@ -3452,21 +3460,21 @@ describe("Room", function () { expect(room.findPredecessor()).toBeNull(); }); - it("Returns null if the create event has no predecessor", () => { + it("Returns null if the create event has no predecessor", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([roomCreateEvent("roomid", null)]); + await room.addLiveEvents([roomCreateEvent("roomid", null)]); expect(room.findPredecessor()).toBeNull(); }); - it("Returns the predecessor ID if one is provided via create event", () => { + it("Returns the predecessor ID if one is provided via create event", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([roomCreateEvent("roomid", "replacedroomid")]); + await room.addLiveEvents([roomCreateEvent("roomid", "replacedroomid")]); expect(room.findPredecessor()).toEqual({ roomId: "replacedroomid", eventId: "id_of_last_known_event" }); }); - it("Prefers the m.predecessor event if one exists", () => { + it("Prefers the m.predecessor event if one exists", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([ + await room.addLiveEvents([ roomCreateEvent("roomid", "replacedroomid"), predecessorEvent("roomid", "otherreplacedroomid"), ]); @@ -3478,9 +3486,9 @@ describe("Room", function () { }); }); - it("uses the m.predecessor event ID if provided", () => { + it("uses the m.predecessor event ID if provided", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([ + await room.addLiveEvents([ roomCreateEvent("roomid", "replacedroomid"), predecessorEvent("roomid", "otherreplacedroomid", "lstevtid", ["one.example.com", "two.example.com"]), ]); @@ -3492,9 +3500,9 @@ describe("Room", function () { }); }); - it("Ignores the m.predecessor event if we don't ask to use it", () => { + it("Ignores the m.predecessor event if we don't ask to use it", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([ + await room.addLiveEvents([ roomCreateEvent("roomid", "replacedroomid"), predecessorEvent("roomid", "otherreplacedroomid"), ]); @@ -3503,9 +3511,9 @@ describe("Room", function () { expect(room.findPredecessor()).toEqual({ roomId: "replacedroomid", eventId: "id_of_last_known_event" }); }); - it("Ignores the m.predecessor event and returns null if we don't ask to use it", () => { + it("Ignores the m.predecessor event and returns null if we don't ask to use it", async () => { const room = new Room("roomid", client!, "@u:example.com"); - room.addLiveEvents([ + await room.addLiveEvents([ roomCreateEvent("roomid", null), // Create event has no predecessor predecessorEvent("roomid", "otherreplacedroomid", "lastevtid"), ]); @@ -3520,8 +3528,8 @@ describe("Room", function () { expect(room.getLastLiveEvent()).toBeUndefined(); }); - it("when there is only an event in the main timeline and there are no threads, it should return the last event from the main timeline", () => { - const lastEventInMainTimeline = mkMessageInRoom(room, 23); + it("when there is only an event in the main timeline and there are no threads, it should return the last event from the main timeline", async () => { + const lastEventInMainTimeline = await mkMessageInRoom(room, 23); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); @@ -3536,29 +3544,29 @@ describe("Room", function () { }); describe("when there are events in both, the main timeline and threads", () => { - it("and the last event is in a thread, it should return the last event from the thread", () => { - mkMessageInRoom(room, 23); + it("and the last event is in a thread, it should return the last event from the thread", async () => { + await mkMessageInRoom(room, 23); const { thread } = mkThread({ room, length: 0 }); const lastEventInThread = mkMessageInThread(thread, 42); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); - it("and the last event is in the main timeline, it should return the last event from the main timeline", () => { - const lastEventInMainTimeline = mkMessageInRoom(room, 42); + it("and the last event is in the main timeline, it should return the last event from the main timeline", async () => { + const lastEventInMainTimeline = await mkMessageInRoom(room, 42); const { thread } = mkThread({ room, length: 0 }); mkMessageInThread(thread, 23); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); - it("and both events have the same timestamp, it should return the last event from the thread", () => { - mkMessageInRoom(room, 23); + it("and both events have the same timestamp, it should return the last event from the thread", async () => { + await mkMessageInRoom(room, 23); const { thread } = mkThread({ room, length: 0 }); const lastEventInThread = mkMessageInThread(thread, 23); expect(room.getLastLiveEvent()).toBe(lastEventInThread); }); - it("and there is a thread without any messages, it should return the last event from the main timeline", () => { - const lastEventInMainTimeline = mkMessageInRoom(room, 23); + it("and there is a thread without any messages, it should return the last event from the main timeline", async () => { + const lastEventInMainTimeline = await mkMessageInRoom(room, 23); mkThread({ room, length: 0 }); expect(room.getLastLiveEvent()).toBe(lastEventInMainTimeline); }); diff --git a/src/@types/event.ts b/src/@types/event.ts index 17af8df0272..a0eca5cc011 100644 --- a/src/@types/event.ts +++ b/src/@types/event.ts @@ -235,6 +235,13 @@ export const LOCAL_NOTIFICATION_SETTINGS_PREFIX = new UnstableValue( "org.matrix.msc3890.local_notification_settings", ); +/** + * https://github.com/matrix-org/matrix-doc/pull/4023 + * + * @experimental + */ +export const UNSIGNED_THREAD_ID_FIELD = new UnstableValue("thread_id", "org.matrix.msc4023.thread_id"); + export interface IEncryptedFile { url: string; mimetype?: string; diff --git a/src/client.ts b/src/client.ts index f35fb94c201..4937494b435 100644 --- a/src/client.ts +++ b/src/client.ts @@ -5573,11 +5573,13 @@ export class MatrixClient extends TypedEventEmitter room.relations.aggregateChildEvent(event)); room.oldState.paginationToken = res.end ?? null; if (res.chunk.length === 0) { @@ -5686,11 +5688,12 @@ export class MatrixClient extends TypedEventEmitter timelineSet.relations.aggregateChildEvent(event)); // There is no guarantee that the event ended up in "timeline" (we might have switched to a neighbouring // timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up @@ -6230,7 +6233,7 @@ export class MatrixClient extends TypedEventEmitter it.getServerAggregatedRelation(THREAD_RELATION_TYPE.name)), false, ); + unknownRelations.forEach((event) => room.relations.aggregateChildEvent(event)); const atEnd = res.end === undefined || res.end === res.start; diff --git a/src/models/event.ts b/src/models/event.ts index 5c55449a3f5..feb21fbba74 100644 --- a/src/models/event.ts +++ b/src/models/event.ts @@ -24,7 +24,13 @@ import { ExtensibleEvent, ExtensibleEvents, Optional } from "matrix-events-sdk"; import type { IEventDecryptionResult } from "../@types/crypto"; import { logger } from "../logger"; import { VerificationRequest } from "../crypto/verification/request/VerificationRequest"; -import { EVENT_VISIBILITY_CHANGE_TYPE, EventType, MsgType, RelationType } from "../@types/event"; +import { + EVENT_VISIBILITY_CHANGE_TYPE, + EventType, + MsgType, + RelationType, + UNSIGNED_THREAD_ID_FIELD, +} from "../@types/event"; import { Crypto } from "../crypto"; import { deepSortedObjectEntries, internaliseString } from "../utils"; import { RoomMember } from "./room-member"; @@ -63,6 +69,7 @@ export interface IUnsigned { "transaction_id"?: string; "invite_room_state"?: StrippedState[]; "m.relations"?: Record; // No common pattern for aggregated relations + [UNSIGNED_THREAD_ID_FIELD.name]?: string; } export interface IThreadBundledRelationship { diff --git a/src/models/room.ts b/src/models/room.ts index 04967091e80..bd6913f45bb 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -39,6 +39,7 @@ import { UNSTABLE_ELEMENT_FUNCTIONAL_USERS, EVENT_VISIBILITY_CHANGE_TYPE, RelationType, + UNSIGNED_THREAD_ID_FIELD, } from "../@types/event"; import { IRoomVersionsCapability, MatrixClient, PendingEventOrdering, RoomVersionStability } from "../client"; import { GuestAccess, HistoryVisibility, JoinRule, ResizeMethod } from "../@types/partials"; @@ -2132,6 +2133,13 @@ export class Room extends ReadReceipt { return this.eventShouldLiveIn(parentEvent, events, roots); } + if (!event.isRelation()) { + return { + shouldLiveInRoom: true, + shouldLiveInThread: false, + }; + } + // Edge case where we know the event is a relation but don't have the parentEvent if (roots?.has(event.relationEventId!)) { return { @@ -2141,9 +2149,20 @@ export class Room extends ReadReceipt { }; } - // We've exhausted all scenarios, can safely assume that this event should live in the room timeline only + const unsigned = event.getUnsigned(); + if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === "string") { + return { + shouldLiveInRoom: false, + shouldLiveInThread: true, + threadId: unsigned[UNSIGNED_THREAD_ID_FIELD.name], + }; + } + + // We've exhausted all scenarios, + // we cannot assume that it lives in the main timeline as this may be a relation for an unknown thread + // adding the event in the wrong timeline causes stuck notifications and can break ability to send read receipts return { - shouldLiveInRoom: true, + shouldLiveInRoom: false, shouldLiveInThread: false, }; } @@ -2156,14 +2175,13 @@ export class Room extends ReadReceipt { } private addThreadedEvents(threadId: string, events: MatrixEvent[], toStartOfTimeline = false): void { - let thread = this.getThread(threadId); - - if (!thread) { + const thread = this.getThread(threadId); + if (thread) { + thread.addEvents(events, toStartOfTimeline); + } else { const rootEvent = this.findEventById(threadId) ?? events.find((e) => e.getId() === threadId); - thread = this.createThread(threadId, rootEvent, events, toStartOfTimeline); + this.createThread(threadId, rootEvent, events, toStartOfTimeline); } - - thread.addEvents(events, toStartOfTimeline); } /** @@ -2700,16 +2718,20 @@ export class Room extends ReadReceipt { * @param addLiveEventOptions - addLiveEvent options * @throws If `duplicateStrategy` is not falsey, 'replace' or 'ignore'. */ - public addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): void; + public async addLiveEvents(events: MatrixEvent[], addLiveEventOptions?: IAddLiveEventOptions): Promise; /** * @deprecated In favor of the overload with `IAddLiveEventOptions` */ - public addLiveEvents(events: MatrixEvent[], duplicateStrategy?: DuplicateStrategy, fromCache?: boolean): void; - public addLiveEvents( + public async addLiveEvents( + events: MatrixEvent[], + duplicateStrategy?: DuplicateStrategy, + fromCache?: boolean, + ): Promise; + public async addLiveEvents( events: MatrixEvent[], duplicateStrategyOrOpts?: DuplicateStrategy | IAddLiveEventOptions, fromCache = false, - ): void { + ): Promise { let duplicateStrategy: DuplicateStrategy | undefined = duplicateStrategyOrOpts as DuplicateStrategy; let timelineWasEmpty: boolean | undefined = false; if (typeof duplicateStrategyOrOpts === "object") { @@ -2760,6 +2782,9 @@ export class Room extends ReadReceipt { timelineWasEmpty, }; + // List of extra events to check for being parents of any relations encountered + const neighbouringEvents = [...events]; + for (const event of events) { // TODO: We should have a filter to say "only add state event types X Y Z to the timeline". this.processLiveEvent(event); @@ -2773,12 +2798,35 @@ export class Room extends ReadReceipt { } } - const { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn( + let { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn( event, - events, + neighbouringEvents, threadRoots, ); + if (!shouldLiveInThread && !shouldLiveInRoom && event.isRelation()) { + try { + const parentEvent = new MatrixEvent( + await this.client.fetchRoomEvent(this.roomId, event.relationEventId!), + ); + neighbouringEvents.push(parentEvent); + if (parentEvent.threadRootId) { + threadRoots.add(parentEvent.threadRootId); + const unsigned = event.getUnsigned(); + unsigned[UNSIGNED_THREAD_ID_FIELD.name] = parentEvent.threadRootId; + event.setUnsigned(unsigned); + } + + ({ shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn( + event, + neighbouringEvents, + threadRoots, + )); + } catch (e) { + logger.error("Failed to load parent event of unhandled relation", e); + } + } + if (shouldLiveInThread && !eventsByThread[threadId ?? ""]) { eventsByThread[threadId ?? ""] = []; } @@ -2786,6 +2834,8 @@ export class Room extends ReadReceipt { if (shouldLiveInRoom) { this.addLiveEvent(event, options); + } else if (!shouldLiveInThread && event.isRelation()) { + this.relations.aggregateChildEvent(event); } } @@ -2796,13 +2846,14 @@ export class Room extends ReadReceipt { public partitionThreadedEvents( events: MatrixEvent[], - ): [timelineEvents: MatrixEvent[], threadedEvents: MatrixEvent[]] { + ): [timelineEvents: MatrixEvent[], threadedEvents: MatrixEvent[], unknownRelations: MatrixEvent[]] { // Indices to the events array, for readability const ROOM = 0; const THREAD = 1; + const UNKNOWN_RELATION = 2; if (this.client.supportsThreads()) { const threadRoots = this.findThreadRoots(events); - return events.reduce( + return events.reduce<[MatrixEvent[], MatrixEvent[], MatrixEvent[]]>( (memo, event: MatrixEvent) => { const { shouldLiveInRoom, shouldLiveInThread, threadId } = this.eventShouldLiveIn( event, @@ -2819,13 +2870,17 @@ export class Room extends ReadReceipt { memo[THREAD].push(event); } + if (!shouldLiveInThread && !shouldLiveInRoom) { + memo[UNKNOWN_RELATION].push(event); + } + return memo; }, - [[] as MatrixEvent[], [] as MatrixEvent[]], + [[], [], []], ); } else { // When `experimentalThreadSupport` is disabled treat all events as timelineEvents - return [events as MatrixEvent[], [] as MatrixEvent[]]; + return [events as MatrixEvent[], [] as MatrixEvent[], [] as MatrixEvent[]]; } } @@ -2838,6 +2893,10 @@ export class Room extends ReadReceipt { if (event.isRelation(THREAD_RELATION_TYPE.name)) { threadRoots.add(event.relationEventId ?? ""); } + const unsigned = event.getUnsigned(); + if (typeof unsigned[UNSIGNED_THREAD_ID_FIELD.name] === "string") { + threadRoots.add(unsigned[UNSIGNED_THREAD_ID_FIELD.name]!); + } } return threadRoots; } diff --git a/src/sliding-sync-sdk.ts b/src/sliding-sync-sdk.ts index b7d2223ffa9..27eae2d94b9 100644 --- a/src/sliding-sync-sdk.ts +++ b/src/sliding-sync-sdk.ts @@ -628,7 +628,7 @@ export class SlidingSyncSdk { if (roomData.invite_state) { const inviteStateEvents = mapEvents(this.client, room.roomId, roomData.invite_state); - this.injectRoomEvents(room, inviteStateEvents); + await this.injectRoomEvents(room, inviteStateEvents); if (roomData.initial) { room.recalculate(); this.client.store.storeRoom(room); @@ -700,7 +700,7 @@ export class SlidingSyncSdk { } } */ - this.injectRoomEvents(room, stateEvents, timelineEvents, roomData.num_live); + await this.injectRoomEvents(room, stateEvents, timelineEvents, roomData.num_live); // we deliberately don't add ephemeral events to the timeline room.addEphemeralEvents(ephemeralEvents); @@ -747,12 +747,12 @@ export class SlidingSyncSdk { * @param numLive - the number of events in timelineEventList which just happened, * supplied from the server. */ - public injectRoomEvents( + public async injectRoomEvents( room: Room, stateEventList: MatrixEvent[], timelineEventList?: MatrixEvent[], numLive?: number, - ): void { + ): Promise { timelineEventList = timelineEventList || []; stateEventList = stateEventList || []; numLive = numLive || 0; @@ -811,11 +811,11 @@ export class SlidingSyncSdk { // if the timeline has any state events in it. // This also needs to be done before running push rules on the events as they need // to be decorated with sender etc. - room.addLiveEvents(timelineEventList, { + await room.addLiveEvents(timelineEventList, { fromCache: true, }); if (liveTimelineEvents.length > 0) { - room.addLiveEvents(liveTimelineEvents, { + await room.addLiveEvents(liveTimelineEvents, { fromCache: false, }); } diff --git a/src/sync.ts b/src/sync.ts index 4800880bc4a..4c78aea89a0 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -501,7 +501,7 @@ export class SyncApi { }, ) .then( - (res) => { + async (res) => { if (this._peekRoom !== peekRoom) { debuglog("Stopped peeking in room %s", peekRoom.roomId); return; @@ -541,7 +541,7 @@ export class SyncApi { }) .map(this.client.getEventMapper()); - peekRoom.addLiveEvents(events); + await peekRoom.addLiveEvents(events); this.peekPoll(peekRoom, res.end); }, (err) => { @@ -899,8 +899,6 @@ export class SyncApi { // Reset after a successful sync this.failedSyncCount = 0; - await this.client.store.setSyncData(data); - const syncEventData = { oldSyncToken: syncToken ?? undefined, nextSyncToken: data.next_batch, @@ -924,6 +922,10 @@ export class SyncApi { this.client.emit(ClientEvent.SyncUnexpectedError, e); } + // Persist after processing as `unsigned` may get mutated + // with an `org.matrix.msc4023.thread_id` + await this.client.store.setSyncData(data); + // update this as it may have changed syncEventData.catchingUp = this.catchingUp; @@ -1627,16 +1629,17 @@ export class SyncApi { return Object.keys(obj) .filter((k) => !unsafeProp(k)) .map((roomId) => { - const arrObj = obj[roomId] as T & { room: Room; isBrandNewRoom: boolean }; let room = client.store.getRoom(roomId); let isBrandNewRoom = false; if (!room) { room = this.createRoom(roomId); isBrandNewRoom = true; } - arrObj.room = room; - arrObj.isBrandNewRoom = isBrandNewRoom; - return arrObj; + return { + ...obj[roomId], + room, + isBrandNewRoom, + }; }); } @@ -1773,7 +1776,7 @@ export class SyncApi { // if the timeline has any state events in it. // This also needs to be done before running push rules on the events as they need // to be decorated with sender etc. - room.addLiveEvents(timelineEventList || [], { + await room.addLiveEvents(timelineEventList || [], { fromCache, timelineWasEmpty, }); From f173014fc0032ca6a67e85a27f7bc00808d6280e Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Thu, 1 Jun 2023 16:54:22 +0100 Subject: [PATCH 91/94] Prepare changelog for v26.0.0-rc.1 --- CHANGELOG.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d15a4cf7b6..419960a769b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,18 @@ -Changes in [25.2.0-rc.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.5) (2023-05-19) +Changes in [26.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v26.0.0-rc.1) (2023-06-01) ============================================================================================================ -## 🐛 Bug Fixes - * Attempt a potential workaround for stuck notifs ([\#3384](https://github.com/matrix-org/matrix-js-sdk/pull/3384)). Fixes vector-im/element-web#25406. Contributed by @andybalaam. - -Changes in [25.2.0-rc.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.2.0-rc.4) (2023-05-16) -============================================================================================================ +## 🚨 BREAKING CHANGES + * Ensure we do not add relations to the wrong timeline ([\#3427](https://github.com/matrix-org/matrix-js-sdk/pull/3427)). Fixes vector-im/element-web#25450 and vector-im/element-web#25494. + * Deprecate `QrCodeEvent`, `SasEvent` and `VerificationEvent` ([\#3386](https://github.com/matrix-org/matrix-js-sdk/pull/3386)). ## 🦖 Deprecations - * Deprecate device methods in MatrixClient ([\#3357](https://github.com/matrix-org/matrix-js-sdk/pull/3357)). + * Move crypto classes into a separate namespace ([\#3385](https://github.com/matrix-org/matrix-js-sdk/pull/3385)). ## ✨ Features + * Mention deno support in the README ([\#3417](https://github.com/matrix-org/matrix-js-sdk/pull/3417)). Contributed by @sigmaSd. + * Mark room version 10 as safe ([\#3425](https://github.com/matrix-org/matrix-js-sdk/pull/3425)). + * Prioritise entirely supported flows for UIA ([\#3402](https://github.com/matrix-org/matrix-js-sdk/pull/3402)). + * Add methods to terminate idb worker ([\#3362](https://github.com/matrix-org/matrix-js-sdk/pull/3362)). * Total summary count ([\#3351](https://github.com/matrix-org/matrix-js-sdk/pull/3351)). Contributed by @toger5. * Audio concealment ([\#3349](https://github.com/matrix-org/matrix-js-sdk/pull/3349)). Contributed by @toger5. @@ -19,6 +21,11 @@ Changes in [25.2.0-rc.4](https://github.com/matrix-org/matrix-js-sdk/releases/ta * Keep measuring a call feed's volume after a stream replacement ([\#3361](https://github.com/matrix-org/matrix-js-sdk/pull/3361)). Fixes vector-im/element-call#1051. * Element-R: Avoid uploading a new fallback key at every `/sync` ([\#3338](https://github.com/matrix-org/matrix-js-sdk/pull/3338)). Fixes vector-im/element-web#25215. * Accumulate receipts for the main thread and unthreaded separately ([\#3339](https://github.com/matrix-org/matrix-js-sdk/pull/3339)). Fixes vector-im/element-web#24629. + * Remove spec non-compliant extended glob format ([\#3423](https://github.com/matrix-org/matrix-js-sdk/pull/3423)). Fixes vector-im/element-web#25474. + * Fix bug where original event was inserted into timeline instead of the edit event ([\#3398](https://github.com/matrix-org/matrix-js-sdk/pull/3398)). Contributed by @andybalaam. + * Only add a local receipt if it's after an existing receipt ([\#3399](https://github.com/matrix-org/matrix-js-sdk/pull/3399)). Contributed by @andybalaam. + * Attempt a potential workaround for stuck notifs ([\#3384](https://github.com/matrix-org/matrix-js-sdk/pull/3384)). Fixes vector-im/element-web#25406. Contributed by @andybalaam. + * Fix verification bug with `pendingEventOrdering: "chronological"` ([\#3382](https://github.com/matrix-org/matrix-js-sdk/pull/3382)). Changes in [25.1.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v25.1.1) (2023-05-16) ================================================================================================== From d4414341d6cd968fcee11e6ef00e0da02c7b5808 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Thu, 1 Jun 2023 16:54:25 +0100 Subject: [PATCH 92/94] v26.0.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6cbb7304d52..e02467fdefc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "25.2.0-rc.5", + "version": "26.0.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0" From 3c4134537a9b64818b134c6c48c3482dbb4d304c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 6 Jun 2023 13:44:03 +0100 Subject: [PATCH 93/94] Prepare changelog for v26.0.0 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 419960a769b..c538f868e8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -Changes in [26.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v26.0.0-rc.1) (2023-06-01) -============================================================================================================ +Changes in [26.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v26.0.0) (2023-06-06) +================================================================================================== ## 🚨 BREAKING CHANGES * Ensure we do not add relations to the wrong timeline ([\#3427](https://github.com/matrix-org/matrix-js-sdk/pull/3427)). Fixes vector-im/element-web#25450 and vector-im/element-web#25494. From f41fa84e72da0542940d2f712dda0f7ee2758b6f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 6 Jun 2023 13:44:07 +0100 Subject: [PATCH 94/94] v26.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e02467fdefc..3836c0c064b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "26.0.0-rc.1", + "version": "26.0.0", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=16.0.0"