Compare commits
95 Commits
v34.2.0
...
v34.6.0-rc.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a13e0389db | |||
| dbb4828eda | |||
| 414ac9d8cc | |||
| 30058a4bdc | |||
| fab9cab3df | |||
| 53b599f8fe | |||
| 17f6cc733e | |||
| c8403f39aa | |||
| 8cf5df73ee | |||
| febe27ddcc | |||
| 7987ce76ec | |||
| 60cedf2fdb | |||
| ed44514974 | |||
| 9f8c1ee953 | |||
| 593a57fc2b | |||
| e8128d34a1 | |||
| ba7bd06295 | |||
| e4db6008b8 | |||
| 52f35409ec | |||
| f50aab37c3 | |||
| df0f817f83 | |||
| 7efa6352f8 | |||
| f74614705e | |||
| 169e8f8613 | |||
| f2f77bd1f7 | |||
| 5a1c70ad19 | |||
| 27cb16ffe4 | |||
| 9be0b3e701 | |||
| 05ba27f36b | |||
| e6acfdf275 | |||
| 2a6612c73a | |||
| 2f8b05b0da | |||
| fe984ede6e | |||
| 3f74b9a0cc | |||
| 802b996b10 | |||
| 8d44f9d665 | |||
| a72a1b294a | |||
| ab5f32f984 | |||
| 6a21d812ab | |||
| ee94e93354 | |||
| 31c4786a96 | |||
| 17b6e59819 | |||
| 42510022a1 | |||
| 5f0978ac3f | |||
| cd6787e0ac | |||
| 03baa3e358 | |||
| 658563e2a7 | |||
| 26d3033b17 | |||
| a4bd7dc7d7 | |||
| 1f48544b38 | |||
| 7ef4062f59 | |||
| 968bc51a35 | |||
| d413f5042e | |||
| b8e8b14375 | |||
| 43e58871de | |||
| 2544c14032 | |||
| 8d19782c57 | |||
| 340bbe1a8f | |||
| 8214fd7156 | |||
| a0efed8b88 | |||
| c408c0d1d5 | |||
| d6080398db | |||
| 467908703b | |||
| 87eddaf51a | |||
| c65ef03567 | |||
| 78cbf7cd28 | |||
| dc7c1a4fef | |||
| 1ae0c2f3ee | |||
| affaa95fb4 | |||
| 9176d3a671 | |||
| de50129a53 | |||
| 5568dfdd41 | |||
| 39216d44ed | |||
| 8c3b249567 | |||
| b8e40ad2a8 | |||
| 4e2831764d | |||
| 09780672aa | |||
| 0fe53876ec | |||
| dfec3dc33c | |||
| fbdd78b428 | |||
| e10c362ef0 | |||
| 89a9a7fa38 | |||
| 687d08dc9d | |||
| 7f91db83d0 | |||
| e0ef467d7d | |||
| 79299891fd | |||
| 5feab37166 | |||
| 1a02835ab2 | |||
| 4d90fecb6a | |||
| 6520e0f54f | |||
| ed7b314e6a | |||
| 24eff501e4 | |||
| a0d73dfaca | |||
| 5d2500b7a7 | |||
| eff52b82e8 |
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org", "import", "jsdoc"],
|
||||
plugins: ["matrix-org", "import", "jsdoc", "node"],
|
||||
extends: ["plugin:matrix-org/babel", "plugin:matrix-org/jest", "plugin:import/typescript"],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
@@ -128,6 +128,14 @@ module.exports = {
|
||||
// These need a bit more work before we can enable
|
||||
// "jsdoc/check-param-names": "error",
|
||||
// "jsdoc/check-indentation": "error",
|
||||
// Ensure .ts extension on imports outside of tests
|
||||
"node/file-extension-in-import": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
tryExtensions: [".ts"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -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@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # 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@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # 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@aac270e08f6b4547ada0b3800f88e1eb3ce9d400 # v1
|
||||
with:
|
||||
upload_url: ${{ inputs.upload-url }}
|
||||
asset_path: ${{ inputs.asset-path }}
|
||||
|
||||
@@ -21,14 +21,14 @@ concurrency:
|
||||
jobs:
|
||||
playwright:
|
||||
name: Playwright
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/end-to-end-tests.yaml@develop
|
||||
uses: element-hq/matrix-react-sdk/.github/workflows/end-to-end-tests.yaml@develop
|
||||
permissions:
|
||||
actions: read
|
||||
issues: read
|
||||
pull-requests: read
|
||||
with:
|
||||
matrix-js-sdk-sha: ${{ github.sha }}
|
||||
react-sdk-repository: matrix-org/matrix-react-sdk
|
||||
react-sdk-repository: element-hq/matrix-react-sdk
|
||||
# We only want to run the playwright tests on merge queue to prevent regressions
|
||||
# from creeping in. They take a long time to run and consume multiple concurrent runners.
|
||||
skip: ${{ github.event_name != 'merge_group' }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
include:
|
||||
- repo: element-hq/element-web
|
||||
event: element-web-notify
|
||||
- repo: matrix-org/matrix-react-sdk
|
||||
- repo: element-hq/matrix-react-sdk
|
||||
event: upstream-sdk-notify
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Preview Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@5847eef68201219cf0a4643ea7be61e77837bbce # v5
|
||||
- uses: mheap/github-action-required-labels@d25134c992b943fb6ad00c25ea00eb5988c0a9dd # v5
|
||||
if: github.event_name != 'merge_group'
|
||||
with:
|
||||
labels: |
|
||||
|
||||
@@ -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("/")) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
final: ${{ inputs.mode == 'final' }}
|
||||
npm: ${{ inputs.npm }}
|
||||
downstreams: '["matrix-org/matrix-react-sdk", "element-hq/element-web"]'
|
||||
downstreams: '["element-hq/matrix-react-sdk", "element-hq/element-web"]'
|
||||
|
||||
docs:
|
||||
name: Publish Documentation
|
||||
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
matrix-react-sdk:
|
||||
name: Downstream test matrix-react-sdk
|
||||
if: github.event_name == 'merge_group'
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/tests.yml@develop
|
||||
uses: element-hq/matrix-react-sdk/.github/workflows/tests.yml@develop
|
||||
with:
|
||||
disable_coverage: true
|
||||
matrix-js-sdk-sha: ${{ github.sha }}
|
||||
|
||||
@@ -1,3 +1,69 @@
|
||||
Changes in [34.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.5.0) (2024-09-10)
|
||||
==================================================================================================
|
||||
## 🦖 Deprecations
|
||||
|
||||
* Deprecate unused callback hooks `CryptoCallbacks.onSecretRequested` and `CryptoCallbacks.getDehydrationKey` ([#4376](https://github.com/matrix-org/matrix-js-sdk/pull/4376)). Contributed by @richvdh.
|
||||
|
||||
|
||||
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
|
||||
|
||||
* Add ability to choose how many timeline events to sync when peeking ([#4300](https://github.com/matrix-org/matrix-js-sdk/pull/4300)). Contributed by @jgarplind.
|
||||
* Remove redundant hack for using the old pickle key in rust crypto ([#4282](https://github.com/matrix-org/matrix-js-sdk/pull/4282)). Contributed by @richvdh.
|
||||
* Add fetching the well known in embedded mode. ([#4259](https://github.com/matrix-org/matrix-js-sdk/pull/4259)). Contributed by @toger5.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
|
||||
* Fix room state being updated with old (now overwritten) state and emitting for those updates. ([#4242](https://github.com/matrix-org/matrix-js-sdk/pull/4242)). Contributed by @toger5.
|
||||
* Fix incorrect "Olm is not available" errors ([#4301](https://github.com/matrix-org/matrix-js-sdk/pull/4301)). Contributed by @richvdh.
|
||||
* Fix build for example script ([#4286](https://github.com/matrix-org/matrix-js-sdk/pull/4286)). Contributed by @richvdh.
|
||||
* Declare matrix-js-sdk as an ES module ([#4285](https://github.com/matrix-org/matrix-js-sdk/pull/4285)). Contributed by @richvdh.
|
||||
|
||||
|
||||
Changes in [34.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.0.0) (2024-07-08)
|
||||
==================================================================================================
|
||||
## 🚨 BREAKING CHANGES
|
||||
|
||||
+10
-1
@@ -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",
|
||||
|
||||
+16
-11
@@ -1,9 +1,15 @@
|
||||
import clc from "cli-color";
|
||||
import fs from "fs";
|
||||
import readline from "readline";
|
||||
import sdk, { ClientEvent, EventType, MsgType, RoomEvent } from "matrix-js-sdk";
|
||||
import { KnownMembership } from "matrix-js-sdk/lib/@types/membership.js";
|
||||
|
||||
var myHomeServer = "http://localhost:8008";
|
||||
var myUserId = "@example:localhost";
|
||||
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
|
||||
var sdk = require("matrix-js-sdk");
|
||||
var clc = require("cli-color");
|
||||
|
||||
var matrixClient = sdk.createClient({
|
||||
baseUrl: "http://localhost:8008",
|
||||
baseUrl: myHomeServer,
|
||||
accessToken: myAccessToken,
|
||||
userId: myUserId,
|
||||
});
|
||||
@@ -15,7 +21,6 @@ var numMessagesToShow = 20;
|
||||
|
||||
// Reading from stdin
|
||||
var CLEAR_CONSOLE = "\x1B[2J";
|
||||
var readline = require("readline");
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
@@ -97,7 +102,7 @@ rl.on("line", function (line) {
|
||||
})
|
||||
.then(function (url) {
|
||||
var content = {
|
||||
msgtype: "m.file",
|
||||
msgtype: MsgType.File,
|
||||
body: filename,
|
||||
url: JSON.parse(url).content_uri,
|
||||
};
|
||||
@@ -138,7 +143,7 @@ rl.on("line", function (line) {
|
||||
// ==== END User input
|
||||
|
||||
// show the room list after syncing.
|
||||
matrixClient.on("sync", function (state, prevState, data) {
|
||||
matrixClient.on(ClientEvent.Sync, function (state, prevState, data) {
|
||||
switch (state) {
|
||||
case "PREPARED":
|
||||
setRoomList();
|
||||
@@ -149,7 +154,7 @@ matrixClient.on("sync", function (state, prevState, data) {
|
||||
}
|
||||
});
|
||||
|
||||
matrixClient.on("Room", function () {
|
||||
matrixClient.on(ClientEvent.Room, function () {
|
||||
setRoomList();
|
||||
if (!viewingRoom) {
|
||||
printRoomList();
|
||||
@@ -158,7 +163,7 @@ matrixClient.on("Room", function () {
|
||||
});
|
||||
|
||||
// print incoming messages.
|
||||
matrixClient.on("Room.timeline", function (event, room, toStartOfTimeline) {
|
||||
matrixClient.on(RoomEvent.Timeline, function (event, room, toStartOfTimeline) {
|
||||
if (toStartOfTimeline) {
|
||||
return; // don't print paginated results
|
||||
}
|
||||
@@ -305,7 +310,7 @@ function printRoomInfo(room) {
|
||||
print(eTypeHeader + sendHeader + contentHeader);
|
||||
print(new Array(100).join("-"));
|
||||
eventMap.keys().forEach(function (eventType) {
|
||||
if (eventType === "m.room.member") {
|
||||
if (eventType === EventType.RoomMember) {
|
||||
return;
|
||||
} // use /members instead.
|
||||
var eventEventMap = eventMap.get(eventType);
|
||||
@@ -343,7 +348,7 @@ function printLine(event) {
|
||||
name = name.slice(0, maxNameWidth - 1) + "\u2026";
|
||||
}
|
||||
|
||||
if (event.getType() === "m.room.message") {
|
||||
if (event.getType() === EventType.RoomMessage) {
|
||||
body = event.getContent().body;
|
||||
} else if (event.isState()) {
|
||||
var stateName = event.getType();
|
||||
@@ -394,4 +399,4 @@ function fixWidth(str, len) {
|
||||
return str;
|
||||
}
|
||||
|
||||
matrixClient.startClient(numMessagesToShow); // messages for each room.
|
||||
matrixClient.startClient({ initialSyncLimit: numMessagesToShow });
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
"main": "app.js",
|
||||
"author": "",
|
||||
"license": "Apache 2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cli-color": "^1.0.0",
|
||||
"matrix-js-sdk": "^32.0.0"
|
||||
"matrix-js-sdk": "^34.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+12
-14
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "34.0.0",
|
||||
"version": "34.6.0-rc.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -31,13 +31,10 @@
|
||||
"keywords": [
|
||||
"matrix-org"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"browser": "./src/browser-index.ts",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_src_browser": "./src/browser-index.ts",
|
||||
"matrix_lib_main": "./lib/index.js",
|
||||
"matrix_lib_browser": "./lib/browser-index.js",
|
||||
"matrix_lib_typings": "./lib/index.d.ts",
|
||||
"type": "module",
|
||||
"main": "./lib/index.js",
|
||||
"browser": "./lib/browser-index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"author": "matrix.org",
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
@@ -53,14 +50,15 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^7.0.0",
|
||||
"@matrix-org/matrix-sdk-crypto-wasm": "^9.0.0",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"another-json": "^0.2.0",
|
||||
"bs58": "^6.0.0",
|
||||
"content-type": "^1.0.4",
|
||||
"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",
|
||||
@@ -82,7 +80,6 @@
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@casualbot/jest-sonar-reporter": "2.2.7",
|
||||
"@matrix-org/olm": "3.2.15",
|
||||
"@peculiar/webcrypto": "^1.4.5",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
@@ -101,12 +98,13 @@
|
||||
"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-node": "^11.1.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.1.0",
|
||||
"fetch-mock": "11.1.3",
|
||||
"fetch-mock-jest": "^1.5.1",
|
||||
"husky": "^9.0.0",
|
||||
"jest": "^29.0.0",
|
||||
|
||||
Executable
+18
@@ -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();
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
switch_package_to_release.cjs
|
||||
@@ -82,6 +82,7 @@ import { SecretStorageKeyDescription } from "../../../src/secret-storage";
|
||||
import {
|
||||
CrossSigningKey,
|
||||
CryptoCallbacks,
|
||||
CryptoMode,
|
||||
DecryptionFailureCode,
|
||||
EventShieldColour,
|
||||
EventShieldReason,
|
||||
@@ -746,6 +747,87 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("crypto (%s)", (backend: string,
|
||||
);
|
||||
});
|
||||
|
||||
newBackendOnly(
|
||||
"fails with an error when cross-signed sender is required but sender is not cross-signed",
|
||||
async () => {
|
||||
// This tests that a message will not be decrypted if the sender
|
||||
// is not sufficiently trusted according to the selected crypto
|
||||
// mode.
|
||||
//
|
||||
// This test is almost the same as the "Alice receives a megolm
|
||||
// message" test, with the main difference that we set the
|
||||
// crypto mode.
|
||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||
|
||||
// Start by using Invisible crypto mode
|
||||
aliceClient.getCrypto()!.setCryptoMode(CryptoMode.Invisible);
|
||||
|
||||
await startClientAndAwaitFirstSync();
|
||||
|
||||
const p2pSession = await createOlmSession(testOlmAccount, keyReceiver);
|
||||
const groupSession = new Olm.OutboundGroupSession();
|
||||
groupSession.create();
|
||||
|
||||
// make the room_key event
|
||||
const roomKeyEncrypted = encryptGroupSessionKey({
|
||||
recipient: aliceClient.getUserId()!,
|
||||
recipientCurve25519Key: keyReceiver.getDeviceKey(),
|
||||
recipientEd25519Key: keyReceiver.getSigningKey(),
|
||||
olmAccount: testOlmAccount,
|
||||
p2pSession: p2pSession,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// encrypt a message with the group session
|
||||
const messageEncrypted = encryptMegolmEvent({
|
||||
senderKey: testSenderKey,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// Alice gets both the events in a single sync
|
||||
const syncResponse = {
|
||||
next_batch: 1,
|
||||
to_device: {
|
||||
events: [roomKeyEncrypted],
|
||||
},
|
||||
rooms: {
|
||||
join: {
|
||||
[ROOM_ID]: { timeline: { events: [messageEncrypted] } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
syncResponder.sendOrQueueSyncResponse(syncResponse);
|
||||
await syncPromise(aliceClient);
|
||||
|
||||
const room = aliceClient.getRoom(ROOM_ID)!;
|
||||
const event = room.getLiveTimeline().getEvents()[0];
|
||||
expect(event.isEncrypted()).toBe(true);
|
||||
|
||||
// it probably won't be decrypted yet, because it takes a while to process the olm keys
|
||||
const decryptedEvent = await testUtils.awaitDecryption(event);
|
||||
// It will error as an unknown device because we haven't fetched
|
||||
// the sender's device keys.
|
||||
expect(decryptedEvent.decryptionFailureReason).toEqual(DecryptionFailureCode.UNKNOWN_SENDER_DEVICE);
|
||||
|
||||
// Next, try decrypting in transition mode, which should also
|
||||
// fail for the same reason
|
||||
aliceClient.getCrypto()!.setCryptoMode(CryptoMode.Transition);
|
||||
|
||||
await event.attemptDecryption(aliceClient["cryptoBackend"]!);
|
||||
expect(decryptedEvent.decryptionFailureReason).toEqual(DecryptionFailureCode.UNKNOWN_SENDER_DEVICE);
|
||||
|
||||
// Decrypting in legacy mode should succeed since it doesn't
|
||||
// care about device trust.
|
||||
aliceClient.getCrypto()!.setCryptoMode(CryptoMode.Legacy);
|
||||
|
||||
await event.attemptDecryption(aliceClient["cryptoBackend"]!);
|
||||
expect(decryptedEvent.decryptionFailureReason).toEqual(null);
|
||||
},
|
||||
);
|
||||
|
||||
it("Decryption fails with Unable to decrypt for other errors", async () => {
|
||||
expectAliceKeyQuery({ device_keys: { "@alice:localhost": {} }, failures: {} });
|
||||
await startClientAndAwaitFirstSync();
|
||||
|
||||
@@ -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()]);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 { keyFromAuthData } from "../../../src/common-crypto/key-passphrase.ts";
|
||||
|
||||
describe("key-passphrase", () => {
|
||||
describe("keyFromAuthData", () => {
|
||||
it("should throw an error if salt or iterations are missing", async () => {
|
||||
// missing salt
|
||||
expect(() => keyFromAuthData({ private_key_iterations: 5 }, "passphrase")).toThrow(
|
||||
"Salt and/or iterations not found: this backup cannot be restored with a passphrase",
|
||||
);
|
||||
|
||||
// missing iterations
|
||||
expect(() => keyFromAuthData({ private_key_salt: "salt" }, "passphrase")).toThrow(
|
||||
"Salt and/or iterations not found: this backup cannot be restored with a passphrase",
|
||||
);
|
||||
});
|
||||
|
||||
it("should derive key from auth data", async () => {
|
||||
const key = await keyFromAuthData({ private_key_salt: "salt", private_key_iterations: 5 }, "passphrase");
|
||||
expect(key).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 { decodeRecoveryKey, encodeRecoveryKey } from "../../../src/crypto-api";
|
||||
|
||||
describe("recovery key", () => {
|
||||
describe("decodeRecoveryKey", () => {
|
||||
it("should thrown an incorrect length error", () => {
|
||||
const key = [0, 1];
|
||||
const encodedKey = encodeRecoveryKey(key)!;
|
||||
|
||||
expect(() => decodeRecoveryKey(encodedKey)).toThrow("Incorrect length");
|
||||
});
|
||||
|
||||
it("should thrown an incorrect parity", () => {
|
||||
const key = Array.from({ length: 32 }, (_, i) => i);
|
||||
let encodedKey = encodeRecoveryKey(key)!;
|
||||
// Mutate the encoded key to have incorrect parity
|
||||
encodedKey = encodedKey.replace("EsSz", "EsSZ");
|
||||
|
||||
expect(() => decodeRecoveryKey(encodedKey!)).toThrow("Incorrect parity");
|
||||
});
|
||||
|
||||
it("should decode a valid encoded key", () => {
|
||||
const key = Array.from({ length: 32 }, (_, i) => i);
|
||||
const encodedKey = encodeRecoveryKey(key)!;
|
||||
|
||||
expect(decodeRecoveryKey(encodedKey)).toEqual(new Uint8Array(key));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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,446 @@ 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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("extended profiles", () => {
|
||||
const unstableMSC4133Prefix = `${ClientPrefix.Unstable}/uk.tcpip.msc4133`;
|
||||
const userId = "@profile_user:example.org";
|
||||
|
||||
beforeEach(() => {
|
||||
unstableFeatures["uk.tcpip.msc4133"] = true;
|
||||
});
|
||||
|
||||
it("throws when unsupported by server", async () => {
|
||||
unstableFeatures["uk.tcpip.msc4133"] = false;
|
||||
const errorMessage = "Server does not support extended profiles";
|
||||
|
||||
await expect(client.doesServerSupportExtendedProfiles()).resolves.toEqual(false);
|
||||
|
||||
await expect(client.getExtendedProfile(userId)).rejects.toThrow(errorMessage);
|
||||
await expect(client.getExtendedProfileProperty(userId, "test_key")).rejects.toThrow(errorMessage);
|
||||
await expect(client.setExtendedProfileProperty("test_key", "foo")).rejects.toThrow(errorMessage);
|
||||
await expect(client.deleteExtendedProfileProperty("test_key")).rejects.toThrow(errorMessage);
|
||||
await expect(client.patchExtendedProfile({ test_key: "foo" })).rejects.toThrow(errorMessage);
|
||||
await expect(client.setExtendedProfile({ test_key: "foo" })).rejects.toThrow(errorMessage);
|
||||
});
|
||||
|
||||
it("can fetch a extended user profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "GET",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(userId),
|
||||
data: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.getExtendedProfile(userId)).resolves.toEqual(testProfile);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can fetch a property from a extended user profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "GET",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(userId) + "/test_key",
|
||||
data: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.getExtendedProfileProperty(userId, "test_key")).resolves.toEqual("foo");
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can set a property in our extended profile", async () => {
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PUT",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!) + "/test_key",
|
||||
expectBody: {
|
||||
test_key: "foo",
|
||||
},
|
||||
},
|
||||
];
|
||||
await expect(client.setExtendedProfileProperty("test_key", "foo")).resolves.toEqual(undefined);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can delete a property in our extended profile", async () => {
|
||||
httpLookups = [
|
||||
{
|
||||
method: "DELETE",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!) + "/test_key",
|
||||
},
|
||||
];
|
||||
await expect(client.deleteExtendedProfileProperty("test_key")).resolves.toEqual(undefined);
|
||||
expect(httpLookups).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("can patch our extended profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
const patchedProfile = {
|
||||
existing: "key",
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PATCH",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!),
|
||||
data: patchedProfile,
|
||||
expectBody: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.patchExtendedProfile(testProfile)).resolves.toEqual(patchedProfile);
|
||||
});
|
||||
|
||||
it("can replace our extended profile", async () => {
|
||||
const testProfile = {
|
||||
test_key: "foo",
|
||||
};
|
||||
httpLookups = [
|
||||
{
|
||||
method: "PUT",
|
||||
prefix: unstableMSC4133Prefix,
|
||||
path: "/profile/" + encodeURIComponent(client.credentials.userId!),
|
||||
data: testProfile,
|
||||
expectBody: testProfile,
|
||||
},
|
||||
];
|
||||
await expect(client.setExtendedProfile(testProfile)).resolves.toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("should create (unstable) file trees", async () => {
|
||||
const userId = "@test:example.org";
|
||||
const roomId = "!room:example.org";
|
||||
@@ -963,7 +1406,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 +3478,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" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -526,24 +594,48 @@ describe("MatrixRTCSession", () => {
|
||||
});
|
||||
|
||||
it("sends keys when joining", async () => {
|
||||
const eventSentPromise = new Promise((resolve) => {
|
||||
sendEventMock.mockImplementation(resolve);
|
||||
});
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
const eventSentPromise = new Promise((resolve) => {
|
||||
sendEventMock.mockImplementation(resolve);
|
||||
});
|
||||
|
||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||
|
||||
await eventSentPromise;
|
||||
|
||||
expect(sendEventMock).toHaveBeenCalledWith(
|
||||
expect.stringMatching(".*"),
|
||||
"io.element.call.encryption_keys",
|
||||
{
|
||||
call_id: "",
|
||||
device_id: "AAAAAAA",
|
||||
keys: [
|
||||
{
|
||||
index: 0,
|
||||
key: expect.stringMatching(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not send key if join called when already joined", () => {
|
||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||
|
||||
await eventSentPromise;
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
expect(sendEventMock).toHaveBeenCalledWith(expect.stringMatching(".*"), "io.element.call.encryption_keys", {
|
||||
call_id: "",
|
||||
device_id: "AAAAAAA",
|
||||
keys: [
|
||||
{
|
||||
index: 0,
|
||||
key: expect.stringMatching(".*"),
|
||||
},
|
||||
],
|
||||
});
|
||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||
expect(client.sendEvent).toHaveBeenCalledTimes(1);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
});
|
||||
|
||||
it("retries key sends", async () => {
|
||||
@@ -572,6 +664,7 @@ describe("MatrixRTCSession", () => {
|
||||
await eventSentPromise;
|
||||
|
||||
expect(sendEventMock).toHaveBeenCalledTimes(2);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
@@ -605,6 +698,7 @@ describe("MatrixRTCSession", () => {
|
||||
|
||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||
await keysSentPromise1;
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
sendEventMock.mockClear();
|
||||
jest.advanceTimersByTime(10000);
|
||||
@@ -622,12 +716,232 @@ describe("MatrixRTCSession", () => {
|
||||
|
||||
mockRoom.getLiveTimeline().getState = jest
|
||||
.fn()
|
||||
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId, undefined));
|
||||
.mockReturnValue(makeMockRoomState([membershipTemplate, member2], mockRoom.roomId));
|
||||
sess.onMembershipUpdate();
|
||||
|
||||
await keysSentPromise2;
|
||||
|
||||
expect(sendEventMock).toHaveBeenCalled();
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
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(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
sendEventMock.mockClear();
|
||||
|
||||
// these should be a no-op:
|
||||
sess.onMembershipUpdate();
|
||||
expect(sendEventMock).toHaveBeenCalledTimes(0);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
} 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(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
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(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||
} 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(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
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(".*"),
|
||||
},
|
||||
],
|
||||
sent_ts: Date.now(),
|
||||
},
|
||||
);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
@@ -659,6 +973,8 @@ describe("MatrixRTCSession", () => {
|
||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||
const firstKeysPayload = await keysSentPromise1;
|
||||
expect(firstKeysPayload.keys).toHaveLength(1);
|
||||
expect(firstKeysPayload.keys[0].index).toEqual(0);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
sendEventMock.mockClear();
|
||||
|
||||
@@ -668,15 +984,17 @@ describe("MatrixRTCSession", () => {
|
||||
|
||||
mockRoom.getLiveTimeline().getState = jest
|
||||
.fn()
|
||||
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId, undefined));
|
||||
.mockReturnValue(makeMockRoomState([membershipTemplate], mockRoom.roomId));
|
||||
sess.onMembershipUpdate();
|
||||
|
||||
jest.advanceTimersByTime(10000);
|
||||
|
||||
const secondKeysPayload = await keysSentPromise2;
|
||||
|
||||
expect(secondKeysPayload.keys).toHaveLength(2);
|
||||
expect(secondKeysPayload.keys).toHaveLength(1);
|
||||
expect(secondKeysPayload.keys[0].index).toEqual(1);
|
||||
expect(onMyEncryptionKeyChanged).toHaveBeenCalledTimes(2);
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(2);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
@@ -697,6 +1015,7 @@ describe("MatrixRTCSession", () => {
|
||||
await keysSentPromise1;
|
||||
|
||||
sendEventMock.mockClear();
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
|
||||
const onMembershipsChanged = jest.fn();
|
||||
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
||||
@@ -707,7 +1026,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) => {
|
||||
@@ -715,6 +1034,7 @@ describe("MatrixRTCSession", () => {
|
||||
});
|
||||
|
||||
expect(sendEventMock).not.toHaveBeenCalled();
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysSent).toEqual(1);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
@@ -739,9 +1059,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 +1069,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 +1097,7 @@ describe("MatrixRTCSession", () => {
|
||||
expires: 1000,
|
||||
}),
|
||||
];
|
||||
const mockRoomNoExpired = makeMockRoom(mockMemberships, 0);
|
||||
const mockRoomNoExpired = makeMockRoom(mockMemberships);
|
||||
|
||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoomNoExpired);
|
||||
|
||||
@@ -818,6 +1136,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,11 +1194,13 @@ 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")!;
|
||||
expect(bobKeys).toHaveLength(1);
|
||||
expect(bobKeys[0]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||
});
|
||||
|
||||
it("collects keys at non-zero indices", () => {
|
||||
@@ -898,6 +1219,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")!;
|
||||
@@ -907,6 +1229,134 @@ describe("MatrixRTCSession", () => {
|
||||
expect(bobKeys[2]).toBeFalsy();
|
||||
expect(bobKeys[3]).toBeFalsy();
|
||||
expect(bobKeys[4]).toEqual(Buffer.from("this is the key", "utf-8"));
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||
});
|
||||
|
||||
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"));
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(1);
|
||||
|
||||
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"));
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
|
||||
});
|
||||
|
||||
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"));
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(2);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
@@ -925,9 +1375,78 @@ 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()!)!;
|
||||
expect(myKeys).toBeFalsy();
|
||||
expect(sess!.statistics.counters.roomEventEncryptionKeysReceived).toEqual(0);
|
||||
});
|
||||
|
||||
it("tracks total age statistics for collected keys", () => {
|
||||
jest.useFakeTimers();
|
||||
try {
|
||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
||||
|
||||
// defaults to getTs()
|
||||
jest.setSystemTime(1000);
|
||||
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(0),
|
||||
} as unknown as MatrixEvent);
|
||||
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(1000);
|
||||
|
||||
jest.setSystemTime(2000);
|
||||
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",
|
||||
},
|
||||
],
|
||||
sent_ts: 0,
|
||||
}),
|
||||
getSender: jest.fn().mockReturnValue("@bob:example.org"),
|
||||
getTs: jest.fn().mockReturnValue(Date.now()),
|
||||
} as unknown as MatrixEvent);
|
||||
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(3000);
|
||||
|
||||
jest.setSystemTime(3000);
|
||||
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",
|
||||
},
|
||||
],
|
||||
sent_ts: 1000,
|
||||
}),
|
||||
getSender: jest.fn().mockReturnValue("@bob:example.org"),
|
||||
getTs: jest.fn().mockReturnValue(Date.now()),
|
||||
} as unknown as MatrixEvent);
|
||||
expect(sess!.statistics.totals.roomEventEncryptionKeysReceivedTotalAge).toEqual(5000);
|
||||
} finally {
|
||||
jest.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Vendored
-45
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -21,7 +21,7 @@ import {
|
||||
REFERENCE_RELATION,
|
||||
RelatesToRelationship,
|
||||
TSNamespace,
|
||||
} from "./extensible_events";
|
||||
} from "./extensible_events.ts";
|
||||
|
||||
/**
|
||||
* Identifier for a disclosed poll.
|
||||
|
||||
@@ -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
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+586
-164
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IAuthData {
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
private_key_bits?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a backup key from a passphrase using the salt and iterations from the auth data.
|
||||
* @param authData - The auth data containing the salt and iterations
|
||||
* @param passphrase - The passphrase to derive the key from
|
||||
* @deprecated Deriving a backup key from a passphrase is not part of the matrix spec. Instead, a random key is generated and stored/shared via 4S.
|
||||
*/
|
||||
export function keyFromAuthData(authData: IAuthData, passphrase: string): Promise<Uint8Array> {
|
||||
if (!authData.private_key_salt || !authData.private_key_iterations) {
|
||||
throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase");
|
||||
}
|
||||
|
||||
return deriveRecoveryKeyFromPassphrase(
|
||||
passphrase,
|
||||
authData.private_key_salt,
|
||||
authData.private_key_iterations,
|
||||
authData.private_key_bits,
|
||||
);
|
||||
}
|
||||
@@ -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
@@ -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.
|
||||
|
||||
+75
-11
@@ -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
|
||||
@@ -40,6 +40,13 @@ export interface CryptoApi {
|
||||
*/
|
||||
globalBlacklistUnverifiedDevices: boolean;
|
||||
|
||||
/**
|
||||
* The cryptography mode to use.
|
||||
*
|
||||
* @see CryptoMode
|
||||
*/
|
||||
setCryptoMode(cryptoMode: CryptoMode): void;
|
||||
|
||||
/**
|
||||
* Return the current version of the crypto module.
|
||||
* For example: `Rust SDK ${versions.matrix_sdk_crypto} (${versions.git_sha}), Vodozemac ${versions.vodozemac}`.
|
||||
@@ -589,6 +596,24 @@ export enum DecryptionFailureCode {
|
||||
*/
|
||||
HISTORICAL_MESSAGE_USER_NOT_JOINED = "HISTORICAL_MESSAGE_USER_NOT_JOINED",
|
||||
|
||||
/**
|
||||
* The sender's identity is not verified, but was previously verified.
|
||||
*/
|
||||
SENDER_IDENTITY_PREVIOUSLY_VERIFIED = "SENDER_IDENTITY_PREVIOUSLY_VERIFIED",
|
||||
|
||||
/**
|
||||
* The sender device is not cross-signed. This will only be used if the
|
||||
* crypto mode is set to `CryptoMode.Invisible` or `CryptoMode.Transition`.
|
||||
*/
|
||||
UNSIGNED_SENDER_DEVICE = "UNSIGNED_SENDER_DEVICE",
|
||||
|
||||
/**
|
||||
* We weren't able to link the message back to any known device. This will
|
||||
* only be used if the crypto mode is set to `CryptoMode.Invisible` or
|
||||
* `CryptoMode.Transition`.
|
||||
*/
|
||||
UNKNOWN_SENDER_DEVICE = "UNKNOWN_SENDER_DEVICE",
|
||||
|
||||
/** Unknown or unclassified error. */
|
||||
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
||||
|
||||
@@ -632,6 +657,38 @@ export enum DecryptionFailureCode {
|
||||
UNKNOWN_ENCRYPTION_ALGORITHM = "UNKNOWN_ENCRYPTION_ALGORITHM",
|
||||
}
|
||||
|
||||
/**
|
||||
* The cryptography mode. Affects how messages are encrypted and decrypted.
|
||||
* Only supported by Rust crypto.
|
||||
*/
|
||||
export enum CryptoMode {
|
||||
/**
|
||||
* Message encryption keys are shared with all devices in the room, except for
|
||||
* blacklisted devices, or unverified devices if
|
||||
* `globalBlacklistUnverifiedDevices` is set. Events from all senders are
|
||||
* decrypted.
|
||||
*/
|
||||
Legacy,
|
||||
|
||||
/**
|
||||
* Events are encrypted as with `Legacy` mode, but encryption will throw an error if a
|
||||
* verified user has an unsigned device, or if a verified user replaces
|
||||
* their identity. Events are decrypted only if they come from cross-signed
|
||||
* devices, or devices that existed before the Rust crypto SDK started
|
||||
* tracking device trust: other events will result in a decryption failure. (To access the failure
|
||||
* reason, see {@link MatrixEvent.decryptionFailureReason}.)
|
||||
*/
|
||||
Transition,
|
||||
|
||||
/**
|
||||
* Message encryption keys are only shared with devices that have been cross-signed by their owner.
|
||||
* Encryption will throw an error if a verified user replaces their identity. Events are
|
||||
* decrypted only if they come from a cross-signed device other events will result in a decryption
|
||||
* failure. (To access the failure reason, see {@link MatrixEvent.decryptionFailureReason}.)
|
||||
*/
|
||||
Invisible,
|
||||
}
|
||||
|
||||
/**
|
||||
* Options object for `CryptoApi.bootstrapCrossSigning`.
|
||||
*/
|
||||
@@ -809,6 +866,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,
|
||||
@@ -816,10 +875,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>;
|
||||
}
|
||||
|
||||
@@ -960,5 +1022,7 @@ export interface OwnDeviceKeys {
|
||||
curve25519: string;
|
||||
}
|
||||
|
||||
export * from "./verification";
|
||||
export * from "./keybackup";
|
||||
export * from "./verification.ts";
|
||||
export * from "./keybackup.ts";
|
||||
export * from "./recovery-key.ts";
|
||||
export * from "./key-passphrase.ts";
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const DEFAULT_BIT_SIZE = 256;
|
||||
|
||||
/**
|
||||
* Derive a recovery key from a passphrase and salt using PBKDF2.
|
||||
* @see https://spec.matrix.org/v1.11/client-server-api/#deriving-keys-from-passphrases
|
||||
*
|
||||
* @param passphrase - The passphrase to derive the key from
|
||||
* @param salt - The salt to use in the derivation
|
||||
* @param iterations - The number of iterations to use in the derivation
|
||||
* @param numBits - The number of bits to derive
|
||||
*/
|
||||
export async function deriveRecoveryKeyFromPassphrase(
|
||||
passphrase: string,
|
||||
salt: string,
|
||||
iterations: number,
|
||||
numBits = DEFAULT_BIT_SIZE,
|
||||
): Promise<Uint8Array> {
|
||||
if (!globalThis.crypto.subtle || !TextEncoder) {
|
||||
throw new Error("Password-based backup is not available on this platform");
|
||||
}
|
||||
|
||||
const key = await globalThis.crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(passphrase),
|
||||
{ name: "PBKDF2" },
|
||||
false,
|
||||
["deriveBits"],
|
||||
);
|
||||
|
||||
const keybits = await globalThis.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt: new TextEncoder().encode(salt),
|
||||
iterations: iterations,
|
||||
hash: "SHA-512",
|
||||
},
|
||||
key,
|
||||
numBits,
|
||||
);
|
||||
|
||||
return new Uint8Array(keybits);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 bs58 from "bs58";
|
||||
|
||||
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
|
||||
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
|
||||
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
|
||||
const KEY_SIZE = 32;
|
||||
|
||||
/**
|
||||
* Encode a recovery key using the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation}
|
||||
* @param key
|
||||
*/
|
||||
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
|
||||
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
|
||||
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
|
||||
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
|
||||
|
||||
let parity = 0;
|
||||
for (let i = 0; i < buf.length - 1; ++i) {
|
||||
parity ^= buf[i];
|
||||
}
|
||||
buf[buf.length - 1] = parity;
|
||||
const base58key = bs58.encode(buf);
|
||||
|
||||
return base58key.match(/.{1,4}/g)?.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.
|
||||
* @param recoveryKey
|
||||
*/
|
||||
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
|
||||
|
||||
let parity = 0;
|
||||
for (const b of result) {
|
||||
parity ^= b;
|
||||
}
|
||||
if (parity !== 0) {
|
||||
throw new Error("Incorrect parity");
|
||||
}
|
||||
|
||||
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
|
||||
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
|
||||
throw new Error("Incorrect prefix");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
|
||||
throw new Error("Incorrect length");
|
||||
}
|
||||
|
||||
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
*
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+16
-9
@@ -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);
|
||||
@@ -30,12 +30,17 @@ export interface IEncryptedPayload {
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypt a string
|
||||
* Encrypt a string using AES-CTR.
|
||||
*
|
||||
* @param data - the plaintext to encrypt
|
||||
* @param key - the encryption key to use
|
||||
* @param name - the name of the secret
|
||||
* @param ivStr - the initialization vector to use
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key for
|
||||
* encryption. Obviously, the same key must be provided when decrypting.
|
||||
* @param name - the name of the secret. Used as an input to the HKDF operation which is used to derive the AES key,
|
||||
* so again the same value must be provided when decrypting.
|
||||
* @param ivStr - the base64-encoded initialization vector to use. If not supplied, a random one will be generated.
|
||||
*
|
||||
* @returns The encrypted result, including the ciphertext itself, the initialization vector (as supplied in `ivStr`,
|
||||
* or generated), and an HMAC on the ciphertext — all base64-encoded.
|
||||
*/
|
||||
export async function encryptAES(
|
||||
data: string,
|
||||
@@ -79,11 +84,13 @@ export async function encryptAES(
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt a string
|
||||
* Decrypt an AES-encrypted string.
|
||||
*
|
||||
* @param data - the encrypted data
|
||||
* @param key - the encryption key to use
|
||||
* @param name - the name of the secret
|
||||
* @param data - the encrypted data, returned by {@link encryptAES}.
|
||||
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must
|
||||
* be the same as provided to {@link encryptAES}.
|
||||
* @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES
|
||||
* key, so again must be the same as provided to {@link encryptAES}.
|
||||
*/
|
||||
export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
|
||||
const [aesKey, hmacKey] = await deriveKeys(key, name);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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 { 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";
|
||||
import { encodeRecoveryKey } from "../crypto-api/index.ts";
|
||||
|
||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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>;
|
||||
|
||||
+72
-58
@@ -20,55 +20,59 @@ 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 { 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,15 +81,18 @@ import {
|
||||
SecretStorageKeyObject,
|
||||
SecretStorageKeyTuple,
|
||||
ServerSideSecretStorageImpl,
|
||||
} from "../secret-storage";
|
||||
import { ISecretRequest } from "./SecretSharing";
|
||||
} from "../secret-storage.ts";
|
||||
import { ISecretRequest } from "./SecretSharing.ts";
|
||||
import {
|
||||
BackupTrustInfo,
|
||||
BootstrapCrossSigningOpts,
|
||||
CrossSigningKeyInfo,
|
||||
CrossSigningStatus,
|
||||
CryptoMode,
|
||||
decodeRecoveryKey,
|
||||
DecryptionFailureCode,
|
||||
DeviceVerificationStatus,
|
||||
encodeRecoveryKey,
|
||||
EventEncryptionInfo,
|
||||
EventShieldColour,
|
||||
EventShieldReason,
|
||||
@@ -94,18 +101,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;
|
||||
|
||||
@@ -642,6 +649,13 @@ export class Crypto extends TypedEventEmitter<CryptoEvent, CryptoEventHandlerMap
|
||||
this.backupManager.checkAndStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link Crypto.CryptoApi#setCryptoMode}.
|
||||
*/
|
||||
public setCryptoMode(cryptoMode: CryptoMode): void {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of {@link Crypto.CryptoApi#getVersion}.
|
||||
*/
|
||||
@@ -4374,4 +4388,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";
|
||||
|
||||
@@ -14,75 +14,29 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { randomString } from "../randomstring";
|
||||
import { randomString } from "../randomstring.ts";
|
||||
import { deriveRecoveryKeyFromPassphrase } from "../crypto-api/index.ts";
|
||||
|
||||
const DEFAULT_ITERATIONS = 500000;
|
||||
|
||||
const DEFAULT_BITSIZE = 256;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IAuthData {
|
||||
private_key_salt?: string;
|
||||
private_key_iterations?: number;
|
||||
private_key_bits?: number;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
interface IKey {
|
||||
key: Uint8Array;
|
||||
salt: string;
|
||||
iterations: number;
|
||||
}
|
||||
|
||||
export function keyFromAuthData(authData: IAuthData, password: string): Promise<Uint8Array> {
|
||||
if (!authData.private_key_salt || !authData.private_key_iterations) {
|
||||
throw new Error("Salt and/or iterations not found: " + "this backup cannot be restored with a passphrase");
|
||||
}
|
||||
|
||||
return deriveKey(
|
||||
password,
|
||||
authData.private_key_salt,
|
||||
authData.private_key_iterations,
|
||||
authData.private_key_bits || DEFAULT_BITSIZE,
|
||||
);
|
||||
}
|
||||
|
||||
export async function keyFromPassphrase(password: string): Promise<IKey> {
|
||||
/**
|
||||
* Generate a new recovery key, based on a passphrase.
|
||||
* @param passphrase - The passphrase to generate the key from
|
||||
*/
|
||||
export async function keyFromPassphrase(passphrase: string): Promise<IKey> {
|
||||
const salt = randomString(32);
|
||||
|
||||
const key = await deriveKey(password, salt, DEFAULT_ITERATIONS, DEFAULT_BITSIZE);
|
||||
const key = await deriveRecoveryKeyFromPassphrase(passphrase, salt, DEFAULT_ITERATIONS);
|
||||
|
||||
return { key, salt, iterations: DEFAULT_ITERATIONS };
|
||||
}
|
||||
|
||||
export async function deriveKey(
|
||||
password: string,
|
||||
salt: string,
|
||||
iterations: number,
|
||||
numBits = DEFAULT_BITSIZE,
|
||||
): Promise<Uint8Array> {
|
||||
if (!globalThis.crypto.subtle || !TextEncoder) {
|
||||
throw new Error("Password-based backup is not available on this platform");
|
||||
}
|
||||
|
||||
const key = await globalThis.crypto.subtle.importKey(
|
||||
"raw",
|
||||
new TextEncoder().encode(password),
|
||||
{ name: "PBKDF2" },
|
||||
false,
|
||||
["deriveBits"],
|
||||
);
|
||||
|
||||
const keybits = await globalThis.crypto.subtle.deriveBits(
|
||||
{
|
||||
name: "PBKDF2",
|
||||
salt: new TextEncoder().encode(salt),
|
||||
iterations: iterations,
|
||||
hash: "SHA-512",
|
||||
},
|
||||
key,
|
||||
numBits,
|
||||
);
|
||||
|
||||
return new Uint8Array(keybits);
|
||||
}
|
||||
// Re-export the key passphrase functions to avoid breaking changes
|
||||
export { deriveRecoveryKeyFromPassphrase as deriveKey };
|
||||
export { keyFromAuthData } from "../common-crypto/key-passphrase.ts";
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -14,48 +14,5 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import bs58 from "bs58";
|
||||
|
||||
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
|
||||
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
|
||||
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
|
||||
const KEY_SIZE = 32;
|
||||
|
||||
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
|
||||
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
|
||||
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
|
||||
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
|
||||
|
||||
let parity = 0;
|
||||
for (let i = 0; i < buf.length - 1; ++i) {
|
||||
parity ^= buf[i];
|
||||
}
|
||||
buf[buf.length - 1] = parity;
|
||||
const base58key = bs58.encode(buf);
|
||||
|
||||
return base58key.match(/.{1,4}/g)?.join(" ");
|
||||
}
|
||||
|
||||
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
|
||||
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
|
||||
|
||||
let parity = 0;
|
||||
for (const b of result) {
|
||||
parity ^= b;
|
||||
}
|
||||
if (parity !== 0) {
|
||||
throw new Error("Incorrect parity");
|
||||
}
|
||||
|
||||
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
|
||||
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
|
||||
throw new Error("Incorrect prefix");
|
||||
}
|
||||
}
|
||||
|
||||
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
|
||||
throw new Error("Incorrect length");
|
||||
}
|
||||
|
||||
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
|
||||
}
|
||||
// Re-export to avoid breaking changes
|
||||
export * from "../crypto-api/recovery-key.ts";
|
||||
|
||||
+13
-13
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user