Compare commits

..

65 Commits

Author SHA1 Message Date
Michael Telatynski 0300d6343f Remove flaky test (#4332)
Fixes https://github.com/matrix-org/matrix-js-sdk/issues/4331
2024-07-29 13:42:07 +00:00
Richard van der Hoff dc1cccfecc Handle late-arriving m.room_key.withheld messages (#4310)
* Restructure eventsPendingKey to remove sender key

For withheld notices, we don't necessarily receive the sender key, so we'll
jhave to do without it.

* Re-decrypt events when we receive a withheld notice

* Extend test to cover late-arriving withheld notices

* update unit tests
2024-07-29 12:11:37 +00:00
Hubert Chathi d32f398345 Fix comment for useAuthorizationHeader config. (#4330) 2024-07-28 02:25:34 +00:00
Timo 0f08c00c07 Be specific about what is considered a MSC4143 call member event. (#4328)
* Be specific about what is considered a MSC4143 call member event.

* review

* check for empty event first

* Optimize for new session type events
If its a session type event we do not want to run two "key in" checks. We expect legacy events to be the less comment type going forward.

* awkward but necessary key count optimization
2024-07-25 10:57:27 +00:00
Timo 6b261b98c9 Add index.ts for matrixrtc module (#4314) 2024-07-25 10:00:11 +00:00
renovate[bot] 99f157a0f1 Update all non-major dependencies (#4323)
* Update all non-major dependencies

* Prettier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix types for widget API update

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-07-24 16:40:51 +00:00
renovate[bot] f9f6d81346 Lock file maintenance (#4327)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 14:42:05 +00:00
renovate[bot] 46604abe7b Update dependency matrix-widget-api to v1.7.0 (#4326)
* Update dependency matrix-widget-api to v1.7.0

* Fix types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-07-24 13:26:04 +00:00
Valere 553758e713 Bump rust sdk wasm version to 7.0.0 (#4316) 2024-07-24 11:53:36 +00:00
renovate[bot] 509e64cfc1 Update dependency typescript to v5.5.4 (#4321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 10:32:17 +00:00
renovate[bot] 60c2e9b3ed Update babel monorepo (#4318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 09:45:56 +00:00
renovate[bot] cfb21fa80a Update typescript-eslint monorepo to v7.16.1 (#4322)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 09:27:51 +00:00
renovate[bot] c3d7f4e730 Update dependency typedoc-plugin-mdn-links to v3.2.5 (#4320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 09:17:18 +00:00
renovate[bot] aa97beae44 Update dependency @types/node to v18.19.41 (#4319)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 09:10:23 +00:00
David Baker 6f63ff1711 Remove the compare function from utils (#4315)
* Remove the compare function from utils

and change the one use of it to just intantiate a collator and use
it.

This was marked as internal module so this shouldn't be a breaking change.
Of course, react-sdk was using it.

Requires: https://github.com/matrix-org/matrix-react-sdk/pull/12782

* Add simple not-a-perf-test test

* recalculate repeatedly

otherwise we aren't testing anything different

* Use fewer members as it was making the test take a bit too long
2024-07-17 14:18:46 +00:00
Richard van der Hoff 30a26813ec Deprecate CreateSecretStorageOpts.getKeyBackupPassphrase (#4313)
it doesn't work in rust crypto, and we have no plans to fix it
(https://github.com/element-hq/element-web/issues/27455)
2024-07-13 10:19:14 +00:00
renovate[bot] f17a4fedb9 Lock file maintenance (#4199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 16:31:06 +00:00
renovate[bot] 94e393c9a6 Update dependency typedoc to v0.26.4 (#4309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 08:01:38 +00:00
Richard van der Hoff 53201688a6 Element-R: detect "withheld key" UTD errors, and mark them as such (#4302)
Partial fix to element-hq/element-web#27653
2024-07-09 21:42:58 +01:00
renovate[bot] 996663bf64 Update dependency rimraf to v6 (#4307)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 17:12:10 +00:00
renovate[bot] d6e4338a37 Update all non-major dependencies (#4305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 16:48:22 +00:00
renovate[bot] b2665f2128 Update typedoc (#4304)
* Update typedoc

* Update README.md

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-07-09 15:30:34 +00:00
renovate[bot] af4b6bc126 Update typescript-eslint monorepo to v7.15.0 (#4306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 14:16:29 +00:00
renovate[bot] 565bb0ef7c Update dependency typescript to v5.5.3 (#4303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 14:15:37 +00:00
Michael Telatynski fe0edcd081 Stop testing on Node 21 as it is EOL (#4308)
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-07-09 14:32:07 +00:00
Michael Telatynski 51544f25a7 Fix bump-downstreams using incompatible Node version
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-07-09 13:17:11 +01:00
Michael Telatynski 2868308079 Prettier 2024-07-08 16:46:14 +01:00
RiotRobot a5ef569717 Resetting package fields for development 2024-07-08 12:18:34 +00:00
RiotRobot c06b22ae7c Merge branch 'master' into develop 2024-07-08 12:18:34 +00:00
RiotRobot 7a51798acb v34.0.0 2024-07-08 12:18:02 +00:00
Richard van der Hoff 712ba617de Remove crypto shims (#4292)
* Inline subtlecrypto shim

The presence of this thing just makes code more confusing.

* Remove pre-node-20 webcrypto hack

Until node 20.0, the webcrypto API lived at `crypto.webCrypto`. It's now
available at the same place as in web -- `globalThis.crypto`.

See: https://nodejs.org/docs/latest-v20.x/api/webcrypto.html#web-crypto-api

* oidc auth test: Clean up mocking

THe previous reset code wasn't really resetting the right thing. Let's just
re-init `window.crypto` on each test.

* Remove `crypto` shim

This isn't very useful any more.
2024-07-05 09:42:06 +00:00
Timo 957329b218 Fix room state being updated with old (now overwritten) state and emitting for those updates. (#4242)
* Fix room state being updated with old (now overwritten) state and emitting for those updates.

* remove timestamp condition

Add configuration for toStartOfTimeline

* fix timeline tests

* only skip event adding if event_id and replaces_state is set.

* fix room tests

* test skipping insertion

* rename back to lastStateEvent

* store if a state is at the start of a timeline in the RoomState class

* make `isStartTimelineState` a `public readonly` and fix condition.
2024-07-05 09:16:59 +00:00
Richard van der Hoff 1733ec7b7f Remove redundant checks on global.Olm (#4301)
These routines don't use Olm, and we shouldn't be checking for it.
2024-07-04 15:49:56 +00:00
RiotRobot 24c589923b v34.0.0-rc.1 2024-07-04 12:49:22 +00:00
Richard van der Hoff 03ed4f5dd7 Bump node.js requirement to 20. (#4293)
According to
https://github.com/matrix-org/matrix-js-sdk?tab=readme-ov-file#supported-platforms,
we *only* supprt the latest LTS release (which is currenly https://github.com/nodejs/release#release-schedule), so this should be safe.
2024-07-04 12:27:31 +00:00
Joel 6e641a28c0 Add ability to choose how many timeline events to sync when peeking (#4300)
* Add ability to choose how many timeline events to sync when peeking.

* Add a test that covers the new method parameter.

* Formatting.

---------

Co-authored-by: Joel <joel.garplind+github@gmail.com>
2024-07-04 12:14:14 +00:00
ElementRobot 1586de44bd [Backport staging] Fix "Unable to restore session" error (#4299)
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
Fixes https://github.com/element-hq/element-web/issues/27666
2024-07-03 20:56:40 +01:00
Richard van der Hoff b36682cb99 Bump matrix-rust-sdk to 6.2.1 (#4298)
Fixes https://github.com/element-hq/element-web/issues/27666
2024-07-03 19:31:23 +00:00
David Baker 04ea2a4e5d Merge pull request #4297 from matrix-org/backport-4296-to-staging
[Backport staging] Fix error when sending encrypted messages in large rooms
2024-07-03 15:22:31 +01:00
Richard van der Hoff b71099d0f8 Bump matrix-rust-sdk to 6.2.0 (#4296)
Fixes https://github.com/element-hq/element-web/issues/27658

(cherry picked from commit ccc2fb5663)
2024-07-03 14:04:38 +00:00
Richard van der Hoff ccc2fb5663 Bump matrix-rust-sdk to 6.2.0 (#4296)
Fixes https://github.com/element-hq/element-web/issues/27658
2024-07-03 13:45:38 +00:00
David Langley 0a7f7efd9d Sync labels with element-meta and add local labels yml for others. (#4295) 2024-07-03 13:36:40 +00:00
Richard van der Hoff ae58d0c8eb Rust crypto: Clean up handling of verification events (#4288)
We had both an `onIncomingKeyVerificationRequest` and an
`onKeyVerificationRequest` which did different, but related, things.

Improve the documentation and reduce the duplication.
2024-07-01 11:32:09 +00:00
Richard van der Hoff 20a6704497 Remove redundant TextEncoder shim (#4290)
I believe the only reason we had this was that, before Node v11.0,
`TextEncoder` wasn't available in the global object. Nowadays it is (see
https://nodejs.org/api/util.html#class-utiltextencoder), so let's get rid of
it.
2024-07-01 09:15:01 +00:00
Kegan Dougal 3337bda752 Remove useless log line (#4289)
- it was spammy: https://github.com/element-hq/element-web/issues/27031
- it didn't actually log the duration, because the `block` function didn't
  `await` the inner promise.
2024-06-28 15:49:52 +00:00
Richard van der Hoff d90292bff5 Use prebuilt js-sdk for node example (#4286)
This example seems to have been broken by the switch to Typescript. We can't
just symlink in `../..` because that gives us the typescript version of the
source, which, obviously, doesn't work in node.

Instead, make sure we use a prebuilt version of the js-sdk.

It's actually even more broken as of js-sdk 33.0.0, thanks to the switch to ES
modules (#4187), but we'll get to that later.
2024-06-27 16:58:26 +00:00
Richard van der Hoff 3de0c02757 Remove redundant hack for using the old pickle key in rust crypto (#4282)
* Remove redundant hack for using the old pickle key in rust crypto

* Fix tests
2024-06-27 15:43:54 +00:00
Richard van der Hoff 65b9c31f9b Rename crypto-api.ts -> crypto-api/index.ts (#4283)
I found it quite confusing having `CryptoApi` be defined so far from the
`crypto-api` folder.
2024-06-26 21:02:40 +00:00
Richard van der Hoff d629a685c2 Declare matrix-js-sdk as an ES module (#4285)
* Declare matrix-js-sdk as an ES module

* Rename `babel.config.js` to show it is a CommonJS module

... otherwise it gets broken by `scripts/switch_package_to_release.js`
2024-06-26 17:11:29 +00:00
Timo 0210106be2 Add fetching the well known in embedded mode. (#4259)
* Add fetching the well known in embedded mode.

This is used to load the focus from the well known in elment-call.

* revert what we dont want in this PR.

* Update src/client.ts

Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>

---------

Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
2024-06-26 09:29:11 +00:00
renovate[bot] 3e05a71068 Update dependency typedoc-plugin-coverage to v3.3.0 (#4281)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 22:21:51 +00:00
renovate[bot] c755810d9c Update dependency typedoc-plugin-mdn-links to v3.2.0 (#4279)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 17:06:31 +00:00
renovate[bot] 2d492f60a0 Update dependency typescript to v5.5.2 (#4280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 16:49:31 +00:00
renovate[bot] 16db2c5f9a Update dependency @types/node to v18.19.39 (#4278)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 16:30:40 +00:00
renovate[bot] 29c02d8c37 Update all non-major dependencies (#4268)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 16:30:03 +00:00
Michael Telatynski 0f98df158c Fix ingest of release notes wiping out the parent notes (#4266)
* Fix ingest of release notes wiping out the parent notes

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove redundant reusable workflow input

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-06-25 16:24:13 +00:00
renovate[bot] 2ea4ce0bb6 Update dependency bs58 to v6 (#4274)
* Update dependency bs58 to v6

* Update import

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* prettier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-06-25 16:02:43 +00:00
renovate[bot] 3e0017fecf Update actions/checkout digest to 692973e (#4267)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 15:25:04 +00:00
renovate[bot] a0073ddaaf Update typescript-eslint monorepo to v7.14.1 (#4272)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 14:36:52 +00:00
renovate[bot] c29e116c0c Update dependency typescript to v5.5.2 (#4271)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 14:36:11 +00:00
renovate[bot] d9f372ca79 Update dependency eslint-plugin-unicorn to v54 (#4275)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 13:54:38 +00:00
renovate[bot] 6417f4fac7 Update dependency fetch-mock to v10 (#4276)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-06-25 16:02:45 +01:00
renovate[bot] 4bae83f59f Update dependency @types/uuid to v10 (#4273)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 13:53:11 +00:00
renovate[bot] b8c68eb102 Update dependency @types/node to v18.19.39 (#4269)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 13:50:33 +00:00
renovate[bot] 8790cde6d4 Update dependency typedoc to ^0.26.0 (#4270)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
2024-06-25 15:21:27 +01:00
57 changed files with 1503 additions and 1722 deletions
+43
View File
@@ -0,0 +1,43 @@
- name: "A-Element-R"
description: "Issues affecting the port of Element's crypto layer to Rust"
color: "bfd4f2"
- name: "A-Packaging"
description: "Packaging, signing, releasing"
color: "bfd4f2"
- name: "A-Technical-Debt"
color: "bfd4f2"
- name: "A-Testing"
description: "Testing, code coverage, etc."
color: "bfd4f2"
- name: "backport staging"
description: "Label to automatically backport PR to staging branch"
color: "B60205"
- name: "Dependencies"
description: "Pull requests that update a dependency file"
color: "0366d6"
- name: "Easy"
color: "5dc9f7"
- name: "Sponsored"
color: "ffc8f4"
- name: "T-Deprecation"
description: "A pull request that makes something deprecated"
color: "98e6ae"
- name: "T-Other"
description: "Questions, user support, anything else"
color: "98e6ae"
- name: "X-Blocked"
color: "ff7979"
- name: "X-Breaking-Change"
color: "ff7979"
- name: "X-Reverted"
description: "PR has been reverted"
color: "F68AA3"
- name: "X-Upcoming-Release-Blocker"
description: "This does not affect the current release cycle but will affect the next one"
color: "e99695"
- name: "Z-Community-PR"
description: "Issue is solved by a community member's PR"
color: "ededed"
- name: "Z-Flaky-Test"
description: "A test is raising false alarms"
color: "ededed"
+6 -4
View File
@@ -24,10 +24,6 @@ on:
description: List of github projects (owner/repo) which should have their dependency bumped to the newly released version (in JSON string array string syntax)
type: string
required: false
include-changes:
description: Project to include changelog entries from in this release.
type: string
required: false
gpg-fingerprint:
description: Fingerprint of the GPG key to use for signing the git tag and assets, if any.
type: string
@@ -120,6 +116,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install dependencies
run: "yarn install --frozen-lockfile"
@@ -319,6 +316,11 @@ jobs:
ref: staging
token: ${{ secrets.ELEMENT_BOT_TOKEN }}
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version: "lts/*"
- name: Bump dependency
env:
DEPENDENCY: ${{ needs.npm.outputs.id }}
+1
View File
@@ -25,6 +25,7 @@ jobs:
with:
cache: "yarn"
registry-url: "https://registry.npmjs.org"
node-version-file: package.json
- name: 🔨 Install dependencies
run: "yarn install --frozen-lockfile"
+1
View File
@@ -43,6 +43,7 @@ jobs:
uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: 🔨 Install dependencies
run: "yarn install --frozen-lockfile"
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
target_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: "🧮 Checkout code"
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
ref: ${{ github.event.workflow_run.head_branch }} # checkout commit that triggered this workflow
+6 -1
View File
@@ -18,6 +18,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install"
@@ -44,6 +45,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install"
@@ -60,6 +62,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install --frozen-lockfile"
@@ -76,12 +79,13 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install"
- name: Generate Docs
run: "yarn run gendoc --treatWarningsAsErrors"
run: "yarn run gendoc --treatWarningsAsErrors --suppressCommentWarningsInDeclarationFiles"
- name: Upload Artifact
uses: actions/upload-artifact@v4
@@ -100,6 +104,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install --frozen-lockfile"
+21
View File
@@ -0,0 +1,21 @@
name: Sync labels
on:
workflow_dispatch: {}
schedule:
- cron: "0 1 * * *" # 1am every day
push:
branches:
- develop
paths:
- .github/labels.yml
jobs:
sync-labels:
uses: element-hq/element-meta/.github/workflows/sync-labels.yml@develop
with:
LABELS: |
element-hq/element-meta
.github/labels.yml
DELETE: true
WET: true
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
+11 -1
View File
@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
specs: [integ, unit]
node: [18, "lts/*", 21]
node: ["lts/*", 22]
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -63,6 +63,16 @@ jobs:
coverage
!coverage/lcov-report
# Dummy completion job to simplify branch protections
jest-complete:
name: Jest tests
needs: jest
if: always()
runs-on: ubuntu-latest
steps:
- if: needs.jest.result != 'skipped' && needs.jest.result != 'success'
run: exit 1
matrix-react-sdk:
name: Downstream test matrix-react-sdk
if: github.event_name == 'merge_group'
+26
View File
@@ -1,3 +1,29 @@
Changes in [34.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.0.0) (2024-07-08)
==================================================================================================
## 🚨 BREAKING CHANGES
* Fetch capabilities in the background ([#4246](https://github.com/matrix-org/matrix-js-sdk/pull/4246)). Contributed by @dbkr.
## ✨ Features
* Prefix the user+device state key if needed ([#4262](https://github.com/matrix-org/matrix-js-sdk/pull/4262)). Contributed by @AndrewFerr.
* Use legacy call membership if anyone else is ([#4260](https://github.com/matrix-org/matrix-js-sdk/pull/4260)). Contributed by @AndrewFerr.
* Fetch capabilities in the background ([#4246](https://github.com/matrix-org/matrix-js-sdk/pull/4246)). Contributed by @dbkr.
* Use server name instead of homeserver url to allow well-known lookups during QR OIDC reciprocation ([#4233](https://github.com/matrix-org/matrix-js-sdk/pull/4233)). Contributed by @t3chguy.
* Add via parameter for MSC4156 ([#4247](https://github.com/matrix-org/matrix-js-sdk/pull/4247)). Contributed by @Johennes.
* Make the js-sdk compatible with MSC preferred foci and active focus. ([#4195](https://github.com/matrix-org/matrix-js-sdk/pull/4195)). Contributed by @toger5.
* Replace usages of setImmediate with setTimeout for wider compatibility ([#4240](https://github.com/matrix-org/matrix-js-sdk/pull/4240)). Contributed by @t3chguy.
## 🐛 Bug Fixes
* [Backport staging] Fix "Unable to restore session" error ([#4299](https://github.com/matrix-org/matrix-js-sdk/pull/4299)). Contributed by @RiotRobot.
* [Backport staging] Fix error when sending encrypted messages in large rooms ([#4297](https://github.com/matrix-org/matrix-js-sdk/pull/4297)). Contributed by @RiotRobot.
* Element-R: Fix resource leaks in verification logic ([#4263](https://github.com/matrix-org/matrix-js-sdk/pull/4263)). Contributed by @richvdh.
* Upgrade Rust Crypto SDK to 6.1.0 ([#4261](https://github.com/matrix-org/matrix-js-sdk/pull/4261)). Contributed by @richvdh.
* Correctly transform base64 with multiple instances of + or / ([#4252](https://github.com/matrix-org/matrix-js-sdk/pull/4252)). Contributed by @robintown.
* Work around spec bug for m.room.avatar state event content type ([#4245](https://github.com/matrix-org/matrix-js-sdk/pull/4245)). Contributed by @t3chguy.
Changes in [33.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v33.1.0) (2024-06-18)
==================================================================================================
## ✨ Features
+1 -1
View File
@@ -39,7 +39,7 @@ 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.
[the Node.js terminal app](examples/node/README.md) for a more complex example.
To start the client:
+2 -4
View File
@@ -3,12 +3,10 @@
"version": "0.0.0",
"description": "",
"main": "app.js",
"scripts": {
"preinstall": "npm install ../.."
},
"author": "",
"license": "Apache 2.0",
"dependencies": {
"cli-color": "^1.0.0"
"cli-color": "^1.0.0",
"matrix-js-sdk": "^32.0.0"
}
}
+14 -15
View File
@@ -1,9 +1,9 @@
{
"name": "matrix-js-sdk",
"version": "34.0.0-rc.0",
"version": "34.0.0",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
},
"scripts": {
"prepack": "yarn build",
@@ -31,8 +31,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",
@@ -53,9 +53,9 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^6.0.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
"another-json": "^0.2.0",
"bs58": "^5.0.0",
"bs58": "^6.0.0",
"content-type": "^1.0.4",
"jwt-decode": "^4.0.0",
"loglevel": "^1.7.1",
@@ -90,7 +90,7 @@
"@types/jest": "^29.0.0",
"@types/node": "18",
"@types/sdp-transform": "^2.4.5",
"@types/uuid": "9",
"@types/uuid": "10",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"babel-jest": "^29.0.0",
@@ -104,9 +104,9 @@
"eslint-plugin-jsdoc": "^48.0.0",
"eslint-plugin-matrix-org": "^1.0.0",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"fake-indexeddb": "^5.0.2",
"fetch-mock": "9.11.0",
"fetch-mock": "10.1.0",
"fetch-mock-jest": "^1.5.1",
"husky": "^9.0.0",
"jest": "^29.0.0",
@@ -117,19 +117,18 @@
"lint-staged": "^15.0.2",
"matrix-mock-request": "^2.5.0",
"node-fetch": "^2.7.0",
"prettier": "3.3.2",
"rimraf": "^5.0.0",
"prettier": "3.3.3",
"rimraf": "^6.0.0",
"ts-node": "^10.9.2",
"typedoc": "^0.25.10",
"typedoc": "^0.26.0",
"typedoc-plugin-coverage": "^3.0.0",
"typedoc-plugin-mdn-links": "^3.0.3",
"typedoc-plugin-missing-exports": "^2.0.0",
"typedoc-plugin-missing-exports": "^3.0.0",
"typescript": "^5.3.3"
},
"@casualbot/jest-sonar-reporter": {
"outputDirectory": "coverage",
"outputName": "jest-sonar-report.xml",
"relativePaths": true
},
"typings": "./lib/index.d.ts"
}
}
+7 -6
View File
@@ -112,7 +112,14 @@ const main = async ({ github, releaseId, dependencies }) => {
const { GITHUB_REPOSITORY } = process.env;
const [owner, repo] = GITHUB_REPOSITORY.split("/");
const { data: release } = await github.rest.repos.getRelease({
owner,
repo,
release_id: releaseId,
});
const sections = Object.fromEntries(categories.map((cat) => [cat, []]));
parseReleaseNotes(release.body, sections);
for (const dependency of dependencies) {
const releases = await getReleases(github, dependency);
for (const release of releases) {
@@ -120,12 +127,6 @@ const main = async ({ github, releaseId, dependencies }) => {
}
}
const { data: release } = await github.rest.repos.getRelease({
owner,
repo,
release_id: releaseId,
});
const intro = release.body.split(HEADING_PREFIX, 2)[0].trim();
let output = "";
+5
View File
@@ -11,6 +11,11 @@ async function main() {
pkgJson[field] = pkgJson["matrix_lib_" + field];
}
}
// matrix-js-sdk is built into ECMAScript modules. Make sure we declare it as such.
// See https://nodejs.org/api/packages.html#type.
pkgJson["type"] = "module";
await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2));
}
+85 -9
View File
@@ -19,7 +19,7 @@ import anotherjson from "another-json";
import fetchMock from "fetch-mock-jest";
import "fake-indexeddb/auto";
import { IDBFactory } from "fake-indexeddb";
import { MockResponse, MockResponseFunction } from "fetch-mock";
import FetchMock from "fetch-mock";
import Olm from "@matrix-org/olm";
import * as testUtils from "../../test-utils/test-utils";
@@ -157,7 +157,7 @@ async function expectSendRoomKey(
return await new Promise<Olm.InboundGroupSession>((resolve) => {
fetchMock.putOnce(
new RegExp("/sendToDevice/m.room.encrypted/"),
(url: string, opts: RequestInit): MockResponse => {
(url: string, opts: RequestInit): FetchMock.MockResponse => {
const content = JSON.parse(opts.body as string);
resolve(onSendRoomKey(content));
return {};
@@ -291,7 +291,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
* @param response - the response to return from the request. Normally an {@link IClaimOTKsResult}
* (or a function that returns one).
*/
function expectAliceKeyClaim(response: MockResponse | MockResponseFunction) {
function expectAliceKeyClaim(response: FetchMock.MockResponse | FetchMock.MockResponseFunction) {
const rootRegexp = escapeRegExp(new URL("/_matrix/client/", aliceClient.getHomeserverUrl()).toString());
fetchMock.postOnce(new RegExp(rootRegexp + "(r0|v3)/keys/claim"), response);
}
@@ -1419,7 +1419,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
fetchMock.putOnce(
{ url: new RegExp("/send/"), name: "send-event" },
(url: string, opts: RequestInit): MockResponse => {
(url: string, opts: RequestInit): FetchMock.MockResponse => {
const content = JSON.parse(opts.body as string);
logger.log("/send:", content);
// make sure that a new session is used
@@ -1484,7 +1484,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
// mark the device as known, and resend.
aliceClient.setDeviceKnown(aliceClient.getUserId()!, "DEVICE_ID");
expectAliceKeyClaim((url: string, opts: RequestInit): MockResponse => {
expectAliceKeyClaim((url: string, opts: RequestInit): FetchMock.MockResponse => {
const content = JSON.parse(opts.body as string);
expect(content.one_time_keys[aliceClient.getUserId()!].DEVICE_ID).toEqual("signed_curve25519");
return getTestKeysClaimResponse(aliceClient.getUserId()!);
@@ -2180,11 +2180,11 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
const inboundGroupSessionPromise = expectSendRoomKey("@bob:xyz", testOlmAccount);
// ... and finally, send the room key. We block the response until `sendRoomMessageDefer` completes.
const sendRoomMessageDefer = defer<MockResponse>();
const sendRoomMessageDefer = defer<FetchMock.MockResponse>();
const reqProm = new Promise<IContent>((resolve) => {
fetchMock.putOnce(
new RegExp("/send/m.room.encrypted/"),
async (url: string, opts: RequestInit): Promise<MockResponse> => {
async (url: string, opts: RequestInit): Promise<FetchMock.MockResponse> => {
resolve(JSON.parse(opts.body as string));
return await sendRoomMessageDefer.promise;
},
@@ -2333,8 +2333,84 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
});
describe("m.room_key.withheld handling", () => {
// TODO: there are a bunch more tests for this sort of thing in spec/unit/crypto/algorithms/megolm.spec.ts.
// They should be converted to integ tests and moved.
describe.each([
["m.blacklisted", "The sender has blocked you.", DecryptionFailureCode.MEGOLM_KEY_WITHHELD],
[
"m.unverified",
"The sender has disabled encrypting to unverified devices.",
DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE,
],
])(
"Decryption fails with withheld error if a withheld notice with code '%s' is received",
(withheldCode, expectedMessage, expectedErrorCode) => {
it.each(["before", "after"])("%s the event", async (when) => {
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
await startClientAndAwaitFirstSync();
// A promise which resolves, with the MatrixEvent which wraps the event, once the decryption fails.
let awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
// Send Alice an encrypted room event which looks like it was encrypted with a megolm session
async function sendEncryptedEvent() {
const event = {
...testData.ENCRYPTED_EVENT,
origin_server_ts: Date.now(),
};
const syncResponse = {
next_batch: 1,
rooms: { join: { [ROOM_ID]: { timeline: { events: [event] } } } },
};
syncResponder.sendOrQueueSyncResponse(syncResponse);
await syncPromise(aliceClient);
}
// Send Alice a withheld notice
async function sendWithheldMessage() {
const withheldMessage = {
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: ROOM_ID,
sender_key: testData.ENCRYPTED_EVENT.content!.sender_key,
session_id: testData.ENCRYPTED_EVENT.content!.session_id,
code: withheldCode,
reason: "zzz",
},
};
syncResponder.sendOrQueueSyncResponse({
next_batch: 1,
to_device: { events: [withheldMessage] },
});
await syncPromise(aliceClient);
}
if (when === "before") {
await sendWithheldMessage();
await sendEncryptedEvent();
} else {
await sendEncryptedEvent();
// Make sure that the first attempt to decrypt has happened before the withheld arrives
await awaitDecryption;
awaitDecryption = emitPromise(aliceClient, MatrixEventEvent.Decrypted);
await sendWithheldMessage();
}
const ev = await awaitDecryption;
expect(ev.getContent()).toEqual({
body: `** Unable to decrypt: DecryptionError: ${expectedMessage} **`,
msgtype: "m.bad.encrypted",
});
expect(ev.decryptionFailureReason).toEqual(expectedErrorCode);
// `isEncryptedDisabledForUnverifiedDevices` should be true for `m.unverified` and false for other errors.
expect(ev.isEncryptedDisabledForUnverifiedDevices).toEqual(withheldCode === "m.unverified");
});
},
);
oldBackendOnly("does not block decryption on an 'm.unavailable' report", async function () {
// there may be a key downloads for alice
+1 -22
View File
@@ -66,26 +66,6 @@ describe("MatrixClient.initRustCrypto", () => {
expect(databaseNames).toEqual(expect.arrayContaining(["matrix-js-sdk::matrix-sdk-crypto"]));
});
it("should create the meta db if given a pickleKey", async () => {
const matrixClient = createClient({
baseUrl: "http://test.server",
userId: "@alice:localhost",
deviceId: "aliceDevice",
pickleKey: "testKey",
});
// No databases.
expect(await indexedDB.databases()).toHaveLength(0);
await matrixClient.initRustCrypto();
// should have two indexed dbs now
const databaseNames = (await indexedDB.databases()).map((db) => db.name);
expect(databaseNames).toEqual(
expect.arrayContaining(["matrix-js-sdk::matrix-sdk-crypto", "matrix-js-sdk::matrix-sdk-crypto-meta"]),
);
});
it("should create the meta db if given a storageKey", async () => {
const matrixClient = createClient({
baseUrl: "http://test.server",
@@ -470,10 +450,9 @@ describe("MatrixClient.clearStores", () => {
baseUrl: "http://test.server",
userId: "@alice:localhost",
deviceId: "aliceDevice",
pickleKey: "testKey",
});
await matrixClient.initRustCrypto();
await matrixClient.initRustCrypto({ storagePassword: "testKey" });
expect(await indexedDB.databases()).toHaveLength(2);
await matrixClient.stopClient();
+3 -3
View File
@@ -17,7 +17,7 @@ limitations under the License.
import "fake-indexeddb/auto";
import anotherjson from "another-json";
import { MockResponse } from "fetch-mock";
import FetchMock from "fetch-mock";
import fetchMock from "fetch-mock-jest";
import { IDBFactory } from "fake-indexeddb";
import { createHash } from "crypto";
@@ -1511,7 +1511,7 @@ function expectSendToDeviceMessage(msgtype: string): Promise<{ messages: any }>
return new Promise((resolve) => {
fetchMock.putOnce(
new RegExp(`/_matrix/client/(r0|v3)/sendToDevice/${escapeRegExp(msgtype)}`),
(url: string, opts: RequestInit): MockResponse => {
(url: string, opts: RequestInit): FetchMock.MockResponse => {
resolve(JSON.parse(opts.body as string));
return {};
},
@@ -1535,7 +1535,7 @@ function mockSecretRequestAndGetPromises(): Map<string, Promise<string>> {
fetchMock.put(
new RegExp(`/_matrix/client/(r0|v3)/sendToDevice/m.secret.request`),
(url: string, opts: RequestInit): MockResponse => {
(url: string, opts: RequestInit): FetchMock.MockResponse => {
const messages = JSON.parse(opts.body as string).messages[TEST_USER_ID];
// rust crypto broadcasts to all devices, old crypto to a specific device, take the first one
const content = Object.values(messages)[0] as any;
+57 -54
View File
@@ -2281,67 +2281,70 @@ describe("MatrixClient syncing", () => {
httpBackend!.expectedRequests = [];
});
it("should return a room based on the room initialSync API", async () => {
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomOne)}/initialSync`).respond(200, {
room_id: roomOne,
membership: KnownMembership.Leave,
messages: {
start: "start",
end: "end",
chunk: [
it.each([undefined, 100])(
"should return a room based on the room initialSync API with limit %s",
async (limit) => {
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomOne)}/initialSync`).respond(200, {
room_id: roomOne,
membership: KnownMembership.Leave,
messages: {
start: "start",
end: "end",
chunk: [
{
content: { body: "Message 1" },
type: "m.room.message",
event_id: "$eventId1",
sender: userA,
origin_server_ts: 12313525,
room_id: roomOne,
},
{
content: { body: "Message 2" },
type: "m.room.message",
event_id: "$eventId2",
sender: userB,
origin_server_ts: 12315625,
room_id: roomOne,
},
],
},
state: [
{
content: { body: "Message 1" },
type: "m.room.message",
event_id: "$eventId1",
content: { name: "Room Name" },
type: "m.room.name",
event_id: "$eventId",
sender: userA,
origin_server_ts: 12313525,
room_id: roomOne,
},
{
content: { body: "Message 2" },
type: "m.room.message",
event_id: "$eventId2",
sender: userB,
origin_server_ts: 12315625,
origin_server_ts: 12314525,
state_key: "",
room_id: roomOne,
},
],
},
state: [
{
content: { name: "Room Name" },
type: "m.room.name",
event_id: "$eventId",
sender: userA,
origin_server_ts: 12314525,
state_key: "",
room_id: roomOne,
},
],
presence: [
{
content: {},
type: "m.presence",
sender: userA,
},
],
});
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
presence: [
{
content: {},
type: "m.presence",
sender: userA,
},
],
});
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
const prom = client!.peekInRoom(roomOne);
await httpBackend!.flushAllExpected();
const room = await prom;
const prom = client!.peekInRoom(roomOne, limit);
await httpBackend!.flushAllExpected();
const room = await prom;
expect(room.roomId).toBe(roomOne);
expect(room.getMyMembership()).toBe(KnownMembership.Leave);
expect(room.name).toBe("Room Name");
expect(room.currentState.getStateEvents("m.room.name", "")?.getId()).toBe("$eventId");
expect(room.timeline[0].getContent().body).toBe("Message 1");
expect(room.timeline[1].getContent().body).toBe("Message 2");
client?.stopPeeking();
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
await httpBackend!.flushAllExpected();
});
expect(room.roomId).toBe(roomOne);
expect(room.getMyMembership()).toBe(KnownMembership.Leave);
expect(room.name).toBe("Room Name");
expect(room.currentState.getStateEvents("m.room.name", "")?.getId()).toBe("$eventId");
expect(room.timeline[0].getContent().body).toBe("Message 1");
expect(room.timeline[1].getContent().body).toBe("Message 2");
client?.stopPeeking();
httpBackend!.when("GET", "/events").respond(200, { chunk: [] });
await httpBackend!.flushAllExpected();
},
);
});
describe("user account data", () => {
+4 -2
View File
@@ -15,7 +15,6 @@ limitations under the License.
*/
import fetchMock from "fetch-mock-jest";
import { MockOptionsMethodPut } from "fetch-mock";
import { ISyncResponder } from "./SyncResponder";
@@ -40,7 +39,10 @@ export class AccountDataAccumulator {
* @param opts - options to pass to fetchMock
* @returns a Promise which will resolve (with the content of the account data) once it is set.
*/
public interceptSetAccountData(accountDataType: string, opts?: MockOptionsMethodPut): Promise<any> {
public interceptSetAccountData(
accountDataType: string,
opts?: Parameters<(typeof fetchMock)["put"]>[2],
): Promise<any> {
return new Promise((resolve) => {
// Called when the cross signing key is uploaded
fetchMock.put(
+2 -2
View File
@@ -17,7 +17,7 @@ limitations under the License.
import debugFunc from "debug";
import { Debugger } from "debug";
import fetchMock from "fetch-mock-jest";
import { MockResponse } from "fetch-mock";
import FetchMock from "fetch-mock";
/** Interface implemented by classes that intercept `/sync` requests from test clients
*
@@ -80,7 +80,7 @@ export class SyncResponder implements ISyncResponder {
);
}
private async onSyncRequest(): Promise<MockResponse> {
private async onSyncRequest(): Promise<FetchMock.MockResponse> {
switch (this.state) {
case SyncResponderState.IDLE: {
this.debug("Got /sync request: waiting for response to be ready");
+6 -1
View File
@@ -412,7 +412,12 @@ describe("MatrixEvent", () => {
const crypto = {
decryptEvent: jest
.fn()
.mockRejectedValue("DecryptionError: The sender has disabled encrypting to unverified devices."),
.mockRejectedValue(
new DecryptionError(
DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE,
"The sender has disabled encrypting to unverified devices.",
),
),
} as unknown as Crypto;
await encryptedEvent.attemptDecryption(crypto);
+5 -13
View File
@@ -26,7 +26,6 @@ import { getRandomValues } from "node:crypto";
import { TextEncoder } from "node:util";
import { Method } from "../../../src";
import * as crypto from "../../../src/crypto/crypto";
import { logger } from "../../../src/logger";
import {
completeAuthorizationCodeGrant,
@@ -39,11 +38,6 @@ import { makeDelegatedAuthConfig, mockOpenIdConfiguration } from "../../test-uti
jest.mock("jwt-decode");
const webCrypto = new Crypto();
// save for resetting mocks
const realSubtleCrypto = crypto.subtleCrypto;
describe("oidc authorization", () => {
const delegatedAuthConfig = makeDelegatedAuthConfig();
const authorizationEndpoint = delegatedAuthConfig.authorizationEndpoint;
@@ -62,7 +56,11 @@ describe("oidc authorization", () => {
delegatedAuthConfig.metadata.issuer + ".well-known/openid-configuration",
mockOpenIdConfiguration(),
);
global.TextEncoder = TextEncoder;
});
beforeEach(() => {
const webCrypto = new Crypto();
Object.defineProperty(window, "crypto", {
value: {
getRandomValues,
@@ -70,12 +68,6 @@ describe("oidc authorization", () => {
subtle: webCrypto.subtle,
},
});
global.TextEncoder = TextEncoder;
});
afterEach(() => {
// @ts-ignore reset any ugly mocking we did
crypto.subtleCrypto = realSubtleCrypto;
});
it("should generate authorization params", () => {
@@ -99,7 +91,7 @@ describe("oidc authorization", () => {
it("should generate url with correct parameters", async () => {
// test the no crypto case here
// @ts-ignore mocking
crypto.subtleCrypto = undefined;
globalThis.crypto.subtle = undefined;
const authorizationParams = generateAuthorizationParams({ redirectUri: baseUrl });
const authUrl = new URL(
+55
View File
@@ -1122,4 +1122,59 @@ describe("RoomState", function () {
).toBeFalsy();
});
});
describe("skipWrongOrderRoomStateInserts", () => {
const idNow = "now";
const idBefore = "before";
const endState = new RoomState(roomId);
const startState = new RoomState(roomId, undefined, true);
let onRoomStateEvent: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void;
const evNow = new MatrixEvent({
type: "m.call.member",
room_id: roomId,
state_key: "@user:example.org",
content: {},
});
evNow.event.unsigned = { replaces_state: idBefore };
evNow.event.event_id = idNow;
const evBefore = new MatrixEvent({
type: "m.call.member",
room_id: roomId,
state_key: "@user:example.org",
content: {},
});
const updatedStateWithBefore = jest.fn();
const updatedStateWithNow = jest.fn();
beforeEach(() => {
evBefore.event.event_id = idBefore;
onRoomStateEvent = (event, _state, _lastState) => {
if (event.event.event_id === idNow) {
updatedStateWithNow();
} else if (event.event.event_id === idBefore) {
updatedStateWithBefore();
}
};
endState.on(RoomStateEvent.Events, onRoomStateEvent);
startState.on(RoomStateEvent.Events, onRoomStateEvent);
});
afterEach(() => {
endState.off(RoomStateEvent.Events, onRoomStateEvent);
startState.off(RoomStateEvent.Events, onRoomStateEvent);
updatedStateWithNow.mockReset();
updatedStateWithBefore.mockReset();
});
it("should skip inserting state to the end of the timeline if the current endState events replaces_state id is the same as the inserted events id", () => {
endState.setStateEvents([evNow, evBefore]);
expect(updatedStateWithBefore).not.toHaveBeenCalled();
expect(updatedStateWithNow).toHaveBeenCalled();
});
it("should skip inserting state at the beginning of the timeline if the inserted events replaces_state is the event id of the current startState", () => {
startState.setStateEvents([evBefore, evNow]);
expect(updatedStateWithBefore).toHaveBeenCalled();
expect(updatedStateWithNow).not.toHaveBeenCalled();
});
});
});
@@ -95,6 +95,7 @@ describe("initRustCrypto", () => {
deleteSecretsFromInbox: jest.fn(),
registerReceiveSecretCallback: jest.fn(),
registerDevicesUpdatedCallback: jest.fn(),
registerRoomKeysWithheldCallback: jest.fn(),
outgoingRequests: jest.fn(),
isBackupEnabled: jest.fn().mockResolvedValue(false),
verifyBackup: jest.fn().mockResolvedValue({ trusted: jest.fn().mockReturnValue(false) }),
+1 -1
View File
@@ -434,7 +434,7 @@ describe("TimelineWindow", function () {
});
function idsOf(events: Array<MatrixEvent>): Array<string> {
return events.map((e) => (e ? e.getId() ?? "MISSING_ID" : "MISSING_EVENT"));
return events.map((e) => (e ? (e.getId() ?? "MISSING_ID") : "MISSING_EVENT"));
}
describe("removing events", () => {
+1 -1
View File
@@ -105,7 +105,7 @@ const mockGetStateEvents =
(events: MatrixEvent[] = FAKE_STATE_EVENTS as MatrixEvent[]) =>
(type: EventType, userId?: string): MatrixEvent[] | MatrixEvent | null => {
if (type === EventType.GroupCallMemberPrefix) {
return userId === undefined ? events : events.find((e) => e.getStateKey() === userId) ?? null;
return userId === undefined ? events : (events.find((e) => e.getStateKey() === userId) ?? null);
} else {
const fakeEvent = { getContent: () => ({}), getTs: () => 0 } as MatrixEvent;
return userId === undefined ? [fakeEvent] : fakeEvent;
+12 -10
View File
@@ -324,10 +324,13 @@ export interface ICreateClientOpts {
localTimeoutMs?: number;
/**
* Set to true to use
* Authorization header instead of query param to send the access token to the server.
* Set to false to send the access token to the server via a query parameter rather
* than the Authorization HTTP header.
*
* Default false.
* Note that as of v1.11 of the Matrix spec, sending the access token via a query
* is deprecated.
*
* Default true.
*/
useAuthorizationHeader?: boolean;
@@ -1075,7 +1078,7 @@ export type ClientEventHandlerMap = {
* });
* ```
*/
[ClientEvent.Sync]: (state: SyncState, lastState: SyncState | null, data?: ISyncStateData) => void;
[ClientEvent.Sync]: (state: SyncState, prevState: SyncState | null, data?: ISyncStateData) => void;
/**
* Fires whenever the SDK receives a new event.
* <p>
@@ -2232,9 +2235,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
cryptoCallbacks: this.cryptoCallbacks,
storePrefix: args.useIndexedDB === false ? null : RUST_SDK_STORE_PREFIX,
storeKey: args.storageKey,
// temporary compatibility hack: if there is no storageKey nor storagePassword, fall back to the pickleKey
storePassphrase: args.storagePassword ?? this.pickleKey,
storePassphrase: args.storagePassword,
legacyCryptoStore: this.cryptoStore,
legacyPickleKey: this.pickleKey ?? "DEFAULT_KEY",
@@ -6605,13 +6606,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Peek into a room and receive updates about the room. This only works if the
* history visibility for the room is world_readable.
* @param roomId - The room to attempt to peek into.
* @param limit - The number of timeline events to initially retrieve.
* @returns Promise which resolves: Room object
* @returns Rejects: with an error response.
*/
public peekInRoom(roomId: string): Promise<Room> {
public peekInRoom(roomId: string, limit: number = 20): Promise<Room> {
this.peekSync?.stopPeeking();
this.peekSync = new SyncApi(this, this.clientOpts, this.buildSyncApiOptions());
return this.peekSync.peek(roomId);
return this.peekSync.peek(roomId, limit);
}
/**
@@ -7410,7 +7412,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return this.http.authedRequest(Method.Post, path, undefined, undefined, { prefix: "" });
}
private async fetchClientWellKnown(): Promise<void> {
protected async fetchClientWellKnown(): Promise<void> {
// `getRawClientConfig` does not throw or reject on network errors, instead
// it absorbs errors and returns `{}`.
this.clientWellKnownPromise = AutoDiscovery.getRawClientConfig(this.getDomain() ?? undefined);
+24 -13
View File
@@ -15,15 +15,15 @@ limitations under the License.
*/
import type { SecretsBundle } from "@matrix-org/matrix-sdk-crypto-wasm";
import type { IMegolmSessionData } from "./@types/crypto";
import { Room } from "./models/room";
import { DeviceMap } from "./models/device";
import { UIAuthCallback } from "./interactive-auth";
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "./secret-storage";
import { VerificationRequest } from "./crypto-api/verification";
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./crypto-api/keybackup";
import { ISignatures } from "./@types/signed";
import { MatrixEvent } from "./models/event";
import type { IMegolmSessionData } from "../@types/crypto";
import { Room } from "../models/room";
import { DeviceMap } from "../models/device";
import { UIAuthCallback } from "../interactive-auth";
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "../secret-storage";
import { VerificationRequest } from "./verification";
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup";
import { ISignatures } from "../@types/signed";
import { MatrixEvent } from "../models/event";
/**
* Public interface to the cryptography parts of the js-sdk
@@ -557,6 +557,12 @@ export enum DecryptionFailureCode {
/** Message was encrypted with a Megolm session whose keys have not been shared with us. */
MEGOLM_UNKNOWN_INBOUND_SESSION_ID = "MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
/** A special case of {@link MEGOLM_UNKNOWN_INBOUND_SESSION_ID}: the sender has told us it is withholding the key. */
MEGOLM_KEY_WITHHELD = "MEGOLM_KEY_WITHHELD",
/** A special case of {@link MEGOLM_KEY_WITHHELD}: the sender has told us it is withholding the key, because the current device is unverified. */
MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE = "MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE",
/** Message was encrypted with a Megolm session which has been shared with us, but in a later ratchet state. */
OLM_UNKNOWN_MESSAGE_INDEX = "OLM_UNKNOWN_MESSAGE_INDEX",
@@ -848,9 +854,14 @@ export interface CreateSecretStorageOpts {
setupNewSecretStorage?: boolean;
/**
* Function called to get the user's
* current key backup passphrase. Should return a promise that resolves with a Uint8Array
* Function called to get the user's current key backup passphrase.
*
* Should return a promise that resolves with a Uint8Array
* containing the key, or rejects if the key cannot be obtained.
*
* Only used when the client has existing key backup, but no secret storage.
*
* @deprecated Not used by the Rust crypto stack.
*/
getKeyBackupPassphrase?: () => Promise<Uint8Array>;
}
@@ -949,5 +960,5 @@ export interface OwnDeviceKeys {
curve25519: string;
}
export * from "./crypto-api/verification";
export * from "./crypto-api/keybackup";
export * from "./verification";
export * from "./keybackup";
+14 -14
View File
@@ -1221,13 +1221,13 @@ export class OlmDevice {
this.getInboundGroupSession(roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null || sessionData === null) {
if (withheld) {
error = new DecryptionError(
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
calculateWithheldMessage(withheld),
{
session: senderKey + "|" + sessionId,
},
);
const failureCode =
withheld.code === "m.unverified"
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
error = new DecryptionError(failureCode, calculateWithheldMessage(withheld), {
session: senderKey + "|" + sessionId,
});
}
result = null;
return;
@@ -1237,13 +1237,13 @@ export class OlmDevice {
res = session.decrypt(body);
} catch (e) {
if ((<Error>e)?.message === "OLM.UNKNOWN_MESSAGE_INDEX" && withheld) {
error = new DecryptionError(
DecryptionFailureCode.MEGOLM_UNKNOWN_INBOUND_SESSION_ID,
calculateWithheldMessage(withheld),
{
session: senderKey + "|" + sessionId,
},
);
const failureCode =
withheld.code === "m.unverified"
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
error = new DecryptionError(failureCode, calculateWithheldMessage(withheld), {
session: senderKey + "|" + sessionId,
});
} else {
error = <Error>e;
}
+12 -10
View File
@@ -15,7 +15,6 @@ limitations under the License.
*/
import { decodeBase64, encodeBase64 } from "../base64";
import { subtleCrypto, crypto, TextEncoder } from "./crypto";
// salt for HKDF, with 8 bytes of zeros
const zeroSalt = new Uint8Array(8);
@@ -49,7 +48,7 @@ export async function encryptAES(
iv = decodeBase64(ivStr);
} else {
iv = new Uint8Array(16);
crypto.getRandomValues(iv);
globalThis.crypto.getRandomValues(iv);
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
// (which would mean we wouldn't be able to decrypt on Android). The loss
@@ -60,7 +59,7 @@ export async function encryptAES(
const [aesKey, hmacKey] = await deriveKeys(key, name);
const encodedData = new TextEncoder().encode(data);
const ciphertext = await subtleCrypto.encrypt(
const ciphertext = await globalThis.crypto.subtle.encrypt(
{
name: "AES-CTR",
counter: iv,
@@ -70,7 +69,7 @@ export async function encryptAES(
encodedData,
);
const hmac = await subtleCrypto.sign({ name: "HMAC" }, hmacKey, ciphertext);
const hmac = await globalThis.crypto.subtle.sign({ name: "HMAC" }, hmacKey, ciphertext);
return {
iv: encodeBase64(iv),
@@ -91,11 +90,11 @@ export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name:
const ciphertext = decodeBase64(data.ciphertext);
if (!(await subtleCrypto.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
if (!(await globalThis.crypto.subtle.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
throw new Error(`Error decrypting secret ${name}: bad MAC`);
}
const plaintext = await subtleCrypto.decrypt(
const plaintext = await globalThis.crypto.subtle.decrypt(
{
name: "AES-CTR",
counter: decodeBase64(data.iv),
@@ -109,8 +108,8 @@ export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name:
}
async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
const hkdfkey = await subtleCrypto.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
const keybits = await subtleCrypto.deriveBits(
const hkdfkey = await globalThis.crypto.subtle.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
const keybits = await globalThis.crypto.subtle.deriveBits(
{
name: "HKDF",
salt: zeroSalt,
@@ -126,9 +125,12 @@ async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, Cr
const aesKey = keybits.slice(0, 32);
const hmacKey = keybits.slice(32);
const aesProm = subtleCrypto.importKey("raw", aesKey, { name: "AES-CTR" }, false, ["encrypt", "decrypt"]);
const aesProm = globalThis.crypto.subtle.importKey("raw", aesKey, { name: "AES-CTR" }, false, [
"encrypt",
"decrypt",
]);
const hmacProm = subtleCrypto.importKey(
const hmacProm = globalThis.crypto.subtle.importKey(
"raw",
hmacKey,
{
+1 -2
View File
@@ -38,7 +38,6 @@ import {
} from "./keybackup";
import { UnstableValue } from "../NamespacedValue";
import { CryptoEvent } from "./index";
import { crypto } from "./crypto";
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api";
import { BackupTrustInfo } from "../crypto-api/keybackup";
import { BackupDecryptor } from "../common-crypto/CryptoBackend";
@@ -764,7 +763,7 @@ export class Curve25519 implements BackupAlgorithm {
function randomBytes(size: number): Uint8Array {
const buf = new Uint8Array(size);
crypto.getRandomValues(buf);
globalThis.crypto.getRandomValues(buf);
return buf;
}
+2 -34
View File
@@ -14,37 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../logger";
export let crypto = globalThis.crypto;
export let subtleCrypto = crypto?.subtle ?? crypto?.webkitSubtle; // TODO: Stop using webkitSubtle fallback
export let TextEncoder = globalThis.TextEncoder;
/* eslint-disable @typescript-eslint/no-var-requires */
if (!crypto) {
try {
crypto = require("crypto").webcrypto;
} catch (e) {
logger.error("Failed to load webcrypto", e);
}
}
if (!subtleCrypto) {
subtleCrypto = crypto?.subtle;
}
if (!TextEncoder) {
try {
TextEncoder = require("util").TextEncoder;
} catch (e) {
logger.error("Failed to load TextEncoder util", e);
}
}
/* eslint-enable @typescript-eslint/no-var-requires */
export function setCrypto(_crypto: Crypto): void {
crypto = _crypto;
subtleCrypto = _crypto.subtle ?? _crypto.webkitSubtle;
}
export function setTextEncoder(_TextEncoder: typeof TextEncoder): void {
TextEncoder = _TextEncoder;
}
/** @deprecated this is a no-op and should no longer be called. */
export function setCrypto(_crypto: Crypto): void {}
+2 -2
View File
@@ -276,7 +276,7 @@ export type CryptoEventHandlerMap = {
* @param deviceId - the id of the verified device
* @param deviceInfo - updated device information
*/
[CryptoEvent.DeviceVerificationChanged]: (userId: string, deviceId: string, device: DeviceInfo) => void;
[CryptoEvent.DeviceVerificationChanged]: (userId: string, deviceId: string, deviceInfo: DeviceInfo) => void;
/**
* Fires when the trust status of a user changes
* If userId is the userId of the logged-in user, this indicated a change
@@ -292,7 +292,7 @@ export type CryptoEventHandlerMap = {
/**
* Fires when we receive a room key request
*
* @param req - request details
* @param request - request details
*/
[CryptoEvent.RoomKeyRequest]: (request: IncomingRoomKeyRequest) => void;
/**
+9 -14
View File
@@ -15,7 +15,6 @@ limitations under the License.
*/
import { randomString } from "../randomstring";
import { subtleCrypto, TextEncoder } from "./crypto";
const DEFAULT_ITERATIONS = 500000;
@@ -36,10 +35,6 @@ interface IKey {
}
export function keyFromAuthData(authData: IAuthData, password: string): Promise<Uint8Array> {
if (!global.Olm) {
throw new Error("Olm is not available");
}
if (!authData.private_key_salt || !authData.private_key_iterations) {
throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase");
}
@@ -53,10 +48,6 @@ export function keyFromAuthData(authData: IAuthData, password: string): Promise<
}
export async function keyFromPassphrase(password: string): Promise<IKey> {
if (!global.Olm) {
throw new Error("Olm is not available");
}
const salt = randomString(32);
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE);
@@ -70,15 +61,19 @@ export async function deriveKey(
iterations: number,
numBits = DEFAULT_BITSIZE,
): Promise<Uint8Array> {
if (!subtleCrypto || !TextEncoder) {
if (!globalThis.crypto.subtle || !TextEncoder) {
throw new Error("Password-based backup is not available on this platform");
}
const key = await subtleCrypto.importKey("raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, [
"deriveBits",
]);
const key = await globalThis.crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveBits"],
);
const keybits = await subtleCrypto.deriveBits(
const keybits = await globalThis.crypto.subtle.deriveBits(
{
name: "PBKDF2",
salt: new TextEncoder().encode(salt),
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as bs58 from "bs58";
import bs58 from "bs58";
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
+1 -2
View File
@@ -18,7 +18,6 @@ limitations under the License.
* QR code key verification.
*/
import { crypto } from "../crypto";
import { VerificationBase as Base } from "./Base";
import { newKeyMismatchError, newUserCancelledError } from "./Error";
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64";
@@ -202,7 +201,7 @@ export class QRCodeData {
private static generateSharedSecret(): string {
const secretBytes = new Uint8Array(11);
crypto.getRandomValues(secretBytes);
globalThis.crypto.getRandomValues(secretBytes);
return encodeUnpaddedBase64(secretBytes);
}
+10 -2
View File
@@ -218,6 +218,14 @@ export class RoomWidgetClient extends MatrixClient {
});
}) ?? [],
);
if (opts.clientWellKnownPollPeriod !== undefined) {
this.clientWellKnownIntervalID = setInterval(() => {
this.fetchClientWellKnown();
}, 1000 * opts.clientWellKnownPollPeriod);
this.fetchClientWellKnown();
}
this.setSyncState(SyncState.Syncing);
logger.info("Finished backfilling events");
@@ -250,7 +258,7 @@ export class RoomWidgetClient extends MatrixClient {
}
room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
return { event_id: response.event_id };
return { event_id: response.event_id! };
}
public async sendStateEvent(
@@ -259,7 +267,7 @@ export class RoomWidgetClient extends MatrixClient {
content: any,
stateKey = "",
): Promise<ISendEventResponse> {
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
return (await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId)) as ISendEventResponse;
}
public async sendToDevice(eventType: string, contentMap: SendToDeviceContentMap): Promise<{}> {
+11 -10
View File
@@ -160,26 +160,27 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
const callMemberships: CallMembership[] = [];
for (const memberEvent of callMemberEvents) {
const content = memberEvent.getContent();
const eventKeysCount = Object.keys(content).length;
// Dont even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
if (eventKeysCount === 0) continue;
let membershipContents: any[] = [];
// We first decide if its a MSC4143 event (per device state key)
if ("memberships" in content) {
if (eventKeysCount > 1 && "focus_active" in content) {
// We have a MSC4143 event membership event
membershipContents.push(content);
} else if (eventKeysCount === 1 && "memberships" in content) {
// we have a legacy (one event for all devices) event
if (!Array.isArray(content["memberships"])) {
logger.warn(`Malformed member event from ${memberEvent.getSender()}: memberships is not an array`);
continue;
}
membershipContents = content["memberships"];
} else {
// We have a MSC4143 event membership event
if (Object.keys(content).length !== 0) {
// We checked for empty content to not try to construct CallMembership's with {}.
membershipContents.push(content);
}
}
if (membershipContents.length === 0) {
continue;
}
if (membershipContents.length === 0) continue;
for (const membershipData of membershipContents) {
try {
const membership = new CallMembership(memberEvent, membershipData);
+22
View File
@@ -0,0 +1,22 @@
/*
Copyright 2024 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.
*/
export * from "./CallMembership";
export * from "./focus";
export * from "./LivekitFocus";
export * from "./MatrixRTCSession";
export * from "./MatrixRTCSessionManager";
export * from "./types";
+1 -1
View File
@@ -118,7 +118,7 @@ export type EventTimelineSetHandlerMap = {
*/
[RoomEvent.TimelineReset]: (
room: Room | undefined,
eventTimelineSet: EventTimelineSet,
timelineSet: EventTimelineSet,
resetAllTimelines: boolean,
) => void;
};
+2 -2
View File
@@ -127,7 +127,7 @@ export class EventTimeline {
public constructor(private readonly eventTimelineSet: EventTimelineSet) {
this.roomId = eventTimelineSet.room?.roomId ?? null;
if (this.roomId) {
this.startState = new RoomState(this.roomId);
this.startState = new RoomState(this.roomId, undefined, true);
this.endState = new RoomState(this.roomId);
}
@@ -267,7 +267,7 @@ export class EventTimeline {
/**
* Get a pagination token
*
* @param direction - EventTimeline.BACKWARDS to get the pagination
* @param direction - EventTimeline.BACKWARDS to get the pagination
* token for going backwards in time; EventTimeline.FORWARDS to get the
* pagination token for going forwards in time.
*
+5 -12
View File
@@ -43,7 +43,6 @@ import { MatrixError } from "../http-api";
import { TypedEventEmitter } from "./typed-event-emitter";
import { EventStatus } from "./event-status";
import { CryptoBackend, DecryptionError } from "../common-crypto/CryptoBackend";
import { WITHHELD_MESSAGES } from "../crypto/OlmDevice";
import { IAnnotatedPushRule } from "../@types/PushRules";
import { Room } from "./room";
import { EventTimeline } from "./event-timeline";
@@ -312,12 +311,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
private thread?: Thread;
private threadId?: string;
/*
* True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and
* the sender has disabled encrypting to unverified devices.
*/
private encryptedDisabledForUnverifiedDevices = false;
/* Set an approximate timestamp for the event relative the local clock.
* This will inherently be approximate because it doesn't take into account
* the time between the server putting the 'age' field on the event as it sent
@@ -411,7 +404,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
// The fallback in these cases will be to use the origin_server_ts.
// For EDUs, the origin_server_ts also is not defined so we use Date.now().
const age = this.getAge();
this.localTimestamp = age !== undefined ? Date.now() - age : this.getTs() ?? Date.now();
this.localTimestamp = age !== undefined ? Date.now() - age : (this.getTs() ?? Date.now());
this.reEmitter = new TypedReEmitter(this);
}
@@ -787,12 +780,14 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
return this._decryptionFailureReason;
}
/*
/**
* True if this event is an encrypted event which we failed to decrypt, the receiver's device is unverified and
* the sender has disabled encrypting to unverified devices.
*
* @deprecated: Prefer `event.decryptionFailureReason === DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE`.
*/
public get isEncryptedDisabledForUnverifiedDevices(): boolean {
return this.isDecryptionFailure() && this.encryptedDisabledForUnverifiedDevices;
return this.decryptionFailureReason === DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE;
}
public shouldAttemptDecryption(): boolean {
@@ -982,7 +977,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.claimedEd25519Key = decryptionResult.claimedEd25519Key ?? null;
this.forwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain || [];
this.untrusted = decryptionResult.untrusted || false;
this.encryptedDisabledForUnverifiedDevices = false;
this.invalidateExtensibleEvent();
}
@@ -1003,7 +997,6 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
this.claimedEd25519Key = null;
this.forwardingCurve25519KeyChain = [];
this.untrusted = false;
this.encryptedDisabledForUnverifiedDevices = reason === `DecryptionError: ${WITHHELD_MESSAGES["m.unverified"]}`;
this.invalidateExtensibleEvent();
}
+31 -3
View File
@@ -95,7 +95,7 @@ export type RoomStateEventHandlerMap = {
* });
* ```
*/
[RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, lastStateEvent: MatrixEvent | null) => void;
[RoomStateEvent.Events]: (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent | null) => void;
/**
* Fires whenever a member in the members dictionary is updated in any way.
* @param event - The matrix event which caused this event to fire.
@@ -194,10 +194,22 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* As the timeline might get reset while they are loading, this state needs to be inherited
* and shared when the room state is cloned for the new timeline.
* This should only be passed from clone.
* @param isStartTimelineState - Optional. This state is marked as a start state.
* This is used to skip state insertions that are
* in the wrong order. The order is determined by the `replaces_state` id.
*
* Example:
* A current state events `replaces_state` value is `1`.
* Trying to insert a state event with `event_id` `1` in its place would fail if isStartTimelineState = false.
*
* A current state events `event_id` is `2`.
* Trying to insert a state event where its `replaces_state` value is `2` would fail if isStartTimelineState = true.
*/
public constructor(
public readonly roomId: string,
private oobMemberFlags = { status: OobStatus.NotStarted },
public readonly isStartTimelineState = false,
) {
super();
this.updateModifiedTime();
@@ -408,7 +420,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* Fires {@link RoomStateEvent.Events}
* Fires {@link RoomStateEvent.Marker}
*/
public setStateEvents(stateEvents: MatrixEvent[], markerFoundOptions?: IMarkerFoundOptions): void {
public setStateEvents(stateEvents: MatrixEvent[], options?: IMarkerFoundOptions): void {
this.updateModifiedTime();
// update the core event dict
@@ -420,6 +432,22 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
}
const lastStateEvent = this.getStateEventMatching(event);
// Safety measure to not update the room (and emit the update) with older state.
// The sync loop really should not send old events but it does very regularly.
// Logging on return in those two conditions results in a large amount of logging. (on startup and when running element)
const lastReplaceId = lastStateEvent?.event.unsigned?.replaces_state;
const lastId = lastStateEvent?.event.event_id;
const newReplaceId = event.event.unsigned?.replaces_state;
const newId = event.event.event_id;
if (this.isStartTimelineState) {
// Add an event to the start of the timeline. Its replace id should not be the same as the one of the current/last start state event.
if (newReplaceId && lastId && newReplaceId === lastId) return;
} else {
// Add an event to the end of the timeline. It should not be the same as the one replaced by the current/last end state event.
if (lastReplaceId && newId && lastReplaceId === newId) return;
}
this.setStateEvent(event);
if (event.getType() === EventType.RoomMember) {
this.updateDisplayNameCache(event.getStateKey()!, event.getContent().displayname ?? "");
@@ -476,7 +504,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
// assume all our sentinels are now out-of-date
this.sentinels = {};
} else if (UNSTABLE_MSC2716_MARKER.matches(event.getType())) {
this.emit(RoomStateEvent.Marker, event, markerFoundOptions);
this.emit(RoomStateEvent.Marker, event, options);
}
});
+4 -3
View File
@@ -24,7 +24,7 @@ import {
} from "./event-timeline-set";
import { Direction, EventTimeline } from "./event-timeline";
import { getHttpUriForMxc } from "../content-repo";
import { compare, removeElement } from "../utils";
import { removeElement } from "../utils";
import { normalize, noUnsafeEventProps } from "../utils";
import { IEvent, IThreadBundledRelationship, MatrixEvent, MatrixEventEvent, MatrixEventHandlerMap } from "./event";
import { EventStatus } from "./event-status";
@@ -209,7 +209,7 @@ export type RoomEventHandlerMap = {
* });
* ```
*/
[RoomEvent.AccountData]: (event: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => void;
[RoomEvent.AccountData]: (event: MatrixEvent, room: Room, prevEvent?: MatrixEvent) => void;
/**
* Fires whenever a receipt is received for a room
* @param event - The receipt event
@@ -3451,7 +3451,8 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
return true;
});
// make sure members have stable order
otherMembers.sort((a, b) => compare(a.userId, b.userId));
const collator = new Intl.Collator();
otherMembers.sort((a, b) => collator.compare(a.userId, b.userId));
// only 5 first members, immitate summaryHeroes
otherMembers = otherMembers.slice(0, 5);
otherNames = otherMembers.map((m) => m.name);
+6 -7
View File
@@ -16,17 +16,16 @@ limitations under the License.
import { IdTokenClaims, Log, OidcClient, SigninResponse, SigninState, WebStorageStateStore } from "oidc-client-ts";
import { subtleCrypto, TextEncoder } from "../crypto/crypto";
import { logger } from "../logger";
import { randomString } from "../randomstring";
import { OidcError } from "./error";
import {
validateIdToken,
ValidatedIssuerMetadata,
validateStoredUserState,
UserState,
BearerTokenResponse,
UserState,
validateBearerTokenResponse,
ValidatedIssuerMetadata,
validateIdToken,
validateStoredUserState,
} from "./validate";
// reexport for backwards compatibility
@@ -57,14 +56,14 @@ export const generateScope = (deviceId?: string): string => {
// https://www.rfc-editor.org/rfc/rfc7636
const generateCodeChallenge = async (codeVerifier: string): Promise<string> => {
if (!subtleCrypto) {
if (!globalThis.crypto.subtle) {
// @TODO(kerrya) should this be allowed? configurable?
logger.warn("A secure context is required to generate code challenge. Using plain text code challenge");
return codeVerifier;
}
const utf8 = new TextEncoder().encode(codeVerifier);
const digest = await subtleCrypto.digest("SHA-256", utf8);
const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/=/g, "")
+1 -2
View File
@@ -16,7 +16,6 @@ limitations under the License.
*/
import { encodeUnpaddedBase64Url } from "./base64";
import { crypto } from "./crypto/crypto";
const LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -24,7 +23,7 @@ const DIGITS = "0123456789";
export function secureRandomBase64Url(len: number): string {
const key = new Uint8Array(len);
crypto.getRandomValues(key);
globalThis.crypto.getRandomValues(key);
return encodeUnpaddedBase64Url(key);
}
@@ -17,16 +17,15 @@ limitations under the License.
import { SAS } from "@matrix-org/olm";
import {
RendezvousError,
RendezvousCode,
RendezvousIntent,
RendezvousChannel,
RendezvousTransportDetails,
RendezvousTransport,
LegacyRendezvousFailureReason as RendezvousFailureReason,
RendezvousChannel,
RendezvousCode,
RendezvousError,
RendezvousIntent,
RendezvousTransport,
RendezvousTransportDetails,
} from "..";
import { encodeUnpaddedBase64, decodeBase64 } from "../../base64";
import { crypto, subtleCrypto, TextEncoder } from "../../crypto/crypto";
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64";
import { generateDecimalSas } from "../../crypto/verification/SASDecimal";
import { UnstableValue } from "../../NamespacedValue";
@@ -56,11 +55,11 @@ export interface EncryptedPayload {
}
async function importKey(key: Uint8Array): Promise<CryptoKey> {
if (!subtleCrypto) {
if (!globalThis.crypto.subtle) {
throw new Error("Web Crypto is not available");
}
const imported = subtleCrypto.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
const imported = globalThis.crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
return imported;
}
@@ -164,16 +163,16 @@ export class MSC3903ECDHv2RendezvousChannel<T> implements RendezvousChannel<T> {
}
private async encrypt(data: T): Promise<MSC3903ECDHPayload> {
if (!subtleCrypto) {
if (!globalThis.crypto.subtle) {
throw new Error("Web Crypto is not available");
}
const iv = new Uint8Array(32);
crypto.getRandomValues(iv);
globalThis.crypto.getRandomValues(iv);
const encodedData = new TextEncoder().encode(JSON.stringify(data));
const ciphertext = await subtleCrypto.encrypt(
const ciphertext = await globalThis.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
@@ -208,11 +207,11 @@ export class MSC3903ECDHv2RendezvousChannel<T> implements RendezvousChannel<T> {
const ciphertextBytes = decodeBase64(ciphertext);
if (!subtleCrypto) {
if (!globalThis.crypto.subtle) {
throw new Error("Web Crypto is not available");
}
const plaintext = await subtleCrypto.decrypt(
const plaintext = await globalThis.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: decodeBase64(iv),
+1 -2
View File
@@ -21,7 +21,6 @@ import { encodeUri } from "../utils";
import { IHttpOpts, MatrixError, MatrixHttpApi, Method } from "../http-api";
import { IToDeviceEvent } from "../sync-accumulator";
import { ServerSideSecretStorage } from "../secret-storage";
import { crypto } from "../crypto/crypto";
import { decodeBase64, encodeUnpaddedBase64 } from "../base64";
import { Logger } from "../logger";
@@ -155,7 +154,7 @@ export class DehydratedDeviceManager {
*/
public async resetKey(): Promise<void> {
const key = new Uint8Array(32);
crypto.getRandomValues(key);
globalThis.crypto.getRandomValues(key);
await this.secretStorage.store(SECRET_STORAGE_NAME, encodeUnpaddedBase64(key));
this.key = key;
}
+12 -10
View File
@@ -14,16 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
import {
CollectStrategy,
EncryptionAlgorithm,
EncryptionSettings,
HistoryVisibility as RustHistoryVisibility,
OlmMachine,
RoomId,
UserId,
HistoryVisibility as RustHistoryVisibility,
ToDeviceRequest,
UserId,
} from "@matrix-org/matrix-sdk-crypto-wasm";
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-wasm";
import { EventType } from "../@types/event";
import { IContent, MatrixEvent } from "../models/event";
@@ -105,10 +106,8 @@ export class RoomEncryptor {
(member.membership == KnownMembership.Invite && this.room.shouldEncryptForInvitedMembers())
) {
// make sure we are tracking the deviceList for this user
logDuration(this.prefixedLogger, "updateTrackedUsers", async () => {
this.olmMachine.updateTrackedUsers([new UserId(member.userId)]).catch((e) => {
this.prefixedLogger.error("Unable to update tracked users", e);
});
this.olmMachine.updateTrackedUsers([new UserId(member.userId)]).catch((e) => {
this.prefixedLogger.error("Unable to update tracked users", e);
});
}
@@ -144,7 +143,7 @@ export class RoomEncryptor {
* @param globalBlacklistUnverifiedDevices - When `true`, it will not send encrypted messages to unverified devices
*/
public encryptEvent(event: MatrixEvent | null, globalBlacklistUnverifiedDevices: boolean): Promise<void> {
const logger = new LogSpan(this.prefixedLogger, event ? event.getTxnId() ?? "" : "prepareForEncryption");
const logger = new LogSpan(this.prefixedLogger, event ? (event.getTxnId() ?? "") : "prepareForEncryption");
// Ensure order of encryption to avoid message ordering issues, as the scheduler only ensures
// events order after they have been encrypted.
const prom = this.currentEncryptionPromise
@@ -254,8 +253,11 @@ export class RoomEncryptor {
// When this.room.getBlacklistUnverifiedDevices() === null, the global settings should be used
// See Room#getBlacklistUnverifiedDevices
rustEncryptionSettings.onlyAllowTrustedDevices =
this.room.getBlacklistUnverifiedDevices() ?? globalBlacklistUnverifiedDevices;
if (this.room.getBlacklistUnverifiedDevices() ?? globalBlacklistUnverifiedDevices) {
rustEncryptionSettings.sharingStrategy = CollectStrategy.DeviceBasedStrategyOnlyTrustedDevices;
} else {
rustEncryptionSettings.sharingStrategy = CollectStrategy.DeviceBasedStrategyAllDevices;
}
await logDuration(this.prefixedLogger, "shareRoomKey", async () => {
const shareMessages: ToDeviceRequest[] = await this.olmMachine.shareRoomKey(
+3
View File
@@ -174,6 +174,9 @@ async function initOlmMachine(
await olmMachine.registerRoomKeyUpdatedCallback((sessions: RustSdkCryptoJs.RoomKeyInfo[]) =>
rustCrypto.onRoomKeysUpdated(sessions),
);
await olmMachine.registerRoomKeysWithheldCallback((withheld: RustSdkCryptoJs.RoomKeyWithheldInfo[]) =>
rustCrypto.onRoomKeysWithheld(withheld),
);
await olmMachine.registerUserIdentityUpdatedCallback((userId: RustSdkCryptoJs.UserId) =>
rustCrypto.onUserIdentityUpdated(userId),
);
+83 -61
View File
@@ -21,7 +21,7 @@ import type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypt
import { KnownMembership } from "../@types/membership";
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
import type { IEncryptedEventInfo } from "../crypto/api";
import { IContent, MatrixEvent, MatrixEventEvent } from "../models/event";
import { MatrixEvent, MatrixEventEvent } from "../models/event";
import { Room } from "../models/room";
import { RoomMember } from "../models/room-member";
import { BackupDecryptor, CryptoBackend, DecryptionError, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
@@ -62,7 +62,6 @@ import { CrossSigningIdentity } from "./CrossSigningIdentity";
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage";
import { keyFromPassphrase } from "../crypto/key_passphrase";
import { encodeRecoveryKey } from "../crypto/recoverykey";
import { crypto } from "../crypto/crypto";
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification";
import { EventType, MsgType } from "../@types/event";
import { CryptoEvent } from "../crypto";
@@ -891,7 +890,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
} else {
// Using the navigator crypto API to generate the private key
const key = new Uint8Array(32);
crypto.getRandomValues(key);
globalThis.crypto.getRandomValues(key);
return {
privateKey: key,
encodedPrivateKey: encodeRecoveryKey(key),
@@ -1315,7 +1314,11 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
// look for interesting to-device messages
for (const message of processed) {
if (message.type === EventType.KeyVerificationRequest) {
this.onIncomingKeyVerificationRequest(message.sender, message.content);
const sender = message.sender;
const transactionId = message.content.transaction_id;
if (transactionId && sender) {
this.onIncomingKeyVerificationRequest(sender, transactionId);
}
}
}
return processed;
@@ -1411,18 +1414,13 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
}
/**
* Handle an incoming m.key.verification request event
* Handle an incoming m.key.verification.request event, received either in-room or in a to-device message.
*
* @param sender - the sender of the event
* @param content - the content of the event
* @param transactionId - the transaction ID for the verification. For to-device messages, this comes from the
* content of the message; for in-room messages it is the event ID.
*/
private onIncomingKeyVerificationRequest(sender: string, content: IContent): void {
const transactionId = content.transaction_id;
if (!transactionId || !sender) {
// not a valid request: ignore
return;
}
private onIncomingKeyVerificationRequest(sender: string, transactionId: string): void {
const request: RustSdkCryptoJs.VerificationRequest | undefined = this.olmMachine.getVerificationRequest(
new RustSdkCryptoJs.UserId(sender),
transactionId,
@@ -1438,6 +1436,12 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
this._supportedVerificationMethods,
),
);
} else {
// There are multiple reasons this can happen; probably the most likely is that the event is an
// in-room event which is too old.
this.logger.info(
`Ignoring just-received verification request ${transactionId} which did not start a rust-side verification`,
);
}
}
@@ -1482,7 +1486,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
this.logger.debug(
`Got update for session ${key.senderKey.toBase64()}|${key.sessionId} in ${key.roomId.toString()}`,
);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(key);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(key.roomId.toString(), key.sessionId);
if (pendingList.length === 0) return;
this.logger.debug(
@@ -1503,6 +1507,37 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
}
}
/**
* Callback for `OlmMachine.registerRoomKeyWithheldCallback`.
*
* Called by the rust sdk whenever we are told that a key has been withheld. We see if we had any events that
* failed to decrypt for the given session, and update their status if so.
*
* @param withheld - Details of the withheld sessions.
*/
public async onRoomKeysWithheld(withheld: RustSdkCryptoJs.RoomKeyWithheldInfo[]): Promise<void> {
for (const session of withheld) {
this.logger.debug(`Got withheld message for session ${session.sessionId} in ${session.roomId.toString()}`);
const pendingList = this.eventDecryptor.getEventsPendingRoomKey(
session.roomId.toString(),
session.sessionId,
);
if (pendingList.length === 0) return;
// The easiest way to update the status of the event is to have another go at decrypting it.
this.logger.debug(
"Retrying decryption on events:",
pendingList.map((e) => `${e.getId()}`),
);
for (const ev of pendingList) {
ev.attemptDecryption(this, { isRetry: true }).catch((_e) => {
// It's somewhat expected that we still can't decrypt here.
});
}
}
}
/**
* Callback for `OlmMachine.registerUserIdentityUpdatedCallback`
*
@@ -1597,7 +1632,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
const processEvent = async (evt: MatrixEvent): Promise<void> => {
// Process only verification event
if (isVerificationEvent(event)) {
await this.onKeyVerificationRequest(evt);
await this.onKeyVerificationEvent(evt);
}
};
@@ -1624,11 +1659,11 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
}
/**
* Handle key verification request.
* Handle an in-room key verification event.
*
* @param event - a key validation request event.
*/
private async onKeyVerificationRequest(event: MatrixEvent): Promise<void> {
private async onKeyVerificationEvent(event: MatrixEvent): Promise<void> {
const roomId = event.getRoomId();
if (!roomId) {
@@ -1655,27 +1690,7 @@ export class RustCrypto extends TypedEventEmitter<RustCryptoEvents, RustCryptoEv
event.getType() === EventType.RoomMessage &&
event.getContent().msgtype === MsgType.KeyVerificationRequest
) {
const request: RustSdkCryptoJs.VerificationRequest | undefined = this.olmMachine.getVerificationRequest(
new RustSdkCryptoJs.UserId(event.getSender()!),
event.getId()!,
);
if (!request) {
// There are multiple reasons this can happen; probably the most likely is that the event is too old.
this.logger.info(
`Ignoring just-received verification request ${event.getId()} which did not start a rust-side verification`,
);
} else {
this.emit(
CryptoEvent.VerificationRequestReceived,
new RustVerificationRequest(
this.olmMachine,
request,
this.outgoingRequestProcessor,
this._supportedVerificationMethods,
),
);
}
this.onIncomingKeyVerificationRequest(event.getSender()!, event.getId()!);
}
// that may have caused us to queue up outgoing requests, so make sure we send them.
@@ -1699,7 +1714,7 @@ class EventDecryptor {
/**
* Events which we couldn't decrypt due to unknown sessions / indexes.
*
* Map from senderKey to sessionId to Set of MatrixEvents
* Map from roomId to sessionId to Set of MatrixEvents
*/
private eventsPendingKey = new MapWithDefault<string, MapWithDefault<string, Set<MatrixEvent>>>(
() => new MapWithDefault<string, Set<MatrixEvent>>(() => new Set()),
@@ -1803,6 +1818,17 @@ class EventDecryptor {
}
}
// If we got a withheld code, expose that.
if (err.maybe_withheld) {
// Unfortunately the Rust SDK API doesn't let us distinguish between different withheld cases, other than
// by string-matching.
const failureCode =
err.maybe_withheld === "The sender has disabled encrypting to unverified devices."
? DecryptionFailureCode.MEGOLM_KEY_WITHHELD_FOR_UNVERIFIED_DEVICE
: DecryptionFailureCode.MEGOLM_KEY_WITHHELD;
throw new DecryptionError(failureCode, err.maybe_withheld, errorDetails);
}
switch (err.code) {
case RustSdkCryptoJs.DecryptionErrorCode.MissingRoomKey:
throw new DecryptionError(
@@ -1848,30 +1874,27 @@ class EventDecryptor {
* Look for events which are waiting for a given megolm session
*
* Returns a list of events which were encrypted by `session` and could not be decrypted
*
* @param session -
*/
public getEventsPendingRoomKey(session: RustSdkCryptoJs.RoomKeyInfo): MatrixEvent[] {
const senderPendingEvents = this.eventsPendingKey.get(session.senderKey.toBase64());
if (!senderPendingEvents) return [];
public getEventsPendingRoomKey(roomId: string, sessionId: string): MatrixEvent[] {
const roomPendingEvents = this.eventsPendingKey.get(roomId);
if (!roomPendingEvents) return [];
const sessionPendingEvents = senderPendingEvents.get(session.sessionId);
const sessionPendingEvents = roomPendingEvents.get(sessionId);
if (!sessionPendingEvents) return [];
const roomId = session.roomId.toString();
return [...sessionPendingEvents].filter((ev) => ev.getRoomId() === roomId);
return [...sessionPendingEvents];
}
/**
* Add an event to the list of those awaiting their session keys.
*/
private addEventToPendingList(event: MatrixEvent): void {
const content = event.getWireContent();
const senderKey = content.sender_key;
const sessionId = content.session_id;
const roomId = event.getRoomId();
// We shouldn't have events without a room id here.
if (!roomId) return;
const senderPendingEvents = this.eventsPendingKey.getOrCreate(senderKey);
const sessionPendingEvents = senderPendingEvents.getOrCreate(sessionId);
const roomPendingEvents = this.eventsPendingKey.getOrCreate(roomId);
const sessionPendingEvents = roomPendingEvents.getOrCreate(event.getWireContent().session_id);
sessionPendingEvents.add(event);
}
@@ -1879,23 +1902,22 @@ class EventDecryptor {
* Remove an event from the list of those awaiting their session keys.
*/
private removeEventFromPendingList(event: MatrixEvent): void {
const content = event.getWireContent();
const senderKey = content.sender_key;
const sessionId = content.session_id;
const roomId = event.getRoomId();
if (!roomId) return;
const senderPendingEvents = this.eventsPendingKey.get(senderKey);
if (!senderPendingEvents) return;
const roomPendingEvents = this.eventsPendingKey.getOrCreate(roomId);
if (!roomPendingEvents) return;
const sessionPendingEvents = senderPendingEvents.get(sessionId);
const sessionPendingEvents = roomPendingEvents.get(event.getWireContent().session_id);
if (!sessionPendingEvents) return;
sessionPendingEvents.delete(event);
// also clean up the higher-level maps if they are now empty
if (sessionPendingEvents.size === 0) {
senderPendingEvents.delete(sessionId);
if (senderPendingEvents.size === 0) {
this.eventsPendingKey.delete(senderKey);
roomPendingEvents.delete(event.getWireContent().session_id);
if (roomPendingEvents.size === 0) {
this.eventsPendingKey.delete(roomId);
}
}
}
+1 -1
View File
@@ -504,7 +504,7 @@ export class SyncAccumulator {
currentData._timeline.push({
event: transformedEvent,
token: index === 0 ? data.timeline.prev_batch ?? null : null,
token: index === 0 ? (data.timeline.prev_batch ?? null) : null,
});
});
+6 -3
View File
@@ -401,17 +401,18 @@ export class SyncApi {
* Peek into a room. This will result in the room in question being synced so it
* is accessible via getRooms(). Live updates for the room will be provided.
* @param roomId - The room ID to peek into.
* @param limit - The number of timeline events to initially retrieve.
* @returns A promise which resolves once the room has been added to the
* store.
*/
public peek(roomId: string): Promise<Room> {
public peek(roomId: string, limit: number = 20): Promise<Room> {
if (this._peekRoom?.roomId === roomId) {
return Promise.resolve(this._peekRoom);
}
const client = this.client;
this._peekRoom = this.createRoom(roomId);
return this.client.roomInitialSync(roomId, 20).then((response) => {
return this.client.roomInitialSync(roomId, limit).then((response) => {
if (this._peekRoom?.roomId !== roomId) {
throw new Error("Peeking aborted");
}
@@ -1402,7 +1403,9 @@ export class SyncApi {
if (limited) {
room.resetLiveTimeline(
joinObj.timeline.prev_batch,
this.syncOpts.canResetEntireTimeline!(room.roomId) ? null : syncEventData.oldSyncToken ?? null,
this.syncOpts.canResetEntireTimeline!(room.roomId)
? null
: (syncEventData.oldSyncToken ?? null),
);
// We have to assume any gap in any timeline is
-10
View File
@@ -649,16 +649,6 @@ export function lexicographicCompare(a: string, b: string): number {
}
}
const collator = new Intl.Collator();
/**
* Performant language-sensitive string comparison
* @param a - the first string to compare
* @param b - the second string to compare
*/
export function compare(a: string, b: string): number {
return collator.compare(a, b);
}
/**
* This function is similar to Object.assign() but it assigns recursively and
* allows you to ignore nullish values from the source
+1 -1
View File
@@ -91,7 +91,7 @@ export type GroupCallEventHandlerMap = {
* `MatrixCall.ERR_NO_USER_MEDIA`. `ERR_LOCAL_OFFER_FAILED` is emitted when the local client
* fails to create an offer. `ERR_NO_USER_MEDIA` is emitted when the user has denied access
* to their audio/video hardware.
* @param err - The error raised by MatrixCall.
* @param error - The error raised by MatrixCall.
* @example
* ```
* matrixCall.on("error", function(err){
+845 -1334
View File
File diff suppressed because it is too large Load Diff