1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-30 04:23:07 +03:00

Apply prettier formatting

This commit is contained in:
Michael Weimann
2022-12-09 09:38:20 +01:00
parent 08a9073bd5
commit 349c2c2587
239 changed files with 22004 additions and 21928 deletions

View File

@ -1,12 +1,15 @@
{
"sourceMaps": true,
"presets": [
["@babel/preset-env", {
[
"@babel/preset-env",
{
"targets": {
"node": 10
},
"modules": "commonjs"
}],
}
],
"@babel/preset-typescript"
],
"plugins": [

View File

@ -1,13 +1,6 @@
module.exports = {
plugins: [
"matrix-org",
"import",
"jsdoc",
],
extends: [
"plugin:matrix-org/babel",
"plugin:import/typescript",
],
plugins: ["matrix-org", "import", "jsdoc"],
extends: ["plugin:matrix-org/babel", "plugin:import/typescript"],
env: {
browser: true,
node: true,
@ -28,12 +21,15 @@ module.exports = {
"padded-blocks": ["error"],
"no-extend-native": ["error"],
"camelcase": ["error"],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
}],
"no-multi-spaces": ["error", { ignoreEOLComments: true }],
"space-before-function-paren": [
"error",
{
anonymous: "never",
named: "never",
asyncArrow: "always",
},
],
"arrow-parens": "off",
"prefer-promise-reject-errors": "off",
"no-constant-condition": "off",
@ -42,30 +38,34 @@ module.exports = {
"no-console": "error",
// restrict EventEmitters to force callers to use TypedEventEmitter
"no-restricted-imports": ["error", {
"no-restricted-imports": [
"error",
{
name: "events",
message: "Please use TypedEventEmitter instead",
}],
"import/no-restricted-paths": ["error", {
"zones": [{
"target": "./src/",
"from": "./src/index.ts",
"message": "The package index is dynamic between src and lib depending on " +
"whether release or development, target the specific module or matrix.ts instead",
}],
}],
},
overrides: [{
files: [
"**/*.ts",
],
plugins: [
"eslint-plugin-tsdoc",
"import/no-restricted-paths": [
"error",
{
zones: [
{
target: "./src/",
from: "./src/index.ts",
message:
"The package index is dynamic between src and lib depending on " +
"whether release or development, target the specific module or matrix.ts instead",
},
],
extends: [
"plugin:matrix-org/typescript",
},
],
},
overrides: [
{
files: ["**/*.ts"],
plugins: ["eslint-plugin-tsdoc"],
extends: ["plugin:matrix-org/typescript"],
rules: {
// TypeScript has its own version of this
"@babel/no-invalid-this": "off",
@ -86,13 +86,11 @@ module.exports = {
"quotes": "off",
// We use a `logger` intermediary module
"no-console": "error",
},
}, {
},
{
// We don't need amazing docs in our spec files
files: [
"src/**/*.ts",
],
files: ["src/**/*.ts"],
rules: {
"tsdoc/syntax": "error",
// We use some select jsdoc rules as the tsdoc linter has only one rule
@ -104,14 +102,14 @@ module.exports = {
// "jsdoc/check-param-names": "error",
// "jsdoc/check-indentation": "error",
},
}, {
files: [
"spec/**/*.ts",
],
},
{
files: ["spec/**/*.ts"],
rules: {
// We don't need super strict typing in test utilities
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
},
}],
},
],
};

View File

@ -2,9 +2,9 @@
## Checklist
* [ ] Tests written for new code (and old code if feasible)
* [ ] Linter and other CI checks pass
* [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md))
- [ ] Tests written for new code (and old code if feasible)
- [ ] Linter and other CI checks pass
- [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md))
<!--
If you would like to specify text for the changelog entry other than your PR title, add the following:

View File

@ -1,6 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>matrix-org/renovate-config-element-web"
]
"extends": ["github>matrix-org/renovate-config-element-web"]
}

View File

@ -17,7 +17,7 @@ jobs:
uses: actions/setup-node@v3
with:
cache: "yarn"
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- name: 🔨 Install dependencies
run: "yarn install --pure-lockfile"

View File

@ -31,7 +31,7 @@ jobs:
with:
repository: ${{ github.event.workflow_run.head_repository.full_name }}
is_pr: ${{ github.event.workflow_run.event == 'pull_request' }}
version_cmd: 'cat package.json | jq -r .version'
version_cmd: "cat package.json | jq -r .version"
branch: ${{ github.event.workflow_run.head_branch }}
revision: ${{ github.event.workflow_run.head_sha }}
token: ${{ secrets.SONAR_TOKEN }}

View File

@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-node@v3
with:
cache: 'yarn'
cache: "yarn"
- name: Install Deps
run: "yarn install"
@ -41,7 +41,7 @@ jobs:
- uses: actions/setup-node@v3
with:
cache: 'yarn'
cache: "yarn"
- name: Install Deps
run: "yarn install"
@ -57,7 +57,7 @@ jobs:
- uses: actions/setup-node@v3
with:
cache: 'yarn'
cache: "yarn"
- name: Install Deps
run: "yarn install"

View File

@ -8,7 +8,7 @@ concurrency:
cancel-in-progress: true
jobs:
jest:
name: 'Jest [${{ matrix.specs }}] (Node ${{ matrix.node }})'
name: "Jest [${{ matrix.specs }}] (Node ${{ matrix.node }})"
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
@ -22,7 +22,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v3
with:
cache: 'yarn'
cache: "yarn"
node-version: ${{ matrix.node }}
- name: Install dependencies

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/setup-node@v3
with:
cache: 'yarn'
cache: "yarn"
- name: Upgrade
run: yarn upgrade && yarn install

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,3 @@
Contributing code to matrix-js-sdk
==================================
# Contributing code to matrix-js-sdk
matrix-js-sdk follows the same pattern as https://github.com/vector-im/element-web/blob/develop/CONTRIBUTING.md

121
README.md
View File

@ -6,27 +6,25 @@
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=matrix-js-sdk&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=matrix-js-sdk&metric=bugs)](https://sonarcloud.io/summary/new_code?id=matrix-js-sdk)
Matrix JavaScript SDK
=====================
# Matrix JavaScript SDK
This is the [Matrix](https://matrix.org) Client-Server SDK for JavaScript and TypeScript. This SDK can be run in a
browser or in Node.js.
The Matrix specification is constantly evolving - while this SDK aims for maximum backwards compatibility, it only
guarantees that a feature will be supported for at least 4 spec releases. For example, if a feature the js-sdk supports
is removed in v1.4 then the feature is *eligible* for removal from the SDK when v1.8 is released. This SDK has no
is removed in v1.4 then the feature is _eligible_ for removal from the SDK when v1.8 is released. This SDK has no
guarantee on implementing all features of any particular spec release, currently. This can mean that the SDK will call
endpoints from before Matrix 1.1, for example.
Quickstart
==========
# Quickstart
## In a browser
In a browser
------------
Download the browser version from
https://github.com/matrix-org/matrix-js-sdk/releases/latest and add that as a
``<script>`` to your page. There will be a global variable ``matrixcs``
attached to ``window`` through which you can access the SDK. See below for how to
`<script>` to your page. There will be a global variable `matrixcs`
attached to `window` through which you can access the SDK. See below for how to
include libolm to enable end-to-end-encryption.
The browser bundle supports recent versions of browsers. Typically this is ES2015
@ -35,8 +33,7 @@ or `> 0.5%, last 2 versions, Firefox ESR, not dead` if using
Please check [the working browser example](examples/browser) for more information.
In Node.js
----------
## In Node.js
Ensure you have the latest LTS version of Node.js installed.
This library relies on `fetch` which is available in Node from v18.0.0 - it should work fine also with polyfills.
@ -45,7 +42,7 @@ If you wish to use a ponyfill or adapter of some sort then pass it as `fetchFn`
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://classic.yarnpkg.com/en/docs/install)
if you do not have it already.
``yarn add matrix-js-sdk``
`yarn add matrix-js-sdk`
```javascript
import * as sdk from "matrix-js-sdk";
@ -67,8 +64,8 @@ await client.startClient({initialSyncLimit: 10});
You can perform a call to `/sync` to get the current state of the client:
```javascript
client.once('sync', function(state, prevState, res) {
if(state === 'PREPARED') {
client.once("sync", function (state, prevState, res) {
if (state === "PREPARED") {
console.log("prepared");
} else {
console.log(state);
@ -81,8 +78,8 @@ To send a message:
```javascript
const content = {
"body": "message text",
"msgtype": "m.text"
body: "message text",
msgtype: "m.text",
};
client.sendEvent("roomId", "m.room.message", content, "", (err, res) => {
console.log(err);
@ -104,17 +101,17 @@ By default, the `matrix-js-sdk` client uses the `MemoryStore` to store events as
```javascript
Object.keys(client.store.rooms).forEach((roomId) => {
client.getRoom(roomId).timeline.forEach(t => {
client.getRoom(roomId).timeline.forEach((t) => {
console.log(t.event);
});
});
```
What does this SDK do?
----------------------
## What does this SDK do?
This SDK provides a full object model around the Matrix Client-Server API and emits
events for incoming data and state changes. Aside from wrapping the HTTP API, it:
- Handles syncing (via `/initialSync` and `/events`)
- Handles the generation of "friendly" room and member names.
- Handles historical `RoomMember` information (e.g. display names).
@ -137,20 +134,18 @@ events for incoming data and state changes. Aside from wrapping the HTTP API, it
- Handles WebRTC calling.
Later versions of the SDK will:
- Expose a `RoomSummary` which would be suitable for a recents page.
- Provide different pluggable storage layers (e.g. local storage, database-backed)
Usage
=====
# Usage
Conventions
-----------
## Conventions
### Emitted events
The SDK will emit events using an ``EventEmitter``. It also
emits object models (e.g. ``Rooms``, ``RoomMembers``) when they
The SDK will emit events using an `EventEmitter`. It also
emits object models (e.g. `Rooms`, `RoomMembers`) when they
are updated.
```javascript
@ -163,8 +158,7 @@ are updated.
client.on("RoomMember.typing", function (event, member) {
if (member.typing) {
console.log(member.name + " is typing...");
}
else {
} else {
console.log(member.name + " stopped typing.");
}
});
@ -187,7 +181,7 @@ The typical usage is something like:
});
```
Alternatively, if you have a Node.js-style ``callback(err, result)`` function,
Alternatively, if you have a Node.js-style `callback(err, result)` function,
you can pass the result of the promise into it with something like:
```javascript
@ -199,13 +193,13 @@ promise-returning function, as that will cause exceptions to go unobserved.
Methods which return a promise show this in their documentation.
Many methods in the SDK support *both* Node.js-style callbacks *and* Promises,
via an optional ``callback`` argument. The callback support is now deprecated:
new methods do not include a ``callback`` argument, and in the future it may be
Many methods in the SDK support _both_ Node.js-style callbacks _and_ Promises,
via an optional `callback` argument. The callback support is now deprecated:
new methods do not include a `callback` argument, and in the future it may be
removed from existing methods.
Examples
--------
## Examples
This section provides some useful code snippets which demonstrate the
core functionality of the SDK. These examples assume the SDK is setup like this:
@ -216,7 +210,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId
userId: myUserId,
});
```
@ -246,7 +240,10 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
}
console.log(
// the room name will update with m.room.name events automatically
"(%s) %s :: %s", room.name, event.getSender(), event.getContent().body
"(%s) %s :: %s",
room.name,
event.getSender(),
event.getContent().body,
);
});
@ -254,6 +251,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
```
Output:
```
(My Room) @megan:localhost :: Hello world
(My Room) @megan:localhost :: how are you?
@ -274,11 +272,7 @@ Output:
console.log(room.name);
console.log(Array(room.name.length + 1).join("=")); // underline
for (var i = 0; i < memberList.length; i++) {
console.log(
"(%s) %s",
memberList[i].membership,
memberList[i].name
);
console.log("(%s) %s", memberList[i].membership, memberList[i].name);
}
});
@ -286,6 +280,7 @@ Output:
```
Output:
```
My Room
=======
@ -295,8 +290,7 @@ Output:
(invite) @charlie:localhost
```
API Reference
=============
# API Reference
A hosted reference can be found at
http://matrix-org.github.io/matrix-js-sdk/index.html
@ -310,21 +304,20 @@ host the API reference from the source files like this:
$ python -m http.server 8005
```
Then visit ``http://localhost:8005`` to see the API docs.
Then visit `http://localhost:8005` to see the API docs.
End-to-end encryption support
=============================
# End-to-end encryption support
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
application to make libolm available, via the ``Olm`` global.
application to make libolm available, via the `Olm` global.
It is also necessary to call ``await matrixClient.initCrypto()`` after creating a new
``MatrixClient`` (but **before** calling ``matrixClient.startClient()``) to
It is also necessary to call `await matrixClient.initCrypto()` after creating a new
`MatrixClient` (but **before** calling `matrixClient.startClient()`) to
initialise the crypto layer.
If the ``Olm`` global is not available, the SDK will show a warning, as shown
below; ``initCrypto()`` will also fail.
If the `Olm` global is not available, the SDK will show a warning, as shown
below; `initCrypto()` will also fail.
```
Unable to load crypto module: crypto will be disabled: Error: global.Olm is not defined
@ -336,41 +329,42 @@ specification.
To provide the Olm library in a browser application:
* download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
- download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
- load `olm.js` as a `<script>` _before_ `browser-matrix.js`.
To provide the Olm library in a node.js application:
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
- `yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz`
(replace the URL with the latest version you want to use from
https://packages.matrix.org/npm/olm/)
* ``global.Olm = require('olm');`` *before* loading ``matrix-js-sdk``.
- `global.Olm = require('olm');` _before_ loading `matrix-js-sdk`.
If you want to package Olm as dependency for your node.js application, you can
use ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``. If your
application also works without e2e crypto enabled, add ``--optional`` to mark it
use `yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz`. If your
application also works without e2e crypto enabled, add `--optional` to mark it
as an optional dependency.
# Contributing
Contributing
============
*This section is for people who want to modify the SDK. If you just
want to use this SDK, skip this section.*
_This section is for people who want to modify the SDK. If you just
want to use this SDK, skip this section._
First, you need to pull in the right build tools:
```
$ yarn install
```
Building
--------
## Building
To build a browser version from scratch when developing::
```
$ yarn build
```
To run tests (Jest):
```
$ yarn test
```
@ -379,6 +373,7 @@ To run tests (Jest):
> The `sync-browserify.spec.ts` requires a browser build (`yarn build`) in order to pass
To run linting:
```
$ yarn lint
```

View File

@ -20,19 +20,19 @@ blurrier.
When we are low on disk space overall or near the group limit / origin quota:
* Chrome
* Log database may fail to start with AbortError
* IndexedDB fails to start for crypto: AbortError in connect from
- Chrome
- Log database may fail to start with AbortError
- IndexedDB fails to start for crypto: AbortError in connect from
indexeddb-store-worker
* When near the quota, QuotaExceededError is used more consistently
* Firefox
* The first error will be QuotaExceededError
* Future write attempts will fail with various errors when space is low,
- When near the quota, QuotaExceededError is used more consistently
- Firefox
- The first error will be QuotaExceededError
- Future write attempts will fail with various errors when space is low,
including nonsense like "InvalidStateError: A mutation operation was
attempted on a database that did not allow mutations."
* Once you start getting errors, the DB is effectively wedged in read-only
- Once you start getting errors, the DB is effectively wedged in read-only
mode
* Can revive access if you reopen the DB
- Can revive access if you reopen the DB
## Cache Eviction
@ -41,9 +41,9 @@ limited by a single quota, in practice, browsers appear to handle `localStorage`
separately from the others, so it has a separate quota limit and isn't evicted
when low on space.
* Chrome, Firefox
* IndexedDB for origin deleted
* Local Storage remains in place
- Chrome, Firefox
- IndexedDB for origin deleted
- Local Storage remains in place
## Persistent Storage
@ -51,12 +51,12 @@ Storage Standard offers a `navigator.storage.persist` API that can be used to
request persistent storage that won't be deleted by the browser because of low
space.
* Chrome
* Chrome 75 seems to grant this without any prompt based on [interaction
- Chrome
- Chrome 75 seems to grant this without any prompt based on [interaction
criteria](https://developers.google.com/web/updates/2016/06/persistent-storage)
* Firefox
* Firefox 67 shows a prompt to grant
* Reverting persistent seems to require revoking permission _and_ clearing
- Firefox
- Firefox 67 shows a prompt to grant
- Reverting persistent seems to require revoking permission _and_ clearing
site data
## Storage Estimation
@ -64,7 +64,7 @@ space.
Storage Standard offers a `navigator.storage.estimate` API to get some clue of
how much space remains.
* Chrome, Firefox
* Can run this at any time to request an estimate of space remaining
* Firefox
* Returns `0` for `usage` if a site is persisted
- Chrome, Firefox
- Can run this at any time to request an estimate of space remaining
- Firefox
- Returns `0` for `usage` if a site is persisted

View File

@ -6,4 +6,4 @@ To try it out, **you must build the SDK first** and then host this folder:
$ python -m http.server 8003
```
Then visit ``http://localhost:8003``.
Then visit `http://localhost:8003`.

View File

@ -2,14 +2,13 @@
<head>
<title>Test</title>
<meta charset="utf-8" />
<link rel="icon" href="data:,">
<link rel="icon" href="data:," />
<script src="lib/matrix.js"></script>
<script src="browserTest.js"></script>
</head>
<body>
Sanity Testing (check the console) : This example is here to make sure that
the SDK works inside a browser. It simply does a GET /publicRooms on
matrix.org
Sanity Testing (check the console) : This example is here to make sure that the SDK works inside a browser. It
simply does a GET /publicRooms on matrix.org
<br />
You should see a message confirming that the SDK works below:
<br />

View File

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Test Crypto in Browser</title>
<script src="lib/olm.js"></script>
<script src="lib/matrix.js"></script>
@ -11,14 +11,10 @@
<body>
<h1>Testing export/import of Olm devices in the browser</h1>
<ul>
<li>Make sure you built the current version of the Matrix JS SDK (<code>yarn build</code>)</li>
<li>
Make sure you built the current version of the Matrix JS SDK
(<code>yarn build</code>)
</li>
<li>
copy <code>olm.js</code> and <code>olm.wasm</code>
from a recent release of Olm (was tested with version 3.1.4)
in directory <code>lib/</code>
copy <code>olm.js</code> and <code>olm.wasm</code> from a recent release of Olm (was tested with version
3.1.4) in directory <code>lib/</code>
</li>
<li>start a local Matrix homeserver (on port 8008, or change the port in the code)</li>
<li>Serve this HTML file (e.g. <code>python3 -m http.server</code>) and go to it through your browser</li>
@ -28,11 +24,14 @@
aliceMatrixClient = await newMatrixClient("alice-"+randomHex());
await aliceMatrixClient.exportDevice();
await aliceMatrixClient.getAccessToken();
</pre>
</pre
>
</li>
<li>
copy the result of <code>exportDevice</code> and <code>getAccessToken</code> somewhere
(<strong>not</strong> in a JS variable as it will be destroyed when you refresh the page)
copy the result of <code>exportDevice</code> and <code>getAccessToken</code> somewhere (<strong
>not</strong
>
in a JS variable as it will be destroyed when you refresh the page)
</li>
<li><strong>refresh the page (F5)</strong> to make sure the client is destroyed</li>
<li>
@ -42,14 +41,16 @@ await aliceMatrixClient.getAccessToken();
bobMatrixClient = await newMatrixClient("bob-"+randomHex());
roomId = await bobMatrixClient.createEncryptedRoom([ALICE_ID]);
await bobMatrixClient.sendTextMessage('Hi Alice!', roomId);
</pre>
</pre
>
</li>
<li>Again, <strong>refresh the page (F5)</strong>. You may want to clear your console as well.</li>
<li>
Now do the following, using the exported data and the access token you saved previously:
<pre>
aliceMatrixClient = await importMatrixClient(EXPORTED_DATA, ACCESS_TOKEN);
</pre>
</pre
>
</li>
<li>You should see the message sent by Bob printed in the console.</li>
</ul>

View File

@ -1,26 +1,18 @@
if (!Olm) {
console.error(
"global.Olm does not seem to be present."
+ " Did you forget to add olm in the lib/ directory?"
);
console.error("global.Olm does not seem to be present." + " Did you forget to add olm in the lib/ directory?");
}
const BASE_URL = 'http://localhost:8008';
const ROOM_CRYPTO_CONFIG = { algorithm: 'm.megolm.v1.aes-sha2' };
const PASSWORD = 'password';
const BASE_URL = "http://localhost:8008";
const ROOM_CRYPTO_CONFIG = { algorithm: "m.megolm.v1.aes-sha2" };
const PASSWORD = "password";
// useful to create new usernames
window.randomHex = () => Math.floor(Math.random() * (10**6)).toString(16);
window.randomHex = () => Math.floor(Math.random() * 10 ** 6).toString(16);
window.newMatrixClient = async function (username) {
const registrationClient = matrixcs.createClient(BASE_URL);
const userRegisterResult = await registrationClient.register(
username,
PASSWORD,
null,
{ type: 'm.login.dummy' }
);
const userRegisterResult = await registrationClient.register(username, PASSWORD, null, { type: "m.login.dummy" });
const matrixClient = matrixcs.createClient({
baseUrl: BASE_URL,
@ -36,7 +28,7 @@ window.newMatrixClient = async function (username) {
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
};
window.importMatrixClient = async function (exportedDevice, accessToken) {
const matrixClient = matrixcs.createClient({
@ -52,37 +44,35 @@ window.importMatrixClient = async function (exportedDevice, accessToken) {
await matrixClient.initCrypto();
await matrixClient.startClient();
return matrixClient;
}
};
function extendMatrixClient(matrixClient) {
// automatic join
matrixClient.on('RoomMember.membership', async (event, member) => {
if (member.membership === 'invite' && member.userId === matrixClient.getUserId()) {
matrixClient.on("RoomMember.membership", async (event, member) => {
if (member.membership === "invite" && member.userId === matrixClient.getUserId()) {
await matrixClient.joinRoom(member.roomId);
// setting up of room encryption seems to be triggered automatically
// but if we don't wait for it the first messages we send are unencrypted
await matrixClient.setRoomEncryption(member.roomId, { algorithm: 'm.megolm.v1.aes-sha2' })
await matrixClient.setRoomEncryption(member.roomId, { algorithm: "m.megolm.v1.aes-sha2" });
}
});
matrixClient.onDecryptedMessage = message => {
console.log('Got encrypted message: ', message);
}
matrixClient.onDecryptedMessage = (message) => {
console.log("Got encrypted message: ", message);
};
matrixClient.on('Event.decrypted', (event) => {
if (event.getType() === 'm.room.message'){
matrixClient.on("Event.decrypted", (event) => {
if (event.getType() === "m.room.message") {
matrixClient.onDecryptedMessage(event.getContent().body);
} else {
console.log('decrypted an event of type', event.getType());
console.log("decrypted an event of type", event.getType());
console.log(event);
}
});
matrixClient.createEncryptedRoom = async function (usersToInvite) {
const {
room_id: roomId,
} = await this.createRoom({
visibility: 'private',
const { room_id: roomId } = await this.createRoom({
visibility: "private",
invite: usersToInvite,
});
@ -90,16 +80,12 @@ function extendMatrixClient(matrixClient) {
// but does not send anything to the server
// (see https://github.com/matrix-org/matrix-js-sdk/issues/905)
// so we do it ourselves with 'sendStateEvent'
await this.sendStateEvent(
roomId, 'm.room.encryption', ROOM_CRYPTO_CONFIG,
);
await this.setRoomEncryption(
roomId, ROOM_CRYPTO_CONFIG,
);
await this.sendStateEvent(roomId, "m.room.encryption", ROOM_CRYPTO_CONFIG);
await this.setRoomEncryption(roomId, ROOM_CRYPTO_CONFIG);
// Marking all devices as verified
let room = this.getRoom(roomId);
let members = (await room.getEncryptionTargetMembers()).map(x => x["userId"])
let members = (await room.getEncryptionTargetMembers()).map((x) => x["userId"]);
let memberkeys = await this.downloadKeys(members);
for (const userId in memberkeys) {
for (const deviceId in memberkeys[userId]) {
@ -108,15 +94,12 @@ function extendMatrixClient(matrixClient) {
}
return roomId;
}
};
matrixClient.sendTextMessage = async function (message, roomId) {
return matrixClient.sendMessage(
roomId,
{
return matrixClient.sendMessage(roomId, {
body: message,
msgtype: 'm.text',
}
)
}
msgtype: "m.text",
});
};
}

View File

@ -1,6 +1,5 @@
This is a functional terminal app which allows you to see the room list for a user, join rooms, send messages and view room membership lists.
To try it out, you will need to edit `app.js` to configure it for your `homeserver`, `access_token` and `user_id`. Then run:
```

View File

@ -5,7 +5,7 @@ var clc = require("cli-color");
var matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId
userId: myUserId,
});
// Data structures
@ -14,15 +14,15 @@ var viewingRoom = null;
var numMessagesToShow = 20;
// Reading from stdin
var CLEAR_CONSOLE = '\x1B[2J';
var CLEAR_CONSOLE = "\x1B[2J";
var readline = require("readline");
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
completer: completer
completer: completer,
});
rl.setPrompt("$ ");
rl.on('line', function(line) {
rl.on("line", function (line) {
if (line.trim().length === 0) {
rl.prompt();
return;
@ -37,14 +37,11 @@ rl.on('line', function(line) {
if (line === "/exit") {
viewingRoom = null;
printRoomList();
}
else if (line === "/members") {
} else if (line === "/members") {
printMemberList(viewingRoom);
}
else if (line === "/roominfo") {
} else if (line === "/roominfo") {
printRoomInfo(viewingRoom);
}
else if (line === "/resend") {
} else if (line === "/resend") {
// get the oldest not sent event.
var notSentEvent;
for (var i = 0; i < viewingRoom.timeline.length; i++) {
@ -54,52 +51,59 @@ rl.on('line', function(line) {
}
}
if (notSentEvent) {
matrixClient.resendEvent(notSentEvent, viewingRoom).then(function() {
matrixClient.resendEvent(notSentEvent, viewingRoom).then(
function () {
printMessages();
rl.prompt();
}, function(err) {
},
function (err) {
printMessages();
print("/resend Error: %s", err);
rl.prompt();
});
},
);
printMessages();
rl.prompt();
}
}
else if (line.indexOf("/more ") === 0) {
} else if (line.indexOf("/more ") === 0) {
var amount = parseInt(line.split(" ")[1]) || 20;
matrixClient.scrollback(viewingRoom, amount).then(function(room) {
matrixClient.scrollback(viewingRoom, amount).then(
function (room) {
printMessages();
rl.prompt();
}, function(err) {
},
function (err) {
print("/more Error: %s", err);
});
}
else if (line.indexOf("/invite ") === 0) {
},
);
} else if (line.indexOf("/invite ") === 0) {
var userId = line.split(" ")[1].trim();
matrixClient.invite(viewingRoom.roomId, userId).then(function() {
matrixClient.invite(viewingRoom.roomId, userId).then(
function () {
printMessages();
rl.prompt();
}, function(err) {
},
function (err) {
print("/invite Error: %s", err);
});
}
else if (line.indexOf("/file ") === 0) {
},
);
} else if (line.indexOf("/file ") === 0) {
var filename = line.split(" ")[1].trim();
var stream = fs.createReadStream(filename);
matrixClient.uploadContent({
matrixClient
.uploadContent({
stream: stream,
name: filename
}).then(function(url) {
name: filename,
})
.then(function (url) {
var content = {
msgtype: "m.file",
body: filename,
url: JSON.parse(url).content_uri
url: JSON.parse(url).content_uri,
};
matrixClient.sendMessage(viewingRoom.roomId, content);
});
}
else {
} else {
matrixClient.sendTextMessage(viewingRoom.roomId, line).finally(function () {
printMessages();
rl.prompt();
@ -107,23 +111,24 @@ rl.on('line', function(line) {
// print local echo immediately
printMessages();
}
}
else {
} else {
if (line.indexOf("/join ") === 0) {
var roomIndex = line.split(" ")[1];
viewingRoom = roomList[roomIndex];
if (viewingRoom.getMember(myUserId).membership === "invite") {
// join the room first
matrixClient.joinRoom(viewingRoom.roomId).then(function(room) {
matrixClient.joinRoom(viewingRoom.roomId).then(
function (room) {
setRoomList();
viewingRoom = room;
printMessages();
rl.prompt();
}, function(err) {
},
function (err) {
print("/join Error: %s", err);
});
}
else {
},
);
} else {
printMessages();
}
}
@ -177,8 +182,7 @@ function setRoomList() {
}
if (aMsg.getTs() > bMsg.getTs()) {
return 1;
}
else if (aMsg.getTs() < bMsg.getTs()) {
} else if (aMsg.getTs() < bMsg.getTs()) {
return -1;
}
return 0;
@ -189,16 +193,15 @@ function printRoomList() {
print(CLEAR_CONSOLE);
print("Room List:");
var fmts = {
"invite": clc.cyanBright,
"leave": clc.blackBright
invite: clc.cyanBright,
leave: clc.blackBright,
};
for (var i = 0; i < roomList.length; i++) {
var msg = roomList[i].timeline[roomList[i].timeline.length - 1];
var dateStr = "---";
var fmt;
if (msg) {
dateStr = new Date(msg.getTs()).toISOString().replace(
/T/, ' ').replace(/\..+/, '');
dateStr = new Date(msg.getTs()).toISOString().replace(/T/, " ").replace(/\..+/, "");
}
var myMembership = roomList[i].getMyMembership();
if (myMembership) {
@ -207,9 +210,10 @@ function printRoomList() {
var roomName = fixWidth(roomList[i].name, 25);
print(
"[%s] %s (%s members) %s",
i, fmt ? fmt(roomName) : roomName,
i,
fmt ? fmt(roomName) : roomName,
roomList[i].getJoinedMembers().length,
dateStr
dateStr,
);
}
}
@ -230,12 +234,12 @@ function printHelp() {
}
function completer(line) {
var completions = [
"/help", "/join ", "/exit", "/members", "/more ", "/resend", "/invite"
];
var hits = completions.filter(function(c) { return c.indexOf(line) == 0 });
var completions = ["/help", "/join ", "/exit", "/members", "/more ", "/resend", "/invite"];
var hits = completions.filter(function (c) {
return c.indexOf(line) == 0;
});
// show all completions if none found
return [hits.length ? hits : completions, line]
return [hits.length ? hits : completions, line];
}
function printMessages() {
@ -252,10 +256,10 @@ function printMessages() {
function printMemberList(room) {
var fmts = {
"join": clc.green,
"ban": clc.red,
"invite": clc.blue,
"leave": clc.blackBright
join: clc.green,
ban: clc.red,
invite: clc.blue,
leave: clc.blackBright,
};
var members = room.currentState.getMembers();
// sorted based on name.
@ -268,21 +272,24 @@ function printMemberList(room) {
}
return 0;
});
print("Membership list for room \"%s\"", room.name);
print('Membership list for room "%s"', room.name);
print(new Array(room.name.length + 28).join("-"));
room.currentState.getMembers().forEach(function (member) {
if (!member.membership) {
return;
}
var fmt = fmts[member.membership] || function(a){return a;};
var membershipWithPadding = (
member.membership + new Array(10 - member.membership.length).join(" ")
);
var fmt =
fmts[member.membership] ||
function (a) {
return a;
};
var membershipWithPadding = member.membership + new Array(10 - member.membership.length).join(" ");
print(
"%s" + fmt(" :: ") + "%s" + fmt(" (") + "%s" + fmt(")"),
membershipWithPadding, member.name,
(member.userId === myUserId ? "Me" : member.userId),
fmt
membershipWithPadding,
member.name,
member.userId === myUserId ? "Me" : member.userId,
fmt,
);
});
}
@ -292,38 +299,31 @@ function printRoomInfo(room) {
var eTypeHeader = " Event Type(state_key) ";
var sendHeader = " Sender ";
// pad content to 100
var restCount = (
100 - "Content".length - " | ".length - " | ".length -
eTypeHeader.length - sendHeader.length
);
var restCount = 100 - "Content".length - " | ".length - " | ".length - eTypeHeader.length - sendHeader.length;
var padSide = new Array(Math.floor(restCount / 2)).join(" ");
var contentHeader = padSide + "Content" + padSide;
print(eTypeHeader + sendHeader + contentHeader);
print(new Array(100).join("-"));
eventMap.keys().forEach(function (eventType) {
if (eventType === "m.room.member") { return; } // use /members instead.
if (eventType === "m.room.member") {
return;
} // use /members instead.
var eventEventMap = eventMap.get(eventType);
eventEventMap.keys().forEach(function (stateKey) {
var typeAndKey = eventType + (
stateKey.length > 0 ? "("+stateKey+")" : ""
);
var typeAndKey = eventType + (stateKey.length > 0 ? "(" + stateKey + ")" : "");
var typeStr = fixWidth(typeAndKey, eTypeHeader.length);
var event = eventEventMap.get(stateKey);
var sendStr = fixWidth(event.getSender(), sendHeader.length);
var contentStr = fixWidth(
JSON.stringify(event.getContent()), contentHeader.length
);
var contentStr = fixWidth(JSON.stringify(event.getContent()), contentHeader.length);
print(typeStr + " | " + sendStr + " | " + contentStr);
});
})
});
}
function printLine(event) {
var fmt;
var name = event.sender ? event.sender.name : event.getSender();
var time = new Date(
event.getTs()
).toISOString().replace(/T/, ' ').replace(/\..+/, '');
var time = new Date(event.getTs()).toISOString().replace(/T/, " ").replace(/\..+/, "");
var separator = "<<<";
if (event.getSender() === myUserId) {
name = "Me";
@ -331,8 +331,7 @@ function printLine(event) {
if (event.status === sdk.EventStatus.SENDING) {
separator = "...";
fmt = clc.xterm(8);
}
else if (event.status === sdk.EventStatus.NOT_SENT) {
} else if (event.status === sdk.EventStatus.NOT_SENT) {
separator = " x ";
fmt = clc.redBright;
}
@ -346,32 +345,23 @@ function printLine(event) {
if (event.getType() === "m.room.message") {
body = event.getContent().body;
}
else if (event.isState()) {
} else if (event.isState()) {
var stateName = event.getType();
if (event.getStateKey().length > 0) {
stateName += " (" + event.getStateKey() + ")";
}
body = (
"[State: "+stateName+" updated to: "+JSON.stringify(event.getContent())+"]"
);
body = "[State: " + stateName + " updated to: " + JSON.stringify(event.getContent()) + "]";
separator = "---";
fmt = clc.xterm(249).italic;
}
else {
} else {
// random message event
body = (
"[Message: "+event.getType()+" Content: "+JSON.stringify(event.getContent())+"]"
);
body = "[Message: " + event.getType() + " Content: " + JSON.stringify(event.getContent()) + "]";
separator = "---";
fmt = clc.xterm(249).italic;
}
if (fmt) {
print(
"[%s] %s %s %s", time, name, separator, body, fmt
);
}
else {
print("[%s] %s %s %s", time, name, separator, body, fmt);
} else {
print("[%s] %s %s %s", time, name, separator, body);
}
}
@ -390,8 +380,7 @@ function print(str, formatter) {
newArgs[i] = fmt(newArgs[i]);
}
console.log.apply(console.log, newArgs);
}
else {
} else {
console.log.apply(console.log, arguments);
}
}
@ -399,8 +388,7 @@ function print(str, formatter) {
function fixWidth(str, len) {
if (str.length > len) {
return str.substring(0, len - 2) + "\u2026";
}
else if (str.length < len) {
} else if (str.length < len) {
return str + new Array(len - str.length).join(" ");
}
return str;

View File

@ -6,4 +6,4 @@ To try it out, **you must build the SDK first** and then host this folder:
$ python -m SimpleHTTPServer 8003
```
Then visit ``http://localhost:8003``.
Then visit `http://localhost:8003`.

View File

@ -9,7 +9,7 @@ const client = matrixcs.createClient({
baseUrl: BASE_URL,
accessToken: TOKEN,
userId: USER_ID,
deviceId: DEVICE_ID
deviceId: DEVICE_ID,
});
let call;
@ -23,9 +23,7 @@ function addListeners(call) {
let lastError = "";
call.on("hangup", function () {
disableButtons(false, true, true);
document.getElementById("result").innerHTML = (
"<p>Call ended. Last error: "+lastError+"</p>"
);
document.getElementById("result").innerHTML = "<p>Call ended. Last error: " + lastError + "</p>";
});
call.on("error", function (err) {
lastError = err.message;
@ -53,10 +51,17 @@ function addListeners(call) {
window.onload = function () {
document.getElementById("result").innerHTML = "<p>Please wait. Syncing...</p>";
document.getElementById("config").innerHTML = "<p>" +
"Homeserver: <code>"+BASE_URL+"</code><br/>"+
"Room: <code>"+ROOM_ID+"</code><br/>"+
"User: <code>"+USER_ID+"</code><br/>"+
document.getElementById("config").innerHTML =
"<p>" +
"Homeserver: <code>" +
BASE_URL +
"</code><br/>" +
"Room: <code>" +
ROOM_ID +
"</code><br/>" +
"User: <code>" +
USER_ID +
"</code><br/>" +
"</p>";
disableButtons(true, true, true);
};
@ -75,9 +80,7 @@ function syncComplete() {
document.getElementById("call").onclick = function () {
console.log("Placing call...");
call = matrixcs.createNewMatrixCall(
client, ROOM_ID
);
call = matrixcs.createNewMatrixCall(client, ROOM_ID);
console.log("Call => %s", call);
addListeners(call);
call.placeVideoCall();

View File

@ -1,5 +1,4 @@
<html>
<head>
<title>VoIP Test</title>
<script src="lib/matrix.js"></script>
@ -7,8 +6,8 @@
</head>
<body>
You can place and receive calls with this example. Make sure to edit the
constants in <code>browserTest.js</code> first.
You can place and receive calls with this example. Make sure to edit the constants in
<code>browserTest.js</code> first.
<div id="config"></div>
<div id="result"></div>
<button id="call">Place Call</button>
@ -19,7 +18,6 @@
<video class="video-element" id="remote"></video>
</div>
</body>
</html>
<style>

View File

@ -1,12 +1,12 @@
#!/usr/bin/env node
const fsProm = require('fs/promises');
const fsProm = require("fs/promises");
const PKGJSON = 'package.json';
const PKGJSON = "package.json";
async function main() {
const pkgJson = JSON.parse(await fsProm.readFile(PKGJSON, 'utf8'));
for (const field of ['main', 'typings']) {
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];
}

View File

@ -17,12 +17,12 @@ limitations under the License.
*/
// load olm before the sdk if possible
import './olm-loader';
import "./olm-loader";
import MockHttpBackend from 'matrix-mock-request';
import MockHttpBackend from "matrix-mock-request";
import { LocalStorageCryptoStore } from '../src/crypto/store/localStorage-crypto-store';
import { logger } from '../src/logger';
import { LocalStorageCryptoStore } from "../src/crypto/store/localStorage-crypto-store";
import { logger } from "../src/logger";
import { syncPromise } from "./test-utils/test-utils";
import { createClient } from "../src/matrix";
import { ICreateClientOpts, IDownloadKeyResult, MatrixClient, PendingEventOrdering } from "../src/client";
@ -30,7 +30,7 @@ import { MockStorageApi } from "./MockStorageApi";
import { encodeUri } from "../src/utils";
import { IDeviceKeys, IOneTimeKey } from "../src/crypto/dehydration";
import { IKeyBackupSession } from "../src/crypto/keybackup";
import { IKeysUploadResponse, IUploadKeysRequest } from '../src/client';
import { IKeysUploadResponse, IUploadKeysRequest } from "../src/client";
/**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@ -73,14 +73,14 @@ export class TestClient {
}
public toString(): string {
return 'TestClient[' + this.userId + ']';
return "TestClient[" + this.userId + "]";
}
/**
* start the client, and wait for it to initialise.
*/
public start(): Promise<void> {
logger.log(this + ': starting');
logger.log(this + ": starting");
this.httpBackend.when("GET", "/versions").respond(200, {});
this.httpBackend.when("GET", "/pushrules").respond(200, {});
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
@ -95,11 +95,8 @@ export class TestClient {
pendingEventOrdering: PendingEventOrdering.Detached,
});
return Promise.all([
this.httpBackend.flushAllExpected(),
syncPromise(this.client),
]).then(() => {
logger.log(this + ': started');
return Promise.all([this.httpBackend.flushAllExpected(), syncPromise(this.client)]).then(() => {
logger.log(this + ": started");
});
}
@ -116,12 +113,13 @@ export class TestClient {
* Set up expectations that the client will upload device keys.
*/
public expectDeviceKeyUpload() {
this.httpBackend.when("POST", "/keys/upload")
this.httpBackend
.when("POST", "/keys/upload")
.respond<IKeysUploadResponse, IUploadKeysRequest>(200, (_path, content) => {
expect(content.one_time_keys).toBe(undefined);
expect(content.device_keys).toBeTruthy();
logger.log(this + ': received device keys');
logger.log(this + ": received device keys");
// we expect this to happen before any one-time keys are uploaded.
expect(Object.keys(this.oneTimeKeys!).length).toEqual(0);
@ -143,30 +141,35 @@ export class TestClient {
return Promise.resolve(this.oneTimeKeys!);
}
this.httpBackend.when("POST", "/keys/upload")
this.httpBackend
.when("POST", "/keys/upload")
.respond<IKeysUploadResponse, IUploadKeysRequest>(200, (_path, content: IUploadKeysRequest) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBe(undefined);
return { one_time_key_counts: {
return {
one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
} };
},
};
});
this.httpBackend.when("POST", "/keys/upload")
this.httpBackend
.when("POST", "/keys/upload")
.respond<IKeysUploadResponse, IUploadKeysRequest>(200, (_path, content: IUploadKeysRequest) => {
expect(content.device_keys).toBe(undefined);
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).not.toEqual({});
logger.log('%s: received %i one-time keys', this,
Object.keys(content.one_time_keys!).length);
logger.log("%s: received %i one-time keys", this, Object.keys(content.one_time_keys!).length);
this.oneTimeKeys = content.one_time_keys;
return { one_time_key_counts: {
return {
one_time_key_counts: {
signed_curve25519: Object.keys(this.oneTimeKeys!).length,
} };
},
};
});
// this can take ages
return this.httpBackend.flush('/keys/upload', 2, 1000).then((flushed) => {
return this.httpBackend.flush("/keys/upload", 2, 1000).then((flushed) => {
expect(flushed).toEqual(2);
return this.oneTimeKeys!;
});
@ -180,8 +183,7 @@ export class TestClient {
* @param response - response to the query.
*/
public expectKeyQuery(response: IDownloadKeyResult) {
this.httpBackend.when('POST', '/keys/query').respond<IDownloadKeyResult>(
200, (_path, content) => {
this.httpBackend.when("POST", "/keys/query").respond<IDownloadKeyResult>(200, (_path, content) => {
Object.keys(response.device_keys).forEach((userId) => {
expect((content.device_keys! as Record<string, any>)[userId]).toEqual([]);
});
@ -193,10 +195,15 @@ export class TestClient {
* Set up expectations that the client will query key backups for a particular session
*/
public expectKeyBackupQuery(roomId: string, sessionId: string, status: number, response: IKeyBackupSession) {
this.httpBackend.when('GET', encodeUri("/room_keys/keys/$roomId/$sessionId", {
this.httpBackend
.when(
"GET",
encodeUri("/room_keys/keys/$roomId/$sessionId", {
$roomId: roomId,
$sessionId: sessionId,
})).respond(status, response);
}),
)
.respond(status, response);
}
/**
@ -205,7 +212,7 @@ export class TestClient {
* @returns base64 device key
*/
public getDeviceKey(): string {
const keyId = 'curve25519:' + this.deviceId;
const keyId = "curve25519:" + this.deviceId;
return this.deviceKeys!.keys[keyId];
}
@ -215,7 +222,7 @@ export class TestClient {
* @returns base64 device key
*/
public getSigningKey(): string {
const keyId = 'ed25519:' + this.deviceId;
const keyId = "ed25519:" + this.deviceId;
return this.deviceKeys!.keys[keyId];
}
@ -224,10 +231,7 @@ export class TestClient {
*/
public flushSync(): Promise<void> {
logger.log(`${this}: flushSync`);
return Promise.all([
this.httpBackend.flush('/sync', 1),
syncPromise(this.client),
]).then(() => {
return Promise.all([this.httpBackend.flush("/sync", 1), syncPromise(this.client)]).then(() => {
logger.log(`${this}: flushSync completed`);
});
}

View File

@ -68,9 +68,7 @@ describe("Browserify Test", function() {
join: {
[ROOM_ID]: {
timeline: {
events: [
event,
],
events: [event],
limited: false,
},
},
@ -81,7 +79,7 @@ describe("Browserify Test", function() {
httpBackend.when("GET", "/sync").respond(200, syncData);
httpBackend.when("GET", "/sync").respond(200, syncData);
const syncPromise = new Promise(r => client.once(global.matrixcs.ClientEvent.Sync, r));
const syncPromise = new Promise((r) => client.once(global.matrixcs.ClientEvent.Sync, r));
const unexpectedErrorFn = jest.fn();
client.once(global.matrixcs.ClientEvent.SyncUnexpectedError, unexpectedErrorFn);

View File

@ -16,9 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { TestClient } from '../TestClient';
import * as testUtils from '../test-utils/test-utils';
import { logger } from '../../src/logger';
import { TestClient } from "../TestClient";
import * as testUtils from "../test-utils/test-utils";
import { logger } from "../../src/logger";
const ROOM_ID = "!room:id";
@ -31,20 +31,22 @@ const ROOM_ID = "!room:id";
function getSyncResponse(roomMembers: string[]) {
const stateEvents = [
testUtils.mkEvent({
type: 'm.room.encryption',
skey: '',
type: "m.room.encryption",
skey: "",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
},
}),
];
Array.prototype.push.apply(
stateEvents,
roomMembers.map((m) => testUtils.mkMembership({
mship: 'join',
roomMembers.map((m) =>
testUtils.mkMembership({
mship: "join",
sender: m,
})),
}),
),
);
const syncResponse = {
@ -65,7 +67,7 @@ function getSyncResponse(roomMembers: string[]) {
describe("DeviceList management:", function () {
if (!global.Olm) {
logger.warn('not running deviceList tests: Olm not present');
logger.warn("not running deviceList tests: Olm not present");
return;
}
@ -73,9 +75,7 @@ describe("DeviceList management:", function() {
let sessionStoreBackend: Storage;
async function createTestClient() {
const testClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs", sessionStoreBackend,
);
const testClient = new TestClient("@alice:localhost", "xzcvb", "akjgkrgjs", sessionStoreBackend);
await testClient.client.initCrypto();
return testClient;
}
@ -94,38 +94,40 @@ describe("DeviceList management:", function() {
it("Alice shouldn't do a second /query for non-e2e-capable devices", function () {
aliceTestClient.expectKeyQuery({
device_keys: { '@alice:localhost': {} },
device_keys: { "@alice:localhost": {} },
failures: {},
});
return aliceTestClient.start().then(function() {
const syncResponse = getSyncResponse(['@bob:xyz']);
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
return aliceTestClient
.start()
.then(function () {
const syncResponse = getSyncResponse(["@bob:xyz"]);
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
return aliceTestClient.flushSync();
}).then(function() {
})
.then(function () {
logger.log("Forcing alice to download our device keys");
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, {
device_keys: {
'@bob:xyz': {},
"@bob:xyz": {},
},
});
return Promise.all([
aliceTestClient.client.downloadKeys(['@bob:xyz']),
aliceTestClient.httpBackend.flush('/keys/query', 1),
aliceTestClient.client.downloadKeys(["@bob:xyz"]),
aliceTestClient.httpBackend.flush("/keys/query", 1),
]);
}).then(function() {
})
.then(function () {
logger.log("Telling alice to send a megolm message");
aliceTestClient.httpBackend.when(
'PUT', '/send/',
).respond(200, {
event_id: '$event_id',
aliceTestClient.httpBackend.when("PUT", "/send/").respond(200, {
event_id: "$event_id",
});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.client.sendTextMessage(ROOM_ID, "test"),
// the crypto stuff can take a while, so give the requests a whole second.
aliceTestClient.httpBackend.flushAllExpected({
@ -138,134 +140,144 @@ describe("DeviceList management:", function() {
it.skip("We should not get confused by out-of-order device query responses", () => {
// https://github.com/vector-im/element-web/issues/3126
aliceTestClient.expectKeyQuery({
device_keys: { '@alice:localhost': {} },
device_keys: { "@alice:localhost": {} },
failures: {},
});
return aliceTestClient.start().then(() => {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
return aliceTestClient
.start()
.then(() => {
aliceTestClient.httpBackend
.when("GET", "/sync")
.respond(200, getSyncResponse(["@bob:xyz", "@chris:abc"]));
return aliceTestClient.flushSync();
}).then(() => {
})
.then(() => {
// to make sure the initial device queries are flushed out, we
// attempt to send a message.
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, {
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, {
device_keys: {
'@bob:xyz': {},
'@chris:abc': {},
"@bob:xyz": {},
"@chris:abc": {},
},
},
);
});
aliceTestClient.httpBackend.when('PUT', '/send/').respond(
200, { event_id: '$event1' });
aliceTestClient.httpBackend.when("PUT", "/send/").respond(200, { event_id: "$event1" });
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
() => aliceTestClient.httpBackend.flush('/send/', 1),
),
aliceTestClient.client.sendTextMessage(ROOM_ID, "test"),
aliceTestClient.httpBackend
.flush("/keys/query", 1)
.then(() => aliceTestClient.httpBackend.flush("/send/", 1)),
aliceTestClient.client.crypto!.deviceList.saveIfDirty(),
]);
}).then(() => {
})
.then(() => {
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
expect(data!.syncToken).toEqual(1);
});
// invalidate bob's and chris's device lists in separate syncs
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, {
next_batch: '2',
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: "2",
device_lists: {
changed: ['@bob:xyz'],
changed: ["@bob:xyz"],
},
});
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, {
next_batch: '3',
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: "3",
device_lists: {
changed: ['@chris:abc'],
changed: ["@chris:abc"],
},
});
// flush both syncs
return aliceTestClient.flushSync().then(() => {
return aliceTestClient.flushSync();
});
}).then(() => {
})
.then(() => {
// check that we don't yet have a request for chris's devices.
aliceTestClient.httpBackend.when('POST', '/keys/query', {
aliceTestClient.httpBackend
.when("POST", "/keys/query", {
device_keys: {
'@chris:abc': {},
"@chris:abc": {},
},
token: '3',
}).respond(200, {
device_keys: { '@chris:abc': {} },
token: "3",
})
.respond(200, {
device_keys: { "@chris:abc": {} },
});
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
return aliceTestClient.httpBackend.flush("/keys/query", 1);
})
.then((flushed) => {
expect(flushed).toEqual(0);
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
})
.then(() => {
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
if (bobStat != 1 && bobStat != 2) {
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
bobStat);
throw new Error("Unexpected status for bob: wanted 1 or 2, got " + bobStat);
}
const chrisStat = data!.trackingStatus['@chris:abc'];
const chrisStat = data!.trackingStatus["@chris:abc"];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + chrisStat,
);
throw new Error("Unexpected status for chris: wanted 1 or 2, got " + chrisStat);
}
});
// now add an expectation for a query for bob's devices, and let
// it complete.
aliceTestClient.httpBackend.when('POST', '/keys/query', {
aliceTestClient.httpBackend
.when("POST", "/keys/query", {
device_keys: {
'@bob:xyz': {},
"@bob:xyz": {},
},
token: '2',
}).respond(200, {
device_keys: { '@bob:xyz': {} },
token: "2",
})
.respond(200, {
device_keys: { "@bob:xyz": {} },
});
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
return aliceTestClient.httpBackend.flush("/keys/query", 1);
})
.then((flushed) => {
expect(flushed).toEqual(1);
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
}).then(() => {
return aliceTestClient.client.downloadKeys(["@bob:xyz"]);
})
.then(() => {
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
})
.then(() => {
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
expect(bobStat).toEqual(3);
const chrisStat = data!.trackingStatus['@chris:abc'];
const chrisStat = data!.trackingStatus["@chris:abc"];
if (chrisStat != 1 && chrisStat != 2) {
throw new Error(
'Unexpected status for chris: wanted 1 or 2, got ' + bobStat,
);
throw new Error("Unexpected status for chris: wanted 1 or 2, got " + bobStat);
}
});
// now let the query for chris's devices complete.
return aliceTestClient.httpBackend.flush('/keys/query', 1);
}).then((flushed) => {
return aliceTestClient.httpBackend.flush("/keys/query", 1);
})
.then((flushed) => {
expect(flushed).toEqual(1);
// wait for the client to stop processing the response
return aliceTestClient.client.downloadKeys(['@chris:abc']);
}).then(() => {
return aliceTestClient.client.downloadKeys(["@chris:abc"]);
})
.then(() => {
return aliceTestClient.client.crypto!.deviceList.saveIfDirty();
}).then(() => {
})
.then(() => {
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const chrisStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
const chrisStat = data!.trackingStatus["@bob:xyz"];
expect(bobStat).toEqual(3);
expect(chrisStat).toEqual(3);
@ -279,37 +291,31 @@ describe("DeviceList management:", function() {
beforeEach(async function () {
await aliceTestClient.start();
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse(['@bob:xyz']));
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, getSyncResponse(["@bob:xyz"]));
await aliceTestClient.flushSync();
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
200, {
aliceTestClient.httpBackend.when("POST", "/keys/query").respond(200, {
device_keys: {
'@bob:xyz': {},
"@bob:xyz": {},
},
},
);
await aliceTestClient.httpBackend.flush('/keys/query', 1);
});
await aliceTestClient.httpBackend.flush("/keys/query", 1);
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
// Alice should be tracking bob's device list
expect(bobStat).toBeGreaterThan(
0,
);
expect(bobStat).toBeGreaterThan(0);
});
});
it("when Bob leaves", async function () {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, {
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 2,
device_lists: {
left: ['@bob:xyz'],
left: ["@bob:xyz"],
},
rooms: {
join: {
@ -317,37 +323,33 @@ describe("DeviceList management:", function() {
timeline: {
events: [
testUtils.mkMembership({
mship: 'leave',
sender: '@bob:xyz',
mship: "leave",
sender: "@bob:xyz",
}),
],
},
},
},
},
},
);
});
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(
0,
);
expect(bobStat).toEqual(0);
});
});
it("when Alice leaves", async function () {
aliceTestClient.httpBackend.when('GET', '/sync').respond(
200, {
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
next_batch: 2,
device_lists: {
left: ['@bob:xyz'],
left: ["@bob:xyz"],
},
rooms: {
leave: {
@ -355,23 +357,22 @@ describe("DeviceList management:", function() {
timeline: {
events: [
testUtils.mkMembership({
mship: 'leave',
sender: '@bob:xyz',
mship: "leave",
sender: "@bob:xyz",
}),
],
},
},
},
},
},
);
});
await aliceTestClient.flushSync();
await aliceTestClient.client.crypto!.deviceList.saveIfDirty();
// @ts-ignore accessing a protected field
aliceTestClient.client.cryptoStore!.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(0);
@ -385,14 +386,13 @@ describe("DeviceList management:", function() {
try {
await anotherTestClient.start();
anotherTestClient.httpBackend.when('GET', '/sync').respond(
200, getSyncResponse([]));
anotherTestClient.httpBackend.when("GET", "/sync").respond(200, getSyncResponse([]));
await anotherTestClient.flushSync();
await anotherTestClient.client?.crypto?.deviceList?.saveIfDirty();
// @ts-ignore accessing private property
anotherTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const bobStat = data!.trackingStatus['@bob:xyz'];
const bobStat = data!.trackingStatus["@bob:xyz"];
// Alice should have marked bob's device list as untracked
expect(bobStat).toEqual(0);

View File

@ -26,15 +26,15 @@ limitations under the License.
*/
// load olm before the sdk if possible
import '../olm-loader';
import "../olm-loader";
import type { Session } from "@matrix-org/olm";
import { logger } from '../../src/logger';
import { logger } from "../../src/logger";
import * as testUtils from "../test-utils/test-utils";
import { TestClient } from "../TestClient";
import { CRYPTO_ENABLED, IClaimKeysRequest, IQueryKeysRequest, IUploadKeysRequest } from "../../src/client";
import { ClientEvent, IContent, ISendEventResponse, MatrixClient, MatrixEvent } from "../../src/matrix";
import { DeviceInfo } from '../../src/crypto/deviceinfo';
import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { IDeviceKeys, IOneTimeKey } from "../../src/crypto/dehydration";
let aliTestClient: TestClient;
@ -68,8 +68,7 @@ function expectQueryKeys(querier: TestClient, uploader: TestClient): Promise<num
const uploaderKeys: Record<string, IDeviceKeys> = {};
uploaderKeys[uploader.deviceId!] = uploader.deviceKeys!;
querier.httpBackend.when("POST", "/keys/query")
.respond(200, function(_path, content: IQueryKeysRequest) {
querier.httpBackend.when("POST", "/keys/query").respond(200, function (_path, content: IQueryKeysRequest) {
expect(content.device_keys![uploader.userId!]).toEqual([]);
const result: Record<string, Record<string, IDeviceKeys>> = {};
result[uploader.userId!] = uploaderKeys;
@ -87,12 +86,10 @@ const expectBobQueryKeys = () => expectQueryKeys(bobTestClient, aliTestClient);
*/
async function expectAliClaimKeys(): Promise<void> {
const keys = await bobTestClient.awaitOneTimeKeyUpload();
aliTestClient.httpBackend.when(
"POST", "/keys/claim",
).respond(200, function(_path, content: IClaimKeysRequest) {
aliTestClient.httpBackend.when("POST", "/keys/claim").respond(200, function (_path, content: IClaimKeysRequest) {
const claimType = content.one_time_keys![bobUserId][bobDeviceId];
expect(claimType).toEqual("signed_curve25519");
let keyId = '';
let keyId = "";
for (keyId in keys) {
if (bobTestClient.oneTimeKeys!.hasOwnProperty(keyId)) {
if (keyId.indexOf(claimType + ":") === 0) {
@ -133,8 +130,7 @@ async function aliDownloadsKeys(): Promise<void> {
aliTestClient.client.cryptoStore.getEndToEndDeviceData(null, (data) => {
const devices = data!.devices[bobUserId]!;
expect(devices[bobDeviceId].keys).toEqual(bobTestClient.deviceKeys!.keys);
expect(devices[bobDeviceId].verified).
toBe(DeviceInfo.DeviceVerification.UNVERIFIED);
expect(devices[bobDeviceId].verified).toBe(DeviceInfo.DeviceVerification.UNVERIFIED);
});
}
@ -157,9 +153,7 @@ async function aliSendsFirstMessage(): Promise<OlmPayload> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, ciphertext] = await Promise.all([
sendMessage(aliTestClient.client),
expectAliQueryKeys()
.then(expectAliClaimKeys)
.then(expectAliSendMessageRequest),
expectAliQueryKeys().then(expectAliClaimKeys).then(expectAliSendMessageRequest),
]);
return ciphertext;
}
@ -172,10 +166,7 @@ async function aliSendsFirstMessage(): Promise<OlmPayload> {
*/
async function aliSendsMessage(): Promise<OlmPayload> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, ciphertext] = await Promise.all([
sendMessage(aliTestClient.client),
expectAliSendMessageRequest(),
]);
const [_, ciphertext] = await Promise.all([sendMessage(aliTestClient.client), expectAliSendMessageRequest()]);
return ciphertext;
}
@ -189,8 +180,7 @@ async function bobSendsReplyMessage(): Promise<OlmPayload> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, ciphertext] = await Promise.all([
sendMessage(bobTestClient.client),
expectBobQueryKeys()
.then(expectBobSendMessageRequest),
expectBobQueryKeys().then(expectBobSendMessageRequest),
]);
return ciphertext;
}
@ -226,9 +216,7 @@ async function expectBobSendMessageRequest(): Promise<OlmPayload> {
}
function sendMessage(client: MatrixClient): Promise<ISendEventResponse> {
return client.sendMessage(
roomId, { msgtype: "m.text", body: "Hello, World" },
);
return client.sendMessage(roomId, { msgtype: "m.text", body: "Hello, World" });
}
async function expectSendMessageRequest(httpBackend: TestClient["httpBackend"]): Promise<IContent> {
@ -249,16 +237,12 @@ async function expectSendMessageRequest(httpBackend: TestClient["httpBackend"]):
function aliRecvMessage(): Promise<void> {
const message = bobMessages.shift()!;
return recvMessage(
aliTestClient.httpBackend, aliTestClient.client, bobUserId, message,
);
return recvMessage(aliTestClient.httpBackend, aliTestClient.client, bobUserId, message);
}
function bobRecvMessage(): Promise<void> {
const message = aliMessages.shift()!;
return recvMessage(
bobTestClient.httpBackend, bobTestClient.client, aliUserId, message,
);
return recvMessage(bobTestClient.httpBackend, bobTestClient.client, aliUserId, message);
}
async function recvMessage(
@ -294,8 +278,7 @@ async function recvMessage(
if (event.getType() == "m.room.member") {
return;
}
logger.log(client.credentials.userId + " received event",
event);
logger.log(client.credentials.userId + " received event", event);
client.removeListener(ClientEvent.Event, onEvent);
resolve(event);
@ -382,8 +365,7 @@ describe("MatrixClient crypto", () => {
it("handles failures to upload device keys", async () => {
// since device keys are uploaded asynchronously, there's not really much to do here other than fail the
// upload.
bobTestClient.httpBackend.when("POST", "/keys/upload")
.fail(0, new Error("bleh"));
bobTestClient.httpBackend.when("POST", "/keys/upload").fail(0, new Error("bleh"));
await bobTestClient.httpBackend.flushAllExpected();
});
@ -398,10 +380,7 @@ describe("MatrixClient crypto", () => {
const bobDeviceKeys = bobTestClient.deviceKeys!;
expect(bobDeviceKeys.keys["curve25519:" + bobDeviceId]).toBeTruthy();
bobDeviceKeys.keys["curve25519:" + bobDeviceId] += "abc";
await Promise.all([
aliTestClient.client.downloadKeys([bobUserId]),
expectAliQueryKeys(),
]);
await Promise.all([aliTestClient.client.downloadKeys([bobUserId]), expectAliQueryKeys()]);
const devices = aliTestClient.client.getStoredDevicesForUser(bobUserId);
// should get an empty list
expect(devices).toEqual([]);
@ -411,26 +390,24 @@ describe("MatrixClient crypto", () => {
const eveUserId = "@eve:localhost";
const bobDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'bvcxz',
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "bvcxz",
keys: {
'ed25519:bvcxz': 'pYuWKMCVuaDLRTM/eWuB8OlXEb61gZhfLVJ+Y54tl0Q',
'curve25519:bvcxz': '7Gni0loo/nzF0nFp9847RbhElGewzwUXHPrljjBGPTQ',
"ed25519:bvcxz": "pYuWKMCVuaDLRTM/eWuB8OlXEb61gZhfLVJ+Y54tl0Q",
"curve25519:bvcxz": "7Gni0loo/nzF0nFp9847RbhElGewzwUXHPrljjBGPTQ",
},
user_id: '@eve:localhost',
user_id: "@eve:localhost",
signatures: {
'@eve:localhost': {
'ed25519:bvcxz': 'CliUPZ7dyVPBxvhSA1d+X+LYa5b2AYdjcTwG' +
'0stXcIxjaJNemQqtdgwKDtBFl3pN2I13SEijRDCf1A8bYiQMDg',
"@eve:localhost": {
"ed25519:bvcxz":
"CliUPZ7dyVPBxvhSA1d+X+LYa5b2AYdjcTwG" + "0stXcIxjaJNemQqtdgwKDtBFl3pN2I13SEijRDCf1A8bYiQMDg",
},
},
};
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
bobKeys[bobDeviceId] = bobDeviceKeys;
aliTestClient.httpBackend.when(
"POST", "/keys/query",
).respond(200, { device_keys: { [bobUserId]: bobKeys } });
aliTestClient.httpBackend.when("POST", "/keys/query").respond(200, { device_keys: { [bobUserId]: bobKeys } });
await Promise.all([
aliTestClient.client.downloadKeys([bobUserId, eveUserId]),
@ -447,26 +424,24 @@ describe("MatrixClient crypto", () => {
it("Ali gets keys with an incorrect deviceId", async () => {
const bobDeviceKeys = {
algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'],
device_id: 'bad_device',
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "bad_device",
keys: {
'ed25519:bad_device': 'e8XlY5V8x2yJcwa5xpSzeC/QVOrU+D5qBgyTK0ko+f0',
'curve25519:bad_device': 'YxuuLG/4L5xGeP8XPl5h0d7DzyYVcof7J7do+OXz0xc',
"ed25519:bad_device": "e8XlY5V8x2yJcwa5xpSzeC/QVOrU+D5qBgyTK0ko+f0",
"curve25519:bad_device": "YxuuLG/4L5xGeP8XPl5h0d7DzyYVcof7J7do+OXz0xc",
},
user_id: '@bob:localhost',
user_id: "@bob:localhost",
signatures: {
'@bob:localhost': {
'ed25519:bad_device': 'fEFTq67RaSoIEVBJ8DtmRovbwUBKJ0A' +
'me9m9PDzM9azPUwZ38Xvf6vv1A7W1PSafH4z3Y2ORIyEnZgHaNby3CQ',
"@bob:localhost": {
"ed25519:bad_device":
"fEFTq67RaSoIEVBJ8DtmRovbwUBKJ0A" + "me9m9PDzM9azPUwZ38Xvf6vv1A7W1PSafH4z3Y2ORIyEnZgHaNby3CQ",
},
},
};
const bobKeys: Record<string, typeof bobDeviceKeys> = {};
bobKeys[bobDeviceId] = bobDeviceKeys;
aliTestClient.httpBackend.when(
"POST", "/keys/query",
).respond(200, { device_keys: { [bobUserId]: bobKeys } });
aliTestClient.httpBackend.when("POST", "/keys/query").respond(200, { device_keys: { [bobUserId]: bobKeys } });
await Promise.all([
aliTestClient.client.downloadKeys([bobUserId]),
@ -559,8 +534,7 @@ describe("MatrixClient crypto", () => {
await aliDownloadsKeys();
aliTestClient.client.setDeviceBlocked(bobUserId, bobDeviceId, true);
const p1 = sendMessage(aliTestClient.client);
const p2 = expectSendMessageRequest(aliTestClient.httpBackend)
.then(function(sentContent) {
const p2 = expectSendMessageRequest(aliTestClient.httpBackend).then(function (sentContent) {
// no unblocked devices, so the ciphertext should be empty
expect(sentContent.ciphertext).toEqual({});
});
@ -589,9 +563,7 @@ describe("MatrixClient crypto", () => {
await firstSync(bobTestClient);
await aliEnablesEncryption();
await aliSendsFirstMessage();
bobTestClient.httpBackend.when('POST', '/keys/query').respond(
200, {},
);
bobTestClient.httpBackend.when("POST", "/keys/query").respond(200, {});
await bobRecvMessage();
await bobEnablesEncryption();
const ciphertext = await bobSendsReplyMessage();
@ -606,17 +578,17 @@ describe("MatrixClient crypto", () => {
await aliTestClient.start();
await firstSync(aliTestClient);
const syncData = {
next_batch: '2',
next_batch: "2",
rooms: {
join: {
[roomId]: {
state: {
events: [
testUtils.mkEvent({
type: 'm.room.encryption',
skey: '',
type: "m.room.encryption",
skey: "",
content: {
algorithm: 'm.olm.v1.curve25519-aes-sha2',
algorithm: "m.olm.v1.curve25519-aes-sha2",
},
}),
],
@ -626,9 +598,8 @@ describe("MatrixClient crypto", () => {
},
};
aliTestClient.httpBackend.when('GET', '/sync').respond(
200, syncData);
await aliTestClient.httpBackend.flush('/sync', 1);
aliTestClient.httpBackend.when("GET", "/sync").respond(200, syncData);
await aliTestClient.httpBackend.flush("/sync", 1);
aliTestClient.expectKeyQuery({
device_keys: {
[bobUserId]: {},
@ -651,7 +622,7 @@ describe("MatrixClient crypto", () => {
// enqueue expectations:
// * Sync with empty one_time_keys => upload keys
logger.log(aliTestClient + ': starting');
logger.log(aliTestClient + ": starting");
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
@ -661,13 +632,9 @@ describe("MatrixClient crypto", () => {
// it will upload one-time keys.
httpBackend.when("GET", "/sync").respond(200, syncDataEmpty);
await Promise.all([
aliTestClient.client.startClient({}),
httpBackend.flushAllExpected(),
]);
logger.log(aliTestClient + ': started');
httpBackend.when("POST", "/keys/upload")
.respond(200, (_path, content: IUploadKeysRequest) => {
await Promise.all([aliTestClient.client.startClient({}), httpBackend.flushAllExpected()]);
logger.log(aliTestClient + ": started");
httpBackend.when("POST", "/keys/upload").respond(200, (_path, content: IUploadKeysRequest) => {
expect(content.one_time_keys).toBeTruthy();
expect(content.one_time_keys).not.toEqual({});
expect(Object.keys(content.one_time_keys!).length).toBeGreaterThanOrEqual(1);
@ -687,7 +654,7 @@ describe("MatrixClient crypto", () => {
sender: "@bob:example.com",
room_id: "!someroom",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
session_id: "sessionid",
sender_key: "senderkey",
},
@ -696,7 +663,7 @@ describe("MatrixClient crypto", () => {
sender: "@bob:example.com",
room_id: "!someroom",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
session_id: "sessionid",
sender_key: "senderkey",
},
@ -705,7 +672,7 @@ describe("MatrixClient crypto", () => {
sender: "@bob:example.com",
room_id: "!someroom",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
session_id: "othersessionid",
sender_key: "senderkey",
},

View File

@ -62,7 +62,9 @@ describe("MatrixClient events", function() {
presence: {
events: [
utils.mkPresence({
user: "@foo:bar", name: "Foo Bar", presence: "online",
user: "@foo:bar",
name: "Foo Bar",
presence: "online",
}),
],
},
@ -72,7 +74,9 @@ describe("MatrixClient events", function() {
timeline: {
events: [
utils.mkMessage({
room: "!erufh:bar", user: "@foo:bar", msg: "hmmm",
room: "!erufh:bar",
user: "@foo:bar",
msg: "hmmm",
}),
],
prev_batch: "s",
@ -80,10 +84,13 @@ describe("MatrixClient events", function() {
state: {
events: [
utils.mkMembership({
room: "!erufh:bar", mship: "join", user: "@foo:bar",
room: "!erufh:bar",
mship: "join",
user: "@foo:bar",
}),
utils.mkEvent({
type: "m.room.create", room: "!erufh:bar",
type: "m.room.create",
room: "!erufh:bar",
user: "@foo:bar",
content: {
creator: "@foo:bar",
@ -103,18 +110,23 @@ describe("MatrixClient events", function() {
timeline: {
events: [
utils.mkMessage({
room: "!erufh:bar", user: "@foo:bar",
room: "!erufh:bar",
user: "@foo:bar",
msg: "ello ello",
}),
utils.mkMessage({
room: "!erufh:bar", user: "@foo:bar", msg: ":D",
room: "!erufh:bar",
user: "@foo:bar",
msg: ":D",
}),
],
},
ephemeral: {
events: [
utils.mkEvent({
type: "m.typing", room: "!erufh:bar", content: {
type: "m.typing",
room: "!erufh:bar",
content: {
user_ids: ["@foo:bar"],
},
}),
@ -125,8 +137,7 @@ describe("MatrixClient events", function() {
},
};
it("should emit events from both the first and subsequent /sync calls",
function() {
it("should emit events from both the first and subsequent /sync calls", function () {
httpBackend!.when("GET", "/sync").respond(200, SYNC_DATA);
httpBackend!.when("GET", "/sync").respond(200, NEXT_SYNC_DATA);
@ -177,9 +188,7 @@ describe("MatrixClient events", function() {
}
expect(event.event).toEqual(SYNC_DATA.presence.events[0]);
expect(user.presence).toEqual(
SYNC_DATA.presence.events[0]?.content?.presence,
);
expect(user.presence).toEqual(SYNC_DATA.presence.events[0]?.content?.presence);
});
client!.startClient();
@ -209,10 +218,7 @@ describe("MatrixClient events", function() {
client!.startClient();
return Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 2),
]).then(function() {
return Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 2)]).then(function () {
expect(roomInvokeCount).toEqual(1);
expect(roomNameInvokeCount).toEqual(1);
expect(timelineFireCount).toEqual(3);
@ -223,9 +229,7 @@ describe("MatrixClient events", function() {
httpBackend!.when("GET", "/sync").respond(200, SYNC_DATA);
httpBackend!.when("GET", "/sync").respond(200, NEXT_SYNC_DATA);
const roomStateEventTypes = [
"m.room.member", "m.room.create",
];
const roomStateEventTypes = ["m.room.member", "m.room.create"];
let eventsInvokeCount = 0;
let membersInvokeCount = 0;
let newMemberInvokeCount = 0;
@ -252,10 +256,7 @@ describe("MatrixClient events", function() {
client!.startClient();
return Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 2),
]).then(function() {
return Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 2)]).then(function () {
expect(membersInvokeCount).toEqual(1);
expect(newMemberInvokeCount).toEqual(1);
expect(eventsInvokeCount).toEqual(2);
@ -287,10 +288,7 @@ describe("MatrixClient events", function() {
client!.startClient();
return Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 2),
]).then(function() {
return Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 2)]).then(function () {
expect(typingInvokeCount).toEqual(1);
expect(powerLevelInvokeCount).toEqual(0);
expect(nameInvokeCount).toEqual(0);
@ -299,7 +297,7 @@ describe("MatrixClient events", function() {
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN", function () {
const error = { errcode: 'M_UNKNOWN_TOKEN' };
const error = { errcode: "M_UNKNOWN_TOKEN" };
httpBackend!.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;
@ -316,7 +314,7 @@ describe("MatrixClient events", function() {
});
it("should emit Session.logged_out on M_UNKNOWN_TOKEN (soft logout)", function () {
const error = { errcode: 'M_UNKNOWN_TOKEN', soft_logout: true };
const error = { errcode: "M_UNKNOWN_TOKEN", soft_logout: true };
httpBackend!.when("GET", "/sync").respond(401, error);
let sessionLoggedOutCount = 0;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,14 @@ describe("MatrixClient opts", function() {
presence: {},
rooms: {
join: {
"!foo:bar": { // roomId
"!foo:bar": {
// roomId
timeline: {
events: [
utils.mkMessage({
room: roomId, user: userB, msg: "hello",
room: roomId,
user: userB,
msg: "hello",
}),
],
prev_batch: "f_1_1",
@ -31,19 +34,29 @@ describe("MatrixClient opts", function() {
state: {
events: [
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB,
type: "m.room.name",
room: roomId,
user: userB,
content: {
name: "Old room name",
},
}),
utils.mkMembership({
room: roomId, mship: "join", user: userB, name: "Bob",
room: roomId,
mship: "join",
user: userB,
name: "Bob",
}),
utils.mkMembership({
room: roomId, mship: "join", user: userId, name: "Alice",
room: roomId,
mship: "join",
user: userId,
name: "Alice",
}),
utils.mkEvent({
type: "m.room.create", room: roomId, user: userId,
type: "m.room.create",
room: roomId,
user: userId,
content: {
creator: userId,
},
@ -94,17 +107,17 @@ describe("MatrixClient opts", function() {
});
it("should be able to sync / get new events", async function () {
const expectedEventTypes = [ // from /initialSync
"m.room.message", "m.room.name", "m.room.member", "m.room.member",
const expectedEventTypes = [
// from /initialSync
"m.room.message",
"m.room.name",
"m.room.member",
"m.room.member",
"m.room.create",
];
client.on(ClientEvent.Event, function (event) {
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(
-1,
);
expectedEventTypes.splice(
expectedEventTypes.indexOf(event.getType()), 1,
);
expect(expectedEventTypes.indexOf(event.getType())).not.toEqual(-1);
expectedEventTypes.splice(expectedEventTypes.indexOf(event.getType()), 1);
});
httpBackend.when("GET", "/versions").respond(200, {});
httpBackend.when("GET", "/pushrules").respond(200, {});
@ -114,13 +127,8 @@ describe("MatrixClient opts", function() {
await httpBackend.flush("/versions", 1);
await httpBackend.flush("/pushrules", 1);
await httpBackend.flush("/filter", 1);
await Promise.all([
httpBackend.flush("/sync", 1),
utils.syncPromise(client),
]);
expect(expectedEventTypes.length).toEqual(
0,
);
await Promise.all([httpBackend.flush("/sync", 1), utils.syncPromise(client)]);
expect(expectedEventTypes.length).toEqual(0);
});
});
@ -142,16 +150,22 @@ describe("MatrixClient opts", function() {
});
it("shouldn't retry sending events", function (done) {
httpBackend.when("PUT", "/txn1").respond(500, new MatrixError({
httpBackend.when("PUT", "/txn1").respond(
500,
new MatrixError({
errcode: "M_SOMETHING",
error: "Ruh roh",
}));
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
}),
);
client.sendTextMessage("!foo:bar", "a body", "txn1").then(
function (res) {
expect(false).toBe(true);
}, function(err) {
},
function (err) {
expect(err.errcode).toEqual("M_SOMETHING");
done();
});
},
);
httpBackend.flush("/txn1", 1);
});

View File

@ -29,13 +29,7 @@ describe("MatrixClient relations", () => {
const setupTests = (): [MatrixClient, HttpBackend] => {
const scheduler = new MatrixScheduler();
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{ scheduler },
);
const testClient = new TestClient(userId, "DEVICE", accessToken, undefined, { scheduler });
const httpBackend = testClient.httpBackend;
const client = testClient.client;
@ -52,88 +46,71 @@ describe("MatrixClient relations", () => {
});
it("should read related events with the default options", async () => {
const response = client!.relations(roomId, '$event-0', null, null);
const response = client!.relations(roomId, "$event-0", null, null);
httpBackend!
.when("GET", "/rooms/!room%3Ahere/event/%24event-0")
.respond(200, null);
httpBackend!.when("GET", "/rooms/!room%3Ahere/event/%24event-0").respond(200, null);
httpBackend!
.when("GET", "/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0?dir=b")
.respond(200, { chunk: [], next_batch: 'NEXT' });
.respond(200, { chunk: [], next_batch: "NEXT" });
await httpBackend!.flushAllExpected();
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
expect(await response).toEqual({ events: [], nextBatch: "NEXT", originalEvent: null, prevBatch: null });
});
it("should read related events with relation type", async () => {
const response = client!.relations(roomId, '$event-0', 'm.reference', null);
const response = client!.relations(roomId, "$event-0", "m.reference", null);
httpBackend!
.when("GET", "/rooms/!room%3Ahere/event/%24event-0")
.respond(200, null);
httpBackend!.when("GET", "/rooms/!room%3Ahere/event/%24event-0").respond(200, null);
httpBackend!
.when("GET", "/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0/m.reference?dir=b")
.respond(200, { chunk: [], next_batch: 'NEXT' });
.respond(200, { chunk: [], next_batch: "NEXT" });
await httpBackend!.flushAllExpected();
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
expect(await response).toEqual({ events: [], nextBatch: "NEXT", originalEvent: null, prevBatch: null });
});
it("should read related events with relation type and event type", async () => {
const response = client!.relations(roomId, '$event-0', 'm.reference', 'm.room.message');
const response = client!.relations(roomId, "$event-0", "m.reference", "m.room.message");
httpBackend!.when("GET", "/rooms/!room%3Ahere/event/%24event-0").respond(200, null);
httpBackend!
.when("GET", "/rooms/!room%3Ahere/event/%24event-0")
.respond(200, null);
httpBackend!
.when(
"GET",
"/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0/m.reference/m.room.message?dir=b",
)
.respond(200, { chunk: [], next_batch: 'NEXT' });
.when("GET", "/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0/m.reference/m.room.message?dir=b")
.respond(200, { chunk: [], next_batch: "NEXT" });
await httpBackend!.flushAllExpected();
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
expect(await response).toEqual({ events: [], nextBatch: "NEXT", originalEvent: null, prevBatch: null });
});
it("should read related events with custom options", async () => {
const response = client!.relations(roomId, '$event-0', null, null, {
const response = client!.relations(roomId, "$event-0", null, null, {
dir: Direction.Forward,
from: 'FROM',
from: "FROM",
limit: 10,
to: 'TO',
to: "TO",
});
httpBackend!.when("GET", "/rooms/!room%3Ahere/event/%24event-0").respond(200, null);
httpBackend!
.when("GET", "/rooms/!room%3Ahere/event/%24event-0")
.respond(200, null);
httpBackend!
.when(
"GET",
"/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0?dir=f&from=FROM&limit=10&to=TO",
)
.respond(200, { chunk: [], next_batch: 'NEXT' });
.when("GET", "/_matrix/client/v1/rooms/!room%3Ahere/relations/%24event-0?dir=f&from=FROM&limit=10&to=TO")
.respond(200, { chunk: [], next_batch: "NEXT" });
await httpBackend!.flushAllExpected();
expect(await response).toEqual({ "events": [], "nextBatch": "NEXT", "originalEvent": null, "prevBatch": null });
expect(await response).toEqual({ events: [], nextBatch: "NEXT", originalEvent: null, prevBatch: null });
});
it('should use default direction in the fetchRelations endpoint', async () => {
const response = client!.fetchRelations(roomId, '$event-0', null, null);
it("should use default direction in the fetchRelations endpoint", async () => {
const response = client!.fetchRelations(roomId, "$event-0", null, null);
httpBackend!
.when(
"GET",
"/rooms/!room%3Ahere/relations/%24event-0?dir=b",
)
.respond(200, { chunk: [], next_batch: 'NEXT' });
.when("GET", "/rooms/!room%3Ahere/relations/%24event-0?dir=b")
.respond(200, { chunk: [], next_batch: "NEXT" });
await httpBackend!.flushAllExpected();
expect(await response).toEqual({ "chunk": [], "next_batch": "NEXT" });
expect(await response).toEqual({ chunk: [], next_batch: "NEXT" });
});
});

View File

@ -30,13 +30,7 @@ describe("MatrixClient retrying", function() {
const setupTests = (): [MatrixClient, HttpBackend, Room] => {
const scheduler = new MatrixScheduler();
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{ scheduler },
);
const testClient = new TestClient(userId, "DEVICE", accessToken, undefined, { scheduler });
const httpBackend = testClient.httpBackend;
const client = testClient.client;
const room = new Room(roomId, client, userId);
@ -54,40 +48,37 @@ describe("MatrixClient retrying", function() {
return httpBackend!.stop();
});
xit("should retry according to MatrixScheduler.retryFn", function() {
xit("should retry according to MatrixScheduler.retryFn", function () {});
});
xit("should queue according to MatrixScheduler.queueFn", function () {});
xit("should queue according to MatrixScheduler.queueFn", function() {
xit("should mark events as EventStatus.NOT_SENT when giving up", function () {});
});
xit("should mark events as EventStatus.NOT_SENT when giving up", function() {
});
xit("should mark events as EventStatus.QUEUED when queued", function() {
});
xit("should mark events as EventStatus.QUEUED when queued", function () {});
it("should mark events as EventStatus.CANCELLED when cancelled", function () {
// send a couple of events; the second will be queued
const p1 = client!.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m1",
}).then(function() {
const p1 = client!
.sendMessage(roomId, {
msgtype: "m.text",
body: "m1",
})
.then(
function () {
// we expect the first message to fail
throw new Error('Message 1 unexpectedly sent successfully');
}, () => {
throw new Error("Message 1 unexpectedly sent successfully");
},
() => {
// this is expected
});
},
);
// XXX: it turns out that the promise returned by this message
// never gets resolved.
// https://github.com/matrix-org/matrix-js-sdk/issues/496
client!.sendMessage(roomId, {
"msgtype": "m.text",
"body": "m2",
msgtype: "m.text",
body: "m2",
});
// both events should be in the timeline at this point
@ -100,7 +91,9 @@ describe("MatrixClient retrying", function() {
expect(ev2.status).toEqual(EventStatus.SENDING);
// the first message should get sent, and the second should get queued
httpBackend!.when("PUT", "/send/m.room.message/").check(function() {
httpBackend!
.when("PUT", "/send/m.room.message/")
.check(function () {
// ev2 should now have been queued
expect(ev2.status).toEqual(EventStatus.QUEUED);
@ -113,7 +106,8 @@ describe("MatrixClient retrying", function() {
expect(function () {
client!.cancelPendingEvent(ev1);
}).toThrow();
}).respond(400); // fail the first message
})
.respond(400); // fail the first message
// wait for the localecho of ev1 to be updated
const p3 = new Promise<void>((resolve, reject) => {
@ -132,19 +126,11 @@ describe("MatrixClient retrying", function() {
expect(tl.length).toEqual(0);
});
return Promise.all([
p1,
p3,
httpBackend!.flushAllExpected(),
]);
return Promise.all([p1, p3, httpBackend!.flushAllExpected()]);
});
describe("resending", function () {
xit("should be able to resend a NOT_SENT event", function() {
});
xit("should be able to resend a sent event", function() {
});
xit("should be able to resend a NOT_SENT event", function () {});
xit("should be able to resend a sent event", function () {});
});
});

View File

@ -26,7 +26,8 @@ import {
RoomEvent,
ISyncResponse,
IMinimalEvent,
IRoomEvent, Room,
IRoomEvent,
Room,
} from "../../src";
import { TestClient } from "../TestClient";
@ -40,10 +41,15 @@ describe("MatrixClient room timelines", function() {
let httpBackend: HttpBackend | undefined;
const USER_MEMBERSHIP_EVENT = utils.mkMembership({
room: roomId, mship: "join", user: userId, name: userName,
room: roomId,
mship: "join",
user: userId,
name: userName,
});
const ROOM_NAME_EVENT = utils.mkEvent({
type: "m.room.name", room: roomId, user: otherUserId,
type: "m.room.name",
room: roomId,
user: otherUserId,
content: {
name: "Old room name",
},
@ -53,11 +59,14 @@ describe("MatrixClient room timelines", function() {
next_batch: "s_5_3",
rooms: {
join: {
"!foo:bar": { // roomId
"!foo:bar": {
// roomId
timeline: {
events: [
utils.mkMessage({
room: roomId, user: otherUserId, msg: "hello",
room: roomId,
user: otherUserId,
msg: "hello",
}),
],
prev_batch: "f_1_1",
@ -66,12 +75,16 @@ describe("MatrixClient room timelines", function() {
events: [
ROOM_NAME_EVENT,
utils.mkMembership({
room: roomId, mship: "join",
user: otherUserId, name: "Bob",
room: roomId,
mship: "join",
user: otherUserId,
name: "Bob",
}),
USER_MEMBERSHIP_EVENT,
utils.mkEvent({
type: "m.room.create", room: roomId, user: userId,
type: "m.room.create",
room: roomId,
user: userId,
content: {
creator: userId,
},
@ -116,13 +129,7 @@ describe("MatrixClient room timelines", function() {
const setupTestClient = (): [MatrixClient, HttpBackend] => {
// these tests should work with or without timelineSupport
const testClient = new TestClient(
userId,
"DEVICE",
accessToken,
undefined,
{ timelineSupport: true },
);
const testClient = new TestClient(userId, "DEVICE", accessToken, undefined, { timelineSupport: true });
const httpBackend = testClient.httpBackend;
const client = testClient.client;
@ -153,8 +160,10 @@ describe("MatrixClient room timelines", function() {
});
describe("local echo events", function () {
it("should be added immediately after calling MatrixClient.sendEvent " +
"with EventStatus.SENDING and the right event.sender", function(done) {
it(
"should be added immediately after calling MatrixClient.sendEvent " +
"with EventStatus.SENDING and the right event.sender",
function (done) {
client!.on(ClientEvent.Sync, function (state) {
if (state !== "PREPARED") {
return;
@ -177,17 +186,22 @@ describe("MatrixClient room timelines", function() {
});
});
httpBackend!.flush("/sync", 1);
});
},
);
it("should be updated correctly when the send request finishes " +
"BEFORE the event comes down the event stream", function(done) {
it(
"should be updated correctly when the send request finishes " +
"BEFORE the event comes down the event stream",
function (done) {
const eventId = "$foo:bar";
httpBackend!.when("PUT", "/txn1").respond(200, {
event_id: eventId,
});
const ev = utils.mkMessage({
msg: "I am a fish", user: userId, room: roomId,
msg: "I am a fish",
user: userId,
room: roomId,
});
ev.event_id = eventId;
ev.unsigned = { transaction_id: "txn1" };
@ -198,8 +212,7 @@ describe("MatrixClient room timelines", function() {
return;
}
const room = client!.getRoom(roomId)!;
client!.sendTextMessage(roomId, "I am a fish", "txn1").then(
function() {
client!.sendTextMessage(roomId, "I am a fish", "txn1").then(function () {
expect(room.timeline[1].getId()).toEqual(eventId);
httpBackend!.flush("/sync", 1).then(function () {
expect(room.timeline[1].getId()).toEqual(eventId);
@ -209,17 +222,22 @@ describe("MatrixClient room timelines", function() {
httpBackend!.flush("/txn1", 1);
});
httpBackend!.flush("/sync", 1);
});
},
);
it("should be updated correctly when the send request finishes " +
"AFTER the event comes down the event stream", function(done) {
it(
"should be updated correctly when the send request finishes " +
"AFTER the event comes down the event stream",
function (done) {
const eventId = "$foo:bar";
httpBackend!.when("PUT", "/txn1").respond(200, {
event_id: eventId,
});
const ev = utils.mkMessage({
msg: "I am a fish", user: userId, room: roomId,
msg: "I am a fish",
user: userId,
room: roomId,
});
ev.event_id = eventId;
ev.unsigned = { transaction_id: "txn1" };
@ -242,7 +260,8 @@ describe("MatrixClient room timelines", function() {
});
});
httpBackend!.flush("/sync", 1);
});
},
);
});
describe("paginated events", function () {
@ -260,8 +279,7 @@ describe("MatrixClient room timelines", function() {
});
});
it("should set Room.oldState.paginationToken to null at the start" +
" of the timeline.", function(done) {
it("should set Room.oldState.paginationToken to null at the start" + " of the timeline.", function (done) {
client!.on(ClientEvent.Sync, function (state) {
if (state !== "PREPARED") {
return;
@ -296,14 +314,20 @@ describe("MatrixClient room timelines", function() {
// make an m.room.member event for alice's join
const joinMshipEvent = utils.mkMembership({
mship: "join", user: userId, room: roomId, name: "Old Alice",
mship: "join",
user: userId,
room: roomId,
name: "Old Alice",
url: undefined,
});
// make an m.room.member event with prev_content for alice's nick
// change
const oldMshipEvent = utils.mkMembership({
mship: "join", user: userId, room: roomId, name: userName,
mship: "join",
user: userId,
room: roomId,
name: userName,
url: "mxc://some/url",
});
oldMshipEvent.prev_content = {
@ -316,11 +340,15 @@ describe("MatrixClient room timelines", function() {
// N.B. synapse returns /messages in reverse chronological order
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I'm alice",
user: userId,
room: roomId,
msg: "I'm alice",
}),
oldMshipEvent,
utils.mkMessage({
user: userId, room: roomId, msg: "I'm old alice",
user: userId,
room: roomId,
msg: "I'm old alice",
}),
joinMshipEvent,
];
@ -357,10 +385,14 @@ describe("MatrixClient room timelines", function() {
// set the list of events to return on scrollback
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I am new",
user: userId,
room: roomId,
msg: "I am new",
}),
utils.mkMessage({
user: userId, room: roomId, msg: "I am old",
user: userId,
room: roomId,
msg: "I am old",
}),
];
@ -391,7 +423,9 @@ describe("MatrixClient room timelines", function() {
// set the list of events to return on scrollback
sbEvents = [
utils.mkMessage({
user: userId, room: roomId, msg: "I am new",
user: userId,
room: roomId,
msg: "I am new",
}),
];
@ -425,10 +459,7 @@ describe("MatrixClient room timelines", function() {
];
setNextSyncData(eventData);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(() => {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(() => {
const room = client!.getRoom(roomId)!;
let index = 0;
@ -440,18 +471,11 @@ describe("MatrixClient room timelines", function() {
});
httpBackend!.flush("/messages", 1);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(function () {
expect(index).toEqual(2);
expect(room.timeline.length).toEqual(3);
expect(room.timeline[2].event).toEqual(
eventData[1],
);
expect(room.timeline[1].event).toEqual(
eventData[0],
);
expect(room.timeline[2].event).toEqual(eventData[1]);
expect(room.timeline[1].event).toEqual(eventData[0]);
});
});
});
@ -460,21 +484,18 @@ describe("MatrixClient room timelines", function() {
const eventData = [
utils.mkMessage({ user: userId, room: roomId }),
utils.mkMembership({
user: userId, room: roomId, mship: "join", name: "New Name",
user: userId,
room: roomId,
mship: "join",
name: "New Name",
}),
utils.mkMessage({ user: userId, room: roomId }),
];
setNextSyncData(eventData);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(() => {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(() => {
const room = client!.getRoom(roomId)!;
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(function () {
const preNameEvent = room.timeline[room.timeline.length - 3];
const postNameEvent = room.timeline[room.timeline.length - 1];
expect(preNameEvent.sender?.name).toEqual(userName);
@ -485,41 +506,40 @@ describe("MatrixClient room timelines", function() {
it("should set the right room.name", function () {
const secondRoomNameEvent = utils.mkEvent({
user: userId, room: roomId, type: "m.room.name", content: {
user: userId,
room: roomId,
type: "m.room.name",
content: {
name: "Room 2",
},
});
setNextSyncData([secondRoomNameEvent]);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(() => {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(() => {
const room = client!.getRoom(roomId)!;
let nameEmitCount = 0;
client!.on(RoomEvent.Name, function (rm) {
nameEmitCount += 1;
});
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)])
.then(function () {
expect(nameEmitCount).toEqual(1);
expect(room.name).toEqual("Room 2");
// do another round
const thirdRoomNameEvent = utils.mkEvent({
user: userId, room: roomId, type: "m.room.name", content: {
user: userId,
room: roomId,
type: "m.room.name",
content: {
name: "Room 3",
},
});
setNextSyncData([thirdRoomNameEvent]);
httpBackend!.when("GET", "/sync").respond(200, NEXT_SYNC_DATA);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]);
}).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]);
})
.then(function () {
expect(nameEmitCount).toEqual(2);
expect(room.name).toEqual("Room 3");
});
@ -531,32 +551,28 @@ describe("MatrixClient room timelines", function() {
const userD = "@dee:bar";
const eventData = [
utils.mkMembership({
user: userC, room: roomId, mship: "join", name: "C",
user: userC,
room: roomId,
mship: "join",
name: "C",
}),
utils.mkMembership({
user: userC, room: roomId, mship: "invite", skey: userD,
user: userC,
room: roomId,
mship: "invite",
skey: userD,
}),
];
setNextSyncData(eventData);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(() => {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(() => {
const room = client!.getRoom(roomId)!;
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(function () {
expect(room.currentState.getMembers().length).toEqual(4);
expect(room.currentState.getMember(userC)!.name).toEqual("C");
expect(room.currentState.getMember(userC)!.membership).toEqual(
"join",
);
expect(room.currentState.getMember(userC)!.membership).toEqual("join");
expect(room.currentState.getMember(userD)!.name).toEqual(userD);
expect(room.currentState.getMember(userD)!.membership).toEqual(
"invite",
);
expect(room.currentState.getMember(userD)!.membership).toEqual("invite");
});
});
});
@ -564,9 +580,7 @@ describe("MatrixClient room timelines", function() {
describe("gappy sync", function () {
it("should copy the last known state to the new timeline", function () {
const eventData = [
utils.mkMessage({ user: userId, room: roomId }),
];
const eventData = [utils.mkMessage({ user: userId, room: roomId })];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
@ -578,36 +592,24 @@ describe("MatrixClient room timelines", function() {
const room = client!.getRoom(roomId)!;
httpBackend!.flush("/messages", 1);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(function () {
expect(room.timeline.length).toEqual(1);
expect(room.timeline[0].event).toEqual(eventData[0]);
expect(room.currentState.getMembers().length).toEqual(2);
expect(room.currentState.getMember(userId)!.name).toEqual(userName);
expect(room.currentState.getMember(userId)!.membership).toEqual(
"join",
);
expect(room.currentState.getMember(userId)!.membership).toEqual("join");
expect(room.currentState.getMember(otherUserId)!.name).toEqual("Bob");
expect(room.currentState.getMember(otherUserId)!.membership).toEqual(
"join",
);
expect(room.currentState.getMember(otherUserId)!.membership).toEqual("join");
});
});
});
it("should emit a `RoomEvent.TimelineReset` event when the sync response is `limited`", function () {
const eventData = [
utils.mkMessage({ user: userId, room: roomId }),
];
const eventData = [utils.mkMessage({ user: userId, room: roomId })];
setNextSyncData(eventData);
NEXT_SYNC_DATA.rooms!.join[roomId].timeline.limited = true;
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(() => {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(() => {
const room = client!.getRoom(roomId)!;
let emitCount = 0;
@ -617,33 +619,29 @@ describe("MatrixClient room timelines", function() {
});
httpBackend!.flush("/messages", 1);
return Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!),
]).then(function() {
return Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!)]).then(function () {
expect(emitCount).toEqual(1);
});
});
});
});
describe('Refresh live timeline', () => {
describe("Refresh live timeline", () => {
const initialSyncEventData = [
utils.mkMessage({ user: userId, room: roomId }),
utils.mkMessage({ user: userId, room: roomId }),
utils.mkMessage({ user: userId, room: roomId }),
];
const contextUrl = `/rooms/${encodeURIComponent(roomId)}/context/` +
const contextUrl =
`/rooms/${encodeURIComponent(roomId)}/context/` +
`${encodeURIComponent(initialSyncEventData[2].event_id!)}`;
const contextResponse = {
start: "start_token",
events_before: [initialSyncEventData[1], initialSyncEventData[0]],
event: initialSyncEventData[2],
events_after: [],
state: [
USER_MEMBERSHIP_EVENT,
],
state: [USER_MEMBERSHIP_EVENT],
end: "end_token",
};
@ -652,21 +650,17 @@ describe("MatrixClient room timelines", function() {
setNextSyncData(initialSyncEventData);
// Create a room from the sync
await Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 1),
]);
await Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 1)]);
// Get the room after the first sync so the room is created
room = client!.getRoom(roomId)!;
expect(room).toBeTruthy();
});
it('should clear and refresh messages in timeline', async () => {
it("should clear and refresh messages in timeline", async () => {
// `/context` request for `refreshLiveTimeline()` -> `getEventTimeline()`
// to construct a new timeline from.
httpBackend!.when("GET", contextUrl)
.respond(200, function() {
httpBackend!.when("GET", contextUrl).respond(200, function () {
// The timeline should be cleared at this point in the refresh
expect(room.timeline.length).toEqual(0);
@ -674,10 +668,7 @@ describe("MatrixClient room timelines", function() {
});
// Refresh the timeline.
await Promise.all([
room.refreshLiveTimeline(),
httpBackend!.flushAllExpected(),
]);
await Promise.all([room.refreshLiveTimeline(), httpBackend!.flushAllExpected()]);
// Make sure the message are visible
const resultantEventsInTimeline = room.getUnfilteredTimelineSet().getLiveTimeline().getEvents();
@ -689,7 +680,7 @@ describe("MatrixClient room timelines", function() {
]);
});
it('Perfectly merges timelines if a sync finishes while refreshing the timeline', async () => {
it("Perfectly merges timelines if a sync finishes while refreshing the timeline", async () => {
// `/context` request for `refreshLiveTimeline()` ->
// `getEventTimeline()` to construct a new timeline from.
//
@ -698,8 +689,7 @@ describe("MatrixClient room timelines", function() {
// middle of all of this refresh timeline logic. We want to make
// sure the sync pagination still works as expected after messing
// the refresh timline logic messes with the pagination tokens.
httpBackend!.when("GET", contextUrl)
.respond(200, () => {
httpBackend!.when("GET", contextUrl).respond(200, () => {
// Now finally return and make the `/context` request respond
return contextResponse;
});
@ -714,22 +704,20 @@ describe("MatrixClient room timelines", function() {
//
// We define this here so the event listener is in place before we
// call `room.refreshLiveTimeline()`.
const racingSyncEventData = [
utils.mkMessage({ user: userId, room: roomId }),
];
const racingSyncEventData = [utils.mkMessage({ user: userId, room: roomId })];
const waitForRaceySyncAfterResetPromise = new Promise<void>((resolve, reject) => {
let eventFired = false;
// Throw a more descriptive error if this part of the test times out.
const failTimeout = setTimeout(() => {
if (eventFired) {
reject(new Error(
'TestError: `RoomEvent.TimelineReset` fired but we timed out trying to make' +
'a `/sync` happen in time.',
));
reject(
new Error(
"TestError: `RoomEvent.TimelineReset` fired but we timed out trying to make" +
"a `/sync` happen in time.",
),
);
} else {
reject(new Error(
'TestError: Timed out while waiting for `RoomEvent.TimelineReset` to fire.',
));
reject(new Error("TestError: Timed out while waiting for `RoomEvent.TimelineReset` to fire."));
}
}, 4000 /* FIXME: Is there a way to reference the current timeout of this test in Jest? */);
@ -746,20 +734,16 @@ describe("MatrixClient room timelines", function() {
httpBackend!.when("GET", "/sync").respond(200, function () {
return NEXT_SYNC_DATA;
});
await Promise.all([
httpBackend!.flush("/sync", 1),
utils.syncPromise(client!, 1),
]);
await Promise.all([httpBackend!.flush("/sync", 1), utils.syncPromise(client!, 1)]);
// Make sure the timeline has the racey sync data
const afterRaceySyncTimelineEvents = room
.getUnfilteredTimelineSet()
.getLiveTimeline()
.getEvents();
const afterRaceySyncTimelineEventIds = afterRaceySyncTimelineEvents
.map((event) => event.getId());
expect(afterRaceySyncTimelineEventIds).toEqual([
racingSyncEventData[0].event_id,
]);
const afterRaceySyncTimelineEventIds = afterRaceySyncTimelineEvents.map((event) =>
event.getId(),
);
expect(afterRaceySyncTimelineEventIds).toEqual([racingSyncEventData[0].event_id]);
clearTimeout(failTimeout);
resolve();
@ -783,17 +767,12 @@ describe("MatrixClient room timelines", function() {
// Make sure sync pagination still works by seeing a new message show up
// after refreshing the timeline.
const afterRefreshEventData = [
utils.mkMessage({ user: userId, room: roomId }),
];
const afterRefreshEventData = [utils.mkMessage({ user: userId, room: roomId })];
setNextSyncData(afterRefreshEventData);
httpBackend!.when("GET", "/sync").respond(200, function () {
return NEXT_SYNC_DATA;
});
await Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 1),
]);
await Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 1)]);
// Make sure the timeline includes the the events from the `/sync`
// that raced and beat us in the middle of everything and the
@ -808,17 +787,24 @@ describe("MatrixClient room timelines", function() {
]);
});
it('Timeline recovers after `/context` request to generate new timeline fails', async () => {
it("Timeline recovers after `/context` request to generate new timeline fails", async () => {
// `/context` request for `refreshLiveTimeline()` -> `getEventTimeline()`
// to construct a new timeline from.
httpBackend!.when("GET", contextUrl).check(() => {
httpBackend!
.when("GET", contextUrl)
.check(() => {
// The timeline should be cleared at this point in the refresh
expect(room.timeline.length).toEqual(0);
}).respond(500, new MatrixError({
errcode: 'TEST_FAKE_ERROR',
error: 'We purposely intercepted this /context request to make it fail ' +
'in order to test whether the refresh timeline code is resilient',
}));
})
.respond(
500,
new MatrixError({
errcode: "TEST_FAKE_ERROR",
error:
"We purposely intercepted this /context request to make it fail " +
"in order to test whether the refresh timeline code is resilient",
}),
);
// Refresh the timeline and expect it to fail
const settledFailedRefreshPromises = await Promise.allSettled([
@ -827,9 +813,9 @@ describe("MatrixClient room timelines", function() {
]);
// We only expect `TEST_FAKE_ERROR` here. Anything else is
// unexpected and should fail the test.
if (settledFailedRefreshPromises[0].status === 'fulfilled') {
throw new Error('Expected the /context request to fail with a 500');
} else if (settledFailedRefreshPromises[0].reason.errcode !== 'TEST_FAKE_ERROR') {
if (settledFailedRefreshPromises[0].status === "fulfilled") {
throw new Error("Expected the /context request to fail with a 500");
} else if (settledFailedRefreshPromises[0].reason.errcode !== "TEST_FAKE_ERROR") {
throw settledFailedRefreshPromises[0].reason;
}
@ -839,20 +825,20 @@ describe("MatrixClient room timelines", function() {
// `/messages` request for `refreshLiveTimeline()` ->
// `getLatestTimeline()` to construct a new timeline from.
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomId)}/messages`)
.respond(200, function() {
httpBackend!.when("GET", `/rooms/${encodeURIComponent(roomId)}/messages`).respond(200, function () {
return {
chunk: [{
chunk: [
{
// The latest message in the room
event_id: initialSyncEventData[2].event_id,
}],
},
],
};
});
// `/context` request for `refreshLiveTimeline()` ->
// `getLatestTimeline()` -> `getEventTimeline()` to construct a new
// timeline from.
httpBackend!.when("GET", contextUrl)
.respond(200, function() {
httpBackend!.when("GET", contextUrl).respond(200, function () {
// The timeline should be cleared at this point in the refresh
expect(room.timeline.length).toEqual(0);
@ -860,24 +846,16 @@ describe("MatrixClient room timelines", function() {
});
// Refresh the timeline again but this time it should pass
await Promise.all([
room.refreshLiveTimeline(),
httpBackend!.flushAllExpected(),
]);
await Promise.all([room.refreshLiveTimeline(), httpBackend!.flushAllExpected()]);
// Make sure sync pagination still works by seeing a new message show up
// after refreshing the timeline.
const afterRefreshEventData = [
utils.mkMessage({ user: userId, room: roomId }),
];
const afterRefreshEventData = [utils.mkMessage({ user: userId, room: roomId })];
setNextSyncData(afterRefreshEventData);
httpBackend!.when("GET", "/sync").respond(200, function () {
return NEXT_SYNC_DATA;
});
await Promise.all([
httpBackend!.flushAllExpected(),
utils.syncPromise(client!, 1),
]);
await Promise.all([httpBackend!.flushAllExpected(), utils.syncPromise(client!, 1)]);
// Make sure the message are visible
const resultantEventsInTimeline = room.getUnfilteredTimelineSet().getLiveTimeline().getEvents();

File diff suppressed because it is too large Load Diff

View File

@ -23,22 +23,23 @@ import { TestClient } from "../TestClient";
import { IEvent } from "../../src";
import { MatrixEvent, MatrixEventEvent } from "../../src/models/event";
const ROOM_ID = '!ROOM:ID';
const ROOM_ID = "!ROOM:ID";
const SESSION_ID = 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc';
const SESSION_ID = "o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc";
const ENCRYPTED_EVENT: Partial<IEvent> = {
type: 'm.room.encrypted',
type: "m.room.encrypted",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: 'SENDER_CURVE25519',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: SESSION_ID,
ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N'
+ 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl'
+ 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs',
ciphertext:
"AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N" +
"CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl" +
"mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs",
},
room_id: '!ROOM:ID',
event_id: '$event1',
room_id: "!ROOM:ID",
event_id: "$event1",
origin_server_ts: 1507753886000,
};
@ -47,19 +48,20 @@ const CURVE25519_KEY_BACKUP_DATA: IKeyBackupSession = {
forwarded_count: 0,
is_verified: false,
session_data: {
ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw'
+ '6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ'
+ 'Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9'
+ 'SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy'
+ 'Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF'
+ 'ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV'
+ '4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv'
+ 'C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe'
+ 'Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf'
+ 'QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy'
+ 'iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg',
mac: '5lxYBHQU80M',
ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14',
ciphertext:
"2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw" +
"6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ" +
"Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9" +
"SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy" +
"Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF" +
"ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV" +
"4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv" +
"C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe" +
"Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf" +
"QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy" +
"iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg",
mac: "5lxYBHQU80M",
ephemeral: "/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14",
},
};
@ -82,16 +84,14 @@ function createOlmSession(olmAccount: Olm.Account, recipientTestClient: TestClie
const otk = keys[otkId];
const session = new global.Olm.Session();
session.create_outbound(
olmAccount, recipientTestClient.getDeviceKey(), otk.key,
);
session.create_outbound(olmAccount, recipientTestClient.getDeviceKey(), otk.key);
return session;
});
}
describe("megolm key backups", function () {
if (!global.Olm) {
logger.warn('not running megolm tests: Olm not present');
logger.warn("not running megolm tests: Olm not present");
return;
}
const Olm = global.Olm;
@ -99,9 +99,7 @@ describe("megolm key backups", function() {
let aliceTestClient: TestClient;
const setupTestClient = (): [Account, TestClient] => {
const aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs",
);
const aliceTestClient = new TestClient("@alice:localhost", "xzcvb", "akjgkrgjs");
const testOlmAccount = new Olm.Account();
testOlmAccount!.create();
@ -136,21 +134,21 @@ describe("megolm key backups", function() {
},
};
return aliceTestClient!.start().then(() => {
return aliceTestClient!
.start()
.then(() => {
return createOlmSession(testOlmAccount, aliceTestClient);
}).then(() => {
})
.then(() => {
const privkey = decodeRecoveryKey(RECOVERY_KEY);
return aliceTestClient!.client!.crypto!.storeSessionBackupPrivateKey(privkey);
}).then(() => {
})
.then(() => {
aliceTestClient!.httpBackend.when("GET", "/sync").respond(200, syncResponse);
aliceTestClient!.expectKeyBackupQuery(
ROOM_ID,
SESSION_ID,
200,
CURVE25519_KEY_BACKUP_DATA,
);
aliceTestClient!.expectKeyBackupQuery(ROOM_ID, SESSION_ID, 200, CURVE25519_KEY_BACKUP_DATA);
return aliceTestClient!.httpBackend.flushAllExpected();
}).then(function(): Promise<MatrixEvent> {
})
.then(function (): Promise<MatrixEvent> {
const room = aliceTestClient!.client.getRoom(ROOM_ID)!;
const event = room.getLiveTimeline().getEvents()[0];
@ -164,8 +162,9 @@ describe("megolm key backups", function() {
resolve(ev);
});
});
}).then((event) => {
expect(event.getContent()).toEqual('testytest');
})
.then((event) => {
expect(event.getContent()).toEqual("testytest");
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,20 @@ import { SlidingSync, SlidingSyncEvent, MSC3575RoomData, SlidingSyncState, Exten
import { TestClient } from "../TestClient";
import { IRoomEvent, IStateEvent } from "../../src/sync-accumulator";
import {
MatrixClient, MatrixEvent, NotificationCountType, JoinRule, MatrixError,
EventType, IPushRules, PushRuleKind, TweakName, ClientEvent, RoomMemberEvent, RoomEvent, Room, IRoomTimelineData,
MatrixClient,
MatrixEvent,
NotificationCountType,
JoinRule,
MatrixError,
EventType,
IPushRules,
PushRuleKind,
TweakName,
ClientEvent,
RoomMemberEvent,
RoomEvent,
Room,
IRoomTimelineData,
} from "../../src";
import { SlidingSyncSdk } from "../../src/sliding-sync-sdk";
import { SyncState } from "../../src/sync";
@ -67,7 +79,7 @@ describe("SlidingSyncSdk", () => {
event_id: "$" + eventIdCounter,
};
};
const mkOwnStateEvent = (evType: string, content: object, stateKey = ''): IStateEvent => {
const mkOwnStateEvent = (evType: string, content: object, stateKey = ""): IStateEvent => {
eventIdCounter++;
return {
type: evType,
@ -195,7 +207,6 @@ describe("SlidingSyncSdk", () => {
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello B" }),
mkOwnEvent(EventType.RoomMessage, { body: "world B" }),
],
initial: true,
},
@ -294,7 +305,9 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomA, data[roomA]);
const gotRoom = client!.getRoom(roomA);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.name).toEqual(data[roomA].name);
expect(gotRoom.getMyMembership()).toEqual("join");
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-2), data[roomA].timeline);
@ -304,7 +317,9 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, data[roomB]);
const gotRoom = client!.getRoom(roomB);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.name).toEqual(data[roomB].name);
expect(gotRoom.getMyMembership()).toEqual("join");
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-5), data[roomB].timeline);
@ -314,27 +329,33 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomC, data[roomC]);
const gotRoom = client!.getRoom(roomC);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
expect(
gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight),
).toEqual(data[roomC].highlight_count);
if (gotRoom == null) {
return;
}
expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual(
data[roomC].highlight_count,
);
});
it("can be created with a notification_count", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomD, data[roomD]);
const gotRoom = client!.getRoom(roomD);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
expect(
gotRoom.getUnreadNotificationCount(NotificationCountType.Total),
).toEqual(data[roomD].notification_count);
if (gotRoom == null) {
return;
}
expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Total)).toEqual(
data[roomD].notification_count,
);
});
it("can be created with an invited/joined_count", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomG, data[roomG]);
const gotRoom = client!.getRoom(roomG);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.getInvitedMemberCount()).toEqual(data[roomG].invited_count);
expect(gotRoom.getJoinedMemberCount()).toEqual(data[roomG].joined_count);
});
@ -358,7 +379,9 @@ describe("SlidingSyncSdk", () => {
client!.off(RoomEvent.Timeline, listener);
const gotRoom = client!.getRoom(roomH);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.name).toEqual(data[roomH].name);
expect(gotRoom.getMyMembership()).toEqual("join");
// check the entire timeline is correct
@ -370,7 +393,9 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomE, data[roomE]);
const gotRoom = client!.getRoom(roomE);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.getMyMembership()).toEqual("invite");
expect(gotRoom.currentState.getJoinRule()).toEqual(JoinRule.Invite);
});
@ -379,10 +404,10 @@ describe("SlidingSyncSdk", () => {
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomF, data[roomF]);
const gotRoom = client!.getRoom(roomF);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
expect(
gotRoom.name,
).toEqual(data[roomF].name);
if (gotRoom == null) {
return;
}
expect(gotRoom.name).toEqual(data[roomF].name);
});
describe("updating", () => {
@ -395,7 +420,9 @@ describe("SlidingSyncSdk", () => {
});
const gotRoom = client!.getRoom(roomA);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
const newTimeline = data[roomA].timeline;
newTimeline.push(newEvent);
assertTimelineEvents(gotRoom.getLiveTimeline().getEvents().slice(-3), newTimeline);
@ -404,18 +431,20 @@ describe("SlidingSyncSdk", () => {
it("can update with a new required_state event", async () => {
let gotRoom = client!.getRoom(roomB);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.getJoinRule()).toEqual(JoinRule.Invite); // default
mockSlidingSync!.emit(SlidingSyncEvent.RoomData, roomB, {
required_state: [
mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, ""),
],
required_state: [mkOwnStateEvent("m.room.join_rules", { join_rule: "restricted" }, "")],
timeline: [],
name: data[roomB].name,
});
gotRoom = client!.getRoom(roomB);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.getJoinRule()).toEqual(JoinRule.Restricted);
});
@ -428,10 +457,10 @@ describe("SlidingSyncSdk", () => {
});
const gotRoom = client!.getRoom(roomC);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
expect(
gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight),
).toEqual(1);
if (gotRoom == null) {
return;
}
expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Highlight)).toEqual(1);
});
it("can update with a new notification_count", async () => {
@ -443,10 +472,10 @@ describe("SlidingSyncSdk", () => {
});
const gotRoom = client!.getRoom(roomD);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
expect(
gotRoom.getUnreadNotificationCount(NotificationCountType.Total),
).toEqual(1);
if (gotRoom == null) {
return;
}
expect(gotRoom.getUnreadNotificationCount(NotificationCountType.Total)).toEqual(1);
});
it("can update with a new joined_count", () => {
@ -458,7 +487,9 @@ describe("SlidingSyncSdk", () => {
});
const gotRoom = client!.getRoom(roomG);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
expect(gotRoom.getJoinedMemberCount()).toEqual(1);
});
@ -482,11 +513,20 @@ describe("SlidingSyncSdk", () => {
});
const gotRoom = client!.getRoom(roomA);
expect(gotRoom).toBeDefined();
if (gotRoom == null) { return; }
if (gotRoom == null) {
return;
}
logger.log("want:", oldTimeline.map((e) => (e.type + " : " + (e.content || {}).body)));
logger.log("got:", gotRoom.getLiveTimeline().getEvents().map(
(e) => (e.getType() + " : " + e.getContent().body)),
logger.log(
"want:",
oldTimeline.map((e) => e.type + " : " + (e.content || {}).body),
);
logger.log(
"got:",
gotRoom
.getLiveTimeline()
.getEvents()
.map((e) => e.getType() + " : " + e.getContent().body),
);
// we expect the timeline now to be oldTimeline (so the old events are in fact old)
@ -506,40 +546,54 @@ describe("SlidingSyncSdk", () => {
const FAILED_SYNC_ERROR_THRESHOLD = 3; // would be nice to export the const in the actual class...
it("emits SyncState.Reconnecting when < FAILED_SYNC_ERROR_THRESHOLD & SyncState.Error when over", async () => {
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete,
{ pos: "h", lists: [], rooms: {}, extensions: {} },
);
mockSlidingSync!.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, {
pos: "h",
lists: [],
rooms: {},
extensions: {},
});
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle, SlidingSyncState.RequestFinished, null, new Error("generic"),
SlidingSyncEvent.Lifecycle,
SlidingSyncState.RequestFinished,
null,
new Error("generic"),
);
expect(sdk!.getSyncState()).toEqual(SyncState.Reconnecting);
for (let i = 0; i < FAILED_SYNC_ERROR_THRESHOLD; i++) {
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle, SlidingSyncState.RequestFinished, null, new Error("generic"),
SlidingSyncEvent.Lifecycle,
SlidingSyncState.RequestFinished,
null,
new Error("generic"),
);
}
expect(sdk!.getSyncState()).toEqual(SyncState.Error);
});
it("emits SyncState.Syncing after a previous SyncState.Error", async () => {
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle,
SlidingSyncState.Complete,
{ pos: "i", lists: [], rooms: {}, extensions: {} },
);
mockSlidingSync!.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.Complete, {
pos: "i",
lists: [],
rooms: {},
extensions: {},
});
expect(sdk!.getSyncState()).toEqual(SyncState.Syncing);
});
it("emits SyncState.Error immediately when receiving M_UNKNOWN_TOKEN and stops syncing", async () => {
expect(mockSlidingSync!.stop).not.toBeCalled();
mockSlidingSync!.emit(SlidingSyncEvent.Lifecycle, SlidingSyncState.RequestFinished, null, new MatrixError({
mockSlidingSync!.emit(
SlidingSyncEvent.Lifecycle,
SlidingSyncState.RequestFinished,
null,
new MatrixError({
errcode: "M_UNKNOWN_TOKEN",
message: "Oh no your access token is no longer valid",
}));
}),
);
expect(sdk!.getSyncState()).toEqual(SyncState.Error);
expect(mockSlidingSync!.stop).toBeCalled();
});
@ -694,7 +748,6 @@ describe("SlidingSyncSdk", () => {
mkOwnStateEvent(EventType.RoomMember, { membership: "join" }, selfUserId),
mkOwnStateEvent(EventType.RoomPowerLevels, { users: { [selfUserId]: 100 } }, ""),
mkOwnEvent(EventType.RoomMessage, { body: "hello" }),
],
initial: true,
});
@ -743,7 +796,8 @@ describe("SlidingSyncSdk", () => {
const roomId = "!foo:bar";
const pushRulesContent: IPushRules = {
global: {
[PushRuleKind.RoomSpecific]: [{
[PushRuleKind.RoomSpecific]: [
{
enabled: true,
default: true,
pattern: "monkey",
@ -754,7 +808,8 @@ describe("SlidingSyncSdk", () => {
},
],
rule_id: roomId,
}],
},
],
},
};
let pushRule = client!.getRoomPushRule("global", roomId);
@ -973,7 +1028,11 @@ describe("SlidingSyncSdk", () => {
let ext: Extension<any, any>;
const generateReceiptResponse = (
userId: string, roomId: string, eventId: string, recType: string, ts: number,
userId: string,
roomId: string,
eventId: string,
recType: string,
ts: number,
) => {
return {
rooms: {
@ -1034,9 +1093,7 @@ describe("SlidingSyncSdk", () => {
const room = client!.getRoom(roomId)!;
expect(room).toBeDefined();
expect(room.getReadReceiptForUserId(alice, true)).toBeNull();
ext.onResponse(
generateReceiptResponse(alice, roomId, lastEvent.event_id, "m.read", 1234567),
);
ext.onResponse(generateReceiptResponse(alice, roomId, lastEvent.event_id, "m.read", 1234567));
const receipt = room.getReadReceiptForUserId(alice);
expect(receipt).toBeDefined();
expect(receipt?.eventId).toEqual(lastEvent.event_id);
@ -1048,9 +1105,7 @@ describe("SlidingSyncSdk", () => {
const roomId = "!room:id";
const alice = "@alice:alice";
const eventId = "$something";
ext.onResponse(
generateReceiptResponse(alice, roomId, eventId, "m.read", 1234567),
);
ext.onResponse(generateReceiptResponse(alice, roomId, eventId, "m.read", 1234567));
// we expect it not to crash
});
});

File diff suppressed because it is too large Load Diff

View File

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { logger } from '../src/logger';
import { logger } from "../src/logger";
// try to load the olm library.
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
global.Olm = require('@matrix-org/olm');
logger.log('loaded libolm');
global.Olm = require("@matrix-org/olm");
logger.log("loaded libolm");
} catch (e) {
logger.warn("unable to run crypto tests: libolm not available");
}

View File

@ -49,7 +49,7 @@ class JestSlowTestReporter {
for (let i = 0; i < slowestTests.length; i++) {
const duration = slowestTests[i].duration;
const filePath = slowestTests[i].filePath.replace(rootPathRegex, '.');
const filePath = slowestTests[i].filePath.replace(rootPathRegex, ".");
if (isTestSuite) {
console.log(` ${duration / 1000} seconds ${filePath}`);

View File

@ -17,10 +17,7 @@ limitations under the License.
import { MatrixEvent } from "../../src";
import { M_BEACON, M_BEACON_INFO } from "../../src/@types/beacon";
import { LocationAssetType } from "../../src/@types/location";
import {
makeBeaconContent,
makeBeaconInfoContent,
} from "../../src/content-helpers";
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
type InfoContentProps = {
timeout: number;
@ -44,13 +41,7 @@ export const makeBeaconInfoEvent = (
contentProps: Partial<InfoContentProps> = {},
eventId?: string,
): MatrixEvent => {
const {
timeout,
isLive,
description,
assetType,
timestamp,
} = {
const { timeout, isLive, description, assetType, timestamp } = {
...DEFAULT_INFO_CONTENT_PROPS,
...contentProps,
};
@ -77,9 +68,9 @@ type ContentProps = {
description?: string;
};
const DEFAULT_CONTENT_PROPS: ContentProps = {
uri: 'geo:-36.24484561954707,175.46884959563613;u=10',
uri: "geo:-36.24484561954707,175.46884959563613;u=10",
timestamp: 123,
beaconInfoId: '$123',
beaconInfoId: "$123",
};
/**
@ -87,10 +78,7 @@ const DEFAULT_CONTENT_PROPS: ContentProps = {
* all required properties are mocked
* override with contentProps
*/
export const makeBeaconEvent = (
sender: string,
contentProps: Partial<ContentProps> = {},
): MatrixEvent => {
export const makeBeaconEvent = (sender: string, contentProps: Partial<ContentProps> = {}): MatrixEvent => {
const { uri, timestamp, beaconInfoId, description } = {
...DEFAULT_CONTENT_PROPS,
...contentProps,
@ -107,10 +95,13 @@ export const makeBeaconEvent = (
* Create a mock geolocation position
* defaults all required properties
*/
export const makeGeolocationPosition = (
{ timestamp, coords }:
{ timestamp?: number, coords: Partial<GeolocationCoordinates> },
): GeolocationPosition => ({
export const makeGeolocationPosition = ({
timestamp,
coords,
}: {
timestamp?: number;
coords: Partial<GeolocationCoordinates>;
}): GeolocationPosition => ({
timestamp: timestamp ?? 1647256791840,
coords: {
accuracy: 1,

View File

@ -58,11 +58,11 @@ export const getMockClientWithEventEmitter = (
});
* ```
*/
export const mockClientMethodsUser = (userId = '@alice:domain') => ({
export const mockClientMethodsUser = (userId = "@alice:domain") => ({
getUserId: jest.fn().mockReturnValue(userId),
getUser: jest.fn().mockReturnValue(new User(userId)),
isGuest: jest.fn().mockReturnValue(false),
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
credentials: { userId },
getThreePids: jest.fn().mockResolvedValue({ threepids: [] }),
getAccessToken: jest.fn(),
@ -91,4 +91,3 @@ export const mockClientMethodsServer = (): Partial<Record<MethodLikeKeys<MatrixC
getCapabilities: jest.fn().mockReturnValue({}),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
});

View File

@ -22,7 +22,7 @@ limitations under the License.
// and avoids assuming anything about the app's behaviour.
const realSetTimeout = setTimeout;
export function flushPromises() {
return new Promise(r => {
return new Promise((r) => {
realSetTimeout(r, 1);
});
}

View File

@ -2,9 +2,9 @@
import EventEmitter from "events";
// load olm before the sdk if possible
import '../olm-loader';
import "../olm-loader";
import { logger } from '../../src/logger';
import { logger } from "../../src/logger";
import { IContent, IEvent, IEventRelation, IUnsigned, MatrixEvent, MatrixEventEvent } from "../../src/models/event";
import { ClientEvent, EventType, IPusher, MatrixClient, MsgType } from "../../src";
import { SyncState } from "../../src/sync";
@ -54,7 +54,8 @@ export function mock<T>(constr: { new(...args: any[]): T }, name: string): T {
result.toString = function () {
return "mock" + (name ? " of " + name : "");
};
for (const key of Object.getOwnPropertyNames(constr.prototype)) { // eslint-disable-line guard-for-in
for (const key of Object.getOwnPropertyNames(constr.prototype)) {
// eslint-disable-line guard-for-in
try {
if (constr.prototype[key] instanceof Function) {
result[key] = jest.fn();
@ -114,7 +115,8 @@ export function mkEvent(opts: IEventOpts & { event?: boolean }, client?: MatrixC
};
if (opts.skey !== undefined) {
event.state_key = opts.skey;
} else if ([
} else if (
[
EventType.RoomName,
EventType.RoomTopic,
EventType.RoomCreate,
@ -122,7 +124,8 @@ export function mkEvent(opts: IEventOpts & { event?: boolean }, client?: MatrixC
EventType.RoomPowerLevels,
EventType.RoomTopic,
"com.example.state",
].includes(opts.type)) {
].includes(opts.type)
) {
event.state_key = "";
}
@ -228,8 +231,8 @@ export function mkMembership(opts: IMembershipOpts & { event?: boolean }): Parti
}
export function mkMembershipCustom<T>(
base: T & { membership: string, sender: string, content?: IContent },
): T & { type: EventType, sender: string, state_key: string, content: IContent } & GeneratedMetadata {
base: T & { membership: string; sender: string; content?: IContent },
): T & { type: EventType; sender: string; state_key: string; content: IContent } & GeneratedMetadata {
const content = base.content || {};
return mkEventCustom({
...base,
@ -315,7 +318,7 @@ export function mkReplyMessage(
"rel_type": "m.in_reply_to",
"event_id": opts.replyToMessage.getId(),
"m.in_reply_to": {
"event_id": opts.replyToMessage.getId()!,
event_id: opts.replyToMessage.getId()!,
},
},
},
@ -364,7 +367,8 @@ export class MockStorageApi implements Storage {
* @returns promise which resolves (to `event`) when the event has been decrypted
*/
export async function awaitDecryption(
event: MatrixEvent, { waitOnDecryptionFailure = false } = {},
event: MatrixEvent,
{ waitOnDecryptionFailure = false } = {},
): Promise<MatrixEvent> {
// An event is not always decrypted ahead of time
// getClearContent is a good signal to know whether an event has been decrypted
@ -387,7 +391,7 @@ export async function awaitDecryption(
});
}
export const emitPromise = (e: EventEmitter, k: string): Promise<any> => new Promise(r => e.once(k, r));
export const emitPromise = (e: EventEmitter, k: string): Promise<any> => new Promise((r) => e.once(k, r));
export const mkPusher = (extra: Partial<IPusher> = {}): IPusher => ({
app_display_name: "app",

View File

@ -21,14 +21,21 @@ import { Room } from "../../src/models/room";
import { Thread } from "../../src/models/thread";
import { mkMessage } from "./test-utils";
export const makeThreadEvent = ({ rootEventId, replyToEventId, ...props }: any & {
rootEventId: string; replyToEventId: string; event?: boolean;
}): MatrixEvent => mkMessage({
export const makeThreadEvent = ({
rootEventId,
replyToEventId,
...props
}: any & {
rootEventId: string;
replyToEventId: string;
event?: boolean;
}): MatrixEvent =>
mkMessage({
...props,
relatesTo: {
event_id: rootEventId,
rel_type: "m.thread",
['m.in_reply_to']: {
["m.in_reply_to"]: {
event_id: replyToEventId,
},
},
@ -50,12 +57,17 @@ type MakeThreadEventsProps = {
};
export const makeThreadEvents = ({
roomId, authorId, participantUserIds, length = 2, ts = 1, currentUserId,
}: MakeThreadEventsProps): { rootEvent: MatrixEvent, events: MatrixEvent[] } => {
roomId,
authorId,
participantUserIds,
length = 2,
ts = 1,
currentUserId,
}: MakeThreadEventsProps): { rootEvent: MatrixEvent; events: MatrixEvent[] } => {
const rootEvent = mkMessage({
user: authorId,
room: roomId,
msg: 'root event message ' + Math.random(),
msg: "root event message " + Math.random(),
ts,
event: true,
});
@ -67,7 +79,8 @@ export const makeThreadEvents = ({
const prevEvent = events[i - 1];
const replyToEventId = prevEvent.getId();
const user = participantUserIds[i % participantUserIds.length];
events.push(makeThreadEvent({
events.push(
makeThreadEvent({
user,
room: roomId,
event: true,
@ -76,7 +89,8 @@ export const makeThreadEvents = ({
replyToEventId,
// replies are 1ms after each other
ts: ts + i,
}));
}),
);
}
rootEvent.setUnsigned({
@ -108,7 +122,7 @@ export const mkThread = ({
participantUserIds,
length = 2,
ts = 1,
}: MakeThreadProps): { thread: Thread, rootEvent: MatrixEvent, events: MatrixEvent[] } => {
}: MakeThreadProps): { thread: Thread; rootEvent: MatrixEvent; events: MatrixEvent[] } => {
const { rootEvent, events } = makeThreadEvents({
roomId: room.roomId,
authorId,
@ -120,9 +134,7 @@ export const mkThread = ({
expect(rootEvent).toBeTruthy();
for (const evt of events) {
room?.reEmitter.reEmit(evt, [
MatrixEventEvent.BeforeRedaction,
]);
room?.reEmitter.reEmit(evt, [MatrixEventEvent.BeforeRedaction]);
}
const thread = room.createThread(rootEvent.getId() ?? "", rootEvent, events, true);

View File

@ -41,7 +41,7 @@ import { GroupCallEventHandlerMap } from "../../src/webrtc/groupCall";
import { GroupCallEventHandlerEvent } from "../../src/webrtc/groupCallEventHandler";
import { IScreensharingOpts, MediaHandler } from "../../src/webrtc/mediaHandler";
export const DUMMY_SDP = (
export const DUMMY_SDP =
"v=0\r\n" +
"o=- 5022425983810148698 2 IN IP4 127.0.0.1\r\n" +
"s=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n" +
@ -78,8 +78,7 @@ export const DUMMY_SDP = (
"a=rtpmap:112 telephone-event/32000\r\n" +
"a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" +
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n"
);
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n";
export const USERMEDIA_STREAM_ID = "mock_stream_from_media_handler";
export const SCREENSHARE_STREAM_ID = "mock_screen_stream_from_media_handler";
@ -100,13 +99,19 @@ class MockMediaStreamAudioSourceNode {
}
class MockAnalyser {
public getFloatFrequencyData() { return 0.0; }
public getFloatFrequencyData() {
return 0.0;
}
}
export class MockAudioContext {
constructor() {}
public createAnalyser() { return new MockAnalyser(); }
public createMediaStreamSource() { return new MockMediaStreamAudioSourceNode(); }
public createAnalyser() {
return new MockAnalyser();
}
public createMediaStreamSource() {
return new MockMediaStreamAudioSourceNode();
}
public close() {}
}
@ -132,7 +137,7 @@ export class MockRTCPeerConnection {
}
public static hasAnyPendingNegotiations(): boolean {
return this.instances.some(i => i.needsNegotiation);
return this.instances.some((i) => i.needsNegotiation);
}
public static resetInstances() {
@ -142,11 +147,11 @@ export class MockRTCPeerConnection {
constructor() {
this.localDescription = {
sdp: DUMMY_SDP,
type: 'offer',
type: "offer",
toJSON: function () {},
};
this.readyToNegotiate = new Promise<void>(resolve => {
this.readyToNegotiate = new Promise<void>((resolve) => {
this.onReadyToNegotiate = resolve;
});
@ -154,26 +159,28 @@ export class MockRTCPeerConnection {
}
public addEventListener(type: string, listener: () => void) {
if (type === 'negotiationneeded') {
if (type === "negotiationneeded") {
this.negotiationNeededListener = listener;
} else if (type == 'icecandidate') {
} else if (type == "icecandidate") {
this.iceCandidateListener = listener;
} else if (type === 'iceconnectionstatechange') {
} else if (type === "iceconnectionstatechange") {
this.iceConnectionStateChangeListener = listener;
} else if (type == 'track') {
} else if (type == "track") {
this.onTrackListener = listener;
}
}
public createDataChannel(label: string, opts: RTCDataChannelInit) { return { label, ...opts }; }
public createDataChannel(label: string, opts: RTCDataChannelInit) {
return { label, ...opts };
}
public createOffer() {
return Promise.resolve({
type: 'offer',
type: "offer",
sdp: DUMMY_SDP,
});
}
public createAnswer() {
return Promise.resolve({
type: 'answer',
type: "answer",
sdp: DUMMY_SDP,
});
}
@ -184,7 +191,9 @@ export class MockRTCPeerConnection {
return Promise.resolve();
}
public close() {}
public getStats() { return []; }
public getStats() {
return [];
}
public addTransceiver(track: MockMediaStreamTrack): MockRTCRtpTransceiver {
this.needsNegotiation = true;
if (this.onReadyToNegotiate) this.onReadyToNegotiate();
@ -209,9 +218,11 @@ export class MockRTCPeerConnection {
if (this.onReadyToNegotiate) this.onReadyToNegotiate();
}
public getTransceivers(): MockRTCRtpTransceiver[] { return this.transceivers; }
public getTransceivers(): MockRTCRtpTransceiver[] {
return this.transceivers;
}
public getSenders(): MockRTCRtpSender[] {
return this.transceivers.map(t => t.sender as unknown as MockRTCRtpSender);
return this.transceivers.map((t) => t.sender as unknown as MockRTCRtpSender);
}
public doNegotiation() {
@ -225,7 +236,9 @@ export class MockRTCPeerConnection {
export class MockRTCRtpSender {
constructor(public track: MockMediaStreamTrack) {}
public replaceTrack(track: MockMediaStreamTrack) { this.track = track; }
public replaceTrack(track: MockMediaStreamTrack) {
this.track = track;
}
}
export class MockRTCRtpReceiver {
@ -254,7 +267,9 @@ export class MockMediaStreamTrack {
public isStopped = false;
public settings?: MediaTrackSettings;
public getSettings(): MediaTrackSettings { return this.settings!; }
public getSettings(): MediaTrackSettings {
return this.settings!;
}
// XXX: Using EventTarget in jest doesn't seem to work, so we write our own
// implementation
@ -273,16 +288,15 @@ export class MockMediaStreamTrack {
});
}
public typed(): MediaStreamTrack { return this as unknown as MediaStreamTrack; }
public typed(): MediaStreamTrack {
return this as unknown as MediaStreamTrack;
}
}
// XXX: Using EventTarget in jest doesn't seem to work, so we write our own
// implementation
export class MockMediaStream {
constructor(
public id: string,
private tracks: MockMediaStreamTrack[] = [],
) {}
constructor(public id: string, private tracks: MockMediaStreamTrack[] = []) {}
public listeners: [string, (...args: any[]) => any][] = [];
public isStopped = false;
@ -293,9 +307,15 @@ export class MockMediaStream {
c();
});
}
public getTracks() { return this.tracks; }
public getAudioTracks() { return this.tracks.filter((track) => track.kind === "audio"); }
public getVideoTracks() { return this.tracks.filter((track) => track.kind === "video"); }
public getTracks() {
return this.tracks;
}
public getAudioTracks() {
return this.tracks.filter((track) => track.kind === "audio");
}
public getVideoTracks() {
return this.tracks.filter((track) => track.kind === "video");
}
public addEventListener(eventType: string, callback: (...args: any[]) => any) {
this.listeners.push([eventType, callback]);
}
@ -308,7 +328,9 @@ export class MockMediaStream {
this.tracks.push(track);
this.dispatchEvent("addtrack");
}
public removeTrack(track: MockMediaStreamTrack) { this.tracks.splice(this.tracks.indexOf(track), 1); }
public removeTrack(track: MockMediaStreamTrack) {
this.tracks.splice(this.tracks.indexOf(track), 1);
}
public clone(): MediaStream {
return new MockMediaStream(this.id + ".clone", this.tracks).typed();
@ -325,11 +347,11 @@ export class MockMediaStream {
}
export class MockMediaDeviceInfo {
constructor(
public kind: "audioinput" | "videoinput" | "audiooutput",
) { }
constructor(public kind: "audioinput" | "videoinput" | "audiooutput") {}
public typed(): MediaDeviceInfo { return this as unknown as MediaDeviceInfo; }
public typed(): MediaDeviceInfo {
return this as unknown as MediaDeviceInfo;
}
}
export class MockMediaHandler {
@ -359,28 +381,38 @@ export class MockMediaHandler {
public stopScreensharingStream(stream: MockMediaStream) {
stream.isStopped = true;
}
public hasAudioDevice() { return true; }
public hasVideoDevice() { return true; }
public hasAudioDevice() {
return true;
}
public hasVideoDevice() {
return true;
}
public stopAllStreams() {}
public typed(): MediaHandler { return this as unknown as MediaHandler; }
public typed(): MediaHandler {
return this as unknown as MediaHandler;
}
}
export class MockMediaDevices {
public enumerateDevices = jest.fn<Promise<MediaDeviceInfo[]>, []>().mockResolvedValue([
public enumerateDevices = jest
.fn<Promise<MediaDeviceInfo[]>, []>()
.mockResolvedValue([
new MockMediaDeviceInfo("audioinput").typed(),
new MockMediaDeviceInfo("videoinput").typed(),
]);
public getUserMedia = jest.fn<Promise<MediaStream>, [MediaStreamConstraints]>().mockReturnValue(
Promise.resolve(new MockMediaStream("local_stream").typed()),
);
public getUserMedia = jest
.fn<Promise<MediaStream>, [MediaStreamConstraints]>()
.mockReturnValue(Promise.resolve(new MockMediaStream("local_stream").typed()));
public getDisplayMedia = jest.fn<Promise<MediaStream>, [MediaStreamConstraints]>().mockReturnValue(
Promise.resolve(new MockMediaStream("local_display_stream").typed()),
);
public getDisplayMedia = jest
.fn<Promise<MediaStream>, [MediaStreamConstraints]>()
.mockReturnValue(Promise.resolve(new MockMediaStream("local_display_stream").typed()));
public typed(): MediaDevices { return this as unknown as MediaDevices; }
public typed(): MediaDevices {
return this as unknown as MediaDevices;
}
}
type EmittedEvents = CallEventHandlerEvent | CallEvent | ClientEvent | RoomStateEvent | GroupCallEventHandlerEvent;
@ -405,21 +437,33 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
calls: new Map<string, MatrixCall>(),
};
public sendStateEvent = jest.fn<Promise<ISendEventResponse>, [
roomId: string, eventType: EventType, content: any, statekey: string,
]>();
public sendToDevice = jest.fn<Promise<{}>, [
public sendStateEvent = jest.fn<
Promise<ISendEventResponse>,
[roomId: string, eventType: EventType, content: any, statekey: string]
>();
public sendToDevice = jest.fn<
Promise<{}>,
[
eventType: string,
contentMap: { [userId: string]: { [deviceId: string]: Record<string, any> } },
txnId?: string,
]>();
]
>();
public getMediaHandler(): MediaHandler { return this.mediaHandler.typed(); }
public getMediaHandler(): MediaHandler {
return this.mediaHandler.typed();
}
public getUserId(): string { return this.userId; }
public getUserId(): string {
return this.userId;
}
public getDeviceId(): string { return this.deviceId; }
public getSessionId(): string { return this.sessionId; }
public getDeviceId(): string {
return this.deviceId;
}
public getSessionId(): string {
return this.sessionId;
}
public getTurnServers = () => [];
public isFallbackICEServerAllowed = () => false;
@ -432,18 +476,17 @@ export class MockCallMatrixClient extends TypedEventEmitter<EmittedEvents, Emitt
public getRooms = jest.fn<Room[], []>().mockReturnValue([]);
public getRoom = jest.fn();
public supportsExperimentalThreads(): boolean { return true; }
public supportsExperimentalThreads(): boolean {
return true;
}
public async decryptEventIfNeeded(): Promise<void> {}
public typed(): MatrixClient { return this as unknown as MatrixClient; }
public typed(): MatrixClient {
return this as unknown as MatrixClient;
}
public emitRoomState(event: MatrixEvent, state: RoomState): void {
this.emit(
RoomStateEvent.Events,
event,
state,
null,
);
this.emit(RoomStateEvent.Events, event, state, null);
}
}
@ -481,15 +524,13 @@ export class MockMatrixCall extends TypedEventEmitter<CallEvent, CallEventHandle
return this.opponentDeviceId;
}
public typed(): MatrixCall { return this as unknown as MatrixCall; }
public typed(): MatrixCall {
return this as unknown as MatrixCall;
}
}
export class MockCallFeed {
constructor(
public userId: string,
public deviceId: string | undefined,
public stream: MockMediaStream,
) {}
constructor(public userId: string, public deviceId: string | undefined, public stream: MockMediaStream) {}
public measureVolumeActivity(val: boolean) {}
public dispose() {}
@ -536,10 +577,14 @@ export function installWebRTCMocks() {
};
}
export function makeMockGroupCallStateEvent(roomId: string, groupCallId: string, content: IContent = {
export function makeMockGroupCallStateEvent(
roomId: string,
groupCallId: string,
content: IContent = {
"m.type": GroupCallType.Video,
"m.intent": GroupCallIntent.Prompt,
}): MatrixEvent {
},
): MatrixEvent {
return {
getType: jest.fn().mockReturnValue(EventType.GroupCallPrefix),
getRoomId: jest.fn().mockReturnValue(roomId),

View File

@ -27,13 +27,11 @@ class EventSource extends EventEmitter {
}
doAnError() {
this.emit('error');
this.emit("error");
}
}
class EventTarget extends EventEmitter {
}
class EventTarget extends EventEmitter {}
describe("ReEmitter", function () {
it("Re-Emits events with the same args", function () {
@ -58,13 +56,13 @@ describe("ReEmitter", function() {
const tgt = new EventTarget();
const reEmitter = new ReEmitter(tgt);
reEmitter.reEmit(src, ['error']);
reEmitter.reEmit(src, ["error"]);
// without the workaround in ReEmitter, this would throw
src.doAnError();
const handler = jest.fn();
tgt.on('error', handler);
tgt.on("error", handler);
src.doAnError();

View File

@ -30,29 +30,41 @@ describe("AutoDiscovery", function() {
getHttpBackend();
return Promise.all([
// @ts-ignore testing no args
AutoDiscovery.findClientConfig(/* no args */).then(() => {
AutoDiscovery.findClientConfig(/* no args */).then(
() => {
throw new Error("Expected a failure, not success with no args");
}, () => {
},
() => {
return true;
}),
},
),
AutoDiscovery.findClientConfig("").then(() => {
AutoDiscovery.findClientConfig("").then(
() => {
throw new Error("Expected a failure, not success with an empty string");
}, () => {
},
() => {
return true;
}),
},
),
AutoDiscovery.findClientConfig(null as any).then(() => {
AutoDiscovery.findClientConfig(null as any).then(
() => {
throw new Error("Expected a failure, not success with null");
}, () => {
},
() => {
return true;
}),
},
),
AutoDiscovery.findClientConfig(true as any).then(() => {
AutoDiscovery.findClientConfig(true as any).then(
() => {
throw new Error("Expected a failure, not success with a non-string");
}, () => {
},
() => {
return true;
}),
},
),
]);
});
@ -169,9 +181,7 @@ describe("AutoDiscovery", function() {
};
return Promise.all([
httpBackend.flushAllExpected(),
AutoDiscovery.findClientConfig("example.org").then(
expect(expected).toEqual,
),
AutoDiscovery.findClientConfig("example.org").then(expect(expected).toEqual),
]);
});
@ -257,8 +267,10 @@ describe("AutoDiscovery", function() {
]);
});
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 404)", function() {
it(
"should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 404)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").respond(404, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -285,10 +297,13 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 500)", function() {
it(
"should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 500)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").respond(500, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -315,10 +330,13 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 200 but wrong content)", function() {
it(
"should return FAIL_ERROR when .well-known has an invalid base_url for " +
"m.homeserver (verification failure: 200 but wrong content)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
not_matrix_versions: ["r0.0.1"],
@ -347,14 +365,17 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return SUCCESS when .well-known has a verifiably accurate base_url for " +
"m.homeserver", function() {
it("should return SUCCESS when .well-known has a verifiably accurate base_url for " + "m.homeserver", function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://example.org/_matrix/client/versions");
}).respond(200, {
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -385,10 +406,12 @@ describe("AutoDiscovery", function() {
it("should return SUCCESS with the right homeserver URL", function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -418,13 +441,16 @@ describe("AutoDiscovery", function() {
]);
});
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (missing base_url)", function() {
it(
"should return SUCCESS / FAIL_PROMPT when the identity server configuration " + "is wrong (missing base_url)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -457,15 +483,19 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (empty base_url)", function() {
it(
"should return SUCCESS / FAIL_PROMPT when the identity server configuration " + "is wrong (empty base_url)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
@ -498,15 +528,20 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 404)", function() {
it(
"should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 404)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").respond(404, {});
@ -540,15 +575,20 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 500)", function() {
it(
"should return SUCCESS / FAIL_PROMPT when the identity server configuration " +
"is wrong (validation error: 500)",
function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").respond(500, {});
@ -582,21 +622,25 @@ describe("AutoDiscovery", function() {
expect(conf).toEqual(expected);
}),
]);
});
},
);
it("should return SUCCESS when the identity server configuration is " +
"verifiably accurate", function() {
it("should return SUCCESS when the identity server configuration is " + "verifiably accurate", function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").check((req) => {
expect(req.path)
.toEqual("https://identity.example.org/_matrix/identity/api/v1");
}).respond(200, {});
httpBackend
.when("GET", "/_matrix/identity/api/v1")
.check((req) => {
expect(req.path).toEqual("https://identity.example.org/_matrix/identity/api/v1");
})
.respond(200, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash
@ -627,19 +671,22 @@ describe("AutoDiscovery", function() {
]);
});
it("should return SUCCESS and preserve non-standard keys from the " +
".well-known response", function() {
it("should return SUCCESS and preserve non-standard keys from the " + ".well-known response", function () {
const httpBackend = getHttpBackend();
httpBackend.when("GET", "/_matrix/client/versions").check((req) => {
expect(req.path)
.toEqual("https://chat.example.org/_matrix/client/versions");
}).respond(200, {
httpBackend
.when("GET", "/_matrix/client/versions")
.check((req) => {
expect(req.path).toEqual("https://chat.example.org/_matrix/client/versions");
})
.respond(200, {
versions: ["r0.0.1"],
});
httpBackend.when("GET", "/_matrix/identity/api/v1").check((req) => {
expect(req.path)
.toEqual("https://identity.example.org/_matrix/identity/api/v1");
}).respond(200, {});
httpBackend
.when("GET", "/_matrix/identity/api/v1")
.check((req) => {
expect(req.path).toEqual("https://identity.example.org/_matrix/identity/api/v1");
})
.respond(200, {});
httpBackend.when("GET", "/.well-known/matrix/client").respond(200, {
"m.homeserver": {
// Note: we also expect this test to trim the trailing slash

View File

@ -26,23 +26,18 @@ import {
parseTopicContent,
} from "../../src/content-helpers";
describe('Beacon content helpers', () => {
describe('makeBeaconInfoContent()', () => {
describe("Beacon content helpers", () => {
describe("makeBeaconInfoContent()", () => {
const mockDateNow = 123456789;
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(mockDateNow);
jest.spyOn(global.Date, "now").mockReturnValue(mockDateNow);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
});
it('create fully defined event content', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
)).toEqual({
description: 'nice beacon_info',
it("create fully defined event content", () => {
expect(makeBeaconInfoContent(1234, true, "nice beacon_info", LocationAssetType.Pin)).toEqual({
description: "nice beacon_info",
timeout: 1234,
live: true,
[M_TIMESTAMP.name]: mockDateNow,
@ -52,78 +47,72 @@ describe('Beacon content helpers', () => {
});
});
it('defaults timestamp to current time', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
)).toEqual(expect.objectContaining({
it("defaults timestamp to current time", () => {
expect(makeBeaconInfoContent(1234, true, "nice beacon_info", LocationAssetType.Pin)).toEqual(
expect.objectContaining({
[M_TIMESTAMP.name]: mockDateNow,
}));
}),
);
});
it('uses timestamp when provided', () => {
expect(makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
LocationAssetType.Pin,
99999,
)).toEqual(expect.objectContaining({
it("uses timestamp when provided", () => {
expect(makeBeaconInfoContent(1234, true, "nice beacon_info", LocationAssetType.Pin, 99999)).toEqual(
expect.objectContaining({
[M_TIMESTAMP.name]: 99999,
}));
}),
);
});
it('defaults asset type to self when not set', () => {
expect(makeBeaconInfoContent(
it("defaults asset type to self when not set", () => {
expect(
makeBeaconInfoContent(
1234,
true,
'nice beacon_info',
"nice beacon_info",
// no assetType passed
)).toEqual(expect.objectContaining({
),
).toEqual(
expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
}),
);
});
});
describe('makeBeaconContent()', () => {
it('creates event content without description', () => {
expect(makeBeaconContent(
'geo:foo',
describe("makeBeaconContent()", () => {
it("creates event content without description", () => {
expect(
makeBeaconContent(
"geo:foo",
123,
'$1234',
"$1234",
// no description
)).toEqual({
),
).toEqual({
[M_LOCATION.name]: {
description: undefined,
uri: 'geo:foo',
uri: "geo:foo",
},
[M_TIMESTAMP.name]: 123,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: '$1234',
event_id: "$1234",
},
});
});
it('creates event content with description', () => {
expect(makeBeaconContent(
'geo:foo',
123,
'$1234',
'test description',
)).toEqual({
it("creates event content with description", () => {
expect(makeBeaconContent("geo:foo", 123, "$1234", "test description")).toEqual({
[M_LOCATION.name]: {
description: 'test description',
uri: 'geo:foo',
description: "test description",
uri: "geo:foo",
},
[M_TIMESTAMP.name]: 123,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: '$1234',
event_id: "$1234",
},
});
});
@ -190,64 +179,81 @@ describe('Beacon content helpers', () => {
});
});
describe('Topic content helpers', () => {
describe('makeTopicContent()', () => {
it('creates fully defined event content without html', () => {
describe("Topic content helpers", () => {
describe("makeTopicContent()", () => {
it("creates fully defined event content without html", () => {
expect(makeTopicContent("pizza")).toEqual({
topic: "pizza",
[M_TOPIC.name]: [{
[M_TOPIC.name]: [
{
body: "pizza",
mimetype: "text/plain",
}],
},
],
});
});
it('creates fully defined event content with html', () => {
it("creates fully defined event content with html", () => {
expect(makeTopicContent("pizza", "<b>pizza</b>")).toEqual({
topic: "pizza",
[M_TOPIC.name]: [{
[M_TOPIC.name]: [
{
body: "pizza",
mimetype: "text/plain",
}, {
},
{
body: "<b>pizza</b>",
mimetype: "text/html",
}],
},
],
});
});
});
describe('parseTopicContent()', () => {
it('parses event content with plain text topic without mimetype', () => {
expect(parseTopicContent({
describe("parseTopicContent()", () => {
it("parses event content with plain text topic without mimetype", () => {
expect(
parseTopicContent({
topic: "pizza",
[M_TOPIC.name]: [{
[M_TOPIC.name]: [
{
body: "pizza",
}],
})).toEqual({
},
],
}),
).toEqual({
text: "pizza",
});
});
it('parses event content with plain text topic', () => {
expect(parseTopicContent({
it("parses event content with plain text topic", () => {
expect(
parseTopicContent({
topic: "pizza",
[M_TOPIC.name]: [{
[M_TOPIC.name]: [
{
body: "pizza",
mimetype: "text/plain",
}],
})).toEqual({
},
],
}),
).toEqual({
text: "pizza",
});
});
it('parses event content with html topic', () => {
expect(parseTopicContent({
it("parses event content with html topic", () => {
expect(
parseTopicContent({
topic: "pizza",
[M_TOPIC.name]: [{
[M_TOPIC.name]: [
{
body: "<b>pizza</b>",
mimetype: "text/html",
}],
})).toEqual({
},
],
}),
).toEqual({
text: "pizza",
html: "<b>pizza</b>",
});

View File

@ -22,11 +22,7 @@ describe("ContentRepo", function() {
describe("getHttpUriForMxc", function () {
it("should do nothing to HTTP URLs when allowing direct links", function () {
const httpUrl = "http://example.com/image.jpeg";
expect(
getHttpUriForMxc(
baseUrl, httpUrl, undefined, undefined, undefined, true,
),
).toEqual(httpUrl);
expect(getHttpUriForMxc(baseUrl, httpUrl, undefined, undefined, undefined, true)).toEqual(httpUrl);
});
it("should return the empty string HTTP URLs by default", function () {
@ -34,8 +30,7 @@ describe("ContentRepo", function() {
expect(getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
});
it("should return a download URL if no width/height/resize are specified",
function() {
it("should return a download URL if no width/height/resize are specified", function () {
const mxcUri = "mxc://server.name/resourceid";
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
@ -43,29 +38,24 @@ describe("ContentRepo", function() {
});
it("should return the empty string for null input", function () {
expect(getHttpUriForMxc(null as any, '')).toEqual("");
expect(getHttpUriForMxc(null as any, "")).toEqual("");
});
it("should return a thumbnail URL if a width/height/resize is specified",
function() {
it("should return a thumbnail URL if a width/height/resize is specified", function () {
const mxcUri = "mxc://server.name/resourceid";
expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32&height=64&method=crop",
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" + "?width=32&height=64&method=crop",
);
});
it("should put fragments from mxc:// URIs after any query parameters",
function() {
it("should put fragments from mxc:// URIs after any query parameters", function () {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32#automade",
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" + "?width=32#automade",
);
});
it("should put fragments from mxc:// URIs at the end of the HTTP URI",
function() {
it("should put fragments from mxc:// URIs at the end of the HTTP URI", function () {
const mxcUri = "mxc://server.name/resourceid#automade";
expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",

View File

@ -1,4 +1,4 @@
import '../olm-loader';
import "../olm-loader";
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
@ -14,11 +14,11 @@ import * as olmlib from "../../src/crypto/olmlib";
import { sleep } from "../../src/utils";
import { CRYPTO_ENABLED } from "../../src/client";
import { DeviceInfo } from "../../src/crypto/deviceinfo";
import { logger } from '../../src/logger';
import { logger } from "../../src/logger";
import { MemoryStore } from "../../src";
import { RoomKeyRequestState } from '../../src/crypto/OutgoingRoomKeyRequestManager';
import { RoomMember } from '../../src/models/room-member';
import { IStore } from '../../src/store';
import { RoomKeyRequestState } from "../../src/crypto/OutgoingRoomKeyRequestManager";
import { RoomMember } from "../../src/models/room-member";
import { IStore } from "../../src/store";
import { IRoomEncryption, RoomList } from "../../src/crypto/RoomList";
const Olm = global.Olm;
@ -75,10 +75,10 @@ function roomKeyEventForEvent(client: MatrixClient, event: MatrixEvent): MatrixE
type: "m.room_key",
sender: client.getUserId()!,
content: {
"algorithm": olmlib.MEGOLM_ALGORITHM,
"room_id": roomId,
"session_id": eventContent.session_id,
"session_key": key.key,
algorithm: olmlib.MEGOLM_ALGORITHM,
room_id: roomId,
session_id: eventContent.session_id,
session_key: key.key,
},
});
// make onRoomKeyEvent think this was an encrypted event
@ -112,28 +112,29 @@ describe("Crypto", function() {
describe("encrypted events", function () {
it("provides encryption information", async function () {
const client = (new TestClient(
"@alice:example.com", "deviceid",
)).client;
const client = new TestClient("@alice:example.com", "deviceid").client;
await client.initCrypto();
// unencrypted event
const event = {
getId: () => "$event_id",
getSenderKey: () => null,
getWireContent: () => {return {};},
getWireContent: () => {
return {};
},
} as unknown as MatrixEvent;
let encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeFalsy();
// unknown sender (e.g. deleted device), forwarded megolm key (untrusted)
event.getSenderKey = () => 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
event.getWireContent = () => {return { algorithm: olmlib.MEGOLM_ALGORITHM };};
event.getSenderKey = () => "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI";
event.getWireContent = () => {
return { algorithm: olmlib.MEGOLM_ALGORITHM };
};
event.getForwardingCurve25519KeyChain = () => ["not empty"];
event.isKeySourceUntrusted = () => true;
event.getClaimedEd25519Key =
() => 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
event.getClaimedEd25519Key = () => "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
@ -144,10 +145,8 @@ describe("Crypto", function() {
event.getForwardingCurve25519KeyChain = () => [];
event.isKeySourceUntrusted = () => true;
const device = new DeviceInfo("FLIBBLE");
device.keys["curve25519:FLIBBLE"] =
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
device.keys["ed25519:FLIBBLE"] =
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
device.keys["curve25519:FLIBBLE"] = "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI";
device.keys["ed25519:FLIBBLE"] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
client.crypto!.deviceList.getDeviceByIdentityKey = () => device;
encryptionInfo = client.getEventEncryptionInfo(event);
@ -158,8 +157,7 @@ describe("Crypto", function() {
// known sender, trusted megolm key, but bad ed25519key
event.isKeySourceUntrusted = () => false;
device.keys["ed25519:FLIBBLE"] =
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB';
device.keys["ed25519:FLIBBLE"] = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
encryptionInfo = client.getEventEncryptionInfo(event);
expect(encryptionInfo.encrypted).toBeTruthy();
@ -171,17 +169,17 @@ describe("Crypto", function() {
});
});
describe('Session management', function() {
describe("Session management", function () {
const otkResponse: IClaimOTKsResult = {
failures: {},
one_time_keys: {
'@alice:home.server': {
"@alice:home.server": {
aliceDevice: {
'signed_curve25519:FLIBBLE': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
"signed_curve25519:FLIBBLE": {
key: "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI",
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally a valid signature',
"@alice:home.server": {
"ed25519:aliceDevice": "totally a valid signature",
},
},
},
@ -201,21 +199,24 @@ describe("Crypto", function() {
const clientStore = new MemoryStore({ localStorage: mockStorage }) as unknown as IStore;
const cryptoStore = new MemoryCryptoStore();
cryptoStore.storeEndToEndDeviceData({
cryptoStore.storeEndToEndDeviceData(
{
devices: {
'@bob:home.server': {
'BOBDEVICE': {
"@bob:home.server": {
BOBDEVICE: {
algorithms: [],
verified: 1,
known: false,
keys: {
'curve25519:BOBDEVICE': 'this is a key',
"curve25519:BOBDEVICE": "this is a key",
},
},
},
},
trackingStatus: {},
}, {});
},
{},
);
mockBaseApis = {
sendToDevice: jest.fn(),
@ -251,38 +252,32 @@ describe("Crypto", function() {
};
});
fakeEmitter.emit('toDeviceEvent', {
fakeEmitter.emit("toDeviceEvent", {
getId: jest.fn().mockReturnValue("$wedged"),
getType: jest.fn().mockReturnValue('m.room.message'),
getType: jest.fn().mockReturnValue("m.room.message"),
getContent: jest.fn().mockReturnValue({
msgtype: 'm.bad.encrypted',
msgtype: "m.bad.encrypted",
}),
getWireContent: jest.fn().mockReturnValue({
algorithm: 'm.olm.v1.curve25519-aes-sha2',
sender_key: 'this is a key',
algorithm: "m.olm.v1.curve25519-aes-sha2",
sender_key: "this is a key",
}),
getSender: jest.fn().mockReturnValue('@bob:home.server'),
getSender: jest.fn().mockReturnValue("@bob:home.server"),
});
await prom;
});
});
describe('Key requests', function() {
describe("Key requests", function () {
let aliceClient: MatrixClient;
let bobClient: MatrixClient;
let claraClient: MatrixClient;
beforeEach(async function () {
aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
claraClient = (new TestClient(
"@clara:example.com", "claradevice",
)).client;
aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
bobClient = new TestClient("@bob:example.com", "bobdevice").client;
claraClient = new TestClient("@clara:example.com", "claradevice").client;
await aliceClient.initCrypto();
await bobClient.initCrypto();
await claraClient.initCrypto();
@ -296,19 +291,21 @@ describe("Crypto", function() {
it("does not cancel keyshare requests until all messages are decrypted with trusted keys", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
// Make Bob invited by Alice so Bob will accept Alice's forwarded keys
bobRoom.currentState.setStateEvents([new MatrixEvent({
bobRoom.currentState.setStateEvents([
new MatrixEvent({
type: "m.room.member",
sender: "@alice:example.com",
room_id: roomId,
content: { membership: "invite" },
state_key: "@bob:example.com",
})]);
}),
]);
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
@ -335,7 +332,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -352,18 +350,19 @@ describe("Crypto", function() {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
}),
);
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const decryptEventsPromise = Promise.all(events.map((ev) => {
const decryptEventsPromise = Promise.all(
events.map((ev) => {
return awaitEvent(ev, "Event.decrypted");
}));
}),
);
// keyshare the session key starting at the second message, so
// the first message can't be decrypted yet, but the second one
@ -420,7 +419,7 @@ describe("Crypto", function() {
it("should error if a forwarded room key lacks a content.sender_key", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -459,9 +458,7 @@ describe("Crypto", function() {
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const ksEvent = await keyshareEventForEvent(aliceClient, event, 1);
ksEvent.getContent().sender_key = undefined; // test
@ -490,8 +487,7 @@ describe("Crypto", function() {
session_id: "sessionid",
sender_key: "senderkey",
};
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
.toBeDefined();
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody)).toBeDefined();
});
it("uses a new txnid for re-requesting keys", async function () {
@ -541,7 +537,7 @@ describe("Crypto", function() {
it("should accept forwarded keys which it requested", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -572,7 +568,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -589,7 +586,8 @@ describe("Crypto", function() {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
}),
);
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
@ -607,18 +605,17 @@ describe("Crypto", function() {
};
const outgoingReq = await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody);
expect(outgoingReq).toBeDefined();
await cryptoStore.updateOutgoingRoomKeyRequest(
outgoingReq!.requestId, RoomKeyRequestState.Unsent,
{ state: RoomKeyRequestState.Sent },
);
await cryptoStore.updateOutgoingRoomKeyRequest(outgoingReq!.requestId, RoomKeyRequestState.Unsent, {
state: RoomKeyRequestState.Sent,
});
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const decryptEventsPromise = Promise.all(events.map((ev) => {
const decryptEventsPromise = Promise.all(
events.map((ev) => {
return awaitEvent(ev, "Event.decrypted");
}));
}),
);
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
await bobDecryptor.onRoomKeyEvent(ksEvent);
const key = await bobClient.crypto!.olmDevice.getInboundGroupSessionKey(
@ -634,20 +631,22 @@ describe("Crypto", function() {
it("should accept forwarded keys from the user who invited it to the room", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
const bobRoom = new Room(roomId, bobClient, "@bob:example.com", {});
const claraRoom = new Room(roomId, claraClient, "@clara:example.com", {});
// Make Bob invited by Clara
bobRoom.currentState.setStateEvents([new MatrixEvent({
bobRoom.currentState.setStateEvents([
new MatrixEvent({
type: "m.room.member",
sender: "@clara:example.com",
room_id: roomId,
content: { membership: "invite" },
state_key: "@bob:example.com",
})]);
}),
]);
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
claraClient.store.storeRoom(claraRoom);
@ -676,7 +675,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -693,19 +693,20 @@ describe("Crypto", function() {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
}),
);
const device = new DeviceInfo(claraClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@clara:example.com";
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const decryptEventsPromise = Promise.all(events.map((ev) => {
const decryptEventsPromise = Promise.all(
events.map((ev) => {
return awaitEvent(ev, "Event.decrypted");
}));
}),
);
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = claraClient.getUserId()!;
ksEvent.sender = new RoomMember(roomId, claraClient.getUserId()!);
@ -723,7 +724,7 @@ describe("Crypto", function() {
it("should accept forwarded keys from one of its own user's other devices", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -754,7 +755,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -771,20 +773,21 @@ describe("Crypto", function() {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
}),
);
const device = new DeviceInfo(claraClient.deviceId!);
device.verified = DeviceInfo.DeviceVerification.VERIFIED;
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@bob:example.com";
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const decryptEventsPromise = Promise.all(events.map((ev) => {
const decryptEventsPromise = Promise.all(
events.map((ev) => {
return awaitEvent(ev, "Event.decrypted");
}));
}),
);
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = bobClient.getUserId()!;
ksEvent.sender = new RoomMember(roomId, bobClient.getUserId()!);
@ -802,7 +805,7 @@ describe("Crypto", function() {
it("should not accept unexpected forwarded keys for a room it's in", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -836,7 +839,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -853,15 +857,14 @@ describe("Crypto", function() {
// we expect this to fail because we don't have the
// decryption keys yet
}
}));
}),
);
const device = new DeviceInfo(claraClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const ksEvent = await keyshareEventForEvent(aliceClient, events[0], 0);
ksEvent.event.sender = claraClient.getUserId()!;
@ -877,7 +880,7 @@ describe("Crypto", function() {
it("should park forwarded keys for a room it's not in", async function () {
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -905,7 +908,8 @@ describe("Crypto", function() {
},
}),
];
await Promise.all(events.map(async (event) => {
await Promise.all(
events.map(async (event) => {
// alice encrypts each event, and then bob tries to decrypt
// them without any keys, so that they'll be in pending
await aliceClient.crypto!.encryptEvent(event, aliceRoom);
@ -916,15 +920,14 @@ describe("Crypto", function() {
event.senderCurve25519Key = null;
// @ts-ignore private properties
event.claimedEd25519Key = null;
}));
}),
);
const device = new DeviceInfo(aliceClient.deviceId!);
bobClient.crypto!.deviceList.getDeviceByIdentityKey = () => device;
bobClient.crypto!.deviceList.getUserByIdentityKey = () => "@alice:example.com";
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(
roomId, olmlib.MEGOLM_ALGORITHM,
);
const bobDecryptor = bobClient.crypto!.getRoomDecryptor(roomId, olmlib.MEGOLM_ALGORITHM);
const content = events[0].getWireContent();
@ -943,22 +946,24 @@ describe("Crypto", function() {
content.session_id,
);
const parked = await bobClient.crypto!.cryptoStore.takeParkedSharedHistory(roomId);
expect(parked).toEqual([{
expect(parked).toEqual([
{
senderId: aliceClient.getUserId(),
senderKey: content.sender_key,
sessionId: content.session_id,
sessionKey: aliceKey!.key,
keysClaimed: { ed25519: aliceKey!.sender_claimed_ed25519_key },
forwardingCurve25519KeyChain: ["akey"],
}]);
},
]);
});
});
describe('Secret storage', function() {
describe("Secret storage", function () {
it("creates secret storage even if there is no keyInfo", async function () {
jest.spyOn(logger, 'log').mockImplementation(() => {});
jest.spyOn(logger, "log").mockImplementation(() => {});
jest.setTimeout(10000);
const client = (new TestClient("@a:example.com", "dev")).client;
const client = new TestClient("@a:example.com", "dev").client;
await client.initCrypto();
client.crypto!.getSecretStorageKey = jest.fn().mockResolvedValue(null);
client.crypto!.isCrossSigningReady = async () => false;
@ -998,10 +1003,7 @@ describe("Crypto", function() {
// running initCrypto should trigger a key upload
client.httpBackend.when("POST", "/keys/upload").respond(200, {});
await Promise.all([
client.client.initCrypto(),
client.httpBackend.flush("/keys/upload", 1),
]);
await Promise.all([client.client.initCrypto(), client.httpBackend.flush("/keys/upload", 1)]);
encryptedPayload = {
algorithm: "m.olm.v1.curve25519-aes-sha2",
@ -1035,7 +1037,8 @@ describe("Crypto", function() {
},
},
});
}).respond(200, {});
})
.respond(200, {});
await Promise.all([
client.client.encryptAndSendToDevices(
@ -1111,10 +1114,13 @@ describe("Crypto", function() {
it("should free PkDecryption", () => {
const free = jest.fn();
jest.spyOn(Olm, "PkDecryption").mockImplementation(() => ({
jest.spyOn(Olm, "PkDecryption").mockImplementation(
() =>
({
init_with_private_key: jest.fn(),
free,
}) as unknown as PkDecryption);
} as unknown as PkDecryption),
);
client.client.checkSecretStoragePrivateKey(new Uint8Array(), "");
expect(free).toHaveBeenCalled();
});
@ -1134,10 +1140,13 @@ describe("Crypto", function() {
it("should free PkSigning", () => {
const free = jest.fn();
jest.spyOn(Olm, "PkSigning").mockImplementation(() => ({
jest.spyOn(Olm, "PkSigning").mockImplementation(
() =>
({
init_with_seed: jest.fn(),
free,
}) as unknown as PkSigning);
} as unknown as PkSigning),
);
client.client.checkCrossSigningPrivateKey(new Uint8Array(), "");
expect(free).toHaveBeenCalled();
});

View File

@ -14,28 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import {
CrossSigningInfo,
createCryptoStoreCacheCallbacks,
} from '../../../src/crypto/CrossSigning';
import {
IndexedDBCryptoStore,
} from '../../../src/crypto/store/indexeddb-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import "../../olm-loader";
import { CrossSigningInfo, createCryptoStoreCacheCallbacks } from "../../../src/crypto/CrossSigning";
import { IndexedDBCryptoStore } from "../../../src/crypto/store/indexeddb-crypto-store";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import "fake-indexeddb/auto";
import "jest-localstorage-mock";
import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { logger } from '../../../src/logger';
import { logger } from "../../../src/logger";
const userId = "@alice:example.com";
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82, 0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef, 0xae, 0xb1, 0x05,
0xc1, 0xe7, 0x62, 0x78, 0xa6, 0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const types = [
@ -52,7 +45,7 @@ const masterKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
describe("CrossSigningInfo.getCrossSigningKey", function () {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
logger.warn("Not running megolm backup unit tests: libolm not present");
return;
}
@ -65,8 +58,7 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
await expect(info.getCrossSigningKey("master")).rejects.toThrow();
});
it.each(types)("should throw if the callback returns falsey",
async ({ type, shouldCache }) => {
it.each(types)("should throw if the callback returns falsey", async ({ type, shouldCache }) => {
const info = new CrossSigningInfo(userId, {
getCrossSigningKey: async () => false as unknown as Uint8Array,
});
@ -96,9 +88,8 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
}
});
it.each(types)("should request a key from the cache callback (if set)" +
" and does not call app if one is found" +
" %o",
it.each(types)(
"should request a key from the cache callback (if set)" + " and does not call app if one is found" + " %o",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
@ -108,28 +99,20 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ getCrossSigningKeyCache },
);
const info = new CrossSigningInfo(userId, { getCrossSigningKey }, { getCrossSigningKeyCache });
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
if (shouldCache) {
expect(getCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
}
});
},
);
it.each(types)("should store a key with the cache callback (if set)",
async ({ type, shouldCache }) => {
it.each(types)("should store a key with the cache callback (if set)", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
const info = new CrossSigningInfo(userId, { getCrossSigningKey }, { storeCrossSigningKeyCache });
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
expect(pubKey).toEqual(masterKeyPub);
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(shouldCache ? 1 : 0);
@ -139,21 +122,15 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
}
});
it.each(types)("does not store a bad key to the cache",
async ({ type, shouldCache }) => {
it.each(types)("does not store a bad key to the cache", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
{ storeCrossSigningKeyCache },
);
const info = new CrossSigningInfo(userId, { getCrossSigningKey }, { storeCrossSigningKeyCache });
await expect(info.getCrossSigningKey(type, masterKeyPub)).rejects.toThrow();
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(0);
});
it.each(types)("does not store a value to the cache if it came from the cache",
async ({ type, shouldCache }) => {
it.each(types)("does not store a value to the cache if it came from the cache", async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockImplementation(() => {
if (shouldCache) {
return Promise.reject(new Error("Regular callback called"));
@ -162,9 +139,7 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
}
});
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
const storeCrossSigningKeyCache = jest.fn().mockRejectedValue(
new Error("Tried to store a value from cache"),
);
const storeCrossSigningKeyCache = jest.fn().mockRejectedValue(new Error("Tried to store a value from cache"));
const info = new CrossSigningInfo(
userId,
{ getCrossSigningKey },
@ -175,8 +150,9 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
expect(pubKey).toEqual(masterKeyPub);
});
it.each(types)("requests a key from the cache callback (if set) and then calls app" +
" if one is not found", async ({ type, shouldCache }) => {
it.each(types)(
"requests a key from the cache callback (if set) and then calls app" + " if one is not found",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
const storeCrossSigningKeyCache = jest.fn();
@ -192,10 +168,12 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
},
);
it.each(types)("requests a key from the cache callback (if set) and then" +
" calls app if that key doesn't match", async ({ type, shouldCache }) => {
it.each(types)(
"requests a key from the cache callback (if set) and then" + " calls app if that key doesn't match",
async ({ type, shouldCache }) => {
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(badKey);
const storeCrossSigningKeyCache = jest.fn();
@ -211,7 +189,8 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
/* Also expect that the cache gets updated */
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
});
},
);
});
/*
@ -219,18 +198,19 @@ describe("CrossSigningInfo.getCrossSigningKey", function() {
* it's not possible to get one in normal execution unless you hack as we do here.
*/
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore",
() => new IndexedDBCryptoStore(undefined!, "tests")],
["MemoryCryptoStore", () => {
["IndexedDBCryptoStore", () => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore", () => new IndexedDBCryptoStore(undefined!, "tests")],
[
"MemoryCryptoStore",
() => {
const store = new IndexedDBCryptoStore(undefined!, "tests");
// @ts-ignore set private properties
store._backend = new MemoryCryptoStore();
// @ts-ignore
store._backendPromise = Promise.resolve(store._backend);
return store;
}],
},
],
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function (name, dbFactory) {
let store: IndexedDBCryptoStore;
@ -245,8 +225,10 @@ describe.each([
it("should cache data to the store and retrieve it", async () => {
await store.startup();
const olmDevice = new OlmDevice(store);
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
createCryptoStoreCacheCallbacks(store, olmDevice);
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } = createCryptoStoreCacheCallbacks(
store,
olmDevice,
);
await storeCrossSigningKeyCache!("self_signing", testKey);
// If we've not saved anything, don't expect anything

View File

@ -25,31 +25,26 @@ import { OlmDevice } from "../../../src/crypto/OlmDevice";
import { CryptoStore } from "../../../src/crypto/store/base";
const signedDeviceList: IDownloadKeyResult = {
"failures": {},
"device_keys": {
failures: {},
device_keys: {
"@test1:sw1v.org": {
"HGKAWHRVJQ": {
"signatures": {
HGKAWHRVJQ: {
signatures: {
"@test1:sw1v.org": {
"ed25519:HGKAWHRVJQ":
"8PB450fxKDn5s8IiRZ2N2t6MiueQYVRLHFEzqIi1eLdxx1w" +
"XEPC1/1Uz9T4gwnKlMVAKkhB5hXQA/3kjaeLABw",
},
},
"user_id": "@test1:sw1v.org",
"keys": {
"ed25519:HGKAWHRVJQ":
"0gI/T6C+mn1pjtvnnW2yB2l1IIBb/5ULlBXi/LXFSEQ",
"curve25519:HGKAWHRVJQ":
"mbIZED1dBsgIgkgzxDpxKkJmsr4hiWlGzQTvUnQe3RY",
user_id: "@test1:sw1v.org",
keys: {
"ed25519:HGKAWHRVJQ": "0gI/T6C+mn1pjtvnnW2yB2l1IIBb/5ULlBXi/LXFSEQ",
"curve25519:HGKAWHRVJQ": "mbIZED1dBsgIgkgzxDpxKkJmsr4hiWlGzQTvUnQe3RY",
},
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2",
],
"device_id": "HGKAWHRVJQ",
"unsigned": {
"device_display_name": "",
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "HGKAWHRVJQ",
unsigned: {
device_display_name: "",
},
},
},
@ -57,38 +52,33 @@ const signedDeviceList: IDownloadKeyResult = {
};
const signedDeviceList2: IDownloadKeyResult = {
"failures": {},
"device_keys": {
failures: {},
device_keys: {
"@test2:sw1v.org": {
"QJVRHWAKGH": {
"signatures": {
QJVRHWAKGH: {
signatures: {
"@test2:sw1v.org": {
"ed25519:QJVRHWAKGH":
"w1xxdLe1iIqzEFHLRVYQeuiM6t2N2ZRiI8s5nDKxf054BP8" +
"1CPEX/AQXh5BhkKAVMlKnwg4T9zU1/wBALeajk3",
},
},
"user_id": "@test2:sw1v.org",
"keys": {
"ed25519:QJVRHWAKGH":
"Ig0/C6T+bBII1l2By2Wnnvtjp1nm/iXBlLU5/QESFXL",
"curve25519:QJVRHWAKGH":
"YR3eQnUvTQzGlWih4rsmJkKxpDxzgkgIgsBd1DEZIbm",
user_id: "@test2:sw1v.org",
keys: {
"ed25519:QJVRHWAKGH": "Ig0/C6T+bBII1l2By2Wnnvtjp1nm/iXBlLU5/QESFXL",
"curve25519:QJVRHWAKGH": "YR3eQnUvTQzGlWih4rsmJkKxpDxzgkgIgsBd1DEZIbm",
},
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2",
],
"device_id": "QJVRHWAKGH",
"unsigned": {
"device_display_name": "",
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "QJVRHWAKGH",
unsigned: {
device_display_name: "",
},
},
},
},
};
describe('DeviceList', function() {
describe("DeviceList", function () {
let downloadSpy: jest.Mock;
let cryptoStore: CryptoStore;
let deviceLists: DeviceList[] = [];
@ -109,8 +99,8 @@ describe('DeviceList', function() {
function createTestDeviceList(keyDownloadChunkSize = 250) {
const baseApis = {
downloadKeysForUsers: downloadSpy,
getUserId: () => '@test1:sw1v.org',
deviceId: 'HGKAWHRVJQ',
getUserId: () => "@test1:sw1v.org",
deviceId: "HGKAWHRVJQ",
} as unknown as MatrixClient;
const mockOlm = {
verifySignature: function (key: string, message: string, signature: string) {},
@ -123,52 +113,53 @@ describe('DeviceList', function() {
it("should successfully download and store device keys", function () {
const dl = createTestDeviceList();
dl.startTrackingDeviceList('@test1:sw1v.org');
dl.startTrackingDeviceList("@test1:sw1v.org");
const queryDefer1 = utils.defer<IDownloadKeyResult>();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenCalledWith(["@test1:sw1v.org"], {});
queryDefer1.resolve(utils.deepCopy(signedDeviceList));
return prom1.then(() => {
const storedKeys = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
const storedKeys = dl.getRawStoredDevicesForUser("@test1:sw1v.org");
expect(Object.keys(storedKeys)).toEqual(["HGKAWHRVJQ"]);
dl.stop();
});
});
it("should have an outdated devicelist on an invalidation while an " +
"update is in progress", function() {
it("should have an outdated devicelist on an invalidation while an " + "update is in progress", function () {
const dl = createTestDeviceList();
dl.startTrackingDeviceList('@test1:sw1v.org');
dl.startTrackingDeviceList("@test1:sw1v.org");
const queryDefer1 = utils.defer<IDownloadKeyResult>();
downloadSpy.mockReturnValue(queryDefer1.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenCalledWith(["@test1:sw1v.org"], {});
downloadSpy.mockReset();
// outdated notif arrives while the request is in flight.
const queryDefer2 = utils.defer();
downloadSpy.mockReturnValue(queryDefer2.promise);
dl.invalidateUserDeviceList('@test1:sw1v.org');
dl.invalidateUserDeviceList("@test1:sw1v.org");
dl.refreshOutdatedDeviceLists();
dl.saveIfDirty().then(() => {
dl.saveIfDirty()
.then(() => {
// the first request completes
queryDefer1.resolve({
failures: {},
device_keys: {
'@test1:sw1v.org': {},
"@test1:sw1v.org": {},
},
});
return prom1;
}).then(() => {
})
.then(() => {
// uh-oh; user restarts before second request completes. The new instance
// should know we never got a complete device list.
logger.log("Creating new devicelist to simulate app reload");
@ -178,16 +169,17 @@ describe('DeviceList', function() {
downloadSpy.mockReturnValue(queryDefer3.promise);
const prom3 = dl2.refreshOutdatedDeviceLists();
expect(downloadSpy).toHaveBeenCalledWith(['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenCalledWith(["@test1:sw1v.org"], {});
dl2.stop();
queryDefer3.resolve(utils.deepCopy(signedDeviceList));
// allow promise chain to complete
return prom3;
}).then(() => {
const storedKeys = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
})
.then(() => {
const storedKeys = dl.getRawStoredDevicesForUser("@test1:sw1v.org");
expect(Object.keys(storedKeys)).toEqual(["HGKAWHRVJQ"]);
dl.stop();
});
});
@ -195,8 +187,8 @@ describe('DeviceList', function() {
it("should download device keys in batches", function () {
const dl = createTestDeviceList(1);
dl.startTrackingDeviceList('@test1:sw1v.org');
dl.startTrackingDeviceList('@test2:sw1v.org');
dl.startTrackingDeviceList("@test1:sw1v.org");
dl.startTrackingDeviceList("@test2:sw1v.org");
const queryDefer1 = utils.defer<IDownloadKeyResult>();
downloadSpy.mockReturnValueOnce(queryDefer1.promise);
@ -205,16 +197,16 @@ describe('DeviceList', function() {
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toBeCalledTimes(2);
expect(downloadSpy).toHaveBeenNthCalledWith(1, ['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenNthCalledWith(2, ['@test2:sw1v.org'], {});
expect(downloadSpy).toHaveBeenNthCalledWith(1, ["@test1:sw1v.org"], {});
expect(downloadSpy).toHaveBeenNthCalledWith(2, ["@test2:sw1v.org"], {});
queryDefer1.resolve(utils.deepCopy(signedDeviceList));
queryDefer2.resolve(utils.deepCopy(signedDeviceList2));
return prom1.then(() => {
const storedKeys1 = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys1)).toEqual(['HGKAWHRVJQ']);
const storedKeys2 = dl.getRawStoredDevicesForUser('@test2:sw1v.org');
expect(Object.keys(storedKeys2)).toEqual(['QJVRHWAKGH']);
const storedKeys1 = dl.getRawStoredDevicesForUser("@test1:sw1v.org");
expect(Object.keys(storedKeys1)).toEqual(["HGKAWHRVJQ"]);
const storedKeys2 = dl.getRawStoredDevicesForUser("@test2:sw1v.org");
expect(Object.keys(storedKeys2)).toEqual(["QJVRHWAKGH"]);
dl.stop();
});
});

View File

@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked, MockedObject } from 'jest-mock';
import { mocked, MockedObject } from "jest-mock";
import '../../../olm-loader';
import "../../../olm-loader";
import type { OutboundGroupSession } from "@matrix-org/olm";
import * as algorithms from "../../../../src/crypto/algorithms";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
@ -28,22 +28,22 @@ import { MatrixEvent } from "../../../../src/models/event";
import { TestClient } from "../../../TestClient";
import { Room } from "../../../../src/models/room";
import * as olmlib from "../../../../src/crypto/olmlib";
import { TypedEventEmitter } from '../../../../src/models/typed-event-emitter';
import { ClientEvent, MatrixClient, RoomMember } from '../../../../src';
import { DeviceInfo, IDevice } from '../../../../src/crypto/deviceinfo';
import { DeviceTrustLevel } from '../../../../src/crypto/CrossSigning';
import { TypedEventEmitter } from "../../../../src/models/typed-event-emitter";
import { ClientEvent, MatrixClient, RoomMember } from "../../../../src";
import { DeviceInfo, IDevice } from "../../../../src/crypto/deviceinfo";
import { DeviceTrustLevel } from "../../../../src/crypto/CrossSigning";
import { MegolmEncryption as MegolmEncryptionClass } from "../../../../src/crypto/algorithms/megolm";
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get("m.megolm.v1.aes-sha2")!;
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES.get("m.megolm.v1.aes-sha2")!;
const ROOM_ID = '!ROOM:ID';
const ROOM_ID = "!ROOM:ID";
const Olm = global.Olm;
describe("MegolmDecryption", function () {
if (!global.Olm) {
logger.warn('Not running megolm unit tests: libolm not present');
logger.warn("Not running megolm unit tests: libolm not present");
return;
}
@ -57,7 +57,7 @@ describe("MegolmDecryption", function() {
let mockBaseApis: MockedObject<MatrixClient>;
beforeEach(async function () {
mockCrypto = testUtils.mock(Crypto, 'Crypto') as MockedObject<Crypto>;
mockCrypto = testUtils.mock(Crypto, "Crypto") as MockedObject<Crypto>;
mockBaseApis = {
claimOneTimeKeys: jest.fn(),
sendToDevice: jest.fn(),
@ -69,7 +69,7 @@ describe("MegolmDecryption", function() {
const olmDevice = new OlmDevice(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
@ -88,7 +88,7 @@ describe("MegolmDecryption", function() {
jest.clearAllMocks();
});
describe('receives some keys:', function() {
describe("receives some keys:", function () {
let groupSession: OutboundGroupSession;
beforeEach(async function () {
groupSession = new global.Olm.OutboundGroupSession();
@ -97,13 +97,13 @@ describe("MegolmDecryption", function() {
// construct a fake decrypted key event via the use of a mocked
// 'crypto' implementation.
const event = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
});
const decryptedData = {
clearEvent: {
type: 'm.room_key',
type: "m.room_key",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
room_id: ROOM_ID,
session_id: groupSession.session_id(),
session_key: groupSession.session_key(),
@ -130,43 +130,45 @@ describe("MegolmDecryption", function() {
});
});
it('can decrypt an event', function() {
it("can decrypt an event", function () {
const event = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: groupSession.session_id(),
ciphertext: groupSession.encrypt(JSON.stringify({
ciphertext: groupSession.encrypt(
JSON.stringify({
room_id: ROOM_ID,
content: 'testytest',
})),
content: "testytest",
}),
),
},
});
return megolmDecryption.decryptEvent(event).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
expect(res.clearEvent.content).toEqual("testytest");
});
});
it('can respond to a key request event', function() {
it("can respond to a key request event", function () {
const keyRequest: IncomingRoomKeyRequest = {
requestId: '123',
requestId: "123",
share: jest.fn(),
userId: '@alice:foo',
deviceId: 'alidevice',
userId: "@alice:foo",
deviceId: "alidevice",
requestBody: {
algorithm: '',
algorithm: "",
room_id: ROOM_ID,
sender_key: "SENDER_CURVE25519",
session_id: groupSession.session_id(),
},
};
return megolmDecryption.hasKeysForKeyRequest(
keyRequest,
).then((hasKeys) => {
return megolmDecryption
.hasKeysForKeyRequest(keyRequest)
.then((hasKeys) => {
expect(hasKeys).toBe(true);
// set up some pre-conditions for the share call
@ -174,10 +176,12 @@ describe("MegolmDecryption", function() {
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
'@alice:foo': { 'alidevice': {
sessionId: 'alisession',
device: new DeviceInfo('alidevice'),
} },
"@alice:foo": {
alidevice: {
sessionId: "alisession",
device: new DeviceInfo("alidevice"),
},
},
});
const awaitEncryptForDevice = new Promise<void>((res, rej) => {
@ -195,7 +199,8 @@ describe("MegolmDecryption", function() {
// it's asynchronous, so we have to wait a bit
return awaitEncryptForDevice;
}).then(() => {
})
.then(() => {
// check that it called encryptMessageForDevice with
// appropriate args.
expect(mockOlmLib.encryptMessageForDevice).toBeCalledTimes(1);
@ -221,15 +226,17 @@ describe("MegolmDecryption", function() {
// message index) triple should result in an exception being thrown
// as it should be detected as a replay attack.
const sessionId = groupSession.session_id();
const cipherText = groupSession.encrypt(JSON.stringify({
const cipherText = groupSession.encrypt(
JSON.stringify({
room_id: ROOM_ID,
content: 'testytest',
}));
content: "testytest",
}),
);
const event1 = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
@ -240,17 +247,17 @@ describe("MegolmDecryption", function() {
const successHandler = jest.fn();
const failureHandler = jest.fn((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
expect(err.toString()).toMatch(/Duplicate message index, possible replay attack/);
});
return megolmDecryption.decryptEvent(event1).then((res) => {
return megolmDecryption
.decryptEvent(event1)
.then((res) => {
const event2 = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
@ -260,10 +267,9 @@ describe("MegolmDecryption", function() {
});
return megolmDecryption.decryptEvent(event2);
}).then(
successHandler,
failureHandler,
).then(() => {
})
.then(successHandler, failureHandler)
.then(() => {
expect(successHandler).not.toHaveBeenCalled();
expect(failureHandler).toHaveBeenCalled();
});
@ -274,15 +280,17 @@ describe("MegolmDecryption", function() {
// timestamp are the same, then it should not be considered a
// replay attack
const sessionId = groupSession.session_id();
const cipherText = groupSession.encrypt(JSON.stringify({
const cipherText = groupSession.encrypt(
JSON.stringify({
room_id: ROOM_ID,
content: 'testytest',
}));
content: "testytest",
}),
);
const event = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
@ -319,13 +327,13 @@ describe("MegolmDecryption", function() {
mockBaseApis.claimOneTimeKeys.mockResolvedValue({
failures: {},
one_time_keys: {
'@alice:home.server': {
"@alice:home.server": {
aliceDevice: {
'signed_curve25519:flooble': {
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
"signed_curve25519:flooble": {
key: "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI",
signatures: {
'@alice:home.server': {
'ed25519:aliceDevice': 'totally valid',
"@alice:home.server": {
"ed25519:aliceDevice": "totally valid",
},
},
},
@ -337,34 +345,34 @@ describe("MegolmDecryption", function() {
mockBaseApis.queueToDevice.mockResolvedValue(undefined);
aliceDeviceInfo = {
deviceId: 'aliceDevice',
deviceId: "aliceDevice",
isBlocked: jest.fn().mockReturnValue(false),
isUnverified: jest.fn().mockReturnValue(false),
getIdentityKey: jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
),
getFingerprint: jest.fn().mockReturnValue(''),
getIdentityKey: jest.fn().mockReturnValue("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE"),
getFingerprint: jest.fn().mockReturnValue(""),
} as unknown as DeviceInfo;
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
'@alice:home.server': {
mockCrypto.downloadKeys.mockReturnValue(
Promise.resolve({
"@alice:home.server": {
aliceDevice: aliceDeviceInfo,
},
}));
}),
);
mockCrypto.checkDeviceTrust.mockReturnValue({
isVerified: () => false,
} as DeviceTrustLevel);
megolmEncryption = new MegolmEncryption({
userId: '@user:id',
deviceId: '12345',
userId: "@user:id",
deviceId: "12345",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
roomId: ROOM_ID,
config: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
rotation_period_ms: rotationPeriodMs,
},
}) as MegolmEncryptionClass;
@ -379,9 +387,7 @@ describe("MegolmDecryption", function() {
mockCrypto.baseApis = mockBaseApis;
mockRoom = {
getEncryptionTargetMembers: jest.fn().mockReturnValue(
[{ userId: "@alice:home.server" }],
),
getEncryptionTargetMembers: jest.fn().mockReturnValue([{ userId: "@alice:home.server" }]),
getBlacklistUnverifiedDevices: jest.fn().mockReturnValue(false),
} as unknown as Room;
});
@ -394,7 +400,9 @@ describe("MegolmDecryption", function() {
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 10000,
[["@alice:home.server", "aliceDevice"]],
"signed_curve25519",
10000,
);
});
@ -422,14 +430,16 @@ describe("MegolmDecryption", function() {
// this should have claimed a key for alice as it's starting a new session
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
['@alice:home.server'], false,
[["@alice:home.server", "aliceDevice"]],
"signed_curve25519",
2000,
);
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(["@alice:home.server"], false);
expect(mockBaseApis.queueToDevice).toHaveBeenCalled();
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalledWith(
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519', 2000,
[["@alice:home.server", "aliceDevice"]],
"signed_curve25519",
2000,
);
mockBaseApis.claimOneTimeKeys.mockReset();
@ -454,7 +464,7 @@ describe("MegolmDecryption", function() {
await megolmEncryption.reshareKeyWithDevice!(
olmDevice.deviceCurve25519Key!,
ct1.session_id,
'@alice:home.server',
"@alice:home.server",
aliceDeviceInfo,
);
@ -466,15 +476,15 @@ describe("MegolmDecryption", function() {
body: "Some text",
});
aliceDeviceInfo.getIdentityKey = jest.fn().mockReturnValue(
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWI',
);
aliceDeviceInfo.getIdentityKey = jest
.fn()
.mockReturnValue("YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWI");
mockBaseApis.queueToDevice.mockClear();
await megolmEncryption.reshareKeyWithDevice!(
olmDevice.deviceCurve25519Key!,
ct1.session_id,
'@alice:home.server',
"@alice:home.server",
aliceDeviceInfo,
);
@ -484,26 +494,16 @@ describe("MegolmDecryption", function() {
});
it("notifies devices that have been blocked", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient1 = (new TestClient(
"@bob:example.com", "bobdevice1",
)).client;
const bobClient2 = (new TestClient(
"@bob:example.com", "bobdevice2",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient1.initCrypto(),
bobClient2.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient1 = new TestClient("@bob:example.com", "bobdevice1").client;
const bobClient2 = new TestClient("@bob:example.com", "bobdevice2").client;
await Promise.all([aliceClient.initCrypto(), bobClient1.initCrypto(), bobClient2.initCrypto()]);
const aliceDevice = aliceClient.crypto!.olmDevice;
const bobDevice1 = bobClient1.crypto!.olmDevice;
const bobDevice2 = bobClient2.crypto!.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -537,9 +537,7 @@ describe("MegolmDecryption", function() {
},
};
aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto!.deviceList.storeDevicesForUser("@bob:example.com", BOB_DEVICES);
aliceClient.crypto!.deviceList.downloadKeys = async function (userIds) {
// @ts-ignore short-circuiting private method
return this.getDevicesFromStore(userIds);
@ -567,20 +565,19 @@ describe("MegolmDecryption", function() {
delete contentMap["@bob:example.com"].bobdevice2.session_id;
delete contentMap["@bob:example.com"].bobdevice2["org.matrix.msgid"];
expect(contentMap).toStrictEqual({
'@bob:example.com': {
"@bob:example.com": {
bobdevice1: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.unverified',
reason:
'The sender has disabled encrypting to unverified devices.',
code: "m.unverified",
reason: "The sender has disabled encrypting to unverified devices.",
sender_key: aliceDevice.deviceCurve25519Key,
},
bobdevice2: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.blacklisted',
reason: 'The sender has blocked you.',
code: "m.blacklisted",
reason: "The sender has blocked you.",
sender_key: aliceDevice.deviceCurve25519Key,
},
},
@ -592,20 +589,13 @@ describe("MegolmDecryption", function() {
});
it("does not block unverified devices when sending verification events", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient = new TestClient("@bob:example.com", "bobdevice").client;
await Promise.all([aliceClient.initCrypto(), bobClient.initCrypto()]);
const bobDevice = bobClient.crypto!.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -630,9 +620,7 @@ describe("MegolmDecryption", function() {
},
};
aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto!.deviceList.storeDevicesForUser("@bob:example.com", BOB_DEVICES);
aliceClient.crypto!.deviceList.downloadKeys = async function (userIds) {
// @ts-ignore private
return this.getDevicesFromStore(userIds);
@ -640,7 +628,7 @@ describe("MegolmDecryption", function() {
await bobDevice.generateOneTimeKeys(1);
const oneTimeKeys = await bobDevice.getOneTimeKeys();
const signedOneTimeKeys: Record<string, { key: string, signatures: object }> = {};
const signedOneTimeKeys: Record<string, { key: string; signatures: object }> = {};
for (const keyId in oneTimeKeys.curve25519) {
if (oneTimeKeys.curve25519.hasOwnProperty(keyId)) {
const k = {
@ -655,7 +643,7 @@ describe("MegolmDecryption", function() {
aliceClient.claimOneTimeKeys = jest.fn().mockResolvedValue({
one_time_keys: {
'@bob:example.com': {
"@bob:example.com": {
bobdevice: signedOneTimeKeys,
},
},
@ -686,21 +674,14 @@ describe("MegolmDecryption", function() {
});
it("notifies devices when unable to create olm session", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient = new TestClient("@bob:example.com", "bobdevice").client;
await Promise.all([aliceClient.initCrypto(), bobClient.initCrypto()]);
const aliceDevice = aliceClient.crypto!.olmDevice;
const bobDevice = bobClient.crypto!.olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const aliceRoom = new Room(roomId, aliceClient, "@alice:example.com", {});
@ -734,9 +715,7 @@ describe("MegolmDecryption", function() {
},
};
aliceClient.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient.crypto!.deviceList.storeDevicesForUser("@bob:example.com", BOB_DEVICES);
aliceClient.crypto!.deviceList.downloadKeys = async function (userIds) {
// @ts-ignore private
return this.getDevicesFromStore(userIds);
@ -764,11 +743,11 @@ describe("MegolmDecryption", function() {
expect(msgtype).toMatch(/^(org.matrix|m).room_key.withheld$/);
delete contentMap["@bob:example.com"]["bobdevice"]["org.matrix.msgid"];
expect(contentMap).toStrictEqual({
'@bob:example.com': {
"@bob:example.com": {
bobdevice: {
algorithm: "m.megolm.v1.aes-sha2",
code: 'm.no_olm',
reason: 'Unable to establish a secure channel.',
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
sender_key: aliceDevice.deviceCurve25519Key,
},
},
@ -779,16 +758,9 @@ describe("MegolmDecryption", function() {
});
it("throws an error describing why it doesn't have a key", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient = new TestClient("@bob:example.com", "bobdevice").client;
await Promise.all([aliceClient.initCrypto(), bobClient.initCrypto()]);
const bobDevice = bobClient.crypto!.olmDevice;
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
@ -796,7 +768,9 @@ describe("MegolmDecryption", function() {
const roomId = "!someroom";
aliceEventEmitter.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({
aliceEventEmitter.emit(
ClientEvent.ToDeviceEvent,
new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
@ -807,9 +781,12 @@ describe("MegolmDecryption", function() {
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
}),
);
await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
await expect(
aliceClient.crypto!.decryptEvent(
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@ -821,9 +798,13 @@ describe("MegolmDecryption", function() {
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id1",
},
}))).rejects.toThrow("The sender has blocked you.");
}),
),
).rejects.toThrow("The sender has blocked you.");
aliceEventEmitter.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({
aliceEventEmitter.emit(
ClientEvent.ToDeviceEvent,
new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
@ -834,9 +815,12 @@ describe("MegolmDecryption", function() {
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
}),
);
await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
await expect(
aliceClient.crypto!.decryptEvent(
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@ -848,22 +832,17 @@ describe("MegolmDecryption", function() {
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id2",
},
}))).rejects.toThrow("The sender has blocked you.");
}),
),
).rejects.toThrow("The sender has blocked you.");
aliceClient.stopClient();
bobClient.stopClient();
});
it("throws an error describing the lack of an olm session", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient = new TestClient("@bob:example.com", "bobdevice").client;
await Promise.all([aliceClient.initCrypto(), bobClient.initCrypto()]);
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
aliceClient.crypto!.registerEventHandlers(aliceEventEmitter);
@ -875,7 +854,9 @@ describe("MegolmDecryption", function() {
const now = Date.now();
aliceEventEmitter.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({
aliceEventEmitter.emit(
ClientEvent.ToDeviceEvent,
new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
@ -886,13 +867,16 @@ describe("MegolmDecryption", function() {
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
},
}));
}),
);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
await expect(
aliceClient.crypto!.decryptEvent(
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@ -905,9 +889,13 @@ describe("MegolmDecryption", function() {
session_id: "session_id1",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
}),
),
).rejects.toThrow("The sender was unable to establish a secure channel.");
aliceEventEmitter.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({
aliceEventEmitter.emit(
ClientEvent.ToDeviceEvent,
new MatrixEvent({
type: "m.room_key.withheld",
sender: "@bob:example.com",
content: {
@ -918,13 +906,16 @@ describe("MegolmDecryption", function() {
code: "m.no_olm",
reason: "Unable to establish a secure channel.",
},
}));
}),
);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
await expect(
aliceClient.crypto!.decryptEvent(
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@ -937,22 +928,17 @@ describe("MegolmDecryption", function() {
session_id: "session_id2",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
}),
),
).rejects.toThrow("The sender was unable to establish a secure channel.");
aliceClient.stopClient();
bobClient.stopClient();
});
it("throws an error to indicate a wedged olm session", async function () {
const aliceClient = (new TestClient(
"@alice:example.com", "alicedevice",
)).client;
const bobClient = (new TestClient(
"@bob:example.com", "bobdevice",
)).client;
await Promise.all([
aliceClient.initCrypto(),
bobClient.initCrypto(),
]);
const aliceClient = new TestClient("@alice:example.com", "alicedevice").client;
const bobClient = new TestClient("@bob:example.com", "bobdevice").client;
await Promise.all([aliceClient.initCrypto(), bobClient.initCrypto()]);
const aliceEventEmitter = new TypedEventEmitter<ClientEvent.ToDeviceEvent, any>();
aliceClient.crypto!.registerEventHandlers(aliceEventEmitter);
@ -964,7 +950,9 @@ describe("MegolmDecryption", function() {
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceEventEmitter.emit(ClientEvent.ToDeviceEvent, new MatrixEvent({
aliceEventEmitter.emit(
ClientEvent.ToDeviceEvent,
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
@ -973,13 +961,16 @@ describe("MegolmDecryption", function() {
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
},
}));
}),
);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient.crypto!.decryptEvent(new MatrixEvent({
await expect(
aliceClient.crypto!.decryptEvent(
new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
@ -992,7 +983,9 @@ describe("MegolmDecryption", function() {
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
}),
),
).rejects.toThrow("The secure channel with the sender was corrupted.");
aliceClient.stopClient();
bobClient.stopClient();
});

View File

@ -15,15 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MockedObject } from 'jest-mock';
import { MockedObject } from "jest-mock";
import '../../../olm-loader';
import "../../../olm-loader";
import { MemoryCryptoStore } from "../../../../src/crypto/store/memory-crypto-store";
import { logger } from "../../../../src/logger";
import { OlmDevice } from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import { DeviceInfo } from "../../../../src/crypto/deviceinfo";
import { MatrixClient } from '../../../../src';
import { MatrixClient } from "../../../../src";
function makeOlmDevice() {
const cryptoStore = new MemoryCryptoStore();
@ -34,7 +34,7 @@ function makeOlmDevice() {
async function setupSession(initiator: OlmDevice, opponent: OlmDevice) {
await opponent.generateOneTimeKeys(1);
const keys = await opponent.getOneTimeKeys();
const firstKey = Object.values(keys['curve25519'])[0];
const firstKey = Object.values(keys["curve25519"])[0];
const sid = await initiator.createOutboundSession(opponent.deviceCurve25519Key!, firstKey);
return sid;
@ -48,7 +48,7 @@ function alwaysSucceed<T>(promise: Promise<T>): Promise<T | void> {
describe("OlmDevice", function () {
if (!global.Olm) {
logger.warn('Not running megolm unit tests: libolm not present');
logger.warn("Not running megolm unit tests: libolm not present");
return;
}
@ -66,42 +66,37 @@ describe("OlmDevice", function() {
await bobOlmDevice.init();
});
describe('olm', function() {
describe("olm", function () {
it("can decrypt messages", async function () {
const sid = await setupSession(aliceOlmDevice, bobOlmDevice);
const ciphertext = await aliceOlmDevice.encryptMessage(
const ciphertext = (await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key!,
sid,
"The olm or proteus is an aquatic salamander in the family Proteidae",
) as any; // OlmDevice.encryptMessage has incorrect return type
)) as any; // OlmDevice.encryptMessage has incorrect return type
const result = await bobOlmDevice.createInboundSession(
aliceOlmDevice.deviceCurve25519Key!,
ciphertext.type,
ciphertext.body,
);
expect(result.payload).toEqual(
"The olm or proteus is an aquatic salamander in the family Proteidae",
);
expect(result.payload).toEqual("The olm or proteus is an aquatic salamander in the family Proteidae");
});
it('exports picked account and olm sessions', async function() {
it("exports picked account and olm sessions", async function () {
const sessionId = await setupSession(aliceOlmDevice, bobOlmDevice);
const exported = await bobOlmDevice.export();
// At this moment only Alice (the “initiator” in setupSession) has a session
expect(exported.sessions).toEqual([]);
const MESSAGE = (
"The olm or proteus is an aquatic salamander"
+ " in the family Proteidae"
);
const ciphertext = await aliceOlmDevice.encryptMessage(
const MESSAGE = "The olm or proteus is an aquatic salamander" + " in the family Proteidae";
const ciphertext = (await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key!,
sessionId,
MESSAGE,
) as any; // OlmDevice.encryptMessage has incorrect return type
)) as any; // OlmDevice.encryptMessage has incorrect return type
const bobRecreatedOlmDevice = makeOlmDevice();
bobRecreatedOlmDevice.init({ fromExportedDevice: exported });
@ -117,15 +112,12 @@ describe("OlmDevice", function() {
// this time we expect Bob to have a session to export
expect(exportedAgain.sessions).toHaveLength(1);
const MESSAGE_2 = (
"In contrast to most amphibians,"
+ " the olm is entirely aquatic"
);
const ciphertext2 = await aliceOlmDevice.encryptMessage(
const MESSAGE_2 = "In contrast to most amphibians," + " the olm is entirely aquatic";
const ciphertext2 = (await aliceOlmDevice.encryptMessage(
bobOlmDevice.deviceCurve25519Key!,
sessionId,
MESSAGE_2,
) as any; // OlmDevice.encryptMessage has incorrect return type
)) as any; // OlmDevice.encryptMessage has incorrect return type
const bobRecreatedAgainOlmDevice = makeOlmDevice();
bobRecreatedAgainOlmDevice.init({ fromExportedDevice: exportedAgain });
@ -156,22 +148,21 @@ describe("OlmDevice", function() {
} as unknown as MockedObject<MatrixClient>;
const devicesByUser = {
"@bob:example.com": [
DeviceInfo.fromStorage({
DeviceInfo.fromStorage(
{
keys: {
"curve25519:ABCDEFG": "akey",
},
}, "ABCDEFG"),
},
"ABCDEFG",
),
],
};
// start two tasks that try to ensure that there's an olm session
const promises = Promise.all([
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUser,
)),
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUser,
)),
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(aliceOlmDevice, baseApis, devicesByUser)),
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(aliceOlmDevice, baseApis, devicesByUser)),
]);
await new Promise((resolve) => {
@ -207,54 +198,47 @@ describe("OlmDevice", function() {
},
} as unknown as MockedObject<MatrixClient>;
const deviceBobA = DeviceInfo.fromStorage({
const deviceBobA = DeviceInfo.fromStorage(
{
keys: {
"curve25519:BOB-A": "akey",
},
}, "BOB-A");
const deviceBobB = DeviceInfo.fromStorage({
},
"BOB-A",
);
const deviceBobB = DeviceInfo.fromStorage(
{
keys: {
"curve25519:BOB-B": "bkey",
},
}, "BOB-B");
},
"BOB-B",
);
// There's no required ordering of devices per user, so here we
// create two different orderings so that each task reserves a
// device the other task needs before continuing.
const devicesByUserAB = {
"@bob:example.com": [
deviceBobA,
deviceBobB,
],
"@bob:example.com": [deviceBobA, deviceBobB],
};
const devicesByUserBA = {
"@bob:example.com": [
deviceBobB,
deviceBobA,
],
"@bob:example.com": [deviceBobB, deviceBobA],
};
const task1 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserAB,
));
const task1 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(aliceOlmDevice, baseApis, devicesByUserAB));
// After a single tick through the first task, it should have
// claimed ownership of all devices to avoid deadlocking others.
expect(Object.keys(aliceOlmDevice.sessionsInProgress).length).toBe(2);
const task2 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
aliceOlmDevice, baseApis, devicesByUserBA,
));
const task2 = alwaysSucceed(olmlib.ensureOlmSessionsForDevices(aliceOlmDevice, baseApis, devicesByUserBA));
// The second task should not have changed the ownership count, as
// it's waiting on the first task.
expect(Object.keys(aliceOlmDevice.sessionsInProgress).length).toBe(2);
// Track the tasks, but don't await them yet.
const promises = Promise.all([
task1,
task2,
]);
const promises = Promise.all([task1, task2]);
await new Promise((resolve) => {
setTimeout(resolve, 200);

View File

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import "../../olm-loader";
import { logger } from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
import { MatrixClient } from "../../../src/client";
@ -28,30 +28,31 @@ import { Crypto } from "../../../src/crypto";
import { resetCrossSigningKeys } from "./crypto-utils";
import { BackupManager } from "../../../src/crypto/backup";
import { StubStore } from "../../../src/store/stub";
import { IndexedDBCryptoStore, MatrixScheduler } from '../../../src';
import { IndexedDBCryptoStore, MatrixScheduler } from "../../../src";
import { CryptoStore } from "../../../src/crypto/store/base";
import { MegolmDecryption as MegolmDecryptionClass } from "../../../src/crypto/algorithms/megolm";
import { IKeyBackupInfo } from "../../../src/crypto/keybackup";
const Olm = global.Olm;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get('m.megolm.v1.aes-sha2')!;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES.get("m.megolm.v1.aes-sha2")!;
const ROOM_ID = '!ROOM:ID';
const ROOM_ID = "!ROOM:ID";
const SESSION_ID = 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc';
const SESSION_ID = "o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc";
const ENCRYPTED_EVENT = new MatrixEvent({
type: 'm.room.encrypted',
room_id: '!ROOM:ID',
type: "m.room.encrypted",
room_id: "!ROOM:ID",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: 'SENDER_CURVE25519',
algorithm: "m.megolm.v1.aes-sha2",
sender_key: "SENDER_CURVE25519",
session_id: SESSION_ID,
ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N'
+ 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl'
+ 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs',
ciphertext:
"AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N" +
"CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl" +
"mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs",
},
event_id: '$event1',
event_id: "$event1",
origin_server_ts: 1507753886000,
});
@ -60,19 +61,20 @@ const CURVE25519_KEY_BACKUP_DATA = {
forwarded_count: 0,
is_verified: false,
session_data: {
ciphertext: '2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw'
+ '6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ'
+ 'Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9'
+ 'SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy'
+ 'Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF'
+ 'ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV'
+ '4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv'
+ 'C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe'
+ 'Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf'
+ 'QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy'
+ 'iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg',
mac: '5lxYBHQU80M',
ephemeral: '/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14',
ciphertext:
"2z2M7CZ+azAiTHN1oFzZ3smAFFt+LEOYY6h3QO3XXGdw" +
"6YpNn/gpHDO6I/rgj1zNd4FoTmzcQgvKdU8kN20u5BWRHxaHTZ" +
"Slne5RxE6vUdREsBgZePglBNyG0AogR/PVdcrv/v18Y6rLM5O9" +
"SELmwbV63uV9Kuu/misMxoqbuqEdG7uujyaEKtjlQsJ5MGPQOy" +
"Syw7XrnesSwF6XWRMxcPGRV0xZr3s9PI350Wve3EncjRgJ9IGF" +
"ru1bcptMqfXgPZkOyGvrphHoFfoK7nY3xMEHUiaTRfRIjq8HNV" +
"4o8QY1qmWGnxNBQgOlL8MZlykjg3ULmQ3DtFfQPj/YYGS3jzxv" +
"C+EBjaafmsg+52CTeK3Rswu72PX450BnSZ1i3If4xWAUKvjTpe" +
"Ug5aDLqttOv1pITolTJDw5W/SD+b5rjEKg1CFCHGEGE9wwV3Nf" +
"QHVCQL+dfpd7Or0poy4dqKMAi3g0o3Tg7edIF8d5rREmxaALPy" +
"iie8PHD8mj/5Y0GLqrac4CD6+Mop7eUTzVovprjg",
mac: "5lxYBHQU80M",
ephemeral: "/Bn0A4UMFwJaDDvh0aEk1XZj3k1IfgCxgFY9P9a0b14",
},
};
@ -81,23 +83,24 @@ const AES256_KEY_BACKUP_DATA = {
forwarded_count: 0,
is_verified: false,
session_data: {
iv: 'b3Jqqvm5S9QdmXrzssspLQ',
ciphertext: 'GOOASO3E9ThogkG0zMjEduGLM3u9jHZTkS7AvNNbNj3q1znwk4OlaVKXce'
+ '7ynofiiYIiS865VlOqrKEEXv96XzRyUpgn68e3WsicwYl96EtjIEh/iY003PG2Qd'
+ 'EluT899Ax7PydpUHxEktbWckMppYomUR5q8x1KI1SsOQIiJaIGThmIMPANRCFiK0'
+ 'WQj+q+dnhzx4lt9AFqU5bKov8qKnw2qGYP7/+6RmJ0Kpvs8tG6lrcNDEHtFc2r0r'
+ 'KKubDypo0Vc8EWSwsAHdKa36ewRavpreOuE8Z9RLfY0QIR1ecXrMqW0CdGFr7H3P'
+ 'vcjF8sjwvQAavzxEKT1WMGizSMLeKWo2mgZ5cKnwV5HGUAw596JQvKs9laG2U89K'
+ 'YrT0sH30vi62HKzcBLcDkWkUSNYPz7UiZ1MM0L380UA+1ZOXSOmtBA9xxzzbc8Xd'
+ 'fRimVgklGdxrxjzuNLYhL2BvVH4oPWonD9j0bvRwE6XkimdbGQA8HB7UmXXjE8WA'
+ 'RgaDHkfzoA3g3aeQ',
mac: 'uR988UYgGL99jrvLLPX3V1ows+UYbktTmMxPAo2kxnU',
iv: "b3Jqqvm5S9QdmXrzssspLQ",
ciphertext:
"GOOASO3E9ThogkG0zMjEduGLM3u9jHZTkS7AvNNbNj3q1znwk4OlaVKXce" +
"7ynofiiYIiS865VlOqrKEEXv96XzRyUpgn68e3WsicwYl96EtjIEh/iY003PG2Qd" +
"EluT899Ax7PydpUHxEktbWckMppYomUR5q8x1KI1SsOQIiJaIGThmIMPANRCFiK0" +
"WQj+q+dnhzx4lt9AFqU5bKov8qKnw2qGYP7/+6RmJ0Kpvs8tG6lrcNDEHtFc2r0r" +
"KKubDypo0Vc8EWSwsAHdKa36ewRavpreOuE8Z9RLfY0QIR1ecXrMqW0CdGFr7H3P" +
"vcjF8sjwvQAavzxEKT1WMGizSMLeKWo2mgZ5cKnwV5HGUAw596JQvKs9laG2U89K" +
"YrT0sH30vi62HKzcBLcDkWkUSNYPz7UiZ1MM0L380UA+1ZOXSOmtBA9xxzzbc8Xd" +
"fRimVgklGdxrxjzuNLYhL2BvVH4oPWonD9j0bvRwE6XkimdbGQA8HB7UmXXjE8WA" +
"RgaDHkfzoA3g3aeQ",
mac: "uR988UYgGL99jrvLLPX3V1ows+UYbktTmMxPAo2kxnU",
},
};
const CURVE25519_BACKUP_INFO = {
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
version: '1',
version: "1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
@ -105,7 +108,7 @@ const CURVE25519_BACKUP_INFO = {
const AES256_BACKUP_INFO: IKeyBackupInfo = {
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
version: '1',
version: "1",
auth_data: {} as IKeyBackupInfo["auth_data"],
};
@ -120,15 +123,13 @@ function saveCrossSigningKeys(k: Record<string, Uint8Array>) {
}
function makeTestScheduler(): MatrixScheduler {
return ([
"getQueueForEvent",
"queueEvent",
"removeEventFromQueue",
"setProcessFunction",
] as const).reduce((r, k) => {
return (["getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction"] as const).reduce(
(r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixScheduler);
},
{} as MatrixScheduler,
);
}
function makeTestClient(cryptoStore: CryptoStore) {
@ -155,7 +156,7 @@ function makeTestClient(cryptoStore: CryptoStore) {
describe("MegolmBackup", function () {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
logger.warn("Not running megolm backup unit tests: libolm not present");
return;
}
@ -169,7 +170,7 @@ describe("MegolmBackup", function() {
let cryptoStore: CryptoStore;
let megolmDecryption: MegolmDecryptionClass;
beforeEach(async function () {
mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto = testUtils.mock(Crypto, "Crypto");
// @ts-ignore making mock
mockCrypto.backupManager = testUtils.mock(BackupManager, "BackupManager");
mockCrypto.backupManager.backupInfo = CURVE25519_BACKUP_INFO;
@ -181,8 +182,7 @@ describe("MegolmBackup", function() {
// we stub out the olm encryption bits
mockOlmLib = {} as unknown as typeof olmlib;
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
mockOlmLib.encryptMessageForDevice =
jest.fn().mockResolvedValue(undefined);
mockOlmLib.encryptMessageForDevice = jest.fn().mockResolvedValue(undefined);
});
describe("backup", function () {
@ -192,7 +192,7 @@ describe("MegolmBackup", function() {
mockBaseApis = {} as unknown as MatrixClient;
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: mockBaseApis,
@ -206,23 +206,23 @@ describe("MegolmBackup", function() {
// ideally we would use lolex, but we have no oportunity
// to tick the clock between the first try and the retry.
const realSetTimeout = global.setTimeout;
jest.spyOn(global, 'setTimeout').mockImplementation(function(f, n) {
jest.spyOn(global, "setTimeout").mockImplementation(function (f, n) {
return realSetTimeout(f!, n! / 100);
});
});
afterEach(function () {
jest.spyOn(global, 'setTimeout').mockRestore();
jest.spyOn(global, "setTimeout").mockRestore();
});
it('automatically calls the key back up', function() {
it("automatically calls the key back up", function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
// construct a fake decrypted key event via the use of a mocked
// 'crypto' implementation.
const event = new MatrixEvent({
type: 'm.room.encrypted',
type: "m.room.encrypted",
});
event.getWireType = () => "m.room.encrypted";
event.getWireContent = () => {
@ -232,9 +232,9 @@ describe("MegolmBackup", function() {
};
const decryptedData = {
clearEvent: {
type: 'm.room_key',
type: "m.room_key",
content: {
algorithm: 'm.megolm.v1.aes-sha2',
algorithm: "m.megolm.v1.aes-sha2",
room_id: ROOM_ID,
session_id: groupSession.session_id(),
session_key: groupSession.session_key(),
@ -254,14 +254,17 @@ describe("MegolmBackup", function() {
backupGroupSession: jest.fn(),
};
return event.attemptDecryption(mockCrypto).then(() => {
return event
.attemptDecryption(mockCrypto)
.then(() => {
return megolmDecryption.onRoomKeyEvent(event);
}).then(() => {
})
.then(() => {
expect(mockCrypto.backupManager.backupGroupSession).toHaveBeenCalled();
});
});
it('sends backups to the server (Curve25519 version)', function() {
it("sends backups to the server (Curve25519 version)", function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
@ -270,7 +273,7 @@ describe("MegolmBackup", function() {
const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
@ -280,12 +283,10 @@ describe("MegolmBackup", function() {
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
return client
.initCrypto()
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
return cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_SESSIONS], (txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
@ -297,22 +298,21 @@ describe("MegolmBackup", function() {
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
txn,
);
});
})
.then(async () => {
await client.enableKeyBackup({
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
version: '1',
version: "1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
});
let numCalls = 0;
return new Promise<void>((resolve, reject) => {
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): any {
client.http.authedRequest = function (method, path, queryParams, data, opts): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
@ -322,7 +322,7 @@ describe("MegolmBackup", function() {
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams?.version).toBe('1');
expect(queryParams?.version).toBe("1");
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
@ -341,7 +341,7 @@ describe("MegolmBackup", function() {
});
});
it('sends backups to the server (AES-256 version)', function() {
it("sends backups to the server (AES-256 version)", function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
@ -350,7 +350,7 @@ describe("MegolmBackup", function() {
const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
@ -360,15 +360,13 @@ describe("MegolmBackup", function() {
// @ts-ignore private field access
megolmDecryption.olmlib = mockOlmLib;
return client.initCrypto()
return client
.initCrypto()
.then(() => {
return client.crypto!.storeSessionBackupPrivateKey(new Uint8Array(32));
})
.then(() => {
return cryptoStore.doTxn(
"readwrite",
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
return cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_SESSIONS], (txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
@ -380,13 +378,14 @@ describe("MegolmBackup", function() {
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
txn,
);
});
})
.then(async () => {
await client.enableKeyBackup({
algorithm: "org.matrix.msc3270.v1.aes-hmac-sha2",
version: '1',
version: "1",
auth_data: {
iv: "PsCAtR7gMc4xBd9YS3A9Ow",
mac: "ZSDsTFEZK7QzlauCLMleUcX96GQZZM7UNtk4sripSqQ",
@ -394,9 +393,7 @@ describe("MegolmBackup", function() {
});
let numCalls = 0;
return new Promise<void>((resolve, reject) => {
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): any {
client.http.authedRequest = function (method, path, queryParams, data, opts): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(1);
if (numCalls >= 2) {
@ -406,7 +403,7 @@ describe("MegolmBackup", function() {
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams?.version).toBe('1');
expect(queryParams?.version).toBe("1");
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
@ -425,7 +422,7 @@ describe("MegolmBackup", function() {
});
});
it('signs backups with the cross-signing master key', async function() {
it("signs backups with the cross-signing master key", async function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
@ -434,7 +431,7 @@ describe("MegolmBackup", function() {
const client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
@ -445,16 +442,18 @@ describe("MegolmBackup", function() {
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
client.uploadDeviceSigningKeys = async function(e) {return {};};
client.uploadKeySignatures = async function(e) {return { failures: {} };};
client.uploadDeviceSigningKeys = async function (e) {
return {};
};
client.uploadKeySignatures = async function (e) {
return { failures: {} };
};
await resetCrossSigningKeys(client);
let numCalls = 0;
await Promise.all([
new Promise<void>((resolve, reject) => {
let backupInfo: Record<string, any> | BodyInit | undefined;
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): any {
client.http.authedRequest = function (method, path, queryParams, data, opts): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls === 1) {
@ -463,7 +462,9 @@ describe("MegolmBackup", function() {
try {
// make sure auth_data is signed by the master key
olmlib.pkVerify(
(data as Record<string, any>).auth_data, client.getCrossSigningId()!, "@alice:bar",
(data as Record<string, any>).auth_data,
client.getCrossSigningId()!,
"@alice:bar",
);
} catch (e) {
reject(e);
@ -494,7 +495,7 @@ describe("MegolmBackup", function() {
client.stopClient();
});
it('retries when a backup fails', async function() {
it("retries when a backup fails", async function () {
const groupSession = new Olm.OutboundGroupSession();
groupSession.create();
const ibGroupSession = new Olm.InboundGroupSession();
@ -517,7 +518,7 @@ describe("MegolmBackup", function() {
client.uploadKeysRequest = jest.fn();
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
@ -528,10 +529,7 @@ describe("MegolmBackup", function() {
megolmDecryption.olmlib = mockOlmLib;
await client.initCrypto();
await cryptoStore.doTxn(
"readwrite",
[IndexedDBCryptoStore.STORE_SESSIONS],
(txn) => {
await cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_SESSIONS], (txn) => {
cryptoStore.addEndToEndInboundGroupSession(
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
groupSession.session_id(),
@ -543,12 +541,13 @@ describe("MegolmBackup", function() {
room_id: ROOM_ID,
session: ibGroupSession.pickle(olmDevice.pickleKey),
},
txn);
txn,
);
});
await client.enableKeyBackup({
algorithm: olmlib.MEGOLM_BACKUP_ALGORITHM,
version: '1',
version: "1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
},
@ -556,9 +555,7 @@ describe("MegolmBackup", function() {
let numCalls = 0;
await new Promise<void>((resolve, reject) => {
client.http.authedRequest = function(
method, path, queryParams, data, opts,
): any {
client.http.authedRequest = function (method, path, queryParams, data, opts): any {
++numCalls;
expect(numCalls).toBeLessThanOrEqual(2);
if (numCalls >= 3) {
@ -568,7 +565,7 @@ describe("MegolmBackup", function() {
}
expect(method).toBe("PUT");
expect(path).toBe("/room_keys/keys");
expect(queryParams?.version).toBe('1');
expect(queryParams?.version).toBe("1");
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toBeDefined();
expect((data as Record<string, any>).rooms[ROOM_ID].sessions).toHaveProperty(
groupSession.session_id(),
@ -577,9 +574,7 @@ describe("MegolmBackup", function() {
resolve();
return Promise.resolve({});
} else {
return Promise.reject(
new Error("this is an expected failure"),
);
return Promise.reject(new Error("this is an expected failure"));
}
};
return client.crypto!.backupManager.backupGroupSession(
@ -599,7 +594,7 @@ describe("MegolmBackup", function() {
client = makeTestClient(cryptoStore);
megolmDecryption = new MegolmDecryption({
userId: '@user:id',
userId: "@user:id",
crypto: mockCrypto,
olmDevice: olmDevice,
baseApis: client,
@ -616,41 +611,47 @@ describe("MegolmBackup", function() {
client.stopClient();
});
it('can restore from backup (Curve25519 version)', function() {
it("can restore from backup (Curve25519 version)", function () {
client.http.authedRequest = function () {
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
return client
.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
CURVE25519_BACKUP_INFO,
).then(() => {
)
.then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
})
.then((res) => {
expect(res.clearEvent.content).toEqual("testytest");
expect(res.untrusted).toBeTruthy(); // keys from Curve25519 backup are untrusted
});
});
it('can restore from backup (AES-256 version)', function() {
it("can restore from backup (AES-256 version)", function () {
client.http.authedRequest = function () {
return Promise.resolve<any>(AES256_KEY_BACKUP_DATA);
};
return client.restoreKeyBackupWithRecoveryKey(
return client
.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
AES256_BACKUP_INFO,
).then(() => {
)
.then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
})
.then((res) => {
expect(res.clearEvent.content).toEqual("testytest");
expect(res.untrusted).toBeFalsy(); // keys from AES backup are trusted
});
});
it('can restore backup by room (Curve25519 version)', function() {
it("can restore backup by room (Curve25519 version)", function () {
client.http.authedRequest = function () {
return Promise.resolve<any>({
rooms: {
@ -662,24 +663,29 @@ describe("MegolmBackup", function() {
},
});
};
return client.restoreKeyBackupWithRecoveryKey(
return client
.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
null!, null!, CURVE25519_BACKUP_INFO,
).then(() => {
null!,
null!,
CURVE25519_BACKUP_INFO,
)
.then(() => {
return megolmDecryption.decryptEvent(ENCRYPTED_EVENT);
}).then((res) => {
expect(res.clearEvent.content).toEqual('testytest');
})
.then((res) => {
expect(res.clearEvent.content).toEqual("testytest");
});
});
it('has working cache functions', async function() {
it("has working cache functions", async function () {
const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]);
await client.crypto!.storeSessionBackupPrivateKey(key);
const result = await client.crypto!.getSessionBackupPrivateKey();
expect(new Uint8Array(result!)).toEqual(key);
});
it('caches session backup keys as it encounters them', async function() {
it("caches session backup keys as it encounters them", async function () {
const cachedNull = await client.crypto!.getSessionBackupPrivateKey();
expect(cachedNull).toBeNull();
client.http.authedRequest = function () {
@ -706,12 +712,14 @@ describe("MegolmBackup", function() {
return Promise.resolve<any>(CURVE25519_KEY_BACKUP_DATA);
};
await expect(client.restoreKeyBackupWithRecoveryKey(
await expect(
client.restoreKeyBackupWithRecoveryKey(
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
ROOM_ID,
SESSION_ID,
BAD_BACKUP_INFO,
)).rejects.toThrow();
),
).rejects.toThrow();
});
});

View File

@ -15,18 +15,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import anotherjson from 'another-json';
import { PkSigning } from '@matrix-org/olm';
import "../../olm-loader";
import anotherjson from "another-json";
import { PkSigning } from "@matrix-org/olm";
import HttpBackend from "matrix-mock-request";
import * as olmlib from "../../../src/crypto/olmlib";
import { MatrixError } from '../../../src/http-api';
import { logger } from '../../../src/logger';
import { ICrossSigningKey, ICreateClientOpts, ISignedKey, MatrixClient } from '../../../src/client';
import { CryptoEvent, IBootstrapCrossSigningOpts } from '../../../src/crypto';
import { IDevice } from '../../../src/crypto/deviceinfo';
import { TestClient } from '../../TestClient';
import { MatrixError } from "../../../src/http-api";
import { logger } from "../../../src/logger";
import { ICrossSigningKey, ICreateClientOpts, ISignedKey, MatrixClient } from "../../../src/client";
import { CryptoEvent, IBootstrapCrossSigningOpts } from "../../../src/crypto";
import { IDevice } from "../../../src/crypto/deviceinfo";
import { TestClient } from "../../TestClient";
import { resetCrossSigningKeys } from "./crypto-utils";
const PUSH_RULES_RESPONSE: Response = {
@ -45,21 +45,19 @@ const filterResponse = function(userId: string): Response {
};
interface Response {
method: 'GET' | 'PUT' | 'POST' | 'DELETE';
method: "GET" | "PUT" | "POST" | "DELETE";
path: string;
data: object;
}
function setHttpResponses(httpBackend: HttpBackend, responses: Response[]) {
responses.forEach(response => {
httpBackend
.when(response.method, response.path)
.respond(200, response.data);
responses.forEach((response) => {
httpBackend.when(response.method, response.path).respond(200, response.data);
});
}
async function makeTestClient(
userInfo: { userId: string, deviceId: string},
userInfo: { userId: string; deviceId: string },
options: Partial<ICreateClientOpts> = {},
keys: Record<string, Uint8Array> = {},
) {
@ -72,11 +70,11 @@ async function makeTestClient(
}
options.cryptoCallbacks = Object.assign(
{}, { getCrossSigningKey, saveCrossSigningKeys }, options.cryptoCallbacks || {},
);
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
{},
{ getCrossSigningKey, saveCrossSigningKeys },
options.cryptoCallbacks || {},
);
const testClient = new TestClient(userInfo.userId, userInfo.deviceId, undefined, undefined, options);
const client = testClient.client;
await client.initCrypto();
@ -86,7 +84,7 @@ async function makeTestClient(
describe("Cross Signing", function () {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
logger.warn("Not running megolm backup unit tests: libolm not present");
return;
}
@ -95,13 +93,14 @@ describe("Cross Signing", function() {
});
it("should sign the master key with the device key", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = jest.fn().mockImplementation(async (auth, keys) => {
await olmlib.verifySignature(
alice.crypto!.olmDevice, keys.master_key, "@alice:example.com",
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
alice.crypto!.olmDevice,
keys.master_key,
"@alice:example.com",
"Osborne2",
alice.crypto!.olmDevice.deviceEd25519Key!,
);
});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -109,24 +108,22 @@ describe("Cross Signing", function() {
alice.getAccountDataFromServer = async <T>() => ({} as T);
// set Alice's cross-signing key
await alice.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => { await func({}); },
authUploadDeviceSigningKeys: async (func) => {
await func({});
},
});
expect(alice.uploadDeviceSigningKeys).toHaveBeenCalled();
alice.stopClient();
});
it("should abort bootstrap if device signing auth fails", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async (auth, keys) => {
const errorResponse = {
session: "sessionId",
flows: [
{
stages: [
"m.login.password",
],
stages: ["m.login.password"],
},
],
params: {},
@ -149,7 +146,7 @@ describe("Cross Signing", function() {
alice.uploadKeySignatures = async () => ({ failures: {} });
alice.setAccountData = async () => ({});
alice.getAccountDataFromServer = async <T extends { [k: string]: any }>(): Promise<T | null> => ({} as T);
const authUploadDeviceSigningKeys: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"] = async func => {
const authUploadDeviceSigningKeys: IBootstrapCrossSigningOpts["authUploadDeviceSigningKeys"] = async (func) => {
await func({});
};
@ -170,9 +167,7 @@ describe("Cross Signing", function() {
});
it("should upload a signature when a user is verified", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
// set Alice's cross-signing key
@ -206,16 +201,12 @@ describe("Cross Signing", function() {
it.skip("should get cross-signing keys from sync", async function () {
const masterKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82, 0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef, 0xae, 0xb1,
0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6, 0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66, 0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0, 0x17, 0xb5,
0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49, 0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const { client: alice, httpBackend } = await makeTestClient(
@ -223,8 +214,8 @@ describe("Cross Signing", function() {
{
cryptoCallbacks: {
// will be called to sign our own device
getCrossSigningKey: async type => {
if (type === 'master') {
getCrossSigningKey: async (type) => {
if (type === "master") {
return masterKey;
} else {
return selfSigningKey;
@ -248,11 +239,10 @@ describe("Cross Signing", function() {
try {
await olmlib.verifySignature(
alice.crypto!.olmDevice,
content["@alice:example.com"][
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
],
content["@alice:example.com"]["nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"],
"@alice:example.com",
"Osborne2", alice.crypto!.olmDevice.deviceEd25519Key!,
"Osborne2",
alice.crypto!.olmDevice.deviceEd25519Key!,
);
olmlib.pkVerify(
content["@alice:example.com"]["Osborne2"],
@ -267,8 +257,7 @@ describe("Cross Signing", function() {
});
// @ts-ignore private property
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"]
.Osborne2;
const deviceInfo = alice.crypto!.deviceList.devices["@alice:example.com"].Osborne2;
const aliceDevice = {
user_id: "@alice:example.com",
device_id: "Osborne2",
@ -276,12 +265,7 @@ describe("Cross Signing", function() {
algorithms: deviceInfo.algorithms,
};
await alice.crypto!.signObject(aliceDevice);
olmlib.pkSign(
aliceDevice as ISignedKey,
selfSigningKey as unknown as PkSigning,
"@alice:example.com",
'',
);
olmlib.pkSign(aliceDevice as ISignedKey, selfSigningKey as unknown as PkSigning, "@alice:example.com", "");
// feed sync result that includes master key, ssk, device key
const responses: Response[] = [
@ -303,10 +287,7 @@ describe("Cross Signing", function() {
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@alice:example.com",
"@bob:example.com",
],
changed: ["@alice:example.com", "@bob:example.com"],
},
},
},
@ -314,13 +295,13 @@ describe("Cross Signing", function() {
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
failures: {},
device_keys: {
"@alice:example.com": {
"Osborne2": aliceDevice,
Osborne2: aliceDevice,
},
},
"master_keys": {
master_keys: {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
@ -330,7 +311,7 @@ describe("Cross Signing", function() {
},
},
},
"self_signing_keys": {
self_signing_keys: {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["self-signing"],
@ -341,8 +322,8 @@ describe("Cross Signing", function() {
signatures: {
"@alice:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs"
+ "Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
"Wqx/HXR851KIi8/u/UX+fbAMtq9Uj8sr8FsOcqrLfVYa6lAmbXs" +
"Vhfy4AlZ3dnEtjgZx0U0QDrghEn2eYBeOCA",
},
},
},
@ -383,9 +364,7 @@ describe("Cross Signing", function() {
});
it("should use trust chain to determine device verification", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
// set Alice's cross-signing key
@ -488,10 +467,8 @@ describe("Cross Signing", function() {
await resetCrossSigningKeys(alice);
const selfSigningKey = new Uint8Array([
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66,
0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0,
0x17, 0xb5, 0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49,
0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
0x1e, 0xf4, 0x01, 0x6d, 0x4f, 0xa1, 0x73, 0x66, 0x6b, 0xf8, 0x93, 0xf5, 0xb0, 0x4d, 0x17, 0xc0, 0x17, 0xb5,
0xa5, 0xf6, 0x59, 0x11, 0x8b, 0x49, 0x34, 0xf2, 0x4b, 0x64, 0x9b, 0x52, 0xf8, 0x5f,
]);
const keyChangePromise = new Promise<void>((resolve, reject) => {
@ -535,22 +512,16 @@ describe("Cross Signing", function() {
verified: 0,
known: false,
};
olmlib.pkSign(
bobDevice,
selfSigningKey as unknown as PkSigning,
"@bob:example.com",
'',
);
olmlib.pkSign(bobDevice, selfSigningKey as unknown as PkSigning, "@bob:example.com", "");
const bobMaster: ICrossSigningKey = {
user_id: "@bob:example.com",
usage: ["master"],
keys: {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk": "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk",
},
};
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com", '');
olmlib.pkSign(bobMaster, aliceKeys.user_signing, "@alice:example.com", "");
// Alice downloads Bob's keys
// - device key
@ -576,9 +547,7 @@ describe("Cross Signing", function() {
data: {
next_batch: "abcdefg",
device_lists: {
changed: [
"@bob:example.com",
],
changed: ["@bob:example.com"],
},
},
},
@ -586,19 +555,19 @@ describe("Cross Signing", function() {
method: "POST",
path: "/keys/query",
data: {
"failures": {},
"device_keys": {
failures: {},
device_keys: {
"@alice:example.com": {
"Osborne2": aliceDevice,
Osborne2: aliceDevice,
},
"@bob:example.com": {
"Dynabook": bobDevice,
Dynabook: bobDevice,
},
},
"master_keys": {
master_keys: {
"@bob:example.com": bobMaster,
},
"self_signing_keys": {
self_signing_keys: {
"@bob:example.com": {
user_id: "@bob:example.com",
usage: ["self-signing"],
@ -609,8 +578,8 @@ describe("Cross Signing", function() {
signatures: {
"@bob:example.com": {
"ed25519:nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk":
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB"
+ "LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
"2KLiufImvEbfJuAFvsaZD+PsL8ELWl7N1u9yr/9hZvwRghBfQMB" +
"LAI86b1kDV9+Cq1lt85ykReeCEzmTEPY2BQ",
},
},
},
@ -647,9 +616,7 @@ describe("Cross Signing", function() {
});
it("should dis-trust an unsigned device", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
// set Alice's cross-signing key
@ -717,9 +684,7 @@ describe("Cross Signing", function() {
});
it("should dis-trust a user when their ssk changes", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
await resetCrossSigningKeys(alice);
@ -874,11 +839,8 @@ describe("Cross Signing", function() {
},
},
},
);
const { client: bob } = await makeTestClient(
{ userId: "@bob:example.com", deviceId: "Dynabook" },
);
const { client: bob } = await makeTestClient({ userId: "@bob:example.com", deviceId: "Dynabook" });
bob.uploadDeviceSigningKeys = async () => ({});
bob.uploadKeySignatures = async () => ({ failures: {} });
@ -895,10 +857,7 @@ describe("Cross Signing", function() {
known: true,
},
});
alice.crypto!.deviceList.storeCrossSigningForUser(
"@bob:example.com",
bob.crypto!.crossSigningInfo.toStorage(),
);
alice.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", bob.crypto!.crossSigningInfo.toStorage());
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -917,8 +876,9 @@ describe("Cross Signing", function() {
expect(bobTrust.isTofu()).toBeTruthy();
// "forget" that Bob is trusted
delete alice.crypto!.deviceList.crossSigningInfo["@bob:example.com"]
.keys.master.signatures!["@alice:example.com"];
delete alice.crypto!.deviceList.crossSigningInfo["@bob:example.com"].keys.master.signatures![
"@alice:example.com"
];
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
expect(bobTrust2.isCrossSigningVerified()).toBeFalsy();
@ -940,12 +900,8 @@ describe("Cross Signing", function() {
bob.stopClient();
});
it(
"should observe that our own device is cross-signed, even if this device doesn't trust the key",
async function() {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
it("should observe that our own device is cross-signed, even if this device doesn't trust the key", async function () {
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -987,7 +943,7 @@ describe("Cross Signing", function() {
});
// Alice has a second device that's cross-signed
const aliceDeviceId = 'Dynabook';
const aliceDeviceId = "Dynabook";
const aliceUnsignedDevice = {
user_id: "@alice:example.com",
device_id: aliceDeviceId,
@ -1006,25 +962,21 @@ describe("Cross Signing", function() {
"@alice:example.com": {
["ed25519:" + alicePubkey]: sig,
},
} };
},
};
alice.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
[aliceDeviceId]: aliceCrossSignedDevice,
});
// We don't trust the cross-signing keys yet...
expect(
alice.checkDeviceTrust("@alice:example.com", aliceDeviceId).isCrossSigningVerified(),
).toBeFalsy();
expect(alice.checkDeviceTrust("@alice:example.com", aliceDeviceId).isCrossSigningVerified()).toBeFalsy();
// ... but we do acknowledge that the device is signed by them
expect(alice.checkIfOwnDeviceCrossSigned(aliceDeviceId)).toBeTruthy();
alice.stopClient();
},
);
});
it("should observe that our own device isn't cross-signed", async function () {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -1084,9 +1036,7 @@ describe("Cross Signing", function() {
});
it("checkIfOwnDeviceCrossSigned should sanely handle unknown devices", async () => {
const { client: alice } = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const { client: alice } = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
alice.uploadDeviceSigningKeys = async () => ({});
alice.uploadKeySignatures = async () => ({ failures: {} });
@ -1160,10 +1110,8 @@ describe("userHasCrossSigningKeys", function() {
});
it("should download devices and return true if one is a cross-signing key", async () => {
httpBackend
.when("POST", "/keys/query")
.respond(200, {
"master_keys": {
httpBackend.when("POST", "/keys/query").respond(200, {
master_keys: {
"@alice:example.com": {
user_id: "@alice:example.com",
usage: ["master"],
@ -1178,20 +1126,22 @@ describe("userHasCrossSigningKeys", function() {
let result: boolean;
await Promise.all([
httpBackend.flush("/keys/query"),
aliceClient.userHasCrossSigningKeys().then((res) => {result = res;}),
aliceClient.userHasCrossSigningKeys().then((res) => {
result = res;
}),
]);
expect(result!).toBeTruthy();
});
it("should download devices and return false if there is no cross-signing key", async () => {
httpBackend
.when("POST", "/keys/query")
.respond(200, {});
httpBackend.when("POST", "/keys/query").respond(200, {});
let result: boolean;
await Promise.all([
httpBackend.flush("/keys/query"),
aliceClient.userHasCrossSigningKeys().then((res) => {result = res;}),
aliceClient.userHasCrossSigningKeys().then((res) => {
result = res;
}),
]);
expect(result!).toBeFalsy();
});

View File

@ -1,6 +1,6 @@
import { IRecoveryKey } from '../../../src/crypto/api';
import { CrossSigningLevel } from '../../../src/crypto/CrossSigning';
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
import { IRecoveryKey } from "../../../src/crypto/api";
import { CrossSigningLevel } from "../../../src/crypto/CrossSigning";
import { IndexedDBCryptoStore } from "../../../src/crypto/store/indexeddb-crypto-store";
import { MatrixClient } from "../../../src";
import { CryptoEvent } from "../../../src/crypto";
@ -17,13 +17,9 @@ export async function resetCrossSigningKeys(
await crypto.crossSigningInfo.resetKeys(level);
await crypto.signObject(crypto.crossSigningInfo.keys.master);
// write a copy locally so we know these are trusted keys
await crypto.cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
(txn) => {
crypto.cryptoStore.storeCrossSigningKeys(
txn, crypto.crossSigningInfo.keys);
},
);
await crypto.cryptoStore.doTxn("readwrite", [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
crypto.cryptoStore.storeCrossSigningKeys(txn, crypto.crossSigningInfo.keys);
});
} catch (e) {
// If anything failed here, revert the keys so we know to try again from the start
// next time.

View File

@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import { TestClient } from '../../TestClient';
import { logger } from '../../../src/logger';
import { DEHYDRATION_ALGORITHM } from '../../../src/crypto/dehydration';
import "../../olm-loader";
import { TestClient } from "../../TestClient";
import { logger } from "../../../src/logger";
import { DEHYDRATION_ALGORITHM } from "../../../src/crypto/dehydration";
const Olm = global.Olm;
describe("Dehydration", () => {
if (!global.Olm) {
logger.warn('Not running dehydration unit tests: libolm not present');
logger.warn("Not running dehydration unit tests: libolm not present");
return;
}
@ -33,14 +33,11 @@ describe("Dehydration", () => {
it("should rehydrate a dehydrated device", async () => {
const key = new Uint8Array([1, 2, 3]);
const alice = new TestClient(
"@alice:example.com", "Osborne2", undefined, undefined,
{
const alice = new TestClient("@alice:example.com", "Osborne2", undefined, undefined, {
cryptoCallbacks: {
getDehydrationKey: async t => key,
getDehydrationKey: async (t) => key,
},
},
);
});
const dehydratedDevice = new Olm.Account();
dehydratedDevice.create();
@ -56,25 +53,20 @@ describe("Dehydration", () => {
success: true,
});
expect((await Promise.all([
alice.client.rehydrateDevice(),
alice.httpBackend.flushAllExpected(),
]))[0])
.toEqual("ABCDEFG");
expect((await Promise.all([alice.client.rehydrateDevice(), alice.httpBackend.flushAllExpected()]))[0]).toEqual(
"ABCDEFG",
);
expect(alice.client.getDeviceId()).toEqual("ABCDEFG");
});
it("should dehydrate a device", async () => {
const key = new Uint8Array([1, 2, 3]);
const alice = new TestClient(
"@alice:example.com", "Osborne2", undefined, undefined,
{
const alice = new TestClient("@alice:example.com", "Osborne2", undefined, undefined, {
cryptoCallbacks: {
getDehydrationKey: async t => key,
getDehydrationKey: async (t) => key,
},
},
);
});
await alice.client.initCrypto();
@ -84,7 +76,8 @@ describe("Dehydration", () => {
let pickledAccount = "";
alice.httpBackend.when("PUT", "/dehydrated_device")
alice.httpBackend
.when("PUT", "/dehydrated_device")
.check((req) => {
expect(req.data.device_data).toMatchObject({
algorithm: DEHYDRATION_ALGORITHM,
@ -95,7 +88,8 @@ describe("Dehydration", () => {
.respond(200, {
device_id: "ABCDEFG",
});
alice.httpBackend.when("POST", "/keys/upload/ABCDEFG")
alice.httpBackend
.when("POST", "/keys/upload/ABCDEFG")
.check((req) => {
expect(req.data).toMatchObject({
"device_keys": expect.objectContaining({
@ -119,11 +113,12 @@ describe("Dehydration", () => {
.respond(200, {});
try {
const deviceId =
(await Promise.all([
const deviceId = (
await Promise.all([
alice.client.createDehydratedDevice(new Uint8Array(key), {}),
alice.httpBackend.flushAllExpected(),
]))[0];
])
)[0];
expect(deviceId).toEqual("ABCDEFG");
expect(deviceId).not.toEqual("");

View File

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { CryptoStore } from '../../../src/crypto/store/base';
import { IndexedDBCryptoStore } from '../../../src/crypto/store/indexeddb-crypto-store';
import { LocalStorageCryptoStore } from '../../../src/crypto/store/localStorage-crypto-store';
import { MemoryCryptoStore } from '../../../src/crypto/store/memory-crypto-store';
import { RoomKeyRequestState } from '../../../src/crypto/OutgoingRoomKeyRequestManager';
import { CryptoStore } from "../../../src/crypto/store/base";
import { IndexedDBCryptoStore } from "../../../src/crypto/store/indexeddb-crypto-store";
import { LocalStorageCryptoStore } from "../../../src/crypto/store/localStorage-crypto-store";
import { MemoryCryptoStore } from "../../../src/crypto/store/memory-crypto-store";
import { RoomKeyRequestState } from "../../../src/crypto/OutgoingRoomKeyRequestManager";
import 'fake-indexeddb/auto';
import 'jest-localstorage-mock';
import "fake-indexeddb/auto";
import "jest-localstorage-mock";
const requests = [
{
@ -46,15 +46,12 @@ const requests = [
requestId: "C",
requestBody: { session_id: "C", room_id: "C", sender_key: "B", algorithm: "m.megolm.v1.aes-sha2" },
state: RoomKeyRequestState.Unsent,
recipients: [
{ userId: "@becca:example.com", deviceId: "foobarbaz" },
],
recipients: [{ userId: "@becca:example.com", deviceId: "foobarbaz" }],
},
];
describe.each([
["IndexedDBCryptoStore",
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["IndexedDBCryptoStore", () => new IndexedDBCryptoStore(global.indexedDB, "tests")],
["LocalStorageCryptoStore", () => new LocalStorageCryptoStore(localStorage)],
["MemoryCryptoStore", () => new MemoryCryptoStore()],
])("Outgoing room key requests [%s]", function (name, dbFactory) {
@ -63,34 +60,29 @@ describe.each([
beforeAll(async () => {
store = dbFactory();
await store.startup();
await Promise.all(requests.map((request) =>
store.getOrAddOutgoingRoomKeyRequest(request),
));
await Promise.all(requests.map((request) => store.getOrAddOutgoingRoomKeyRequest(request)));
});
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state",
async () => {
const r = await
store.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
it("getAllOutgoingRoomKeyRequestsByState retrieves all entries in a given state", async () => {
const r = await store.getAllOutgoingRoomKeyRequestsByState(RoomKeyRequestState.Sent);
expect(r).toHaveLength(2);
requests.filter((e) => e.state === RoomKeyRequestState.Sent).forEach((e) => {
requests
.filter((e) => e.state === RoomKeyRequestState.Sent)
.forEach((e) => {
expect(r).toContainEqual(e);
});
});
it("getOutgoingRoomKeyRequestsByTarget retrieves all entries with a given target",
async () => {
const r = await store.getOutgoingRoomKeyRequestsByTarget(
"@becca:example.com", "foobarbaz", [RoomKeyRequestState.Sent],
);
it("getOutgoingRoomKeyRequestsByTarget retrieves all entries with a given target", async () => {
const r = await store.getOutgoingRoomKeyRequestsByTarget("@becca:example.com", "foobarbaz", [
RoomKeyRequestState.Sent,
]);
expect(r).toHaveLength(1);
expect(r[0]).toEqual(requests[0]);
});
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state",
async () => {
const r =
await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
test("getOutgoingRoomKeyRequestByState retrieves any entry in a given state", async () => {
const r = await store.getOutgoingRoomKeyRequestByState([RoomKeyRequestState.Sent]);
expect(r).not.toBeNull();
expect(r).not.toBeUndefined();
expect(r!.state).toEqual(RoomKeyRequestState.Sent);

View File

@ -14,26 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import "../../olm-loader";
import * as olmlib from "../../../src/crypto/olmlib";
import { IObject } from "../../../src/crypto/olmlib";
import { SECRET_STORAGE_ALGORITHM_V1_AES } from "../../../src/crypto/SecretStorage";
import { MatrixEvent } from "../../../src/models/event";
import { TestClient } from '../../TestClient';
import { makeTestClients } from './verification/util';
import { TestClient } from "../../TestClient";
import { makeTestClients } from "./verification/util";
import { encryptAES } from "../../../src/crypto/aes";
import { createSecretStorageKey, resetCrossSigningKeys } from "./crypto-utils";
import { logger } from '../../../src/logger';
import { ClientEvent, ICreateClientOpts, ICrossSigningKey, MatrixClient } from '../../../src/client';
import { ISecretStorageKeyInfo } from '../../../src/crypto/api';
import { DeviceInfo } from '../../../src/crypto/deviceinfo';
import { logger } from "../../../src/logger";
import { ClientEvent, ICreateClientOpts, ICrossSigningKey, MatrixClient } from "../../../src/client";
import { ISecretStorageKeyInfo } from "../../../src/crypto/api";
import { DeviceInfo } from "../../../src/crypto/deviceinfo";
import { ISignatures } from "../../../src/@types/signed";
import { ICurve25519AuthData } from "../../../src/crypto/keybackup";
async function makeTestClient(userInfo: { userId: string, deviceId: string}, options: Partial<ICreateClientOpts> = {}) {
const client = (new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined, options,
)).client;
async function makeTestClient(
userInfo: { userId: string; deviceId: string },
options: Partial<ICreateClientOpts> = {},
) {
const client = new TestClient(userInfo.userId, userInfo.deviceId, undefined, undefined, options).client;
// Make it seem as if we've synced and thus the store can be trusted to
// contain valid account data.
@ -44,18 +45,22 @@ async function makeTestClient(userInfo: { userId: string, deviceId: string}, opt
await client.initCrypto();
// No need to download keys for these tests
jest.spyOn(client.crypto!, 'downloadKeys').mockResolvedValue({});
jest.spyOn(client.crypto!, "downloadKeys").mockResolvedValue({});
return client;
}
// Wrapper around pkSign to return a signed object. pkSign returns the
// signature, rather than the signed object.
function sign<T extends IObject | ICurve25519AuthData>(obj: T, key: Uint8Array, userId: string): T & {
function sign<T extends IObject | ICurve25519AuthData>(
obj: T,
key: Uint8Array,
userId: string,
): T & {
signatures: ISignatures;
unsigned?: object;
} {
olmlib.pkSign(obj, key, userId, '');
olmlib.pkSign(obj, key, userId, "");
return obj as T & {
signatures: ISignatures;
unsigned?: object;
@ -64,7 +69,7 @@ function sign<T extends IObject | ICurve25519AuthData>(obj: T, key: Uint8Array,
describe("Secrets", function () {
if (!global.Olm) {
logger.warn('Not running megolm backup unit tests: libolm not present');
logger.warn("Not running megolm backup unit tests: libolm not present");
return;
}
@ -82,22 +87,22 @@ describe("Secrets", function() {
const signingkeyInfo = {
user_id: "@alice:example.com",
usage: ['master'],
usage: ["master"],
keys: {
['ed25519:' + signingPubKey]: signingPubKey,
["ed25519:" + signingPubKey]: signingPubKey,
},
};
const getKey = jest.fn().mockImplementation(async e => {
const getKey = jest.fn().mockImplementation(async (e) => {
expect(Object.keys(e.keys)).toEqual(["abc"]);
return ['abc', key];
return ["abc", key];
});
const alice = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: async t => signingKey,
getCrossSigningKey: async (t) => signingKey,
getSecretStorageKey: getKey,
},
},
@ -108,8 +113,7 @@ describe("Secrets", function() {
const secretStorage = alice.crypto!.secretStorage;
jest.spyOn(alice, 'setAccountData').mockImplementation(
async function(eventType, contents) {
jest.spyOn(alice, "setAccountData").mockImplementation(async function (eventType, contents) {
alice.store.storeAccountDataEvents([
new MatrixEvent({
type: eventType,
@ -122,7 +126,7 @@ describe("Secrets", function() {
const keyAccountData = {
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
};
await alice.crypto!.crossSigningInfo.signObject(keyAccountData, 'master');
await alice.crypto!.crossSigningInfo.signObject(keyAccountData, "master");
alice.store.storeAccountDataEvents([
new MatrixEvent({
@ -143,37 +147,31 @@ describe("Secrets", function() {
});
it("should throw if given a key that doesn't exist", async function () {
const alice = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
try {
await alice.storeSecret("foo", "bar", ["this secret does not exist"]);
// should be able to use expect(...).toThrow() but mocha still fails
// the test even when it throws for reasons I have no inclination to debug
expect(true).toBeFalsy();
} catch (e) {
}
} catch (e) {}
alice.stopClient();
});
it("should refuse to encrypt with zero keys", async function () {
const alice = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
try {
await alice.storeSecret("foo", "bar", []);
expect(true).toBeFalsy();
} catch (e) {
}
} catch (e) {}
alice.stopClient();
});
it("should encrypt with default key if keys is null", async function () {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn().mockImplementation(async e => {
const getKey = jest.fn().mockImplementation(async (e) => {
expect(Object.keys(e.keys)).toEqual([newKeyId]);
return [newKeyId, key];
});
@ -183,8 +181,8 @@ describe("Secrets", function() {
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: t => Promise.resolve(keys[t]),
saveCrossSigningKeys: k => keys = k,
getCrossSigningKey: (t) => Promise.resolve(keys[t]),
saveCrossSigningKeys: (k) => (keys = k),
getSecretStorageKey: getKey,
},
},
@ -200,29 +198,27 @@ describe("Secrets", function() {
};
resetCrossSigningKeys(alice);
const { keyId: newKeyId } = await alice.addSecretStorageKey(
SECRET_STORAGE_ALGORITHM_V1_AES, { pubkey: undefined, key: undefined },
);
const { keyId: newKeyId } = await alice.addSecretStorageKey(SECRET_STORAGE_ALGORITHM_V1_AES, {
pubkey: undefined,
key: undefined,
});
// we don't await on this because it waits for the event to come down the sync
// which won't happen in the test setup
alice.setDefaultSecretStorageKeyId(newKeyId);
await alice.storeSecret("foo", "bar");
const accountData = alice.getAccountData('foo');
const accountData = alice.getAccountData("foo");
expect(accountData!.getContent().encrypted).toBeTruthy();
alice.stopClient();
});
it("should refuse to encrypt if no keys given and no default key", async function () {
const alice = await makeTestClient(
{ userId: "@alice:example.com", deviceId: "Osborne2" },
);
const alice = await makeTestClient({ userId: "@alice:example.com", deviceId: "Osborne2" });
try {
await alice.storeSecret("foo", "bar");
expect(true).toBeFalsy();
} catch (e) {
}
} catch (e) {}
alice.stopClient();
});
@ -247,7 +243,7 @@ describe("Secrets", function() {
const secretStorage = osborne2.client.crypto!.secretStorage;
osborne2.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
"VAX": {
VAX: {
known: false,
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
@ -258,7 +254,7 @@ describe("Secrets", function() {
},
});
vax.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", {
"Osborne2": {
Osborne2: {
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
verified: 0,
known: false,
@ -291,28 +287,18 @@ describe("Secrets", function() {
describe("bootstrap", function () {
// keys used in some of the tests
const XSK = new Uint8Array(
olmlib.decodeBase64("3lo2YdJugHjfE+Or7KJ47NuKbhE7AAGLgQ/dc19913Q="),
);
const XSK = new Uint8Array(olmlib.decodeBase64("3lo2YdJugHjfE+Or7KJ47NuKbhE7AAGLgQ/dc19913Q="));
const XSPubKey = "DRb8pFVJyEJ9OWvXeUoM0jq/C2Wt+NxzBZVuk2nRb+0";
const USK = new Uint8Array(
olmlib.decodeBase64("lKWi3hJGUie5xxHgySoz8PHFnZv6wvNaud/p2shN9VU="),
);
const USK = new Uint8Array(olmlib.decodeBase64("lKWi3hJGUie5xxHgySoz8PHFnZv6wvNaud/p2shN9VU="));
const USPubKey = "CUpoiTtHiyXpUmd+3ohb7JVxAlUaOG1NYs9Jlx8soQU";
const SSK = new Uint8Array(
olmlib.decodeBase64("1R6JVlXX99UcfUZzKuCDGQgJTw8ur1/ofgPD8pp+96M="),
);
const SSK = new Uint8Array(olmlib.decodeBase64("1R6JVlXX99UcfUZzKuCDGQgJTw8ur1/ofgPD8pp+96M="));
const SSPubKey = "0DfNsRDzEvkCLA0gD3m7VAGJ5VClhjEsewI35xq873Q";
const SSSSKey = new Uint8Array(
olmlib.decodeBase64(
"XrmITOOdBhw6yY5Bh7trb/bgp1FRdIGyCUxxMP873R0=",
),
);
const SSSSKey = new Uint8Array(olmlib.decodeBase64("XrmITOOdBhw6yY5Bh7trb/bgp1FRdIGyCUxxMP873R0="));
it("bootstraps when no storage or cross-signing keys locally", async function () {
const key = new Uint8Array(16);
for (let i = 0; i < 16; i++) key[i] = i;
const getKey = jest.fn().mockImplementation(async e => {
const getKey = jest.fn().mockImplementation(async (e) => {
return [Object.keys(e.keys)[0], key];
});
@ -334,15 +320,15 @@ describe("Secrets", function() {
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.store.storeAccountDataEvents([event]);
this.emit(ClientEvent.AccountData, event);
return {};
};
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => { await func({}); },
authUploadDeviceSigningKeys: async (func) => {
await func({});
},
});
await bob.bootstrapSecretStorage({
createSecretStorageKey,
@ -352,8 +338,7 @@ describe("Secrets", function() {
const secretStorage = bob.crypto!.secretStorage;
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
bob.stopClient();
});
@ -370,7 +355,7 @@ describe("Secrets", function() {
},
{
cryptoCallbacks: {
getSecretStorageKey: async request => {
getSecretStorageKey: async (request) => {
const defaultKeyId = await bob.getDefaultSecretStorageKeyId();
expect(Object.keys(request.keys)).toEqual([defaultKeyId]);
return [defaultKeyId!, storagePrivateKey];
@ -386,9 +371,7 @@ describe("Secrets", function() {
type: eventType,
content: contents,
});
this.store.storeAccountDataEvents([
event,
]);
this.store.storeAccountDataEvents([event]);
this.emit(ClientEvent.AccountData, event);
return {};
};
@ -399,7 +382,7 @@ describe("Secrets", function() {
// Set up cross-signing keys from scratch with specific storage key
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => {
authUploadDeviceSigningKeys: async (func) => {
await func({});
},
});
@ -412,20 +395,16 @@ describe("Secrets", function() {
});
// Clear local cross-signing keys and read from secret storage
bob.crypto!.deviceList.storeCrossSigningForUser(
"@bob:example.com",
crossSigning.toStorage(),
);
bob.crypto!.deviceList.storeCrossSigningForUser("@bob:example.com", crossSigning.toStorage());
crossSigning.keys = {};
await bob.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async func => {
authUploadDeviceSigningKeys: async (func) => {
await func({});
},
});
expect(crossSigning.getId()).toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
.toBeTruthy();
expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy();
expect(await secretStorage.hasKey()).toBeTruthy();
bob.stopClient();
});
@ -443,8 +422,8 @@ describe("Secrets", function() {
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: async t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getCrossSigningKey: async (t) => crossSigningKeys[t],
saveCrossSigningKeys: (k) => (crossSigningKeys = k),
getSecretStorageKey: async ({ keys }, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
@ -512,29 +491,41 @@ describe("Secrets", function() {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign<ICrossSigningKey>({
self_signing: sign<ICrossSigningKey>(
{
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign<ICrossSigningKey>({
},
XSK,
"@alice:example.com",
),
user_signing: sign<ICrossSigningKey>(
{
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
XSK,
"@alice:example.com",
),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
auth_data: sign(
{
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
},
XSK,
"@alice:example.com",
),
};
};
alice.setAccountData = async function (name, data) {
@ -549,11 +540,9 @@ describe("Secrets", function() {
await alice.bootstrapSecretStorage({});
expect(alice.getAccountData("m.secret_storage.default_key")!.getContent())
.toEqual({ key: "key_id" });
expect(alice.getAccountData("m.secret_storage.default_key")!.getContent()).toEqual({ key: "key_id" });
const keyInfo = alice.getAccountData("m.secret_storage.key.key_id")!.getContent<ISecretStorageKeyInfo>();
expect(keyInfo.algorithm)
.toEqual("m.secret_storage.v1.aes-hmac-sha2");
expect(keyInfo.algorithm).toEqual("m.secret_storage.v1.aes-hmac-sha2");
expect(keyInfo.passphrase).toEqual({
algorithm: "m.pbkdf2",
iterations: 500000,
@ -561,8 +550,7 @@ describe("Secrets", function() {
});
expect(keyInfo).toHaveProperty("iv");
expect(keyInfo).toHaveProperty("mac");
expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo))
.toBeTruthy();
expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo)).toBeTruthy();
alice.stopClient();
});
it("fixes backup keys in the wrong format", async function () {
@ -578,8 +566,8 @@ describe("Secrets", function() {
{ userId: "@alice:example.com", deviceId: "Osborne2" },
{
cryptoCallbacks: {
getCrossSigningKey: async t => crossSigningKeys[t],
saveCrossSigningKeys: k => crossSigningKeys = k,
getCrossSigningKey: async (t) => crossSigningKeys[t],
saveCrossSigningKeys: (k) => (crossSigningKeys = k),
getSecretStorageKey: async ({ keys }, name) => {
for (const keyId of Object.keys(keys)) {
if (secretStorageKeys[keyId]) {
@ -639,7 +627,8 @@ describe("Secrets", function() {
encrypted: {
key_id: await encryptAES(
"123,45,6,7,89,1,234,56,78,90,12,34,5,67,8,90",
secretStorageKeys.key_id, "m.megolm_backup.v1",
secretStorageKeys.key_id,
"m.megolm_backup.v1",
),
},
},
@ -656,29 +645,41 @@ describe("Secrets", function() {
[`ed25519:${XSPubKey}`]: XSPubKey,
},
},
self_signing: sign<ICrossSigningKey>({
self_signing: sign<ICrossSigningKey>(
{
user_id: "@alice:example.com",
usage: ["self_signing"],
keys: {
[`ed25519:${SSPubKey}`]: SSPubKey,
},
}, XSK, "@alice:example.com"),
user_signing: sign<ICrossSigningKey>({
},
XSK,
"@alice:example.com",
),
user_signing: sign<ICrossSigningKey>(
{
user_id: "@alice:example.com",
usage: ["user_signing"],
keys: {
[`ed25519:${USPubKey}`]: USPubKey,
},
}, XSK, "@alice:example.com"),
},
XSK,
"@alice:example.com",
),
},
});
alice.getKeyBackupVersion = async () => {
return {
version: "1",
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
auth_data: sign({
auth_data: sign(
{
public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A",
}, XSK, "@alice:example.com"),
},
XSK,
"@alice:example.com",
),
};
};
alice.setAccountData = async function (name, data) {
@ -695,8 +696,7 @@ describe("Secrets", function() {
const backupKey = alice.getAccountData("m.megolm_backup.v1")!.getContent();
expect(backupKey.encrypted).toHaveProperty("key_id");
expect(await alice.getSecret("m.megolm_backup.v1"))
.toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
expect(await alice.getSecret("m.megolm_backup.v1")).toEqual("ey0GB1kB6jhOWgwiBUMIWg==");
alice.stopClient();
});
});

View File

@ -22,7 +22,9 @@ describe("InRoomChannel tests", function() {
const BOB = "@bob:hs.tld";
const MALORY = "@malory:hs.tld";
const client = {
getUserId() { return ALICE; },
getUserId() {
return ALICE;
},
} as unknown as MatrixClient;
it("getEventType only returns .request for a message with a msgtype", function () {
@ -34,11 +36,9 @@ describe("InRoomChannel tests", function() {
type: "m.room.message",
content: { msgtype: "m.key.verification.request" },
});
expect(InRoomChannel.getEventType(validEvent)).
toStrictEqual("m.key.verification.request");
expect(InRoomChannel.getEventType(validEvent)).toStrictEqual("m.key.verification.request");
const validFooEvent = new MatrixEvent({ type: "m.foo" });
expect(InRoomChannel.getEventType(validFooEvent)).
toStrictEqual("m.foo");
expect(InRoomChannel.getEventType(validFooEvent)).toStrictEqual("m.foo");
});
it("getEventType should return m.room.message for messages", function () {
@ -47,8 +47,7 @@ describe("InRoomChannel tests", function() {
content: { msgtype: "m.text" },
});
// XXX: The event type doesn't matter too much, just as long as it's not a verification event
expect(InRoomChannel.getEventType(messageEvent)).
toStrictEqual("m.room.message");
expect(InRoomChannel.getEventType(messageEvent)).toStrictEqual("m.room.message");
});
it("getEventType should return actual type for non-message events", function () {
@ -56,12 +55,10 @@ describe("InRoomChannel tests", function() {
type: "m.room.member",
content: {},
});
expect(InRoomChannel.getEventType(event)).
toStrictEqual("m.room.member");
expect(InRoomChannel.getEventType(event)).toStrictEqual("m.room.member");
});
it("getOtherPartyUserId should not return anything for a request not " +
"directed at me", function() {
it("getOtherPartyUserId should not return anything for a request not " + "directed at me", function () {
const event = new MatrixEvent({
sender: BOB,
type: "m.room.message",
@ -70,29 +67,25 @@ describe("InRoomChannel tests", function() {
expect(InRoomChannel.getOtherPartyUserId(event, client)).toStrictEqual(undefined);
});
it("getOtherPartyUserId should not return anything an event that is not of a valid " +
"request type", function() {
it("getOtherPartyUserId should not return anything an event that is not of a valid " + "request type", function () {
// invalid because this should be a room message with msgtype
const invalidRequest = new MatrixEvent({
sender: BOB,
type: "m.key.verification.request",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(invalidRequest, client))
.toStrictEqual(undefined);
expect(InRoomChannel.getOtherPartyUserId(invalidRequest, client)).toStrictEqual(undefined);
const startEvent = new MatrixEvent({
sender: BOB,
type: "m.key.verification.start",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(startEvent, client))
.toStrictEqual(undefined);
expect(InRoomChannel.getOtherPartyUserId(startEvent, client)).toStrictEqual(undefined);
const fooEvent = new MatrixEvent({
sender: BOB,
type: "m.foo",
content: { to: ALICE },
});
expect(InRoomChannel.getOtherPartyUserId(fooEvent, client))
.toStrictEqual(undefined);
expect(InRoomChannel.getOtherPartyUserId(fooEvent, client)).toStrictEqual(undefined);
});
});

View File

@ -21,7 +21,7 @@ const Olm = global.Olm;
describe("QR code verification", function () {
if (!global.Olm) {
logger.warn('Not running device verification tests: libolm not present');
logger.warn("Not running device verification tests: libolm not present");
return;
}

View File

@ -18,7 +18,7 @@ import "../../../olm-loader";
import { CryptoEvent, verificationMethods } from "../../../../src/crypto";
import { logger } from "../../../../src/logger";
import { SAS } from "../../../../src/crypto/verification/SAS";
import { makeTestClients } from './util';
import { makeTestClients } from "./util";
const Olm = global.Olm;
@ -26,7 +26,7 @@ jest.useFakeTimers();
describe("verification request integration tests with crypto layer", function () {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
logger.warn("Not running device verification unit tests: libolm not present");
return;
}
@ -66,7 +66,7 @@ describe("verification request integration tests with crypto layer", function()
bobVerifier.endTimer();
});
const aliceRequest = await alice.client.requestVerification("@bob:example.com");
await aliceRequest.waitFor(r => r.started);
await aliceRequest.waitFor((r) => r.started);
const aliceVerifier = aliceRequest.verifier;
expect(aliceVerifier).toBeInstanceOf(SAS);

View File

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import "../../../olm-loader";
import { makeTestClients } from './util';
import { makeTestClients } from "./util";
import { MatrixEvent } from "../../../../src/models/event";
import { ISasEvent, SAS, SasEvent } from "../../../../src/crypto/verification/SAS";
import { DeviceInfo, IDevice } from "../../../../src/crypto/deviceinfo";
@ -36,7 +36,7 @@ let BOB_DEVICES: Record<string, IDevice>;
describe("SAS verification", function () {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');
logger.warn("Not running device verification unit tests: libolm not present");
return;
}
@ -55,19 +55,21 @@ describe("SAS verification", function() {
},
} as unknown as IVerificationChannel;
const mockClient = {} as unknown as MatrixClient;
const event = new MatrixEvent({ type: 'test' });
const event = new MatrixEvent({ type: "test" });
const sas = new SAS(channel, mockClient, "@alice:example.com", "ABCDEFG", event, request);
sas.handleEvent(new MatrixEvent({
sas.handleEvent(
new MatrixEvent({
sender: "@alice:example.com",
type: "es.inquisition",
content: {},
}));
}),
);
const spy = jest.fn();
await sas.verify().catch(spy);
expect(spy).toHaveBeenCalled();
// Cancel the SAS for cleanup (we started a verification, so abort)
sas.cancel(new Error('error'));
sas.cancel(new Error("error"));
});
describe("verification", () => {
@ -117,16 +119,12 @@ describe("SAS verification", function() {
},
};
alice.client.crypto!.deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
alice.client.crypto!.deviceList.storeDevicesForUser("@bob:example.com", BOB_DEVICES);
alice.client.downloadKeys = () => {
return Promise.resolve({});
};
bob.client.crypto!.deviceList.storeDevicesForUser(
"@alice:example.com", ALICE_DEVICES,
);
bob.client.crypto!.deviceList.storeDevicesForUser("@alice:example.com", ALICE_DEVICES);
bob.client.downloadKeys = () => {
return Promise.resolve({});
};
@ -135,7 +133,7 @@ describe("SAS verification", function() {
bobSasEvent = null;
bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
bob.client.on(CryptoEvent.VerificationRequest, request => {
bob.client.on(CryptoEvent.VerificationRequest, (request) => {
(<SAS>request.verifier!).on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
e.cancel();
@ -157,7 +155,9 @@ describe("SAS verification", function() {
});
aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId()!, bob.deviceId!,
verificationMethods.SAS,
bob.client.getUserId()!,
bob.deviceId!,
) as SAS;
aliceVerifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
@ -177,10 +177,7 @@ describe("SAS verification", function() {
});
});
afterEach(async () => {
await Promise.all([
alice.stop(),
bob.stop(),
]);
await Promise.all([alice.stop(), bob.stop()]);
clearTestClientTimeouts();
});
@ -191,21 +188,19 @@ describe("SAS verification", function() {
const origSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = function (type, map) {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
keyAgreement = map[alice.client.getUserId()!][alice.client.deviceId!]
.key_agreement_protocol;
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!].message_authentication_code;
keyAgreement = map[alice.client.getUserId()!][alice.client.deviceId!].key_agreement_protocol;
}
return origSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
alice.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
bob.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
@ -224,11 +219,9 @@ describe("SAS verification", function() {
expect(keyAgreement).toBe("curve25519-hkdf-sha256");
// make sure Alice and Bob verified each other
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
const bobDevice = await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice?.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
const aliceDevice = await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice?.isVerified()).toBeTruthy();
});
@ -244,27 +237,27 @@ describe("SAS verification", function() {
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()!][bob.client.deviceId!]
.message_authentication_codes = ['hkdf-hmac-sha256'];
map[bob.client.getUserId()!][bob.client.deviceId!].message_authentication_codes = [
"hkdf-hmac-sha256",
];
}
return aliceOrigSendToDevice(type, map);
};
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!].message_authentication_code;
}
return bobOrigSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
alice.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
bob.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
@ -280,11 +273,9 @@ describe("SAS verification", function() {
expect(macMethod).toBe("hkdf-hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
const bobDevice = await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice!.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
const aliceDevice = await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice!.isVerified()).toBeTruthy();
});
@ -300,27 +291,25 @@ describe("SAS verification", function() {
// has, since it is the same object. If this does not
// happen, the verification will fail due to a hash
// commitment mismatch.
map[bob.client.getUserId()!][bob.client.deviceId!]
.message_authentication_codes = ['hmac-sha256'];
map[bob.client.getUserId()!][bob.client.deviceId!].message_authentication_codes = ["hmac-sha256"];
}
return aliceOrigSendToDevice(type, map);
};
const bobOrigSendToDevice = bob.client.sendToDevice.bind(bob.client);
bob.client.sendToDevice = (type, map) => {
if (type === "m.key.verification.accept") {
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!]
.message_authentication_code;
macMethod = map[alice.client.getUserId()!][alice.client.deviceId!].message_authentication_code;
}
return bobOrigSendToDevice(type, map);
};
alice.httpBackend.when('POST', '/keys/query').respond(200, {
alice.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@bob:example.com": BOB_DEVICES,
},
});
bob.httpBackend.when('POST', '/keys/query').respond(200, {
bob.httpBackend.when("POST", "/keys/query").respond(200, {
failures: {},
device_keys: {
"@alice:example.com": ALICE_DEVICES,
@ -336,41 +325,33 @@ describe("SAS verification", function() {
expect(macMethod).toBe("hmac-sha256");
const bobDevice
= await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
const bobDevice = await alice.client.getStoredDevice("@bob:example.com", "Dynabook");
expect(bobDevice?.isVerified()).toBeTruthy();
const aliceDevice
= await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
const aliceDevice = await bob.client.getStoredDevice("@alice:example.com", "Osborne2");
expect(aliceDevice?.isVerified()).toBeTruthy();
});
it("should verify a cross-signing key", async () => {
alice.httpBackend.when('POST', '/keys/device_signing/upload').respond(
200, {},
);
alice.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
alice.httpBackend.when("POST", "/keys/device_signing/upload").respond(200, {});
alice.httpBackend.when("POST", "/keys/signatures/upload").respond(200, {});
alice.httpBackend.flush(undefined, 2);
await resetCrossSigningKeys(alice.client);
bob.httpBackend.when('POST', '/keys/device_signing/upload').respond(200, {});
bob.httpBackend.when('POST', '/keys/signatures/upload').respond(200, {});
bob.httpBackend.when("POST", "/keys/device_signing/upload").respond(200, {});
bob.httpBackend.when("POST", "/keys/signatures/upload").respond(200, {});
bob.httpBackend.flush(undefined, 2);
await resetCrossSigningKeys(bob.client);
bob.client.crypto!.deviceList.storeCrossSigningForUser(
"@alice:example.com", {
bob.client.crypto!.deviceList.storeCrossSigningForUser("@alice:example.com", {
keys: alice.client.crypto!.crossSigningInfo.keys,
crossSigningVerifiedBefore: false,
firstUse: true,
},
);
});
const verifyProm = Promise.all([
aliceVerifier.verify(),
bobPromise.then((verifier) => {
bob.httpBackend.when(
'POST', '/keys/signatures/upload',
).respond(200, {});
bob.httpBackend.when("POST", "/keys/signatures/upload").respond(200, {});
bob.httpBackend.flush(undefined, 1, 2000);
return verifier.verify();
}),
@ -378,9 +359,7 @@ describe("SAS verification", function() {
await verifyProm;
const bobDeviceTrust = alice.client.checkDeviceTrust(
"@bob:example.com", "Dynabook",
);
const bobDeviceTrust = alice.client.checkDeviceTrust("@bob:example.com", "Dynabook");
expect(bobDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(bobDeviceTrust.isCrossSigningVerified()).toBeFalsy();
@ -388,9 +367,7 @@ describe("SAS verification", function() {
expect(aliceTrust.isCrossSigningVerified()).toBeTruthy();
expect(aliceTrust.isTofu()).toBeTruthy();
const aliceDeviceTrust = bob.client.checkDeviceTrust(
"@alice:example.com", "Osborne2",
);
const aliceDeviceTrust = bob.client.checkDeviceTrust("@alice:example.com", "Osborne2");
expect(aliceDeviceTrust.isLocallyVerified()).toBeTruthy();
expect(aliceDeviceTrust.isCrossSigningVerified()).toBeFalsy();
});
@ -412,7 +389,7 @@ describe("SAS verification", function() {
bob.client.downloadKeys = jest.fn().mockResolvedValue({});
const bobPromise = new Promise<VerificationBase<any, any>>((resolve, reject) => {
bob.client.on(CryptoEvent.VerificationRequest, request => {
bob.client.on(CryptoEvent.VerificationRequest, (request) => {
(<SAS>request.verifier!).on(SasEvent.ShowSas, (e) => {
e.mismatch();
});
@ -421,7 +398,9 @@ describe("SAS verification", function() {
});
const aliceVerifier = alice.client.beginKeyVerification(
verificationMethods.SAS, bob.client.getUserId()!, bob.client.deviceId!,
verificationMethods.SAS,
bob.client.getUserId()!,
bob.client.deviceId!,
);
const aliceSpy = jest.fn();
@ -432,10 +411,8 @@ describe("SAS verification", function() {
]);
expect(aliceSpy).toHaveBeenCalled();
expect(bobSpy).toHaveBeenCalled();
expect(alice.client.setDeviceVerified)
.not.toHaveBeenCalled();
expect(bob.client.setDeviceVerified)
.not.toHaveBeenCalled();
expect(alice.client.setDeviceVerified).not.toHaveBeenCalled();
expect(bob.client.setDeviceVerified).not.toHaveBeenCalled();
alice.stop();
bob.stop();
@ -526,7 +503,7 @@ describe("SAS verification", function() {
});
const aliceRequest = await alice.client.requestVerificationDM(bob.client.getUserId()!, "!room_id");
await aliceRequest.waitFor(r => r.started);
await aliceRequest.waitFor((r) => r.started);
aliceVerifier = aliceRequest.verifier! as SAS;
aliceVerifier.on(SasEvent.ShowSas, (e) => {
if (!e.sas.emoji || !e.sas.decimal) {
@ -546,23 +523,16 @@ describe("SAS verification", function() {
});
});
afterEach(async function () {
await Promise.all([
alice.stop(),
bob.stop(),
]);
await Promise.all([alice.stop(), bob.stop()]);
clearTestClientTimeouts();
});
it("should verify a key", async function () {
await Promise.all([
aliceVerifier.verify(),
bobPromise,
]);
await Promise.all([aliceVerifier.verify(), bobPromise]);
// make sure Alice and Bob verified each other
expect(alice.client.crypto!.setDeviceVerification)
.toHaveBeenCalledWith(
expect(alice.client.crypto!.setDeviceVerification).toHaveBeenCalledWith(
bob.client.getUserId(),
bob.client.deviceId,
true,
@ -570,8 +540,7 @@ describe("SAS verification", function() {
null,
{ "ed25519:Dynabook": "bob+base64+ed25519+key" },
);
expect(bob.client.crypto!.setDeviceVerification)
.toHaveBeenCalledWith(
expect(bob.client.crypto!.setDeviceVerification).toHaveBeenCalledWith(
alice.client.getUserId(),
alice.client.deviceId,
true,

View File

@ -14,23 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../../olm-loader';
import { MatrixClient, MatrixEvent } from '../../../../src/matrix';
import "../../../olm-loader";
import { MatrixClient, MatrixEvent } from "../../../../src/matrix";
import { encodeBase64 } from "../../../../src/crypto/olmlib";
import "../../../../src/crypto"; // import this to cycle-break
import { CrossSigningInfo } from '../../../../src/crypto/CrossSigning';
import { VerificationRequest } from '../../../../src/crypto/verification/request/VerificationRequest';
import { IVerificationChannel } from '../../../../src/crypto/verification/request/Channel';
import { VerificationBase } from '../../../../src/crypto/verification/Base';
import { CrossSigningInfo } from "../../../../src/crypto/CrossSigning";
import { VerificationRequest } from "../../../../src/crypto/verification/request/VerificationRequest";
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
import { VerificationBase } from "../../../../src/crypto/verification/Base";
jest.useFakeTimers();
// Private key for tests only
const testKey = new Uint8Array([
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82, 0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef, 0xae, 0xb1, 0x05,
0xc1, 0xe7, 0x62, 0x78, 0xa6, 0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
]);
const testKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
@ -47,26 +45,22 @@ describe("self-verifications", () => {
storeCrossSigningKeyCache: jest.fn(),
};
const crossSigningInfo = new CrossSigningInfo(
userId,
{},
cacheCallbacks,
);
const crossSigningInfo = new CrossSigningInfo(userId, {}, cacheCallbacks);
crossSigningInfo.keys = {
master: {
keys: { X: testKeyPub },
usage: [],
user_id: 'user-id',
user_id: "user-id",
},
self_signing: {
keys: { X: testKeyPub },
usage: [],
user_id: 'user-id',
user_id: "user-id",
},
user_signing: {
keys: { X: testKeyPub },
usage: [],
user_id: 'user-id',
user_id: "user-id",
},
};
@ -114,13 +108,10 @@ describe("self-verifications", () => {
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls.length).toBe(3);
expect(secretStorage.request.mock.calls.length).toBe(4);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[0][1])
.toEqual(testKey);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[1][1])
.toEqual(testKey);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[0][1]).toEqual(testKey);
expect(cacheCallbacks.storeCrossSigningKeyCache.mock.calls[1][1]).toEqual(testKey);
expect(storeSessionBackupPrivateKey.mock.calls[0][0])
.toEqual(testKey);
expect(storeSessionBackupPrivateKey.mock.calls[0][0]).toEqual(testKey);
expect(restoreKeyBackupWithCache).toHaveBeenCalled();

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../../olm-loader';
import "../../../olm-loader";
import { CRYPTO_ENABLED, MatrixClient } from "../../../../src/client";
import { TestClient } from "../../../TestClient";
@ -35,7 +35,7 @@ describe("crypto.setDeviceVerification", () => {
});
beforeEach(async () => {
client = (new TestClient(userId, deviceId1)).client;
client = new TestClient(userId, deviceId1).client;
await client.initCrypto();
});
@ -46,11 +46,7 @@ describe("crypto.setDeviceVerification", () => {
describe("when setting an own device as verified", () => {
beforeEach(async () => {
jest.spyOn(client.crypto!, "cancelAndResendAllOutgoingKeyRequests");
await client.crypto!.setDeviceVerification(
userId,
deviceId1,
true,
);
await client.crypto!.setDeviceVerification(userId, deviceId1, true);
});
it("cancelAndResendAllOutgoingKeyRequests should be called", () => {

View File

@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { TestClient } from '../../../TestClient';
import { TestClient } from "../../../TestClient";
import { IContent, MatrixEvent } from "../../../../src/models/event";
import { IRoomTimelineData } from "../../../../src/models/event-timeline-set";
import { Room, RoomEvent } from "../../../../src/models/room";
import { logger } from '../../../../src/logger';
import { MatrixClient, ClientEvent, ICreateClientOpts } from '../../../../src/client';
import { logger } from "../../../../src/logger";
import { MatrixClient, ClientEvent, ICreateClientOpts } from "../../../../src/client";
interface UserInfo {
userId: string;
@ -34,7 +34,9 @@ export async function makeTestClients(
const clients: TestClient[] = [];
const timeouts: ReturnType<typeof setTimeout>[] = [];
const clientMap: Record<string, Record<string, MatrixClient>> = {};
const makeSendToDevice = (matrixClient: MatrixClient): MatrixClient['sendToDevice'] => async (type, map) => {
const makeSendToDevice =
(matrixClient: MatrixClient): MatrixClient["sendToDevice"] =>
async (type, map) => {
// logger.log(this.getUserId(), "sends", type, map);
for (const [userId, devMap] of Object.entries(map)) {
if (userId in clientMap) {
@ -46,13 +48,11 @@ export async function makeTestClients(
content: msg,
});
const client = clientMap[userId][deviceId];
const decryptionPromise = event.isEncrypted() ?
event.attemptDecryption(client.crypto!) :
Promise.resolve();
const decryptionPromise = event.isEncrypted()
? event.attemptDecryption(client.crypto!)
: Promise.resolve();
decryptionPromise.then(
() => client.emit(ClientEvent.ToDeviceEvent, event),
);
decryptionPromise.then(() => client.emit(ClientEvent.ToDeviceEvent, event));
}
}
}
@ -71,15 +71,17 @@ export async function makeTestClients(
origin_server_ts: Date.now(),
};
const event = new MatrixEvent(rawEvent);
const remoteEcho = new MatrixEvent(Object.assign({}, rawEvent, {
const remoteEcho = new MatrixEvent(
Object.assign({}, rawEvent, {
unsigned: {
transaction_id: matrixClient.makeTxnId(),
},
}));
}),
);
const timeout = setTimeout(() => {
for (const tc of clients) {
const room = new Room('test', tc.client, tc.client.getUserId()!);
const room = new Room("test", tc.client, tc.client.getUserId()!);
const roomTimelineData = {} as unknown as IRoomTimelineData;
if (tc.client === matrixClient) {
logger.log("sending remote echo!!");
@ -100,14 +102,13 @@ export async function makeTestClients(
if (!options) options = {};
if (!options.cryptoCallbacks) options.cryptoCallbacks = {};
if (!options.cryptoCallbacks.saveCrossSigningKeys) {
options.cryptoCallbacks.saveCrossSigningKeys = k => { keys = k; };
options.cryptoCallbacks.saveCrossSigningKeys = (k) => {
keys = k;
};
// @ts-ignore tsc getting confused by overloads
options.cryptoCallbacks.getCrossSigningKey = typ => keys[typ];
options.cryptoCallbacks.getCrossSigningKey = (typ) => keys[typ];
}
const testClient = new TestClient(
userInfo.userId, userInfo.deviceId, undefined, undefined,
options,
);
const testClient = new TestClient(userInfo.userId, userInfo.deviceId, undefined, undefined, options);
if (!(userInfo.userId in clientMap)) {
clientMap[userInfo.userId] = {};
}

View File

@ -13,11 +13,14 @@ 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 { VerificationRequest, READY_TYPE, START_TYPE, DONE_TYPE } from
"../../../../src/crypto/verification/request/VerificationRequest";
import {
VerificationRequest,
READY_TYPE,
START_TYPE,
DONE_TYPE,
} from "../../../../src/crypto/verification/request/VerificationRequest";
import { InRoomChannel } from "../../../../src/crypto/verification/request/InRoomChannel";
import { ToDeviceChannel } from
"../../../../src/crypto/verification/request/ToDeviceChannel";
import { ToDeviceChannel } from "../../../../src/crypto/verification/request/ToDeviceChannel";
import { IContent, MatrixEvent } from "../../../../src/models/event";
import { MatrixClient } from "../../../../src/client";
import { IVerificationChannel } from "../../../../src/crypto/verification/request/Channel";
@ -32,20 +35,26 @@ function makeMockClient(userId: string, deviceId: string): MockClient {
let events: MatrixEvent[] = [];
const deviceEvents: Record<string, Record<string, MatrixEvent[]>> = {};
return {
getUserId() { return userId; },
getDeviceId() { return deviceId; },
getUserId() {
return userId;
},
getDeviceId() {
return deviceId;
},
sendEvent(roomId: string, type: string, content: IContent) {
counter = counter + 1;
const eventId = `$${userId}-${deviceId}-${counter}`;
events.push(new MatrixEvent({
events.push(
new MatrixEvent({
sender: userId,
event_id: eventId,
room_id: roomId,
type,
content,
origin_server_ts: Date.now(),
}));
}),
);
return Promise.resolve({ event_id: eventId });
},
@ -84,7 +93,7 @@ function makeMockClient(userId: string, deviceId: string): MockClient {
}
const MOCK_METHOD = "mock-verify";
class MockVerifier extends VerificationBase<'', any> {
class MockVerifier extends VerificationBase<"", any> {
public _channel;
public _startEvent;
constructor(
@ -123,11 +132,13 @@ class MockVerifier extends VerificationBase<'', any> {
}
function makeRemoteEcho(event: MatrixEvent) {
return new MatrixEvent(Object.assign({}, event.event, {
return new MatrixEvent(
Object.assign({}, event.event, {
unsigned: {
transaction_id: "abc",
},
}));
}),
);
}
async function distributeEvent(
@ -135,11 +146,7 @@ async function distributeEvent(
theirRequest: VerificationRequest,
event: MatrixEvent,
): Promise<void> {
await ownRequest.channel.handleEvent(
makeRemoteEcho(event),
ownRequest,
true,
);
await ownRequest.channel.handleEvent(makeRemoteEcho(event), ownRequest, true);
await theirRequest.channel.handleEvent(event, theirRequest, true);
}
@ -149,19 +156,16 @@ describe("verification request unit tests", function() {
it("transition from UNSENT to DONE through happy path", async function () {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const verificationMethods = new Map(
[[MOCK_METHOD, MockVerifier]],
) as unknown as Map<string, typeof VerificationBase>;
const verificationMethods = new Map([[MOCK_METHOD, MockVerifier]]) as unknown as Map<
string,
typeof VerificationBase
>;
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()!),
verificationMethods,
alice,
);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
verificationMethods,
bob,
);
const bobRequest = new VerificationRequest(new InRoomChannel(bob, "!room"), verificationMethods, bob);
expect(aliceRequest.invalid).toBe(true);
expect(bobRequest.invalid).toBe(true);
@ -202,20 +206,20 @@ describe("verification request unit tests", function() {
it("methods only contains common methods", async function () {
const alice = makeMockClient("@alice:matrix.tld", "device1");
const bob = makeMockClient("@bob:matrix.tld", "device1");
const aliceVerificationMethods = new Map(
[["c", function() {}], ["a", function() {}]],
) as unknown as Map<string, typeof VerificationBase>;
const bobVerificationMethods = new Map(
[["c", function() {}], ["b", function() {}]],
) as unknown as Map<string, typeof VerificationBase>;
const aliceVerificationMethods = new Map([
["c", function () {}],
["a", function () {}],
]) as unknown as Map<string, typeof VerificationBase>;
const bobVerificationMethods = new Map([
["c", function () {}],
["b", function () {}],
]) as unknown as Map<string, typeof VerificationBase>;
const aliceRequest = new VerificationRequest(
new InRoomChannel(alice, "!room", bob.getUserId()!),
aliceVerificationMethods, alice);
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
bobVerificationMethods,
bob,
aliceVerificationMethods,
alice,
);
const bobRequest = new VerificationRequest(new InRoomChannel(bob, "!room"), bobVerificationMethods, bob);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
await distributeEvent(aliceRequest, bobRequest, requestEvent);
@ -237,16 +241,8 @@ describe("verification request unit tests", function() {
);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bob1Request = new VerificationRequest(
new InRoomChannel(bob1, "!room"),
new Map(),
bob1,
);
const bob2Request = new VerificationRequest(
new InRoomChannel(bob2, "!room"),
new Map(),
bob2,
);
const bob1Request = new VerificationRequest(new InRoomChannel(bob1, "!room"), new Map(), bob1);
const bob2Request = new VerificationRequest(new InRoomChannel(bob2, "!room"), new Map(), bob2);
await bob1Request.channel.handleEvent(requestEvent, bob1Request, true);
await bob2Request.channel.handleEvent(requestEvent, bob2Request, true);
@ -261,9 +257,10 @@ describe("verification request unit tests", function() {
it("verify own device with to_device messages", async function () {
const bob1 = makeMockClient("@bob:matrix.tld", "device1");
const bob2 = makeMockClient("@bob:matrix.tld", "device2");
const verificationMethods = new Map(
[[MOCK_METHOD, MockVerifier]],
) as unknown as Map<string, typeof VerificationBase>;
const verificationMethods = new Map([[MOCK_METHOD, MockVerifier]]) as unknown as Map<
string,
typeof VerificationBase
>;
const bob1Request = new VerificationRequest(
new ToDeviceChannel(
bob1,
@ -328,11 +325,7 @@ describe("verification request unit tests", function() {
);
await aliceRequest.sendRequest();
const [requestEvent] = alice.popEvents();
const bobRequest = new VerificationRequest(
new InRoomChannel(bob, "!room"),
new Map(),
bob,
);
const bobRequest = new VerificationRequest(new InRoomChannel(bob, "!room"), new Map(), bob);
await bobRequest.channel.handleEvent(requestEvent, bobRequest, true);

View File

@ -23,13 +23,7 @@ limitations under the License.
// eslint-disable-next-line no-restricted-imports
import { EventEmitter } from "events";
import { MockedObject } from "jest-mock";
import {
WidgetApi,
WidgetApiToWidgetAction,
MatrixCapabilities,
ITurnServer,
IRoomEvent,
} from "matrix-widget-api";
import { WidgetApi, WidgetApiToWidgetAction, MatrixCapabilities, ITurnServer, IRoomEvent } from "matrix-widget-api";
import { createRoomWidgetClient, MsgType } from "../../src/matrix";
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "../../src/client";
@ -88,7 +82,9 @@ describe("RoomWidgetClient", () => {
expect(widgetApi.requestCapabilityToSendEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
await client.sendEvent("!1:example.org", "org.matrix.rageshake_request", { request_id: 123 });
expect(widgetApi.sendRoomEvent).toHaveBeenCalledWith(
"org.matrix.rageshake_request", { request_id: 123 }, "!1:example.org",
"org.matrix.rageshake_request",
{ request_id: 123 },
"!1:example.org",
);
});
@ -105,8 +101,8 @@ describe("RoomWidgetClient", () => {
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToReceiveEvent).toHaveBeenCalledWith("org.matrix.rageshake_request");
const emittedEvent = new Promise<MatrixEvent>(resolve => client.once(ClientEvent.Event, resolve));
const emittedSync = new Promise<SyncState>(resolve => client.once(ClientEvent.Sync, resolve));
const emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.Event, resolve));
const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
widgetApi.emit(
`action:${WidgetApiToWidgetAction.SendEvent}`,
new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }),
@ -118,7 +114,12 @@ describe("RoomWidgetClient", () => {
// It should've also inserted the event into the room object
const room = client.getRoom("!1:example.org");
expect(room).not.toBeNull();
expect(room!.getLiveTimeline().getEvents().map(e => e.getEffectiveEvent())).toEqual([event]);
expect(
room!
.getLiveTimeline()
.getEvents()
.map((e) => e.getEffectiveEvent()),
).toEqual([event]);
});
});
@ -157,7 +158,10 @@ describe("RoomWidgetClient", () => {
expect(widgetApi.requestCapabilityToSendState).toHaveBeenCalledWith("org.example.foo", "bar");
await client.sendStateEvent("!1:example.org", "org.example.foo", { hello: "world" }, "bar");
expect(widgetApi.sendStateEvent).toHaveBeenCalledWith(
"org.example.foo", "bar", { hello: "world" }, "!1:example.org",
"org.example.foo",
"bar",
{ hello: "world" },
"!1:example.org",
);
});
@ -166,8 +170,8 @@ describe("RoomWidgetClient", () => {
expect(widgetApi.requestCapabilityForRoomTimeline).toHaveBeenCalledWith("!1:example.org");
expect(widgetApi.requestCapabilityToReceiveState).toHaveBeenCalledWith("org.example.foo", "bar");
const emittedEvent = new Promise<MatrixEvent>(resolve => client.once(ClientEvent.Event, resolve));
const emittedSync = new Promise<SyncState>(resolve => client.once(ClientEvent.Sync, resolve));
const emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.Event, resolve));
const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
widgetApi.emit(
`action:${WidgetApiToWidgetAction.SendEvent}`,
new CustomEvent(`action:${WidgetApiToWidgetAction.SendEvent}`, { detail: { data: event } }),
@ -260,8 +264,8 @@ describe("RoomWidgetClient", () => {
content: { hello: "world" },
};
const emittedEvent = new Promise<MatrixEvent>(resolve => client.once(ClientEvent.ToDeviceEvent, resolve));
const emittedSync = new Promise<SyncState>(resolve => client.once(ClientEvent.Sync, resolve));
const emittedEvent = new Promise<MatrixEvent>((resolve) => client.once(ClientEvent.ToDeviceEvent, resolve));
const emittedSync = new Promise<SyncState>((resolve) => client.once(ClientEvent.Sync, resolve));
widgetApi.emit(
`action:${WidgetApiToWidgetAction.SendToDevice}`,
new CustomEvent(`action:${WidgetApiToWidgetAction.SendToDevice}`, { detail: { data: event } }),
@ -308,7 +312,7 @@ describe("RoomWidgetClient", () => {
};
let emitServer2: () => void;
const getServer2 = new Promise<ITurnServer>(resolve => emitServer2 = () => resolve(server2));
const getServer2 = new Promise<ITurnServer>((resolve) => (emitServer2 = () => resolve(server2)));
widgetApi.getTurnServers.mockImplementation(async function* () {
yield server1;
yield await getServer2;
@ -321,7 +325,7 @@ describe("RoomWidgetClient", () => {
expect(client.getTurnServers()).toEqual([clientServer1]);
// Subsequent servers arrive asynchronously and should emit an event
const emittedServer = new Promise<IClientTurnServer[]>(resolve =>
const emittedServer = new Promise<IClientTurnServer[]>((resolve) =>
client.once(ClientEvent.TurnServers, resolve),
);
emitServer2!();

View File

@ -32,7 +32,7 @@ describe("eventMapperFor", function() {
fetchFn: function () {} as any, // NOP
store: {
getRoom(roomId: string): Room | null {
return rooms.find(r => r.roomId === roomId) ?? null;
return rooms.find((r) => r.roomId === roomId) ?? null;
},
} as IStore,
scheduler: {

View File

@ -25,12 +25,12 @@ import {
MatrixEvent,
MatrixEventEvent,
Room,
} from '../../src';
} from "../../src";
import { Thread } from "../../src/models/thread";
import { ReEmitter } from "../../src/ReEmitter";
describe('EventTimelineSet', () => {
const roomId = '!foo:bar';
describe("EventTimelineSet", () => {
const roomId = "!foo:bar";
const userA = "@alice:bar";
let room: Room;
@ -42,7 +42,7 @@ describe('EventTimelineSet', () => {
let replyEvent: MatrixEvent;
const itShouldReturnTheRelatedEvents = () => {
it('should return the related events', () => {
it("should return the related events", () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId()!,
@ -55,7 +55,9 @@ describe('EventTimelineSet', () => {
});
};
const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({
const mkThreadResponse = (root: MatrixEvent) =>
utils.mkEvent(
{
event: true,
type: EventType.RoomMessage,
user: userA,
@ -65,35 +67,37 @@ describe('EventTimelineSet', () => {
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId(),
event_id: root.getId(),
},
"rel_type": "m.thread",
},
},
}, room.client);
},
room.client,
);
beforeEach(() => {
client = utils.mock(MatrixClient, 'MatrixClient');
client.reEmitter = utils.mock(ReEmitter, 'ReEmitter');
client = utils.mock(MatrixClient, "MatrixClient");
client.reEmitter = utils.mock(ReEmitter, "ReEmitter");
room = new Room(roomId, client, userA);
eventTimelineSet = new EventTimelineSet(room);
eventTimeline = new EventTimeline(eventTimelineSet);
messageEvent = utils.mkMessage({
room: roomId,
user: userA,
msg: 'Hi!',
msg: "Hi!",
event: true,
});
replyEvent = utils.mkReplyMessage({
room: roomId,
user: userA,
msg: 'Hoo!',
msg: "Hoo!",
event: true,
replyToMessage: messageEvent,
});
});
describe('addLiveEvent', () => {
describe("addLiveEvent", () => {
it("Adds event to the live timeline in the timeline set", () => {
const liveTimeline = eventTimelineSet.getLiveTimeline();
expect(liveTimeline.getEvents().length).toStrictEqual(0);
@ -111,7 +115,10 @@ describe('EventTimelineSet', () => {
// make a duplicate
const duplicateMessageEvent = utils.mkMessage({
room: roomId, user: userA, msg: "dupe", event: true,
room: roomId,
user: userA,
msg: "dupe",
event: true,
});
duplicateMessageEvent.event.event_id = messageEvent.getId();
@ -133,7 +140,7 @@ describe('EventTimelineSet', () => {
});
});
describe('addEventToTimeline', () => {
describe("addEventToTimeline", () => {
let thread: Thread;
beforeEach(() => {
@ -153,19 +160,10 @@ describe('EventTimelineSet', () => {
it("Make sure legacy overload passing options directly as parameters still works", () => {
const liveTimeline = eventTimelineSet.getLiveTimeline();
expect(() => {
eventTimelineSet.addEventToTimeline(
messageEvent,
liveTimeline,
true,
);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true);
}).not.toThrow();
expect(() => {
eventTimelineSet.addEventToTimeline(
messageEvent,
liveTimeline,
true,
false,
);
eventTimelineSet.addEventToTimeline(messageEvent, liveTimeline, true, false);
}).not.toThrow();
});
@ -204,8 +202,8 @@ describe('EventTimelineSet', () => {
expect(liveTimeline.getEvents().length).toStrictEqual(0);
});
describe('non-room timeline', () => {
it('Adds event to timeline', () => {
describe("non-room timeline", () => {
it("Adds event to timeline", () => {
const nonRoomEventTimelineSet = new EventTimelineSet(
// This is what we're specifically testing against, a timeline
// without a `room` defined
@ -222,24 +220,16 @@ describe('EventTimelineSet', () => {
});
});
describe('aggregateRelations', () => {
describe('with unencrypted events', () => {
describe("aggregateRelations", () => {
describe("with unencrypted events", () => {
beforeEach(() => {
eventTimelineSet.addEventsToTimeline(
[
messageEvent,
replyEvent,
],
true,
eventTimeline,
'foo',
);
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, eventTimeline, "foo");
});
itShouldReturnTheRelatedEvents();
});
describe('with events to be decrypted', () => {
describe("with events to be decrypted", () => {
let messageEventShouldAttemptDecryptionSpy: jest.SpyInstance;
let messageEventIsDecryptionFailureSpy: jest.SpyInstance;
@ -247,26 +237,18 @@ describe('EventTimelineSet', () => {
let replyEventIsDecryptionFailureSpy: jest.SpyInstance;
beforeEach(() => {
messageEventShouldAttemptDecryptionSpy = jest.spyOn(messageEvent, 'shouldAttemptDecryption');
messageEventShouldAttemptDecryptionSpy = jest.spyOn(messageEvent, "shouldAttemptDecryption");
messageEventShouldAttemptDecryptionSpy.mockReturnValue(true);
messageEventIsDecryptionFailureSpy = jest.spyOn(messageEvent, 'isDecryptionFailure');
messageEventIsDecryptionFailureSpy = jest.spyOn(messageEvent, "isDecryptionFailure");
replyEventShouldAttemptDecryptionSpy = jest.spyOn(replyEvent, 'shouldAttemptDecryption');
replyEventShouldAttemptDecryptionSpy = jest.spyOn(replyEvent, "shouldAttemptDecryption");
replyEventShouldAttemptDecryptionSpy.mockReturnValue(true);
replyEventIsDecryptionFailureSpy = jest.spyOn(messageEvent, 'isDecryptionFailure');
replyEventIsDecryptionFailureSpy = jest.spyOn(messageEvent, "isDecryptionFailure");
eventTimelineSet.addEventsToTimeline(
[
messageEvent,
replyEvent,
],
true,
eventTimeline,
'foo',
);
eventTimelineSet.addEventsToTimeline([messageEvent, replyEvent], true, eventTimeline, "foo");
});
it('should not return the related events', () => {
it("should not return the related events", () => {
eventTimelineSet.relations.aggregateChildEvent(messageEvent);
const relations = eventTimelineSet.relations.getChildEventsForEvent(
messageEvent.getId()!,
@ -276,7 +258,7 @@ describe('EventTimelineSet', () => {
expect(relations).toBeUndefined();
});
describe('after decryption', () => {
describe("after decryption", () => {
beforeEach(() => {
// simulate decryption failure once
messageEventIsDecryptionFailureSpy.mockReturnValue(true);
@ -302,7 +284,9 @@ describe('EventTimelineSet', () => {
});
describe("canContain", () => {
const mkThreadResponse = (root: MatrixEvent) => utils.mkEvent({
const mkThreadResponse = (root: MatrixEvent) =>
utils.mkEvent(
{
event: true,
type: EventType.RoomMessage,
user: userA,
@ -312,12 +296,14 @@ describe('EventTimelineSet', () => {
"m.relates_to": {
"event_id": root.getId(),
"m.in_reply_to": {
"event_id": root.getId()!,
event_id: root.getId()!,
},
"rel_type": "m.thread",
},
},
}, room.client);
},
room.client,
);
let thread: Thread;

View File

@ -1,4 +1,4 @@
import { mocked } from 'jest-mock';
import { mocked } from "jest-mock";
import * as utils from "../test-utils/test-utils";
import { Direction, EventTimeline } from "../../src/models/event-timeline";
@ -19,7 +19,7 @@ describe("EventTimeline", function() {
const getTimeline = (): EventTimeline => {
const room = new Room(roomId, mockClient, userA);
const timelineSet = new EventTimelineSet(room);
jest.spyOn(room, 'getUnfilteredTimelineSet').mockReturnValue(timelineSet);
jest.spyOn(room, "getUnfilteredTimelineSet").mockReturnValue(timelineSet);
const timeline = new EventTimeline(timelineSet);
// We manually stub the methods we'll be mocking out later instead of mocking the whole module
@ -49,11 +49,16 @@ describe("EventTimeline", function() {
it("should copy state events to start and end state", function () {
const events = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA,
room: roomId,
mship: "invite",
user: userB,
skey: userA,
event: true,
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB,
type: "m.room.name",
room: roomId,
user: userB,
event: true,
content: { name: "New room" },
}),
@ -61,28 +66,30 @@ describe("EventTimeline", function() {
timeline.initialiseState(events);
// @ts-ignore private prop
const timelineStartState = timeline.startState!;
expect(mocked(timelineStartState).setStateEvents).toHaveBeenCalledWith(
events,
{ timelineWasEmpty: undefined },
);
expect(mocked(timelineStartState).setStateEvents).toHaveBeenCalledWith(events, {
timelineWasEmpty: undefined,
});
// @ts-ignore private prop
const timelineEndState = timeline.endState!;
expect(mocked(timelineEndState).setStateEvents).toHaveBeenCalledWith(
events,
{ timelineWasEmpty: undefined },
);
expect(mocked(timelineEndState).setStateEvents).toHaveBeenCalledWith(events, {
timelineWasEmpty: undefined,
});
});
it("should raise an exception if called after events are added", function () {
const event =
utils.mkMessage({
room: roomId, user: userA, msg: "Adam stole the plushies",
const event = utils.mkMessage({
room: roomId,
user: userA,
msg: "Adam stole the plushies",
event: true,
});
const state = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA,
room: roomId,
mship: "invite",
user: userB,
skey: userA,
event: true,
}),
];
@ -142,8 +149,7 @@ describe("EventTimeline", function() {
expect(function () {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS))
.toBe(prev);
expect(timeline.getNeighbouringTimeline(EventTimeline.BACKWARDS)).toBe(prev);
expect(function () {
timeline.setNeighbouringTimeline(prev, EventTimeline.BACKWARDS);
}).toThrow();
@ -151,8 +157,7 @@ describe("EventTimeline", function() {
expect(function () {
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
}).not.toThrow();
expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS))
.toBe(next);
expect(timeline.getNeighbouringTimeline(EventTimeline.FORWARDS)).toBe(next);
expect(function () {
timeline.setNeighbouringTimeline(next, EventTimeline.FORWARDS);
}).toThrow();
@ -162,11 +167,15 @@ describe("EventTimeline", function() {
describe("addEvent", function () {
const events = [
utils.mkMessage({
room: roomId, user: userA, msg: "hungry hungry hungry",
room: roomId,
user: userA,
msg: "hungry hungry hungry",
event: true,
}),
utils.mkMessage({
room: roomId, user: userB, msg: "nom nom nom",
room: roomId,
user: userB,
msg: "nom nom nom",
event: true,
}),
];
@ -200,15 +209,13 @@ describe("EventTimeline", function() {
sentinel.name = "Old Alice";
sentinel.membership = "join";
mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember
.mockImplementation(function(uid) {
mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember.mockImplementation(function (uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember
.mockImplementation(function(uid) {
mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember.mockImplementation(function (uid) {
if (uid === userA) {
return oldSentinel;
}
@ -216,11 +223,17 @@ describe("EventTimeline", function() {
});
const newEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
type: "m.room.name",
room: roomId,
user: userA,
event: true,
content: { name: "New Room Name" },
});
const oldEv = utils.mkEvent({
type: "m.room.name", room: roomId, user: userA, event: true,
type: "m.room.name",
room: roomId,
user: userA,
event: true,
content: { name: "Old Room Name" },
});
@ -230,8 +243,7 @@ describe("EventTimeline", function() {
expect(oldEv.sender).toEqual(oldSentinel);
});
it("should set event.target for new and old m.room.member events",
function() {
it("should set event.target for new and old m.room.member events", function () {
const sentinel = new RoomMember(roomId, userA);
sentinel.name = "Alice";
sentinel.membership = "join";
@ -240,15 +252,13 @@ describe("EventTimeline", function() {
sentinel.name = "Old Alice";
sentinel.membership = "join";
mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember
.mockImplementation(function(uid) {
mocked(timeline.getState(EventTimeline.FORWARDS)!).getSentinelMember.mockImplementation(function (uid) {
if (uid === userA) {
return sentinel;
}
return null;
});
mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember
.mockImplementation(function(uid) {
mocked(timeline.getState(EventTimeline.BACKWARDS)!).getSentinelMember.mockImplementation(function (uid) {
if (uid === userA) {
return oldSentinel;
}
@ -256,10 +266,18 @@ describe("EventTimeline", function() {
});
const newEv = utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true,
room: roomId,
mship: "invite",
user: userB,
skey: userA,
event: true,
});
const oldEv = utils.mkMembership({
room: roomId, mship: "ban", user: userB, skey: userA, event: true,
room: roomId,
mship: "ban",
user: userB,
skey: userA,
event: true,
});
timeline.addEvent(newEv, { toStartOfTimeline: false });
expect(newEv.target).toEqual(sentinel);
@ -267,14 +285,22 @@ describe("EventTimeline", function() {
expect(oldEv.target).toEqual(oldSentinel);
});
it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for new events", function() {
it(
"should call setStateEvents on the right RoomState with the right " + "forwardLooking value for new events",
function () {
const events = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true,
room: roomId,
mship: "invite",
user: userB,
skey: userA,
event: true,
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB, event: true,
type: "m.room.name",
room: roomId,
user: userB,
event: true,
content: {
name: "New room",
},
@ -284,26 +310,36 @@ describe("EventTimeline", function() {
timeline.addEvent(events[0], { toStartOfTimeline: false });
timeline.addEvent(events[1], { toStartOfTimeline: false });
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).
toHaveBeenCalledWith([events[0]], { timelineWasEmpty: undefined });
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).
toHaveBeenCalledWith([events[1]], { timelineWasEmpty: undefined });
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
});
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: undefined,
});
expect(events[0].forwardLooking).toBe(true);
expect(events[1].forwardLooking).toBe(true);
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).
not.toHaveBeenCalled();
});
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).not.toHaveBeenCalled();
},
);
it("should call setStateEvents on the right RoomState with the right " +
"forwardLooking value for old events", function() {
it(
"should call setStateEvents on the right RoomState with the right " + "forwardLooking value for old events",
function () {
const events = [
utils.mkMembership({
room: roomId, mship: "invite", user: userB, skey: userA, event: true,
room: roomId,
mship: "invite",
user: userB,
skey: userA,
event: true,
}),
utils.mkEvent({
type: "m.room.name", room: roomId, user: userB, event: true,
type: "m.room.name",
room: roomId,
user: userB,
event: true,
content: {
name: "New room",
},
@ -313,40 +349,48 @@ describe("EventTimeline", function() {
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.addEvent(events[1], { toStartOfTimeline: true });
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).
toHaveBeenCalledWith([events[0]], { timelineWasEmpty: undefined });
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).
toHaveBeenCalledWith([events[1]], { timelineWasEmpty: undefined });
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).toHaveBeenCalledWith([events[0]], {
timelineWasEmpty: undefined,
});
expect(timeline.getState(EventTimeline.BACKWARDS)!.setStateEvents).toHaveBeenCalledWith([events[1]], {
timelineWasEmpty: undefined,
});
expect(events[0].forwardLooking).toBe(false);
expect(events[1].forwardLooking).toBe(false);
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).
not.toHaveBeenCalled();
});
expect(timeline.getState(EventTimeline.FORWARDS)!.setStateEvents).not.toHaveBeenCalled();
},
);
it("Make sure legacy overload passing options directly as parameters still works", () => {
expect(() => timeline.addEvent(events[0], { toStartOfTimeline: true })).not.toThrow();
// @ts-ignore stateContext is not a valid param
expect(() => timeline.addEvent(events[0], { stateContext: new RoomState(roomId) })).not.toThrow();
expect(() => timeline.addEvent(events[0],
{ toStartOfTimeline: false, roomState: new RoomState(roomId) },
)).not.toThrow();
expect(() =>
timeline.addEvent(events[0], { toStartOfTimeline: false, roomState: new RoomState(roomId) }),
).not.toThrow();
});
});
describe("removeEvent", function () {
const events = [
utils.mkMessage({
room: roomId, user: userA, msg: "hungry hungry hungry",
room: roomId,
user: userA,
msg: "hungry hungry hungry",
event: true,
}),
utils.mkMessage({
room: roomId, user: userB, msg: "nom nom nom",
room: roomId,
user: userB,
msg: "nom nom nom",
event: true,
}),
utils.mkMessage({
room: roomId, user: userB, msg: "piiie",
room: roomId,
user: userB,
msg: "piiie",
event: true,
}),
];
@ -384,8 +428,7 @@ describe("EventTimeline", function() {
// this is basically https://github.com/vector-im/vector-web/issues/937
// - removing the last event got baseIndex into such a state that
// further addEvent(ev, false) calls made the index increase.
it("should not make baseIndex assplode when removing the last event",
function() {
it("should not make baseIndex assplode when removing the last event", function () {
timeline.addEvent(events[0], { toStartOfTimeline: true });
timeline.removeEvent(events[0].getId()!);
const initialIndex = timeline.getBaseIndex();

View File

@ -1,15 +1,15 @@
import { RelationType } from "../../src";
import { FilterComponent } from "../../src/filter-component";
import { mkEvent } from '../test-utils/test-utils';
import { mkEvent } from "../test-utils/test-utils";
describe("Filter Component", function () {
describe("types", function () {
it("should filter out events with other types", function () {
const filter = new FilterComponent({ types: ['m.room.message'] });
const filter = new FilterComponent({ types: ["m.room.message"] });
const event = mkEvent({
type: 'm.room.member',
type: "m.room.member",
content: {},
room: 'roomId',
room: "roomId",
event: true,
});
@ -19,11 +19,11 @@ describe("Filter Component", function() {
});
it("should validate events with the same type", function () {
const filter = new FilterComponent({ types: ['m.room.message'] });
const filter = new FilterComponent({ types: ["m.room.message"] });
const event = mkEvent({
type: 'm.room.message',
type: "m.room.message",
content: {},
room: 'roomId',
room: "roomId",
event: true,
});
@ -33,16 +33,19 @@ describe("Filter Component", function() {
});
it("should filter out events by relation participation", function () {
const currentUserId = '@me:server.org';
const filter = new FilterComponent({
const currentUserId = "@me:server.org";
const filter = new FilterComponent(
{
related_by_senders: [currentUserId],
}, currentUserId);
},
currentUserId,
);
const threadRootNotParticipated = mkEvent({
type: 'm.room.message',
type: "m.room.message",
content: {},
room: 'roomId',
user: '@someone-else:server.org',
room: "roomId",
user: "@someone-else:server.org",
event: true,
unsigned: {
"m.relations": {
@ -58,13 +61,16 @@ describe("Filter Component", function() {
});
it("should keep events by relation participation", function () {
const currentUserId = '@me:server.org';
const filter = new FilterComponent({
const currentUserId = "@me:server.org";
const filter = new FilterComponent(
{
related_by_senders: [currentUserId],
}, currentUserId);
},
currentUserId,
);
const threadRootParticipated = mkEvent({
type: 'm.room.message',
type: "m.room.message",
content: {},
unsigned: {
"m.relations": {
@ -74,8 +80,8 @@ describe("Filter Component", function() {
},
},
},
user: '@someone-else:server.org',
room: 'roomId',
user: "@someone-else:server.org",
room: "roomId",
event: true,
});
@ -88,9 +94,9 @@ describe("Filter Component", function() {
});
const referenceRelationEvent = mkEvent({
type: 'm.room.message',
type: "m.room.message",
content: {},
room: 'roomId',
room: "roomId",
event: true,
unsigned: {
"m.relations": {
@ -108,7 +114,7 @@ describe("Filter Component", function() {
});
const threadRootEvent = mkEvent({
type: 'm.room.message',
type: "m.room.message",
content: {},
unsigned: {
"m.relations": {
@ -118,22 +124,22 @@ describe("Filter Component", function() {
},
},
},
room: 'roomId',
room: "roomId",
event: true,
});
const eventWithMultipleRelations = mkEvent({
"type": "m.room.message",
"content": {},
"unsigned": {
type: "m.room.message",
content: {},
unsigned: {
"m.relations": {
"testtesttest": {},
"m.annotation": {
"chunk": [
chunk: [
{
"type": "m.reaction",
"key": "🤫",
"count": 1,
type: "m.reaction",
key: "🤫",
count: 1,
},
],
},
@ -143,20 +149,20 @@ describe("Filter Component", function() {
},
},
},
"room": 'roomId',
"event": true,
room: "roomId",
event: true,
});
const noMatchEvent = mkEvent({
"type": "m.room.message",
"content": {},
"unsigned": {
type: "m.room.message",
content: {},
unsigned: {
"m.relations": {
"testtesttest": {},
testtesttest: {},
},
},
"room": 'roomId',
"event": true,
room: "roomId",
event: true,
});
expect(filter.check(threadRootEvent)).toBe(true);

View File

@ -56,7 +56,7 @@ describe("Filter", function() {
describe("setDefinition/getDefinition", function () {
it("should set and get the filter body", function () {
const definition = {
event_format: "client" as IFilterDefinition['event_format'],
event_format: "client" as IFilterDefinition["event_format"],
};
filter.setDefinition(definition);
expect(filter.getDefinition()).toEqual(definition);

View File

@ -65,8 +65,9 @@ describe("FetchHttpApi", () => {
describe("idServerRequest", () => {
it("should throw if no idBaseUrl", () => {
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
expect(() => api.idServerRequest(Method.Get, "/test", {}, IdentityPrefix.V2))
.toThrow("No identity server base URL set");
expect(() => api.idServerRequest(Method.Get, "/test", {}, IdentityPrefix.V2)).toThrow(
"No identity server base URL set",
);
});
it("should send params as query string for GET requests", () => {
@ -105,9 +106,11 @@ describe("FetchHttpApi", () => {
const text = "418 I'm a teapot";
const fetchFn = jest.fn().mockResolvedValue({ ok: true, text: jest.fn().mockResolvedValue(text) });
const api = new FetchHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix, fetchFn, onlyData: true });
await expect(api.requestOtherUrl(Method.Get, "http://url", undefined, {
await expect(
api.requestOtherUrl(Method.Get, "http://url", undefined, {
json: false,
})).resolves.toBe(text);
}),
).resolves.toBe(text);
});
it("should send token via query params if useAuthorizationHeader=false", () => {
@ -207,10 +210,12 @@ describe("FetchHttpApi", () => {
return name === "Content-Type" ? "application/json" : null;
},
},
text: jest.fn().mockResolvedValue(JSON.stringify({
text: jest.fn().mockResolvedValue(
JSON.stringify({
errcode: "M_CONSENT_NOT_GIVEN",
error: "Ye shall ask for consent",
})),
}),
),
});
const emitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
const api = new FetchHttpApi(emitter, { baseUrl, prefix, fetchFn });

View File

@ -85,8 +85,10 @@ describe("MatrixHttpApi", () => {
useAuthorizationHeader: false,
});
upload = api.uploadContent({} as File);
expect(xhr.open)
.toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload?access_token=token");
expect(xhr.open).toHaveBeenCalledWith(
Method.Post,
baseUrl.toLowerCase() + "/_matrix/media/r0/upload?access_token=token",
);
expect(xhr.setRequestHeader).not.toHaveBeenCalledWith("Authorization");
});
@ -104,8 +106,10 @@ describe("MatrixHttpApi", () => {
it("should include filename by default", () => {
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
upload = api.uploadContent({} as File, { name: "name" });
expect(xhr.open)
.toHaveBeenCalledWith(Method.Post, baseUrl.toLowerCase() + "/_matrix/media/r0/upload?filename=name");
expect(xhr.open).toHaveBeenCalledWith(
Method.Post,
baseUrl.toLowerCase() + "/_matrix/media/r0/upload?filename=name",
);
});
it("should allow not sending the filename", () => {
@ -216,9 +220,9 @@ describe("MatrixHttpApi", () => {
it("should return active uploads in `getCurrentUploads`", () => {
const api = new MatrixHttpApi(new TypedEventEmitter<any, any>(), { baseUrl, prefix });
upload = api.uploadContent({} as File);
expect(api.getCurrentUploads().find(u => u.promise === upload)).toBeTruthy();
expect(api.getCurrentUploads().find((u) => u.promise === upload)).toBeTruthy();
api.cancelUpload(upload);
expect(api.getCurrentUploads().find(u => u.promise === upload)).toBeFalsy();
expect(api.getCurrentUploads().find((u) => u.promise === upload)).toBeFalsy();
});
it("should return expected object from `getContentUri`", () => {

View File

@ -51,10 +51,7 @@ describe("anySignal", () => {
jest.useFakeTimers();
it("should fire when any signal fires", () => {
const { signal } = anySignal([
timeoutSignal(3000),
timeoutSignal(2000),
]);
const { signal } = anySignal([timeoutSignal(3000), timeoutSignal(2000)]);
const onabort = jest.fn();
signal.onabort = onabort;
@ -67,10 +64,7 @@ describe("anySignal", () => {
});
it("should cleanup when instructed", () => {
const { signal, cleanup } = anySignal([
timeoutSignal(3000),
timeoutSignal(2000),
]);
const { signal, cleanup } = anySignal([timeoutSignal(3000), timeoutSignal(2000)]);
const onabort = jest.fn();
signal.onabort = onabort;
@ -93,43 +87,76 @@ describe("anySignal", () => {
describe("parseErrorResponse", () => {
it("should resolve Matrix Errors from XHR", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
getResponseHeader(name: string): string | null {
return name === "Content-Type" ? "application/json" : null;
},
status: 500,
} as XMLHttpRequest, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
} as XMLHttpRequest,
'{"errcode": "TEST"}',
),
).toStrictEqual(
new MatrixError(
{
errcode: "TEST",
}, 500));
},
500,
),
);
});
it("should resolve Matrix Errors from fetch", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
headers: {
get(name: string): string | null {
return name === "Content-Type" ? "application/json" : null;
},
},
status: 500,
} as Response, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
} as Response,
'{"errcode": "TEST"}',
),
).toStrictEqual(
new MatrixError(
{
errcode: "TEST",
}, 500));
},
500,
),
);
});
it("should resolve Matrix Errors from XHR with urls", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
responseURL: "https://example.com",
getResponseHeader(name: string): string | null {
return name === "Content-Type" ? "application/json" : null;
},
status: 500,
} as XMLHttpRequest, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
} as XMLHttpRequest,
'{"errcode": "TEST"}',
),
).toStrictEqual(
new MatrixError(
{
errcode: "TEST",
}, 500, "https://example.com"));
},
500,
"https://example.com",
),
);
});
it("should resolve Matrix Errors from fetch with urls", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
url: "https://example.com",
headers: {
get(name: string): string | null {
@ -137,9 +164,18 @@ describe("parseErrorResponse", () => {
},
},
status: 500,
} as Response, '{"errcode": "TEST"}')).toStrictEqual(new MatrixError({
} as Response,
'{"errcode": "TEST"}',
),
).toStrictEqual(
new MatrixError(
{
errcode: "TEST",
}, 500, "https://example.com"));
},
500,
"https://example.com",
),
);
});
it("should set a sensible default error message on MatrixError", () => {
@ -152,37 +188,51 @@ describe("parseErrorResponse", () => {
});
it("should handle no type gracefully", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
headers: {
get(name: string): string | null {
return null;
},
},
status: 500,
} as Response, '{"errcode": "TEST"}')).toStrictEqual(new HTTPError("Server returned 500 error", 500));
} as Response,
'{"errcode": "TEST"}',
),
).toStrictEqual(new HTTPError("Server returned 500 error", 500));
});
it("should handle invalid type gracefully", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
headers: {
get(name: string): string | null {
return name === "Content-Type" ? " " : null;
},
},
status: 500,
} as Response, '{"errcode": "TEST"}'))
.toStrictEqual(new Error("Error parsing Content-Type ' ': TypeError: invalid media type"));
} as Response,
'{"errcode": "TEST"}',
),
).toStrictEqual(new Error("Error parsing Content-Type ' ': TypeError: invalid media type"));
});
it("should handle plaintext errors", () => {
expect(parseErrorResponse({
expect(
parseErrorResponse(
{
headers: {
get(name: string): string | null {
return name === "Content-Type" ? "text/plain" : null;
},
},
status: 418,
} as Response, "I'm a teapot")).toStrictEqual(new HTTPError("Server returned 418 error: I'm a teapot", 418));
} as Response,
"I'm a teapot",
),
).toStrictEqual(new HTTPError("Server returned 418 error: I'm a teapot", 418));
});
});

View File

@ -44,9 +44,7 @@ describe("InteractiveAuth", () => {
requestEmailToken: jest.fn(),
authData: {
session: "sessionId",
flows: [
{ stages: [AuthType.Password] },
],
flows: [{ stages: [AuthType.Password] }],
params: {
[AuthType.Password]: { param: "aa" },
},
@ -60,7 +58,7 @@ describe("InteractiveAuth", () => {
// first we expect a call here
stateUpdated.mockImplementation((stage) => {
logger.log('aaaa');
logger.log("aaaa");
expect(stage).toEqual(AuthType.Password);
ia.submitAuthDict({
type: AuthType.Password,
@ -68,9 +66,9 @@ describe("InteractiveAuth", () => {
});
// .. which should trigger a call here
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
doRequest.mockImplementation(async (authData) => {
logger.log('cccc');
logger.log("cccc");
expect(authData).toEqual({
session: "sessionId",
type: AuthType.Password,
@ -95,9 +93,7 @@ describe("InteractiveAuth", () => {
requestEmailToken: jest.fn(),
authData: {
session: "sessionId",
flows: [
{ stages: [AuthType.Password] },
],
flows: [{ stages: [AuthType.Password] }],
errcode: "MockError0",
params: {
[AuthType.Password]: { param: "aa" },
@ -112,7 +108,7 @@ describe("InteractiveAuth", () => {
// first we expect a call here
stateUpdated.mockImplementation((stage) => {
logger.log('aaaa');
logger.log("aaaa");
expect(stage).toEqual(AuthType.Password);
ia.submitAuthDict({
type: AuthType.Password,
@ -120,9 +116,9 @@ describe("InteractiveAuth", () => {
});
// .. which should trigger a call here
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
doRequest.mockImplementation(async (authData) => {
logger.log('cccc');
logger.log("cccc");
expect(authData).toEqual({
session: "sessionId",
type: AuthType.Password,
@ -146,12 +142,10 @@ describe("InteractiveAuth", () => {
stateUpdated,
requestEmailToken,
matrixClient: getFakeClient(),
emailSid: 'myEmailSid',
emailSid: "myEmailSid",
authData: {
session: "sessionId",
flows: [
{ stages: [AuthType.Email, AuthType.Password] },
],
flows: [{ stages: [AuthType.Email, AuthType.Password] }],
params: {
[AuthType.Email]: { param: "aa" },
[AuthType.Password]: { param: "bb" },
@ -166,7 +160,7 @@ describe("InteractiveAuth", () => {
// first we expect a call here
stateUpdated.mockImplementation((stage) => {
logger.log('husky');
logger.log("husky");
expect(stage).toEqual(AuthType.Email);
ia.submitAuthDict({
type: AuthType.Email,
@ -174,9 +168,9 @@ describe("InteractiveAuth", () => {
});
// .. which should trigger a call here
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
doRequest.mockImplementation(async (authData) => {
logger.log('barfoo');
logger.log("barfoo");
expect(authData).toEqual({
session: "sessionId",
type: AuthType.Email,
@ -211,20 +205,21 @@ describe("InteractiveAuth", () => {
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
const err = new MatrixError(
{
session: "sessionId",
flows: [
{ stages: [AuthType.Password] },
],
flows: [{ stages: [AuthType.Password] }],
params: {
[AuthType.Password]: { param: "aa" },
},
}, 401);
},
401,
);
throw err;
});
// .. which should be followed by a call to stateUpdated
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
stateUpdated.mockImplementation((stage) => {
expect(stage).toEqual(AuthType.Password);
expect(ia.getSessionId()).toEqual("sessionId");
@ -272,20 +267,21 @@ describe("InteractiveAuth", () => {
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
const err = new MatrixError(
{
session: "sessionId",
flows: [
{ stages: [AuthType.Password] },
],
flows: [{ stages: [AuthType.Password] }],
params: {
[AuthType.Password]: { param: "aa" },
},
}, 401);
},
401,
);
throw err;
});
// .. which should be followed by a call to stateUpdated
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
stateUpdated.mockImplementation((stage) => {
expect(stage).toEqual(AuthType.Password);
expect(ia.getSessionId()).toEqual("sessionId");
@ -329,19 +325,20 @@ describe("InteractiveAuth", () => {
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual(null); // first request should be null
const err = new MatrixError({
const err = new MatrixError(
{
session: "sessionId",
flows: [],
params: {
[AuthType.Password]: { param: "aa" },
},
}, 401);
},
401,
);
throw err;
});
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(
new Error('No appropriate authentication flow found'),
);
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(new Error("No appropriate authentication flow found"));
});
it("should start an auth stage and reject if no auth flow but has session", async () => {
@ -354,15 +351,15 @@ describe("InteractiveAuth", () => {
doRequest,
stateUpdated,
requestEmailToken,
authData: {
},
authData: {},
sessionId: "sessionId",
});
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual({ "session": "sessionId" }); // has existing sessionId
const err = new MatrixError({
expect(authData).toEqual({ session: "sessionId" }); // has existing sessionId
const err = new MatrixError(
{
session: "sessionId",
flows: [],
params: {
@ -370,13 +367,13 @@ describe("InteractiveAuth", () => {
},
error: "Mock Error 1",
errcode: "MOCKERR1",
}, 401);
},
401,
);
throw err;
});
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(
new Error('No appropriate authentication flow found'),
);
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(new Error("No appropriate authentication flow found"));
});
it("should handle unexpected error types without data propery set", async () => {
@ -396,14 +393,12 @@ describe("InteractiveAuth", () => {
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual({ "session": "sessionId" }); // has existing sessionId
const err = new HTTPError('myerror', 401);
expect(authData).toEqual({ session: "sessionId" }); // has existing sessionId
const err = new HTTPError("myerror", 401);
throw err;
});
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(
new Error("myerror"),
);
await expect(ia.attemptAuth.bind(ia)).rejects.toThrow(new Error("myerror"));
});
it("should allow dummy auth", async () => {
@ -417,15 +412,13 @@ describe("InteractiveAuth", () => {
stateUpdated,
requestEmailToken,
authData: {
session: 'sessionId',
flows: [
{ stages: [AuthType.Dummy] },
],
session: "sessionId",
flows: [{ stages: [AuthType.Dummy] }],
params: {},
},
});
const requestRes = { "a": "b" };
const requestRes = { a: "b" };
doRequest.mockImplementation((authData) => {
logger.log("request1", authData);
expect(authData).toEqual({
@ -450,7 +443,9 @@ describe("InteractiveAuth", () => {
const ia = new InteractiveAuth({
matrixClient: getFakeClient(),
doRequest, stateUpdated, requestEmailToken,
doRequest,
stateUpdated,
requestEmailToken,
});
await ia.requestEmailToken();
@ -477,7 +472,9 @@ describe("InteractiveAuth", () => {
const ia = new InteractiveAuth({
matrixClient: getFakeClient(),
doRequest, stateUpdated, requestEmailToken,
doRequest,
stateUpdated,
requestEmailToken,
});
await ia.requestEmailToken();
@ -506,7 +503,9 @@ describe("InteractiveAuth", () => {
const ia = new InteractiveAuth({
matrixClient: getFakeClient(),
doRequest, stateUpdated, requestEmailToken,
doRequest,
stateUpdated,
requestEmailToken,
});
await expect(ia.requestEmailToken.bind(ia)).rejects.toThrowError("unspecific network error");
@ -520,7 +519,9 @@ describe("InteractiveAuth", () => {
const ia = new InteractiveAuth({
matrixClient: getFakeClient(),
doRequest, stateUpdated, requestEmailToken,
doRequest,
stateUpdated,
requestEmailToken,
});
await Promise.all([ia.requestEmailToken(), ia.requestEmailToken(), ia.requestEmailToken()]);
@ -536,7 +537,9 @@ describe("InteractiveAuth", () => {
const ia = new InteractiveAuth({
matrixClient: getFakeClient(),
doRequest, stateUpdated, requestEmailToken,
doRequest,
stateUpdated,
requestEmailToken,
});
await ia.requestEmailToken();

View File

@ -16,15 +16,13 @@ limitations under the License.
import { LocalNotificationSettings } from "../../src/@types/local_notifications";
import { LOCAL_NOTIFICATION_SETTINGS_PREFIX, MatrixClient } from "../../src/matrix";
import { TestClient } from '../TestClient';
import { TestClient } from "../TestClient";
let client: MatrixClient;
describe("Local notification settings", () => {
beforeEach(() => {
client = (new TestClient(
"@alice:matrix.org", "123", undefined, undefined, undefined,
)).client;
client = new TestClient("@alice:matrix.org", "123", undefined, undefined, undefined).client;
client.setAccountData = jest.fn();
});

View File

@ -27,11 +27,11 @@ import { MsgType } from "../../src/@types/event";
describe("Location", function () {
const defaultContent = {
"body": "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
"msgtype": "m.location",
"geo_uri": "geo:-36.24484561954707,175.46884959563613;u=10",
[M_LOCATION.name]: { "uri": "geo:-36.24484561954707,175.46884959563613;u=10", "description": null },
[M_ASSET.name]: { "type": "m.self" },
body: "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
msgtype: "m.location",
geo_uri: "geo:-36.24484561954707,175.46884959563613;u=10",
[M_LOCATION.name]: { uri: "geo:-36.24484561954707,175.46884959563613;u=10", description: null },
[M_ASSET.name]: { type: "m.self" },
[TEXT_NODE_TYPE.name]: "Location geo:-36.24484561954707,175.46884959563613;u=10 at 2022-03-09T11:01:52.443Z",
[M_TIMESTAMP.name]: 1646823712443,
} as any;
@ -44,12 +44,14 @@ describe("Location", function() {
const legacyEventContent = {
// eslint-disable-next-line camelcase
body, msgtype, geo_uri,
body,
msgtype,
geo_uri,
} as LocationEventWireContent;
it("should create a valid location with defaults", function () {
const loc = makeLocationContent(undefined, "geo:foo", 134235435);
expect(loc.body).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(loc.body).toEqual("User Location geo:foo at 1970-01-02T13:17:15.435Z");
expect(loc.msgtype).toEqual(MsgType.Location);
expect(loc.geo_uri).toEqual("geo:foo");
expect(M_LOCATION.findIn(loc)).toEqual({
@ -57,13 +59,12 @@ describe("Location", function() {
description: undefined,
});
expect(M_ASSET.findIn(loc)).toEqual({ type: LocationAssetType.Self });
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual('User Location geo:foo at 1970-01-02T13:17:15.435Z');
expect(TEXT_NODE_TYPE.findIn(loc)).toEqual("User Location geo:foo at 1970-01-02T13:17:15.435Z");
expect(M_TIMESTAMP.findIn(loc)).toEqual(134235435);
});
it("should create a valid location with explicit properties", function () {
const loc = makeLocationContent(
undefined, "geo:bar", 134235436, "desc", LocationAssetType.Pin);
const loc = makeLocationContent(undefined, "geo:bar", 134235436, "desc", LocationAssetType.Pin);
expect(loc.body).toEqual('Location "desc" geo:bar at 1970-01-02T13:17:15.436Z');
expect(loc.msgtype).toEqual(MsgType.Location);
@ -77,19 +78,19 @@ describe("Location", function() {
expect(M_TIMESTAMP.findIn(loc)).toEqual(134235436);
});
it('parses backwards compatible event correctly', () => {
it("parses backwards compatible event correctly", () => {
const eventContent = parseLocationEvent(backwardsCompatibleEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses modern correctly', () => {
it("parses modern correctly", () => {
const eventContent = parseLocationEvent(modernEventContent);
expect(eventContent).toEqual(backwardsCompatibleEventContent);
});
it('parses legacy event correctly', () => {
it("parses legacy event correctly", () => {
const eventContent = parseLocationEvent(legacyEventContent);
const {

View File

@ -1,7 +1,7 @@
import { SSOAction } from '../../src/@types/auth';
import { TestClient } from '../TestClient';
import { SSOAction } from "../../src/@types/auth";
import { TestClient } from "../TestClient";
describe('Login request', function() {
describe("Login request", function () {
let client: TestClient;
beforeEach(function () {
@ -15,16 +15,16 @@ describe('Login request', function() {
it('should store "access_token" and "user_id" if in response', async function () {
const response = { user_id: 1, access_token: Date.now().toString(16) };
client.httpBackend.when('POST', '/login').respond(200, response);
client.httpBackend.flush('/login', 1, 100);
await client.client.login('m.login.any', { user: 'test', password: '12312za' });
client.httpBackend.when("POST", "/login").respond(200, response);
client.httpBackend.flush("/login", 1, 100);
await client.client.login("m.login.any", { user: "test", password: "12312za" });
expect(client.client.getAccessToken()).toBe(response.access_token);
expect(client.client.getUserId()).toBe(response.user_id);
});
});
describe('SSO login URL', function() {
describe("SSO login URL", function () {
let client: TestClient;
beforeEach(function () {
@ -35,25 +35,25 @@ describe('SSO login URL', function() {
client.stop();
});
describe('SSOAction', function() {
describe("SSOAction", function () {
const redirectUri = "https://test.com/foo";
it('No action', function() {
it("No action", function () {
const urlString = client.client.getSsoLoginUrl(redirectUri, undefined, undefined, undefined);
const url = new URL(urlString);
expect(url.searchParams.has('org.matrix.msc3824.action')).toBe(false);
expect(url.searchParams.has("org.matrix.msc3824.action")).toBe(false);
});
it('register', function() {
it("register", function () {
const urlString = client.client.getSsoLoginUrl(redirectUri, undefined, undefined, SSOAction.REGISTER);
const url = new URL(urlString);
expect(url.searchParams.get('org.matrix.msc3824.action')).toEqual('register');
expect(url.searchParams.get("org.matrix.msc3824.action")).toEqual("register");
});
it('login', function() {
it("login", function () {
const urlString = client.client.getSsoLoginUrl(redirectUri, undefined, undefined, SSOAction.LOGIN);
const url = new URL(urlString);
expect(url.searchParams.get('org.matrix.msc3824.action')).toEqual('login');
expect(url.searchParams.get("org.matrix.msc3824.action")).toEqual("login");
});
});
});

View File

@ -38,7 +38,8 @@ import { makeBeaconInfoContent } from "../../src/content-helpers";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import {
ContentHelpers,
EventTimeline, ICreateRoomOpts,
EventTimeline,
ICreateRoomOpts,
IRequestOpts,
MatrixError,
MatrixHttpApi,
@ -137,13 +138,20 @@ describe("MatrixClient", function() {
});
}
const next = httpLookups.shift();
const logLine = (
"MatrixClient[UT] RECV " + method + " " + path + " " +
"EXPECT " + (next ? next.method : next) + " " + (next ? next.path : next)
);
const logLine =
"MatrixClient[UT] RECV " +
method +
" " +
path +
" " +
"EXPECT " +
(next ? next.method : next) +
" " +
(next ? next.path : next);
logger.log(logLine);
if (!next) { // no more things to return
if (!next) {
// no more things to return
if (pendingLookup) {
if (pendingLookup.method === method && pendingLookup.path === path) {
return pendingLookup.promise;
@ -159,10 +167,7 @@ describe("MatrixClient", function() {
return pendingLookup.promise;
}
if (next.path === path && next.method === method) {
logger.log(
"MatrixClient[UT] Matched. Returning " +
(next.error ? "BAD" : "GOOD") + " response",
);
logger.log("MatrixClient[UT] Matched. Returning " + (next.error ? "BAD" : "GOOD") + " response");
if (next.expectBody) {
expect(data).toEqual(next.expectBody);
}
@ -207,12 +212,7 @@ describe("MatrixClient", function() {
userId: userId,
});
// 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"] as const).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixHttpApi<any>);
@ -221,21 +221,34 @@ describe("MatrixClient", function() {
}
beforeEach(function () {
scheduler = ([
"getQueueForEvent",
"queueEvent",
"removeEventFromQueue",
"setProcessFunction",
] as const).reduce((r, k) => {
scheduler = (["getQueueForEvent", "queueEvent", "removeEventFromQueue", "setProcessFunction"] as const).reduce(
(r, k) => {
r[k] = jest.fn();
return r;
}, {} as MatrixScheduler);
store = ([
"getRoom", "getRooms", "getUser", "getSyncToken", "scrollback",
"save", "wantsSave", "setSyncToken", "storeEvents", "storeRoom", "storeUser",
"getFilterIdByName", "setFilterIdByName", "getFilter", "storeFilter",
"startup", "deleteAllData",
] as const).reduce((r, k) => {
},
{} as MatrixScheduler,
);
store = (
[
"getRoom",
"getRooms",
"getUser",
"getSyncToken",
"scrollback",
"save",
"wantsSave",
"setSyncToken",
"storeEvents",
"storeRoom",
"storeUser",
"getFilterIdByName",
"setFilterIdByName",
"getFilter",
"storeFilter",
"startup",
"deleteAllData",
] as const
).reduce((r, k) => {
r[k] = jest.fn();
return r;
}, {} as Store);
@ -276,12 +289,14 @@ describe("MatrixClient", function() {
it("overload without threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: content,
}];
},
];
await client.sendEvent(roomId, EventType.RoomMessage, { ...content }, txnId);
});
@ -289,12 +304,14 @@ describe("MatrixClient", function() {
it("overload with null threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: content,
}];
},
];
await client.sendEvent(roomId, null, EventType.RoomMessage, { ...content }, txnId);
});
@ -303,19 +320,21 @@ describe("MatrixClient", function() {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
const threadId = "$threadId:server";
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
expectBody: {
...content,
"m.relates_to": {
"event_id": threadId,
"is_falling_back": true,
"rel_type": "m.thread",
event_id: threadId,
is_falling_back: true,
rel_type: "m.thread",
},
},
}];
},
];
await client.sendEvent(roomId, threadId, EventType.RoomMessage, { ...content }, txnId);
});
@ -331,7 +350,8 @@ describe("MatrixClient", function() {
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
@ -346,7 +366,8 @@ describe("MatrixClient", function() {
"rel_type": "m.thread",
},
},
}];
},
];
await client.sendEvent(roomId, threadId, EventType.RoomMessage, { ...content }, txnId);
});
@ -371,7 +392,8 @@ describe("MatrixClient", function() {
const rootEvent = new MatrixEvent({ event_id: threadId });
room.createThread(threadId, rootEvent, [rootEvent], false);
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/send/m.room.message/${txnId}`,
data: { event_id: eventId },
@ -386,7 +408,8 @@ describe("MatrixClient", function() {
"rel_type": "m.thread",
},
},
}];
},
];
await client.sendEvent(roomId, threadId, EventType.RoomMessage, { ...content }, txnId);
});
@ -573,14 +596,11 @@ describe("MatrixClient", function() {
});
it("should not POST /filter if a matching filter already exists", async function () {
httpLookups = [
PUSH_RULES_RESPONSE,
SYNC_RESPONSE,
];
httpLookups = [PUSH_RULES_RESPONSE, SYNC_RESPONSE];
const filterId = "ehfewf";
mocked(store.getFilterIdByName).mockReturnValue(filterId);
const filter = new Filter("0", filterId);
filter.setDefinition({ "room": { "timeline": { "limit": 8 } } });
filter.setDefinition({ room: { timeline: { limit: 8 } } });
mocked(store.getFilter).mockReturnValue(filter);
const syncPromise = new Promise<void>((resolve, reject) => {
client.on(ClientEvent.Sync, function syncListener(state) {
@ -618,21 +638,19 @@ describe("MatrixClient", function() {
});
describe("getOrCreateFilter", function () {
it("should POST createFilter if no id is present in localStorage", function() {
});
it("should use an existing filter if id is present in localStorage", function() {
});
it("should POST createFilter if no id is present in localStorage", function () {});
it("should use an existing filter if id is present in localStorage", function () {});
it("should handle localStorage filterId missing from the server", function (done) {
function getFilterName(userId: string, suffix?: string) {
// scope this on the user ID because people may login on many accounts
// and they all need to be stored!
return "FILTER_SYNC_" + userId + (suffix ? "_" + suffix : "");
}
const invalidFilterId = 'invalidF1lt3r';
const invalidFilterId = "invalidF1lt3r";
httpLookups = [];
httpLookups.push({
method: "GET",
path: FILTER_PATH + '/' + invalidFilterId,
path: FILTER_PATH + "/" + invalidFilterId,
error: {
errcode: "M_UNKNOWN",
name: "M_UNKNOWN",
@ -666,7 +684,9 @@ describe("MatrixClient", function() {
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push({
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
method: "POST",
path: FILTER_PATH,
error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push(FILTER_RESPONSE);
httpLookups.push(SYNC_RESPONSE);
@ -689,18 +709,20 @@ describe("MatrixClient", function() {
it("should work on /sync", function (done) {
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
method: "GET",
path: "/sync",
error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
method: "GET",
path: "/sync",
data: SYNC_DATA,
});
client.on(ClientEvent.Sync, function syncListener(state) {
if (state === "ERROR" && httpLookups.length > 0) {
expect(httpLookups.length).toEqual(1);
expect(client.retryImmediately()).toBe(
true,
);
expect(client.retryImmediately()).toBe(true);
jest.advanceTimersByTime(1);
} else if (state === "RECONNECTING" && httpLookups.length > 0) {
jest.advanceTimersByTime(10000);
@ -715,7 +737,9 @@ describe("MatrixClient", function() {
it("should work on /pushrules", function (done) {
httpLookups = [];
httpLookups.push({
method: "GET", path: "/pushrules/", error: { errcode: "NOPE_NOPE_NOPE" },
method: "GET",
path: "/pushrules/",
error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
@ -742,9 +766,7 @@ describe("MatrixClient", function() {
function syncChecker(expectedStates: [string, string | null][], done: Function) {
return function syncListener(state: SyncState, old: SyncState | null) {
const expected = expectedStates.shift();
logger.log(
"'sync' curr=%s old=%s EXPECT=%s", state, old, expected,
);
logger.log("'sync' curr=%s old=%s EXPECT=%s", state, old, expected);
if (!expected) {
done();
return;
@ -772,7 +794,9 @@ describe("MatrixClient", function() {
httpLookups = [];
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push({
method: "POST", path: FILTER_PATH, error: { errcode: "NOPE_NOPE_NOPE" },
method: "POST",
path: FILTER_PATH,
error: { errcode: "NOPE_NOPE_NOPE" },
});
expectedStates.push(["ERROR", null]);
client.on(ClientEvent.Sync, syncChecker(expectedStates, done));
@ -789,17 +813,24 @@ describe("MatrixClient", function() {
httpLookups.push(PUSH_RULES_RESPONSE);
httpLookups.push(FILTER_RESPONSE);
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NOPE_NOPE_NOPE" },
method: "GET",
path: "/sync",
error: { errcode: "NOPE_NOPE_NOPE" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
method: "GET",
path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH, data: {},
method: "GET",
path: KEEP_ALIVE_PATH,
data: {},
});
httpLookups.push({
method: "GET", path: "/sync", data: SYNC_DATA,
method: "GET",
path: "/sync",
data: SYNC_DATA,
});
expectedStates.push(["RECONNECTING", null]);
@ -821,10 +852,13 @@ describe("MatrixClient", function() {
acceptKeepalives = false;
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
method: "GET",
path: "/sync",
error: { errcode: "NONONONONO" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
method: "GET",
path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
@ -839,7 +873,9 @@ describe("MatrixClient", function() {
xit("should transition ERROR -> SYNCING after /sync if prev failed", function (done) {
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
method: "GET",
path: "/sync",
error: { errcode: "NONONONONO" },
});
httpLookups.push(SYNC_RESPONSE);
@ -866,14 +902,18 @@ describe("MatrixClient", function() {
acceptKeepalives = false;
const expectedStates: [string, string | null][] = [];
httpLookups.push({
method: "GET", path: "/sync", error: { errcode: "NONONONONO" },
method: "GET",
path: "/sync",
error: { errcode: "NONONONONO" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
method: "GET",
path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
httpLookups.push({
method: "GET", path: KEEP_ALIVE_PATH,
method: "GET",
path: KEEP_ALIVE_PATH,
error: { errcode: "KEEPALIVE_FAIL" },
});
@ -891,7 +931,8 @@ describe("MatrixClient", function() {
const roomId = "!foo:bar";
it("should send an invite HTTP POST", function () {
httpLookups = [{
httpLookups = [
{
method: "POST",
path: "/rooms/!foo%3Abar/invite",
data: {},
@ -900,7 +941,8 @@ describe("MatrixClient", function() {
medium: "email",
address: "alice@gmail.com",
},
}];
},
];
client.inviteByEmail(roomId, "alice@gmail.com");
expect(httpLookups.length).toEqual(0);
});
@ -919,20 +961,21 @@ describe("MatrixClient", function() {
expect(httpLookups.length).toBe(0);
});
xit("should be able to peek into a room using peekInRoom", function(done) {
});
xit("should be able to peek into a room using peekInRoom", function (done) {});
});
describe("getPresence", function () {
it("should send a presence HTTP GET", function () {
httpLookups = [{
httpLookups = [
{
method: "GET",
path: `/presence/${encodeURIComponent(userId)}/status`,
data: {
"presence": "unavailable",
"last_active_ago": 420845,
presence: "unavailable",
last_active_ago: 420845,
},
}];
},
];
client.getPresence(userId);
expect(httpLookups.length).toEqual(0);
});
@ -970,11 +1013,13 @@ describe("MatrixClient", function() {
it("overload without threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
},
];
await client.redactEvent(roomId, eventId, txnId);
});
@ -982,11 +1027,13 @@ describe("MatrixClient", function() {
it("overload with null threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
},
];
await client.redactEvent(roomId, null, eventId, txnId);
});
@ -994,11 +1041,13 @@ describe("MatrixClient", function() {
it("overload with threadId works", async () => {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
data: { event_id: eventId },
}];
},
];
await client.redactEvent(roomId, "$threadId:server", eventId, txnId);
});
@ -1007,12 +1056,14 @@ describe("MatrixClient", function() {
const eventId = "$eventId:example.org";
const txnId = client.makeTxnId();
const reason = "This is the redaction reason";
httpLookups = [{
httpLookups = [
{
method: "PUT",
path: `/rooms/${encodeURIComponent(roomId)}/redact/${encodeURIComponent(eventId)}/${txnId}`,
expectBody: { reason }, // NOT ENCRYPTED
data: { event_id: eventId },
}];
},
];
await client.redactEvent(roomId, eventId, txnId, { reason });
});
@ -1058,7 +1109,8 @@ describe("MatrixClient", function() {
expect(getRoomId).toEqual(roomId);
return mockRoom;
};
client.crypto = { // mock crypto
client.crypto = {
// mock crypto
encryptEvent: () => new Promise(() => {}),
stop: jest.fn(),
} as unknown as Crypto;
@ -1067,7 +1119,7 @@ describe("MatrixClient", function() {
function assertCancelled() {
expect(event.status).toBe(EventStatus.CANCELLED);
expect(client.scheduler?.removeEventFromQueue(event)).toBeFalsy();
expect(httpLookups.filter(h => h.path.includes("/send/")).length).toBe(0);
expect(httpLookups.filter((h) => h.path.includes("/send/")).length).toBe(0);
}
it("should cancel an event which is queued", () => {
@ -1105,22 +1157,22 @@ describe("MatrixClient", function() {
const room = new Room("!room1:matrix.org", client, userId);
const rootEvent = new MatrixEvent({
"content": {},
"origin_server_ts": 1,
"room_id": "!room1:matrix.org",
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
content: {},
origin_server_ts: 1,
room_id: "!room1:matrix.org",
sender: "@alice:matrix.org",
type: "m.room.message",
unsigned: {
"m.relations": {
"m.thread": {
"latest_event": {},
"count": 33,
"current_user_participated": false,
latest_event: {},
count: 33,
current_user_participated: false,
},
},
},
"event_id": "$ev1",
"user_id": "@alice:matrix.org",
event_id: "$ev1",
user_id: "@alice:matrix.org",
});
expect(rootEvent.isThreadRoot).toBe(true);
@ -1145,12 +1197,7 @@ describe("MatrixClient", function() {
const rpEvent = new MatrixEvent({ event_id: "read_private_event_id" });
client.getRoom = () => room;
client.setRoomReadMarkers(
"room_id",
"read_marker_event_id",
rrEvent,
rpEvent,
);
client.setRoomReadMarkers("room_id", "read_marker_event_id", rrEvent, rpEvent);
expect(client.setRoomReadMarkersHttpRequest).toHaveBeenCalledWith(
"room_id",
@ -1175,7 +1222,7 @@ describe("MatrixClient", function() {
});
describe("beacons", () => {
const roomId = '!room:server.org';
const roomId = "!room:server.org";
const content = makeBeaconInfoContent(100, true);
beforeEach(() => {
@ -1188,7 +1235,7 @@ describe("MatrixClient", function() {
// event type combined
const expectedEventType = M_BEACON_INFO.name;
const [method, path, queryParams, requestContent] = mocked(client.http.authedRequest).mock.calls[0];
expect(method).toBe('PUT');
expect(method).toBe("PUT");
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
`${encodeURIComponent(expectedEventType)}/${encodeURIComponent(userId)}`,
@ -1209,26 +1256,26 @@ describe("MatrixClient", function() {
expect(requestContent).toEqual(content);
});
describe('processBeaconEvents()', () => {
it('does nothing when events is falsy', () => {
describe("processBeaconEvents()", () => {
it("does nothing when events is falsy", () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
const roomStateProcessSpy = jest.spyOn(room.currentState, "processBeaconEvents");
client.processBeaconEvents(room, undefined);
expect(roomStateProcessSpy).not.toHaveBeenCalled();
});
it('does nothing when events is of length 0', () => {
it("does nothing when events is of length 0", () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
const roomStateProcessSpy = jest.spyOn(room.currentState, "processBeaconEvents");
client.processBeaconEvents(room, []);
expect(roomStateProcessSpy).not.toHaveBeenCalled();
});
it('calls room states processBeaconEvents with events', () => {
it("calls room states processBeaconEvents with events", () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
const roomStateProcessSpy = jest.spyOn(room.currentState, "processBeaconEvents");
const messageEvent = testUtils.mkMessage({ room: roomId, user: userId, event: true });
const beaconEvent = makeBeaconEvent(userId);
@ -1242,8 +1289,7 @@ describe("MatrixClient", function() {
describe("setRoomTopic", () => {
const roomId = "!foofoofoofoofoofoo:matrix.org";
const createSendStateEventMock = (topic: string, htmlTopic?: string) => {
return jest.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
return jest.fn().mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(roomId);
expect(eventType).toEqual(EventType.RoomTopic);
expect(content).toMatchObject(ContentHelpers.makeTopicContent(topic, htmlTopic));
@ -1275,13 +1321,13 @@ describe("MatrixClient", function() {
});
describe("setPassword", () => {
const auth = { session: 'abcdef', type: 'foo' };
const newPassword = 'newpassword';
const auth = { session: "abcdef", type: "foo" };
const newPassword = "newpassword";
const passwordTest = (expectedRequestContent: any) => {
const [method, path, queryParams, requestContent] = mocked(client.http.authedRequest).mock.calls[0];
expect(method).toBe('POST');
expect(path).toEqual('/account/password');
expect(method).toBe("POST");
expect(path).toEqual("/account/password");
expect(queryParams).toBeFalsy();
expect(requestContent).toEqual(expectedRequestContent);
};
@ -1334,7 +1380,7 @@ describe("MatrixClient", function() {
// Current version of the endpoint we support is v3
const [method, path, queryParams, data, opts] = mocked(client.http.authedRequest).mock.calls[0];
expect(data).toBeFalsy();
expect(method).toBe('GET');
expect(method).toBe("GET");
expect(path).toEqual(`/rooms/${encodeURIComponent(roomId)}/aliases`);
expect(opts).toMatchObject({ prefix: "/_matrix/client/v3" });
expect(queryParams).toBeFalsy();
@ -1397,11 +1443,17 @@ describe("MatrixClient", function() {
client.on(ClientEvent.TurnServers, onTurnServers);
expect(await client.checkTurnServers()).toBe(true);
client.off(ClientEvent.TurnServers, onTurnServers);
expect(events).toEqual([[[{
expect(events).toEqual([
[
[
{
urls: turnServer.uris,
username: turnServer.username,
credential: turnServer.password,
}]]]);
},
],
],
]);
});
it("emits an event when an error occurs", async () => {
@ -1477,7 +1529,7 @@ describe("MatrixClient", function() {
return {
getStateEvents: function (type) {
const store = state.get(type) || {};
return Object.keys(store).map(key => store[key]);
return Object.keys(store).map((key) => store[key]);
},
};
},

View File

@ -50,23 +50,27 @@ describe("MSC3089Branch", () => {
}
},
};
indexEvent = ({
indexEvent = {
getRoomId: () => branchRoomId,
getStateKey: () => fileEventId,
});
};
directory = new MSC3089TreeSpace(client, branchRoomId);
branch = new MSC3089Branch(client, indexEvent, directory);
branch2 = new MSC3089Branch(client, {
branch2 = new MSC3089Branch(
client,
{
getRoomId: () => branchRoomId,
getStateKey: () => fileEventId2,
} as MatrixEvent, directory);
} as MatrixEvent,
directory,
);
});
it('should know the file event ID', () => {
it("should know the file event ID", () => {
expect(branch.id).toEqual(fileEventId);
});
it('should know if the file is active or not', () => {
it("should know if the file is active or not", () => {
indexEvent.getContent = () => ({});
expect(branch.isActive).toBe(false);
indexEvent.getContent = () => ({ active: false });
@ -77,15 +81,16 @@ describe("MSC3089Branch", () => {
expect(branch.isActive).toBe(false);
});
it('should be able to delete the file', async () => {
it("should be able to delete the file", async () => {
const eventIdOrder = [fileEventId, fileEventId2];
const stateFn = jest.fn()
const stateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
expect(content).toMatchObject({});
expect(content['active']).toBeUndefined();
expect(content["active"]).toBeUndefined();
expect(stateKey).toEqual(eventIdOrder[stateFn.mock.calls.length - 1]);
return Promise.resolve(); // return value not used
@ -109,7 +114,7 @@ describe("MSC3089Branch", () => {
expect(redactFn).toHaveBeenCalledTimes(2);
});
it('should know its name', async () => {
it("should know its name", async () => {
const name = "My File.txt";
indexEvent.getContent = () => ({ active: true, name: name });
@ -118,10 +123,11 @@ describe("MSC3089Branch", () => {
expect(res).toEqual(name);
});
it('should be able to change its name', async () => {
it("should be able to change its name", async () => {
const name = "My File.txt";
indexEvent.getContent = () => ({ active: true, retained: true });
const stateFn = jest.fn()
const stateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
@ -141,7 +147,7 @@ describe("MSC3089Branch", () => {
expect(stateFn).toHaveBeenCalledTimes(1);
});
it('should be v1 by default', () => {
it("should be v1 by default", () => {
indexEvent.getContent = () => ({ active: true });
const res = branch.version;
@ -149,7 +155,7 @@ describe("MSC3089Branch", () => {
expect(res).toEqual(1);
});
it('should be vN when set', () => {
it("should be vN when set", () => {
indexEvent.getContent = () => ({ active: true, version: 3 });
const res = branch.version;
@ -157,7 +163,7 @@ describe("MSC3089Branch", () => {
expect(res).toEqual(3);
});
it('should be unlocked by default', async () => {
it("should be unlocked by default", async () => {
indexEvent.getContent = () => ({ active: true });
const res = branch.isLocked();
@ -165,7 +171,7 @@ describe("MSC3089Branch", () => {
expect(res).toEqual(false);
});
it('should use lock status from index event', async () => {
it("should use lock status from index event", async () => {
indexEvent.getContent = () => ({ active: true, locked: true });
const res = branch.isLocked();
@ -173,10 +179,11 @@ describe("MSC3089Branch", () => {
expect(res).toEqual(true);
});
it('should be able to change its locked status', async () => {
it("should be able to change its locked status", async () => {
const locked = true;
indexEvent.getContent = () => ({ active: true, retained: true });
const stateFn = jest.fn()
const stateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
@ -196,16 +203,17 @@ describe("MSC3089Branch", () => {
expect(stateFn).toHaveBeenCalledTimes(1);
});
it('should be able to return event information', async () => {
it("should be able to return event information", async () => {
const mxcLatter = "example.org/file";
const fileContent = { isFile: "not quite", url: "mxc://" + mxcLatter };
const fileEvent = { getId: () => fileEventId, getOriginalContent: () => ({ file: fileContent }) };
staticRoom.getUnfilteredTimelineSet = () => ({
staticRoom.getUnfilteredTimelineSet = () =>
({
findEventById: (eventId) => {
expect(eventId).toEqual(fileEventId);
return fileEvent;
},
}) as EventTimelineSet;
} as EventTimelineSet);
client.mxcUrlToHttp = (mxc: string) => {
expect(mxc).toEqual("mxc://" + mxcLatter);
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
@ -217,20 +225,21 @@ describe("MSC3089Branch", () => {
expect(res).toMatchObject({
info: fileContent,
// Escape regex from MDN guides: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
httpUrl: expect.stringMatching(`.+${mxcLatter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`),
httpUrl: expect.stringMatching(`.+${mxcLatter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`),
});
});
it('should be able to return the event object', async () => {
it("should be able to return the event object", async () => {
const mxcLatter = "example.org/file";
const fileContent = { isFile: "not quite", url: "mxc://" + mxcLatter };
const fileEvent = { getId: () => fileEventId, getOriginalContent: () => ({ file: fileContent }) };
staticRoom.getUnfilteredTimelineSet = () => ({
staticRoom.getUnfilteredTimelineSet = () =>
({
findEventById: (eventId) => {
expect(eventId).toEqual(fileEventId);
return fileEvent;
},
}) as EventTimelineSet;
} as EventTimelineSet);
client.mxcUrlToHttp = (mxc: string) => {
expect(mxc).toEqual("mxc://" + mxcLatter);
return `https://example.org/_matrix/media/v1/download/${mxcLatter}`;
@ -242,14 +251,15 @@ describe("MSC3089Branch", () => {
expect(res).toBe(fileEvent);
});
it('should create new versions of itself', async () => {
it("should create new versions of itself", async () => {
const canaryName = "canary";
const canaryContents = "contents go here";
const canaryFile = {} as IEncryptedFile;
const canaryAddl = { canary: true };
indexEvent.getContent = () => ({ active: true, retained: true });
const stateKeyOrder = [fileEventId2, fileEventId];
const stateFn = jest.fn()
const stateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(branchRoomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test that we're definitely using the unstable value
@ -273,12 +283,10 @@ describe("MSC3089Branch", () => {
});
client.sendStateEvent = stateFn;
const createFn = jest.fn().mockImplementation((
name: string,
contents: ArrayBuffer,
info: Partial<IEncryptedFile>,
addl: IContent,
) => {
const createFn = jest
.fn()
.mockImplementation(
(name: string, contents: ArrayBuffer, info: Partial<IEncryptedFile>, addl: IContent) => {
expect(name).toEqual(canaryName);
expect(contents).toBe(canaryContents);
expect(info).toBe(canaryFile);
@ -286,13 +294,14 @@ describe("MSC3089Branch", () => {
...canaryAddl,
"m.new_content": true,
"m.relates_to": {
"rel_type": RelationType.Replace,
"event_id": fileEventId,
rel_type: RelationType.Replace,
event_id: fileEventId,
},
});
return Promise.resolve({ event_id: fileEventId2 });
});
},
);
directory.createFile = createFn;
await branch.createNewVersion(canaryName, canaryContents, canaryFile, canaryAddl);
@ -301,21 +310,27 @@ describe("MSC3089Branch", () => {
expect(createFn).toHaveBeenCalledTimes(1);
});
it('should fetch file history', async () => {
branch2.getFileEvent = () => Promise.resolve({
it("should fetch file history", async () => {
branch2.getFileEvent = () =>
Promise.resolve({
replacingEventId: () => undefined,
getId: () => fileEventId2,
} as MatrixEvent);
branch.getFileEvent = () => Promise.resolve({
branch.getFileEvent = () =>
Promise.resolve({
replacingEventId: () => fileEventId2,
getId: () => fileEventId,
} as MatrixEvent);
const events = [await branch.getFileEvent(), await branch2.getFileEvent(), {
const events = [
await branch.getFileEvent(),
await branch2.getFileEvent(),
{
replacingEventId: (): string | undefined => undefined,
getId: () => "$unknown",
}];
staticRoom.getLiveTimeline = () => ({ getEvents: () => events }) as EventTimeline;
},
];
staticRoom.getLiveTimeline = () => ({ getEvents: () => events } as EventTimeline);
directory.getFile = (evId: string) => {
expect(evId).toEqual(fileEventId);
@ -323,9 +338,6 @@ describe("MSC3089Branch", () => {
};
const results = await branch2.getVersionHistory();
expect(results).toMatchObject([
branch2,
branch,
]);
expect(results).toMatchObject([branch2, branch]);
});
});

View File

@ -72,18 +72,19 @@ describe("MSC3089TreeSpace", () => {
});
}
it('should populate the room reference', () => {
it("should populate the room reference", () => {
expect(tree.room).toBe(room);
});
it('should proxy the ID member to room ID', () => {
it("should proxy the ID member to room ID", () => {
expect(tree.id).toEqual(tree.roomId);
expect(tree.id).toEqual(roomId);
});
it('should support setting the name of the space', async () => {
it("should support setting the name of the space", async () => {
const newName = "NEW NAME";
const fn = jest.fn()
const fn = jest
.fn()
.mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(stateRoomId).toEqual(roomId);
expect(eventType).toEqual(EventType.RoomName);
@ -96,7 +97,7 @@ describe("MSC3089TreeSpace", () => {
expect(fn).toHaveBeenCalledTimes(1);
});
it('should support inviting users to the space', async () => {
it("should support inviting users to the space", async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
@ -108,7 +109,7 @@ describe("MSC3089TreeSpace", () => {
expect(fn).toHaveBeenCalledTimes(1);
});
it('should retry invites to the space', async () => {
it("should retry invites to the space", async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
@ -121,7 +122,7 @@ describe("MSC3089TreeSpace", () => {
expect(fn).toHaveBeenCalledTimes(2);
});
it('should not retry invite permission errors', async () => {
it("should not retry invite permission errors", async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
@ -141,7 +142,7 @@ describe("MSC3089TreeSpace", () => {
expect(fn).toHaveBeenCalledTimes(1);
});
it('should invite to subspaces', async () => {
it("should invite to subspaces", async () => {
const target = targetUser;
const fn = jest.fn().mockImplementation((inviteRoomId: string, userId: string) => {
expect(inviteRoomId).toEqual(roomId);
@ -162,7 +163,7 @@ describe("MSC3089TreeSpace", () => {
expect(fn).toHaveBeenCalledTimes(4);
});
it('should share keys with invitees', async () => {
it("should share keys with invitees", async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
@ -190,7 +191,7 @@ describe("MSC3089TreeSpace", () => {
expect(historyFn).toHaveBeenCalledTimes(1);
});
it('should not share keys with invitees if inappropriate history visibility', async () => {
it("should not share keys with invitees if inappropriate history visibility", async () => {
const target = targetUser;
const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => {
expect(inviteRoomId).toEqual(roomId);
@ -215,7 +216,8 @@ describe("MSC3089TreeSpace", () => {
async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) {
makePowerLevels(pls);
const fn = jest.fn()
const fn = jest
.fn()
.mockImplementation((stateRoomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(stateRoomId).toEqual(roomId);
expect(eventType).toEqual(EventType.RoomPowerLevels);
@ -240,41 +242,54 @@ describe("MSC3089TreeSpace", () => {
expect(finalPermissions).toEqual(role);
}
it('should support setting Viewer permissions', () => {
return evaluatePowerLevels({
it("should support setting Viewer permissions", () => {
return evaluatePowerLevels(
{
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Viewer, 1024);
},
TreePermissions.Viewer,
1024,
);
});
it('should support setting Editor permissions', () => {
return evaluatePowerLevels({
it("should support setting Editor permissions", () => {
return evaluatePowerLevels(
{
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Editor, 1025);
},
TreePermissions.Editor,
1025,
);
});
it('should support setting Owner permissions', () => {
return evaluatePowerLevels({
it("should support setting Owner permissions", () => {
return evaluatePowerLevels(
{
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
events: {
[EventType.RoomPowerLevels]: 1026,
},
}, TreePermissions.Owner, 1026);
},
TreePermissions.Owner,
1026,
);
});
it('should support demoting permissions', () => {
return evaluatePowerLevels({
it("should support demoting permissions", () => {
return evaluatePowerLevels(
{
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
@ -284,11 +299,15 @@ describe("MSC3089TreeSpace", () => {
users: {
[targetUser]: 2222,
},
}, TreePermissions.Viewer, 1024);
},
TreePermissions.Viewer,
1024,
);
});
it('should support promoting permissions', () => {
return evaluatePowerLevels({
it("should support promoting permissions", () => {
return evaluatePowerLevels(
{
...DEFAULT_TREE_POWER_LEVELS_TEMPLATE,
users_default: 1024,
events_default: 1025,
@ -298,22 +317,25 @@ describe("MSC3089TreeSpace", () => {
users: {
[targetUser]: 5,
},
}, TreePermissions.Editor, 1025);
},
TreePermissions.Editor,
1025,
);
});
it('should support defaults: Viewer', () => {
it("should support defaults: Viewer", () => {
return evaluatePowerLevels({}, TreePermissions.Viewer, 0);
});
it('should support defaults: Editor', () => {
it("should support defaults: Editor", () => {
return evaluatePowerLevels({}, TreePermissions.Editor, 50);
});
it('should support defaults: Owner', () => {
it("should support defaults: Owner", () => {
return evaluatePowerLevels({}, TreePermissions.Owner, 100);
});
it('should create subdirectories', async () => {
it("should create subdirectories", async () => {
const subspaceName = "subdirectory";
const subspaceId = "!subspace:localhost";
const domain = "domain.example.com";
@ -331,7 +353,8 @@ describe("MSC3089TreeSpace", () => {
expect(name).toEqual(subspaceName);
return new MSC3089TreeSpace(client, subspaceId);
});
const sendStateFn = jest.fn()
const sendStateFn = jest
.fn()
.mockImplementation(async (roomId: string, eventType: EventType, content: any, stateKey: string) => {
expect([tree.roomId, subspaceId]).toContain(roomId);
if (roomId === subspaceId) {
@ -361,7 +384,7 @@ describe("MSC3089TreeSpace", () => {
expect(sendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, content, subspaceId);
});
it('should find subdirectories', () => {
it("should find subdirectories", () => {
const firstChildRoom = "!one:example.org";
const secondChildRoom = "!two:example.org";
const thirdChildRoom = "!three:example.org"; // to ensure it doesn't end up in the subdirectories
@ -399,7 +422,7 @@ describe("MSC3089TreeSpace", () => {
expect(getFn).toHaveBeenCalledWith(thirdChildRoom); // check to make sure it tried
});
it('should find specific directories', () => {
it("should find specific directories", () => {
client.getRoom = () => ({} as Room); // to appease the TreeSpace constructor
// Only mocking used API
@ -415,7 +438,7 @@ describe("MSC3089TreeSpace", () => {
expect(result).toBeFalsy();
});
it('should be able to delete itself', async () => {
it("should be able to delete itself", async () => {
const delete1 = jest.fn().mockImplementation(() => Promise.resolve());
const subdir1 = { delete: delete1 } as any as MSC3089TreeSpace; // mock tested bits
@ -466,7 +489,7 @@ describe("MSC3089TreeSpace", () => {
expect(leaveFn).toHaveBeenCalledTimes(1);
});
describe('get and set order', () => {
describe("get and set order", () => {
// Danger: these are partial implementations for testing purposes only
// @ts-ignore - "MatrixEvent is a value but used as a type", which is true but not important
@ -483,7 +506,7 @@ describe("MSC3089TreeSpace", () => {
const content: IContent = {
via: [staticDomain],
};
if (order) content['order'] = order;
if (order) content["order"] = order;
parentState.push({
getType: () => EventType.SpaceChild,
getStateKey: () => roomId,
@ -511,7 +534,7 @@ describe("MSC3089TreeSpace", () => {
}
function expectOrder(childRoomId: string, order: number) {
const child = childTrees.find(c => c.roomId === childRoomId);
const child = childTrees.find((c) => c.roomId === childRoomId);
expect(child).toBeDefined();
expect(child!.getOrder()).toEqual(order);
}
@ -523,10 +546,10 @@ describe("MSC3089TreeSpace", () => {
expect([EventType.SpaceParent, EventType.RoomCreate]).toContain(eventType);
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return childState[roomId].find(e => e.getType() === EventType.RoomCreate);
return childState[roomId].find((e) => e.getType() === EventType.RoomCreate);
} else {
expect(stateKey).toBeUndefined();
return childState[roomId].filter(e => e.getType() === eventType);
return childState[roomId].filter((e) => e.getType() === eventType);
}
},
},
@ -541,22 +564,22 @@ describe("MSC3089TreeSpace", () => {
roomId: tree.roomId,
currentState: {
getStateEvents: (eventType: EventType, stateKey?: string) => {
expect([
EventType.SpaceChild,
EventType.RoomCreate,
EventType.SpaceParent,
]).toContain(eventType);
expect([EventType.SpaceChild, EventType.RoomCreate, EventType.SpaceParent]).toContain(
eventType,
);
if (eventType === EventType.RoomCreate) {
expect(stateKey).toEqual("");
return parentState.filter(e => e.getType() === EventType.RoomCreate)[0];
return parentState.filter((e) => e.getType() === EventType.RoomCreate)[0];
} else {
if (stateKey !== undefined) {
expect(Object.keys(rooms)).toContain(stateKey);
expect(stateKey).not.toEqual(tree.roomId);
return parentState.find(e => e.getType() === eventType && e.getStateKey() === stateKey);
return parentState.find(
(e) => e.getType() === eventType && e.getStateKey() === stateKey,
);
} // else fine
return parentState.filter(e => e.getType() === eventType);
return parentState.filter((e) => e.getType() === eventType);
}
},
},
@ -567,18 +590,23 @@ describe("MSC3089TreeSpace", () => {
(<any>tree).room = parentRoom; // override readonly
client.getRoom = (r) => rooms[r ?? ""];
clientSendStateFn = jest.fn()
clientSendStateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: EventType, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(EventType.SpaceChild);
expect(content).toMatchObject(expect.objectContaining({
expect(content).toMatchObject(
expect.objectContaining({
via: expect.any(Array),
order: expect.any(String),
}));
}),
);
expect(Object.keys(rooms)).toContain(stateKey);
expect(stateKey).not.toEqual(tree.roomId);
const stateEvent = parentState.find(e => e.getType() === eventType && e.getStateKey() === stateKey);
const stateEvent = parentState.find(
(e) => e.getType() === eventType && e.getStateKey() === stateKey,
);
expect(stateEvent).toBeDefined();
stateEvent.getContent = () => content;
@ -587,7 +615,7 @@ describe("MSC3089TreeSpace", () => {
client.sendStateEvent = clientSendStateFn;
});
it('should know when something is top level', () => {
it("should know when something is top level", () => {
const a = "!a:example.org";
addSubspace(a);
@ -595,12 +623,12 @@ describe("MSC3089TreeSpace", () => {
expect(childTrees[0].isTopLevel).toBe(false); // a bit of a hack to get at this, but it's fine
});
it('should return -1 for top level spaces', () => {
it("should return -1 for top level spaces", () => {
// The tree is what we've defined as top level, so it should work
expect(tree.getOrder()).toEqual(-1);
});
it('should throw when setting an order at the top level space', async () => {
it("should throw when setting an order at the top level space", async () => {
try {
// The tree is what we've defined as top level, so it should work
await tree.setOrder(2);
@ -612,7 +640,7 @@ describe("MSC3089TreeSpace", () => {
}
});
it('should return a stable order for unordered children', () => {
it("should return a stable order for unordered children", () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -627,7 +655,7 @@ describe("MSC3089TreeSpace", () => {
expectOrder(c, 2);
});
it('should return a stable order for ordered children', () => {
it("should return a stable order for ordered children", () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -642,7 +670,7 @@ describe("MSC3089TreeSpace", () => {
expectOrder(a, 2);
});
it('should return a stable order for partially ordered children', () => {
it("should return a stable order for partially ordered children", () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -660,7 +688,7 @@ describe("MSC3089TreeSpace", () => {
expectOrder(a, 2);
});
it('should return a stable order if the create event timestamps are the same', () => {
it("should return a stable order if the create event timestamps are the same", () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -675,7 +703,7 @@ describe("MSC3089TreeSpace", () => {
expectOrder(c, 2);
});
it('should return a stable order if there are no known create events', () => {
it("should return a stable order if there are no known create events", () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -692,7 +720,7 @@ describe("MSC3089TreeSpace", () => {
// XXX: These tests rely on `getOrder()` re-calculating and not caching values.
it('should allow reordering within unordered children', async () => {
it("should allow reordering within unordered children", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -704,32 +732,47 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
const treeA = childTrees.find((c) => c.roomId === a);
expect(treeA).toBeDefined();
await treeA!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(3);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
// Because of how the reordering works (maintain stable ordering before moving), we end up calling this
// function twice for the same room.
order: DEFAULT_ALPHABET[0],
}), a);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
}),
a,
);
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: DEFAULT_ALPHABET[1],
}), b);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
}),
b,
);
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: DEFAULT_ALPHABET[2],
}), a);
}),
a,
);
expectOrder(a, 1);
expectOrder(b, 0);
expectOrder(c, 2);
});
it('should allow reordering within ordered children', async () => {
it("should allow reordering within ordered children", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -741,21 +784,26 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
const treeA = childTrees.find((c) => c.roomId === a);
expect(treeA).toBeDefined();
await treeA!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Y',
}), a);
order: "Y",
}),
a,
);
expectOrder(a, 1);
expectOrder(b, 0);
expectOrder(c, 2);
});
it('should allow reordering within partially ordered children', async () => {
it("should allow reordering within partially ordered children", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -769,22 +817,27 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeA = childTrees.find(c => c.roomId === a);
const treeA = childTrees.find((c) => c.roomId === a);
expect(treeA).toBeDefined();
await treeA!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Z',
}), a);
order: "Z",
}),
a,
);
expectOrder(a, 2);
expectOrder(b, 3);
expectOrder(c, 1);
expectOrder(d, 0);
});
it('should support moving upwards', async () => {
it("should support moving upwards", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -798,22 +851,27 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeB = childTrees.find(c => c.roomId === b);
const treeB = childTrees.find((c) => c.roomId === b);
expect(treeB).toBeDefined();
await treeB!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'Y',
}), b);
order: "Y",
}),
b,
);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
expectOrder(d, 3);
});
it('should support moving downwards', async () => {
it("should support moving downwards", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -827,22 +885,27 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeC = childTrees.find(ch => ch.roomId === c);
const treeC = childTrees.find((ch) => ch.roomId === c);
expect(treeC).toBeDefined();
await treeC!.setOrder(1);
expect(clientSendStateFn).toHaveBeenCalledTimes(1);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'U',
}), c);
order: "U",
}),
c,
);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
expectOrder(d, 3);
});
it('should support moving over the partial ordering boundary', async () => {
it("should support moving over the partial ordering boundary", async () => {
const a = "!a:example.org";
const b = "!b:example.org";
const c = "!c:example.org";
@ -856,19 +919,29 @@ describe("MSC3089TreeSpace", () => {
// Order of this state is validated by other tests.
const treeB = childTrees.find(ch => ch.roomId === b);
const treeB = childTrees.find((ch) => ch.roomId === b);
expect(treeB).toBeDefined();
await treeB!.setOrder(2);
expect(clientSendStateFn).toHaveBeenCalledTimes(2);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'W',
}), c);
expect(clientSendStateFn).toHaveBeenCalledWith(tree.roomId, EventType.SpaceChild, expect.objectContaining({
order: "W",
}),
c,
);
expect(clientSendStateFn).toHaveBeenCalledWith(
tree.roomId,
EventType.SpaceChild,
expect.objectContaining({
via: [staticDomain], // should retain domain independent of client.getDomain()
order: 'X',
}), b);
order: "X",
}),
b,
);
expectOrder(a, 0);
expectOrder(b, 2);
expectOrder(c, 1);
@ -876,7 +949,7 @@ describe("MSC3089TreeSpace", () => {
});
});
it('should upload files', async () => {
it("should upload files", async () => {
const mxc = "mxc://example.org/file";
const fileInfo = {
mimetype: "text/plain",
@ -910,7 +983,8 @@ describe("MSC3089TreeSpace", () => {
});
client.sendMessage = sendMsgFn;
const sendStateFn = jest.fn()
const sendStateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
@ -935,7 +1009,7 @@ describe("MSC3089TreeSpace", () => {
expect(sendStateFn).toHaveBeenCalledTimes(1);
});
it('should upload file versions', async () => {
it("should upload file versions", async () => {
const mxc = "mxc://example.org/file";
const fileInfo = {
mimetype: "text/plain",
@ -972,7 +1046,8 @@ describe("MSC3089TreeSpace", () => {
});
client.sendMessage = sendMsgFn;
const sendStateFn = jest.fn()
const sendStateFn = jest
.fn()
.mockImplementation((roomId: string, eventType: string, content: any, stateKey: string) => {
expect(roomId).toEqual(tree.roomId);
expect(eventType).toEqual(UNSTABLE_MSC3089_BRANCH.unstable); // test to ensure we're definitely using unstable
@ -997,7 +1072,7 @@ describe("MSC3089TreeSpace", () => {
expect(sendStateFn).toHaveBeenCalledTimes(1);
});
it('should support getting files', () => {
it("should support getting files", () => {
const fileEventId = "$file";
const fileEvent = { forTest: true }; // MatrixEvent mock
room.currentState = {
@ -1013,7 +1088,7 @@ describe("MSC3089TreeSpace", () => {
expect(file!.indexEvent).toBe(fileEvent);
});
it('should return falsy for unknown files', () => {
it("should return falsy for unknown files", () => {
const fileEventId = "$file";
room.currentState = {
getStateEvents: (eventType: string, stateKey?: string): MatrixEvent[] | MatrixEvent | null => {
@ -1027,7 +1102,7 @@ describe("MSC3089TreeSpace", () => {
expect(file).toBeFalsy();
});
it('should list files', () => {
it("should list files", () => {
const firstFile = { getContent: () => ({ active: true }) };
const secondFile = { getContent: () => ({ active: false }) }; // deliberately inactive
room.currentState = {
@ -1044,7 +1119,7 @@ describe("MSC3089TreeSpace", () => {
expect(files[0].indexEvent).toBe(firstFile);
});
it('should list all files', () => {
it("should list all files", () => {
const firstFile = { getContent: () => ({ active: true }) };
const secondFile = { getContent: () => ({ active: false }) }; // deliberately inactive
room.currentState = {

View File

@ -18,50 +18,46 @@ import { REFERENCE_RELATION } from "matrix-events-sdk";
import { MatrixEvent } from "../../../src";
import { M_BEACON_INFO } from "../../../src/@types/beacon";
import {
isTimestampInDuration,
Beacon,
BeaconEvent,
} from "../../../src/models/beacon";
import { isTimestampInDuration, Beacon, BeaconEvent } from "../../../src/models/beacon";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../../test-utils/beacon";
jest.useFakeTimers();
describe('Beacon', () => {
describe('isTimestampInDuration()', () => {
const startTs = new Date('2022-03-11T12:07:47.592Z').getTime();
describe("Beacon", () => {
describe("isTimestampInDuration()", () => {
const startTs = new Date("2022-03-11T12:07:47.592Z").getTime();
const HOUR_MS = 3600000;
it('returns false when timestamp is before start time', () => {
it("returns false when timestamp is before start time", () => {
// day before
const timestamp = new Date('2022-03-10T12:07:47.592Z').getTime();
const timestamp = new Date("2022-03-10T12:07:47.592Z").getTime();
expect(isTimestampInDuration(startTs, HOUR_MS, timestamp)).toBe(false);
});
it('returns false when timestamp is after start time + duration', () => {
it("returns false when timestamp is after start time + duration", () => {
// 1 second later
const timestamp = new Date('2022-03-10T12:07:48.592Z').getTime();
const timestamp = new Date("2022-03-10T12:07:48.592Z").getTime();
expect(isTimestampInDuration(startTs, HOUR_MS, timestamp)).toBe(false);
});
it('returns true when timestamp is exactly start time', () => {
it("returns true when timestamp is exactly start time", () => {
expect(isTimestampInDuration(startTs, HOUR_MS, startTs)).toBe(true);
});
it('returns true when timestamp is exactly the end of the duration', () => {
it("returns true when timestamp is exactly the end of the duration", () => {
expect(isTimestampInDuration(startTs, HOUR_MS, startTs + HOUR_MS)).toBe(true);
});
it('returns true when timestamp is within the duration', () => {
it("returns true when timestamp is within the duration", () => {
const twoHourDuration = HOUR_MS * 2;
const now = startTs + HOUR_MS;
expect(isTimestampInDuration(startTs, twoHourDuration, now)).toBe(true);
});
});
describe('Beacon', () => {
const userId = '@user:server.org';
const userId2 = '@user2:server.org';
const roomId = '$room:server.org';
describe("Beacon", () => {
const userId = "@user:server.org";
const userId2 = "@user2:server.org";
const roomId = "$room:server.org";
// 14.03.2022 16:15
const now = 1647270879403;
const HOUR_MS = 3600000;
@ -75,7 +71,7 @@ describe('Beacon', () => {
const advanceDateAndTime = (ms: number) => {
// bc liveness check uses Date.now we have to advance this mock
jest.spyOn(global.Date, 'now').mockReturnValue(Date.now() + ms);
jest.spyOn(global.Date, "now").mockReturnValue(Date.now() + ms);
// then advance time for the interval by the same amount
jest.advanceTimersByTime(ms);
};
@ -89,7 +85,7 @@ describe('Beacon', () => {
isLive: true,
timestamp: now - HOUR_MS,
},
'$live123',
"$live123",
);
notLiveBeaconEvent = makeBeaconInfoEvent(
userId,
@ -99,7 +95,7 @@ describe('Beacon', () => {
isLive: false,
timestamp: now - HOUR_MS,
},
'$dead123',
"$dead123",
);
user2BeaconEvent = makeBeaconInfoEvent(
userId2,
@ -109,18 +105,18 @@ describe('Beacon', () => {
isLive: true,
timestamp: now - HOUR_MS,
},
'$user2live123',
"$user2live123",
);
// back to 'now'
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(global.Date, "now").mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
});
it('creates beacon from event', () => {
it("creates beacon from event", () => {
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
@ -132,7 +128,7 @@ describe('Beacon', () => {
expect(beacon.beaconInfo).toBeTruthy();
});
it('creates beacon without error from a malformed event', () => {
it("creates beacon without error from a malformed event", () => {
const event = new MatrixEvent({
type: M_BEACON_INFO.name,
room_id: roomId,
@ -150,13 +146,13 @@ describe('Beacon', () => {
expect(beacon.beaconInfo).toBeTruthy();
});
describe('isLive()', () => {
it('returns false when beacon is explicitly set to not live', () => {
describe("isLive()", () => {
it("returns false when beacon is explicitly set to not live", () => {
const beacon = new Beacon(notLiveBeaconEvent);
expect(beacon.isLive).toEqual(false);
});
it('returns false when beacon is expired', () => {
it("returns false when beacon is expired", () => {
const expiredBeaconEvent = makeBeaconInfoEvent(
userId2,
roomId,
@ -165,13 +161,13 @@ describe('Beacon', () => {
isLive: true,
timestamp: now - HOUR_MS * 2,
},
'$user2live123',
"$user2live123",
);
const beacon = new Beacon(expiredBeaconEvent);
expect(beacon.isLive).toEqual(false);
});
it('returns false when beacon timestamp is in future by an hour', () => {
it("returns false when beacon timestamp is in future by an hour", () => {
const beaconStartsInHour = makeBeaconInfoEvent(
userId2,
roomId,
@ -180,13 +176,13 @@ describe('Beacon', () => {
isLive: true,
timestamp: now + HOUR_MS,
},
'$user2live123',
"$user2live123",
);
const beacon = new Beacon(beaconStartsInHour);
expect(beacon.isLive).toEqual(false);
});
it('returns true when beacon timestamp is one minute in the future', () => {
it("returns true when beacon timestamp is one minute in the future", () => {
const beaconStartsInOneMin = makeBeaconInfoEvent(
userId2,
roomId,
@ -195,13 +191,13 @@ describe('Beacon', () => {
isLive: true,
timestamp: now + 60000,
},
'$user2live123',
"$user2live123",
);
const beacon = new Beacon(beaconStartsInOneMin);
expect(beacon.isLive).toEqual(true);
});
it('returns true when beacon timestamp is one minute before expiry', () => {
it("returns true when beacon timestamp is one minute before expiry", () => {
// this test case is to check the start time leniency doesn't affect
// strict expiry time checks
const expiresInOneMin = makeBeaconInfoEvent(
@ -212,13 +208,13 @@ describe('Beacon', () => {
isLive: true,
timestamp: now - HOUR_MS + 60000,
},
'$user2live123',
"$user2live123",
);
const beacon = new Beacon(expiresInOneMin);
expect(beacon.isLive).toEqual(true);
});
it('returns false when beacon timestamp is one minute after expiry', () => {
it("returns false when beacon timestamp is one minute after expiry", () => {
// this test case is to check the start time leniency doesn't affect
// strict expiry time checks
const expiredOneMinAgo = makeBeaconInfoEvent(
@ -229,21 +225,21 @@ describe('Beacon', () => {
isLive: true,
timestamp: now - HOUR_MS - 60000,
},
'$user2live123',
"$user2live123",
);
const beacon = new Beacon(expiredOneMinAgo);
expect(beacon.isLive).toEqual(false);
});
it('returns true when beacon was created in past and not yet expired', () => {
it("returns true when beacon was created in past and not yet expired", () => {
// liveBeaconEvent was created 1 hour ago
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toEqual(true);
});
});
describe('update()', () => {
it('does not update with different event', () => {
describe("update()", () => {
it("does not update with different event", () => {
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
@ -253,15 +249,12 @@ describe('Beacon', () => {
expect(beacon.identifier).toEqual(`${roomId}_${userId}`);
});
it('does not update with an older event', () => {
it("does not update with an older event", () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit').mockClear();
const emitSpy = jest.spyOn(beacon, "emit").mockClear();
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
const oldUpdateEvent = makeBeaconInfoEvent(
userId,
roomId,
);
const oldUpdateEvent = makeBeaconInfoEvent(userId, roomId);
// less than the original event
oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts! - 1000;
@ -271,23 +264,27 @@ describe('Beacon', () => {
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
});
it('updates event', () => {
it("updates event", () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
expect(beacon.isLive).toEqual(true);
const updatedBeaconEvent = makeBeaconInfoEvent(
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123');
userId,
roomId,
{ timeout: HOUR_MS * 3, isLive: false },
"$live123",
);
beacon.update(updatedBeaconEvent);
expect(beacon.isLive).toEqual(false);
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.Update, updatedBeaconEvent, beacon);
});
it('emits livenesschange event when beacon liveness changes', () => {
it("emits livenesschange event when beacon liveness changes", () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
expect(beacon.isLive).toEqual(true);
@ -304,12 +301,12 @@ describe('Beacon', () => {
});
});
describe('monitorLiveness()', () => {
it('does not set a monitor interval when beacon is not live', () => {
describe("monitorLiveness()", () => {
it("does not set a monitor interval when beacon is not live", () => {
// beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(notLiveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.monitorLiveness();
@ -321,7 +318,7 @@ describe('Beacon', () => {
expect(emitSpy).not.toHaveBeenCalled();
});
it('checks liveness of beacon at expected start time', () => {
it("checks liveness of beacon at expected start time", () => {
const futureBeaconEvent = makeBeaconInfoEvent(
userId,
roomId,
@ -331,12 +328,12 @@ describe('Beacon', () => {
// start timestamp hour in future
timestamp: now + HOUR_MS,
},
'$live123',
"$live123",
);
const beacon = new Beacon(futureBeaconEvent);
expect(beacon.isLive).toBeFalsy();
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.monitorLiveness();
@ -355,12 +352,12 @@ describe('Beacon', () => {
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
});
it('checks liveness of beacon at expected expiry time', () => {
it("checks liveness of beacon at expected expiry time", () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toBeTruthy();
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.monitorLiveness();
advanceDateAndTime(HOUR_MS * 2 + 1);
@ -369,7 +366,7 @@ describe('Beacon', () => {
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LivenessChange, false, beacon);
});
it('clears monitor interval when re-monitoring liveness', () => {
it("clears monitor interval when re-monitoring liveness", () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
@ -385,12 +382,12 @@ describe('Beacon', () => {
expect(beacon.livenessWatchTimeout).not.toEqual(oldMonitor);
});
it('destroy kills liveness monitor and emits', () => {
it("destroy kills liveness monitor and emits", () => {
// live beacon was created an hour ago
// and has a 3hr duration
const beacon = new Beacon(liveBeaconEvent);
expect(beacon.isLive).toBeTruthy();
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.monitorLiveness();
@ -407,10 +404,10 @@ describe('Beacon', () => {
});
});
describe('addLocations', () => {
it('ignores locations when beacon is not live', () => {
describe("addLocations", () => {
it("ignores locations when beacon is not live", () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: false }));
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.addLocations([
makeBeaconEvent(userId, { beaconInfoId: beacon.beaconInfoId, timestamp: now + 1 }),
@ -420,9 +417,9 @@ describe('Beacon', () => {
expect(emitSpy).not.toHaveBeenCalled();
});
it('ignores locations outside the beacon live duration', () => {
it("ignores locations outside the beacon live duration", () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
beacon.addLocations([
// beacon has now + 60000 live period
@ -435,7 +432,7 @@ describe('Beacon', () => {
it("should ignore invalid beacon events", () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
const ev = new MatrixEvent({
type: M_BEACON_INFO.name,
@ -454,50 +451,48 @@ describe('Beacon', () => {
expect(emitSpy).not.toHaveBeenCalled();
});
describe('when beacon is live with a start timestamp is in the future', () => {
it('ignores locations before the beacon start timestamp', () => {
describe("when beacon is live with a start timestamp is in the future", () => {
it("ignores locations before the beacon start timestamp", () => {
const startTimestamp = now + 60000;
const beacon = new Beacon(makeBeaconInfoEvent(
userId,
roomId,
{ isLive: true, timeout: 60000, timestamp: startTimestamp },
));
const emitSpy = jest.spyOn(beacon, 'emit');
const beacon = new Beacon(
makeBeaconInfoEvent(userId, roomId, {
isLive: true,
timeout: 60000,
timestamp: startTimestamp,
}),
);
const emitSpy = jest.spyOn(beacon, "emit");
beacon.addLocations([
// beacon has now + 60000 live period
makeBeaconEvent(
userId,
{
makeBeaconEvent(userId, {
beaconInfoId: beacon.beaconInfoId,
// now < location timestamp < beacon timestamp
timestamp: now + 10,
},
),
}),
]);
expect(beacon.latestLocationState).toBeFalsy();
expect(emitSpy).not.toHaveBeenCalled();
});
it('sets latest location when location timestamp is after startTimestamp', () => {
it("sets latest location when location timestamp is after startTimestamp", () => {
const startTimestamp = now + 60000;
const beacon = new Beacon(makeBeaconInfoEvent(
userId,
roomId,
{ isLive: true, timeout: 600000, timestamp: startTimestamp },
));
const emitSpy = jest.spyOn(beacon, 'emit');
const beacon = new Beacon(
makeBeaconInfoEvent(userId, roomId, {
isLive: true,
timeout: 600000,
timestamp: startTimestamp,
}),
);
const emitSpy = jest.spyOn(beacon, "emit");
beacon.addLocations([
// beacon has now + 600000 live period
makeBeaconEvent(
userId,
{
makeBeaconEvent(userId, {
beaconInfoId: beacon.beaconInfoId,
// now < beacon timestamp < location timestamp
timestamp: startTimestamp + 10,
},
),
}),
]);
expect(beacon.latestLocationState).toBeTruthy();
@ -505,23 +500,21 @@ describe('Beacon', () => {
});
});
it('sets latest location state to most recent location', () => {
it("sets latest location state to most recent location", () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const emitSpy = jest.spyOn(beacon, 'emit');
const emitSpy = jest.spyOn(beacon, "emit");
const locations = [
// older
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:foo', timestamp: now + 1 },
),
makeBeaconEvent(userId, { beaconInfoId: beacon.beaconInfoId, uri: "geo:foo", timestamp: now + 1 }),
// newer
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:bar', timestamp: now + 10000 },
),
makeBeaconEvent(userId, {
beaconInfoId: beacon.beaconInfoId,
uri: "geo:bar",
timestamp: now + 10000,
}),
// not valid
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:baz', timestamp: now - 5 },
),
makeBeaconEvent(userId, { beaconInfoId: beacon.beaconInfoId, uri: "geo:baz", timestamp: now - 5 }),
];
beacon.addLocations(locations);
@ -529,7 +522,7 @@ describe('Beacon', () => {
const expectedLatestLocation = {
description: undefined,
timestamp: now + 10000,
uri: 'geo:bar',
uri: "geo:bar",
};
// the newest valid location
@ -538,32 +531,40 @@ describe('Beacon', () => {
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LocationUpdate, expectedLatestLocation);
});
it('ignores locations that are less recent that the current latest location', () => {
it("ignores locations that are less recent that the current latest location", () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const olderLocation = makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:foo', timestamp: now + 1 },
);
const newerLocation = makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:bar', timestamp: now + 10000 },
);
const olderLocation = makeBeaconEvent(userId, {
beaconInfoId: beacon.beaconInfoId,
uri: "geo:foo",
timestamp: now + 1,
});
const newerLocation = makeBeaconEvent(userId, {
beaconInfoId: beacon.beaconInfoId,
uri: "geo:bar",
timestamp: now + 10000,
});
beacon.addLocations([newerLocation]);
// latest location set to newerLocation
expect(beacon.latestLocationState).toEqual(expect.objectContaining({
uri: 'geo:bar',
}));
expect(beacon.latestLocationState).toEqual(
expect.objectContaining({
uri: "geo:bar",
}),
);
expect(beacon.latestLocationEvent).toEqual(newerLocation);
const emitSpy = jest.spyOn(beacon, 'emit').mockClear();
const emitSpy = jest.spyOn(beacon, "emit").mockClear();
// add older location
beacon.addLocations([olderLocation]);
// no change
expect(beacon.latestLocationState).toEqual(expect.objectContaining({
uri: 'geo:bar',
}));
expect(beacon.latestLocationState).toEqual(
expect.objectContaining({
uri: "geo:bar",
}),
);
// no emit
expect(emitSpy).not.toHaveBeenCalled();
});

View File

@ -19,8 +19,8 @@ import { emitPromise } from "../../test-utils/test-utils";
import { EventType } from "../../../src";
import { Crypto } from "../../../src/crypto";
describe('MatrixEvent', () => {
it('should create copies of itself', () => {
describe("MatrixEvent", () => {
it("should create copies of itself", () => {
const a = new MatrixEvent({
type: "com.example.test",
content: {
@ -38,7 +38,7 @@ describe('MatrixEvent', () => {
// The other properties we're not super interested in, honestly.
});
it('should compare itself to other events using json', () => {
it("should compare itself to other events using json", () => {
const a = new MatrixEvent({
type: "com.example.test",
content: {
@ -122,36 +122,37 @@ describe('MatrixEvent', () => {
describe(".attemptDecryption", () => {
let encryptedEvent: MatrixEvent;
const eventId = 'test_encrypted_event';
const eventId = "test_encrypted_event";
beforeEach(() => {
encryptedEvent = new MatrixEvent({
event_id: eventId,
type: 'm.room.encrypted',
type: "m.room.encrypted",
content: {
ciphertext: 'secrets',
ciphertext: "secrets",
},
});
});
it('should retry decryption if a retry is queued', async () => {
const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, 'attemptDecryption');
it("should retry decryption if a retry is queued", async () => {
const eventAttemptDecryptionSpy = jest.spyOn(encryptedEvent, "attemptDecryption");
const crypto = {
decryptEvent: jest.fn()
decryptEvent: jest
.fn()
.mockImplementationOnce(() => {
// schedule a second decryption attempt while
// the first one is still running.
encryptedEvent.attemptDecryption(crypto);
const error = new Error("nope");
error.name = 'DecryptionError';
error.name = "DecryptionError";
return Promise.reject(error);
})
.mockImplementationOnce(() => {
return Promise.resolve({
clearEvent: {
type: 'm.room.message',
type: "m.room.message",
},
});
}),
@ -161,7 +162,7 @@ describe('MatrixEvent', () => {
expect(eventAttemptDecryptionSpy).toHaveBeenCalledTimes(2);
expect(crypto.decryptEvent).toHaveBeenCalledTimes(2);
expect(encryptedEvent.getType()).toEqual('m.room.message');
expect(encryptedEvent.getType()).toEqual("m.room.message");
});
});
});

View File

@ -22,7 +22,7 @@ import { TestClient } from "../../TestClient";
import { emitPromise, mkMessage } from "../../test-utils/test-utils";
import { EventStatus } from "../../../src";
describe('Thread', () => {
describe("Thread", () => {
describe("constructor", () => {
it("should explode for element-web#22141 logging", () => {
// Logging/debugging for https://github.com/vector-im/element-web/issues/22141
@ -34,13 +34,7 @@ describe('Thread', () => {
it("includes pending events in replyCount", async () => {
const myUserId = "@bob:example.org";
const testClient = new TestClient(
myUserId,
"DEVICE",
"ACCESS_TOKEN",
undefined,
{ timelineSupport: false },
);
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, { timelineSupport: false });
const client = testClient.client;
const room = new Room("123", client, myUserId, {
pendingEventOrdering: PendingEventOrdering.Detached,
@ -82,13 +76,9 @@ describe('Thread', () => {
let room: Room;
beforeEach(() => {
const testClient = new TestClient(
myUserId,
"DEVICE",
"ACCESS_TOKEN",
undefined,
{ timelineSupport: false },
);
const testClient = new TestClient(myUserId, "DEVICE", "ACCESS_TOKEN", undefined, {
timelineSupport: false,
});
client = testClient.client;
room = new Room("123", client, myUserId);

View File

@ -57,9 +57,9 @@ describe("fixNotificationCountOnDecryption", () => {
decryptEventIfNeeded: jest.fn().mockResolvedValue(void 0),
supportsExperimentalThreads: jest.fn().mockReturnValue(true),
});
mockClient.reEmitter = mock(ReEmitter, 'ReEmitter');
mockClient.reEmitter = mock(ReEmitter, "ReEmitter");
mockClient.canSupport = new Map();
Object.keys(Feature).forEach(feature => {
Object.keys(Feature).forEach((feature) => {
mockClient.canSupport.set(feature as Feature, ServerSupport.Stable);
});
@ -67,14 +67,17 @@ describe("fixNotificationCountOnDecryption", () => {
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
event = mkEvent({
event = mkEvent(
{
type: EventType.RoomMessage,
content: {
msgtype: MsgType.Text,
body: "Hello world!",
},
event: true,
}, mockClient);
},
mockClient,
);
THREAD_ID = event.getId()!;
threadEvent = mkEvent({

View File

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import MockHttpBackend from 'matrix-mock-request';
import MockHttpBackend from "matrix-mock-request";
import { MatrixClient, PUSHER_ENABLED } from "../../src/matrix";
import { mkPusher } from '../test-utils/test-utils';
import { mkPusher } from "../test-utils/test-utils";
const realSetTimeout = setTimeout;
function flushPromises() {
return new Promise(r => {
return new Promise((r) => {
realSetTimeout(r, 1);
});
}

View File

@ -2,7 +2,7 @@ import * as utils from "../test-utils/test-utils";
import { IActionsObject, PushProcessor } from "../../src/pushprocessor";
import { EventType, IContent, MatrixClient, MatrixEvent } from "../../src";
describe('NotificationService', function() {
describe("NotificationService", function () {
const testUserId = "@ali:matrix.org";
const testDisplayName = "Alice M";
const testRoomId = "!fl1bb13:localhost";
@ -32,136 +32,136 @@ describe('NotificationService', function() {
userId: testUserId,
},
pushRules: {
"device": {},
"global": {
"content": [
device: {},
global: {
content: [
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "ali",
"rule_id": ".m.rule.contains_user_name",
enabled: true,
pattern: "ali",
rule_id: ".m.rule.contains_user_name",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "coffee",
"rule_id": "coffee",
enabled: true,
pattern: "coffee",
rule_id: "coffee",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "foo*bar",
"rule_id": "foobar",
enabled: true,
pattern: "foo*bar",
rule_id: "foobar",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "p[io]ng",
"rule_id": "pingpong",
enabled: true,
pattern: "p[io]ng",
rule_id: "pingpong",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "I ate [0-9] pies",
"rule_id": "pies",
enabled: true,
pattern: "I ate [0-9] pies",
rule_id: "pies",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"enabled": true,
"pattern": "b[!ai]ke",
"rule_id": "bakebike",
enabled: true,
pattern: "b[!ai]ke",
rule_id: "bakebike",
},
],
"override": [
override: [
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
{
"set_tweak": "highlight",
set_tweak: "highlight",
},
],
"conditions": [
conditions: [
{
"kind": "contains_display_name",
kind: "contains_display_name",
},
],
"enabled": true,
"rule_id": ".m.rule.contains_display_name",
enabled: true,
rule_id: ".m.rule.contains_display_name",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "sound",
"value": "default",
set_tweak: "sound",
value: "default",
},
],
"conditions": [
conditions: [
{
"is": "2",
"kind": "room_member_count",
is: "2",
kind: "room_member_count",
},
],
"enabled": true,
"rule_id": ".m.rule.room_one_to_one",
enabled: true,
rule_id: ".m.rule.room_one_to_one",
},
{
rule_id: ".org.matrix.msc3914.rule.room.call",
@ -180,34 +180,32 @@ describe('NotificationService', function() {
actions: ["notify", { set_tweak: "sound", value: "default" }],
},
],
"room": [],
"sender": [],
"underride": [
room: [],
sender: [],
underride: [
{
"actions": [
"dont-notify",
],
"conditions": [
actions: ["dont-notify"],
conditions: [
{
"key": "content.msgtype",
"kind": "event_match",
"pattern": "m.notice",
key: "content.msgtype",
kind: "event_match",
pattern: "m.notice",
},
],
"enabled": true,
"rule_id": ".m.rule.suppress_notices",
enabled: true,
rule_id: ".m.rule.suppress_notices",
},
{
"actions": [
actions: [
"notify",
{
"set_tweak": "highlight",
"value": false,
set_tweak: "highlight",
value: false,
},
],
"conditions": [],
"enabled": true,
"rule_id": ".m.rule.fallback",
conditions: [],
enabled: true,
rule_id: ".m.rule.fallback",
},
],
},
@ -231,25 +229,25 @@ describe('NotificationService', function() {
// User IDs
it('should bing on a user ID.', function() {
it("should bing on a user ID.", function () {
testEvent.event.content!.body = "Hello @ali:matrix.org, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a partial user ID with an @.', function() {
it("should bing on a partial user ID with an @.", function () {
testEvent.event.content!.body = "Hello @ali, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a partial user ID without @.', function() {
it("should bing on a partial user ID without @.", function () {
testEvent.event.content!.body = "Hello ali, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a case-insensitive user ID.', function() {
it("should bing on a case-insensitive user ID.", function () {
testEvent.event.content!.body = "Hello @AlI:matrix.org, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
@ -257,13 +255,13 @@ describe('NotificationService', function() {
// Display names
it('should bing on a display name.', function() {
it("should bing on a display name.", function () {
testEvent.event.content!.body = "Hello Alice M, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on a case-insensitive display name.', function() {
it("should bing on a case-insensitive display name.", function () {
testEvent.event.content!.body = "Hello ALICE M, how are you?";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
@ -271,25 +269,25 @@ describe('NotificationService', function() {
// Bing words
it('should bing on a bing word.', function() {
it("should bing on a bing word.", function () {
testEvent.event.content!.body = "I really like coffee";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on case-insensitive bing words.', function() {
it("should bing on case-insensitive bing words.", function () {
testEvent.event.content!.body = "Coffee is great";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on wildcard (.*) bing words.', function() {
it("should bing on wildcard (.*) bing words.", function () {
testEvent.event.content!.body = "It was foomahbar I think.";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character group ([abc]) bing words.', function() {
it("should bing on character group ([abc]) bing words.", function () {
testEvent.event.content!.body = "Ping!";
let actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
@ -298,13 +296,13 @@ describe('NotificationService', function() {
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character range ([a-z]) bing words.', function() {
it("should bing on character range ([a-z]) bing words.", function () {
testEvent.event.content!.body = "I ate 6 pies";
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
});
it('should bing on character negation ([!a]) bing words.', function() {
it("should bing on character negation ([!a]) bing words.", function () {
testEvent.event.content!.body = "boke";
let actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(true);
@ -313,7 +311,7 @@ describe('NotificationService', function() {
expect(actions.tweaks.highlight).toEqual(false);
});
it('should not bing on room server ACL changes', function() {
it("should not bing on room server ACL changes", function () {
testEvent = utils.mkEvent({
type: EventType.RoomServerAcl,
room: testRoomId,
@ -331,31 +329,41 @@ describe('NotificationService', function() {
// invalid
it('should gracefully handle bad input.', function() {
testEvent.event.content!.body = { "foo": "bar" };
it("should gracefully handle bad input.", function () {
testEvent.event.content!.body = { foo: "bar" };
const actions = pushProcessor.actionsForEvent(testEvent);
expect(actions.tweaks.highlight).toEqual(false);
});
it("a rule with no conditions matches every event.", function () {
expect(pushProcessor.ruleMatchesEvent({
expect(
pushProcessor.ruleMatchesEvent(
{
rule_id: "rule1",
actions: [],
conditions: [],
default: false,
enabled: true,
}, testEvent)).toBe(true);
expect(pushProcessor.ruleMatchesEvent({
},
testEvent,
),
).toBe(true);
expect(
pushProcessor.ruleMatchesEvent(
{
rule_id: "rule1",
actions: [],
default: false,
enabled: true,
}, testEvent)).toBe(true);
},
testEvent,
),
).toBe(true);
});
describe("group call started push rule", () => {
beforeEach(() => {
matrixClient.pushRules!.global!.underride!.find(r => r.rule_id === ".m.rule.fallback")!.enabled = false;
matrixClient.pushRules!.global!.underride!.find((r) => r.rule_id === ".m.rule.fallback")!.enabled = false;
});
const getActionsForEvent = (prevContent: IContent, content: IContent): IActionsObject => {
@ -384,73 +392,104 @@ describe('NotificationService', function() {
expect(actions?.tweaks?.highlight).toBeFalsy();
};
it.each(
["m.ring", "m.prompt"],
)("should notify when new group call event appears with %s intent", (intent: string) => {
assertDoesNotify(getActionsForEvent({}, {
it.each(["m.ring", "m.prompt"])(
"should notify when new group call event appears with %s intent",
(intent: string) => {
assertDoesNotify(
getActionsForEvent(
{},
{
"m.intent": intent,
"m.type": "m.voice",
"m.name": "Call",
}));
});
},
),
);
},
);
it("should notify when a call is un-terminated", () => {
assertDoesNotify(getActionsForEvent({
assertDoesNotify(
getActionsForEvent(
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
"m.terminated": "All users left",
}, {
},
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
}));
},
),
);
});
it("should not notify when call is terminated", () => {
assertDoesNotNotify(getActionsForEvent({
assertDoesNotNotify(
getActionsForEvent(
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
}, {
},
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
"m.terminated": "All users left",
}));
},
),
);
});
it("should ignore with m.room intent", () => {
assertDoesNotNotify(getActionsForEvent({}, {
assertDoesNotNotify(
getActionsForEvent(
{},
{
"m.intent": "m.room",
"m.type": "m.voice",
"m.name": "Call",
}));
},
),
);
});
describe("ignoring non-relevant state changes", () => {
it("should ignore intent changes", () => {
assertDoesNotNotify(getActionsForEvent({
assertDoesNotNotify(
getActionsForEvent(
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
}, {
},
{
"m.intent": "m.ring",
"m.type": "m.video",
"m.name": "Call",
}));
},
),
);
});
it("should ignore name changes", () => {
assertDoesNotNotify(getActionsForEvent({
assertDoesNotNotify(
getActionsForEvent(
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "Call",
}, {
},
{
"m.intent": "m.ring",
"m.type": "m.voice",
"m.name": "New call",
}));
},
),
);
});
});
});

View File

@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import MockHttpBackend from 'matrix-mock-request';
import { indexedDB as fakeIndexedDB } from 'fake-indexeddb';
import MockHttpBackend from "matrix-mock-request";
import { indexedDB as fakeIndexedDB } from "fake-indexeddb";
import { IndexedDBStore, MatrixEvent, MemoryStore, Room } from "../../src";
import { MatrixClient } from "../../src/client";
import { ToDeviceBatch } from '../../src/models/ToDeviceMessage';
import { logger } from '../../src/logger';
import { IStore } from '../../src/store';
import { flushPromises } from '../test-utils/flushPromises';
import { ToDeviceBatch } from "../../src/models/ToDeviceMessage";
import { logger } from "../../src/logger";
import { IStore } from "../../src/store";
import { flushPromises } from "../test-utils/flushPromises";
import { removeElement } from "../../src/utils";
const FAKE_USER = "@alice:example.org";
const FAKE_DEVICE_ID = "AAAAAAAA";
const FAKE_PAYLOAD = {
"foo": 42,
foo: 42,
};
const EXPECTED_BODY = {
messages: {
@ -45,8 +45,8 @@ const FAKE_MSG = {
};
enum StoreType {
Memory = 'Memory',
IndexedDB = 'IndexedDB',
Memory = "Memory",
IndexedDB = "IndexedDB",
}
async function flushAndRunTimersUntil(cond: () => boolean) {
@ -57,9 +57,7 @@ async function flushAndRunTimersUntil(cond: () => boolean) {
}
}
describe.each([
[StoreType.Memory], [StoreType.IndexedDB],
])("queueToDevice (%s store)", function(storeType) {
describe.each([[StoreType.Memory], [StoreType.IndexedDB]])("queueToDevice (%s store)", function (storeType) {
let httpBackend: MockHttpBackend;
let client: MatrixClient;
@ -91,17 +89,16 @@ describe.each([
});
it("sends a to-device message", async function () {
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).check((request) => {
httpBackend
.when("PUT", "/sendToDevice/org.example.foo/")
.check((request) => {
expect(request.data).toEqual(EXPECTED_BODY);
}).respond(200, {});
})
.respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
await httpBackend.flushAllExpected();
@ -114,21 +111,18 @@ describe.each([
it("retries on error", async function () {
jest.useFakeTimers();
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(500);
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).check((request) => {
httpBackend
.when("PUT", "/sendToDevice/org.example.foo/")
.check((request) => {
expect(request.data).toEqual(EXPECTED_BODY);
}).respond(200, {});
})
.respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
@ -144,15 +138,11 @@ describe.each([
it("stops retrying on 4xx errors", async function () {
jest.useFakeTimers();
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(400);
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(400);
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
@ -173,22 +163,16 @@ describe.each([
// retry delay the algorithm uses anyway
const retryDelay = 279 * 1000;
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(429, {
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(429, {
errcode: "M_LIMIT_EXCEEDED",
retry_after_ms: retryDelay,
});
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(200, {});
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
@ -216,19 +200,13 @@ describe.each([
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(500);
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(200, {});
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises();
@ -246,19 +224,13 @@ describe.each([
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(500);
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(200, {});
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
await flushPromises();
@ -276,19 +248,13 @@ describe.each([
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(500);
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).respond(200, {});
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
await client.queueToDevice({
eventType: "org.example.foo",
batch: [
FAKE_MSG,
],
batch: [FAKE_MSG],
});
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
@ -320,16 +286,20 @@ describe.each([
}
const expectedCounts = [20, 1];
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).check((request) => {
expect(removeElement(expectedCounts, c => c === Object.keys(request.data.messages).length)).toBeTruthy();
}).respond(200, {});
httpBackend.when(
"PUT", "/sendToDevice/org.example.foo/",
).check((request) => {
httpBackend
.when("PUT", "/sendToDevice/org.example.foo/")
.check((request) => {
expect(
removeElement(expectedCounts, (c) => c === Object.keys(request.data.messages).length),
).toBeTruthy();
})
.respond(200, {});
httpBackend
.when("PUT", "/sendToDevice/org.example.foo/")
.check((request) => {
expect(Object.keys(request.data.messages).length).toEqual(1);
}).respond(200, {});
})
.respond(200, {});
await client.queueToDevice(batch);
await httpBackend.flushAllExpected();

View File

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import MockHttpBackend from 'matrix-mock-request';
import MockHttpBackend from "matrix-mock-request";
import { MAIN_ROOM_TIMELINE, ReceiptType } from '../../src/@types/read_receipts';
import { MAIN_ROOM_TIMELINE, ReceiptType } from "../../src/@types/read_receipts";
import { MatrixClient } from "../../src/client";
import { Feature, ServerSupport } from '../../src/feature';
import { EventType } from '../../src/matrix';
import { synthesizeReceipt } from '../../src/models/read-receipt';
import { encodeUri } from '../../src/utils';
import { Feature, ServerSupport } from "../../src/feature";
import { EventType } from "../../src/matrix";
import { synthesizeReceipt } from "../../src/models/read-receipt";
import { encodeUri } from "../../src/utils";
import * as utils from "../test-utils/test-utils";
// Jest now uses @sinonjs/fake-timers which exposes tickAsync() and a number of
@ -32,7 +32,7 @@ import * as utils from "../test-utils/test-utils";
// and avoids assuming anything about the app's behaviour.
const realSetTimeout = setTimeout;
function flushPromises() {
return new Promise(r => {
return new Promise((r) => {
realSetTimeout(r, 1);
});
}
@ -53,7 +53,7 @@ const threadEvent = utils.mkEvent({
"m.relates_to": {
"event_id": THREAD_ID,
"m.in_reply_to": {
"event_id": THREAD_ID,
event_id: THREAD_ID,
},
"rel_type": "m.thread",
},
@ -66,7 +66,7 @@ const roomEvent = utils.mkEvent({
user: "@bob:matrix.org",
room: ROOM_ID,
content: {
"body": "Hello from a room",
body: "Hello from a room",
},
});
@ -87,15 +87,19 @@ describe("Read receipt", () => {
describe("sendReceipt", () => {
it("sends a thread read receipt", async () => {
httpBackend.when(
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
httpBackend
.when(
"POST",
encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId()!,
}),
).check((request) => {
)
.check((request) => {
expect(request.data.thread_id).toEqual(THREAD_ID);
}).respond(200, {});
})
.respond(200, {});
mockServerSideSupport(client, ServerSupport.Stable);
client.sendReceipt(threadEvent, ReceiptType.Read, {});
@ -105,15 +109,19 @@ describe("Read receipt", () => {
});
it("sends an unthreaded receipt", async () => {
httpBackend.when(
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
httpBackend
.when(
"POST",
encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId()!,
}),
).check((request) => {
)
.check((request) => {
expect(request.data.thread_id).toBeUndefined();
}).respond(200, {});
})
.respond(200, {});
mockServerSideSupport(client, ServerSupport.Stable);
client.sendReadReceipt(threadEvent, ReceiptType.Read, true);
@ -123,15 +131,19 @@ describe("Read receipt", () => {
});
it("sends a room read receipt", async () => {
httpBackend.when(
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
httpBackend
.when(
"POST",
encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: roomEvent.getId()!,
}),
).check((request) => {
)
.check((request) => {
expect(request.data.thread_id).toEqual(MAIN_ROOM_TIMELINE);
}).respond(200, {});
})
.respond(200, {});
mockServerSideSupport(client, ServerSupport.Stable);
client.sendReceipt(roomEvent, ReceiptType.Read, {});
@ -141,15 +153,19 @@ describe("Read receipt", () => {
});
it("sends a room read receipt when there's no server support", async () => {
httpBackend.when(
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
httpBackend
.when(
"POST",
encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId()!,
}),
).check((request) => {
)
.check((request) => {
expect(request.data.thread_id).toBeUndefined();
}).respond(200, {});
})
.respond(200, {});
mockServerSideSupport(client, ServerSupport.Unsupported);
client.sendReceipt(threadEvent, ReceiptType.Read, {});
@ -159,15 +175,19 @@ describe("Read receipt", () => {
});
it("sends a valid room read receipt even when body omitted", async () => {
httpBackend.when(
"POST", encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
httpBackend
.when(
"POST",
encodeUri("/rooms/$roomId/receipt/$receiptType/$eventId", {
$roomId: ROOM_ID,
$receiptType: ReceiptType.Read,
$eventId: threadEvent.getId()!,
}),
).check((request) => {
)
.check((request) => {
expect(request.data).toEqual({});
}).respond(200, {});
})
.respond(200, {});
mockServerSideSupport(client, ServerSupport.Unsupported);
client.sendReceipt(threadEvent, ReceiptType.Read, undefined);

View File

@ -27,15 +27,15 @@ describe("Relations", function() {
// Create an instance of an annotation
const eventData = {
"sender": "@bob:example.com",
"type": "m.reaction",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
sender: "@bob:example.com",
type: "m.reaction",
event_id: "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
room_id: "!pzVjCQSoQPpXQeHpmK:example.com",
content: {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"key": "👍️",
"rel_type": "m.annotation",
event_id: "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
key: "👍️",
rel_type: "m.annotation",
},
},
};
@ -77,22 +77,22 @@ describe("Relations", function() {
it("should emit created regardless of ordering", async function () {
const targetEvent = new MatrixEvent({
"sender": "@bob:example.com",
"type": "m.room.message",
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {},
sender: "@bob:example.com",
type: "m.room.message",
event_id: "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
room_id: "!pzVjCQSoQPpXQeHpmK:example.com",
content: {},
});
const relationEvent = new MatrixEvent({
"sender": "@bob:example.com",
"type": "m.reaction",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
sender: "@bob:example.com",
type: "m.reaction",
event_id: "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
room_id: "!pzVjCQSoQPpXQeHpmK:example.com",
content: {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"key": "👍️",
"rel_type": "m.annotation",
event_id: "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
key: "👍️",
rel_type: "m.annotation",
},
},
});
@ -100,7 +100,7 @@ describe("Relations", function() {
// Add the target event first, then the relation event
{
const room = new Room("room123", null!, null!);
const relationsCreated = new Promise(resolve => {
const relationsCreated = new Promise((resolve) => {
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
@ -114,7 +114,7 @@ describe("Relations", function() {
// Add the relation event first, then the target event
{
const room = new Room("room123", null!, null!);
const relationsCreated = new Promise(resolve => {
const relationsCreated = new Promise((resolve) => {
targetEvent.once(MatrixEventEvent.RelationsCreated, resolve);
});
@ -141,31 +141,31 @@ describe("Relations", function() {
// Create an instance of a state event with rel_type m.replace
const originalTopic = new MatrixEvent({
"sender": userId,
"type": "m.room.topic",
"event_id": "$orig",
"room_id": room.roomId,
"content": {
"topic": "orig",
sender: userId,
type: "m.room.topic",
event_id: "$orig",
room_id: room.roomId,
content: {
topic: "orig",
},
"state_key": "",
state_key: "",
});
const badlyEditedTopic = new MatrixEvent({
"sender": userId,
"type": "m.room.topic",
"event_id": "$orig",
"room_id": room.roomId,
"content": {
sender: userId,
type: "m.room.topic",
event_id: "$orig",
room_id: room.roomId,
content: {
"topic": "topic",
"m.new_content": {
"topic": "edit",
topic: "edit",
},
"m.relates_to": {
"event_id": "$orig",
"rel_type": "m.replace",
event_id: "$orig",
rel_type: "m.replace",
},
},
"state_key": "",
state_key: "",
});
await relations.setTargetEvent(originalTopic);
@ -188,14 +188,14 @@ describe("Relations", function() {
// Create an instance of an annotation
const eventData = {
"sender": "@bob:example.com",
"type": "m.room.message",
"event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
"room_id": "!pzVjCQSoQPpXQeHpmK:example.com",
"content": {
sender: "@bob:example.com",
type: "m.room.message",
event_id: "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw",
room_id: "!pzVjCQSoQPpXQeHpmK:example.com",
content: {
"m.relates_to": {
"event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
"rel_type": "m.replace",
event_id: "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o",
rel_type: "m.replace",
},
},
};

View File

@ -21,7 +21,7 @@ import {
RendezvousTransport,
RendezvousTransportDetails,
} from "../../../src/rendezvous";
import { sleep } from '../../../src/utils';
import { sleep } from "../../../src/utils";
export class DummyTransport<D extends RendezvousTransportDetails, T> implements RendezvousTransport<T> {
otherParty?: DummyTransport<D, T>;
@ -41,8 +41,9 @@ export class DummyTransport<D extends RendezvousTransportDetails, T> implements
async send(data: T): Promise<void> {
logger.info(
`[${this.name}] => [${this.otherParty?.name}] Attempting to send data: ${
JSON.stringify(data)} where etag matches ${this.etag}`,
`[${this.name}] => [${this.otherParty?.name}] Attempting to send data: ${JSON.stringify(
data,
)} where etag matches ${this.etag}`,
);
// eslint-disable-next-line no-constant-condition
while (!this.cancelled) {
@ -72,8 +73,9 @@ export class DummyTransport<D extends RendezvousTransportDetails, T> implements
);
return this.data;
}
logger.info(`[${this.name}] Sleeping to retry receive after etag ${
this.lastEtagReceived} as remote is ${this.etag}`);
logger.info(
`[${this.name}] Sleeping to retry receive after etag ${this.lastEtagReceived} as remote is ${this.etag}`,
);
await sleep(250);
}

View File

@ -14,25 +14,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import '../../olm-loader';
import "../../olm-loader";
import { RendezvousFailureReason, RendezvousIntent } from "../../../src/rendezvous";
import { MSC3903ECDHPayload, MSC3903ECDHv1RendezvousChannel } from '../../../src/rendezvous/channels';
import { decodeBase64 } from '../../../src/crypto/olmlib';
import { DummyTransport } from './DummyTransport';
import { MSC3903ECDHPayload, MSC3903ECDHv1RendezvousChannel } from "../../../src/rendezvous/channels";
import { decodeBase64 } from "../../../src/crypto/olmlib";
import { DummyTransport } from "./DummyTransport";
function makeTransport(name: string) {
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: 'dummy' });
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: "dummy" });
}
describe('ECDHv1', function() {
describe("ECDHv1", function () {
beforeAll(async function () {
await global.Olm.init();
});
describe('with crypto', () => {
describe("with crypto", () => {
it("initiator wants to sign in", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -56,8 +56,8 @@ describe('ECDHv1', function() {
});
it("initiator wants to reciprocate", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -81,8 +81,8 @@ describe('ECDHv1', function() {
});
it("double connect", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -103,8 +103,8 @@ describe('ECDHv1', function() {
});
it("closed", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -129,8 +129,8 @@ describe('ECDHv1', function() {
});
it("require ciphertext", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -153,8 +153,8 @@ describe('ECDHv1', function() {
});
it("ciphertext before set up", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob");
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;

View File

@ -16,13 +16,8 @@ limitations under the License.
import MockHttpBackend from "matrix-mock-request";
import '../../olm-loader';
import {
MSC3906Rendezvous,
RendezvousCode,
RendezvousFailureReason,
RendezvousIntent,
} from "../../../src/rendezvous";
import "../../olm-loader";
import { MSC3906Rendezvous, RendezvousCode, RendezvousFailureReason, RendezvousIntent } from "../../../src/rendezvous";
import {
ECDHv1RendezvousCode,
MSC3903ECDHPayload,
@ -46,7 +41,11 @@ function makeMockClient(opts: {
msc3886Enabled: boolean;
devices?: Record<string, Partial<DeviceInfo>>;
verificationFunction?: (
userId: string, deviceId: string, verified: boolean, blocked: boolean, known: boolean,
userId: string,
deviceId: string,
verified: boolean,
blocked: boolean,
known: boolean,
) => void;
crossSigningIds?: Record<string, string>;
}): MatrixClient {
@ -59,9 +58,15 @@ function makeMockClient(opts: {
},
};
},
getUserId() { return opts.userId; },
getDeviceId() { return opts.deviceId; },
getDeviceEd25519Key() { return opts.deviceKey; },
getUserId() {
return opts.userId;
},
getDeviceId() {
return opts.deviceId;
},
getDeviceEd25519Key() {
return opts.deviceKey;
},
baseUrl: "https://example.com",
crypto: {
getStoredDevice(userId: string, deviceId: string) {
@ -77,8 +82,8 @@ function makeMockClient(opts: {
} as unknown as MatrixClient;
}
function makeTransport(name: string, uri = 'https://test.rz/123456') {
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: 'http.v1', uri });
function makeTransport(name: string, uri = "https://test.rz/123456") {
return new DummyTransport<any, MSC3903ECDHPayload>(name, { type: "http.v1", uri });
}
describe("Rendezvous", function () {
@ -97,7 +102,7 @@ describe("Rendezvous", function() {
});
afterEach(function () {
transports.forEach(x => x.cleanup());
transports.forEach((x) => x.cleanup());
});
it("generate and cancel", async function () {
@ -127,11 +132,11 @@ describe("Rendezvous", function() {
expect(aliceRz.code).toBeUndefined();
const codePromise = aliceRz.generateCode();
await httpBackend.flush('');
await httpBackend.flush("");
await aliceRz.generateCode();
expect(typeof aliceRz.code).toBe('string');
expect(typeof aliceRz.code).toBe("string");
await codePromise;
@ -140,8 +145,9 @@ describe("Rendezvous", function() {
expect(code.intent).toEqual(RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE);
expect(code.rendezvous?.algorithm).toEqual("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256");
expect(code.rendezvous?.transport.type).toEqual("org.matrix.msc3886.http.v1");
expect((code.rendezvous?.transport as MSC3886SimpleHttpRendezvousTransportDetails).uri)
.toEqual("https://fallbackserver/rz/123");
expect((code.rendezvous?.transport as MSC3886SimpleHttpRendezvousTransportDetails).uri).toEqual(
"https://fallbackserver/rz/123",
);
httpBackend.when("DELETE", "https://fallbackserver/rz").response = {
body: null,
@ -152,7 +158,7 @@ describe("Rendezvous", function() {
};
const cancelPromise = aliceRz.cancel(RendezvousFailureReason.UserDeclined);
await httpBackend.flush('');
await httpBackend.flush("");
expect(cancelPromise).resolves.toBeUndefined();
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequests();
@ -161,8 +167,8 @@ describe("Rendezvous", function() {
});
it("no protocols", async function () {
const aliceTransport = makeTransport('Alice');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -199,14 +205,14 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.finish',
outcome: 'unsupported',
type: "m.login.finish",
outcome: "unsupported",
});
})();
@ -215,8 +221,8 @@ describe("Rendezvous", function() {
});
it("new device declines protocol", async function () {
const aliceTransport = makeTransport('Alice', 'https://test.rz/123456');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -253,17 +259,17 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.progress',
protocols: ['org.matrix.msc3906.login_token'],
type: "m.login.progress",
protocols: ["org.matrix.msc3906.login_token"],
});
await bobEcdh.send({ type: 'm.login.finish', outcome: 'unsupported' });
await bobEcdh.send({ type: "m.login.finish", outcome: "unsupported" });
})();
await aliceStartProm;
@ -273,8 +279,8 @@ describe("Rendezvous", function() {
});
it("new device declines protocol", async function () {
const aliceTransport = makeTransport('Alice', 'https://test.rz/123456');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -311,17 +317,17 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.progress',
protocols: ['org.matrix.msc3906.login_token'],
type: "m.login.progress",
protocols: ["org.matrix.msc3906.login_token"],
});
await bobEcdh.send({ type: 'm.login.progress', protocol: 'bad protocol' });
await bobEcdh.send({ type: "m.login.progress", protocol: "bad protocol" });
})();
await aliceStartProm;
@ -331,8 +337,8 @@ describe("Rendezvous", function() {
});
it("decline on existing device", async function () {
const aliceTransport = makeTransport('Alice', 'https://test.rz/123456');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -369,17 +375,17 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.progress',
protocols: ['org.matrix.msc3906.login_token'],
type: "m.login.progress",
protocols: ["org.matrix.msc3906.login_token"],
});
await bobEcdh.send({ type: 'm.login.progress', protocol: 'org.matrix.msc3906.login_token' });
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
})();
await aliceStartProm;
@ -387,12 +393,12 @@ describe("Rendezvous", function() {
await aliceRz.declineLoginOnExistingDevice();
const loginToken = await bobEcdh.receive();
expect(loginToken).toEqual({ type: 'm.login.finish', outcome: 'declined' });
expect(loginToken).toEqual({ type: "m.login.finish", outcome: "declined" });
});
it("approve on existing device + no verification", async function () {
const aliceTransport = makeTransport('Alice', 'https://test.rz/123456');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -429,17 +435,17 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.progress',
protocols: ['org.matrix.msc3906.login_token'],
type: "m.login.progress",
protocols: ["org.matrix.msc3906.login_token"],
});
await bobEcdh.send({ type: 'm.login.progress', protocol: 'org.matrix.msc3906.login_token' });
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
})();
await aliceStartProm;
@ -449,8 +455,8 @@ describe("Rendezvous", function() {
const bobCompleteProm = (async () => {
const loginToken = await bobEcdh.receive();
expect(loginToken).toEqual({ type: 'm.login.progress', login_token: 'token', homeserver: alice.baseUrl });
await bobEcdh.send({ type: 'm.login.finish', outcome: 'success' });
expect(loginToken).toEqual({ type: "m.login.progress", login_token: "token", homeserver: alice.baseUrl });
await bobEcdh.send({ type: "m.login.finish", outcome: "success" });
})();
await confirmProm;
@ -458,8 +464,8 @@ describe("Rendezvous", function() {
});
async function completeLogin(devices: Record<string, Partial<DeviceInfo>>) {
const aliceTransport = makeTransport('Alice', 'https://test.rz/123456');
const bobTransport = makeTransport('Bob', 'https://test.rz/999999');
const aliceTransport = makeTransport("Alice", "https://test.rz/123456");
const bobTransport = makeTransport("Bob", "https://test.rz/999999");
transports.push(aliceTransport, bobTransport);
aliceTransport.otherParty = bobTransport;
bobTransport.otherParty = aliceTransport;
@ -473,10 +479,10 @@ describe("Rendezvous", function() {
msc3882Enabled: true,
msc3886Enabled: false,
devices,
deviceKey: 'aaaa',
deviceKey: "aaaa",
verificationFunction: aliceVerification,
crossSigningIds: {
master: 'mmmmm',
master: "mmmmm",
},
});
const aliceEcdh = new MSC3903ECDHv1RendezvousChannel(aliceTransport, undefined, aliceOnFailure);
@ -503,17 +509,17 @@ describe("Rendezvous", function() {
// await bobEcdh.send({ type: 'm.login.progress', intent: RendezvousIntent.LOGIN_ON_NEW_DEVICE });
// wait for protocols
logger.info('Bob waiting for protocols');
logger.info("Bob waiting for protocols");
const protocols = await bobEcdh.receive();
logger.info(`Bob protocols: ${JSON.stringify(protocols)}`);
expect(protocols).toEqual({
type: 'm.login.progress',
protocols: ['org.matrix.msc3906.login_token'],
type: "m.login.progress",
protocols: ["org.matrix.msc3906.login_token"],
});
await bobEcdh.send({ type: 'm.login.progress', protocol: 'org.matrix.msc3906.login_token' });
await bobEcdh.send({ type: "m.login.progress", protocol: "org.matrix.msc3906.login_token" });
})();
await aliceStartProm;
@ -523,11 +529,11 @@ describe("Rendezvous", function() {
const bobLoginProm = (async () => {
const loginToken = await bobEcdh.receive();
expect(loginToken).toEqual({ type: 'm.login.progress', login_token: 'token', homeserver: alice.baseUrl });
await bobEcdh.send({ type: 'm.login.finish', outcome: 'success', device_id: 'BOB', device_key: 'bbbb' });
expect(loginToken).toEqual({ type: "m.login.progress", login_token: "token", homeserver: alice.baseUrl });
await bobEcdh.send({ type: "m.login.finish", outcome: "success", device_id: "BOB", device_key: "bbbb" });
})();
expect(await confirmProm).toEqual('BOB');
expect(await confirmProm).toEqual("BOB");
await bobLoginProm;
return {
@ -550,11 +556,11 @@ describe("Rendezvous", function() {
const bobVerifyProm = (async () => {
const verified = await bobEcdh.receive();
expect(verified).toEqual({
type: 'm.login.finish',
outcome: 'verified',
verifying_device_id: 'ALICE',
verifying_device_key: 'aaaa',
master_key: 'mmmmm',
type: "m.login.finish",
outcome: "verified",
verifying_device_id: "ALICE",
verifying_device_key: "aaaa",
master_key: "mmmmm",
});
})();

View File

@ -20,13 +20,17 @@ import type { MatrixClient } from "../../../src";
import { RendezvousFailureReason } from "../../../src/rendezvous";
import { MSC3886SimpleHttpRendezvousTransport } from "../../../src/rendezvous/transports";
function makeMockClient(opts: { userId: string, deviceId: string, msc3886Enabled: boolean}): MatrixClient {
function makeMockClient(opts: { userId: string; deviceId: string; msc3886Enabled: boolean }): MatrixClient {
return {
doesServerSupportUnstableFeature(feature: string) {
return Promise.resolve(opts.msc3886Enabled && feature === "org.matrix.msc3886");
},
getUserId() { return opts.userId; },
getDeviceId() { return opts.deviceId; },
getUserId() {
return opts.userId;
},
getDeviceId() {
return opts.deviceId;
},
requestLoginToken() {
return Promise.resolve({ login_token: "token" });
},
@ -51,10 +55,11 @@ describe("SimpleHttpRendezvousTransport", function() {
) {
const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc3886Enabled });
const simpleHttpTransport = new MSC3886SimpleHttpRendezvousTransport({ client, fallbackRzServer, fetchFn });
{ // initial POST
const expectedPostLocation = msc3886Enabled ?
`${client.baseUrl}/_matrix/client/unstable/org.matrix.msc3886/rendezvous` :
fallbackRzServer;
{
// initial POST
const expectedPostLocation = msc3886Enabled
? `${client.baseUrl}/_matrix/client/unstable/org.matrix.msc3886/rendezvous`
: fallbackRzServer;
const prom = simpleHttpTransport.send({});
httpBackend.when("POST", expectedPostLocation).response = {
@ -66,13 +71,14 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
await prom;
}
const details = await simpleHttpTransport.details();
expect(details.uri).toBe(expectedFinalLocation);
{ // first GET without etag
{
// first GET without etag
const prom = simpleHttpTransport.receive();
httpBackend.when("GET", expectedFinalLocation).response = {
body: {},
@ -83,7 +89,7 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toEqual({});
httpBackend.verifyNoOutstandingRequests();
httpBackend.verifyNoOutstandingExpectation();
@ -112,7 +118,7 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toStrictEqual(undefined);
});
@ -132,16 +138,11 @@ describe("SimpleHttpRendezvousTransport", function() {
headers: {},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
});
it("POST with absolute path response", async function () {
await postAndCheckLocation(
false,
"https://fallbackserver/rz",
"/123",
"https://fallbackserver/123",
);
await postAndCheckLocation(false, "https://fallbackserver/rz", "/123", "https://fallbackserver/123");
});
it("POST to built-in MSC3886 implementation", async function () {
@ -198,7 +199,7 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toStrictEqual(undefined);
});
@ -209,7 +210,8 @@ describe("SimpleHttpRendezvousTransport", function() {
fallbackRzServer: "https://fallbackserver/rz",
fetchFn,
});
{ // initial POST
{
// initial POST
const prom = simpleHttpTransport.send({ foo: "baa" });
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
expect(headers["content-type"]).toEqual("application/json");
@ -223,10 +225,11 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toStrictEqual(undefined);
}
{ // first GET without etag
{
// first GET without etag
const prom = simpleHttpTransport.receive();
httpBackend.when("GET", "https://fallbackserver/rz/123").response = {
body: { foo: "baa" },
@ -238,10 +241,11 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toEqual({ foo: "baa" });
}
{ // subsequent GET which should have etag from previous request
{
// subsequent GET which should have etag from previous request
const prom = simpleHttpTransport.receive();
httpBackend.when("GET", "https://fallbackserver/rz/123").check(({ headers }) => {
expect(headers["if-none-match"]).toEqual("aaa");
@ -255,7 +259,7 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toEqual({ foo: "baa" });
}
});
@ -267,7 +271,8 @@ describe("SimpleHttpRendezvousTransport", function() {
fallbackRzServer: "https://fallbackserver/rz",
fetchFn,
});
{ // initial POST
{
// initial POST
const prom = simpleHttpTransport.send({ foo: "baa" });
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
expect(headers["content-type"]).toEqual("application/json");
@ -281,10 +286,11 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('', 1);
await httpBackend.flush("", 1);
await prom;
}
{ // first PUT without etag
{
// first PUT without etag
const prom = simpleHttpTransport.send({ a: "b" });
httpBackend.when("PUT", "https://fallbackserver/rz/123").check(({ headers, data }) => {
expect(headers["if-match"]).toBeUndefined();
@ -294,14 +300,15 @@ describe("SimpleHttpRendezvousTransport", function() {
response: {
statusCode: 202,
headers: {
"etag": "aaa",
etag: "aaa",
},
},
};
await httpBackend.flush('', 1);
await httpBackend.flush("", 1);
await prom;
}
{ // subsequent PUT which should have etag from previous request
{
// subsequent PUT which should have etag from previous request
const prom = simpleHttpTransport.send({ c: "d" });
httpBackend.when("PUT", "https://fallbackserver/rz/123").check(({ headers }) => {
expect(headers["if-match"]).toEqual("aaa");
@ -310,11 +317,11 @@ describe("SimpleHttpRendezvousTransport", function() {
response: {
statusCode: 202,
headers: {
"etag": "bbb",
etag: "bbb",
},
},
};
await httpBackend.flush('', 1);
await httpBackend.flush("", 1);
await prom;
}
});
@ -326,7 +333,8 @@ describe("SimpleHttpRendezvousTransport", function() {
fallbackRzServer: "https://fallbackserver/rz",
fetchFn,
});
{ // Create
{
// Create
const prom = simpleHttpTransport.send({ foo: "baa" });
httpBackend.when("POST", "https://fallbackserver/rz").check(({ headers, data }) => {
expect(headers["content-type"]).toEqual("application/json");
@ -340,10 +348,11 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(await prom).toStrictEqual(undefined);
}
{ // Cancel
{
// Cancel
const prom = simpleHttpTransport.cancel(RendezvousFailureReason.UserDeclined);
httpBackend.when("DELETE", "https://fallbackserver/rz/123").response = {
body: null,
@ -352,7 +361,7 @@ describe("SimpleHttpRendezvousTransport", function() {
headers: {},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
await prom;
}
});
@ -406,7 +415,7 @@ describe("SimpleHttpRendezvousTransport", function() {
headers: {},
},
};
await httpBackend.flush('', 1);
await httpBackend.flush("", 1);
expect(onFailure).toBeCalledWith(RendezvousFailureReason.Unknown);
});
@ -420,7 +429,8 @@ describe("SimpleHttpRendezvousTransport", function() {
onFailure,
});
{ // initial POST
{
// initial POST
const prom = simpleHttpTransport.send({ foo: "baa" });
httpBackend.when("POST", "https://fallbackserver/rz").response = {
body: null,
@ -432,10 +442,11 @@ describe("SimpleHttpRendezvousTransport", function() {
},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
await prom;
}
{ // GET with 404 to simulate expiry
{
// GET with 404 to simulate expiry
expect(simpleHttpTransport.receive()).resolves.toBeUndefined();
httpBackend.when("GET", "https://fallbackserver/rz/123").response = {
body: { foo: "baa" },
@ -444,7 +455,7 @@ describe("SimpleHttpRendezvousTransport", function() {
headers: {},
},
};
await httpBackend.flush('');
await httpBackend.flush("");
expect(onFailure).toBeCalledWith(RendezvousFailureReason.Expired);
}
});

View File

@ -44,14 +44,13 @@ describe("RoomMember", function() {
avatar_url: "mxc://flibble/wibble",
},
});
const url = member.getAvatarUrl(hsUrl, 1, 1, '', false, false);
const url = member.getAvatarUrl(hsUrl, 1, 1, "", false, false);
// we don't care about how the mxc->http conversion is done, other
// than it contains the mxc body.
expect(url?.indexOf("flibble/wibble")).not.toEqual(-1);
});
it("should return nothing if there is no m.room.member and allowDefault=false",
function() {
it("should return nothing if there is no m.room.member and allowDefault=false", function () {
const url = member.getAvatarUrl(hsUrl, 64, 64, "crop", false, false);
expect(url).toEqual(null);
});
@ -82,8 +81,7 @@ describe("RoomMember", function() {
expect(memberB.powerLevelNorm).toEqual(100);
});
it("should emit 'RoomMember.powerLevel' if the power level changes.",
function() {
it("should emit 'RoomMember.powerLevel' if the power level changes.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
@ -111,8 +109,7 @@ describe("RoomMember", function() {
expect(emitCount).toEqual(1);
});
it("should honour power levels of zero.",
function() {
it("should honour power levels of zero.", function () {
const event = utils.mkEvent({
type: "m.room.power_levels",
room: roomId,
@ -132,7 +129,7 @@ describe("RoomMember", function() {
member.powerLevel = 1;
member.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(0);
expect(emitEvent).toEqual(event);
});
@ -159,7 +156,7 @@ describe("RoomMember", function() {
member.on(RoomMemberEvent.PowerLevel, function (emitEvent, emitMember) {
emitCount += 1;
expect(emitMember.userId).toEqual('@alice:bar');
expect(emitMember.userId).toEqual("@alice:bar");
expect(emitMember.powerLevel).toEqual(20);
expect(emitEvent).toEqual(event);
});
@ -172,7 +169,8 @@ describe("RoomMember", function() {
it("should no-op if given a non-state or unrelated event", () => {
const fn = jest.spyOn(member, "emit");
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
member.setPowerLevelEvent(utils.mkEvent({
member.setPowerLevelEvent(
utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
user: userA,
@ -184,7 +182,8 @@ describe("RoomMember", function() {
},
skey: "invalid",
event: true,
}));
}),
);
const nonStateEv = utils.mkEvent({
type: EventType.RoomPowerLevels,
room: roomId,
@ -199,13 +198,15 @@ describe("RoomMember", function() {
});
delete nonStateEv.event.state_key;
member.setPowerLevelEvent(nonStateEv);
member.setPowerLevelEvent(utils.mkEvent({
member.setPowerLevelEvent(
utils.mkEvent({
type: EventType.Sticker,
room: roomId,
user: userA,
content: {},
event: true,
}));
}),
);
expect(fn).not.toHaveBeenCalledWith(RoomMemberEvent.PowerLevel);
});
});
@ -223,9 +224,7 @@ describe("RoomMember", function() {
user: userA,
room: roomId,
content: {
user_ids: [
userA, userC,
],
user_ids: [userA, userC],
},
event: true,
});
@ -238,15 +237,12 @@ describe("RoomMember", function() {
expect(memberC.typing).toEqual(true);
});
it("should emit 'RoomMember.typing' if the typing state changes",
function() {
it("should emit 'RoomMember.typing' if the typing state changes", function () {
const event = utils.mkEvent({
type: "m.typing",
room: roomId,
content: {
user_ids: [
userA, userC,
],
user_ids: [userA, userC],
},
event: true,
});
@ -363,8 +359,7 @@ describe("RoomMember", function() {
room: roomId,
});
it("should set 'membership' and assign the event to 'events.member'.",
function() {
it("should set 'membership' and assign the event to 'events.member'.", function () {
member.setMembershipEvent(inviteEvent);
expect(member.membership).toEqual("invite");
expect(member.events.member).toEqual(inviteEvent);
@ -381,12 +376,17 @@ describe("RoomMember", function() {
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
event: true,
mship: "join",
room: roomId,
user: userB,
}),
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
event: true,
mship: "join",
room: roomId,
user: userC,
name: "Alice",
}),
joinEvent,
];
@ -460,8 +460,11 @@ describe("RoomMember", function() {
}
return [
utils.mkMembership({
event: true, mship: "join", room: roomId,
user: userC, name: "Alice",
event: true,
mship: "join",
room: roomId,
user: userC,
name: "Alice",
}),
joinEvent,
];

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