Compare commits

...

89 Commits

Author SHA1 Message Date
RiotRobot 5a1c70ad19 v34.5.0-rc.0 2024-09-03 12:31:16 +00:00
David Baker 27cb16ffe4 Merge pull request #4379 from matrix-org/renovate/matrix-org
Update dependency matrix-widget-api to v1.9.0
2024-08-30 17:05:06 +01:00
renovate[bot] 9be0b3e701 Update dependency matrix-widget-api to v1.9.0 2024-08-30 15:54:06 +00:00
Johannes Marbach 05ba27f36b Change imports for Node.js compatibility (#4377)
* Change imports for Node.js compatibility

Fixes: #4287
Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>

* Run prettier

* Run prettier (again)

* Add comment

* Update babel.config.cjs

---------

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2024-08-29 15:56:32 +00:00
David Baker e6acfdf275 Rename merge release notes script to cjs (#4361)
and update the corresponding workflow file
2024-08-28 10:40:16 +00:00
RiotRobot 2a6612c73a Merge branch 'master' into develop 2024-08-27 12:38:13 +00:00
RiotRobot 2f8b05b0da v34.4.0 2024-08-27 12:37:46 +00:00
Hugh Nimmo-Smith fe984ede6e Use prefixed loggers for MatrixRTC (#4378) 2024-08-23 19:04:22 +00:00
Richard van der Hoff 3f74b9a0cc Deprecate unused CryptoCallbacks (#4376) 2024-08-23 14:00:27 +00:00
David Baker 802b996b10 Set web team as codeowner for the scripts dir (#4354)
The scripts in here are used in the release, and from the develop
branch too (because it's the main branch and github actions does this)
so it's critical for the release process.
2024-08-23 12:46:27 +00:00
renovate[bot] 8d44f9d665 Update dependency eslint-plugin-unicorn to v55 (#4371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 12:32:26 +00:00
renovate[bot] a72a1b294a Update dependency eslint-plugin-jsdoc to v50 (#4370)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 11:22:35 +00:00
renovate[bot] ab5f32f984 Update typedoc (#4367)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 10:46:01 +00:00
renovate[bot] 6a21d812ab Update typescript-eslint monorepo to v7.18.0 (#4368)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 10:41:47 +00:00
Richard van der Hoff ee94e93354 set entrypoints to use ./lib rather than ./src (#4357)
Currently, we replace the entrypoints in package.json during the release
cycle. I think. historically, this was done to make matrix-react-sdk and
element-web development easier, but neither of those projects actually use
these entrypoints (instead they import from `src`).

Accordingly, I think the switcheroo is unnecessary; furthermore it causes a
whole bunch of confusion by making the development environment different from
the release environment, and it complicates our CI and release process.

In short, the switcheroo has to die.
2024-08-21 18:31:05 +00:00
renovate[bot] 31c4786a96 Update dependency fetch-mock to v11 (#4369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 16:14:39 +00:00
renovate[bot] 17b6e59819 Update shogo82148/actions-upload-release-asset digest to 6d4fd50 (#4366)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 15:47:42 +00:00
renovate[bot] 42510022a1 Update dependency typescript to v5.5.4 (#4365)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 15:08:24 +00:00
renovate[bot] 5f0978ac3f Update babel monorepo (#4363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 14:41:16 +00:00
renovate[bot] cd6787e0ac Update dependency @types/node to v18.19.44 (#4364)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 14:22:08 +00:00
renovate[bot] 03baa3e358 Update all non-major dependencies (#4362)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-21 13:51:57 +00:00
RiotRobot 658563e2a7 v34.4.0-rc.1 2024-08-21 12:56:47 +00:00
David Baker 26d3033b17 Merge pull request #4358 from matrix-org/backport-4356-to-staging
[Backport staging] Rename slowReporter to reflect that it is CommonJS
2024-08-21 13:01:49 +01:00
David Baker a4bd7dc7d7 Merge branch 'staging' into backport-4356-to-staging 2024-08-21 12:53:16 +01:00
David Baker 1f48544b38 Merge pull request #4359 from matrix-org/dbkr/backport_4355
Move `type:module` declaration into package.json. (Backport 4355)
2024-08-21 12:46:40 +01:00
Richard van der Hoff 7ef4062f59 Move type:module declaration into package.json. (#4355)
* Rename `switch_package_to_release.js` to `.cjs`

Slightly surprisingly, the symlink is enough to make `node
switch_package_to_release.js` work.

* Rename .eslintrc.js to .cjs

Again, declare this as commonjs

* Move `type:module` declaration into package.json.

matrix-js-sdk is built into ECMAScript modules, and we should declare it as
such. See https://nodejs.org/api/packages.html#type. Failure to do so causes
problems for javascript projects attempting to build against matrix-js-sdk: see https://github.com/matrix-org/matrix-js-sdk/issues/4347.

Previously, we did this as part of the package.json switcheroo, but that is
unnecessarily fragile.

matrix-react-sdk, element-web, etc are unaffected by this, because they use the
typescript files directly, by importing `matrix-js-sdk/src/...`.
2024-08-21 12:04:59 +01:00
Richard van der Hoff 968bc51a35 Rename slowReporter to reflect that it is CommonJS (#4356)
Fix tests failing as a result of
https://github.com/matrix-org/matrix-js-sdk/pull/4355. This wasn't detected in
CI because the slowReporter is only enabled when building against develop.

(cherry picked from commit d413f5042e)
2024-08-21 09:39:52 +00:00
Richard van der Hoff d413f5042e Rename slowReporter to reflect that it is CommonJS (#4356)
Fix tests failing as a result of
https://github.com/matrix-org/matrix-js-sdk/pull/4355. This wasn't detected in
CI because the slowReporter is only enabled when building against develop.
2024-08-20 20:48:27 +00:00
Richard van der Hoff b8e8b14375 Move type:module declaration into package.json. (#4355)
* Rename `switch_package_to_release.js` to `.cjs`

Slightly surprisingly, the symlink is enough to make `node
switch_package_to_release.js` work.

* Rename .eslintrc.js to .cjs

Again, declare this as commonjs

* Move `type:module` declaration into package.json.

matrix-js-sdk is built into ECMAScript modules, and we should declare it as
such. See https://nodejs.org/api/packages.html#type. Failure to do so causes
problems for javascript projects attempting to build against matrix-js-sdk: see https://github.com/matrix-org/matrix-js-sdk/issues/4347.

Previously, we did this as part of the package.json switcheroo, but that is
unnecessarily fragile.

matrix-react-sdk, element-web, etc are unaffected by this, because they use the
typescript files directly, by importing `matrix-js-sdk/src/...`.
2024-08-20 16:23:03 +00:00
RiotRobot 43e58871de v34.4.0-rc.0 2024-08-20 13:40:49 +00:00
Richard van der Hoff 2544c14032 Unrevert prerelease fix, and fix release error (#4353)
* Reapply "Add "type" = "module" to ensure it is present (#4350)" (#4352)

This reverts commit 8214fd7156.

* Mark prettier config file as CommonJS

I *think* this will fix a problem with the release process in which we saw an
error:

```
Error:  Invalid configuration for file "/home/runner/work/matrix-js-sdk/matrix-js-sdk/package.json":
Error:  module is not defined in ES module scope
Error:  This file is being treated as an ES module because it has a '.js' file extension and '/home/runner/work/matrix-js-sdk/matrix-js-sdk/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
```
2024-08-20 13:16:44 +00:00
RiotRobot 8d19782c57 Merge branch 'master' into develop 2024-08-20 11:30:12 +00:00
RiotRobot 340bbe1a8f v34.3.1 2024-08-20 11:29:48 +00:00
David Baker 8214fd7156 Revert "Add "type" = "module" to ensure it is present (#4350)" (#4352)
This reverts commit d6080398db.
2024-08-20 11:10:15 +00:00
David Baker a0efed8b88 Merge commit from fork
Detect cycles when looking for predecessor rooms
2024-08-20 11:27:28 +01:00
Hugh Nimmo-Smith c408c0d1d5 Retry event decryption failures on first failure (#4346)
* Retry event decryption failures on first failure

* Suggestion from code review

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

---------

Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
2024-08-19 13:35:45 +00:00
BLCK d6080398db Add "type" = "module" to ensure it is present (#4350) 2024-08-15 14:50:05 +00:00
Hugh Nimmo-Smith 467908703b Don't attempt to adjust for clock skews when calculating group call membership expiry (#4340)
* Use origin server timestamp for calculating group call membership expiry

* Fix tests

* Docs

* Refactor comments to reflect that the logic hasn't changed

* Make comment maintainable

* Fix up merge

* Fix test
2024-08-15 09:11:06 +00:00
Hugh Nimmo-Smith 87eddaf51a Handle MatrixRTC encryption keys arriving out of order (#4345)
* Handle MatrixRTC encryption keys arriving out of order

* Apply suggestions from code review

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

* Suggestion from code review

---------

Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
2024-08-15 07:58:36 +00:00
Hugh Nimmo-Smith c65ef03567 Resend MatrixRTC encryption keys if a membership has changed (#4343)
* Resend MatrixRTC encryption keys if a membership has changed

* JSDoc

* Update src/matrixrtc/MatrixRTCSession.ts

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

* Add note about using Set. symmetricDifference() when available

* Always store latest fingerprints

Should reduce unnecessary retransmits

* Refactor

---------

Co-authored-by: Andrew Ferrazzutti <andrewf@element.io>
2024-08-14 14:08:52 +00:00
RiotRobot 78cbf7cd28 Resetting package fields for development 2024-08-13 12:06:18 +00:00
RiotRobot dc7c1a4fef Merge branch 'master' into develop 2024-08-13 12:06:17 +00:00
RiotRobot 1ae0c2f3ee v34.3.0 2024-08-13 12:05:45 +00:00
Hugh Nimmo-Smith affaa95fb4 delayBeforeuse => delayBeforeUse for consistency (#4344) 2024-08-09 14:34:13 +00:00
Andrew Ferrazzutti 9176d3a671 Use non-legacy calls if any are found (#4337)
Akin to how legacy call events should be sent in rooms where there is
any ongoing legacy call, send non-legacy events in rooms where there are
only non-legacy calls; else fall back to the config preference.
2024-08-07 15:44:44 +00:00
RiotRobot de50129a53 v34.3.0-rc.1 2024-08-06 12:26:55 +00:00
Michael Telatynski 5568dfdd41 Move olm to dependencies as its types are needed downstream
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-08-06 13:25:55 +01:00
RiotRobot 39216d44ed v34.3.0-rc.0 2024-08-06 12:03:46 +00:00
Michael Telatynski 8c3b249567 Re-add olm dependency which is needed for types
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-08-06 12:00:30 +01:00
Michael Telatynski b8e40ad2a8 Resetting package fields for development 2024-08-06 11:46:18 +01:00
Michael Telatynski 4e2831764d Merge branch 'master' into develop 2024-08-06 11:46:05 +01:00
Michael Telatynski 09780672aa Fix release-gitflow.yml node version 2024-08-06 11:42:35 +01:00
Andrew Ferrazzutti 0fe53876ec Bump matrix-widget-api (#4336) 2024-08-02 12:10:24 +00:00
Michael Telatynski dfec3dc33c Make code tsc es2022 compliant (#4335)
* Remove redundant global.d.ts definitions

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

* Remove roomId overload

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

* Update base.ts

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2024-08-01 15:16:59 +00:00
Andrew Ferrazzutti fbdd78b428 Also check for MSC3757 for session state keys (#4334)
Do not prefix the non-legacy session membership state keys with an
underscore for rooms using MSC3757 (in addtion to MSC3779).
2024-08-01 14:57:29 +00:00
Andrew Ferrazzutti e10c362ef0 Support MSC4157: delayed events via Widget API (#4311) 2024-08-01 14:17:52 +00:00
David Baker 89a9a7fa38 Fix hashed ID server lookups with no Olm (#4333)
* Fix hashed ID server lookups with no Olm

It used the hash function from Olm (presumably to work cross-platform)
but subtle crypto is available on node nowadays so we can just use
that.

Refactor existing code that did this out to a common function, add
tests.

* Test the code when crypto is available

* Test case of no crypto available

* Move digest file to src to get it out of the way of the olm / e2e stuff

* Fix import

* Fix error string & doc

* subtle crypto, not webcrypto

* Extract the base64 part

* Fix test

* Move test file too

* Add more doc

* Fix imports
2024-08-01 10:55:23 +00:00
Andrew Ferrazzutti 687d08dc9d Support MSC4140: Delayed events (#4294)
and use them for more reliable MatrixRTC session membership events.

Also implement "parent" delayed events, which were in a previous version
of the MSC and may be reintroduced or be part of a new MSC later.

NOTE: Still missing is support for sending encrypted delayed events.
2024-07-30 12:43:25 +00:00
RiotRobot 7f91db83d0 v34.2.0 2024-07-30 12:37:58 +00:00
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
David Baker e0ef467d7d break instead of return 2024-07-29 13:55:08 +01: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
David Baker 79299891fd Detect cycles when looking for predecessor rooms 2024-07-29 11:14:58 +01: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
RiotRobot 5feab37166 v34.2.0-rc.0 2024-07-23 11:58:56 +00:00
Michael Telatynski 1a02835ab2 Merge branch 'develop' of github.com:matrix-org/matrix-js-sdk into staging 2024-07-23 12:51:48 +01: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
225 changed files with 4775 additions and 3315 deletions
View File
+1
View File
@@ -2,6 +2,7 @@
/.github/workflows/** @matrix-org/element-web-team
/package.json @matrix-org/element-web-team
/yarn.lock @matrix-org/element-web-team
/scripts/** @matrix-org/element-web-team
/src/webrtc @matrix-org/element-call-reviewers
/src/matrixrtc @matrix-org/element-call-reviewers
/spec/*/webrtc @matrix-org/element-call-reviewers
@@ -22,7 +22,7 @@ runs:
- name: Upload tarball signature
if: ${{ inputs.upload-url }}
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
uses: shogo82148/actions-upload-release-asset@6d4fd50e333ee797e83ae380e6bc55b048baec41 # v1
with:
upload_url: ${{ inputs.upload-url }}
asset_path: ${{ env.VERSION }}.tar.gz.asc
@@ -29,13 +29,13 @@ runs:
- name: Upload asset signatures
if: inputs.gpg-fingerprint
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
uses: shogo82148/actions-upload-release-asset@6d4fd50e333ee797e83ae380e6bc55b048baec41 # v1
with:
upload_url: ${{ inputs.upload-url }}
asset_path: ${{ inputs.asset-path }}.asc
- name: Upload assets
uses: shogo82148/actions-upload-release-asset@8f032eff0255912cc9c8455797fd6d72f25c7ab7 # v1
uses: shogo82148/actions-upload-release-asset@6d4fd50e333ee797e83ae380e6bc55b048baec41 # v1
with:
upload_url: ${{ inputs.upload-url }}
asset_path: ${{ inputs.asset-path }}
@@ -56,7 +56,7 @@ jobs:
script: |
const { RELEASE_ID: releaseId, DEPENDENCY, VERSION } = process.env;
const { owner, repo } = context.repo;
const script = require("./.action-repo/scripts/release/merge-release-notes.js");
const script = require("./.action-repo/scripts/release/merge-release-notes.cjs");
let deps = [];
if (DEPENDENCY.includes("/")) {
+1
View File
@@ -34,6 +34,7 @@ jobs:
- uses: actions/setup-node@v4
with:
cache: "yarn"
node-version-file: package.json
- name: Install Deps
run: "yarn install --frozen-lockfile"
+11 -1
View File
@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
specs: [integ, unit]
node: ["lts/*", 21, 22]
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'
View File
+43
View File
@@ -1,3 +1,46 @@
Changes in [34.4.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.4.0) (2024-08-27)
==================================================================================================
## ✨ Features
* Use non-legacy calls if any are found ([#4337](https://github.com/matrix-org/matrix-js-sdk/pull/4337)). Contributed by @AndrewFerr.
## 🐛 Bug Fixes
* Retry event decryption failures on first failure ([#4346](https://github.com/matrix-org/matrix-js-sdk/pull/4346)). Contributed by @hughns.
* Ensure "type" = "module" ES declaration in pre-release.sh ([#4350](https://github.com/matrix-org/matrix-js-sdk/pull/4350)). Contributed by @BLCK-B.
* Handle MatrixRTC encryption keys arriving out of order ([#4345](https://github.com/matrix-org/matrix-js-sdk/pull/4345)). Contributed by @hughns.
* Resend MatrixRTC encryption keys if a membership has changed ([#4343](https://github.com/matrix-org/matrix-js-sdk/pull/4343)). Contributed by @hughns.
Changes in [34.3.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.3.1) (2024-08-20)
==================================================================================================
# Security
- Fixes for [CVE-2024-42369](https://nvd.nist.gov/vuln/detail/CVE-2024-42369) / [GHSA-vhr5-g3pm-49fm](https://github.com/matrix-org/matrix-js-sdk/security/advisories/GHSA-vhr5-g3pm-49fm).
Changes in [34.3.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.3.0) (2024-08-13)
==================================================================================================
## ✨ Features
* Bump matrix-widget-api ([#4336](https://github.com/matrix-org/matrix-js-sdk/pull/4336)). Contributed by @AndrewFerr.
* Also check for MSC3757 for session state keys ([#4334](https://github.com/matrix-org/matrix-js-sdk/pull/4334)). Contributed by @AndrewFerr.
* Support Futures via widgets ([#4311](https://github.com/matrix-org/matrix-js-sdk/pull/4311)). Contributed by @AndrewFerr.
* Support MSC4140: Delayed events (Futures) ([#4294](https://github.com/matrix-org/matrix-js-sdk/pull/4294)). Contributed by @AndrewFerr.
* Handle late-arriving `m.room_key.withheld` messages ([#4310](https://github.com/matrix-org/matrix-js-sdk/pull/4310)). Contributed by @richvdh.
* Be specific about what is considered a MSC4143 call member event. ([#4328](https://github.com/matrix-org/matrix-js-sdk/pull/4328)). Contributed by @toger5.
* Add index.ts for matrixrtc module ([#4314](https://github.com/matrix-org/matrix-js-sdk/pull/4314)). Contributed by @toger5.
## 🐛 Bug Fixes
* Fix hashed ID server lookups with no Olm ([#4333](https://github.com/matrix-org/matrix-js-sdk/pull/4333)). Contributed by @dbkr.
Changes in [34.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.2.0) (2024-07-30)
==================================================================================================
## 🐛 Bug Fixes
* Element-R: detect "withheld key" UTD errors, and mark them as such ([#4302](https://github.com/matrix-org/matrix-js-sdk/pull/4302)). Contributed by @richvdh.
Changes in [34.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.1.0) (2024-07-16)
==================================================================================================
## ✨ 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:
+10 -1
View File
@@ -14,7 +14,16 @@ module.exports = {
modules: process.env.NODE_ENV === "test" ? "commonjs" : false,
},
],
"@babel/preset-typescript",
[
"@babel/preset-typescript",
{
// When using the transpiled javascript in `lib`, Node.js requires `.js` extensions on any `import`
// specifiers. However, Jest uses the TS source (via babel) and fails to resolve the `.js` names.
// To resolve this,we use the `.ts` names in the source, and rewrite the `import` specifiers to use
// `.js` during transpilation, *except* when we are targetting Jest.
rewriteImportExtensions: process.env.NODE_ENV !== "test",
},
],
],
plugins: [
"@babel/plugin-transform-numeric-separator",
+1 -1
View File
@@ -39,7 +39,7 @@ if (env["GITHUB_ACTIONS"] !== undefined) {
// if we're running against the develop branch, also enable the slow test reporter
if (env["GITHUB_REF"] == "refs/heads/develop") {
reporters.push("<rootDir>/spec/slowReporter.js");
reporters.push("<rootDir>/spec/slowReporter.cjs");
}
config.reporters = reporters;
}
+11 -15
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "34.1.0",
"version": "34.5.0-rc.0",
"description": "Matrix Client-Server SDK for Javascript",
"engines": {
"node": ">=20.0.0"
@@ -31,13 +31,10 @@
"keywords": [
"matrix-org"
],
"type": "module",
"main": "./lib/index.js",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.ts",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_browser": "./lib/browser-index.js",
"matrix_lib_typings": "./lib/index.d.ts",
"typings": "./lib/index.d.ts",
"author": "matrix.org",
"license": "Apache-2.0",
"files": [
@@ -53,7 +50,7 @@
],
"dependencies": {
"@babel/runtime": "^7.12.5",
"@matrix-org/matrix-sdk-crypto-wasm": "^6.0.0",
"@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
"@matrix-org/olm": "3.2.15",
"another-json": "^0.2.0",
"bs58": "^6.0.0",
@@ -61,7 +58,7 @@
"jwt-decode": "^4.0.0",
"loglevel": "^1.7.1",
"matrix-events-sdk": "0.0.1",
"matrix-widget-api": "^1.6.0",
"matrix-widget-api": "^1.8.2",
"oidc-client-ts": "^3.0.1",
"p-retry": "4",
"sdp-transform": "^2.14.1",
@@ -101,12 +98,12 @@
"eslint-import-resolver-typescript": "^3.5.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest": "^28.0.0",
"eslint-plugin-jsdoc": "^48.0.0",
"eslint-plugin-jsdoc": "^50.0.0",
"eslint-plugin-matrix-org": "^1.0.0",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^54.0.0",
"eslint-plugin-unicorn": "^55.0.0",
"fake-indexeddb": "^5.0.2",
"fetch-mock": "10.0.7",
"fetch-mock": "11.1.1",
"fetch-mock-jest": "^1.5.1",
"husky": "^9.0.0",
"jest": "^29.0.0",
@@ -117,8 +114,8 @@
"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.26.0",
"typedoc-plugin-coverage": "^3.0.0",
@@ -130,6 +127,5 @@
"outputDirectory": "coverage",
"outputName": "jest-sonar-report.xml",
"relativePaths": true
},
"typings": "./lib/index.d.ts"
}
}
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env node
const fsProm = require("fs/promises");
const PKGJSON = "package.json";
async function main() {
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, "utf8"));
for (const field of ["main", "typings"]) {
if (pkgJson["matrix_lib_" + field] !== undefined) {
pkgJson[field] = pkgJson["matrix_lib_" + field];
}
}
await fsProm.writeFile(PKGJSON, JSON.stringify(pkgJson, null, 2));
}
main();
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env node
const fsProm = require("fs/promises");
const PKGJSON = "package.json";
async function main() {
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, "utf8"));
for (const field of ["main", "typings"]) {
if (pkgJson["matrix_lib_" + field] !== undefined) {
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));
}
main();
+1
View File
@@ -0,0 +1 @@
switch_package_to_release.cjs
+78 -2
View File
@@ -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
@@ -268,7 +268,8 @@ describe("MSC4108SignInWithQR", () => {
it("should abort if device doesn't come up by timeout", async () => {
jest.spyOn(global, "setTimeout").mockImplementation((fn) => {
(<Function>fn)();
return -1;
// TODO: mock timers properly
return -1 as any;
});
jest.spyOn(Date, "now").mockImplementation(() => {
return 12345678 + mocked(setTimeout).mock.calls.length * 1000;
@@ -320,7 +321,8 @@ describe("MSC4108SignInWithQR", () => {
it("should not send secrets if user cancels", async () => {
jest.spyOn(global, "setTimeout").mockImplementation((fn) => {
(<Function>fn)();
return -1;
// TODO: mock timers properly
return -1 as any;
});
await Promise.all([ourLogin.negotiateProtocols(), opponentLogin.negotiateProtocols()]);
+40
View File
@@ -0,0 +1,40 @@
/*
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.
*/
import { encodeUnpaddedBase64Url } from "../../src";
import { sha256 } from "../../src/digest";
describe("sha256", () => {
it("should hash a string", async () => {
const hash = await sha256("test");
expect(encodeUnpaddedBase64Url(hash)).toBe("n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg");
});
it("should hash a string with emoji", async () => {
const hash = await sha256("test 🍱");
expect(encodeUnpaddedBase64Url(hash)).toBe("X2aDNrrwfq3nCTOl90R9qg9ynxhHnSzsMqtrdYX-SGw");
});
it("throws if webcrypto is not available", async () => {
const oldCrypto = global.crypto;
try {
global.crypto = {} as any;
await expect(sha256("test")).rejects.toThrow();
} finally {
global.crypto = oldCrypto;
}
});
});
+236 -3
View File
@@ -32,7 +32,7 @@ import {
IOpenIDCredentials,
} from "matrix-widget-api";
import { createRoomWidgetClient, MsgType } from "../../src/matrix";
import { createRoomWidgetClient, MsgType, UpdateDelayedEventAction } from "../../src/matrix";
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "../../src/client";
import { SyncState } from "../../src/sync";
import { ICapabilities } from "../../src/embedded";
@@ -59,8 +59,26 @@ class MockWidgetApi extends EventEmitter {
public requestCapabilityToReceiveState = jest.fn();
public requestCapabilityToSendToDevice = jest.fn();
public requestCapabilityToReceiveToDevice = jest.fn();
public sendRoomEvent = jest.fn(() => ({ event_id: `$${Math.random()}` }));
public sendStateEvent = jest.fn();
public sendRoomEvent = jest.fn(
(eventType: string, content: unknown, roomId?: string, delay?: number, parentDelayId?: string) =>
delay === undefined && parentDelayId === undefined
? { event_id: `$${Math.random()}` }
: { delay_id: `id-${Math.random()}` },
);
public sendStateEvent = jest.fn(
(
eventType: string,
stateKey: string,
content: unknown,
roomId?: string,
delay?: number,
parentDelayId?: string,
) =>
delay === undefined && parentDelayId === undefined
? { event_id: `$${Math.random()}` }
: { delay_id: `id-${Math.random()}` },
);
public updateDelayedEvent = jest.fn();
public sendToDevice = jest.fn();
public requestOpenIDConnectToken = jest.fn(() => {
return testOIDCToken;
@@ -125,6 +143,17 @@ describe("RoomWidgetClient", () => {
);
});
it("send handles wrong field in response", async () => {
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
widgetApi.sendRoomEvent.mockResolvedValueOnce({
room_id: "!1:example.org",
delay_id: `id-${Math.random}`,
});
await expect(
client.sendEvent("!1:example.org", "org.matrix.rageshake_request", { request_id: 123 }),
).rejects.toThrow();
});
it("receives", async () => {
const event = new MatrixEvent({
type: "org.matrix.rageshake_request",
@@ -160,6 +189,199 @@ describe("RoomWidgetClient", () => {
});
});
describe("delayed events", () => {
describe("when supported", () => {
const doesServerSupportUnstableFeatureMock = jest.fn((feature) =>
Promise.resolve(feature === "org.matrix.msc4140"),
);
beforeAll(() => {
MatrixClient.prototype.doesServerSupportUnstableFeature = doesServerSupportUnstableFeatureMock;
});
afterAll(() => {
doesServerSupportUnstableFeatureMock.mockReset();
});
it("sends delayed message events", async () => {
await makeClient({ sendDelayedEvents: true, sendEvent: ["org.matrix.rageshake_request"] });
expect(widgetApi.requestCapability).toHaveBeenCalledWith(MatrixCapabilities.MSC4157SendDelayedEvent);
await client._unstable_sendDelayedEvent(
"!1:example.org",
{ delay: 2000 },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
);
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
"org.matrix.rageshake_request",
{ request_id: 123 },
"!1:example.org",
2000,
undefined,
);
});
it("sends child action delayed message events", async () => {
await makeClient({ sendDelayedEvents: true, sendEvent: ["org.matrix.rageshake_request"] });
expect(widgetApi.requestCapability).toHaveBeenCalledWith(MatrixCapabilities.MSC4157SendDelayedEvent);
const parentDelayId = `id-${Math.random()}`;
await client._unstable_sendDelayedEvent(
"!1:example.org",
{ parent_delay_id: parentDelayId },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
);
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
"org.matrix.rageshake_request",
{ request_id: 123 },
"!1:example.org",
undefined,
parentDelayId,
);
});
it("sends delayed state events", async () => {
await makeClient({
sendDelayedEvents: true,
sendState: [{ eventType: "org.example.foo", stateKey: "bar" }],
});
expect(widgetApi.requestCapability).toHaveBeenCalledWith(MatrixCapabilities.MSC4157SendDelayedEvent);
await client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ delay: 2000 },
"org.example.foo",
{ hello: "world" },
"bar",
);
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
"org.example.foo",
"bar",
{ hello: "world" },
"!1:example.org",
2000,
undefined,
);
});
it("sends child action delayed state events", async () => {
await makeClient({
sendDelayedEvents: true,
sendState: [{ eventType: "org.example.foo", stateKey: "bar" }],
});
expect(widgetApi.requestCapability).toHaveBeenCalledWith(MatrixCapabilities.MSC4157SendDelayedEvent);
const parentDelayId = `fg-${Math.random()}`;
await client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ parent_delay_id: parentDelayId },
"org.example.foo",
{ hello: "world" },
"bar",
);
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
"org.example.foo",
"bar",
{ hello: "world" },
"!1:example.org",
undefined,
parentDelayId,
);
});
it("send delayed message events handles wrong field in response", async () => {
await makeClient({ sendDelayedEvents: true, sendEvent: ["org.matrix.rageshake_request"] });
widgetApi.sendRoomEvent.mockResolvedValueOnce({
room_id: "!1:example.org",
event_id: `$${Math.random()}`,
});
await expect(
client._unstable_sendDelayedEvent(
"!1:example.org",
{ delay: 2000 },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
),
).rejects.toThrow();
});
it("send delayed state events handles wrong field in response", async () => {
await makeClient({
sendDelayedEvents: true,
sendState: [{ eventType: "org.example.foo", stateKey: "bar" }],
});
widgetApi.sendStateEvent.mockResolvedValueOnce({
room_id: "!1:example.org",
event_id: `$${Math.random()}`,
});
await expect(
client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ delay: 2000 },
"org.example.foo",
{ hello: "world" },
"bar",
),
).rejects.toThrow();
});
it("updates delayed events", async () => {
await makeClient({ updateDelayedEvents: true, sendEvent: ["org.matrix.rageshake_request"] });
expect(widgetApi.requestCapability).toHaveBeenCalledWith(MatrixCapabilities.MSC4157UpdateDelayedEvent);
for (const action of [
UpdateDelayedEventAction.Cancel,
UpdateDelayedEventAction.Restart,
UpdateDelayedEventAction.Send,
]) {
await client._unstable_updateDelayedEvent("id", action);
expect(widgetApi.updateDelayedEvent).toHaveBeenCalledWith("id", action);
}
});
});
describe("when unsupported", () => {
it("fails to send delayed message events", async () => {
await makeClient({ sendEvent: ["org.matrix.rageshake_request"] });
await expect(
client._unstable_sendDelayedEvent(
"!1:example.org",
{ delay: 2000 },
null,
"org.matrix.rageshake_request",
{ request_id: 123 },
),
).rejects.toThrow("Server does not support");
});
it("fails to send delayed state events", async () => {
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
await expect(
client._unstable_sendDelayedStateEvent(
"!1:example.org",
{ delay: 2000 },
"org.example.foo",
{ hello: "world" },
"bar",
),
).rejects.toThrow("Server does not support");
});
it("fails to update delayed state events", async () => {
await makeClient({});
for (const action of [
UpdateDelayedEventAction.Cancel,
UpdateDelayedEventAction.Restart,
UpdateDelayedEventAction.Send,
]) {
await expect(client._unstable_updateDelayedEvent("id", action)).rejects.toThrow(
"Server does not support",
);
}
});
});
});
describe("initialization", () => {
it("requests permissions for specific message types", async () => {
await makeClient({ sendMessage: [MsgType.Text], receiveMessage: [MsgType.Text] });
@@ -211,6 +433,17 @@ describe("RoomWidgetClient", () => {
);
});
it("send handles incorrect response", async () => {
await makeClient({ sendState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
widgetApi.sendStateEvent.mockResolvedValueOnce({
room_id: "!1:example.org",
delay_id: `id-${Math.random}`,
});
await expect(
client.sendStateEvent("!1:example.org", "org.example.foo", { hello: "world" }, "bar"),
).rejects.toThrow();
});
it("receives", async () => {
await makeClient({ receiveState: [{ eventType: "org.example.foo", stateKey: "bar" }] });
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
+369 -3
View File
@@ -57,6 +57,7 @@ import {
Room,
RuleId,
TweakName,
UpdateDelayedEventAction,
} from "../../src";
import { supportsMatrixCall } from "../../src/webrtc/call";
import { makeBeaconEvent } from "../test-utils/beacon";
@@ -97,7 +98,7 @@ type HttpLookup = {
method: string;
path: string;
prefix?: string;
data?: Record<string, any>;
data?: Record<string, any> | Record<string, any>[];
error?: object;
expectBody?: Record<string, any>;
expectQueryParams?: QueryDict;
@@ -298,7 +299,9 @@ describe("MatrixClient", function () {
...(opts || {}),
});
// FIXME: We shouldn't be yanking http like this.
client.http = (["authedRequest", "getContentUri", "request", "uploadContent"] as const).reduce((r, k) => {
client.http = (
["authedRequest", "getContentUri", "request", "uploadContent", "idServerRequest"] as const
).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixHttpApi<any>);
@@ -704,6 +707,328 @@ describe("MatrixClient", function () {
});
});
describe("_unstable_sendDelayedEvent", () => {
const unstableMSC4140Prefix = `${ClientPrefix.Unstable}/org.matrix.msc4140`;
const roomId = "!room:example.org";
const body = "This is the body";
const content = { body, msgtype: MsgType.Text } satisfies RoomMessageEventContent;
const timeoutDelayOpts = { delay: 2000 };
const realTimeoutDelayOpts = { "org.matrix.msc4140.delay": 2000 };
beforeEach(() => {
unstableFeatures["org.matrix.msc4140"] = true;
});
it("throws when unsupported by server", async () => {
unstableFeatures["org.matrix.msc4140"] = false;
const errorMessage = "Server does not support";
await expect(
client._unstable_sendDelayedEvent(
roomId,
timeoutDelayOpts,
null,
EventType.RoomMessage,
{ ...content },
client.makeTxnId(),
),
).rejects.toThrow(errorMessage);
await expect(
client._unstable_sendDelayedStateEvent(roomId, timeoutDelayOpts, EventType.RoomTopic, {
topic: "topic",
}),
).rejects.toThrow(errorMessage);
await expect(client._unstable_getDelayedEvents()).rejects.toThrow(errorMessage);
await expect(
client._unstable_updateDelayedEvent("anyDelayId", UpdateDelayedEventAction.Send),
).rejects.toThrow(errorMessage);
});
it("works with null threadId", async () => {
httpLookups = [];
const timeoutDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${timeoutDelayTxnId}`,
expectQueryParams: realTimeoutDelayOpts,
data: { delay_id: "id1" },
expectBody: content,
});
const { delay_id: timeoutDelayId } = await client._unstable_sendDelayedEvent(
roomId,
timeoutDelayOpts,
null,
EventType.RoomMessage,
{ ...content },
timeoutDelayTxnId,
);
const actionDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${actionDelayTxnId}`,
expectQueryParams: { "org.matrix.msc4140.parent_delay_id": timeoutDelayId },
data: { delay_id: "id2" },
expectBody: content,
});
await client._unstable_sendDelayedEvent(
roomId,
{ parent_delay_id: timeoutDelayId },
null,
EventType.RoomMessage,
{ ...content },
actionDelayTxnId,
);
});
it("works with non-null threadId", async () => {
httpLookups = [];
const threadId = "$threadId:server";
const expectBody = {
...content,
"m.relates_to": {
event_id: threadId,
is_falling_back: true,
rel_type: "m.thread",
},
};
const timeoutDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${timeoutDelayTxnId}`,
expectQueryParams: realTimeoutDelayOpts,
data: { delay_id: "id1" },
expectBody,
});
const { delay_id: timeoutDelayId } = await client._unstable_sendDelayedEvent(
roomId,
timeoutDelayOpts,
threadId,
EventType.RoomMessage,
{ ...content },
timeoutDelayTxnId,
);
const actionDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${actionDelayTxnId}`,
expectQueryParams: { "org.matrix.msc4140.parent_delay_id": timeoutDelayId },
data: { delay_id: "id2" },
expectBody,
});
await client._unstable_sendDelayedEvent(
roomId,
{ parent_delay_id: timeoutDelayId },
threadId,
EventType.RoomMessage,
{ ...content },
actionDelayTxnId,
);
});
it("should add thread relation if threadId is passed and the relation is missing", async () => {
httpLookups = [];
const threadId = "$threadId:server";
const expectBody = {
...content,
"m.relates_to": {
"m.in_reply_to": {
event_id: threadId,
},
"event_id": threadId,
"is_falling_back": true,
"rel_type": "m.thread",
},
};
const room = new Room(roomId, client, userId);
mocked(store.getRoom).mockReturnValue(room);
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
const timeoutDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${timeoutDelayTxnId}`,
expectQueryParams: realTimeoutDelayOpts,
data: { delay_id: "id1" },
expectBody,
});
const { delay_id: timeoutDelayId } = await client._unstable_sendDelayedEvent(
roomId,
timeoutDelayOpts,
threadId,
EventType.RoomMessage,
{ ...content },
timeoutDelayTxnId,
);
const actionDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${actionDelayTxnId}`,
expectQueryParams: { "org.matrix.msc4140.parent_delay_id": timeoutDelayId },
data: { delay_id: "id2" },
expectBody,
});
await client._unstable_sendDelayedEvent(
roomId,
{ parent_delay_id: timeoutDelayId },
threadId,
EventType.RoomMessage,
{ ...content },
actionDelayTxnId,
);
});
it("should add thread relation if threadId is passed and the relation is missing with reply", async () => {
httpLookups = [];
const threadId = "$threadId:server";
const content = {
body,
"msgtype": MsgType.Text,
"m.relates_to": {
"m.in_reply_to": {
event_id: "$other:event",
},
},
} satisfies RoomMessageEventContent;
const expectBody = {
...content,
"m.relates_to": {
"m.in_reply_to": {
event_id: "$other:event",
},
"event_id": threadId,
"is_falling_back": false,
"rel_type": "m.thread",
},
};
const room = new Room(roomId, client, userId);
mocked(store.getRoom).mockReturnValue(room);
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
const timeoutDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${timeoutDelayTxnId}`,
expectQueryParams: realTimeoutDelayOpts,
data: { delay_id: "id1" },
expectBody,
});
const { delay_id: timeoutDelayId } = await client._unstable_sendDelayedEvent(
roomId,
timeoutDelayOpts,
threadId,
EventType.RoomMessage,
{ ...content },
timeoutDelayTxnId,
);
const actionDelayTxnId = client.makeTxnId();
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${actionDelayTxnId}`,
expectQueryParams: { "org.matrix.msc4140.parent_delay_id": timeoutDelayId },
data: { delay_id: "id2" },
expectBody,
});
await client._unstable_sendDelayedEvent(
roomId,
{ parent_delay_id: timeoutDelayId },
threadId,
EventType.RoomMessage,
{ ...content },
actionDelayTxnId,
);
});
it("can send a delayed state event", async () => {
httpLookups = [];
const content = { topic: "The year 2000" };
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/state/m.room.topic/`,
expectQueryParams: realTimeoutDelayOpts,
data: { delay_id: "id1" },
expectBody: content,
});
const { delay_id: timeoutDelayId } = await client._unstable_sendDelayedStateEvent(
roomId,
timeoutDelayOpts,
EventType.RoomTopic,
{ ...content },
);
httpLookups.push({
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/state/m.room.topic/`,
expectQueryParams: { "org.matrix.msc4140.parent_delay_id": timeoutDelayId },
data: { delay_id: "id2" },
expectBody: content,
});
await client._unstable_sendDelayedStateEvent(
roomId,
{ parent_delay_id: timeoutDelayId },
EventType.RoomTopic,
{ ...content },
);
});
it("can look up delayed events", async () => {
httpLookups = [
{
method: "GET",
prefix: unstableMSC4140Prefix,
path: "/delayed_events",
data: [],
},
];
await client._unstable_getDelayedEvents();
});
it("can update delayed events", async () => {
const delayId = "id";
const action = UpdateDelayedEventAction.Restart;
httpLookups = [
{
method: "POST",
prefix: unstableMSC4140Prefix,
path: `/delayed_events/${encodeURIComponent(delayId)}`,
data: {
action,
},
},
];
await client._unstable_updateDelayedEvent(delayId, action);
});
});
it("should create (unstable) file trees", async () => {
const userId = "@test:example.org";
const roomId = "!room:example.org";
@@ -963,7 +1288,7 @@ describe("MatrixClient", function () {
const filter = new Filter(client.credentials.userId);
const filterId = await client.getOrCreateFilter(filterName, filter);
expect(filterId).toEqual(FILTER_RESPONSE.data?.filter_id);
expect(filterId).toEqual(!Array.isArray(FILTER_RESPONSE.data) && FILTER_RESPONSE.data?.filter_id);
});
});
@@ -3035,4 +3360,45 @@ describe("MatrixClient", function () {
expect(httpLookups.length).toEqual(0);
});
});
describe("identityHashedLookup", () => {
it("should return hashed lookup results", async () => {
const ID_ACCESS_TOKEN = "hello_id_server_please_let_me_make_a_request";
client.http.idServerRequest = jest.fn().mockImplementation((method, path, params) => {
if (method === "GET" && path === "/hash_details") {
return { algorithms: ["sha256"], lookup_pepper: "carrot" };
} else if (method === "POST" && path === "/lookup") {
return {
mappings: {
"WHA-MgrrsZACDI9F8OaVagpiyiV2sjZylGHJteT4OMU": "@bob:homeserver.dummy",
},
};
}
throw new Error("Test impl doesn't know about this request");
});
const lookupResult = await client.identityHashedLookup([["bob@email.dummy", "email"]], ID_ACCESS_TOKEN);
expect(client.http.idServerRequest).toHaveBeenCalledWith(
"GET",
"/hash_details",
undefined,
"/_matrix/identity/v2",
ID_ACCESS_TOKEN,
);
expect(client.http.idServerRequest).toHaveBeenCalledWith(
"POST",
"/lookup",
{ pepper: "carrot", algorithm: "sha256", addresses: ["WHA-MgrrsZACDI9F8OaVagpiyiV2sjZylGHJteT4OMU"] },
"/_matrix/identity/v2",
ID_ACCESS_TOKEN,
);
expect(lookupResult).toHaveLength(1);
expect(lookupResult[0]).toEqual({ address: "bob@email.dummy", mxid: "@bob:homeserver.dummy" });
});
});
});
+9 -22
View File
@@ -98,20 +98,6 @@ describe("CallMembership", () => {
expect(membership.getAbsoluteExpiry()).toEqual(5000 + 1000);
});
it("considers memberships unexpired if local age low enough", () => {
const fakeEvent = makeMockEvent(1000);
fakeEvent.getLocalAge = jest.fn().mockReturnValue(3000);
const membership = new CallMembership(fakeEvent, membershipTemplate);
expect(membership.isExpired()).toEqual(false);
});
it("considers memberships expired when local age large", () => {
const fakeEvent = makeMockEvent(1000);
fakeEvent.localTimestamp = Date.now() - 6000;
const membership = new CallMembership(fakeEvent, membershipTemplate);
expect(membership.isExpired()).toEqual(true);
});
it("returns preferred foci", () => {
const fakeEvent = makeMockEvent();
const mockFocus = { type: "this_is_a_mock_focus" };
@@ -198,13 +184,6 @@ describe("CallMembership", () => {
beforeEach(() => {
// server origin timestamp for this event is 1000
fakeEvent = makeMockEvent(1000);
// our clock would have been at 2000 at the creation time (our clock at event receive time - age)
// (ie. the local clock is 1 second ahead of the servers' clocks)
fakeEvent.localTimestamp = 2000;
// for simplicity's sake, we say that the event's age is zero
fakeEvent.getLocalAge = jest.fn().mockReturnValue(0);
membership = new CallMembership(fakeEvent!, membershipTemplate);
jest.useFakeTimers();
@@ -215,6 +194,13 @@ describe("CallMembership", () => {
});
it("converts expiry time into local clock", () => {
// our clock would have been at 2000 at the creation time (our clock at event receive time - age)
// (ie. the local clock is 1 second ahead of the servers' clocks)
fakeEvent.localTimestamp = 2000;
// for simplicity's sake, we say that the event's age is zero
fakeEvent.getLocalAge = jest.fn().mockReturnValue(0);
// for sanity's sake, make sure the server-relative expiry time is what we expect
expect(membership.getAbsoluteExpiry()).toEqual(6000);
// therefore the expiry time converted to our clock should be 1 second later
@@ -223,7 +209,8 @@ describe("CallMembership", () => {
it("calculates time until expiry", () => {
jest.setSystemTime(2000);
expect(membership.getMsUntilExpiry()).toEqual(5000);
// should be using absolute expiry time
expect(membership.getMsUntilExpiry()).toEqual(4000);
});
});
});
+443 -30
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventTimeline, EventType, MatrixClient, MatrixError, MatrixEvent, Room } from "../../../src";
import { encodeBase64, EventTimeline, EventType, MatrixClient, MatrixError, MatrixEvent, Room } from "../../../src";
import { KnownMembership } from "../../../src/@types/membership";
import {
CallMembershipData,
@@ -46,6 +46,9 @@ describe("MatrixRTCSession", () => {
client = new MatrixClient({ baseUrl: "base_url" });
client.getUserId = jest.fn().mockReturnValue("@alice:example.org");
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
client.doesServerSupportUnstableFeature = jest.fn((feature) =>
Promise.resolve(feature === "org.matrix.msc4140"),
);
});
afterEach(() => {
@@ -70,14 +73,17 @@ describe("MatrixRTCSession", () => {
});
it("ignores expired memberships events", () => {
jest.useFakeTimers();
const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.expires = 1000;
expiredMembership.device_id = "EXPIRED";
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership], 10000);
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
jest.advanceTimersByTime(2000);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
jest.useRealTimers();
});
it("ignores memberships events of members not in the room", () => {
@@ -88,12 +94,15 @@ describe("MatrixRTCSession", () => {
});
it("honours created_ts", () => {
jest.useFakeTimers();
jest.setSystemTime(500);
const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.created_ts = 500;
expiredMembership.expires = 1000;
const mockRoom = makeMockRoom([expiredMembership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
expect(sess?.memberships[0].getAbsoluteExpiry()).toEqual(1500);
jest.useRealTimers();
});
it("returns empty session if no membership events are present", () => {
@@ -241,40 +250,68 @@ describe("MatrixRTCSession", () => {
foci_preferred: [mockFocus],
};
function testSession(
let sendStateEventMock: jest.Mock;
let sendDelayedStateMock: jest.Mock;
let sentStateEvent: Promise<void>;
let sentDelayedState: Promise<void>;
beforeEach(() => {
sentStateEvent = new Promise((resolve) => {
sendStateEventMock = jest.fn(resolve);
});
sentDelayedState = new Promise((resolve) => {
sendDelayedStateMock = jest.fn(() => {
resolve();
return {
delay_id: "id",
};
});
});
client.sendStateEvent = sendStateEventMock;
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
});
async function testSession(
membershipData: CallMembershipData[] | SessionMembershipData,
shouldUseLegacy: boolean,
): void {
): Promise<void> {
sess = MatrixRTCSession.roomSessionForRoom(client, makeMockRoom(membershipData));
const makeNewLegacyMembershipsMock = jest.spyOn(sess as any, "makeNewLegacyMemberships");
const makeNewMembershipMock = jest.spyOn(sess as any, "makeNewMembership");
sess.joinRoomSession([mockFocus], mockFocus, joinSessionConfig);
await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 500))]);
expect(makeNewLegacyMembershipsMock).toHaveBeenCalledTimes(shouldUseLegacy ? 1 : 0);
expect(makeNewMembershipMock).toHaveBeenCalledTimes(shouldUseLegacy ? 0 : 1);
await Promise.race([sentDelayedState, new Promise((resolve) => setTimeout(resolve, 500))]);
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(shouldUseLegacy ? 0 : 1);
}
it("uses legacy events if there are any active legacy calls", () => {
testSession([expiredLegacyMembershipData, legacyMembershipData, sessionMembershipData], true);
it("uses legacy events if there are any active legacy calls", async () => {
await testSession([expiredLegacyMembershipData, legacyMembershipData, sessionMembershipData], true);
});
it('uses legacy events if a non-legacy call is in a "memberships" array', () => {
testSession([sessionMembershipData], true);
it('uses legacy events if a non-legacy call is in a "memberships" array', async () => {
await testSession([sessionMembershipData], true);
});
it("uses non-legacy events if all legacy calls are expired", () => {
testSession([expiredLegacyMembershipData], false);
it("uses non-legacy events if all legacy calls are expired", async () => {
await testSession([expiredLegacyMembershipData], false);
});
it("uses non-legacy events if there are only non-legacy calls", () => {
testSession(sessionMembershipData, false);
it("uses non-legacy events if there are only non-legacy calls", async () => {
await testSession(sessionMembershipData, false);
});
});
describe("getOldestMembership", () => {
it("returns the oldest membership event", () => {
jest.useFakeTimers();
jest.setSystemTime(4000);
const mockRoom = makeMockRoom([
Object.assign({}, membershipTemplate, { device_id: "foo", created_ts: 3000 }),
Object.assign({}, membershipTemplate, { device_id: "old", created_ts: 1000 }),
@@ -283,12 +320,15 @@ describe("MatrixRTCSession", () => {
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
expect(sess.getOldestMembership()!.deviceId).toEqual("old");
jest.useRealTimers();
});
});
describe("getsActiveFocus", () => {
const activeFociConfig = { type: "livekit", livekit_service_url: "https://active.url" };
it("gets the correct active focus with oldest_membership", () => {
jest.useFakeTimers();
jest.setSystemTime(3000);
const mockRoom = makeMockRoom([
Object.assign({}, membershipTemplate, {
device_id: "foo",
@@ -306,6 +346,7 @@ describe("MatrixRTCSession", () => {
focus_selection: "oldest_membership",
});
expect(sess.getActiveFocus()).toBe(activeFociConfig);
jest.useRealTimers();
});
it("does not provide focus if the selction method is unknown", () => {
const mockRoom = makeMockRoom([
@@ -327,6 +368,8 @@ describe("MatrixRTCSession", () => {
expect(sess.getActiveFocus()).toBe(undefined);
});
it("gets the correct active focus legacy", () => {
jest.useFakeTimers();
jest.setSystemTime(3000);
const mockRoom = makeMockRoom([
Object.assign({}, membershipTemplate, {
device_id: "foo",
@@ -341,18 +384,34 @@ describe("MatrixRTCSession", () => {
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }]);
expect(sess.getActiveFocus()).toBe(activeFociConfig);
jest.useRealTimers();
});
});
describe("joining", () => {
let mockRoom: Room;
let sendStateEventMock: jest.Mock;
let sendDelayedStateMock: jest.Mock;
let sendEventMock: jest.Mock;
let sentStateEvent: Promise<void>;
let sentDelayedState: Promise<void>;
beforeEach(() => {
sendStateEventMock = jest.fn();
sentStateEvent = new Promise((resolve) => {
sendStateEventMock = jest.fn(resolve);
});
sentDelayedState = new Promise((resolve) => {
sendDelayedStateMock = jest.fn(() => {
resolve();
return {
delay_id: "id",
};
});
});
sendEventMock = jest.fn();
client.sendStateEvent = sendStateEventMock;
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
client.sendEvent = sendEventMock;
mockRoom = makeMockRoom([]);
@@ -373,9 +432,11 @@ describe("MatrixRTCSession", () => {
expect(sess!.isJoined()).toEqual(true);
});
it("sends a membership event when joining a call", () => {
it("sends a membership event when joining a call", async () => {
const realSetTimeout = setTimeout;
jest.useFakeTimers();
sess!.joinRoomSession([mockFocus], mockFocus);
await Promise.race([sentStateEvent, new Promise((resolve) => realSetTimeout(resolve, 500))]);
expect(client.sendStateEvent).toHaveBeenCalledWith(
mockRoom!.roomId,
EventType.GroupCallMemberPrefix,
@@ -396,6 +457,8 @@ describe("MatrixRTCSession", () => {
},
"@alice:example.org",
);
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(0);
jest.useRealTimers();
});
@@ -403,13 +466,15 @@ describe("MatrixRTCSession", () => {
const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" };
const activeFocus = { type: "livekit", focus_selection: "oldest_membership" };
function testJoin(useOwnedStateEvents: boolean): void {
async function testJoin(useOwnedStateEvents: boolean): Promise<void> {
const realSetTimeout = setTimeout;
if (useOwnedStateEvents) {
mockRoom.getVersion = jest.fn().mockReturnValue("org.matrix.msc3779.default");
}
jest.useFakeTimers();
sess!.joinRoomSession([activeFocusConfig], activeFocus, { useLegacyMemberEvents: false });
await Promise.race([sentStateEvent, new Promise((resolve) => realSetTimeout(resolve, 500))]);
expect(client.sendStateEvent).toHaveBeenCalledWith(
mockRoom!.roomId,
EventType.GroupCallMemberPrefix,
@@ -423,15 +488,17 @@ describe("MatrixRTCSession", () => {
} satisfies SessionMembershipData,
`${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA`,
);
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
jest.useRealTimers();
}
it("sends a membership event with session payload when joining a non-legacy call", () => {
testJoin(false);
it("sends a membership event with session payload when joining a non-legacy call", async () => {
await testJoin(false);
});
it("does not prefix the state key with _ for rooms that support user-owned state events", () => {
testJoin(true);
it("does not prefix the state key with _ for rooms that support user-owned state events", async () => {
await testJoin(true);
});
});
@@ -461,9 +528,8 @@ describe("MatrixRTCSession", () => {
const eventContent = await eventSentPromise;
// definitely should have renewed by 1 second before the expiry!
const timeElapsed = 60 * 60 * 1000 - 1000;
const event = mockRTCEvent(eventContent.memberships, mockRoom.roomId, timeElapsed);
jest.setSystemTime(1000);
const event = mockRTCEvent(eventContent.memberships, mockRoom.roomId);
const getState = mockRoom.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
getState.getStateEvents = jest.fn().mockReturnValue(event);
getState.events = new Map([
@@ -486,6 +552,8 @@ describe("MatrixRTCSession", () => {
sendStateEventMock.mockReset().mockImplementation(resolveFn);
// definitely should have renewed by 1 second before the expiry!
const timeElapsed = 60 * 60 * 1000 - 1000;
jest.setSystemTime(Date.now() + timeElapsed);
jest.advanceTimersByTime(timeElapsed);
await eventReSentPromise;
@@ -546,6 +614,17 @@ describe("MatrixRTCSession", () => {
});
});
it("does not send key if join called when already joined", () => {
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
expect(client.sendEvent).toHaveBeenCalledTimes(1);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
expect(client.sendEvent).toHaveBeenCalledTimes(1);
});
it("retries key sends", async () => {
jest.useFakeTimers();
let firstEventSent = false;
@@ -622,7 +701,7 @@ describe("MatrixRTCSession", () => {
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId, undefined));
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
sess.onMembershipUpdate();
await keysSentPromise2;
@@ -633,6 +712,214 @@ describe("MatrixRTCSession", () => {
}
});
it("Does not re-send key if memberships stays same", async () => {
jest.useFakeTimers();
try {
const keysSentPromise1 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
});
const member1 = membershipTemplate;
const member2 = Object.assign({}, membershipTemplate, {
device_id: "BBBBBBB",
});
const mockRoom = makeMockRoom([member1, member2]);
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([member1, member2], mockRoom.roomId));
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1;
// make sure an encryption key was sent
expect(sendEventMock).toHaveBeenCalledWith(
expect.stringMatching(".*"),
"io.element.call.encryption_keys",
{
call_id: "",
device_id: "AAAAAAA",
keys: [
{
index: 0,
key: expect.stringMatching(".*"),
},
],
},
);
sendEventMock.mockClear();
// these should be a no-op:
sess.onMembershipUpdate();
expect(sendEventMock).toHaveBeenCalledTimes(0);
} finally {
jest.useRealTimers();
}
});
it("Re-sends key if a member changes membership ID", async () => {
jest.useFakeTimers();
try {
const keysSentPromise1 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
});
const member1 = membershipTemplate;
const member2 = {
...membershipTemplate,
device_id: "BBBBBBB",
};
const mockRoom = makeMockRoom([member1, member2]);
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([member1, member2], mockRoom.roomId));
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1;
// make sure an encryption key was sent
expect(sendEventMock).toHaveBeenCalledWith(
expect.stringMatching(".*"),
"io.element.call.encryption_keys",
{
call_id: "",
device_id: "AAAAAAA",
keys: [
{
index: 0,
key: expect.stringMatching(".*"),
},
],
},
);
sendEventMock.mockClear();
// this should be a no-op:
sess.onMembershipUpdate();
expect(sendEventMock).toHaveBeenCalledTimes(0);
// advance time to avoid key throttling
jest.advanceTimersByTime(10000);
// update membership ID
member2.membershipID = "newID";
const keysSentPromise2 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
});
// this should re-send the key
sess.onMembershipUpdate();
await keysSentPromise2;
expect(sendEventMock).toHaveBeenCalledWith(
expect.stringMatching(".*"),
"io.element.call.encryption_keys",
{
call_id: "",
device_id: "AAAAAAA",
keys: [
{
index: 0,
key: expect.stringMatching(".*"),
},
],
},
);
} finally {
jest.useRealTimers();
}
});
it("Re-sends key if a member changes created_ts", async () => {
jest.useFakeTimers();
jest.setSystemTime(1000);
try {
const keysSentPromise1 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
});
const member1 = { ...membershipTemplate, created_ts: 1000 };
const member2 = {
...membershipTemplate,
created_ts: 1000,
device_id: "BBBBBBB",
};
const mockRoom = makeMockRoom([member1, member2]);
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([member1, member2], mockRoom.roomId));
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1;
// make sure an encryption key was sent
expect(sendEventMock).toHaveBeenCalledWith(
expect.stringMatching(".*"),
"io.element.call.encryption_keys",
{
call_id: "",
device_id: "AAAAAAA",
keys: [
{
index: 0,
key: expect.stringMatching(".*"),
},
],
},
);
sendEventMock.mockClear();
// this should be a no-op:
sess.onMembershipUpdate();
expect(sendEventMock).toHaveBeenCalledTimes(0);
// advance time to avoid key throttling
jest.advanceTimersByTime(10000);
// update created_ts
member2.created_ts = 5000;
const keysSentPromise2 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
});
// this should re-send the key
sess.onMembershipUpdate();
await keysSentPromise2;
expect(sendEventMock).toHaveBeenCalledWith(
expect.stringMatching(".*"),
"io.element.call.encryption_keys",
{
call_id: "",
device_id: "AAAAAAA",
keys: [
{
index: 0,
key: expect.stringMatching(".*"),
},
],
},
);
} finally {
jest.useRealTimers();
}
});
it("Rotates key if a member leaves", async () => {
jest.useFakeTimers();
try {
@@ -668,7 +955,7 @@ describe("MatrixRTCSession", () => {
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId, undefined));
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId));
sess.onMembershipUpdate();
jest.advanceTimersByTime(10000);
@@ -707,7 +994,7 @@ describe("MatrixRTCSession", () => {
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId, undefined));
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
sess.onMembershipUpdate();
await new Promise((resolve) => {
@@ -739,9 +1026,7 @@ describe("MatrixRTCSession", () => {
const onMembershipsChanged = jest.fn();
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
mockRoom.getLiveTimeline().getState = jest
.fn()
.mockReturnValue(makeMockRoomState([], mockRoom.roomId, undefined));
mockRoom.getLiveTimeline().getState = jest.fn().mockReturnValue(makeMockRoomState([], mockRoom.roomId));
sess.onMembershipUpdate();
expect(onMembershipsChanged).toHaveBeenCalled();
@@ -751,7 +1036,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers();
try {
const membership = Object.assign({}, membershipTemplate);
const mockRoom = makeMockRoom([membership], 0);
const mockRoom = makeMockRoom([membership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
const membershipObject = sess.memberships[0];
@@ -779,7 +1064,7 @@ describe("MatrixRTCSession", () => {
expires: 1000,
}),
];
const mockRoomNoExpired = makeMockRoom(mockMemberships, 0);
const mockRoomNoExpired = makeMockRoom(mockMemberships);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoomNoExpired);
@@ -818,6 +1103,7 @@ describe("MatrixRTCSession", () => {
it("fills in created_ts for other memberships on update", () => {
client.sendStateEvent = jest.fn();
jest.useFakeTimers();
jest.setSystemTime(1000);
const mockRoom = makeMockRoom([
Object.assign({}, membershipTemplate, {
device_id: "OTHERDEVICE",
@@ -875,6 +1161,7 @@ describe("MatrixRTCSession", () => {
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(Date.now()),
} as unknown as MatrixEvent);
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
@@ -898,6 +1185,7 @@ describe("MatrixRTCSession", () => {
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(Date.now()),
} as unknown as MatrixEvent);
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
@@ -909,6 +1197,130 @@ describe("MatrixRTCSession", () => {
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
});
it("collects keys by merging", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 0,
key: "dGhpcyBpcyB0aGUga2V5",
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(Date.now()),
} as unknown as MatrixEvent);
let bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
expect(bobKeys).toHaveLength(1);
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 4,
key: "dGhpcyBpcyB0aGUga2V5",
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(Date.now()),
} as unknown as MatrixEvent);
bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
expect(bobKeys).toHaveLength(5);
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
});
it("ignores older keys at same index", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 0,
key: encodeBase64(Buffer.from("newer key", "utf-8")),
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(2000),
} as unknown as MatrixEvent);
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 0,
key: encodeBase64(Buffer.from("older key", "utf-8")),
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(1000), // earlier timestamp than the newer key
} as unknown as MatrixEvent);
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
expect(bobKeys).toHaveLength(1);
expect(bobKeys[0]).toEqual(Buffer.from("newer key", "utf-8"));
});
it("key timestamps are treated as monotonic", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 0,
key: encodeBase64(Buffer.from("first key", "utf-8")),
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(1000),
} as unknown as MatrixEvent);
sess.onCallEncryption({
getType: jest.fn().mockReturnValue("io.element.call.encryption_keys"),
getContent: jest.fn().mockReturnValue({
device_id: "bobsphone",
call_id: "",
keys: [
{
index: 0,
key: encodeBase64(Buffer.from("second key", "utf-8")),
},
],
}),
getSender: jest.fn().mockReturnValue("@bob:example.org"),
getTs: jest.fn().mockReturnValue(1000), // same timestamp as the first key
} as unknown as MatrixEvent);
const bobKeys = sess.getKeysForParticipant("@bob:example.org", "bobsphone")!;
expect(bobKeys).toHaveLength(1);
expect(bobKeys[0]).toEqual(Buffer.from("second key", "utf-8"));
});
it("ignores keys event for the local participant", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
@@ -925,6 +1337,7 @@ describe("MatrixRTCSession", () => {
],
}),
getSender: jest.fn().mockReturnValue(client.getUserId()),
getTs: jest.fn().mockReturnValue(Date.now()),
} as unknown as MatrixEvent);
const myKeys = sess.getKeysForParticipant(client.getUserId()!, client.getDeviceId()!)!;
@@ -102,6 +102,7 @@ describe("MatrixRTCSessionManager", () => {
getContent: jest.fn().mockReturnValue({}),
getSender: jest.fn().mockReturnValue("@mock:user.example"),
getRoomId: jest.fn().mockReturnValue("!room:id"),
isDecryptionFailure: jest.fn().mockReturnValue(false),
sender: {
userId: "@mock:user.example",
},
@@ -110,4 +111,93 @@ describe("MatrixRTCSessionManager", () => {
await new Promise(process.nextTick);
expect(onCallEncryptionMock).toHaveBeenCalled();
});
describe("event decryption", () => {
it("Retries decryption and processes success", async () => {
try {
jest.useFakeTimers();
const room1 = makeMockRoom([membershipTemplate]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
jest.spyOn(client, "getRoom").mockReturnValue(room1);
client.emit(ClientEvent.Room, room1);
const onCallEncryptionMock = jest.fn();
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
let isDecryptionFailure = true;
client.decryptEventIfNeeded = jest
.fn()
.mockReturnValueOnce(Promise.resolve())
.mockImplementation(() => {
isDecryptionFailure = false;
return Promise.resolve();
});
const timelineEvent = {
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
getContent: jest.fn().mockReturnValue({}),
getSender: jest.fn().mockReturnValue("@mock:user.example"),
getRoomId: jest.fn().mockReturnValue("!room:id"),
isDecryptionFailure: jest.fn().mockImplementation(() => isDecryptionFailure),
getId: jest.fn().mockReturnValue("event_id"),
sender: {
userId: "@mock:user.example",
},
} as unknown as MatrixEvent;
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
// should retry after one second:
await jest.advanceTimersByTimeAsync(1500);
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
expect(onCallEncryptionMock).toHaveBeenCalledTimes(1);
} finally {
jest.useRealTimers();
}
});
it("Retries decryption and processes failure", async () => {
try {
jest.useFakeTimers();
const room1 = makeMockRoom([membershipTemplate]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
jest.spyOn(client, "getRoom").mockReturnValue(room1);
client.emit(ClientEvent.Room, room1);
const onCallEncryptionMock = jest.fn();
client.matrixRTC.getRoomSession(room1).onCallEncryption = onCallEncryptionMock;
client.decryptEventIfNeeded = jest.fn().mockReturnValue(Promise.resolve());
const timelineEvent = {
getType: jest.fn().mockReturnValue(EventType.CallEncryptionKeysPrefix),
getContent: jest.fn().mockReturnValue({}),
getSender: jest.fn().mockReturnValue("@mock:user.example"),
getRoomId: jest.fn().mockReturnValue("!room:id"),
isDecryptionFailure: jest.fn().mockReturnValue(true), // always fail
getId: jest.fn().mockReturnValue("event_id"),
sender: {
userId: "@mock:user.example",
},
} as unknown as MatrixEvent;
client.emit(RoomEvent.Timeline, timelineEvent, undefined, undefined, false, {} as IRoomTimelineData);
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(1);
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
// should retry after one second:
await jest.advanceTimersByTimeAsync(1500);
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
// doesn't retry again:
await jest.advanceTimersByTimeAsync(1500);
expect(client.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
expect(onCallEncryptionMock).toHaveBeenCalledTimes(0);
} finally {
jest.useRealTimers();
}
});
});
});
+7 -7
View File
@@ -20,10 +20,10 @@ import { randomString } from "../../../src/randomstring";
type MembershipData = CallMembershipData[] | SessionMembershipData;
export function makeMockRoom(membershipData: MembershipData, localAge: number | null = null): Room {
export function makeMockRoom(membershipData: MembershipData): Room {
const roomId = randomString(8);
// Caching roomState here so it does not get recreated when calling `getLiveTimeline.getState()`
const roomState = makeMockRoomState(membershipData, roomId, localAge);
const roomState = makeMockRoomState(membershipData, roomId);
return {
roomId: roomId,
hasMembershipState: jest.fn().mockReturnValue(true),
@@ -34,8 +34,8 @@ export function makeMockRoom(membershipData: MembershipData, localAge: number |
} as unknown as Room;
}
export function makeMockRoomState(membershipData: MembershipData, roomId: string, localAge: number | null = null) {
const event = mockRTCEvent(membershipData, roomId, localAge);
export function makeMockRoomState(membershipData: MembershipData, roomId: string) {
const event = mockRTCEvent(membershipData, roomId);
return {
on: jest.fn(),
off: jest.fn(),
@@ -57,7 +57,7 @@ export function makeMockRoomState(membershipData: MembershipData, roomId: string
};
}
export function mockRTCEvent(membershipData: MembershipData, roomId: string, localAge: number | null): MatrixEvent {
export function mockRTCEvent(membershipData: MembershipData, roomId: string): MatrixEvent {
return {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
getContent: jest.fn().mockReturnValue(
@@ -68,11 +68,11 @@ export function mockRTCEvent(membershipData: MembershipData, roomId: string, loc
},
),
getSender: jest.fn().mockReturnValue("@mock:user.example"),
getTs: jest.fn().mockReturnValue(1000),
localTimestamp: Date.now() - (localAge ?? 10),
getTs: jest.fn().mockReturnValue(Date.now()),
getRoomId: jest.fn().mockReturnValue(roomId),
sender: {
userId: "@mock:user.example",
},
isDecryptionFailure: jest.fn().mockReturnValue(false),
} as unknown as MatrixEvent;
}
+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);
+13 -4
View File
@@ -89,11 +89,8 @@ describe("oidc authorization", () => {
describe("generateAuthorizationUrl()", () => {
it("should generate url with correct parameters", async () => {
// test the no crypto case here
// @ts-ignore mocking
globalThis.crypto.subtle = undefined;
const authorizationParams = generateAuthorizationParams({ redirectUri: baseUrl });
authorizationParams.codeVerifier = "test-code-verifier";
const authUrl = new URL(
await generateAuthorizationUrl(authorizationEndpoint, clientId, authorizationParams),
);
@@ -105,6 +102,18 @@ describe("oidc authorization", () => {
expect(authUrl.searchParams.get("scope")).toEqual(authorizationParams.scope);
expect(authUrl.searchParams.get("state")).toEqual(authorizationParams.state);
expect(authUrl.searchParams.get("nonce")).toEqual(authorizationParams.nonce);
expect(authUrl.searchParams.get("code_challenge")).toEqual("0FLIKahrX7kqxncwhV5WD82lu_wi5GA8FsRSLubaOpU");
});
it("should log a warning if crypto is not available", async () => {
// test the no crypto case here
// @ts-ignore mocking
globalThis.crypto.subtle = undefined;
const authorizationParams = generateAuthorizationParams({ redirectUri: baseUrl });
const authUrl = new URL(
await generateAuthorizationUrl(authorizationEndpoint, clientId, authorizationParams),
);
// crypto not available, plain text code_challenge is used
expect(authUrl.searchParams.get("code_challenge")).toEqual(authorizationParams.codeVerifier);
@@ -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;
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { UnstableValue } from "../NamespacedValue";
import { IClientWellKnown } from "../client";
import { UnstableValue } from "../NamespacedValue.ts";
import { IClientWellKnown } from "../client.ts";
// disable lint because these are wire responses
/* eslint-disable camelcase */
+3 -3
View File
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { RelatesToRelationship, REFERENCE_RELATION } from "./extensible_events";
import { UnstableValue } from "../NamespacedValue";
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
import { RelatesToRelationship, REFERENCE_RELATION } from "./extensible_events.ts";
import { UnstableValue } from "../NamespacedValue.ts";
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location.ts";
/**
* Beacon info and beacon event types as described in MSC3672
+2 -2
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { ISignatures } from "./signed";
import type { ISignatures } from "./signed.ts";
export type OlmGroupSessionExtraData = {
untrusted?: boolean;
@@ -22,7 +22,7 @@ export type OlmGroupSessionExtraData = {
};
// Backwards compatible re-export
export type { EventDecryptionResult as IEventDecryptionResult } from "../common-crypto/CryptoBackend";
export type { EventDecryptionResult as IEventDecryptionResult } from "../common-crypto/CryptoBackend.ts";
interface Extensible {
[key: string]: any;
+11 -11
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { NamespacedValue, UnstableValue } from "../NamespacedValue";
import { NamespacedValue, UnstableValue } from "../NamespacedValue.ts";
import {
PolicyRuleEventContent,
RoomAvatarEventContent,
@@ -34,16 +34,16 @@ import {
RoomTopicEventContent,
SpaceChildEventContent,
SpaceParentEventContent,
} from "./state_events";
} from "./state_events.ts";
import {
ExperimentalGroupCallRoomMemberState,
IGroupCallRoomMemberState,
IGroupCallRoomState,
} from "../webrtc/groupCall";
import { MSC3089EventContent } from "../models/MSC3089Branch";
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon";
import { XOR } from "./common";
import { ReactionEventContent, RoomMessageEventContent, StickerEventContent } from "./events";
} from "../webrtc/groupCall.ts";
import { MSC3089EventContent } from "../models/MSC3089Branch.ts";
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon.ts";
import { XOR } from "./common.ts";
import { ReactionEventContent, RoomMessageEventContent, StickerEventContent } from "./events.ts";
import {
MCallAnswer,
MCallBase,
@@ -54,10 +54,10 @@ import {
MCallSelectAnswer,
SDPStreamMetadata,
SDPStreamMetadataKey,
} from "../webrtc/callEventTypes";
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types";
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls";
import { SessionMembershipData } from "../matrixrtc/CallMembership";
} from "../webrtc/callEventTypes.ts";
import { EncryptionKeysEventContent, ICallNotifyContent } from "../matrixrtc/types.ts";
import { M_POLL_END, M_POLL_START, PollEndEventContent, PollStartEventContent } from "./polls.ts";
import { SessionMembershipData } from "../matrixrtc/CallMembership.ts";
export enum EventType {
// Room state events
+3 -3
View File
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MsgType, RelationType } from "./event";
import { FileInfo, ImageInfo, MediaEventContent } from "./media";
import { XOR } from "./common";
import { MsgType, RelationType } from "./event.ts";
import { FileInfo, ImageInfo, MediaEventContent } from "./media.ts";
import { XOR } from "./common.ts";
interface BaseTimelineEvent {
"body": string;
+1 -1
View File
@@ -16,7 +16,7 @@ limitations under the License.
import { EitherAnd, NamespacedValue, Optional, UnstableValue } from "matrix-events-sdk";
import { isProvided } from "../extensible_events_v1/utilities";
import { isProvided } from "../extensible_events_v1/utilities.ts";
// Types and utilities for MSC1767: Extensible events (version 1) in Matrix
-45
View File
@@ -29,20 +29,11 @@ declare global {
namespace NodeJS {
interface Global {
localStorage: Storage;
// marker variable used to detect both the browser & node entrypoints being used at once
__js_sdk_entrypoint: unknown;
}
}
interface Window {
webkitAudioContext: typeof AudioContext;
}
interface Crypto {
webkitSubtle?: Window["crypto"]["subtle"];
}
interface MediaDevices {
// This is experimental and types don't know about it yet
// https://github.com/microsoft/TypeScript/issues/33232
@@ -76,40 +67,4 @@ declare global {
// on webkit: we should check if we still need to do this
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
}
export interface ISettledFulfilled<T> {
status: "fulfilled";
value: T;
}
export interface ISettledRejected {
status: "rejected";
reason: any;
}
interface PromiseConstructor {
allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFulfilled<T> | ISettledRejected>>;
}
interface RTCRtpTransceiver {
// This has been removed from TS
// (https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029),
// but we still need this for MatrixCall::getRidOfRTXCodecs()
setCodecPreferences(codecs: RTCRtpCodecCapability[]): void;
}
interface RequestInit {
/**
* Specifies the priority of the fetch request relative to other requests of the same type.
* Must be one of the following strings:
* high: A high priority fetch request relative to other requests of the same type.
* low: A low priority fetch request relative to other requests of the same type.
* auto: Automatically determine the priority of the fetch request relative to other requests of the same type (default).
*
* @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fetch-priority-attribute
* @see https://github.com/microsoft/TypeScript/issues/54472
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility
* Not yet supported in Safari or Firefox
*/
priority?: "high" | "low" | "auto";
}
}
+2 -2
View File
@@ -17,8 +17,8 @@ limitations under the License.
// Types for MSC3488 - m.location: Extending events with location data
import { EitherAnd } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue";
import { M_TEXT } from "./extensible_events";
import { UnstableValue } from "../NamespacedValue.ts";
import { M_TEXT } from "./extensible_events.ts";
export enum LocationAssetType {
Self = "m.self",
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MsgType } from "../@types/event";
import { MsgType } from "../@types/event.ts";
/**
* Information on encrypted media attachments.
+1 -1
View File
@@ -21,7 +21,7 @@ import {
REFERENCE_RELATION,
RelatesToRelationship,
TSNamespace,
} from "./extensible_events";
} from "./extensible_events.ts";
/**
* Identifier for a disclosed poll.
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { AuthDict } from "../interactive-auth";
import { AuthDict } from "../interactive-auth.ts";
/**
* The request body of a call to `POST /_matrix/client/v3/register`.
+58 -8
View File
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IContent, IEvent } from "../models/event";
import { Preset, Visibility } from "./partials";
import { IEventWithRoomId, SearchKey } from "./search";
import { IRoomEventFilter } from "../filter";
import { Direction } from "../models/event-timeline";
import { PushRuleAction } from "./PushRules";
import { IRoomEvent } from "../sync-accumulator";
import { EventType, RelationType, RoomType } from "./event";
import { IContent, IEvent } from "../models/event.ts";
import { Preset, Visibility } from "./partials.ts";
import { IEventWithRoomId, SearchKey } from "./search.ts";
import { IRoomEventFilter } from "../filter.ts";
import { Direction } from "../models/event-timeline.ts";
import { PushRuleAction } from "./PushRules.ts";
import { IRoomEvent } from "../sync-accumulator.ts";
import { EventType, RelationType, RoomType } from "./event.ts";
// allow camelcase as these are things that go onto the wire
/* eslint-disable camelcase */
@@ -76,6 +76,56 @@ export interface ISendEventResponse {
event_id: string;
}
export type TimeoutDelay = {
delay: number;
};
export type ParentDelayId = {
parent_delay_id: string;
};
export type SendTimeoutDelayedEventRequestOpts = TimeoutDelay & Partial<ParentDelayId>;
export type SendActionDelayedEventRequestOpts = ParentDelayId;
export type SendDelayedEventRequestOpts = SendTimeoutDelayedEventRequestOpts | SendActionDelayedEventRequestOpts;
export type SendDelayedEventResponse = {
delay_id: string;
};
export enum UpdateDelayedEventAction {
Cancel = "cancel",
Restart = "restart",
Send = "send",
}
export type UpdateDelayedEventRequestOpts = SendDelayedEventResponse & {
action: UpdateDelayedEventAction;
};
type DelayedPartialTimelineEvent = {
room_id: string;
type: string;
content: IContent;
};
type DelayedPartialStateEvent = DelayedPartialTimelineEvent & {
state_key: string;
transaction_id: string;
};
type DelayedPartialEvent = DelayedPartialTimelineEvent | DelayedPartialStateEvent;
export type DelayedEventInfo = {
delayed_events: DelayedPartialEvent &
SendDelayedEventResponse &
SendDelayedEventRequestOpts &
{
running_since: number;
}[];
next_batch?: string;
};
export interface IPresenceOpts {
// One of "online", "offline" or "unavailable"
presence: "online" | "offline" | "unavailable";
+3 -3
View File
@@ -16,9 +16,9 @@ limitations under the License.
// Types relating to the /search API
import { IRoomEvent, IStateEvent } from "../sync-accumulator";
import { IRoomEventFilter } from "../filter";
import { SearchResult } from "../models/search-result";
import { IRoomEvent, IStateEvent } from "../sync-accumulator.ts";
import { IRoomEventFilter } from "../filter.ts";
import { SearchResult } from "../models/search-result.ts";
/* eslint-disable camelcase */
export interface IEventWithRoomId extends IRoomEvent {
+3 -3
View File
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IPublicRoomsChunkRoom } from "../client";
import { RoomType } from "./event";
import { IStrippedState } from "../sync-accumulator";
import { IPublicRoomsChunkRoom } from "../client.ts";
import { RoomType } from "./event.ts";
import { IStrippedState } from "../sync-accumulator.ts";
// Types relating to Rooms of type `m.space` and related APIs
+4 -4
View File
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { RoomType } from "./event";
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "./partials";
import { ImageInfo } from "./media";
import { PolicyRecommendation } from "../models/invites-ignorer";
import { RoomType } from "./event.ts";
import { GuestAccess, HistoryVisibility, JoinRule, RestrictedAllowType } from "./partials.ts";
import { ImageInfo } from "./media.ts";
import { PolicyRecommendation } from "../models/invites-ignorer.ts";
export interface RoomCanonicalAliasEventContent {
alias?: string;
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IdServerUnbindResult } from "./partials";
import { IdServerUnbindResult } from "./partials.ts";
// Types relating to Synapse Admin APIs
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ServerControlledNamespacedValue } from "../NamespacedValue";
import { ServerControlledNamespacedValue } from "../NamespacedValue.ts";
/**
* https://github.com/matrix-org/matrix-doc/pull/3773
+2 -2
View File
@@ -16,8 +16,8 @@ limitations under the License.
import { EitherAnd } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue";
import { IMessageRendering } from "./extensible_events";
import { UnstableValue } from "../NamespacedValue.ts";
import { IMessageRendering } from "./extensible_events.ts";
/**
* Extensible topic event type based on MSC3765
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { AuthDict, IAuthData } from "../interactive-auth";
import { AuthDict, IAuthData } from "../interactive-auth.ts";
/**
* Helper type to represent HTTP request body for a UIA enabled endpoint
+1 -1
View File
@@ -19,7 +19,7 @@ limitations under the License.
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter";
import { ListenerMap, TypedEventEmitter } from "./models/typed-event-emitter.ts";
export class ReEmitter {
public constructor(private readonly target: EventEmitter) {}
+13 -8
View File
@@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ToDeviceMessageId } from "./@types/event";
import { logger } from "./logger";
import { MatrixClient, ClientEvent } from "./client";
import { MatrixError } from "./http-api";
import { IndexedToDeviceBatch, ToDeviceBatch, ToDeviceBatchWithTxnId, ToDevicePayload } from "./models/ToDeviceMessage";
import { MatrixScheduler } from "./scheduler";
import { SyncState } from "./sync";
import { MapWithDefault } from "./utils";
import { ToDeviceMessageId } from "./@types/event.ts";
import { logger } from "./logger.ts";
import { MatrixClient, ClientEvent } from "./client.ts";
import { MatrixError } from "./http-api/index.ts";
import {
IndexedToDeviceBatch,
ToDeviceBatch,
ToDeviceBatchWithTxnId,
ToDevicePayload,
} from "./models/ToDeviceMessage.ts";
import { MatrixScheduler } from "./scheduler.ts";
import { SyncState } from "./sync.ts";
import { MapWithDefault } from "./utils.ts";
const MAX_BATCH_SIZE = 20;
+4 -4
View File
@@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IClientWellKnown, IWellKnownConfig, IServerVersions } from "./client";
import { logger } from "./logger";
import { MatrixError, Method, timeoutSignal } from "./http-api";
import { SUPPORTED_MATRIX_VERSIONS } from "./version-support";
import { IClientWellKnown, IWellKnownConfig, IServerVersions } from "./client.ts";
import { logger } from "./logger.ts";
import { MatrixError, Method, timeoutSignal } from "./http-api/index.ts";
import { SUPPORTED_MATRIX_VERSIONS } from "./version-support.ts";
// Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
+2 -2
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import * as matrixcs from "./matrix.ts";
type BrowserMatrix = typeof matrixcs;
declare global {
@@ -40,5 +40,5 @@ if (indexedDB) {
matrixcs.setCryptoStoreFactory(() => new matrixcs.IndexedDBCryptoStore(indexedDB!, "matrix-js-sdk:crypto"));
}
export * from "./matrix";
export * from "./matrix.ts";
globalThis.matrixcs = matrixcs;
+337 -134
View File
@@ -20,8 +20,8 @@ limitations under the License.
import { Optional } from "matrix-events-sdk";
import type { IDeviceKeys, IMegolmSessionData, IOneTimeKey } from "./@types/crypto";
import { ISyncStateData, SetPresence, SyncApi, SyncApiOptions, SyncState } from "./sync";
import type { IDeviceKeys, IMegolmSessionData, IOneTimeKey } from "./@types/crypto.ts";
import { ISyncStateData, SetPresence, SyncApi, SyncApiOptions, SyncState } from "./sync.ts";
import {
EventStatus,
IContent,
@@ -31,29 +31,29 @@ import {
MatrixEventEvent,
MatrixEventHandlerMap,
PushDetails,
} from "./models/event";
import { StubStore } from "./store/stub";
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call";
import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter";
import { CallEventHandler, CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler";
} from "./models/event.ts";
import { StubStore } from "./store/stub.ts";
import { CallEvent, CallEventHandlerMap, createNewMatrixCall, MatrixCall, supportsMatrixCall } from "./webrtc/call.ts";
import { Filter, IFilterDefinition, IRoomEventFilter } from "./filter.ts";
import { CallEventHandler, CallEventHandlerEvent, CallEventHandlerEventHandlerMap } from "./webrtc/callEventHandler.ts";
import {
GroupCallEventHandler,
GroupCallEventHandlerEvent,
GroupCallEventHandlerEventHandlerMap,
} from "./webrtc/groupCallEventHandler";
import * as utils from "./utils";
import { noUnsafeEventProps, QueryDict, replaceParam, safeSet, sleep } from "./utils";
import { Direction, EventTimeline } from "./models/event-timeline";
import { IActionsObject, PushProcessor } from "./pushprocessor";
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery";
import * as olmlib from "./crypto/olmlib";
import { decodeBase64, encodeBase64 } from "./base64";
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice";
import { IOlmDevice } from "./crypto/algorithms/megolm";
import { TypedReEmitter } from "./ReEmitter";
import { IRoomEncryption } from "./crypto/RoomList";
import { logger, Logger } from "./logger";
import { SERVICE_TYPES } from "./service-types";
} from "./webrtc/groupCallEventHandler.ts";
import * as utils from "./utils.ts";
import { noUnsafeEventProps, QueryDict, replaceParam, safeSet, sleep } from "./utils.ts";
import { Direction, EventTimeline } from "./models/event-timeline.ts";
import { IActionsObject, PushProcessor } from "./pushprocessor.ts";
import { AutoDiscovery, AutoDiscoveryAction } from "./autodiscovery.ts";
import * as olmlib from "./crypto/olmlib.ts";
import { decodeBase64, encodeBase64, encodeUnpaddedBase64Url } from "./base64.ts";
import { IExportedDevice as IExportedOlmDevice } from "./crypto/OlmDevice.ts";
import { IOlmDevice } from "./crypto/algorithms/megolm.ts";
import { TypedReEmitter } from "./ReEmitter.ts";
import { IRoomEncryption } from "./crypto/RoomList.ts";
import { logger, Logger } from "./logger.ts";
import { SERVICE_TYPES } from "./service-types.ts";
import {
Body,
ClientPrefix,
@@ -73,7 +73,7 @@ import {
Upload,
UploadOpts,
UploadResponse,
} from "./http-api";
} from "./http-api/index.ts";
import {
Crypto,
CryptoEvent,
@@ -83,14 +83,14 @@ import {
ICryptoCallbacks,
IRoomKeyRequestBody,
isCryptoAvailable,
} from "./crypto";
import { DeviceInfo } from "./crypto/deviceinfo";
import { decodeRecoveryKey } from "./crypto/recoverykey";
import { keyFromAuthData } from "./crypto/key_passphrase";
import { User, UserEvent, UserEventHandlerMap } from "./models/user";
import { getHttpUriForMxc } from "./content-repo";
import { SearchResult } from "./models/search-result";
import { DEHYDRATION_ALGORITHM, IDehydratedDevice, IDehydratedDeviceKeyInfo } from "./crypto/dehydration";
} from "./crypto/index.ts";
import { DeviceInfo } from "./crypto/deviceinfo.ts";
import { decodeRecoveryKey } from "./crypto/recoverykey.ts";
import { keyFromAuthData } from "./crypto/key_passphrase.ts";
import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts";
import { getHttpUriForMxc } from "./content-repo.ts";
import { SearchResult } from "./models/search-result.ts";
import { DEHYDRATION_ALGORITHM, IDehydratedDevice, IDehydratedDeviceKeyInfo } from "./crypto/dehydration.ts";
import {
IKeyBackupInfo,
IKeyBackupPrepareOpts,
@@ -98,22 +98,23 @@ import {
IKeyBackupRestoreResult,
IKeyBackupRoomSessions,
IKeyBackupSession,
} from "./crypto/keybackup";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
import { MatrixScheduler } from "./scheduler";
import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon";
import { AuthDict } from "./interactive-auth";
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator";
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api";
import { EventTimelineSet } from "./models/event-timeline-set";
import { VerificationRequest } from "./crypto/verification/request/VerificationRequest";
import { VerificationBase as Verification } from "./crypto/verification/Base";
import * as ContentHelpers from "./content-helpers";
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning";
import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room";
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member";
import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state";
} from "./crypto/keybackup.ts";
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider.ts";
import { MatrixScheduler } from "./scheduler.ts";
import { BeaconEvent, BeaconEventHandlerMap } from "./models/beacon.ts";
import { AuthDict } from "./interactive-auth.ts";
import { IMinimalEvent, IRoomEvent, IStateEvent } from "./sync-accumulator.ts";
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./crypto/api.ts";
import { EventTimelineSet } from "./models/event-timeline-set.ts";
import { VerificationRequest } from "./crypto/verification/request/VerificationRequest.ts";
import { VerificationBase as Verification } from "./crypto/verification/Base.ts";
import * as ContentHelpers from "./content-helpers.ts";
import { CrossSigningInfo, DeviceTrustLevel, ICacheCallbacks, UserTrustLevel } from "./crypto/CrossSigning.ts";
import { NotificationCountType, Room, RoomEvent, RoomEventHandlerMap, RoomNameState } from "./models/room.ts";
import { RoomMemberEvent, RoomMemberEventHandlerMap } from "./models/room-member.ts";
import { IPowerLevelsContent, RoomStateEvent, RoomStateEventHandlerMap } from "./models/room-state.ts";
import {
DelayedEventInfo,
IAddThreePidOnlyBody,
IBindThreePidBody,
IContextResponse,
@@ -134,7 +135,10 @@ import {
IStatusResponse,
ITagsResponse,
KnockRoomOpts,
} from "./@types/requests";
SendDelayedEventRequestOpts,
SendDelayedEventResponse,
UpdateDelayedEventAction,
} from "./@types/requests.ts";
import {
EventType,
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
@@ -149,15 +153,22 @@ import {
UNSTABLE_MSC3088_ENABLED,
UNSTABLE_MSC3088_PURPOSE,
UNSTABLE_MSC3089_TREE_SUBTYPE,
} from "./@types/event";
import { GuestAccess, HistoryVisibility, IdServerUnbindResult, JoinRule, Preset, Visibility } from "./@types/partials";
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper";
import { randomString } from "./randomstring";
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace";
import { ISignatures } from "./@types/signed";
import { IStore } from "./store";
import { ISecretRequest } from "./crypto/SecretStorage";
} from "./@types/event.ts";
import {
GuestAccess,
HistoryVisibility,
IdServerUnbindResult,
JoinRule,
Preset,
Visibility,
} from "./@types/partials.ts";
import { EventMapper, eventMapperFor, MapperOpts } from "./event-mapper.ts";
import { randomString } from "./randomstring.ts";
import { BackupManager, IKeyBackup, IKeyBackupCheck, IPreparedKeyBackupVersion, TrustInfo } from "./crypto/backup.ts";
import { DEFAULT_TREE_POWER_LEVELS_TEMPLATE, MSC3089TreeSpace } from "./models/MSC3089TreeSpace.ts";
import { ISignatures } from "./@types/signed.ts";
import { IStore } from "./store/index.ts";
import { ISecretRequest } from "./crypto/SecretStorage.ts";
import {
IEventWithRoomId,
ISearchRequestBody,
@@ -165,9 +176,9 @@ import {
ISearchResults,
IStateEventWithRoomId,
SearchOrderBy,
} from "./@types/search";
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse";
import { IHierarchyRoom } from "./@types/spaces";
} from "./@types/search.ts";
import { ISynapseAdminDeactivateResponse, ISynapseAdminWhoisResponse } from "./@types/synapse.ts";
import { IHierarchyRoom } from "./@types/spaces.ts";
import {
IPusher,
IPusherRequest,
@@ -177,11 +188,11 @@ import {
PushRuleActionName,
PushRuleKind,
RuleId,
} from "./@types/PushRules";
import { IThreepid } from "./@types/threepids";
import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base";
import { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from "./webrtc/groupCall";
import { MediaHandler } from "./webrtc/mediaHandler";
} from "./@types/PushRules.ts";
import { IThreepid } from "./@types/threepids.ts";
import { CryptoStore, OutgoingRoomKeyRequest } from "./crypto/store/base.ts";
import { GroupCall, GroupCallIntent, GroupCallType, IGroupCallDataChannelOptions } from "./webrtc/groupCall.ts";
import { MediaHandler } from "./webrtc/mediaHandler.ts";
import {
ILoginFlowsResponse,
IRefreshTokenResponse,
@@ -189,11 +200,11 @@ import {
LoginResponse,
LoginTokenPostResponse,
SSOAction,
} from "./@types/auth";
import { TypedEventEmitter } from "./models/typed-event-emitter";
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts";
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync";
import { SlidingSyncSdk } from "./sliding-sync-sdk";
} from "./@types/auth.ts";
import { TypedEventEmitter } from "./models/typed-event-emitter.ts";
import { MAIN_ROOM_TIMELINE, ReceiptType } from "./@types/read_receipts.ts";
import { MSC3575SlidingSyncRequest, MSC3575SlidingSyncResponse, SlidingSync } from "./sliding-sync.ts";
import { SlidingSyncSdk } from "./sliding-sync-sdk.ts";
import {
determineFeatureSupport,
FeatureSupport,
@@ -201,32 +212,33 @@ import {
THREAD_RELATION_TYPE,
ThreadFilterType,
threadFilterTypeToFilter,
} from "./models/thread";
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./@types/beacon";
import { NamespacedValue, UnstableValue } from "./NamespacedValue";
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue";
import { ToDeviceBatch } from "./models/ToDeviceMessage";
import { IgnoredInvites } from "./models/invites-ignorer";
import { UIARequest, UIAResponse } from "./@types/uia";
import { LocalNotificationSettings } from "./@types/local_notifications";
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature";
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend";
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api";
import { DeviceInfoMap } from "./crypto/DeviceList";
} from "./models/thread.ts";
import { M_BEACON_INFO, MBeaconInfoEventContent } from "./@types/beacon.ts";
import { NamespacedValue, UnstableValue } from "./NamespacedValue.ts";
import { ToDeviceMessageQueue } from "./ToDeviceMessageQueue.ts";
import { ToDeviceBatch } from "./models/ToDeviceMessage.ts";
import { IgnoredInvites } from "./models/invites-ignorer.ts";
import { UIARequest, UIAResponse } from "./@types/uia.ts";
import { LocalNotificationSettings } from "./@types/local_notifications.ts";
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts";
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts";
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api/index.ts";
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
import {
AddSecretStorageKeyOpts,
SecretStorageKeyDescription,
ServerSideSecretStorage,
ServerSideSecretStorageImpl,
} from "./secret-storage";
import { RegisterRequest, RegisterResponse } from "./@types/registration";
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager";
import { getRelationsThreadFilter } from "./thread-utils";
import { KnownMembership, Membership } from "./@types/membership";
import { RoomMessageEventContent, StickerEventContent } from "./@types/events";
import { ImageInfo } from "./@types/media";
import { Capabilities, ServerCapabilities } from "./serverCapabilities";
} from "./secret-storage.ts";
import { RegisterRequest, RegisterResponse } from "./@types/registration.ts";
import { MatrixRTCSessionManager } from "./matrixrtc/MatrixRTCSessionManager.ts";
import { getRelationsThreadFilter } from "./thread-utils.ts";
import { KnownMembership, Membership } from "./@types/membership.ts";
import { RoomMessageEventContent, StickerEventContent } from "./@types/events.ts";
import { ImageInfo } from "./@types/media.ts";
import { Capabilities, ServerCapabilities } from "./serverCapabilities.ts";
import { sha256 } from "./digest.ts";
export type Store = IStore;
@@ -324,10 +336,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;
@@ -527,6 +542,8 @@ export const UNSTABLE_MSC2666_SHARED_ROOMS = "uk.half-shot.msc2666";
export const UNSTABLE_MSC2666_MUTUAL_ROOMS = "uk.half-shot.msc2666.mutual_rooms";
export const UNSTABLE_MSC2666_QUERY_MUTUAL_ROOMS = "uk.half-shot.msc2666.query_mutual_rooms";
export const UNSTABLE_MSC4140_DELAYED_EVENTS = "org.matrix.msc4140";
enum CrossSigningKeyType {
MasterKey = "master_key",
SelfSigningKey = "self_signing_key",
@@ -4570,12 +4587,19 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
threadId = threadIdOrEventType;
}
// If we expect that an event is part of a thread but is missing the relation
// we need to add it manually, as well as the reply fallback
if (threadId && !content!["m.relates_to"]?.rel_type) {
const isReply = !!content!["m.relates_to"]?.["m.in_reply_to"];
content!["m.relates_to"] = {
...content!["m.relates_to"],
this.addThreadRelationIfNeeded(content, threadId, roomId);
return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId);
}
/**
* If we expect that an event is part of a thread but is missing the relation
* we need to add it manually, as well as the reply fallback
*/
private addThreadRelationIfNeeded(content: IContent, threadId: string | null, roomId: string): void {
if (threadId && !content["m.relates_to"]?.rel_type) {
const isReply = !!content["m.relates_to"]?.["m.in_reply_to"];
content["m.relates_to"] = {
...content["m.relates_to"],
rel_type: THREAD_RELATION_TYPE.name,
event_id: threadId,
// Set is_falling_back to true unless this is actually intended to be a reply
@@ -4583,7 +4607,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
};
const thread = this.getRoom(roomId)?.getThread(threadId);
if (thread && !isReply) {
content!["m.relates_to"]["m.in_reply_to"] = {
content["m.relates_to"]["m.in_reply_to"] = {
event_id:
thread
.lastReply((ev: MatrixEvent) => {
@@ -4593,8 +4617,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
};
}
}
return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, txnId);
}
/**
@@ -4608,7 +4630,38 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
threadId: string | null,
eventObject: Partial<IEvent>,
txnId?: string,
): Promise<ISendEventResponse> {
): Promise<ISendEventResponse>;
/**
* Sends a delayed event (MSC4140).
* @param eventObject - An object with the partial structure of an event, to which event_id, user_id, room_id and origin_server_ts will be added.
* @param delayOpts - Properties of the delay for this event.
* @param txnId - Optional.
* @returns Promise which resolves: to an empty object `{}`
* @returns Rejects: with an error response.
*/
private sendCompleteEvent(
roomId: string,
threadId: string | null,
eventObject: Partial<IEvent>,
delayOpts: SendDelayedEventRequestOpts,
txnId?: string,
): Promise<SendDelayedEventResponse>;
private sendCompleteEvent(
roomId: string,
threadId: string | null,
eventObject: Partial<IEvent>,
delayOptsOrTxnId?: SendDelayedEventRequestOpts | string,
txnIdOrVoid?: string,
): Promise<ISendEventResponse | SendDelayedEventResponse> {
let delayOpts: SendDelayedEventRequestOpts | undefined;
let txnId: string | undefined;
if (typeof delayOptsOrTxnId === "string") {
txnId = delayOptsOrTxnId;
} else {
delayOpts = delayOptsOrTxnId;
txnId = txnIdOrVoid;
}
if (!txnId) {
txnId = this.makeTxnId();
}
@@ -4631,9 +4684,11 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
localEvent.setThread(thread);
}
// set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here
this.reEmitter.reEmit(localEvent, [MatrixEventEvent.Replaced, MatrixEventEvent.VisibilityChange]);
room?.reEmitter.reEmit(localEvent, [MatrixEventEvent.BeforeRedaction]);
if (!delayOpts) {
// set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here
this.reEmitter.reEmit(localEvent, [MatrixEventEvent.Replaced, MatrixEventEvent.VisibilityChange]);
room?.reEmitter.reEmit(localEvent, [MatrixEventEvent.BeforeRedaction]);
}
// if this is a relation or redaction of an event
// that hasn't been sent yet (e.g. with a local id starting with a ~)
@@ -4648,29 +4703,56 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
const type = localEvent.getType();
this.logger.debug(`sendEvent of type ${type} in ${roomId} with txnId ${txnId}`);
this.logger.debug(
`sendEvent of type ${type} in ${roomId} with txnId ${txnId}${delayOpts ? " (delayed event)" : ""}`,
);
localEvent.setTxnId(txnId);
localEvent.setStatus(EventStatus.SENDING);
// add this event immediately to the local store as 'sending'.
room?.addPendingEvent(localEvent, txnId);
// TODO: separate store for delayed events?
if (!delayOpts) {
// add this event immediately to the local store as 'sending'.
room?.addPendingEvent(localEvent, txnId);
// addPendingEvent can change the state to NOT_SENT if it believes
// that there's other events that have failed. We won't bother to
// try sending the event if the state has changed as such.
if (localEvent.status === EventStatus.NOT_SENT) {
return Promise.reject(new Error("Event blocked by other events not yet sent"));
// addPendingEvent can change the state to NOT_SENT if it believes
// that there's other events that have failed. We won't bother to
// try sending the event if the state has changed as such.
if (localEvent.status === EventStatus.NOT_SENT) {
return Promise.reject(new Error("Event blocked by other events not yet sent"));
}
return this.encryptAndSendEvent(room, localEvent);
} else {
return this.encryptAndSendEvent(room, localEvent, delayOpts);
}
return this.encryptAndSendEvent(room, localEvent);
}
/**
* encrypts the event if necessary; adds the event to the queue, or sends it; marks the event as sent/unsent
* @returns returns a promise which resolves with the result of the send request
*/
protected async encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse> {
protected async encryptAndSendEvent(room: Room | null, event: MatrixEvent): Promise<ISendEventResponse>;
/**
* Simply sends a delayed event without encrypting it.
* TODO: Allow encrypted delayed events, and encrypt them properly
* @param delayOpts - Properties of the delay for this event.
* @returns returns a promise which resolves with the result of the delayed send request
*/
protected async encryptAndSendEvent(
room: Room | null,
event: MatrixEvent,
delayOpts: SendDelayedEventRequestOpts,
): Promise<SendDelayedEventResponse>;
protected async encryptAndSendEvent(
room: Room | null,
event: MatrixEvent,
delayOpts?: SendDelayedEventRequestOpts,
): Promise<ISendEventResponse | SendDelayedEventResponse> {
if (delayOpts) {
return this.sendEventHttpRequest(event, delayOpts);
}
try {
let cancelled: boolean;
this.eventsBeingEncrypted.add(event.getId()!);
@@ -4821,7 +4903,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
}
private sendEventHttpRequest(event: MatrixEvent): Promise<ISendEventResponse> {
private sendEventHttpRequest(event: MatrixEvent): Promise<ISendEventResponse>;
private sendEventHttpRequest(
event: MatrixEvent,
delayOpts: SendDelayedEventRequestOpts,
): Promise<SendDelayedEventResponse>;
private sendEventHttpRequest(
event: MatrixEvent,
delayOpts?: SendDelayedEventRequestOpts,
): Promise<ISendEventResponse | SendDelayedEventResponse> {
let txnId = event.getTxnId();
if (!txnId) {
txnId = this.makeTxnId();
@@ -4853,12 +4943,20 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
path = utils.encodeUri("/rooms/$roomId/send/$eventType/$txnId", pathParams);
}
return this.http
.authedRequest<ISendEventResponse>(Method.Put, path, undefined, event.getWireContent())
.then((res) => {
const content = event.getWireContent();
if (!delayOpts) {
return this.http.authedRequest<ISendEventResponse>(Method.Put, path, undefined, content).then((res) => {
this.logger.debug(`Event sent to ${event.getRoomId()} with event id ${res.event_id}`);
return res;
});
} else {
return this.http.authedRequest<SendDelayedEventResponse>(
Method.Put,
path,
getUnstableDelayQueryOpts(delayOpts),
content,
);
}
}
/**
@@ -5188,6 +5286,101 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return this.sendMessage(roomId, threadId, content);
}
/**
* Send a delayed timeline event.
*
* Note: This endpoint is unstable, and can throw an `Error`.
* Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
*/
// eslint-disable-next-line
public async _unstable_sendDelayedEvent<K extends keyof TimelineEvents>(
roomId: string,
delayOpts: SendDelayedEventRequestOpts,
threadId: string | null,
eventType: K,
content: TimelineEvents[K],
txnId?: string,
): Promise<SendDelayedEventResponse> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
this.addThreadRelationIfNeeded(content, threadId, roomId);
return this.sendCompleteEvent(roomId, threadId, { type: eventType, content }, delayOpts, txnId);
}
/**
* Send a delayed state event.
*
* Note: This endpoint is unstable, and can throw an `Error`.
* Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
*/
// eslint-disable-next-line
public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(
roomId: string,
delayOpts: SendDelayedEventRequestOpts,
eventType: K,
content: StateEvents[K],
stateKey = "",
opts: IRequestOpts = {},
): Promise<SendDelayedEventResponse> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
const pathParams = {
$roomId: roomId,
$eventType: eventType,
$stateKey: stateKey,
};
let path = utils.encodeUri("/rooms/$roomId/state/$eventType", pathParams);
if (stateKey !== undefined) {
path = utils.encodeUri(path + "/$stateKey", pathParams);
}
return this.http.authedRequest(Method.Put, path, getUnstableDelayQueryOpts(delayOpts), content as Body, opts);
}
/**
* Get all pending delayed events for the calling user.
*
* Note: This endpoint is unstable, and can throw an `Error`.
* Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
*/
// eslint-disable-next-line
public async _unstable_getDelayedEvents(fromToken?: string): Promise<DelayedEventInfo> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
const queryDict = fromToken ? { from: fromToken } : undefined;
return await this.http.authedRequest(Method.Get, "/delayed_events", queryDict, undefined, {
prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,
});
}
/**
* Manage a delayed event associated with the given delay_id.
*
* Note: This endpoint is unstable, and can throw an `Error`.
* Check progress on [MSC4140](https://github.com/matrix-org/matrix-spec-proposals/pull/4140) for more details.
*/
// eslint-disable-next-line
public async _unstable_updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise<{}> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
const path = utils.encodeUri("/delayed_events/$delayId", {
$delayId: delayId,
});
const data = {
action,
};
return await this.http.authedRequest(Method.Post, path, undefined, data, {
prefix: `${ClientPrefix.Unstable}/${UNSTABLE_MSC4140_DELAYED_EVENTS}`,
});
}
/**
* Send a receipt.
* @param event - The event being acknowledged
@@ -5401,10 +5594,15 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
private findPredecessorRooms(room: Room, verifyLinks: boolean, msc3946ProcessDynamicPredecessor: boolean): Room[] {
const ret: Room[] = [];
const seenRoomIDs = new Set<string>([room.roomId]);
// Work backwards from newer to older rooms
let predecessorRoomId = room.findPredecessor(msc3946ProcessDynamicPredecessor)?.roomId;
while (predecessorRoomId !== null) {
if (predecessorRoomId) {
if (seenRoomIDs.has(predecessorRoomId)) break;
seenRoomIDs.add(predecessorRoomId);
}
const predecessorRoom = this.getRoom(predecessorRoomId);
if (predecessorRoom === null) {
break;
@@ -9299,20 +9497,19 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// When picking an algorithm, we pick the hashed over no hashes
if (hashes["algorithms"].includes("sha256")) {
// Abuse the olm hashing
const olmutil = new global.Olm.Utility();
params["addresses"] = addressPairs.map((p) => {
const addr = p[0].toLowerCase(); // lowercase to get consistent hashes
const med = p[1].toLowerCase();
const hashed = olmutil
.sha256(`${addr} ${med} ${params["pepper"]}`)
.replace(/\+/g, "-")
.replace(/\//g, "_"); // URL-safe base64
// Map the hash to a known (case-sensitive) address. We use the case
// sensitive version because the caller might be expecting that.
localMapping[hashed] = p[0];
return hashed;
});
params["addresses"] = await Promise.all(
addressPairs.map(async (p) => {
const addr = p[0].toLowerCase(); // lowercase to get consistent hashes
const med = p[1].toLowerCase();
const hashBuffer = await sha256(`${addr} ${med} ${params["pepper"]}`);
const hashed = encodeUnpaddedBase64Url(hashBuffer);
// Map the hash to a known (case-sensitive) address. We use the case
// sensitive version because the caller might be expecting that.
localMapping[hashed] = p[0];
return hashed;
}),
);
params["algorithm"] = "sha256";
} else if (hashes["algorithms"].includes("none")) {
params["addresses"] = addressPairs.map((p) => {
@@ -9889,6 +10086,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
}
function getUnstableDelayQueryOpts(delayOpts: SendDelayedEventRequestOpts): QueryDict {
return Object.fromEntries(
Object.entries(delayOpts).map(([k, v]) => [`${UNSTABLE_MSC4140_DELAYED_EVENTS}.${k}`, v]),
);
}
/**
* recalculates an accurate notifications count on event decryption.
* Servers do not have enough knowledge about encrypted events to calculate an
+8 -8
View File
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator";
import { IClearEvent, MatrixEvent } from "../models/event";
import { Room } from "../models/room";
import { CryptoApi, DecryptionFailureCode, ImportRoomKeysOpts } from "../crypto-api";
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning";
import { IEncryptedEventInfo } from "../crypto/api";
import { KeyBackupInfo, KeyBackupSession } from "../crypto-api/keybackup";
import { IMegolmSessionData } from "../@types/crypto";
import type { IDeviceLists, IToDeviceEvent } from "../sync-accumulator.ts";
import { IClearEvent, MatrixEvent } from "../models/event.ts";
import { Room } from "../models/room.ts";
import { CryptoApi, DecryptionFailureCode, ImportRoomKeysOpts } from "../crypto-api/index.ts";
import { CrossSigningInfo, UserTrustLevel } from "../crypto/CrossSigning.ts";
import { IEncryptedEventInfo } from "../crypto/api.ts";
import { KeyBackupInfo, KeyBackupSession } from "../crypto-api/keybackup.ts";
import { IMegolmSessionData } from "../@types/crypto.ts";
/**
* Common interface for the crypto implementations
+7 -7
View File
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon";
import { MsgType } from "./@types/event";
import { M_TEXT, REFERENCE_RELATION } from "./@types/extensible_events";
import { isProvided } from "./extensible_events_v1/utilities";
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon.ts";
import { MsgType } from "./@types/event.ts";
import { M_TEXT, REFERENCE_RELATION } from "./@types/extensible_events.ts";
import { isProvided } from "./extensible_events_v1/utilities.ts";
import {
M_ASSET,
LocationAssetType,
@@ -28,9 +28,9 @@ import {
MLocationContent,
MAssetContent,
LegacyLocationEventContent,
} from "./@types/location";
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic";
import { RoomMessageEventContent } from "./@types/events";
} from "./@types/location.ts";
import { MRoomTopicEventContent, MTopicContent, M_TOPIC } from "./@types/topic.ts";
import { RoomMessageEventContent } from "./@types/events.ts";
/**
* Generates the content for a HTML Message event
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { encodeParams } from "./utils";
import { encodeParams } from "./utils.ts";
/**
* Get the HTTP URL for an MXC URI.
+29 -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 "./verification";
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup";
import { ISignatures } from "../@types/signed";
import { MatrixEvent } from "../models/event";
import type { IMegolmSessionData } from "../@types/crypto.ts";
import { Room } from "../models/room.ts";
import { DeviceMap } from "../models/device.ts";
import { UIAuthCallback } from "../interactive-auth.ts";
import { PassphraseInfo, SecretStorageCallbacks, SecretStorageKeyDescription } from "../secret-storage.ts";
import { VerificationRequest } from "./verification.ts";
import { BackupTrustInfo, KeyBackupCheck, KeyBackupInfo } from "./keybackup.ts";
import { ISignatures } from "../@types/signed.ts";
import { MatrixEvent } from "../models/event.ts";
/**
* 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",
@@ -803,6 +809,8 @@ export interface CryptoCallbacks extends SecretStorageCallbacks {
* @param key - private key to store
*/
cacheSecretStorageKey?: (keyId: string, keyInfo: SecretStorageKeyDescription, key: Uint8Array) => void;
/** @deprecated: unused with the Rust crypto stack. */
onSecretRequested?: (
userId: string,
deviceId: string,
@@ -810,10 +818,13 @@ export interface CryptoCallbacks extends SecretStorageCallbacks {
secretName: string,
deviceTrust: DeviceVerificationStatus,
) => Promise<string | undefined>;
/** @deprecated: unused with the Rust crypto stack. */
getDehydrationKey?: (
keyInfo: SecretStorageKeyDescription,
checkFunc: (key: Uint8Array) => void,
) => Promise<Uint8Array>;
getBackupKey?: () => Promise<Uint8Array>;
}
@@ -848,9 +859,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 +965,5 @@ export interface OwnDeviceKeys {
curve25519: string;
}
export * from "./verification";
export * from "./keybackup";
export * from "./verification.ts";
export * from "./keybackup.ts";
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ISigned } from "../@types/signed";
import { IEncryptedPayload } from "../crypto/aes";
import { ISigned } from "../@types/signed.ts";
import { IEncryptedPayload } from "../crypto/aes.ts";
export interface Curve25519AuthData {
public_key: string;
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "../models/event";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { MatrixEvent } from "../models/event.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
/**
* An incoming, or outgoing, request to verify a user or a device via cross-signing.
+17 -13
View File
@@ -19,19 +19,23 @@ limitations under the License.
*/
import type { PkSigning } from "@matrix-org/olm";
import { IObject, pkSign, pkVerify } from "./olmlib";
import { logger } from "../logger";
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store";
import { decryptAES, encryptAES } from "./aes";
import { DeviceInfo } from "./deviceinfo";
import { ISignedKey, MatrixClient } from "../client";
import { OlmDevice } from "./OlmDevice";
import { ICryptoCallbacks } from ".";
import { ISignatures } from "../@types/signed";
import { CryptoStore, SecretStorePrivateKeys } from "./store/base";
import { ServerSideSecretStorage, SecretStorageKeyDescription } from "../secret-storage";
import { CrossSigningKeyInfo, DeviceVerificationStatus, UserVerificationStatus as UserTrustLevel } from "../crypto-api";
import { decodeBase64, encodeBase64 } from "../base64";
import { IObject, pkSign, pkVerify } from "./olmlib.ts";
import { logger } from "../logger.ts";
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store.ts";
import { decryptAES, encryptAES } from "./aes.ts";
import { DeviceInfo } from "./deviceinfo.ts";
import { ISignedKey, MatrixClient } from "../client.ts";
import { OlmDevice } from "./OlmDevice.ts";
import { ICryptoCallbacks } from "./index.ts";
import { ISignatures } from "../@types/signed.ts";
import { CryptoStore, SecretStorePrivateKeys } from "./store/base.ts";
import { ServerSideSecretStorage, SecretStorageKeyDescription } from "../secret-storage.ts";
import {
CrossSigningKeyInfo,
DeviceVerificationStatus,
UserVerificationStatus as UserTrustLevel,
} from "../crypto-api/index.ts";
import { decodeBase64, encodeBase64 } from "../base64.ts";
// backwards-compatibility re-exports
export { UserTrustLevel };
+11 -11
View File
@@ -18,17 +18,17 @@ limitations under the License.
* Manages the list of other users' devices
*/
import { logger } from "../logger";
import { DeviceInfo, IDevice } from "./deviceinfo";
import { CrossSigningInfo, ICrossSigningInfo } from "./CrossSigning";
import * as olmlib from "./olmlib";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { chunkPromises, defer, IDeferred, sleep } from "../utils";
import { DeviceKeys, IDownloadKeyResult, Keys, MatrixClient, SigningKeys } from "../client";
import { OlmDevice } from "./OlmDevice";
import { CryptoStore } from "./store/base";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { CryptoEvent, CryptoEventHandlerMap } from "./index";
import { logger } from "../logger.ts";
import { DeviceInfo, IDevice } from "./deviceinfo.ts";
import { CrossSigningInfo, ICrossSigningInfo } from "./CrossSigning.ts";
import * as olmlib from "./olmlib.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { chunkPromises, defer, IDeferred, sleep } from "../utils.ts";
import { DeviceKeys, IDownloadKeyResult, Keys, MatrixClient, SigningKeys } from "../client.ts";
import { OlmDevice } from "./OlmDevice.ts";
import { CryptoStore } from "./store/base.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { CryptoEvent, CryptoEventHandlerMap } from "./index.ts";
/* State transition diagram for DeviceList.deviceTrackingStatus
*
+11 -11
View File
@@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../logger";
import { MatrixEvent } from "../models/event";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { Method, ClientPrefix } from "../http-api";
import { Crypto, ICryptoCallbacks } from "./index";
import { ClientEvent, ClientEventHandlerMap, CrossSigningKeys, ISignedKey, KeySignatures } from "../client";
import { IKeyBackupInfo } from "./keybackup";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo } from "../crypto-api";
import { logger } from "../logger.ts";
import { MatrixEvent } from "../models/event.ts";
import { createCryptoStoreCacheCallbacks, ICacheCallbacks } from "./CrossSigning.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { Method, ClientPrefix } from "../http-api/index.ts";
import { Crypto, ICryptoCallbacks } from "./index.ts";
import { ClientEvent, ClientEventHandlerMap, CrossSigningKeys, ISignedKey, KeySignatures } from "../client.ts";
import { IKeyBackupInfo } from "./keybackup.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { AccountDataClient, SecretStorageKeyDescription } from "../secret-storage.ts";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo } from "../crypto-api/index.ts";
interface ICrossSigningKeys {
authUpload: BootstrapCrossSigningOpts["authUploadDeviceSigningKeys"];
+22 -22
View File
@@ -16,14 +16,14 @@ limitations under the License.
import { Account, InboundGroupSession, OutboundGroupSession, Session, Utility } from "@matrix-org/olm";
import { logger, Logger } from "../logger";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base";
import { IOlmDevice, IOutboundGroupSessionKey } from "./algorithms/megolm";
import { IMegolmSessionData, OlmGroupSessionExtraData } from "../@types/crypto";
import { IMessage } from "./algorithms/olm";
import { DecryptionFailureCode } from "../crypto-api";
import { DecryptionError } from "../common-crypto/CryptoBackend";
import { logger, Logger } from "../logger.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { CryptoStore, IProblem, ISessionInfo, IWithheld } from "./store/base.ts";
import { IOlmDevice, IOutboundGroupSessionKey } from "./algorithms/megolm.ts";
import { IMegolmSessionData, OlmGroupSessionExtraData } from "../@types/crypto.ts";
import { IMessage } from "./algorithms/olm.ts";
import { DecryptionFailureCode } from "../crypto-api/index.ts";
import { DecryptionError } from "../common-crypto/CryptoBackend.ts";
// The maximum size of an event is 65K, and we base64 the content, so this is a
// reasonable approximation to the biggest plaintext we can encrypt.
@@ -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;
}
+6 -6
View File
@@ -16,12 +16,12 @@ limitations under the License.
import { v4 as uuidv4 } from "uuid";
import { logger } from "../logger";
import { MatrixClient } from "../client";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index";
import { CryptoStore, OutgoingRoomKeyRequest } from "./store/base";
import { EventType, ToDeviceMessageId } from "../@types/event";
import { MapWithDefault } from "../utils";
import { logger } from "../logger.ts";
import { MatrixClient } from "../client.ts";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "./index.ts";
import { CryptoStore, OutgoingRoomKeyRequest } from "./store/base.ts";
import { EventType, ToDeviceMessageId } from "../@types/event.ts";
import { MapWithDefault } from "../utils.ts";
/**
* Internal module. Management of outgoing room key requests.
+2 -2
View File
@@ -18,8 +18,8 @@ limitations under the License.
* Manages the list of encrypted rooms
*/
import { CryptoStore } from "./store/base";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { CryptoStore } from "./store/base.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
/* eslint-disable camelcase */
export interface IRoomEncryption {
+7 -7
View File
@@ -15,13 +15,13 @@ limitations under the License.
*/
import { v4 as uuidv4 } from "uuid";
import { MatrixClient } from "../client";
import { ICryptoCallbacks, IEncryptedContent } from "./index";
import { defer, IDeferred } from "../utils";
import { ToDeviceMessageId } from "../@types/event";
import { logger } from "../logger";
import { MatrixEvent } from "../models/event";
import * as olmlib from "./olmlib";
import { MatrixClient } from "../client.ts";
import { ICryptoCallbacks, IEncryptedContent } from "./index.ts";
import { defer, IDeferred } from "../utils.ts";
import { ToDeviceMessageId } from "../@types/event.ts";
import { logger } from "../logger.ts";
import { MatrixEvent } from "../models/event.ts";
import * as olmlib from "./olmlib.ts";
export interface ISecretRequest {
requestId: string;
+7 -7
View File
@@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ICryptoCallbacks } from ".";
import { MatrixEvent } from "../models/event";
import { MatrixClient } from "../client";
import { ICryptoCallbacks } from "./index.ts";
import { MatrixEvent } from "../models/event.ts";
import { MatrixClient } from "../client.ts";
import {
SecretStorageKeyDescription,
SecretStorageKeyTuple,
@@ -25,8 +25,8 @@ import {
AccountDataClient,
ServerSideSecretStorage,
ServerSideSecretStorageImpl,
} from "../secret-storage";
import { ISecretRequest, SecretSharing } from "./SecretSharing";
} from "../secret-storage.ts";
import { ISecretRequest, SecretSharing } from "./SecretSharing.ts";
/* re-exports for backwards compatibility */
export type {
@@ -34,9 +34,9 @@ export type {
SecretStorageKeyTuple,
SecretStorageKeyObject,
SECRET_STORAGE_ALGORITHM_V1_AES,
} from "../secret-storage";
} from "../secret-storage.ts";
export type { ISecretRequest } from "./SecretSharing";
export type { ISecretRequest } from "./SecretSharing.ts";
/**
* Implements Secure Secret Storage and Sharing (MSC1946)
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { decodeBase64, encodeBase64 } from "../base64";
import { decodeBase64, encodeBase64 } from "../base64.ts";
// salt for HKDF, with 8 bytes of zeros
const zeroSalt = new Uint8Array(8);
+10 -14
View File
@@ -18,15 +18,15 @@ limitations under the License.
* Internal module. Defines the base classes of the encryption implementations
*/
import type { IMegolmSessionData } from "../../@types/crypto";
import { MatrixClient } from "../../client";
import { Room } from "../../models/room";
import { OlmDevice } from "../OlmDevice";
import { IContent, MatrixEvent, RoomMember } from "../../matrix";
import { Crypto, IEncryptedContent, IEventDecryptionResult, IncomingRoomKeyRequest } from "..";
import { DeviceInfo } from "../deviceinfo";
import { IRoomEncryption } from "../RoomList";
import { DeviceInfoMap } from "../DeviceList";
import type { IMegolmSessionData } from "../../@types/crypto.ts";
import { MatrixClient } from "../../client.ts";
import { Room } from "../../models/room.ts";
import { OlmDevice } from "../OlmDevice.ts";
import { IContent, MatrixEvent, RoomMember } from "../../matrix.ts";
import { Crypto, IEncryptedContent, IEventDecryptionResult, IncomingRoomKeyRequest } from "../index.ts";
import { DeviceInfo } from "../deviceinfo.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { DeviceInfoMap } from "../DeviceList.ts";
/**
* Map of registered encryption algorithm classes. A map from string to {@link EncryptionAlgorithm} class
@@ -66,7 +66,6 @@ export abstract class EncryptionAlgorithm {
protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient;
protected readonly roomId?: string;
/**
* @param params - parameters
@@ -77,7 +76,6 @@ export abstract class EncryptionAlgorithm {
this.crypto = params.crypto;
this.olmDevice = params.olmDevice;
this.baseApis = params.baseApis;
this.roomId = params.roomId;
}
/**
@@ -127,14 +125,12 @@ export abstract class DecryptionAlgorithm {
protected readonly crypto: Crypto;
protected readonly olmDevice: OlmDevice;
protected readonly baseApis: MatrixClient;
protected readonly roomId?: string;
public constructor(params: DecryptionClassParams) {
this.userId = params.userId;
this.crypto = params.crypto;
this.olmDevice = params.olmDevice;
this.baseApis = params.baseApis;
this.roomId = params.roomId;
}
/**
@@ -237,4 +233,4 @@ export function registerAlgorithm<P extends IParams = IParams>(
}
/* Re-export for backwards compatibility. Deprecated: this is an internal class. */
export { DecryptionError } from "../../common-crypto/CryptoBackend";
export { DecryptionError } from "../../common-crypto/CryptoBackend.ts";
+3 -3
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import "./olm";
import "./megolm";
import "./olm.ts";
import "./megolm.ts";
export * from "./base";
export * from "./base.ts";
+19 -19
View File
@@ -20,9 +20,9 @@ limitations under the License.
import { v4 as uuidv4 } from "uuid";
import type { IEventDecryptionResult, IMegolmSessionData } from "../../@types/crypto";
import { logger, Logger } from "../../logger";
import * as olmlib from "../olmlib";
import type { IEventDecryptionResult, IMegolmSessionData } from "../../@types/crypto.ts";
import { logger, Logger } from "../../logger.ts";
import * as olmlib from "../olmlib.ts";
import {
DecryptionAlgorithm,
DecryptionClassParams,
@@ -30,22 +30,22 @@ import {
IParams,
registerAlgorithm,
UnknownDeviceError,
} from "./base";
import { IDecryptedGroupMessage, WITHHELD_MESSAGES } from "../OlmDevice";
import { Room } from "../../models/room";
import { DeviceInfo } from "../deviceinfo";
import { IOlmSessionResult } from "../olmlib";
import { DeviceInfoMap } from "../DeviceList";
import { IContent, MatrixEvent } from "../../models/event";
import { EventType, MsgType, ToDeviceMessageId } from "../../@types/event";
import { IMegolmEncryptedContent, IncomingRoomKeyRequest, IEncryptedContent } from "../index";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
import { OlmGroupSessionExtraData } from "../../@types/crypto";
import { MatrixError } from "../../http-api";
import { immediate, MapWithDefault } from "../../utils";
import { KnownMembership } from "../../@types/membership";
import { DecryptionFailureCode } from "../../crypto-api";
import { DecryptionError } from "../../common-crypto/CryptoBackend";
} from "./base.ts";
import { IDecryptedGroupMessage, WITHHELD_MESSAGES } from "../OlmDevice.ts";
import { Room } from "../../models/room.ts";
import { DeviceInfo } from "../deviceinfo.ts";
import { IOlmSessionResult } from "../olmlib.ts";
import { DeviceInfoMap } from "../DeviceList.ts";
import { IContent, MatrixEvent } from "../../models/event.ts";
import { EventType, MsgType, ToDeviceMessageId } from "../../@types/event.ts";
import { IMegolmEncryptedContent, IncomingRoomKeyRequest, IEncryptedContent } from "../index.ts";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager.ts";
import { OlmGroupSessionExtraData } from "../../@types/crypto.ts";
import { MatrixError } from "../../http-api/index.ts";
import { immediate, MapWithDefault } from "../../utils.ts";
import { KnownMembership } from "../../@types/membership.ts";
import { DecryptionFailureCode } from "../../crypto-api/index.ts";
import { DecryptionError } from "../../common-crypto/CryptoBackend.ts";
// determine whether the key can be shared with invitees
export function isRoomSharedHistory(room: Room): boolean {
+11 -11
View File
@@ -18,17 +18,17 @@ limitations under the License.
* Defines m.olm encryption/decryption
*/
import type { IEventDecryptionResult } from "../../@types/crypto";
import { logger } from "../../logger";
import * as olmlib from "../olmlib";
import { DeviceInfo } from "../deviceinfo";
import { DecryptionAlgorithm, EncryptionAlgorithm, registerAlgorithm } from "./base";
import { Room } from "../../models/room";
import { IContent, MatrixEvent } from "../../models/event";
import { IEncryptedContent, IOlmEncryptedContent } from "../index";
import { IInboundSession } from "../OlmDevice";
import { DecryptionFailureCode } from "../../crypto-api";
import { DecryptionError } from "../../common-crypto/CryptoBackend";
import type { IEventDecryptionResult } from "../../@types/crypto.ts";
import { logger } from "../../logger.ts";
import * as olmlib from "../olmlib.ts";
import { DeviceInfo } from "../deviceinfo.ts";
import { DecryptionAlgorithm, EncryptionAlgorithm, registerAlgorithm } from "./base.ts";
import { Room } from "../../models/room.ts";
import { IContent, MatrixEvent } from "../../models/event.ts";
import { IEncryptedContent, IOlmEncryptedContent } from "../index.ts";
import { IInboundSession } from "../OlmDevice.ts";
import { DecryptionFailureCode } from "../../crypto-api/index.ts";
import { DecryptionError } from "../../common-crypto/CryptoBackend.ts";
const DeviceVerification = DeviceInfo.DeviceVerification;
+5 -5
View File
@@ -14,26 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { DeviceInfo } from "./deviceinfo";
import { DeviceInfo } from "./deviceinfo.ts";
/* re-exports for backwards compatibility. */
// CrossSigningKey is used as a value in `client.ts`, we can't export it as a type
export { CrossSigningKey } from "../crypto-api";
export { CrossSigningKey } from "../crypto-api/index.ts";
export type {
GeneratedSecretStorageKey as IRecoveryKey,
CreateSecretStorageOpts as ICreateSecretStorageOpts,
} from "../crypto-api";
} from "../crypto-api/index.ts";
export type {
ImportRoomKeyProgressData as IImportOpts,
ImportRoomKeysOpts as IImportRoomKeysOpts,
} from "../crypto-api";
} from "../crypto-api/index.ts";
export type {
AddSecretStorageKeyOpts as IAddSecretStorageKeyOpts,
PassphraseInfo as IPassphraseInfo,
SecretStorageKeyDescription as ISecretStorageKeyInfo,
} from "../secret-storage";
} from "../secret-storage.ts";
// TODO: Merge this with crypto.js once converted
+17 -17
View File
@@ -18,29 +18,29 @@ limitations under the License.
* Classes for dealing with key backup.
*/
import type { IMegolmSessionData } from "../@types/crypto";
import { MatrixClient } from "../client";
import { logger } from "../logger";
import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib";
import { DeviceInfo } from "./deviceinfo";
import { DeviceTrustLevel } from "./CrossSigning";
import { keyFromPassphrase } from "./key_passphrase";
import { encodeUri, safeSet, sleep } from "../utils";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { encodeRecoveryKey } from "./recoverykey";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
import type { IMegolmSessionData } from "../@types/crypto.ts";
import { MatrixClient } from "../client.ts";
import { logger } from "../logger.ts";
import { MEGOLM_ALGORITHM, verifySignature } from "./olmlib.ts";
import { DeviceInfo } from "./deviceinfo.ts";
import { DeviceTrustLevel } from "./CrossSigning.ts";
import { keyFromPassphrase } from "./key_passphrase.ts";
import { encodeUri, safeSet, sleep } from "../utils.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { encodeRecoveryKey } from "./recoverykey.ts";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
import {
Curve25519SessionData,
IAes256AuthData,
ICurve25519AuthData,
IKeyBackupInfo,
IKeyBackupSession,
} from "./keybackup";
import { UnstableValue } from "../NamespacedValue";
import { CryptoEvent } from "./index";
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api";
import { BackupTrustInfo } from "../crypto-api/keybackup";
import { BackupDecryptor } from "../common-crypto/CryptoBackend";
} from "./keybackup.ts";
import { UnstableValue } from "../NamespacedValue.ts";
import { CryptoEvent } from "./index.ts";
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts";
import { BackupTrustInfo } from "../crypto-api/keybackup.ts";
import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
+8 -8
View File
@@ -16,14 +16,14 @@ limitations under the License.
import anotherjson from "another-json";
import type { IDeviceKeys, IOneTimeKey } from "../@types/crypto";
import { decodeBase64, encodeBase64 } from "../base64";
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store";
import { decryptAES, encryptAES } from "./aes";
import { logger } from "../logger";
import { Crypto } from "./index";
import { Method } from "../http-api";
import { SecretStorageKeyDescription } from "../secret-storage";
import type { IDeviceKeys, IOneTimeKey } from "../@types/crypto.ts";
import { decodeBase64, encodeBase64 } from "../base64.ts";
import { IndexedDBCryptoStore } from "../crypto/store/indexeddb-crypto-store.ts";
import { decryptAES, encryptAES } from "./aes.ts";
import { logger } from "../logger.ts";
import { Crypto } from "./index.ts";
import { Method } from "../http-api/index.ts";
import { SecretStorageKeyDescription } from "../secret-storage.ts";
export interface IDehydratedDevice {
device_id: string; // eslint-disable-line camelcase
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Device } from "../models/device";
import { DeviceInfo } from "./deviceinfo";
import { Device } from "../models/device.ts";
import { DeviceInfo } from "./deviceinfo.ts";
/**
* Convert a {@link DeviceInfo} to a {@link Device}.
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ISignatures } from "../@types/signed";
import { DeviceVerification } from "../models/device";
import { ISignatures } from "../@types/signed.ts";
import { DeviceVerification } from "../models/device.ts";
export interface IDevice {
keys: Record<string, string>;
+63 -58
View File
@@ -20,55 +20,60 @@ limitations under the License.
import anotherjson from "another-json";
import { v4 as uuidv4 } from "uuid";
import type { IDeviceKeys, IEventDecryptionResult, IMegolmSessionData, IOneTimeKey } from "../@types/crypto";
import type { IDeviceKeys, IEventDecryptionResult, IMegolmSessionData, IOneTimeKey } from "../@types/crypto.ts";
import type { PkDecryption, PkSigning } from "@matrix-org/olm";
import { EventType, ToDeviceMessageId } from "../@types/event";
import { TypedReEmitter } from "../ReEmitter";
import { logger } from "../logger";
import { IExportedDevice, OlmDevice } from "./OlmDevice";
import { IOlmDevice } from "./algorithms/megolm";
import * as olmlib from "./olmlib";
import { DeviceInfoMap, DeviceList } from "./DeviceList";
import { DeviceInfo, IDevice } from "./deviceinfo";
import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms";
import * as algorithms from "./algorithms";
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning";
import { EncryptionSetupBuilder } from "./EncryptionSetup";
import { SecretStorage as LegacySecretStorage } from "./SecretStorage";
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./api";
import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store";
import { VerificationBase } from "./verification/Base";
import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode";
import { SAS as SASVerification } from "./verification/SAS";
import { keyFromPassphrase } from "./key_passphrase";
import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey";
import { VerificationRequest } from "./verification/request/VerificationRequest";
import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel";
import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel";
import { IllegalMethod } from "./verification/IllegalMethod";
import { KeySignatureUploadError } from "../errors";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes";
import { DehydrationManager } from "./dehydration";
import { BackupManager, LibOlmBackupDecryptor, backupTrustInfoFromLegacyTrustInfo } from "./backup";
import { IStore } from "../store";
import { Room, RoomEvent } from "../models/room";
import { RoomMember, RoomMemberEvent } from "../models/room-member";
import { EventStatus, IContent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event";
import { ToDeviceBatch } from "../models/ToDeviceMessage";
import { ClientEvent, IKeysUploadResponse, ISignedKey, IUploadKeySignaturesResponse, MatrixClient } from "../client";
import { IRoomEncryption, RoomList } from "./RoomList";
import { IKeyBackupInfo } from "./keybackup";
import { ISyncStateData } from "../sync";
import { CryptoStore } from "./store/base";
import { IVerificationChannel } from "./verification/request/Channel";
import { TypedEventEmitter } from "../models/typed-event-emitter";
import { IDeviceLists, ISyncResponse, IToDeviceEvent } from "../sync-accumulator";
import { ISignatures } from "../@types/signed";
import { IMessage } from "./algorithms/olm";
import { BackupDecryptor, CryptoBackend, DecryptionError, OnSyncCompletedData } from "../common-crypto/CryptoBackend";
import { RoomState, RoomStateEvent } from "../models/room-state";
import { MapWithDefault, recursiveMapToObject } from "../utils";
import { EventType, ToDeviceMessageId } from "../@types/event.ts";
import { TypedReEmitter } from "../ReEmitter.ts";
import { logger } from "../logger.ts";
import { IExportedDevice, OlmDevice } from "./OlmDevice.ts";
import { IOlmDevice } from "./algorithms/megolm.ts";
import * as olmlib from "./olmlib.ts";
import { DeviceInfoMap, DeviceList } from "./DeviceList.ts";
import { DeviceInfo, IDevice } from "./deviceinfo.ts";
import type { DecryptionAlgorithm, EncryptionAlgorithm } from "./algorithms/index.ts";
import * as algorithms from "./algorithms/index.ts";
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./CrossSigning.ts";
import { EncryptionSetupBuilder } from "./EncryptionSetup.ts";
import { SecretStorage as LegacySecretStorage } from "./SecretStorage.ts";
import { CrossSigningKey, ICreateSecretStorageOpts, IEncryptedEventInfo, IRecoveryKey } from "./api.ts";
import { OutgoingRoomKeyRequestManager } from "./OutgoingRoomKeyRequestManager.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { VerificationBase } from "./verification/Base.ts";
import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts";
import { SAS as SASVerification } from "./verification/SAS.ts";
import { keyFromPassphrase } from "./key_passphrase.ts";
import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey.ts";
import { VerificationRequest } from "./verification/request/VerificationRequest.ts";
import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts";
import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts";
import { IllegalMethod } from "./verification/IllegalMethod.ts";
import { KeySignatureUploadError } from "../errors.ts";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
import { DehydrationManager } from "./dehydration.ts";
import { BackupManager, LibOlmBackupDecryptor, backupTrustInfoFromLegacyTrustInfo } from "./backup.ts";
import { IStore } from "../store/index.ts";
import { Room, RoomEvent } from "../models/room.ts";
import { RoomMember, RoomMemberEvent } from "../models/room-member.ts";
import { EventStatus, IContent, IEvent, MatrixEvent, MatrixEventEvent } from "../models/event.ts";
import { ToDeviceBatch } from "../models/ToDeviceMessage.ts";
import { ClientEvent, IKeysUploadResponse, ISignedKey, IUploadKeySignaturesResponse, MatrixClient } from "../client.ts";
import { IRoomEncryption, RoomList } from "./RoomList.ts";
import { IKeyBackupInfo } from "./keybackup.ts";
import { ISyncStateData } from "../sync.ts";
import { CryptoStore } from "./store/base.ts";
import { IVerificationChannel } from "./verification/request/Channel.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { IDeviceLists, ISyncResponse, IToDeviceEvent } from "../sync-accumulator.ts";
import { ISignatures } from "../@types/signed.ts";
import { IMessage } from "./algorithms/olm.ts";
import {
BackupDecryptor,
CryptoBackend,
DecryptionError,
OnSyncCompletedData,
} from "../common-crypto/CryptoBackend.ts";
import { RoomState, RoomStateEvent } from "../models/room-state.ts";
import { MapWithDefault, recursiveMapToObject } from "../utils.ts";
import {
AccountDataClient,
AddSecretStorageKeyOpts,
@@ -77,8 +82,8 @@ import {
SecretStorageKeyObject,
SecretStorageKeyTuple,
ServerSideSecretStorageImpl,
} from "../secret-storage";
import { ISecretRequest } from "./SecretSharing";
} from "../secret-storage.ts";
import { ISecretRequest } from "./SecretSharing.ts";
import {
BackupTrustInfo,
BootstrapCrossSigningOpts,
@@ -94,18 +99,18 @@ import {
KeyBackupInfo,
OwnDeviceKeys,
VerificationRequest as CryptoApiVerificationRequest,
} from "../crypto-api";
import { Device, DeviceMap } from "../models/device";
import { deviceInfoToDevice } from "./device-converter";
import { ClientPrefix, MatrixError, Method } from "../http-api";
import { decodeBase64, encodeBase64 } from "../base64";
import { KnownMembership } from "../@types/membership";
} from "../crypto-api/index.ts";
import { Device, DeviceMap } from "../models/device.ts";
import { deviceInfoToDevice } from "./device-converter.ts";
import { ClientPrefix, MatrixError, Method } from "../http-api/index.ts";
import { decodeBase64, encodeBase64 } from "../base64.ts";
import { KnownMembership } from "../@types/membership.ts";
/* re-exports for backwards compatibility */
export type {
BootstrapCrossSigningOpts as IBootstrapCrossSigningOpts,
CryptoCallbacks as ICryptoCallbacks,
} from "../crypto-api";
} from "../crypto-api/index.ts";
const DeviceVerification = DeviceInfo.DeviceVerification;
@@ -4374,4 +4379,4 @@ class IncomingRoomKeyRequestCancellation {
}
// a number of types are re-exported for backwards compatibility, in case any applications are referencing it.
export type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto";
export type { IEventDecryptionResult, IMegolmSessionData } from "../@types/crypto.ts";
+1 -1
View File
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { randomString } from "../randomstring";
import { randomString } from "../randomstring.ts";
const DEFAULT_ITERATIONS = 500000;
+2 -2
View File
@@ -15,7 +15,7 @@ limitations under the License.
*/
// Export for backward compatibility
import { ImportRoomKeyProgressData } from "../crypto-api";
import { ImportRoomKeyProgressData } from "../crypto-api/index.ts";
export type {
Curve25519AuthData as ICurve25519AuthData,
@@ -24,7 +24,7 @@ export type {
Curve25519SessionData,
KeyBackupSession as IKeyBackupSession,
KeyBackupRoomSessions as IKeyBackupRoomSessions,
} from "../crypto-api/keybackup";
} from "../crypto-api/keybackup.ts";
/* eslint-enable camelcase */
+10 -10
View File
@@ -21,16 +21,16 @@ limitations under the License.
import anotherjson from "another-json";
import type { PkSigning } from "@matrix-org/olm";
import type { IOneTimeKey } from "../@types/crypto";
import { OlmDevice } from "./OlmDevice";
import { DeviceInfo } from "./deviceinfo";
import { Logger, logger } from "../logger";
import { IClaimOTKsResult, MatrixClient } from "../client";
import { ISignatures } from "../@types/signed";
import { MatrixEvent } from "../models/event";
import { EventType } from "../@types/event";
import { IMessage } from "./algorithms/olm";
import { MapWithDefault } from "../utils";
import type { IOneTimeKey } from "../@types/crypto.ts";
import { OlmDevice } from "./OlmDevice.ts";
import { DeviceInfo } from "./deviceinfo.ts";
import { Logger, logger } from "../logger.ts";
import { IClaimOTKsResult, MatrixClient } from "../client.ts";
import { ISignatures } from "../@types/signed.ts";
import { MatrixEvent } from "../models/event.ts";
import { EventType } from "../@types/event.ts";
import { IMessage } from "./algorithms/olm.ts";
import { MapWithDefault } from "../utils.ts";
enum Algorithm {
Olm = "m.olm.v1.curve25519-aes-sha2",
+13 -13
View File
@@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager";
import { IOlmDevice } from "../algorithms/megolm";
import { TrackingStatus } from "../DeviceList";
import { IRoomEncryption } from "../RoomList";
import { IDevice } from "../deviceinfo";
import { ICrossSigningInfo } from "../CrossSigning";
import { Logger } from "../../logger";
import { InboundGroupSessionData } from "../OlmDevice";
import { MatrixEvent } from "../../models/event";
import { DehydrationManager } from "../dehydration";
import { IEncryptedPayload } from "../aes";
import { CrossSigningKeyInfo } from "../../crypto-api";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index.ts";
import { RoomKeyRequestState } from "../OutgoingRoomKeyRequestManager.ts";
import { IOlmDevice } from "../algorithms/megolm.ts";
import { TrackingStatus } from "../DeviceList.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { IDevice } from "../deviceinfo.ts";
import { ICrossSigningInfo } from "../CrossSigning.ts";
import { Logger } from "../../logger.ts";
import { InboundGroupSessionData } from "../OlmDevice.ts";
import { MatrixEvent } from "../../models/event.ts";
import { DehydrationManager } from "../dehydration.ts";
import { IEncryptedPayload } from "../aes.ts";
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
/**
* Internal module. Definitions for storage for the crypto module
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { Logger, logger } from "../../logger";
import { deepCompare } from "../../utils";
import { Logger, logger } from "../../logger.ts";
import { deepCompare } from "../../utils.ts";
import {
CryptoStore,
IDeviceData,
@@ -31,13 +31,13 @@ import {
SecretStorePrivateKeys,
SESSION_BATCH_SIZE,
ACCOUNT_OBJECT_KEY_MIGRATION_STATE,
} from "./base";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
import { IndexedDBCryptoStore } from "./indexeddb-crypto-store";
import { CrossSigningKeyInfo } from "../../crypto-api";
} from "./base.ts";
import { IRoomKeyRequestBody, IRoomKeyRequestRecipient } from "../index.ts";
import { IOlmDevice } from "../algorithms/megolm.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { InboundGroupSessionData } from "../OlmDevice.ts";
import { IndexedDBCryptoStore } from "./indexeddb-crypto-store.ts";
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
const PROFILE_TRANSACTIONS = false;
+12 -12
View File
@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger, Logger } from "../../logger";
import { LocalStorageCryptoStore } from "./localStorage-crypto-store";
import { MemoryCryptoStore } from "./memory-crypto-store";
import * as IndexedDBCryptoStoreBackend from "./indexeddb-crypto-store-backend";
import { InvalidCryptoStoreError, InvalidCryptoStoreState } from "../../errors";
import * as IndexedDBHelpers from "../../indexeddb-helpers";
import { logger, Logger } from "../../logger.ts";
import { LocalStorageCryptoStore } from "./localStorage-crypto-store.ts";
import { MemoryCryptoStore } from "./memory-crypto-store.ts";
import * as IndexedDBCryptoStoreBackend from "./indexeddb-crypto-store-backend.ts";
import { InvalidCryptoStoreError, InvalidCryptoStoreState } from "../../errors.ts";
import * as IndexedDBHelpers from "../../indexeddb-helpers.ts";
import {
CryptoStore,
IDeviceData,
@@ -34,12 +34,12 @@ import {
ParkedSharedHistory,
SecretStorePrivateKeys,
ACCOUNT_OBJECT_KEY_MIGRATION_STATE,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
import { CrossSigningKeyInfo } from "../../crypto-api";
} from "./base.ts";
import { IRoomKeyRequestBody } from "../index.ts";
import { IOlmDevice } from "../algorithms/megolm.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { InboundGroupSessionData } from "../OlmDevice.ts";
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
/*
* Internal module. indexeddb storage for e2e.
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger";
import { MemoryCryptoStore } from "./memory-crypto-store";
import { logger } from "../../logger.ts";
import { MemoryCryptoStore } from "./memory-crypto-store.ts";
import {
CryptoStore,
IDeviceData,
@@ -28,12 +28,12 @@ import {
Mode,
SecretStorePrivateKeys,
SESSION_BATCH_SIZE,
} from "./base";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
import { safeSet } from "../../utils";
import { CrossSigningKeyInfo } from "../../crypto-api";
} from "./base.ts";
import { IOlmDevice } from "../algorithms/megolm.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { InboundGroupSessionData } from "../OlmDevice.ts";
import { safeSet } from "../../utils.ts";
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
/**
* Internal module. Partial localStorage backed storage for e2e.
+8 -8
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../logger";
import { deepCompare, promiseTry, safeSet } from "../../utils";
import { logger } from "../../logger.ts";
import { deepCompare, promiseTry, safeSet } from "../../utils.ts";
import {
CryptoStore,
IDeviceData,
@@ -30,12 +30,12 @@ import {
ParkedSharedHistory,
SecretStorePrivateKeys,
SESSION_BATCH_SIZE,
} from "./base";
import { IRoomKeyRequestBody } from "../index";
import { IOlmDevice } from "../algorithms/megolm";
import { IRoomEncryption } from "../RoomList";
import { InboundGroupSessionData } from "../OlmDevice";
import { CrossSigningKeyInfo } from "../../crypto-api";
} from "./base.ts";
import { IRoomKeyRequestBody } from "../index.ts";
import { IOlmDevice } from "../algorithms/megolm.ts";
import { IRoomEncryption } from "../RoomList.ts";
import { InboundGroupSessionData } from "../OlmDevice.ts";
import { CrossSigningKeyInfo } from "../../crypto-api/index.ts";
function encodeSessionKey(senderCurve25519Key: string, sessionId: string): string {
return encodeURIComponent(senderCurve25519Key) + "/" + encodeURIComponent(sessionId);
+11 -11
View File
@@ -19,23 +19,23 @@ limitations under the License.
* Base class for verification methods.
*/
import { MatrixEvent } from "../../models/event";
import { EventType } from "../../@types/event";
import { logger } from "../../logger";
import { DeviceInfo } from "../deviceinfo";
import { newTimeoutError } from "./Error";
import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossSigning";
import { IVerificationChannel } from "./request/Channel";
import { MatrixClient } from "../../client";
import { VerificationRequest } from "./request/VerificationRequest";
import { TypedEventEmitter } from "../../models/typed-event-emitter";
import { MatrixEvent } from "../../models/event.ts";
import { EventType } from "../../@types/event.ts";
import { logger } from "../../logger.ts";
import { DeviceInfo } from "../deviceinfo.ts";
import { newTimeoutError } from "./Error.ts";
import { KeysDuringVerification, requestKeysDuringVerification } from "../CrossSigning.ts";
import { IVerificationChannel } from "./request/Channel.ts";
import { MatrixClient } from "../../client.ts";
import { VerificationRequest } from "./request/VerificationRequest.ts";
import { TypedEventEmitter } from "../../models/typed-event-emitter.ts";
import {
ShowQrCodeCallbacks,
ShowSasCallbacks,
Verifier,
VerifierEvent,
VerifierEventHandlerMap,
} from "../../crypto-api/verification";
} from "../../crypto-api/verification.ts";
const timeoutException = new Error("Verification timed out");
+2 -2
View File
@@ -18,8 +18,8 @@ limitations under the License.
* Error messages.
*/
import { MatrixEvent } from "../../models/event";
import { EventType } from "../../@types/event";
import { MatrixEvent } from "../../models/event.ts";
import { EventType } from "../../@types/event.ts";
export function newVerificationError(code: string, reason: string, extraData?: Record<string, any>): MatrixEvent {
const content = Object.assign({}, { code, reason }, extraData);
+5 -5
View File
@@ -19,11 +19,11 @@ limitations under the License.
* do verification with this method).
*/
import { VerificationBase as Base, VerificationEvent, VerificationEventHandlerMap } from "./Base";
import { IVerificationChannel } from "./request/Channel";
import { MatrixClient } from "../../client";
import { MatrixEvent } from "../../models/event";
import { VerificationRequest } from "./request/VerificationRequest";
import { VerificationBase as Base, VerificationEvent, VerificationEventHandlerMap } from "./Base.ts";
import { IVerificationChannel } from "./request/Channel.ts";
import { MatrixClient } from "../../client.ts";
import { MatrixEvent } from "../../models/event.ts";
import { VerificationRequest } from "./request/VerificationRequest.ts";
export class IllegalMethod extends Base<VerificationEvent, VerificationEventHandlerMap> {
public static factory(
+10 -10
View File
@@ -18,16 +18,16 @@ limitations under the License.
* QR code key verification.
*/
import { VerificationBase as Base } from "./Base";
import { newKeyMismatchError, newUserCancelledError } from "./Error";
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64";
import { logger } from "../../logger";
import { VerificationRequest } from "./request/VerificationRequest";
import { MatrixClient } from "../../client";
import { IVerificationChannel } from "./request/Channel";
import { MatrixEvent } from "../../models/event";
import { ShowQrCodeCallbacks, VerifierEvent } from "../../crypto-api/verification";
import { VerificationMethod } from "../../types";
import { VerificationBase as Base } from "./Base.ts";
import { newKeyMismatchError, newUserCancelledError } from "./Error.ts";
import { decodeBase64, encodeUnpaddedBase64 } from "../../base64.ts";
import { logger } from "../../logger.ts";
import { VerificationRequest } from "./request/VerificationRequest.ts";
import { MatrixClient } from "../../client.ts";
import { IVerificationChannel } from "./request/Channel.ts";
import { MatrixEvent } from "../../models/event.ts";
import { ShowQrCodeCallbacks, VerifierEvent } from "../../crypto-api/verification.ts";
import { VerificationMethod } from "../../types.ts";
export const SHOW_QR_CODE_METHOD = VerificationMethod.ShowQrCode;
export const SCAN_QR_CODE_METHOD = VerificationMethod.ScanQrCode;
+9 -9
View File
@@ -21,27 +21,27 @@ limitations under the License.
import anotherjson from "another-json";
import { Utility, SAS as OlmSAS } from "@matrix-org/olm";
import { VerificationBase as Base, SwitchStartEventError } from "./Base";
import { VerificationBase as Base, SwitchStartEventError } from "./Base.ts";
import {
errorFactory,
newInvalidMessageError,
newKeyMismatchError,
newUnknownMethodError,
newUserCancelledError,
} from "./Error";
import { logger } from "../../logger";
import { IContent, MatrixEvent } from "../../models/event";
import { generateDecimalSas } from "./SASDecimal";
import { EventType } from "../../@types/event";
import { EmojiMapping, GeneratedSas, ShowSasCallbacks, VerifierEvent } from "../../crypto-api/verification";
import { VerificationMethod } from "../../types";
} from "./Error.ts";
import { logger } from "../../logger.ts";
import { IContent, MatrixEvent } from "../../models/event.ts";
import { generateDecimalSas } from "./SASDecimal.ts";
import { EventType } from "../../@types/event.ts";
import { EmojiMapping, GeneratedSas, ShowSasCallbacks, VerifierEvent } from "../../crypto-api/verification.ts";
import { VerificationMethod } from "../../types.ts";
// backwards-compatibility exports
export type {
ShowSasCallbacks as ISasEvent,
GeneratedSas as IGeneratedSas,
EmojiMapping,
} from "../../crypto-api/verification";
} from "../../crypto-api/verification.ts";
const START_TYPE = EventType.KeyVerificationStart;
+2 -2
View File
@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixEvent } from "../../../models/event";
import { VerificationRequest } from "./VerificationRequest";
import { MatrixEvent } from "../../../models/event.ts";
import { VerificationRequest } from "./VerificationRequest.ts";
export interface IVerificationChannel {
request?: VerificationRequest;
@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { VerificationRequest, REQUEST_TYPE, READY_TYPE, START_TYPE } from "./VerificationRequest";
import { logger } from "../../../logger";
import { IVerificationChannel } from "./Channel";
import { EventType, TimelineEvents } from "../../../@types/event";
import { MatrixClient } from "../../../client";
import { MatrixEvent } from "../../../models/event";
import { IRequestsMap } from "../..";
import { VerificationRequest, REQUEST_TYPE, READY_TYPE, START_TYPE } from "./VerificationRequest.ts";
import { logger } from "../../../logger.ts";
import { IVerificationChannel } from "./Channel.ts";
import { EventType, TimelineEvents } from "../../../@types/event.ts";
import { MatrixClient } from "../../../client.ts";
import { MatrixEvent } from "../../../models/event.ts";
import { IRequestsMap } from "../../index.ts";
const MESSAGE_TYPE = EventType.RoomMessage;
const M_REFERENCE = "m.reference";
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { randomString } from "../../../randomstring";
import { logger } from "../../../logger";
import { randomString } from "../../../randomstring.ts";
import { logger } from "../../../logger.ts";
import {
CANCEL_TYPE,
PHASE_STARTED,
@@ -25,12 +25,12 @@ import {
READY_TYPE,
START_TYPE,
VerificationRequest,
} from "./VerificationRequest";
import { errorFromEvent, newUnexpectedMessageError } from "../Error";
import { MatrixEvent } from "../../../models/event";
import { IVerificationChannel } from "./Channel";
import { MatrixClient } from "../../../client";
import { IRequestsMap } from "../..";
} from "./VerificationRequest.ts";
import { errorFromEvent, newUnexpectedMessageError } from "../Error.ts";
import { MatrixEvent } from "../../../models/event.ts";
import { IVerificationChannel } from "./Channel.ts";
import { MatrixClient } from "../../../client.ts";
import { IRequestsMap } from "../../index.ts";
export type Request = VerificationRequest<ToDeviceChannel>;
@@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from "../../../logger";
import { errorFactory, errorFromEvent, newUnexpectedMessageError, newUnknownMethodError } from "../Error";
import { QRCodeData, SCAN_QR_CODE_METHOD } from "../QRCode";
import { IVerificationChannel } from "./Channel";
import { MatrixClient } from "../../../client";
import { MatrixEvent } from "../../../models/event";
import { EventType } from "../../../@types/event";
import { VerificationBase } from "../Base";
import { VerificationMethod } from "../../index";
import { TypedEventEmitter } from "../../../models/typed-event-emitter";
import { logger } from "../../../logger.ts";
import { errorFactory, errorFromEvent, newUnexpectedMessageError, newUnknownMethodError } from "../Error.ts";
import { QRCodeData, SCAN_QR_CODE_METHOD } from "../QRCode.ts";
import { IVerificationChannel } from "./Channel.ts";
import { MatrixClient } from "../../../client.ts";
import { MatrixEvent } from "../../../models/event.ts";
import { EventType } from "../../../@types/event.ts";
import { VerificationBase } from "../Base.ts";
import { VerificationMethod } from "../../index.ts";
import { TypedEventEmitter } from "../../../models/typed-event-emitter.ts";
import {
canAcceptVerificationRequest,
VerificationPhase as Phase,
@@ -31,10 +31,10 @@ import {
VerificationRequestEvent,
VerificationRequestEventHandlerMap,
Verifier,
} from "../../../crypto-api/verification";
} from "../../../crypto-api/verification.ts";
// backwards-compatibility exports
export { VerificationPhase as Phase, VerificationRequestEvent } from "../../../crypto-api/verification";
export { VerificationPhase as Phase, VerificationRequestEvent } from "../../../crypto-api/verification.ts";
// How long after the event's timestamp that the request times out
const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes
+34
View File
@@ -0,0 +1,34 @@
/*
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.
*/
/**
* Computes a SHA-256 hash of a string (after utf-8 encoding) and returns it as an ArrayBuffer.
*
* @param plaintext The string to hash
* @returns An ArrayBuffer containing the SHA-256 hash of the input string
* @throws If the subtle crypto API is not available, for example if the code is running
* in a web page with an insecure context (eg. served over plain HTTP).
*/
export async function sha256(plaintext: string): Promise<ArrayBuffer> {
if (!globalThis.crypto.subtle) {
throw new Error("Crypto.subtle is not available: insecure context?");
}
const utf8 = new TextEncoder().encode(plaintext);
const digest = await globalThis.crypto.subtle.digest("SHA-256", utf8);
return digest;
}
+120 -16
View File
@@ -25,10 +25,15 @@ import {
ISendEventFromWidgetResponseData,
} from "matrix-widget-api";
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event";
import { ISendEventResponse } from "./@types/requests";
import { EventType } from "./@types/event";
import { logger } from "./logger";
import { MatrixEvent, IEvent, IContent, EventStatus } from "./models/event.ts";
import {
ISendEventResponse,
SendDelayedEventRequestOpts,
SendDelayedEventResponse,
UpdateDelayedEventAction,
} from "./@types/requests.ts";
import { EventType, StateEvents } from "./@types/event.ts";
import { logger } from "./logger.ts";
import {
MatrixClient,
ClientEvent,
@@ -36,15 +41,16 @@ import {
IStartClientOpts,
SendToDeviceContentMap,
IOpenIDToken,
} from "./client";
import { SyncApi, SyncState } from "./sync";
import { SlidingSyncSdk } from "./sliding-sync-sdk";
import { User } from "./models/user";
import { Room } from "./models/room";
import { ToDeviceBatch, ToDevicePayload } from "./models/ToDeviceMessage";
import { DeviceInfo } from "./crypto/deviceinfo";
import { IOlmDevice } from "./crypto/algorithms/megolm";
import { MapWithDefault, recursiveMapToObject } from "./utils";
UNSTABLE_MSC4140_DELAYED_EVENTS,
} from "./client.ts";
import { SyncApi, SyncState } from "./sync.ts";
import { SlidingSyncSdk } from "./sliding-sync-sdk.ts";
import { User } from "./models/user.ts";
import { Room } from "./models/room.ts";
import { ToDeviceBatch, ToDevicePayload } from "./models/ToDeviceMessage.ts";
import { DeviceInfo } from "./crypto/deviceinfo.ts";
import { IOlmDevice } from "./crypto/algorithms/megolm.ts";
import { MapWithDefault, recursiveMapToObject } from "./utils.ts";
interface IStateEventRequest {
eventType: string;
@@ -95,6 +101,20 @@ export interface ICapabilities {
* @defaultValue false
*/
turnServers?: boolean;
/**
* Whether this client needs to be able to send delayed events.
* @experimental Part of MSC4140 & MSC4157
* @defaultValue false
*/
sendDelayedEvents?: boolean;
/**
* Whether this client needs to be able to update delayed events.
* @experimental Part of MSC4140 & MSC4157
* @defaultValue false
*/
updateDelayedEvents?: boolean;
}
/**
@@ -162,6 +182,18 @@ export class RoomWidgetClient extends MatrixClient {
);
capabilities.sendToDevice?.forEach((eventType) => widgetApi.requestCapabilityToSendToDevice(eventType));
capabilities.receiveToDevice?.forEach((eventType) => widgetApi.requestCapabilityToReceiveToDevice(eventType));
if (
capabilities.sendDelayedEvents &&
(capabilities.sendEvent?.length ||
capabilities.sendMessage === true ||
(Array.isArray(capabilities.sendMessage) && capabilities.sendMessage.length) ||
capabilities.sendState?.length)
) {
widgetApi.requestCapability(MatrixCapabilities.MSC4157SendDelayedEvent);
}
if (capabilities.updateDelayedEvents) {
widgetApi.requestCapability(MatrixCapabilities.MSC4157UpdateDelayedEvent);
}
if (capabilities.turnServers) {
widgetApi.requestCapability(MatrixCapabilities.MSC3846TurnServers);
}
@@ -248,7 +280,29 @@ export class RoomWidgetClient extends MatrixClient {
throw new Error(`Unknown room: ${roomIdOrAlias}`);
}
protected async encryptAndSendEvent(room: Room, event: MatrixEvent): Promise<ISendEventResponse> {
protected async encryptAndSendEvent(room: Room, event: MatrixEvent): Promise<ISendEventResponse>;
protected async encryptAndSendEvent(
room: Room,
event: MatrixEvent,
delayOpts: SendDelayedEventRequestOpts,
): Promise<SendDelayedEventResponse>;
protected async encryptAndSendEvent(
room: Room,
event: MatrixEvent,
delayOpts?: SendDelayedEventRequestOpts,
): Promise<ISendEventResponse | SendDelayedEventResponse> {
if (delayOpts) {
// TODO: updatePendingEvent for delayed events?
const response = await this.widgetApi.sendRoomEvent(
event.getType(),
event.getContent(),
room.roomId,
"delay" in delayOpts ? delayOpts.delay : undefined,
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
);
return this.validateSendDelayedEventResponse(response);
}
let response: ISendEventFromWidgetResponseData;
try {
response = await this.widgetApi.sendRoomEvent(event.getType(), event.getContent(), room.roomId);
@@ -257,8 +311,9 @@ export class RoomWidgetClient extends MatrixClient {
throw e;
}
// This also checks for an event id on the response
room.updatePendingEvent(event, EventStatus.SENT, response.event_id);
return { event_id: response.event_id };
return { event_id: response.event_id! };
}
public async sendStateEvent(
@@ -267,7 +322,56 @@ export class RoomWidgetClient extends MatrixClient {
content: any,
stateKey = "",
): Promise<ISendEventResponse> {
return await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
const response = await this.widgetApi.sendStateEvent(eventType, stateKey, content, roomId);
if (response.event_id === undefined) {
throw new Error("'event_id' absent from response to an event request");
}
return { event_id: response.event_id };
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
// eslint-disable-next-line
public async _unstable_sendDelayedStateEvent<K extends keyof StateEvents>(
roomId: string,
delayOpts: SendDelayedEventRequestOpts,
eventType: K,
content: StateEvents[K],
stateKey = "",
): Promise<SendDelayedEventResponse> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
const response = await this.widgetApi.sendStateEvent(
eventType,
stateKey,
content,
roomId,
"delay" in delayOpts ? delayOpts.delay : undefined,
"parent_delay_id" in delayOpts ? delayOpts.parent_delay_id : undefined,
);
return this.validateSendDelayedEventResponse(response);
}
private validateSendDelayedEventResponse(response: ISendEventFromWidgetResponseData): SendDelayedEventResponse {
if (response.delay_id === undefined) {
throw new Error("'delay_id' absent from response to a delayed event request");
}
return { delay_id: response.delay_id };
}
/**
* @experimental This currently relies on an unstable MSC (MSC4140).
*/
// eslint-disable-next-line
public async _unstable_updateDelayedEvent(delayId: string, action: UpdateDelayedEventAction): Promise<{}> {
if (!(await this.doesServerSupportUnstableFeature(UNSTABLE_MSC4140_DELAYED_EVENTS))) {
throw Error("Server does not support the delayed events API");
}
return await this.widgetApi.updateDelayedEvent(delayId, action);
}
public async sendToDevice(eventType: string, contentMap: SendToDeviceContentMap): Promise<{}> {

Some files were not shown because too many files have changed in this diff Show More