1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-02 17:02:31 +03:00

Merge branch 'develop' into bwindels/verification-right-panel

This commit is contained in:
Travis Ralston
2020-01-16 13:10:04 -07:00
124 changed files with 2875 additions and 1834 deletions

View File

@@ -1,11 +1,9 @@
{ {
"sourceMaps": true,
"presets": [ "presets": [
["@babel/preset-env", { ["@babel/preset-env", {
"targets": { "targets": {
"browsers": [ "node": 10
"last 2 versions"
],
"node": 12
}, },
"modules": "commonjs" "modules": "commonjs"
}], }],

View File

@@ -1,11 +1,35 @@
steps: steps:
- label: ":eslint: Lint" - label: ":eslint: JS Lint"
command: command:
- "yarn install" - "yarn install"
- "yarn lint" - "yarn lint:js"
plugins: plugins:
- docker#v3.0.1: - docker#v3.0.1:
image: "node:10" image: "node:12"
- label: ":tslint: TS Lint"
command:
- "yarn install"
- "yarn lint:ts"
plugins:
- docker#v3.0.1:
image: "node:12"
- label: ":typescript: Types Lint"
command:
- "yarn install"
- "yarn lint:types"
plugins:
- docker#v3.0.1:
image: "node:12"
- label: "🛠 Build"
command:
- "yarn install"
- "yarn build"
plugins:
- docker#v3.0.1:
image: "node:12"
- label: ":jest: Tests" - label: ":jest: Tests"
command: command:
@@ -13,7 +37,7 @@ steps:
- "yarn test" - "yarn test"
plugins: plugins:
- docker#v3.0.1: - docker#v3.0.1:
image: "node:10" image: "node:12"
- label: "📃 Docs" - label: "📃 Docs"
command: command:
@@ -21,7 +45,7 @@ steps:
- "yarn gendoc" - "yarn gendoc"
plugins: plugins:
- docker#v3.0.1: - docker#v3.0.1:
image: "node:10" image: "node:12"
- wait - wait

View File

@@ -1,3 +1,61 @@
Changes in [3.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.0.0) (2020-01-13)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v3.0.0-rc.1...v3.0.0)
* No changes from rc.1
Changes in [3.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v3.0.0-rc.1) (2020-01-06)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6...v3.0.0-rc.1)
BREAKING CHANGES
================
* matrix-js-sdk no longer uses bluebird promises, so promises returned
by the js-sdk no longer support the done() method. Code that calls
done() on promises returned by the js-sdk will break and will need
to be updated to remove the done() call.
All Changes
===========
* Make displayName disambiguation more fuzzy especially against RTL/LTR
content
[\#1141](https://github.com/matrix-org/matrix-js-sdk/pull/1141)
* stop trying to resend event if we get M_TOO_LARGE
[\#1129](https://github.com/matrix-org/matrix-js-sdk/pull/1129)
* Fix creating a key backup with cross signing diabled
[\#1139](https://github.com/matrix-org/matrix-js-sdk/pull/1139)
* Use checkDeviceTrust with key backup
[\#1138](https://github.com/matrix-org/matrix-js-sdk/pull/1138)
* Add support for passthrough SSSS secrets
[\#1128](https://github.com/matrix-org/matrix-js-sdk/pull/1128)
* Add support for key backups using secret storage
[\#1118](https://github.com/matrix-org/matrix-js-sdk/pull/1118)
* Remove unused user verification event
[\#1117](https://github.com/matrix-org/matrix-js-sdk/pull/1117)
* Fix check for private keys
[\#1116](https://github.com/matrix-org/matrix-js-sdk/pull/1116)
* Restore watching mode for `start:watch`
[\#1115](https://github.com/matrix-org/matrix-js-sdk/pull/1115)
* Add secret storage bootstrap flow
[\#1079](https://github.com/matrix-org/matrix-js-sdk/pull/1079)
* Part 1 of many: Upgrade to babel@7 and TypeScript
[\#1112](https://github.com/matrix-org/matrix-js-sdk/pull/1112)
* Remove Bluebird: phase 2.5
[\#1100](https://github.com/matrix-org/matrix-js-sdk/pull/1100)
* Remove Bluebird: phase 3
[\#1088](https://github.com/matrix-org/matrix-js-sdk/pull/1088)
* ignore m.key.verification.done messages when we don't expect any more
messages
[\#1104](https://github.com/matrix-org/matrix-js-sdk/pull/1104)
* dont cancel on remote echo of own .request event
[\#1111](https://github.com/matrix-org/matrix-js-sdk/pull/1111)
* Refactor verification request code
[\#1109](https://github.com/matrix-org/matrix-js-sdk/pull/1109)
* Fix device list's cross-signing storage path
[\#1105](https://github.com/matrix-org/matrix-js-sdk/pull/1105)
* yarn upgrade
[\#1103](https://github.com/matrix-org/matrix-js-sdk/pull/1103)
Changes in [2.4.6](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6) (2019-12-09) Changes in [2.4.6](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6) (2019-12-09)
================================================================================================ ================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6-rc.1...v2.4.6) [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.6-rc.1...v2.4.6)

View File

@@ -36,8 +36,16 @@ minutes.
Code style Code style
~~~~~~~~~~ ~~~~~~~~~~
The code-style for matrix-js-sdk is not formally documented, but contributors The js-sdk aims to target TypeScript/ES6. All new files should be written in
are encouraged to read the code style document for matrix-react-sdk TypeScript and existing files should use ES6 principles where possible.
Members should not be exported as a default export in general - it causes problems
with the architecture of the SDK (index file becomes less clear) and could
introduce naming problems (as default exports get aliased upon import). In
general, avoid using `export default`.
The remaining code-style for matrix-js-sdk is not formally documented, but
contributors are encouraged to read the code style document for matrix-react-sdk
(`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_) (`<https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md>`_)
and follow the principles set out there. and follow the principles set out there.

View File

@@ -9,12 +9,16 @@ Quickstart
In a browser In a browser
------------ ------------
Download either the full or minified version from Download the browser version from
https://github.com/matrix-org/matrix-js-sdk/releases/latest and add that as a 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`` ``<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 attached to ``window`` through which you can access the SDK. See below for how to
include libolm to enable end-to-end-encryption. include libolm to enable end-to-end-encryption.
The browser bundle supports recent versions of browsers. Typically this is ES2015
or `> 0.5%, last 2 versions, Firefox ESR, not dead` if using
[browserlists](https://github.com/browserslist/browserslist).
Please check [the working browser example](examples/browser) for more information. Please check [the working browser example](examples/browser) for more information.
In Node.js In Node.js
@@ -22,13 +26,18 @@ In Node.js
Ensure you have the latest LTS version of Node.js installed. Ensure you have the latest LTS version of Node.js installed.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/) if you do not have it already. This SDK targets Node 10 for compatibility, which translates to ES6. If you're using
a bundler like webpack you'll likely have to transpile dependencies, including this
SDK, to match your target browsers.
Using `yarn` instead of `npm` is recommended. Please see the Yarn [install guide](https://yarnpkg.com/docs/install/)
if you do not have it already.
``yarn add matrix-js-sdk`` ``yarn add matrix-js-sdk``
```javascript ```javascript
var sdk = require("matrix-js-sdk"); import * as sdk from "matrix-js-sdk";
var client = sdk.createClient("https://matrix.org"); const client = sdk.createClient("https://matrix.org");
client.publicRooms(function(err, data) { client.publicRooms(function(err, data) {
console.log("Public Rooms: %s", JSON.stringify(data)); console.log("Public Rooms: %s", JSON.stringify(data));
}); });
@@ -59,7 +68,7 @@ client.once('sync', function(state, prevState, res) {
To send a message: To send a message:
```javascript ```javascript
var content = { const content = {
"body": "message text", "body": "message text",
"msgtype": "m.text" "msgtype": "m.text"
}; };
@@ -191,10 +200,10 @@ This section provides some useful code snippets which demonstrate the
core functionality of the SDK. These examples assume the SDK is setup like this: core functionality of the SDK. These examples assume the SDK is setup like this:
```javascript ```javascript
var sdk = require("matrix-js-sdk"); import * as sdk from "matrix-js-sdk";
var myUserId = "@example:localhost"; const myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP"; const myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var matrixClient = sdk.createClient({ const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008", baseUrl: "http://localhost:8008",
accessToken: myAccessToken, accessToken: myAccessToken,
userId: myUserId userId: myUserId
@@ -247,11 +256,11 @@ Output:
```javascript ```javascript
matrixClient.on("RoomState.members", function(event, state, member) { matrixClient.on("RoomState.members", function(event, state, member) {
var room = matrixClient.getRoom(state.roomId); const room = matrixClient.getRoom(state.roomId);
if (!room) { if (!room) {
return; return;
} }
var memberList = state.getMembers(); const memberList = state.getMembers();
console.log(room.name); console.log(room.name);
console.log(Array(room.name.length + 1).join("=")); // underline console.log(Array(room.name.length + 1).join("=")); // underline
for (var i = 0; i < memberList.length; i++) { for (var i = 0; i < memberList.length; i++) {
@@ -351,11 +360,6 @@ To build a browser version from scratch when developing::
$ yarn build $ yarn build
``` ```
To constantly do builds when files are modified (using ``watchify``)::
```
$ yarn watch
```
To run tests (Jasmine):: To run tests (Jasmine)::
``` ```
$ yarn test $ yarn test

View File

@@ -1,35 +0,0 @@
var matrixcs = require("./lib/matrix");
const request = require('browser-request');
const queryString = require('qs');
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
// we mimic `request`'s query string interface to make it all work
// as expected.
// browser-request will happily take the constructed string as the
// query string without trying to modify it further.
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
return request(opts, fn);
});
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
var indexedDB;
try {
indexedDB = global.indexedDB;
} catch(e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto"
);
}
);
}
module.exports = matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;

View File

@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk"); console.log("Loading browser sdk");
var client = matrixcs.createClient("http://matrix.org"); var client = matrixcs.createClient("http://matrix.org");

View File

@@ -1,5 +1,3 @@
"use strict";
var myUserId = "@example:localhost"; var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP"; var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var sdk = require("matrix-js-sdk"); var sdk = require("matrix-js-sdk");

View File

@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk"); console.log("Loading browser sdk");
var BASE_URL = "https://matrix.org"; var BASE_URL = "https://matrix.org";
var TOKEN = "accesstokengoeshere"; var TOKEN = "accesstokengoeshere";

View File

@@ -1,6 +0,0 @@
var matrixcs = require("./lib/matrix");
matrixcs.request(require("request"));
module.exports = matrixcs;
var utils = require("./lib/utils");
utils.runPolyfills();

View File

@@ -1,21 +1,23 @@
{ {
"name": "matrix-js-sdk", "name": "matrix-js-sdk",
"version": "2.4.6", "version": "3.0.0",
"description": "Matrix Client-Server SDK for Javascript", "description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": { "scripts": {
"test:watch": "jest spec/ --coverage --testEnvironment node --watch", "start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"test": "jest spec/ --coverage --testEnvironment node", "dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"start": "yarn start:init && yarn start:watch",
"start:watch": "babel src -s -w --skip-initial-build -d lib --verbose --extensions \".ts,.js\"",
"start:init": "babel src -s -d lib --verbose --extensions \".ts,.js\"",
"clean": "rimraf lib dist", "clean": "rimraf lib dist",
"build": "babel src -s -d lib --verbose --extensions \".ts,.js\" && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && terser -c -m -o dist/browser-matrix.min.js --source-map \"content='dist/browser-matrix.js.map'\" dist/browser-matrix.js", "build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"dist": "yarn build", "build:types": "tsc --emitDeclarationOnly",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v", "build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"lint": "eslint --max-warnings 93 src spec", "build:compile-browser": "mkdirp dist && browserify -d src/browser-index.js -p [ tsify -p ./tsconfig.json ] -t [ babelify --sourceMaps=inline --presets [ @babel/preset-env @babel/preset-typescript ] ] | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt" "build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
"gendoc": "jsdoc -c jsdoc.json -P package.json",
"lint": "yarn lint:types && yarn lint:ts && yarn lint:js",
"lint:js": "eslint --max-warnings 93 src spec",
"lint:types": "tsc --noEmit",
"lint:ts": "tslint --project ./tsconfig.json -t stylish",
"test": "jest spec/ --coverage --testEnvironment node",
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -24,28 +26,23 @@
"keywords": [ "keywords": [
"matrix-org" "matrix-org"
], ],
"browser": "browser-index.js", "main": "./lib/index.js",
"typings": "./lib/index.d.ts",
"browser": "./lib/browser-index.js",
"matrix_src_main": "./src/index.ts",
"matrix_src_browser": "./src/browser-index.js",
"author": "matrix.org", "author": "matrix.org",
"license": "Apache-2.0", "license": "Apache-2.0",
"files": [ "files": [
".babelrc", "lib",
".eslintrc.js", "src",
"spec/.eslintrc.js", "git-revision.txt",
"CHANGELOG.md", "CHANGELOG.md",
"CONTRIBUTING.rst", "CONTRIBUTING.rst",
"LICENSE", "LICENSE",
"README.md", "README.md",
"RELEASING.md",
"examples",
"git-hooks",
"git-revision.txt",
"index.js",
"browser-index.js",
"lib",
"package.json", "package.json",
"release.sh", "release.sh"
"spec",
"src"
], ],
"dependencies": { "dependencies": {
"another-json": "^0.2.0", "another-json": "^0.2.0",
@@ -69,11 +66,12 @@
"@babel/preset-typescript": "^7.7.4", "@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4", "@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6", "@babel/runtime": "^7.7.6",
"@types/node": "12",
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
"babelify": "^10.0.0",
"better-docs": "^1.4.7", "better-docs": "^1.4.7",
"browserify": "^16.2.3", "browserify": "^16.5.0",
"browserify-shim": "^3.8.13",
"eslint": "^5.12.0", "eslint": "^5.12.0",
"eslint-config-google": "^0.7.1", "eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.3.0", "eslint-plugin-babel": "^5.3.0",
@@ -84,15 +82,12 @@
"matrix-mock-request": "^1.2.3", "matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz", "olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"source-map-support": "^0.5.13", "terser": "^4.4.3",
"sourceify": "^1.0.0", "tsify": "^4.0.1",
"terser": "^4.3.8", "tslint": "^5.20.1",
"typescript": "^3.7.3", "typescript": "^3.7.3"
"watchify": "^3.11.1"
}, },
"browserify": { "jest": {
"transform": [ "testEnvironment": "node"
"sourceify"
]
} }
} }

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# #
# Script to perform a release of matrix-js-sdk. # Script to perform a release of matrix-js-sdk and downstream projects.
# #
# Requires: # Requires:
# github-changelog-generator; install via: # github-changelog-generator; install via:
@@ -9,6 +9,8 @@
# hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9 # hub; install via brew (macOS) or source/pre-compiled binaries (debian) (https://github.com/github/hub) - Tested on v2.2.9
# npm; typically installed by Node.js # npm; typically installed by Node.js
# yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/) # yarn; install via brew (macOS) or similar (https://yarnpkg.com/docs/install/)
#
# Note: this script is also used to release matrix-react-sdk and riot-web.
set -e set -e

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -18,7 +19,7 @@ limitations under the License.
* A mock implementation of the webstorage api * A mock implementation of the webstorage api
* @constructor * @constructor
*/ */
function MockStorageApi() { export function MockStorageApi() {
this.data = {}; this.data = {};
this.keys = []; this.keys = [];
this.length = 0; this.length = 0;
@@ -52,5 +53,3 @@ MockStorageApi.prototype = {
}, },
}; };
/** */
module.exports = MockStorageApi;

View File

@@ -16,16 +16,16 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
// load olm before the sdk if possible // load olm before the sdk if possible
import './olm-loader'; import './olm-loader';
import sdk from '..';
import testUtils from './test-utils';
import MockHttpBackend from 'matrix-mock-request'; import MockHttpBackend from 'matrix-mock-request';
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store'; import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
import logger from '../lib/logger'; import {logger} from '../src/logger';
import {WebStorageSessionStore} from "../src/store/session/webstorage";
import {syncPromise} from "./test-utils";
import {createClient} from "../src/matrix";
import {MockStorageApi} from "./MockStorageApi";
/** /**
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient * Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
@@ -39,16 +39,16 @@ import logger from '../lib/logger';
* session store. If undefined, we will create a MockStorageApi. * session store. If undefined, we will create a MockStorageApi.
* @param {object} options additional options to pass to the client * @param {object} options additional options to pass to the client
*/ */
export default function TestClient( export function TestClient(
userId, deviceId, accessToken, sessionStoreBackend, options, userId, deviceId, accessToken, sessionStoreBackend, options,
) { ) {
this.userId = userId; this.userId = userId;
this.deviceId = deviceId; this.deviceId = deviceId;
if (sessionStoreBackend === undefined) { if (sessionStoreBackend === undefined) {
sessionStoreBackend = new testUtils.MockStorageApi(); sessionStoreBackend = new MockStorageApi();
} }
const sessionStore = new sdk.WebStorageSessionStore(sessionStoreBackend); const sessionStore = new WebStorageSessionStore(sessionStoreBackend);
this.httpBackend = new MockHttpBackend(); this.httpBackend = new MockHttpBackend();
@@ -65,7 +65,7 @@ export default function TestClient(
this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend); this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend);
options.cryptoStore = this.cryptoStore; options.cryptoStore = this.cryptoStore;
} }
this.client = sdk.createClient(options); this.client = createClient(options);
this.deviceKeys = null; this.deviceKeys = null;
this.oneTimeKeys = {}; this.oneTimeKeys = {};
@@ -97,7 +97,7 @@ TestClient.prototype.start = function() {
return Promise.all([ return Promise.all([
this.httpBackend.flushAllExpected(), this.httpBackend.flushAllExpected(),
testUtils.syncPromise(this.client), syncPromise(this.client),
]).then(() => { ]).then(() => {
logger.log(this + ': started'); logger.log(this + ': started');
}); });
@@ -225,7 +225,7 @@ TestClient.prototype.flushSync = function() {
logger.log(`${this}: flushSync`); logger.log(`${this}: flushSync`);
return Promise.all([ return Promise.all([
this.httpBackend.flush('/sync', 1), this.httpBackend.flush('/sync', 1),
testUtils.syncPromise(this.client), syncPromise(this.client),
]).then(() => { ]).then(() => {
logger.log(`${this}: flushSync completed`); logger.log(`${this}: flushSync completed`);
}); });

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,9 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import TestClient from '../TestClient'; import {TestClient} from '../TestClient';
import testUtils from '../test-utils'; import * as testUtils from '../test-utils';
import logger from '../../lib/logger'; import {logger} from '../../src/logger';
const ROOM_ID = "!room:id"; const ROOM_ID = "!room:id";

View File

@@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -24,17 +25,14 @@ limitations under the License.
* See also `megolm.spec.js`. * See also `megolm.spec.js`.
*/ */
"use strict";
import 'source-map-support/register';
// load olm before the sdk if possible // load olm before the sdk if possible
import '../olm-loader'; import '../olm-loader';
const sdk = require("../.."); import {logger} from '../../src/logger';
const utils = require("../../lib/utils"); import * as testUtils from "../test-utils";
const testUtils = require("../test-utils"); import * as utils from "../../src/utils";
const TestClient = require('../TestClient').default; import {TestClient} from "../TestClient";
import logger from '../../lib/logger'; import {CRYPTO_ENABLED} from "../../src/client";
let aliTestClient; let aliTestClient;
const roomId = "!room:localhost"; const roomId = "!room:localhost";
@@ -400,7 +398,7 @@ function firstSync(testClient) {
describe("MatrixClient crypto", function() { describe("MatrixClient crypto", function() {
if (!sdk.CRYPTO_ENABLED) { if (!CRYPTO_ENABLED) {
return; return;
} }

View File

@@ -1,24 +1,16 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {TestClient} from "../TestClient";
const sdk = require("../..");
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
describe("MatrixClient events", function() { describe("MatrixClient events", function() {
const baseUrl = "http://localhost.or.something";
let client; let client;
let httpBackend; let httpBackend;
const selfUserId = "@alice:localhost"; const selfUserId = "@alice:localhost";
const selfAccessToken = "aseukfgwef"; const selfAccessToken = "aseukfgwef";
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
sdk.request(httpBackend.requestFn); client = testClient.client;
client = sdk.createClient({ httpBackend = testClient.httpBackend;
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
httpBackend.when("GET", "/pushrules").respond(200, {}); httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
}); });

View File

@@ -1,12 +1,8 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {EventTimeline} from "../../src/matrix";
const sdk = require("../.."); import {logger} from "../../src/logger";
const HttpBackend = require("matrix-mock-request"); import {TestClient} from "../TestClient";
const utils = require("../test-utils");
const EventTimeline = sdk.EventTimeline;
import logger from '../../lib/logger';
const baseUrl = "http://localhost.or.something";
const userId = "@alice:localhost"; const userId = "@alice:localhost";
const userName = "Alice"; const userName = "Alice";
const accessToken = "aseukfgwef"; const accessToken = "aseukfgwef";
@@ -103,8 +99,9 @@ describe("getEventTimeline support", function() {
let client; let client;
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); const testClient = new TestClient(userId, "DEVICE", accessToken);
sdk.request(httpBackend.requestFn); client = testClient.client;
httpBackend = testClient.httpBackend;
}); });
afterEach(function() { afterEach(function() {
@@ -115,12 +112,6 @@ describe("getEventTimeline support", function() {
}); });
it("timeline support must be enabled to work", function() { it("timeline support must be enabled to work", function() {
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
return startClient(httpBackend, client).then(function() { return startClient(httpBackend, client).then(function() {
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
const timelineSet = room.getTimelineSets()[0]; const timelineSet = room.getTimelineSets()[0];
@@ -131,12 +122,15 @@ describe("getEventTimeline support", function() {
}); });
it("timeline support works when enabled", function() { it("timeline support works when enabled", function() {
client = sdk.createClient({ const testClient = new TestClient(
baseUrl: baseUrl, userId,
userId: userId, "DEVICE",
accessToken: accessToken, accessToken,
timelineSupport: true, undefined,
}); {timelineSupport: true},
);
client = testClient.client;
httpBackend = testClient.httpBackend;
return startClient(httpBackend, client).then(() => { return startClient(httpBackend, client).then(() => {
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
@@ -151,11 +145,7 @@ describe("getEventTimeline support", function() {
it("scrollback should be able to scroll back to before a gappy /sync", it("scrollback should be able to scroll back to before a gappy /sync",
function() { function() {
// need a client with timelineSupport disabled to make this work // need a client with timelineSupport disabled to make this work
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
accessToken: accessToken,
});
let room; let room;
return startClient(httpBackend, client).then(function() { return startClient(httpBackend, client).then(function() {
@@ -223,15 +213,15 @@ describe("MatrixClient event timelines", function() {
let httpBackend = null; let httpBackend = null;
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); const testClient = new TestClient(
sdk.request(httpBackend.requestFn); userId,
"DEVICE",
client = sdk.createClient({ accessToken,
baseUrl: baseUrl, undefined,
userId: userId, {timelineSupport: true},
accessToken: accessToken, );
timelineSupport: true, client = testClient.client;
}); httpBackend = testClient.httpBackend;
return startClient(httpBackend, client); return startClient(httpBackend, client);
}); });

View File

@@ -1,39 +1,23 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {CRYPTO_ENABLED} from "../../src/client";
const sdk = require("../.."); import {Filter, MemoryStore, Room} from "../../src/matrix";
const HttpBackend = require("matrix-mock-request"); import {TestClient} from "../TestClient";
const publicGlobals = require("../../lib/matrix");
const Room = publicGlobals.Room;
const MemoryStore = publicGlobals.MemoryStore;
const Filter = publicGlobals.Filter;
const utils = require("../test-utils");
const MockStorageApi = require("../MockStorageApi");
describe("MatrixClient", function() { describe("MatrixClient", function() {
const baseUrl = "http://localhost.or.something";
let client = null; let client = null;
let httpBackend = null; let httpBackend = null;
let store = null; let store = null;
let sessionStore = null;
const userId = "@alice:localhost"; const userId = "@alice:localhost";
const accessToken = "aseukfgwef"; const accessToken = "aseukfgwef";
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend();
store = new MemoryStore(); store = new MemoryStore();
const mockStorage = new MockStorageApi(); const testClient = new TestClient(userId, "aliceDevice", accessToken, undefined, {
sessionStore = new sdk.WebStorageSessionStore(mockStorage);
sdk.request(httpBackend.requestFn);
client = sdk.createClient({
baseUrl: baseUrl,
userId: userId,
deviceId: "aliceDevice",
accessToken: accessToken,
store: store, store: store,
sessionStore: sessionStore,
}); });
httpBackend = testClient.httpBackend;
client = testClient.client;
}); });
afterEach(function() { afterEach(function() {
@@ -303,7 +287,7 @@ describe("MatrixClient", function() {
describe("downloadKeys", function() { describe("downloadKeys", function() {
if (!sdk.CRYPTO_ENABLED) { if (!CRYPTO_ENABLED) {
return; return;
} }

View File

@@ -1,9 +1,8 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import HttpBackend from "matrix-mock-request";
const sdk = require("../.."); import {MatrixClient} from "../../src/matrix";
const MatrixClient = sdk.MatrixClient; import {MatrixScheduler} from "../../src/scheduler";
const HttpBackend = require("matrix-mock-request"); import {MemoryStore} from "../../src/store/memory";
const utils = require("../test-utils");
describe("MatrixClient opts", function() { describe("MatrixClient opts", function() {
const baseUrl = "http://localhost.or.something"; const baseUrl = "http://localhost.or.something";
@@ -71,7 +70,7 @@ describe("MatrixClient opts", function() {
baseUrl: baseUrl, baseUrl: baseUrl,
userId: userId, userId: userId,
accessToken: accessToken, accessToken: accessToken,
scheduler: new sdk.MatrixScheduler(), scheduler: new MatrixScheduler(),
}); });
}); });
@@ -124,7 +123,7 @@ describe("MatrixClient opts", function() {
beforeEach(function() { beforeEach(function() {
client = new MatrixClient({ client = new MatrixClient({
request: httpBackend.requestFn, request: httpBackend.requestFn,
store: new sdk.MemoryStore(), store: new MemoryStore(),
baseUrl: baseUrl, baseUrl: baseUrl,
userId: userId, userId: userId,
accessToken: accessToken, accessToken: accessToken,

View File

@@ -1,12 +1,9 @@
"use strict"; import {EventStatus} from "../../src/matrix";
import 'source-map-support/register'; import {MatrixScheduler} from "../../src/scheduler";
import {Room} from "../../src/models/room";
const sdk = require("../.."); import {TestClient} from "../TestClient";
const HttpBackend = require("matrix-mock-request");
const EventStatus = sdk.EventStatus;
describe("MatrixClient retrying", function() { describe("MatrixClient retrying", function() {
const baseUrl = "http://localhost.or.something";
let client = null; let client = null;
let httpBackend = null; let httpBackend = null;
let scheduler; let scheduler;
@@ -16,16 +13,17 @@ describe("MatrixClient retrying", function() {
let room; let room;
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); scheduler = new MatrixScheduler();
sdk.request(httpBackend.requestFn); const testClient = new TestClient(
scheduler = new sdk.MatrixScheduler(); userId,
client = sdk.createClient({ "DEVICE",
baseUrl: baseUrl, accessToken,
userId: userId, undefined,
accessToken: accessToken, {scheduler},
scheduler: scheduler, );
}); httpBackend = testClient.httpBackend;
room = new sdk.Room(roomId); client = testClient.client;
room = new Room(roomId);
client.store.storeRoom(room); client.store.storeRoom(room);
}); });

View File

@@ -1,12 +1,9 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {EventStatus} from "../../src/models/event";
const sdk = require("../.."); import {TestClient} from "../TestClient";
const EventStatus = sdk.EventStatus;
const HttpBackend = require("matrix-mock-request");
const utils = require("../test-utils");
describe("MatrixClient room timelines", function() { describe("MatrixClient room timelines", function() {
const baseUrl = "http://localhost.or.something";
let client = null; let client = null;
let httpBackend = null; let httpBackend = null;
const userId = "@alice:localhost"; const userId = "@alice:localhost";
@@ -101,15 +98,17 @@ describe("MatrixClient room timelines", function() {
} }
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); // these tests should work with or without timelineSupport
sdk.request(httpBackend.requestFn); const testClient = new TestClient(
client = sdk.createClient({ userId,
baseUrl: baseUrl, "DEVICE",
userId: userId, accessToken,
accessToken: accessToken, undefined,
// these tests should work with or without timelineSupport {timelineSupport: true},
timelineSupport: true, );
}); httpBackend = testClient.httpBackend;
client = testClient.client;
setNextSyncData(); setNextSyncData();
httpBackend.when("GET", "/pushrules").respond(200, {}); httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" }); httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });

View File

@@ -1,13 +1,9 @@
"use strict"; import {MatrixEvent} from "../../src/models/event";
import 'source-map-support/register'; import {EventTimeline} from "../../src/models/event-timeline";
const sdk = require("../.."); import * as utils from "../test-utils";
const HttpBackend = require("matrix-mock-request"); import {TestClient} from "../TestClient";
const utils = require("../test-utils");
const MatrixEvent = sdk.MatrixEvent;
const EventTimeline = sdk.EventTimeline;
describe("MatrixClient syncing", function() { describe("MatrixClient syncing", function() {
const baseUrl = "http://localhost.or.something";
let client = null; let client = null;
let httpBackend = null; let httpBackend = null;
const selfUserId = "@alice:localhost"; const selfUserId = "@alice:localhost";
@@ -20,13 +16,9 @@ describe("MatrixClient syncing", function() {
const roomTwo = "!bar:localhost"; const roomTwo = "!bar:localhost";
beforeEach(function() { beforeEach(function() {
httpBackend = new HttpBackend(); const testClient = new TestClient(selfUserId, "DEVICE", selfAccessToken);
sdk.request(httpBackend.requestFn); httpBackend = testClient.httpBackend;
client = sdk.createClient({ client = testClient.client;
baseUrl: baseUrl,
userId: selfUserId,
accessToken: selfAccessToken,
});
httpBackend.when("GET", "/pushrules").respond(200, {}); httpBackend.when("GET", "/pushrules").respond(200, {});
httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" }); httpBackend.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
}); });

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,14 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict"; import anotherjson from "another-json";
import * as utils from "../../src/utils";
const anotherjson = require('another-json'); import * as testUtils from "../test-utils";
import {TestClient} from "../TestClient";
const utils = require('../../lib/utils'); import {logger} from "../../src/logger";
const testUtils = require('../test-utils');
const TestClient = require('../TestClient').default;
import logger from '../../lib/logger';
const ROOM_ID = "!room:id"; const ROOM_ID = "!room:id";
@@ -617,6 +615,9 @@ describe("megolm", function() {
).respond(200, { ).respond(200, {
event_id: '$event_id', event_id: '$event_id',
}); });
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([ return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'), aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
@@ -714,6 +715,9 @@ describe("megolm", function() {
event_id: '$event_id', event_id: '$event_id',
}; };
}); });
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([ return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'), aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017 Vector creations Ltd Copyright 2017 Vector creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../lib/logger'; import {logger} from '../src/logger';
// try to load the olm library. // try to load the olm library.
try { try {

View File

@@ -1,10 +1,8 @@
"use strict";
// load olm before the sdk if possible // load olm before the sdk if possible
import './olm-loader'; import './olm-loader';
import logger from '../lib/logger'; import {logger} from '../src/logger';
import sdk from '..'; import {MatrixEvent} from "../src/models/event";
const MatrixEvent = sdk.MatrixEvent;
/** /**
* Return a promise that is resolved when the client next emits a * Return a promise that is resolved when the client next emits a
@@ -13,7 +11,7 @@ const MatrixEvent = sdk.MatrixEvent;
* @param {Number=} count Number of syncs to wait for (default 1) * @param {Number=} count Number of syncs to wait for (default 1)
* @return {Promise} Resolves once the client has emitted a SYNCING event * @return {Promise} Resolves once the client has emitted a SYNCING event
*/ */
module.exports.syncPromise = function(client, count) { export function syncPromise(client, count) {
if (count === undefined) { if (count === undefined) {
count = 1; count = 1;
} }
@@ -24,7 +22,7 @@ module.exports.syncPromise = function(client, count) {
const p = new Promise((resolve, reject) => { const p = new Promise((resolve, reject) => {
const cb = (state) => { const cb = (state) => {
logger.log(`${Date.now()} syncPromise(${count}): ${state}`); logger.log(`${Date.now()} syncPromise(${count}): ${state}`);
if (state == 'SYNCING') { if (state === 'SYNCING') {
resolve(); resolve();
} else { } else {
client.once('sync', cb); client.once('sync', cb);
@@ -34,9 +32,9 @@ module.exports.syncPromise = function(client, count) {
}); });
return p.then(() => { return p.then(() => {
return module.exports.syncPromise(client, count-1); return syncPromise(client, count-1);
}); });
}; }
/** /**
* Create a spy for an object and automatically spy its methods. * Create a spy for an object and automatically spy its methods.
@@ -44,7 +42,7 @@ module.exports.syncPromise = function(client, count) {
* @param {string} name The name of the class * @param {string} name The name of the class
* @return {Object} An instantiated object with spied methods/properties. * @return {Object} An instantiated object with spied methods/properties.
*/ */
module.exports.mock = function(constr, name) { export function mock(constr, name) {
// Based on // Based on
// http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/ // http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/
const HelperConstr = new Function(); // jshint ignore:line const HelperConstr = new Function(); // jshint ignore:line
@@ -65,7 +63,7 @@ module.exports.mock = function(constr, name) {
} }
} }
return result; return result;
}; }
/** /**
* Create an Event. * Create an Event.
@@ -78,7 +76,7 @@ module.exports.mock = function(constr, name) {
* @param {boolean} opts.event True to make a MatrixEvent. * @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object} a JSON object representing this event. * @return {Object} a JSON object representing this event.
*/ */
module.exports.mkEvent = function(opts) { export function mkEvent(opts) {
if (!opts.type || !opts.content) { if (!opts.type || !opts.content) {
throw new Error("Missing .type or .content =>" + JSON.stringify(opts)); throw new Error("Missing .type or .content =>" + JSON.stringify(opts));
} }
@@ -97,14 +95,14 @@ module.exports.mkEvent = function(opts) {
event.state_key = ""; event.state_key = "";
} }
return opts.event ? new MatrixEvent(event) : event; return opts.event ? new MatrixEvent(event) : event;
}; }
/** /**
* Create an m.presence event. * Create an m.presence event.
* @param {Object} opts Values for the presence. * @param {Object} opts Values for the presence.
* @return {Object|MatrixEvent} The event * @return {Object|MatrixEvent} The event
*/ */
module.exports.mkPresence = function(opts) { export function mkPresence(opts) {
if (!opts.user) { if (!opts.user) {
throw new Error("Missing user"); throw new Error("Missing user");
} }
@@ -120,7 +118,7 @@ module.exports.mkPresence = function(opts) {
}, },
}; };
return opts.event ? new MatrixEvent(event) : event; return opts.event ? new MatrixEvent(event) : event;
}; }
/** /**
* Create an m.room.member event. * Create an m.room.member event.
@@ -135,7 +133,7 @@ module.exports.mkPresence = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent. * @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event * @return {Object|MatrixEvent} The event
*/ */
module.exports.mkMembership = function(opts) { export function mkMembership(opts) {
opts.type = "m.room.member"; opts.type = "m.room.member";
if (!opts.skey) { if (!opts.skey) {
opts.skey = opts.sender || opts.user; opts.skey = opts.sender || opts.user;
@@ -152,8 +150,8 @@ module.exports.mkMembership = function(opts) {
if (opts.url) { if (opts.url) {
opts.content.avatar_url = opts.url; opts.content.avatar_url = opts.url;
} }
return module.exports.mkEvent(opts); return mkEvent(opts);
}; }
/** /**
* Create an m.room.message event. * Create an m.room.message event.
@@ -164,7 +162,7 @@ module.exports.mkMembership = function(opts) {
* @param {boolean} opts.event True to make a MatrixEvent. * @param {boolean} opts.event True to make a MatrixEvent.
* @return {Object|MatrixEvent} The event * @return {Object|MatrixEvent} The event
*/ */
module.exports.mkMessage = function(opts) { export function mkMessage(opts) {
opts.type = "m.room.message"; opts.type = "m.room.message";
if (!opts.msg) { if (!opts.msg) {
opts.msg = "Random->" + Math.random(); opts.msg = "Random->" + Math.random();
@@ -176,8 +174,8 @@ module.exports.mkMessage = function(opts) {
msgtype: "m.text", msgtype: "m.text",
body: opts.msg, body: opts.msg,
}; };
return module.exports.mkEvent(opts); return mkEvent(opts);
}; }
/** /**
@@ -185,10 +183,10 @@ module.exports.mkMessage = function(opts) {
* *
* @constructor * @constructor
*/ */
module.exports.MockStorageApi = function() { export function MockStorageApi() {
this.data = {}; this.data = {};
}; }
module.exports.MockStorageApi.prototype = { MockStorageApi.prototype = {
get length() { get length() {
return Object.keys(this.data).length; return Object.keys(this.data).length;
}, },
@@ -213,7 +211,7 @@ module.exports.MockStorageApi.prototype = {
* @param {MatrixEvent} event * @param {MatrixEvent} event
* @returns {Promise} promise which resolves (to `event`) when the event has been decrypted * @returns {Promise} promise which resolves (to `event`) when the event has been decrypted
*/ */
module.exports.awaitDecryption = function(event) { export function awaitDecryption(event) {
if (!event.isBeingDecrypted()) { if (!event.isBeingDecrypted()) {
return Promise.resolve(event); return Promise.resolve(event);
} }
@@ -226,19 +224,19 @@ module.exports.awaitDecryption = function(event) {
resolve(ev); resolve(ev);
}); });
}); });
}; }
const HttpResponse = module.exports.HttpResponse = function( export function HttpResponse(
httpLookups, acceptKeepalives, ignoreUnhandledSync, httpLookups, acceptKeepalives, ignoreUnhandledSync,
) { ) {
this.httpLookups = httpLookups; this.httpLookups = httpLookups;
this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives; this.acceptKeepalives = acceptKeepalives === undefined ? true : acceptKeepalives;
this.ignoreUnhandledSync = ignoreUnhandledSync; this.ignoreUnhandledSync = ignoreUnhandledSync;
this.pendingLookup = null; this.pendingLookup = null;
}; }
HttpResponse.prototype.request = function HttpResponse( HttpResponse.prototype.request = function(
cb, method, path, qp, data, prefix, cb, method, path, qp, data, prefix,
) { ) {
if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) { if (path === HttpResponse.KEEP_ALIVE_PATH && this.acceptKeepalives) {
@@ -351,7 +349,7 @@ HttpResponse.defaultResponses = function(userId) {
]; ];
}; };
module.exports.setHttpResponses = function setHttpResponses( export function setHttpResponses(
client, responses, acceptKeepalives, ignoreUnhandledSyncs, client, responses, acceptKeepalives, ignoreUnhandledSyncs,
) { ) {
const httpResponseObj = new HttpResponse( const httpResponseObj = new HttpResponse(
@@ -367,4 +365,4 @@ module.exports.setHttpResponses = function setHttpResponses(
client._http.authedRequestWithPrefix.mockImplementation(httpReq); client._http.authedRequestWithPrefix.mockImplementation(httpReq);
client._http.requestWithPrefix.mockImplementation(httpReq); client._http.requestWithPrefix.mockImplementation(httpReq);
client._http.request.mockImplementation(httpReq); client._http.request.mockImplementation(httpReq);
}; }

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,15 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const AutoDiscovery = sdk.AutoDiscovery;
import MockHttpBackend from "matrix-mock-request"; import MockHttpBackend from "matrix-mock-request";
import * as sdk from "../../src";
import {AutoDiscovery} from "../../src/autodiscovery";
describe("AutoDiscovery", function() { describe("AutoDiscovery", function() {
let httpBackend = null; let httpBackend = null;

View File

@@ -1,6 +1,4 @@
"use strict"; import {getHttpUriForMxc, getIdenticonUri} from "../../src/content-repo";
import 'source-map-support/register';
const ContentRepo = require("../../lib/content-repo");
describe("ContentRepo", function() { describe("ContentRepo", function() {
const baseUrl = "https://my.home.server"; const baseUrl = "https://my.home.server";
@@ -9,7 +7,7 @@ describe("ContentRepo", function() {
it("should do nothing to HTTP URLs when allowing direct links", function() { it("should do nothing to HTTP URLs when allowing direct links", function() {
const httpUrl = "http://example.com/image.jpeg"; const httpUrl = "http://example.com/image.jpeg";
expect( expect(
ContentRepo.getHttpUriForMxc( getHttpUriForMxc(
baseUrl, httpUrl, undefined, undefined, undefined, true, baseUrl, httpUrl, undefined, undefined, undefined, true,
), ),
).toEqual(httpUrl); ).toEqual(httpUrl);
@@ -17,25 +15,25 @@ describe("ContentRepo", function() {
it("should return the empty string HTTP URLs by default", function() { it("should return the empty string HTTP URLs by default", function() {
const httpUrl = "http://example.com/image.jpeg"; const httpUrl = "http://example.com/image.jpeg";
expect(ContentRepo.getHttpUriForMxc(baseUrl, httpUrl)).toEqual(""); expect(getHttpUriForMxc(baseUrl, httpUrl)).toEqual("");
}); });
it("should return a download URL if no width/height/resize are specified", it("should return a download URL if no width/height/resize are specified",
function() { function() {
const mxcUri = "mxc://server.name/resourceid"; const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual( expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid", baseUrl + "/_matrix/media/r0/download/server.name/resourceid",
); );
}); });
it("should return the empty string for null input", function() { it("should return the empty string for null input", function() {
expect(ContentRepo.getHttpUriForMxc(null)).toEqual(""); expect(getHttpUriForMxc(null)).toEqual("");
}); });
it("should return a thumbnail URL if a width/height/resize is specified", it("should return a thumbnail URL if a width/height/resize is specified",
function() { function() {
const mxcUri = "mxc://server.name/resourceid"; const mxcUri = "mxc://server.name/resourceid";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual( expect(getHttpUriForMxc(baseUrl, mxcUri, 32, 64, "crop")).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" + baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32&height=64&method=crop", "?width=32&height=64&method=crop",
); );
@@ -44,7 +42,7 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs after any query parameters", it("should put fragments from mxc:// URIs after any query parameters",
function() { function() {
const mxcUri = "mxc://server.name/resourceid#automade"; const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual( expect(getHttpUriForMxc(baseUrl, mxcUri, 32)).toEqual(
baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" + baseUrl + "/_matrix/media/r0/thumbnail/server.name/resourceid" +
"?width=32#automade", "?width=32#automade",
); );
@@ -53,7 +51,7 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs at the end of the HTTP URI", it("should put fragments from mxc:// URIs at the end of the HTTP URI",
function() { function() {
const mxcUri = "mxc://server.name/resourceid#automade"; const mxcUri = "mxc://server.name/resourceid#automade";
expect(ContentRepo.getHttpUriForMxc(baseUrl, mxcUri)).toEqual( expect(getHttpUriForMxc(baseUrl, mxcUri)).toEqual(
baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade", baseUrl + "/_matrix/media/r0/download/server.name/resourceid#automade",
); );
}); });
@@ -61,25 +59,25 @@ describe("ContentRepo", function() {
describe("getIdenticonUri", function() { describe("getIdenticonUri", function() {
it("should do nothing for null input", function() { it("should do nothing for null input", function() {
expect(ContentRepo.getIdenticonUri(null)).toEqual(null); expect(getIdenticonUri(null)).toEqual(null);
}); });
it("should set w/h by default to 96", function() { it("should set w/h by default to 96", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual( expect(getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" + baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=96&height=96", "?width=96&height=96",
); );
}); });
it("should be able to set custom w/h", function() { it("should be able to set custom w/h", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual( expect(getIdenticonUri(baseUrl, "foobar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" + baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=32&height=64", "?width=32&height=64",
); );
}); });
it("should URL encode the identicon string", function() { it("should URL encode the identicon string", function() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual( expect(getIdenticonUri(baseUrl, "foo#bar", 32, 64)).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" + baseUrl + "/_matrix/media/unstable/identicon/foo%23bar" +
"?width=32&height=64", "?width=32&height=64",
); );

View File

@@ -1,26 +1,20 @@
import 'source-map-support/register';
import '../olm-loader'; import '../olm-loader';
import {Crypto} from "../../src/crypto";
import Crypto from '../../lib/crypto'; import {WebStorageSessionStore} from "../../src/store/session/webstorage";
import {MemoryCryptoStore} from "../../src/crypto/store/memory-crypto-store";
import WebStorageSessionStore from '../../lib/store/session/webstorage'; import {MockStorageApi} from "../MockStorageApi";
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js'; import {TestClient} from "../TestClient";
import MockStorageApi from '../MockStorageApi'; import {MatrixEvent} from "../../src/models/event";
import TestClient from '../TestClient'; import {Room} from "../../src/models/room";
import {MatrixEvent} from '../../lib/models/event'; import * as olmlib from "../../src/crypto/olmlib";
import Room from '../../lib/models/room';
import olmlib from '../../lib/crypto/olmlib';
import {sleep} from "../../src/utils"; import {sleep} from "../../src/utils";
import {EventEmitter} from "events";
const EventEmitter = require("events").EventEmitter; import {CRYPTO_ENABLED} from "../../src/client";
const sdk = require("../..");
const Olm = global.Olm; const Olm = global.Olm;
describe("Crypto", function() { describe("Crypto", function() {
if (!sdk.CRYPTO_ENABLED) { if (!CRYPTO_ENABLED) {
return; return;
} }

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,10 +16,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import DeviceList from '../../../lib/crypto/DeviceList'; import {logger} from "../../../src/logger";
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js'; import * as utils from "../../../src/utils";
import utils from '../../../lib/utils'; import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import logger from '../../../lib/logger'; import {DeviceList} from "../../../src/crypto/DeviceList";
const signedDeviceList = { const signedDeviceList = {
"failures": {}, "failures": {},

View File

@@ -1,15 +1,16 @@
import '../../../olm-loader'; import '../../../olm-loader';
import * as algorithms from "../../../../src/crypto/algorithms";
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import * as testUtils from "../../../test-utils";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import {Crypto} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {MatrixEvent} from "../../../../src/models/event";
import {TestClient} from "../../../TestClient";
import {Room} from "../../../../src/models/room";
import * as olmlib from "../../../../src/crypto/olmlib";
import sdk from '../../../..';
import algorithms from '../../../../lib/crypto/algorithms';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import testUtils from '../../../test-utils';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import Crypto from '../../../../lib/crypto';
import logger from '../../../../lib/logger';
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -342,4 +343,346 @@ describe("MegolmDecryption", function() {
expect(ct2.session_id).toEqual(ct1.session_id); expect(ct2.session_id).toEqual(ct1.session_id);
}); });
}); });
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 aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice1 = bobClient1._crypto._olmDevice;
const bobDevice2 = bobClient2._crypto._olmDevice;
const encryptionCfg = {
"algorithm": "m.megolm.v1.aes-sha2",
};
const roomId = "!someroom";
const room = new Room(roomId, aliceClient, "@alice:example.com", {});
room.getEncryptionTargetMembers = async function() {
return [{userId: "@bob:example.com"}];
};
room.setBlacklistUnverifiedDevices(true);
aliceClient.store.storeRoom(room);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
const BOB_DEVICES = {
bobdevice1: {
user_id: "@bob:example.com",
device_id: "bobdevice1",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice1.deviceEd25519Key,
"curve25519:Dynabook": bobDevice1.deviceCurve25519Key,
},
verified: 0,
},
bobdevice2: {
user_id: "@bob:example.com",
device_id: "bobdevice2",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:Dynabook": bobDevice2.deviceEd25519Key,
"curve25519:Dynabook": bobDevice2.deviceCurve25519Key,
},
verified: -1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
let run = false;
aliceClient.sendToDevice = async (msgtype, contentMap) => {
run = true;
expect(msgtype).toBe("org.matrix.room_key.withheld");
delete contentMap["@bob:example.com"].bobdevice1.session_id;
delete contentMap["@bob:example.com"].bobdevice2.session_id;
expect(contentMap).toStrictEqual({
'@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.',
sender_key: aliceDevice.deviceCurve25519Key,
},
bobdevice2: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
code: 'm.blacklisted',
reason: 'The sender has blocked you.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
};
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {
msgtype: "m.text",
body: "secret",
},
});
await aliceClient._crypto.encryptEvent(event, room);
expect(run).toBe(true);
aliceClient.stopClient();
bobClient1.stopClient();
bobClient2.stopClient();
});
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 aliceDevice = aliceClient._crypto._olmDevice;
const bobDevice = bobClient._crypto._olmDevice;
const encryptionCfg = {
"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", {});
aliceClient.store.storeRoom(aliceRoom);
bobClient.store.storeRoom(bobRoom);
await aliceClient.setRoomEncryption(roomId, encryptionCfg);
await bobClient.setRoomEncryption(roomId, encryptionCfg);
aliceRoom.getEncryptionTargetMembers = async () => {
return [
{
userId: "@alice:example.com",
membership: "join",
},
{
userId: "@bob:example.com",
membership: "join",
},
];
};
const BOB_DEVICES = {
bobdevice: {
user_id: "@bob:example.com",
device_id: "bobdevice",
algorithms: [olmlib.OLM_ALGORITHM, olmlib.MEGOLM_ALGORITHM],
keys: {
"ed25519:bobdevice": bobDevice.deviceEd25519Key,
"curve25519:bobdevice": bobDevice.deviceCurve25519Key,
},
known: true,
verified: 1,
},
};
aliceClient._crypto._deviceList.storeDevicesForUser(
"@bob:example.com", BOB_DEVICES,
);
aliceClient._crypto._deviceList.downloadKeys = async function(userIds) {
return this._getDevicesFromStore(userIds);
};
aliceClient.claimOneTimeKeys = async () => {
// Bob has no one-time keys
return {
one_time_keys: {},
};
};
let run = false;
aliceClient.sendToDevice = async (msgtype, contentMap) => {
run = true;
expect(msgtype).toBe("org.matrix.room_key.withheld");
expect(contentMap).toStrictEqual({
'@bob:example.com': {
bobdevice: {
algorithm: "m.megolm.v1.aes-sha2",
code: 'm.no_olm',
reason: 'Unable to establish a secure channel.',
sender_key: aliceDevice.deviceCurve25519Key,
},
},
});
};
const event = new MatrixEvent({
type: "m.room.message",
sender: "@alice:example.com",
room_id: roomId,
event_id: "$event",
content: {},
});
await aliceClient._crypto.encryptEvent(event, aliceRoom);
expect(run).toBe(true);
});
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 bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
code: "m.blacklisted",
reason: "You have been blocked",
},
}));
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
}))).rejects.toThrow("The sender has blocked you.");
});
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 bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
const now = Date.now();
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "org.matrix.room_key.withheld",
sender: "@bob:example.com",
content: {
algorithm: "m.megolm.v1.aes-sha2",
room_id: roomId,
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
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({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The sender was unable to establish a secure channel.");
});
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 bobDevice = bobClient._crypto._olmDevice;
const roomId = "!someroom";
const now = Date.now();
// pretend we got an event that we can't decrypt
aliceClient._crypto._onToDeviceEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
content: {
msgtype: "m.bad.encrypted",
algorithm: "m.megolm.v1.aes-sha2",
session_id: "session_id",
sender_key: bobDevice.deviceCurve25519Key,
},
}));
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await expect(aliceClient._crypto.decryptEvent(new MatrixEvent({
type: "m.room.encrypted",
sender: "@bob:example.com",
event_id: "$event",
room_id: roomId,
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blablabla",
device_id: "bobdevice",
sender_key: bobDevice.deviceCurve25519Key,
session_id: "session_id",
},
origin_server_ts: now,
}))).rejects.toThrow("The secure channel with the sender was corrupted.");
});
}); });

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018,2019 New Vector Ltd Copyright 2018,2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,14 +16,12 @@ limitations under the License.
*/ */
import '../../../olm-loader'; import '../../../olm-loader';
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js'; import {MockStorageApi} from "../../../MockStorageApi";
import MockStorageApi from '../../../MockStorageApi'; import {logger} from "../../../../src/logger";
import logger from '../../../../lib/logger'; import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import OlmDevice from '../../../../lib/crypto/OlmDevice'; import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import olmlib from '../../../../lib/crypto/olmlib';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
function makeOlmDevice() { function makeOlmDevice() {
const mockStorage = new MockStorageApi(); const mockStorage = new MockStorageApi();

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,23 +16,20 @@ limitations under the License.
*/ */
import '../../olm-loader'; import '../../olm-loader';
import {logger} from "../../../src/logger";
import sdk from '../../..'; import * as olmlib from "../../../src/crypto/olmlib";
import algorithms from '../../../lib/crypto/algorithms'; import {MatrixClient} from "../../../src/client";
import WebStorageSessionStore from '../../../lib/store/session/webstorage'; import {MatrixEvent} from "../../../src/models/event";
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js'; import * as algorithms from "../../../src/crypto/algorithms";
import MockStorageApi from '../../MockStorageApi'; import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
import testUtils from '../../test-utils'; import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../MockStorageApi";
import OlmDevice from '../../../lib/crypto/OlmDevice'; import * as testUtils from "../../test-utils";
import Crypto from '../../../lib/crypto'; import {OlmDevice} from "../../../src/crypto/OlmDevice";
import logger from '../../../lib/logger'; import {Crypto} from "../../../src/crypto";
import olmlib from '../../../lib/crypto/olmlib';
const Olm = global.Olm; const Olm = global.Olm;
const MatrixClient = sdk.MatrixClient;
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const ROOM_ID = '!ROOM:ID'; const ROOM_ID = '!ROOM:ID';

View File

@@ -16,13 +16,9 @@ limitations under the License.
*/ */
import '../../olm-loader'; import '../../olm-loader';
import anotherjson from 'another-json'; import anotherjson from 'another-json';
import * as olmlib from "../../../src/crypto/olmlib";
import olmlib from '../../../lib/crypto/olmlib'; import {TestClient} from '../../TestClient';
import TestClient from '../../TestClient';
import {HttpResponse, setHttpResponses} from '../../test-utils'; import {HttpResponse, setHttpResponses} from '../../test-utils';
async function makeTestClient(userInfo, options, keys) { async function makeTestClient(userInfo, options, keys) {

View File

@@ -15,14 +15,11 @@ limitations under the License.
*/ */
import '../../olm-loader'; import '../../olm-loader';
import * as olmlib from "../../../src/crypto/olmlib";
import { MatrixEvent } from '../../../lib/models/event'; import {SECRET_STORAGE_ALGORITHM_V1} from "../../../src/crypto/SecretStorage";
import { SECRET_STORAGE_ALGORITHM_V1 } from '../../../lib/crypto/SecretStorage'; import {MatrixEvent} from "../../../src/models/event";
import {TestClient} from '../../TestClient';
import olmlib from '../../../lib/crypto/olmlib'; import {makeTestClients} from './verification/util';
import TestClient from '../../TestClient';
import { makeTestClients } from './verification/util';
async function makeTestClient(userInfo, options) { async function makeTestClient(userInfo, options) {
const client = (new TestClient( const client = (new TestClient(

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018-2019 New Vector Ltd Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,17 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../../../lib/logger'; import "../../../olm-loader";
import {logger} from "../../../../src/logger";
try { import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
global.Olm = require('olm'); import {ScanQRCode, ShowQRCode} from "../../../../src/crypto/verification/QRCode";
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import {ShowQRCode, ScanQRCode} from '../../../../lib/crypto/verification/QRCode';
const Olm = global.Olm; const Olm = global.Olm;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2019 New Vector Ltd Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,22 +14,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../../../lib/logger'; import "../../../olm-loader";
import {verificationMethods} from "../../../../src/crypto";
try { import {logger} from "../../../../src/logger";
global.Olm = require('olm'); import {SAS} from "../../../../src/crypto/verification/SAS";
} catch (e) { import {makeTestClients} from './util';
logger.warn("unable to run device verification tests: libolm not available");
}
import {verificationMethods} from '../../../../lib/crypto';
import SAS from '../../../../lib/crypto/verification/SAS';
const Olm = global.Olm; const Olm = global.Olm;
import {makeTestClients} from './util';
describe("verification request", function() { describe("verification request", function() {
if (!global.Olm) { 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');

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018-2019 New Vector Ltd Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,29 +14,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../../../lib/logger'; import "../../../olm-loader";
import {makeTestClients} from './util';
try { import {MatrixEvent} from "../../../../src/models/event";
global.Olm = require('olm'); import {SAS} from "../../../../src/crypto/verification/SAS";
} catch (e) { import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
logger.warn("unable to run device verification tests: libolm not available"); import {verificationMethods} from "../../../../src/crypto";
} import * as olmlib from "../../../../src/crypto/olmlib";
import {logger} from "../../../../src/logger";
import olmlib from '../../../../lib/crypto/olmlib';
import sdk from '../../../..';
import {verificationMethods} from '../../../../lib/crypto';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import SAS from '../../../../lib/crypto/verification/SAS';
const Olm = global.Olm; const Olm = global.Olm;
const MatrixEvent = sdk.MatrixEvent;
import {makeTestClients} from './util';
let ALICE_DEVICES; let ALICE_DEVICES;
let BOB_DEVICES; let BOB_DEVICES;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2019 New Vector Ltd Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import TestClient from '../../../TestClient'; import {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
import sdk from '../../../..';
const MatrixEvent = sdk.MatrixEvent;
export async function makeTestClients(userInfos, options) { export async function makeTestClients(userInfos, options) {
const clients = []; const clients = [];

View File

@@ -1,12 +1,10 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {EventTimeline} from "../../src/models/event-timeline";
const sdk = require("../.."); import {RoomState} from "../../src/models/room-state";
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
function mockRoomStates(timeline) { function mockRoomStates(timeline) {
timeline._startState = utils.mock(sdk.RoomState, "startState"); timeline._startState = utils.mock(RoomState, "startState");
timeline._endState = utils.mock(sdk.RoomState, "endState"); timeline._endState = utils.mock(RoomState, "endState");
} }
describe("EventTimeline", function() { describe("EventTimeline", function() {

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017 New Vector Ltd Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundaction C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import sdk from '../..'; import {logger} from "../../src/logger";
const MatrixEvent = sdk.MatrixEvent; import {MatrixEvent} from "../../src/models/event";
import logger from '../../lib/logger';
describe("MatrixEvent", () => { describe("MatrixEvent", () => {
describe(".attemptDecryption", () => { describe(".attemptDecryption", () => {

View File

@@ -1,7 +1,4 @@
"use strict"; import {Filter} from "../../src/filter";
import 'source-map-support/register';
const sdk = require("../..");
const Filter = sdk.Filter;
describe("Filter", function() { describe("Filter", function() {
const filterId = "f1lt3ring15g00d4ursoul"; const filterId = "f1lt3ring15g00d4ursoul";

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,15 +14,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
import 'source-map-support/register'; import {logger} from "../../src/logger";
const sdk = require("../.."); import {InteractiveAuth} from "../../src/interactive-auth";
import {MatrixError} from "../../src/http-api";
const InteractiveAuth = sdk.InteractiveAuth;
const MatrixError = sdk.MatrixError;
import logger from '../../lib/logger';
// Trivial client object to test interactive auth // Trivial client object to test interactive auth
// (we do not need TestClient here) // (we do not need TestClient here)

View File

@@ -1,4 +1,4 @@
import TestClient from '../TestClient'; import {TestClient} from '../TestClient';
describe('Login request', function() { describe('Login request', function() {
let client; let client;

View File

@@ -1,9 +1,6 @@
"use strict"; import {logger} from "../../src/logger";
import 'source-map-support/register'; import {MatrixClient} from "../../src/client";
const sdk = require("../.."); import {Filter} from "../../src/filter";
const MatrixClient = sdk.MatrixClient;
import logger from '../../lib/logger';
jest.useFakeTimers(); jest.useFakeTimers();
@@ -180,7 +177,7 @@ describe("MatrixClient", function() {
httpLookups.push(SYNC_RESPONSE); httpLookups.push(SYNC_RESPONSE);
const filterId = "ehfewf"; const filterId = "ehfewf";
store.getFilterIdByName.mockReturnValue(filterId); store.getFilterIdByName.mockReturnValue(filterId);
const filter = new sdk.Filter(0, filterId); const filter = new Filter(0, filterId);
filter.setDefinition({"room": {"timeline": {"limit": 8}}}); filter.setDefinition({"room": {"timeline": {"limit": 8}}});
store.getFilter.mockReturnValue(filter); store.getFilter.mockReturnValue(filter);
const syncPromise = new Promise((resolve, reject) => { const syncPromise = new Promise((resolve, reject) => {
@@ -247,7 +244,7 @@ describe("MatrixClient", function() {
const filterName = getFilterName(client.credentials.userId); const filterName = getFilterName(client.credentials.userId);
client.store.setFilterIdByName(filterName, invalidFilterId); client.store.setFilterIdByName(filterName, invalidFilterId);
const filter = new sdk.Filter(client.credentials.userId); const filter = new Filter(client.credentials.userId);
client.getOrCreateFilter(filterName, filter).then(function(filterId) { client.getOrCreateFilter(filterName, filter).then(function(filterId) {
expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id); expect(filterId).toEqual(FILTER_RESPONSE.data.filter_id);

View File

@@ -1,7 +1,5 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {PushProcessor} from "../../src/pushprocessor";
const PushProcessor = require("../../lib/pushprocessor");
const utils = require("../test-utils");
describe('NotificationService', function() { describe('NotificationService', function() {
const testUserId = "@ali:matrix.org"; const testUserId = "@ali:matrix.org";

View File

@@ -1,7 +1,4 @@
"use strict"; import * as callbacks from "../../src/realtime-callbacks";
import 'source-map-support/register';
const callbacks = require("../../src/realtime-callbacks");
let wallTime = 1234567890; let wallTime = 1234567890;
jest.useFakeTimers(); jest.useFakeTimers();

View File

@@ -1,8 +1,5 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {RoomMember} from "../../src/models/room-member";
const sdk = require("../..");
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
describe("RoomMember", function() { describe("RoomMember", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";

View File

@@ -1,9 +1,6 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {RoomState} from "../../src/models/room-state";
const sdk = require("../.."); import {RoomMember} from "../../src/models/room-member";
const RoomState = sdk.RoomState;
const RoomMember = sdk.RoomMember;
const utils = require("../test-utils");
describe("RoomState", function() { describe("RoomState", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";

View File

@@ -1,12 +1,8 @@
"use strict"; import * as utils from "../test-utils";
import 'source-map-support/register'; import {EventStatus, MatrixEvent} from "../../src/models/event";
const sdk = require("../.."); import {EventTimeline} from "../../src/models/event-timeline";
const Room = sdk.Room; import {RoomState} from "../../src/models/room-state";
const RoomState = sdk.RoomState; import {Room} from "../../src/models/room";
const MatrixEvent = sdk.MatrixEvent;
const EventStatus = sdk.EventStatus;
const EventTimeline = sdk.EventTimeline;
const utils = require("../test-utils");
describe("Room", function() { describe("Room", function() {
const roomId = "!foo:bar"; const roomId = "!foo:bar";
@@ -20,9 +16,9 @@ describe("Room", function() {
room = new Room(roomId); room = new Room(roomId);
// mock RoomStates // mock RoomStates
room.oldState = room.getLiveTimeline()._startState = room.oldState = room.getLiveTimeline()._startState =
utils.mock(sdk.RoomState, "oldState"); utils.mock(RoomState, "oldState");
room.currentState = room.getLiveTimeline()._endState = room.currentState = room.getLiveTimeline()._endState =
utils.mock(sdk.RoomState, "currentState"); utils.mock(RoomState, "currentState");
}); });
describe("getAvatarUrl", function() { describe("getAvatarUrl", function() {

View File

@@ -1,12 +1,10 @@
// This file had a function whose name is all caps, which displeases eslint // This file had a function whose name is all caps, which displeases eslint
/* eslint new-cap: "off" */ /* eslint new-cap: "off" */
import 'source-map-support/register';
import {defer} from '../../src/utils'; import {defer} from '../../src/utils';
const sdk = require("../.."); import {MatrixError} from "../../src/http-api";
const MatrixScheduler = sdk.MatrixScheduler; import {MatrixScheduler} from "../../src/scheduler";
const MatrixError = sdk.MatrixError; import * as utils from "../test-utils";
const utils = require("../test-utils");
jest.useFakeTimers(); jest.useFakeTimers();

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,11 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict"; import {SyncAccumulator} from "../../src/sync-accumulator";
import 'source-map-support/register';
import sdk from "../..";
const SyncAccumulator = sdk.SyncAccumulator;
describe("SyncAccumulator", function() { describe("SyncAccumulator", function() {
let sa; let sa;

View File

@@ -1,11 +1,6 @@
"use strict"; import {EventTimeline} from "../../src/models/event-timeline";
import 'source-map-support/register'; import {TimelineIndex, TimelineWindow} from "../../src/timeline-window";
const sdk = require("../.."); import * as utils from "../test-utils";
const EventTimeline = sdk.EventTimeline;
const TimelineWindow = sdk.TimelineWindow;
const TimelineIndex = require("../../lib/timeline-window").TimelineIndex;
const utils = require("../test-utils");
const ROOM_ID = "roomId"; const ROOM_ID = "roomId";
const USER_ID = "userId"; const USER_ID = "userId";

View File

@@ -1,8 +1,5 @@
"use strict"; import {User} from "../../src/models/user";
import 'source-map-support/register'; import * as utils from "../test-utils";
const sdk = require("../..");
const User = sdk.User;
const utils = require("../test-utils");
describe("User", function() { describe("User", function() {
const userId = "@alice:bar"; const userId = "@alice:bar";

View File

@@ -1,6 +1,4 @@
"use strict"; import * as utils from "../../src/utils";
import 'source-map-support/register';
const utils = require("../../lib/utils");
describe("utils", function() { describe("utils", function() {
describe("encodeParams", function() { describe("encodeParams", function() {

View File

@@ -20,7 +20,7 @@ limitations under the License.
* @module * @module
*/ */
export default class Reemitter { export class ReEmitter {
constructor(target) { constructor(target) {
this.target = target; this.target = target;

View File

@@ -17,8 +17,8 @@ limitations under the License.
/** @module auto-discovery */ /** @module auto-discovery */
import logger from './logger'; import {logger} from './logger';
import { URL as NodeURL } from "url"; import {URL as NodeURL} from "url";
// Dev note: Auto discovery is part of the spec. // Dev note: Auto discovery is part of the spec.
// See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery // See: https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery

View File

@@ -16,7 +16,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* This is an internal module. MatrixBaseApis is currently only meant to be used * This is an internal module. MatrixBaseApis is currently only meant to be used
@@ -25,17 +24,22 @@ limitations under the License.
* @module base-apis * @module base-apis
*/ */
import { SERVICE_TYPES } from './service-types'; import {SERVICE_TYPES} from './service-types';
import logger from './logger'; import {logger} from './logger';
import {PushProcessor} from "./pushprocessor";
const httpApi = require("./http-api"); import * as utils from "./utils";
const utils = require("./utils"); import {
const PushProcessor = require("./pushprocessor"); MatrixHttpApi,
PREFIX_IDENTITY_V1,
PREFIX_IDENTITY_V2,
PREFIX_R0,
PREFIX_UNSTABLE,
} from "./http-api";
function termsUrlForService(serviceType, baseUrl) { function termsUrlForService(serviceType, baseUrl) {
switch (serviceType) { switch (serviceType) {
case SERVICE_TYPES.IS: case SERVICE_TYPES.IS:
return baseUrl + httpApi.PREFIX_IDENTITY_V2 + '/terms'; return baseUrl + PREFIX_IDENTITY_V2 + '/terms';
case SERVICE_TYPES.IM: case SERVICE_TYPES.IM:
return baseUrl + '/_matrix/integrations/v1/terms'; return baseUrl + '/_matrix/integrations/v1/terms';
default: default:
@@ -83,7 +87,7 @@ function termsUrlForService(serviceType, baseUrl) {
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use * @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
* Authorization header instead of query param to send the access token to the server. * Authorization header instead of query param to send the access token to the server.
*/ */
function MatrixBaseApis(opts) { export function MatrixBaseApis(opts) {
utils.checkObjectHasKeys(opts, ["baseUrl", "request"]); utils.checkObjectHasKeys(opts, ["baseUrl", "request"]);
this.baseUrl = opts.baseUrl; this.baseUrl = opts.baseUrl;
@@ -95,13 +99,13 @@ function MatrixBaseApis(opts) {
idBaseUrl: opts.idBaseUrl, idBaseUrl: opts.idBaseUrl,
accessToken: opts.accessToken, accessToken: opts.accessToken,
request: opts.request, request: opts.request,
prefix: httpApi.PREFIX_R0, prefix: PREFIX_R0,
onlyData: true, onlyData: true,
extraParams: opts.queryParams, extraParams: opts.queryParams,
localTimeoutMs: opts.localTimeoutMs, localTimeoutMs: opts.localTimeoutMs,
useAuthorizationHeader: opts.useAuthorizationHeader, useAuthorizationHeader: opts.useAuthorizationHeader,
}; };
this._http = new httpApi.MatrixHttpApi(this, httpOpts); this._http = new MatrixHttpApi(this, httpOpts);
this._txnCtr = 0; this._txnCtr = 0;
} }
@@ -369,7 +373,7 @@ MatrixBaseApis.prototype.getSsoLoginUrl = function(redirectUrl, loginType) {
} }
return this._http.getUrl("/login/"+loginType+"/redirect", { return this._http.getUrl("/login/"+loginType+"/redirect", {
"redirectUrl": redirectUrl, "redirectUrl": redirectUrl,
}, httpApi.PREFIX_R0); }, PREFIX_R0);
}; };
/** /**
@@ -447,7 +451,7 @@ MatrixBaseApis.prototype.getFallbackAuthUrl = function(loginType, authSessionId)
return this._http.getUrl(path, { return this._http.getUrl(path, {
session: authSessionId, session: authSessionId,
}, httpApi.PREFIX_R0); }, PREFIX_R0);
}; };
// Room operations // Room operations
@@ -499,7 +503,7 @@ MatrixBaseApis.prototype.fetchRelations =
}); });
const response = await this._http.authedRequest( const response = await this._http.authedRequest(
undefined, "GET", path, null, null, { undefined, "GET", path, null, null, {
prefix: httpApi.PREFIX_UNSTABLE, prefix: PREFIX_UNSTABLE,
}, },
); );
return response; return response;
@@ -1377,7 +1381,7 @@ MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) {
MatrixBaseApis.prototype.addThreePidOnly = async function(data) { MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
const path = "/account/3pid/add"; const path = "/account/3pid/add";
const prefix = await this.isVersionSupported("r0.6.0") ? const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE; PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix }, undefined, "POST", path, null, data, { prefix },
); );
@@ -1400,7 +1404,7 @@ MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
MatrixBaseApis.prototype.bindThreePid = async function(data) { MatrixBaseApis.prototype.bindThreePid = async function(data) {
const path = "/account/3pid/bind"; const path = "/account/3pid/bind";
const prefix = await this.isVersionSupported("r0.6.0") ? const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE; PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix }, undefined, "POST", path, null, data, { prefix },
); );
@@ -1425,7 +1429,7 @@ MatrixBaseApis.prototype.unbindThreePid = async function(medium, address) {
id_server: this.getIdentityServerUrl(true), id_server: this.getIdentityServerUrl(true),
}; };
const prefix = await this.isVersionSupported("r0.6.0") ? const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE; PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix }, undefined, "POST", path, null, data, { prefix },
); );
@@ -1722,7 +1726,7 @@ MatrixBaseApis.prototype.uploadKeySignatures = function(content) {
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", '/keys/signatures/upload', undefined, undefined, "POST", '/keys/signatures/upload', undefined,
content, { content, {
prefix: httpApi.PREFIX_UNSTABLE, prefix: PREFIX_UNSTABLE,
}, },
); );
}; };
@@ -1815,7 +1819,7 @@ MatrixBaseApis.prototype.uploadDeviceSigningKeys = function(auth, keys) {
const data = Object.assign({}, keys, {auth}); const data = Object.assign({}, keys, {auth});
return this._http.authedRequest( return this._http.authedRequest(
undefined, "POST", "/keys/device_signing/upload", undefined, data, { undefined, "POST", "/keys/device_signing/upload", undefined, data, {
prefix: httpApi.PREFIX_UNSTABLE, prefix: PREFIX_UNSTABLE,
}, },
); );
}; };
@@ -1841,7 +1845,7 @@ MatrixBaseApis.prototype.registerWithIdentityServer = function(hsOpenIdToken) {
throw new Error("No Identity Server base URL set"); throw new Error("No Identity Server base URL set");
} }
const uri = this.idBaseUrl + httpApi.PREFIX_IDENTITY_V2 + "/account/register"; const uri = this.idBaseUrl + PREFIX_IDENTITY_V2 + "/account/register";
return this._http.requestOtherUrl( return this._http.requestOtherUrl(
undefined, "POST", uri, undefined, "POST", uri,
null, hsOpenIdToken, null, hsOpenIdToken,
@@ -1890,7 +1894,7 @@ MatrixBaseApis.prototype.requestEmailToken = async function(
try { try {
const response = await this._http.idServerRequest( const response = await this._http.idServerRequest(
undefined, "POST", "/validate/email/requestToken", undefined, "POST", "/validate/email/requestToken",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, params, PREFIX_IDENTITY_V2, identityAccessToken,
); );
// TODO: Fold callback into above call once v1 path below is removed // TODO: Fold callback into above call once v1 path below is removed
if (callback) callback(null, response); if (callback) callback(null, response);
@@ -1903,7 +1907,7 @@ MatrixBaseApis.prototype.requestEmailToken = async function(
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
callback, "POST", "/validate/email/requestToken", callback, "POST", "/validate/email/requestToken",
params, httpApi.PREFIX_IDENTITY_V1, params, PREFIX_IDENTITY_V1,
); );
} }
if (callback) callback(err); if (callback) callback(err);
@@ -1958,7 +1962,7 @@ MatrixBaseApis.prototype.requestMsisdnToken = async function(
try { try {
const response = await this._http.idServerRequest( const response = await this._http.idServerRequest(
undefined, "POST", "/validate/msisdn/requestToken", undefined, "POST", "/validate/msisdn/requestToken",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, params, PREFIX_IDENTITY_V2, identityAccessToken,
); );
// TODO: Fold callback into above call once v1 path below is removed // TODO: Fold callback into above call once v1 path below is removed
if (callback) callback(null, response); if (callback) callback(null, response);
@@ -1971,7 +1975,7 @@ MatrixBaseApis.prototype.requestMsisdnToken = async function(
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
callback, "POST", "/validate/msisdn/requestToken", callback, "POST", "/validate/msisdn/requestToken",
params, httpApi.PREFIX_IDENTITY_V1, params, PREFIX_IDENTITY_V1,
); );
} }
if (callback) callback(err); if (callback) callback(err);
@@ -2013,7 +2017,7 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
try { try {
return await this._http.idServerRequest( return await this._http.idServerRequest(
undefined, "POST", "/validate/msisdn/submitToken", undefined, "POST", "/validate/msisdn/submitToken",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, params, PREFIX_IDENTITY_V2, identityAccessToken,
); );
} catch (err) { } catch (err) {
if (err.cors === "rejected" || err.httpStatus === 404) { if (err.cors === "rejected" || err.httpStatus === 404) {
@@ -2023,7 +2027,7 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
undefined, "POST", "/validate/msisdn/submitToken", undefined, "POST", "/validate/msisdn/submitToken",
params, httpApi.PREFIX_IDENTITY_V1, params, PREFIX_IDENTITY_V1,
); );
} }
throw err; throw err;
@@ -2074,7 +2078,7 @@ MatrixBaseApis.prototype.submitMsisdnTokenOtherUrl = function(
MatrixBaseApis.prototype.getIdentityHashDetails = function(identityAccessToken) { MatrixBaseApis.prototype.getIdentityHashDetails = function(identityAccessToken) {
return this._http.idServerRequest( return this._http.idServerRequest(
undefined, "GET", "/hash_details", undefined, "GET", "/hash_details",
null, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, null, PREFIX_IDENTITY_V2, identityAccessToken,
); );
}; };
@@ -2143,7 +2147,7 @@ MatrixBaseApis.prototype.identityHashedLookup = async function(
const response = await this._http.idServerRequest( const response = await this._http.idServerRequest(
undefined, "POST", "/lookup", undefined, "POST", "/lookup",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, params, PREFIX_IDENTITY_V2, identityAccessToken,
); );
if (!response || !response['mappings']) return []; // no results if (!response || !response['mappings']) return []; // no results
@@ -2223,7 +2227,7 @@ MatrixBaseApis.prototype.lookupThreePid = async function(
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
callback, "GET", "/lookup", callback, "GET", "/lookup",
params, httpApi.PREFIX_IDENTITY_V1, params, PREFIX_IDENTITY_V1,
); );
} }
if (callback) callback(err, undefined); if (callback) callback(err, undefined);
@@ -2281,7 +2285,7 @@ MatrixBaseApis.prototype.bulkLookupThreePids = async function(
logger.warn("IS doesn't support v2, falling back to deprecated v1"); logger.warn("IS doesn't support v2, falling back to deprecated v1");
return await this._http.idServerRequest( return await this._http.idServerRequest(
undefined, "POST", "/bulk_lookup", params, undefined, "POST", "/bulk_lookup", params,
httpApi.PREFIX_IDENTITY_V1, identityAccessToken, PREFIX_IDENTITY_V1, identityAccessToken,
); );
} }
throw err; throw err;
@@ -2304,7 +2308,7 @@ MatrixBaseApis.prototype.getIdentityAccount = function(
) { ) {
return this._http.idServerRequest( return this._http.idServerRequest(
undefined, "GET", "/account", undefined, "GET", "/account",
undefined, httpApi.PREFIX_IDENTITY_V2, identityAccessToken, undefined, PREFIX_IDENTITY_V2, identityAccessToken,
); );
}; };
@@ -2432,7 +2436,3 @@ MatrixBaseApis.prototype.reportEvent = function(roomId, eventId, score, reason)
return this._http.authedRequest(undefined, "POST", path, null, {score, reason}); return this._http.authedRequest(undefined, "POST", path, null, {score, reason});
}; };
/**
* MatrixBaseApis object
*/
module.exports = MatrixBaseApis;

54
src/browser-index.js Normal file
View File

@@ -0,0 +1,54 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import request from "browser-request";
import queryString from "qs";
matrixcs.request(function(opts, fn) {
// We manually fix the query string for browser-request because
// it doesn't correctly handle cases like ?via=one&via=two. Instead
// we mimic `request`'s query string interface to make it all work
// as expected.
// browser-request will happily take the constructed string as the
// query string without trying to modify it further.
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
return request(opts, fn);
});
// just *accessing* indexedDB throws an exception in firefox with
// indexeddb disabled.
let indexedDB;
try {
indexedDB = global.indexedDB;
} catch(e) {}
// if our browser (appears to) support indexeddb, use an indexeddb crypto store.
if (indexedDB) {
matrixcs.setCryptoStoreFactory(
function() {
return new matrixcs.IndexedDBCryptoStore(
indexedDB, "matrix-js-sdk:crypto",
);
},
);
}
// We export 3 things to make browserify happy as well as downstream projects.
// It's awkward, but required.
export * from "./matrix";
export default matrixcs; // keep export for browserify package deps
global.matrixcs = matrixcs;

View File

@@ -16,47 +16,41 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
const PushProcessor = require('./pushprocessor');
import {sleep} from './utils';
/** /**
* This is an internal module. See {@link MatrixClient} for the public class. * This is an internal module. See {@link MatrixClient} for the public class.
* @module client * @module client
*/ */
const EventEmitter = require("events").EventEmitter;
const url = require('url');
const httpApi = require("./http-api"); import url from "url";
const MatrixEvent = require("./models/event").MatrixEvent; import {EventEmitter} from "events";
const EventStatus = require("./models/event").EventStatus; import {MatrixBaseApis} from "./base-apis";
const EventTimeline = require("./models/event-timeline"); import {Filter} from "./filter";
const SearchResult = require("./models/search-result"); import {SyncApi} from "./sync";
const StubStore = require("./store/stub"); import {EventStatus, MatrixEvent} from "./models/event";
const webRtcCall = require("./webrtc/call"); import {EventTimeline} from "./models/event-timeline";
const utils = require("./utils"); import {SearchResult} from "./models/search-result";
const contentRepo = require("./content-repo"); import {StubStore} from "./store/stub";
const Filter = require("./filter"); import {createNewMatrixCall} from "./webrtc/call";
const SyncApi = require("./sync"); import * as utils from './utils';
const MatrixBaseApis = require("./base-apis"); import {sleep} from './utils';
const MatrixError = httpApi.MatrixError; import {MatrixError, PREFIX_MEDIA_R0, PREFIX_UNSTABLE} from "./http-api";
const ContentHelpers = require("./content-helpers"); import {getHttpUriForMxc} from "./content-repo";
const olmlib = require("./crypto/olmlib"); import * as ContentHelpers from "./content-helpers";
import * as olmlib from "./crypto/olmlib";
import ReEmitter from './ReEmitter'; import {ReEmitter} from './ReEmitter';
import RoomList from './crypto/RoomList'; import {RoomList} from './crypto/RoomList';
import logger from './logger'; import {logger} from './logger';
import {Crypto, isCryptoAvailable} from './crypto';
import Crypto from './crypto'; import {decodeRecoveryKey} from './crypto/recoverykey';
import { isCryptoAvailable } from './crypto'; import {keyFromAuthData} from './crypto/key_passphrase';
import { decodeRecoveryKey } from './crypto/recoverykey'; import {randomString} from './randomstring';
import { keyFromAuthData } from './crypto/key_passphrase'; import {PushProcessor} from "./pushprocessor";
import { randomString } from './randomstring'; import {encodeBase64, decodeBase64} from "./crypto/olmlib";
import { encodeBase64, decodeBase64 } from '../lib/crypto/olmlib';
const SCROLLBACK_DELAY_MS = 3000; const SCROLLBACK_DELAY_MS = 3000;
const CRYPTO_ENABLED = isCryptoAvailable(); export const CRYPTO_ENABLED = isCryptoAvailable();
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
function keysFromRecoverySession(sessions, decryptionKey, roomId) { function keysFromRecoverySession(sessions, decryptionKey, roomId) {
@@ -235,7 +229,7 @@ function keyFromRecoverySession(session, decryptionKey) {
* {DeviceTrustLevel} device_trust: The trust status of the device requesting * {DeviceTrustLevel} device_trust: The trust status of the device requesting
* the secret as returned by {@link module:client~MatrixClient#checkDeviceTrust}. * the secret as returned by {@link module:client~MatrixClient#checkDeviceTrust}.
*/ */
function MatrixClient(opts) { export function MatrixClient(opts) {
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl); opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl); opts.idBaseUrl = utils.ensureNoTrailingSlash(opts.idBaseUrl);
@@ -274,7 +268,7 @@ function MatrixClient(opts) {
// try constructing a MatrixCall to see if we are running in an environment // try constructing a MatrixCall to see if we are running in an environment
// which has WebRTC. If we are, listen for and handle m.call.* events. // which has WebRTC. If we are, listen for and handle m.call.* events.
const call = webRtcCall.createNewMatrixCall(this); const call = createNewMatrixCall(this);
this._supportsVoip = false; this._supportsVoip = false;
if (call) { if (call) {
setupCallEventHandler(this); setupCallEventHandler(this);
@@ -946,6 +940,35 @@ MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function() {
return this._crypto.getGlobalBlacklistUnverifiedDevices(); return this._crypto.getGlobalBlacklistUnverifiedDevices();
}; };
/**
* Set whether sendMessage in a room with unknown and unverified devices
* should throw an error and not send them message. This has 'Global' for
* symmetry with setGlobalBlacklistUnverifiedDevices but there is currently
* no room-level equivalent for this setting.
*
* This API is currently UNSTABLE and may change or be removed without notice.
*
* @param {boolean} value whether error on unknown devices
*/
MatrixClient.prototype.setGlobalErrorOnUnknownDevices = function(value) {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
this._crypto.setGlobalErrorOnUnknownDevices(value);
};
/**
* @return {boolean} whether to error on unknown devices
*
* This API is currently UNSTABLE and may change or be removed without notice.
*/
MatrixClient.prototype.getGlobalErrorOnUnknownDevices = function() {
if (this._crypto === null) {
throw new Error("End-to-end encryption disabled");
}
return this._crypto.getGlobalErrorOnUnknownDevices();
};
/** /**
* Add methods that call the corresponding method in this._crypto * Add methods that call the corresponding method in this._crypto
* *
@@ -1346,7 +1369,7 @@ MatrixClient.prototype.checkKeyBackup = function() {
MatrixClient.prototype.getKeyBackupVersion = function() { MatrixClient.prototype.getKeyBackupVersion = function() {
return this._http.authedRequest( return this._http.authedRequest(
undefined, "GET", "/room_keys/version", undefined, undefined, undefined, "GET", "/room_keys/version", undefined, undefined,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
).then((res) => { ).then((res) => {
if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) { if (res.algorithm !== olmlib.MEGOLM_BACKUP_ALGORITHM) {
const err = "Unknown backup algorithm: " + res.algorithm; const err = "Unknown backup algorithm: " + res.algorithm;
@@ -1505,14 +1528,20 @@ MatrixClient.prototype.createKeyBackupVersion = async function(info) {
// favour of just signing with the cross-singing master key. // favour of just signing with the cross-singing master key.
await this._crypto._signObject(data.auth_data); await this._crypto._signObject(data.auth_data);
if (this._crypto._crossSigningInfo.getId()) { if (
this._cryptoCallbacks.getCrossSigningKey &&
this._crypto._crossSigningInfo.getId()
) {
// now also sign the auth data with the cross-signing master key // now also sign the auth data with the cross-signing master key
// we check for the callback explicitly here because we still want to be able
// to create an un-cross-signed key backup if there is a cross-signing key but
// no callback supplied.
await this._crypto._crossSigningInfo.signObject(data.auth_data, "master"); await this._crypto._crossSigningInfo.signObject(data.auth_data, "master");
} }
const res = await this._http.authedRequest( const res = await this._http.authedRequest(
undefined, "POST", "/room_keys/version", undefined, data, undefined, "POST", "/room_keys/version", undefined, data,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
); );
// We could assume everything's okay and enable directly, but this ensures // We could assume everything's okay and enable directly, but this ensures
@@ -1544,7 +1573,7 @@ MatrixClient.prototype.deleteKeyBackupVersion = function(version) {
return this._http.authedRequest( return this._http.authedRequest(
undefined, "DELETE", path, undefined, undefined, undefined, "DELETE", path, undefined, undefined,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
); );
}; };
@@ -1586,7 +1615,7 @@ MatrixClient.prototype.sendKeyBackup = function(roomId, sessionId, version, data
const path = this._makeKeyBackupPath(roomId, sessionId, version); const path = this._makeKeyBackupPath(roomId, sessionId, version);
return this._http.authedRequest( return this._http.authedRequest(
undefined, "PUT", path.path, path.queryData, data, undefined, "PUT", path.path, path.queryData, data,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
); );
}; };
@@ -1720,7 +1749,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
return this._http.authedRequest( return this._http.authedRequest(
undefined, "GET", path.path, path.queryData, undefined, undefined, "GET", path.path, path.queryData, undefined,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
).then((res) => { ).then((res) => {
if (res.rooms) { if (res.rooms) {
for (const [roomId, roomData] of Object.entries(res.rooms)) { for (const [roomId, roomData] of Object.entries(res.rooms)) {
@@ -1728,7 +1757,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
totalKeyCount += Object.keys(roomData.sessions).length; totalKeyCount += Object.keys(roomData.sessions).length;
const roomKeys = keysFromRecoverySession( const roomKeys = keysFromRecoverySession(
roomData.sessions, decryption, roomId, roomKeys, roomData.sessions, decryption, roomId,
); );
for (const k of roomKeys) { for (const k of roomKeys) {
k.room_id = roomId; k.room_id = roomId;
@@ -1770,7 +1799,7 @@ MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, versio
const path = this._makeKeyBackupPath(roomId, sessionId, version); const path = this._makeKeyBackupPath(roomId, sessionId, version);
return this._http.authedRequest( return this._http.authedRequest(
undefined, "DELETE", path.path, path.queryData, undefined, undefined, "DELETE", path.path, path.queryData, undefined,
{prefix: httpApi.PREFIX_UNSTABLE}, {prefix: PREFIX_UNSTABLE},
); );
}; };
@@ -1806,7 +1835,7 @@ MatrixClient.prototype.getGroups = function() {
MatrixClient.prototype.getMediaConfig = function(callback) { MatrixClient.prototype.getMediaConfig = function(callback) {
return this._http.authedRequest( return this._http.authedRequest(
callback, "GET", "/config", undefined, undefined, { callback, "GET", "/config", undefined, undefined, {
prefix: httpApi.PREFIX_MEDIA_R0, prefix: PREFIX_MEDIA_R0,
}, },
); );
}; };
@@ -1913,6 +1942,25 @@ MatrixClient.prototype.getAccountData = function(eventType) {
return this.store.getAccountData(eventType); return this.store.getAccountData(eventType);
}; };
/**
* Get account data event of given type for the current user. This variant
* bypasses the local store and gets account data directly from the homeserver,
* which can be useful very early in startup before the initial sync.
* @param {string} eventType The event type
* @return {module:client.Promise} Resolves: The contents of the given account
* data event.
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixClient.prototype.getAccountDataFromServer = function(eventType) {
const path = utils.encodeUri("/user/$userId/account_data/$type", {
$userId: this.credentials.userId,
$type: eventType,
});
return this._http.authedRequest(
undefined, "GET", path, undefined,
);
};
/** /**
* Gets the users that are ignored by this client * Gets the users that are ignored by this client
* @returns {string[]} The array of users that are ignored (empty if none) * @returns {string[]} The array of users that are ignored (empty if none)
@@ -2743,7 +2791,7 @@ MatrixClient.prototype.getUrlPreview = function(url, ts, callback) {
url: url, url: url,
ts: ts, ts: ts,
}, undefined, { }, undefined, {
prefix: httpApi.PREFIX_MEDIA_R0, prefix: PREFIX_MEDIA_R0,
}, },
).then(function(response) { ).then(function(response) {
// TODO: expire cache occasionally // TODO: expire cache occasionally
@@ -3193,7 +3241,7 @@ MatrixClient.prototype.setAvatarUrl = function(url, callback) {
*/ */
MatrixClient.prototype.mxcUrlToHttp = MatrixClient.prototype.mxcUrlToHttp =
function(mxcUrl, width, height, resizeMethod, allowDirectLinks) { function(mxcUrl, width, height, resizeMethod, allowDirectLinks) {
return contentRepo.getHttpUriForMxc( return getHttpUriForMxc(
this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks, this.baseUrl, mxcUrl, width, height, resizeMethod, allowDirectLinks,
); );
}; };
@@ -4852,7 +4900,7 @@ function setupCallEventHandler(client) {
); );
} }
call = webRtcCall.createNewMatrixCall(client, event.getRoomId(), { call = createNewMatrixCall(client, event.getRoomId(), {
forceTURN: client._forceTURN, forceTURN: client._forceTURN,
}); });
if (!call) { if (!call) {
@@ -4952,7 +5000,7 @@ function setupCallEventHandler(client) {
// if not live, store the fact that the call has ended because // if not live, store the fact that the call has ended because
// we're probably getting events backwards so // we're probably getting events backwards so
// the hangup will come before the invite // the hangup will come before the invite
call = webRtcCall.createNewMatrixCall(client, event.getRoomId()); call = createNewMatrixCall(client, event.getRoomId());
if (call) { if (call) {
call.callId = content.call_id; call.callId = content.call_id;
call._initWithHangup(event); call._initWithHangup(event);
@@ -5052,11 +5100,6 @@ MatrixClient.prototype.generateClientSecret = function() {
return randomString(32); return randomString(32);
}; };
/** */
module.exports.MatrixClient = MatrixClient;
/** */
module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
// MatrixClient Event JSDocs // MatrixClient Event JSDocs
/** /**

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,88 +14,86 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** @module ContentHelpers */ /** @module ContentHelpers */
module.exports = {
/**
* Generates the content for a HTML Message event
* @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlMessage: function(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
/** /**
* Generates the content for a HTML Notice event * Generates the content for a HTML Message event
* @param {string} body the plaintext body of the notice * @param {string} body the plaintext body of the message
* @param {string} htmlBody the HTML representation of the notice * @param {string} htmlBody the HTML representation of the message
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}} * @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/ */
makeHtmlNotice: function(body, htmlBody) { export function makeHtmlMessage(body, htmlBody) {
return { return {
msgtype: "m.notice", msgtype: "m.text",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
body: body, body: body,
formatted_body: htmlBody, formatted_body: htmlBody,
}; };
}, }
/** /**
* Generates the content for a HTML Emote event * Generates the content for a HTML Notice event
* @param {string} body the plaintext body of the emote * @param {string} body the plaintext body of the notice
* @param {string} htmlBody the HTML representation of the emote * @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}} * @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/ */
makeHtmlEmote: function(body, htmlBody) { export function makeHtmlNotice(body, htmlBody) {
return { return {
msgtype: "m.emote", msgtype: "m.notice",
format: "org.matrix.custom.html", format: "org.matrix.custom.html",
body: body, body: body,
formatted_body: htmlBody, formatted_body: htmlBody,
}; };
}, }
/** /**
* Generates the content for a Plaintext Message event * Generates the content for a HTML Emote event
* @param {string} body the plaintext body of the emote * @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}} * @param {string} htmlBody the HTML representation of the emote
*/ * @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
makeTextMessage: function(body) { */
return { export function makeHtmlEmote(body, htmlBody) {
msgtype: "m.text", return {
body: body, msgtype: "m.emote",
}; format: "org.matrix.custom.html",
}, body: body,
formatted_body: htmlBody,
};
}
/** /**
* Generates the content for a Plaintext Notice event * Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the notice * @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}} * @returns {{msgtype: string, body: string}}
*/ */
makeNotice: function(body) { export function makeTextMessage(body) {
return { return {
msgtype: "m.notice", msgtype: "m.text",
body: body, body: body,
}; };
}, }
/** /**
* Generates the content for a Plaintext Emote event * Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the emote * @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}} * @returns {{msgtype: string, body: string}}
*/ */
makeEmoteMessage: function(body) { export function makeNotice(body) {
return { return {
msgtype: "m.emote", msgtype: "m.notice",
body: body, body: body,
}; };
}, }
};
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
export function makeEmoteMessage(body) {
return {
msgtype: "m.emote",
body: body,
};
}

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -16,95 +17,93 @@ limitations under the License.
/** /**
* @module content-repo * @module content-repo
*/ */
const utils = require("./utils");
/** Content Repo utility functions */ import * as utils from "./utils";
module.exports = {
/** /**
* Get the HTTP URL for an MXC URI. * Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo. * @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} mxc The mxc:// URI. * @param {string} mxc The mxc:// URI.
* @param {Number} width The desired width of the thumbnail. * @param {Number} width The desired width of the thumbnail.
* @param {Number} height The desired height of the thumbnail. * @param {Number} height The desired height of the thumbnail.
* @param {string} resizeMethod The thumbnail resize method to use, either * @param {string} resizeMethod The thumbnail resize method to use, either
* "crop" or "scale". * "crop" or "scale".
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs * @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
* directly. Fetching such URLs will leak information about the user to * directly. Fetching such URLs will leak information about the user to
* anyone they share a room with. If false, will return the emptry string * anyone they share a room with. If false, will return the emptry string
* for such URLs. * for such URLs.
* @return {string} The complete URL to the content. * @return {string} The complete URL to the content.
*/ */
getHttpUriForMxc: function(baseUrl, mxc, width, height, export function getHttpUriForMxc(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) { resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) { if (typeof mxc !== "string" || !mxc) {
return '';
}
if (mxc.indexOf("mxc://") !== 0) {
if (allowDirectLinks) {
return mxc;
} else {
return ''; return '';
} }
if (mxc.indexOf("mxc://") !== 0) { }
if (allowDirectLinks) { let serverAndMediaId = mxc.slice(6); // strips mxc://
return mxc; let prefix = "/_matrix/media/r0/download/";
} else { const params = {};
return '';
}
}
let serverAndMediaId = mxc.slice(6); // strips mxc://
let prefix = "/_matrix/media/r0/download/";
const params = {};
if (width) { if (width) {
params.width = Math.round(width); params.width = Math.round(width);
} }
if (height) { if (height) {
params.height = Math.round(height); params.height = Math.round(height);
} }
if (resizeMethod) { if (resizeMethod) {
params.method = resizeMethod; params.method = resizeMethod;
} }
if (utils.keys(params).length > 0) { if (utils.keys(params).length > 0) {
// these are thumbnailing params so they probably want the // these are thumbnailing params so they probably want the
// thumbnailing API... // thumbnailing API...
prefix = "/_matrix/media/r0/thumbnail/"; prefix = "/_matrix/media/r0/thumbnail/";
} }
const fragmentOffset = serverAndMediaId.indexOf("#"); const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = ""; let fragment = "";
if (fragmentOffset >= 0) { if (fragmentOffset >= 0) {
fragment = serverAndMediaId.substr(fragmentOffset); fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset); serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
} }
return baseUrl + prefix + serverAndMediaId + return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" : (utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment; ("?" + utils.encodeParams(params))) + fragment;
}, }
/** /**
* Get an identicon URL from an arbitrary string. * Get an identicon URL from an arbitrary string.
* @param {string} baseUrl The base homeserver url which has a content repo. * @param {string} baseUrl The base homeserver url which has a content repo.
* @param {string} identiconString The string to create an identicon for. * @param {string} identiconString The string to create an identicon for.
* @param {Number} width The desired width of the image in pixels. Default: 96. * @param {Number} width The desired width of the image in pixels. Default: 96.
* @param {Number} height The desired height of the image in pixels. Default: 96. * @param {Number} height The desired height of the image in pixels. Default: 96.
* @return {string} The complete URL to the identicon. * @return {string} The complete URL to the identicon.
* @deprecated This is no longer in the specification. * @deprecated This is no longer in the specification.
*/ */
getIdenticonUri: function(baseUrl, identiconString, width, height) { export function getIdenticonUri(baseUrl, identiconString, width, height) {
if (!identiconString) { if (!identiconString) {
return null; return null;
} }
if (!width) { if (!width) {
width = 96; width = 96;
} }
if (!height) { if (!height) {
height = 96; height = 96;
} }
const params = { const params = {
width: width, width: width,
height: height, height: height,
}; };
const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", { const path = utils.encodeUri("/_matrix/media/unstable/identicon/$ident", {
$ident: identiconString, $ident: identiconString,
}); });
return baseUrl + path + return baseUrl + path +
(utils.keys(params).length === 0 ? "" : (utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))); ("?" + utils.encodeParams(params)));
}, }
};

View File

@@ -20,9 +20,9 @@ limitations under the License.
* @module crypto/CrossSigning * @module crypto/CrossSigning
*/ */
import {pkSign, pkVerify, encodeBase64, decodeBase64} from './olmlib'; import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import logger from '../logger'; import {logger} from '../logger';
function publicKeyFromKeyInfo(keyInfo) { function publicKeyFromKeyInfo(keyInfo) {
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey } // `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }

View File

@@ -15,7 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module crypto/DeviceList * @module crypto/DeviceList
@@ -24,12 +23,11 @@ limitations under the License.
*/ */
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import {logger} from '../logger';
import logger from '../logger'; import {DeviceInfo} from './deviceinfo';
import DeviceInfo from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning'; import {CrossSigningInfo} from './CrossSigning';
import olmlib from './olmlib'; import * as olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils'; import {defer, sleep} from '../utils';
@@ -63,7 +61,7 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
/** /**
* @alias module:crypto/DeviceList * @alias module:crypto/DeviceList
*/ */
export default class DeviceList extends EventEmitter { export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) { constructor(baseApis, cryptoStore, olmDevice) {
super(); super();

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017, 2019 New Vector Ltd Copyright 2017, 2019 New Vector Ltd
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,8 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../logger'; import {logger} from '../logger';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import * as algorithms from './algorithms';
// The maximum size of an event is 65K, and we base64 the content, so this is a // The maximum size of an event is 65K, and we base64 the content, so this is a
// reasonable approximation to the biggest plaintext we can encrypt. // reasonable approximation to the biggest plaintext we can encrypt.
@@ -69,7 +71,7 @@ function checkPayloadLength(payloadString) {
* @property {string} deviceCurve25519Key Curve25519 key for the account * @property {string} deviceCurve25519Key Curve25519 key for the account
* @property {string} deviceEd25519Key Ed25519 key for the account * @property {string} deviceEd25519Key Ed25519 key for the account
*/ */
function OlmDevice(cryptoStore) { export function OlmDevice(cryptoStore) {
this._cryptoStore = cryptoStore; this._cryptoStore = cryptoStore;
this._pickleKey = "DEFAULT_KEY"; this._pickleKey = "DEFAULT_KEY";
@@ -671,6 +673,18 @@ OlmDevice.prototype.matchesSession = async function(
return matches; return matches;
}; };
OlmDevice.prototype.recordSessionProblem = async function(deviceKey, type, fixed) {
await this._cryptoStore.storeEndToEndSessionProblem(deviceKey, type, fixed);
};
OlmDevice.prototype.sessionMayHaveProblems = async function(deviceKey, timestamp) {
return await this._cryptoStore.getEndToEndSessionProblem(deviceKey, timestamp);
};
OlmDevice.prototype.filterOutNotifiedErrorDevices = async function(devices) {
return await this._cryptoStore.filterOutNotifiedErrorDevices(devices);
};
// Outbound group session // Outbound group session
// ====================== // ======================
@@ -818,9 +832,9 @@ OlmDevice.prototype._getInboundGroupSession = function(
roomId, senderKey, sessionId, txn, func, roomId, senderKey, sessionId, txn, func,
) { ) {
this._cryptoStore.getEndToEndInboundGroupSession( this._cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData) => { senderKey, sessionId, txn, (sessionData, withheld) => {
if (sessionData === null) { if (sessionData === null) {
func(null); func(null, null, withheld);
return; return;
} }
@@ -834,7 +848,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
} }
this._unpickleInboundGroupSession(sessionData, (session) => { this._unpickleInboundGroupSession(sessionData, (session) => {
func(session, sessionData); func(session, sessionData, withheld);
}); });
}, },
); );
@@ -859,7 +873,10 @@ OlmDevice.prototype.addInboundGroupSession = async function(
exportFormat, exportFormat,
) { ) {
await this._cryptoStore.doTxn( await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { 'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
/* if we already have this session, consider updating it */ /* if we already have this session, consider updating it */
this._getInboundGroupSession( this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, roomId, senderKey, sessionId, txn,
@@ -914,6 +931,60 @@ OlmDevice.prototype.addInboundGroupSession = async function(
); );
}; };
/**
* Record in the data store why an inbound group session was withheld.
*
* @param {string} roomId room that the session belongs to
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier
* @param {string} code reason code
* @param {string} reason human-readable version of `code`
*/
OlmDevice.prototype.addInboundGroupSessionWithheld = async function(
roomId, senderKey, sessionId, code, reason,
) {
await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD],
(txn) => {
this._cryptoStore.storeEndToEndInboundGroupSessionWithheld(
senderKey, sessionId,
{
room_id: roomId,
code: code,
reason: reason,
},
txn,
);
},
);
};
export const WITHHELD_MESSAGES = {
"m.unverified": "The sender has disabled encrypting to unverified devices.",
"m.blacklisted": "The sender has blocked you.",
"m.unauthorised": "You are not authorised to read the message.",
"m.no_olm": "Unable to establish a secure channel.",
};
/**
* Calculate the message to use for the exception when a session key is withheld.
*
* @param {object} withheld An object that describes why the key was withheld.
*
* @return {string} the message
*
* @private
*/
function _calculateWithheldMessage(withheld) {
if (withheld.code && withheld.code in WITHHELD_MESSAGES) {
return WITHHELD_MESSAGES[withheld.code];
} else if (withheld.reason) {
return withheld.reason;
} else {
return "decryption key withheld";
}
}
/** /**
* Decrypt a received message with an inbound group session * Decrypt a received message with an inbound group session
* *
@@ -934,16 +1005,49 @@ OlmDevice.prototype.decryptGroupMessage = async function(
roomId, senderKey, sessionId, body, eventId, timestamp, roomId, senderKey, sessionId, body, eventId, timestamp,
) { ) {
let result; let result;
// when the localstorage crypto store is used as an indexeddb backend,
// exceptions thrown from within the inner function are not passed through
// to the top level, so we store exceptions in a variable and raise them at
// the end
let error;
await this._cryptoStore.doTxn( await this._cryptoStore.doTxn(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { 'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._getInboundGroupSession( this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => { roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null) { if (session === null) {
if (withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
_calculateWithheldMessage(withheld),
{
session: senderKey + '|' + sessionId,
},
);
}
result = null; result = null;
return; return;
} }
const res = session.decrypt(body); let res;
try {
res = session.decrypt(body);
} catch (e) {
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX' && withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
_calculateWithheldMessage(withheld),
{
session: senderKey + '|' + sessionId,
},
);
} else {
error = e;
}
return;
}
let plaintext = res.plaintext; let plaintext = res.plaintext;
if (plaintext === undefined) { if (plaintext === undefined) {
@@ -965,10 +1069,11 @@ OlmDevice.prototype.decryptGroupMessage = async function(
msgInfo.id !== eventId || msgInfo.id !== eventId ||
msgInfo.timestamp !== timestamp msgInfo.timestamp !== timestamp
) { ) {
throw new Error( error = new Error(
"Duplicate message index, possible replay attack: " + "Duplicate message index, possible replay attack: " +
messageIndexKey, messageIndexKey,
); );
return;
} }
} }
this._inboundGroupSessionMessageIndexes[messageIndexKey] = { this._inboundGroupSessionMessageIndexes[messageIndexKey] = {
@@ -994,6 +1099,9 @@ OlmDevice.prototype.decryptGroupMessage = async function(
}, },
); );
if (error) {
throw error;
}
return result; return result;
}; };
@@ -1009,7 +1117,10 @@ OlmDevice.prototype.decryptGroupMessage = async function(
OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) { OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) {
let result; let result;
await this._cryptoStore.doTxn( await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { 'readonly', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._cryptoStore.getEndToEndInboundGroupSession( this._cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData) => { senderKey, sessionId, txn, (sessionData) => {
if (sessionData === null) { if (sessionData === null) {
@@ -1060,7 +1171,10 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
) { ) {
let result; let result;
await this._cryptoStore.doTxn( await this._cryptoStore.doTxn(
'readonly', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => { 'readonly', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._getInboundGroupSession( this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => { roomId, senderKey, sessionId, txn, (session, sessionData) => {
if (session === null) { if (session === null) {
@@ -1139,6 +1253,3 @@ OlmDevice.prototype.verifySignature = function(
util.ed25519_verify(key, message, signature); util.ed25519_verify(key, message, signature);
}); });
}; };
/** */
module.exports = OlmDevice;

View File

@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../logger'; import {logger} from '../logger';
import utils from '../utils'; import * as utils from '../utils';
/** /**
* Internal module. Management of outgoing room key requests. * Internal module. Management of outgoing room key requests.
@@ -75,7 +75,7 @@ const ROOM_KEY_REQUEST_STATES = {
CANCELLATION_PENDING_AND_WILL_RESEND: 3, CANCELLATION_PENDING_AND_WILL_RESEND: 3,
}; };
export default class OutgoingRoomKeyRequestManager { export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) { constructor(baseApis, deviceId, cryptoStore) {
this._baseApis = baseApis; this._baseApis = baseApis;
this._deviceId = deviceId; this._deviceId = deviceId;

View File

@@ -20,12 +20,12 @@ limitations under the License.
* Manages the list of encrypted rooms * Manages the list of encrypted rooms
*/ */
import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
/** /**
* @alias module:crypto/RoomList * @alias module:crypto/RoomList
*/ */
export default class RoomList { export class RoomList {
constructor(cryptoStore) { constructor(cryptoStore) {
this._cryptoStore = cryptoStore; this._cryptoStore = cryptoStore;

View File

@@ -15,10 +15,10 @@ limitations under the License.
*/ */
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import logger from '../logger'; import {logger} from '../logger';
import olmlib from './olmlib'; import * as olmlib from './olmlib';
import { randomString } from '../randomstring'; import {pkVerify} from './olmlib';
import { pkVerify } from './olmlib'; import {randomString} from '../randomstring';
export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-sha2"; export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-sha2";
@@ -26,7 +26,7 @@ export const SECRET_STORAGE_ALGORITHM_V1 = "m.secret_storage.v1.curve25519-aes-s
* Implements Secure Secret Storage and Sharing (MSC1946) * Implements Secure Secret Storage and Sharing (MSC1946)
* @module crypto/SecretStorage * @module crypto/SecretStorage
*/ */
export default class SecretStorage extends EventEmitter { export class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks, crossSigningInfo) { constructor(baseApis, cryptoCallbacks, crossSigningInfo) {
super(); super();
this._baseApis = baseApis; this._baseApis = baseApis;

View File

@@ -50,7 +50,7 @@ export const DECRYPTION_CLASSES = {};
* @param {string} params.roomId The ID of the room we will be sending to * @param {string} params.roomId The ID of the room we will be sending to
* @param {object} params.config The body of the m.room.encryption event * @param {object} params.config The body of the m.room.encryption event
*/ */
class EncryptionAlgorithm { export class EncryptionAlgorithm {
constructor(params) { constructor(params) {
this._userId = params.userId; this._userId = params.userId;
this._deviceId = params.deviceId; this._deviceId = params.deviceId;
@@ -84,7 +84,6 @@ class EncryptionAlgorithm {
onRoomMembership(event, member, oldMembership) { onRoomMembership(event, member, oldMembership) {
} }
} }
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/** /**
* base type for decryption implementations * base type for decryption implementations
@@ -98,7 +97,7 @@ export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
* @param {string=} params.roomId The ID of the room we will be receiving * @param {string=} params.roomId The ID of the room we will be receiving
* from. Null for to-device events. * from. Null for to-device events.
*/ */
class DecryptionAlgorithm { export class DecryptionAlgorithm {
constructor(params) { constructor(params) {
this._userId = params.userId; this._userId = params.userId;
this._crypto = params.crypto; this._crypto = params.crypto;
@@ -159,8 +158,17 @@ class DecryptionAlgorithm {
shareKeysWithDevice(keyRequest) { shareKeysWithDevice(keyRequest) {
throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm"); throw new Error("shareKeysWithDevice not supported for this DecryptionAlgorithm");
} }
/**
* Retry decrypting all the events from a sender that haven't been
* decrypted yet.
*
* @param {string} senderKey the sender's key
*/
async retryDecryptionFromSender(senderKey) {
// ignore by default
}
} }
export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/** /**
* Exception thrown when decryption fails * Exception thrown when decryption fails
@@ -173,7 +181,7 @@ export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
* *
* @extends Error * @extends Error
*/ */
class DecryptionError extends Error { export class DecryptionError extends Error {
constructor(code, msg, details) { constructor(code, msg, details) {
super(msg); super(msg);
this.code = code; this.code = code;
@@ -181,7 +189,6 @@ class DecryptionError extends Error {
this.detailedString = _detailedStringForDecryptionError(this, details); this.detailedString = _detailedStringForDecryptionError(this, details);
} }
} }
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
function _detailedStringForDecryptionError(err, details) { function _detailedStringForDecryptionError(err, details) {
let result = err.name + '[msg: ' + err.message; let result = err.name + '[msg: ' + err.message;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,28 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module crypto/algorithms * @module crypto/algorithms
*/ */
const base = require("./base"); import "./olm";
import "./megolm";
require("./olm"); export * from "./base";
require("./megolm");
/**
* @see module:crypto/algorithms/base.ENCRYPTION_CLASSES
*/
module.exports.ENCRYPTION_CLASSES = base.ENCRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DECRYPTION_CLASSES
*/
module.exports.DECRYPTION_CLASSES = base.DECRYPTION_CLASSES;
/**
* @see module:crypto/algorithms/base.DecryptionError
*/
module.exports.DecryptionError = base.DecryptionError;

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,7 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* Defines m.olm encryption/decryption * Defines m.olm encryption/decryption
@@ -22,11 +22,19 @@ limitations under the License.
* @module crypto/algorithms/megolm * @module crypto/algorithms/megolm
*/ */
import logger from '../../logger'; import {logger} from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import * as olmlib from "../olmlib";
import {
DecryptionAlgorithm,
DecryptionError,
EncryptionAlgorithm,
registerAlgorithm,
UnknownDeviceError,
} from "./base";
const utils = require("../../utils"); import {WITHHELD_MESSAGES} from '../OlmDevice';
const olmlib = require("../olmlib");
const base = require("./base");
/** /**
* @private * @private
@@ -47,6 +55,7 @@ function OutboundSessionInfo(sessionId) {
this.useCount = 0; this.useCount = 0;
this.creationTime = new Date().getTime(); this.creationTime = new Date().getTime();
this.sharedWithDevices = {}; this.sharedWithDevices = {};
this.blockedDevicesNotified = {};
} }
@@ -84,6 +93,15 @@ OutboundSessionInfo.prototype.markSharedWithDevice = function(
this.sharedWithDevices[userId][deviceId] = chainIndex; this.sharedWithDevices[userId][deviceId] = chainIndex;
}; };
OutboundSessionInfo.prototype.markNotifiedBlockedDevice = function(
userId, deviceId,
) {
if (!this.blockedDevicesNotified[userId]) {
this.blockedDevicesNotified[userId] = {};
}
this.blockedDevicesNotified[userId][deviceId] = true;
};
/** /**
* Determine if this session has been shared with devices which it shouldn't * Determine if this session has been shared with devices which it shouldn't
* have been. * have been.
@@ -128,13 +146,13 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
* Megolm encryption implementation * Megolm encryption implementation
* *
* @constructor * @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm} * @extends {module:crypto/algorithms/EncryptionAlgorithm}
* *
* @param {object} params parameters, as per * @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm} * {@link module:crypto/algorithms/EncryptionAlgorithm}
*/ */
function MegolmEncryption(params) { function MegolmEncryption(params) {
base.EncryptionAlgorithm.call(this, params); polyfillSuper(this, EncryptionAlgorithm, params);
// the most recent attempt to set up a session. This is used to serialise // the most recent attempt to set up a session. This is used to serialise
// the session setups, so that we have a race-free view of which session we // the session setups, so that we have a race-free view of which session we
@@ -160,17 +178,20 @@ function MegolmEncryption(params) {
this._sessionRotationPeriodMsgs = params.config.rotation_period_msgs; this._sessionRotationPeriodMsgs = params.config.rotation_period_msgs;
} }
} }
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm); utils.inherits(MegolmEncryption, EncryptionAlgorithm);
/** /**
* @private * @private
* *
* @param {Object} devicesInRoom The devices in this room, indexed by user ID * @param {Object} devicesInRoom The devices in this room, indexed by user ID
* @param {Object} blocked The devices that are blocked, indexed by user ID
* *
* @return {module:client.Promise} Promise which resolves to the * @return {module:client.Promise} Promise which resolves to the
* OutboundSessionInfo when setup is complete. * OutboundSessionInfo when setup is complete.
*/ */
MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) { MegolmEncryption.prototype._ensureOutboundSession = async function(
devicesInRoom, blocked,
) {
const self = this; const self = this;
let session; let session;
@@ -237,9 +258,50 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
} }
} }
return self._shareKeyWithDevices( const errorDevices = [];
session, shareMap,
await self._shareKeyWithDevices(
session, shareMap, errorDevices,
); );
// are there any new blocked devices that we need to notify?
const blockedMap = {};
for (const userId in blocked) {
if (!blocked.hasOwnProperty(userId)) {
continue;
}
const userBlockedDevices = blocked[userId];
for (const deviceId in userBlockedDevices) {
if (!userBlockedDevices.hasOwnProperty(deviceId)) {
continue;
}
if (
!session.blockedDevicesNotified[userId] ||
session.blockedDevicesNotified[userId][deviceId] === undefined
) {
blockedMap[userId] = blockedMap[userId] || [];
blockedMap[userId].push(userBlockedDevices[deviceId]);
}
}
}
const filteredErrorDevices =
await self._olmDevice.filterOutNotifiedErrorDevices(errorDevices);
for (const {userId, deviceInfo} of filteredErrorDevices) {
blockedMap[userId] = blockedMap[userId] || [];
blockedMap[userId].push({
code: "m.no_olm",
reason: WITHHELD_MESSAGES["m.no_olm"],
deviceInfo,
});
}
// notify blocked devices that they're blocked
await self._notifyBlockedDevices(session, blockedMap);
} }
// helper which returns the session prepared by prepareSession // helper which returns the session prepared by prepareSession
@@ -287,6 +349,10 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
}; };
/** /**
* Splits the user device map into multiple chunks to reduce the number of
* devices we encrypt to per API call. Also filters out devices we don't have
* a session with.
*
* @private * @private
* *
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session * @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
@@ -299,12 +365,16 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser * @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices * map from userid to list of devices
* *
* @param {array<object>} errorDevices
* array that will be populated with the devices that can't get an
* olm session for
*
* @return {array<object<userid, deviceInfo>>} * @return {array<object<userid, deviceInfo>>}
*/ */
MegolmEncryption.prototype._splitUserDeviceMap = function( MegolmEncryption.prototype._splitUserDeviceMap = function(
session, chainIndex, devicemap, devicesByUser, session, chainIndex, devicemap, devicesByUser, errorDevices,
) { ) {
const maxToDeviceMessagesPerRequest = 20; const maxUsersPerRequest = 20;
// use an array where the slices of a content map gets stored // use an array where the slices of a content map gets stored
const mapSlices = []; const mapSlices = [];
@@ -334,6 +404,8 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
// to claim a one-time-key for dead devices on every message. // to claim a one-time-key for dead devices on every message.
session.markSharedWithDevice(userId, deviceId, chainIndex); session.markSharedWithDevice(userId, deviceId, chainIndex);
errorDevices.push({userId, deviceInfo});
// ensureOlmSessionsForUsers has already done the logging, // ensureOlmSessionsForUsers has already done the logging,
// so just skip it. // so just skip it.
continue; continue;
@@ -343,11 +415,6 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
"share keys with device " + userId + ":" + deviceId, "share keys with device " + userId + ":" + deviceId,
); );
if (entriesInCurrentSlice > maxToDeviceMessagesPerRequest) {
// the current slice is filled up. Start inserting into the next slice
entriesInCurrentSlice = 0;
currentSliceId++;
}
if (!mapSlices[currentSliceId]) { if (!mapSlices[currentSliceId]) {
mapSlices[currentSliceId] = []; mapSlices[currentSliceId] = [];
} }
@@ -359,6 +426,61 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
entriesInCurrentSlice++; entriesInCurrentSlice++;
} }
// We do this in the per-user loop as we prefer that all messages to the
// same user end up in the same API call to make it easier for the
// server (e.g. only have to send one EDU if a remote user, etc). This
// does mean that if a user has many devices we may go over the desired
// limit, but its not a hard limit so that is fine.
if (entriesInCurrentSlice > maxUsersPerRequest) {
// the current slice is filled up. Start inserting into the next slice
entriesInCurrentSlice = 0;
currentSliceId++;
}
}
return mapSlices;
};
/**
* Splits the user device map into multiple chunks to reduce the number of
* devices we encrypt to per API call.
*
* @private
*
* @param {object} devicesByUser map from userid to list of devices
*
* @return {array<array<object>>} the blocked devices, split into chunks
*/
MegolmEncryption.prototype._splitBlockedDevices = function(devicesByUser) {
const maxUsersPerRequest = 20;
// use an array where the slices of a content map gets stored
let currentSlice = [];
const mapSlices = [currentSlice];
for (const userId of Object.keys(devicesByUser)) {
const userBlockedDevicesToShareWith = devicesByUser[userId];
for (const blockedInfo of userBlockedDevicesToShareWith) {
currentSlice.push({
userId: userId,
blockedInfo: blockedInfo,
});
}
// We do this in the per-user loop as we prefer that all messages to the
// same user end up in the same API call to make it easier for the
// server (e.g. only have to send one EDU if a remote user, etc). This
// does mean that if a user has many devices we may go over the desired
// limit, but its not a hard limit so that is fine.
if (currentSlice.length > maxUsersPerRequest) {
// the current slice is filled up. Start inserting into the next slice
currentSlice = [];
mapSlices.push(currentSlice);
}
}
if (currentSlice.length === 0) {
mapSlices.pop();
} }
return mapSlices; return mapSlices;
}; };
@@ -381,15 +503,15 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
MegolmEncryption.prototype._encryptAndSendKeysToDevices = function( MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
session, chainIndex, userDeviceMap, payload, session, chainIndex, userDeviceMap, payload,
) { ) {
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
const contentMap = {}; const contentMap = {};
const promises = []; const promises = [];
for (let i = 0; i < userDeviceMap.length; i++) { for (let i = 0; i < userDeviceMap.length; i++) {
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
const val = userDeviceMap[i]; const val = userDeviceMap[i];
const userId = val.userId; const userId = val.userId;
const deviceInfo = val.deviceInfo; const deviceInfo = val.deviceInfo;
@@ -427,6 +549,53 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
}); });
}; };
/**
* @private
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*
* @param {array<object>} userDeviceMap list of blocked devices to notify
*
* @param {object} payload fields to include in the notification payload
*
* @return {module:client.Promise} Promise which resolves once the notifications
* for the given userDeviceMap is generated and has been sent.
*/
MegolmEncryption.prototype._sendBlockedNotificationsToDevices = async function(
session, userDeviceMap, payload,
) {
const contentMap = {};
for (const val of userDeviceMap) {
const userId = val.userId;
const blockedInfo = val.blockedInfo;
const deviceInfo = blockedInfo.deviceInfo;
const deviceId = deviceInfo.deviceId;
const message = Object.assign({}, payload);
message.code = blockedInfo.code;
message.reason = blockedInfo.reason;
if (message.code === "m.no_olm") {
delete message.room_id;
delete message.session_id;
}
if (!contentMap[userId]) {
contentMap[userId] = {};
}
contentMap[userId][deviceId] = message;
}
await this._baseApis.sendToDevice("org.matrix.room_key.withheld", contentMap);
// store that we successfully uploaded the keys of the current slice
for (const userId of Object.keys(contentMap)) {
for (const deviceId of Object.keys(contentMap[userId])) {
session.markNotifiedBlockedDevice(userId, deviceId);
}
}
};
/** /**
* Re-shares a megolm session key with devices if the key has already been * Re-shares a megolm session key with devices if the key has already been
* sent to them. * sent to them.
@@ -523,8 +692,14 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
* *
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser * @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices * map from userid to list of devices
*
* @param {array<object>} errorDevices
* array that will be populated with the devices that we can't get an
* olm session for
*/ */
MegolmEncryption.prototype._shareKeyWithDevices = async function(session, devicesByUser) { MegolmEncryption.prototype._shareKeyWithDevices = async function(
session, devicesByUser, errorDevices,
) {
const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId); const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId);
const payload = { const payload = {
type: "m.room_key", type: "m.room_key",
@@ -542,7 +717,7 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
); );
const userDeviceMaps = this._splitUserDeviceMap( const userDeviceMaps = this._splitUserDeviceMap(
session, key.chain_index, devicemap, devicesByUser, session, key.chain_index, devicemap, devicesByUser, errorDevices,
); );
for (let i = 0; i < userDeviceMaps.length; i++) { for (let i = 0; i < userDeviceMaps.length; i++) {
@@ -561,6 +736,42 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
} }
}; };
/**
* Notify blocked devices that they have been blocked.
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*
* @param {object<string, object>} devicesByUser
* map from userid to device ID to blocked data
*/
MegolmEncryption.prototype._notifyBlockedDevices = async function(
session, devicesByUser,
) {
const payload = {
room_id: this._roomId,
session_id: session.sessionId,
algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
};
const userDeviceMaps = this._splitBlockedDevices(devicesByUser);
for (let i = 0; i < userDeviceMaps.length; i++) {
try {
await this._sendBlockedNotificationsToDevices(
session, userDeviceMaps[i], payload,
);
logger.log(`Completed blacklist notification for ${session.sessionId} `
+ `in ${this._roomId} (slice ${i + 1}/${userDeviceMaps.length})`);
} catch (e) {
logger.log(`blacklist notification for ${session.sessionId} in `
+ `${this._roomId} (slice ${i + 1}/${userDeviceMaps.length}) failed`);
throw e;
}
}
};
/** /**
* @inheritdoc * @inheritdoc
* *
@@ -570,42 +781,43 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
* *
* @return {module:client.Promise} Promise which resolves to the new event body * @return {module:client.Promise} Promise which resolves to the new event body
*/ */
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) { MegolmEncryption.prototype.encryptMessage = async function(room, eventType, content) {
const self = this; const self = this;
logger.log(`Starting to encrypt event for ${this._roomId}`); logger.log(`Starting to encrypt event for ${this._roomId}`);
return this._getDevicesInRoom(room).then(function(devicesInRoom) { const [devicesInRoom, blocked] = await this._getDevicesInRoom(room);
// check if any of these devices are not yet known to the user.
// if so, warn the user so they can verify or ignore. // check if any of these devices are not yet known to the user.
// if so, warn the user so they can verify or ignore.
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
self._checkForUnknownDevices(devicesInRoom); self._checkForUnknownDevices(devicesInRoom);
}
return self._ensureOutboundSession(devicesInRoom); const session = await self._ensureOutboundSession(devicesInRoom, blocked);
}).then(function(session) { const payloadJson = {
const payloadJson = { room_id: self._roomId,
room_id: self._roomId, type: eventType,
type: eventType, content: content,
content: content, };
};
const ciphertext = self._olmDevice.encryptGroupMessage( const ciphertext = self._olmDevice.encryptGroupMessage(
session.sessionId, JSON.stringify(payloadJson), session.sessionId, JSON.stringify(payloadJson),
); );
const encryptedContent = { const encryptedContent = {
algorithm: olmlib.MEGOLM_ALGORITHM, algorithm: olmlib.MEGOLM_ALGORITHM,
sender_key: self._olmDevice.deviceCurve25519Key, sender_key: self._olmDevice.deviceCurve25519Key,
ciphertext: ciphertext, ciphertext: ciphertext,
session_id: session.sessionId, session_id: session.sessionId,
// Include our device ID so that recipients can send us a // Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key. // m.new_device message if they don't have our session key.
// XXX: Do we still need this now that m.new_device messages // XXX: Do we still need this now that m.new_device messages
// no longer exist since #483? // no longer exist since #483?
device_id: self._deviceId, device_id: self._deviceId,
}; };
session.useCount++; session.useCount++;
return encryptedContent; return encryptedContent;
});
}; };
/** /**
@@ -643,7 +855,7 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
if (Object.keys(unknownDevices).length) { if (Object.keys(unknownDevices).length) {
// it'd be kind to pass unknownDevices up to the user in this error // it'd be kind to pass unknownDevices up to the user in this error
throw new base.UnknownDeviceError( throw new UnknownDeviceError(
"This room contains unknown devices which have not been verified. " + "This room contains unknown devices which have not been verified. " +
"We strongly recommend you verify them before continuing.", unknownDevices); "We strongly recommend you verify them before continuing.", unknownDevices);
} }
@@ -654,8 +866,11 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
* *
* @param {module:models/room} room * @param {module:models/room} room
* *
* @return {module:client.Promise} Promise which resolves to a map * @return {module:client.Promise} Promise which resolves to an array whose
* from userId to deviceId to deviceInfo * first element is a map from userId to deviceId to deviceInfo indicating
* the devices that messages should be encrypted to, and whose second
* element is a map from userId to deviceId to data indicating the devices
* that are in the room but that have been blocked
*/ */
MegolmEncryption.prototype._getDevicesInRoom = async function(room) { MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
const members = await room.getEncryptionTargetMembers(); const members = await room.getEncryptionTargetMembers();
@@ -676,6 +891,7 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
// using all the device_lists changes and left fields. // using all the device_lists changes and left fields.
// See https://github.com/vector-im/riot-web/issues/2305 for details. // See https://github.com/vector-im/riot-web/issues/2305 for details.
const devices = await this._crypto.downloadKeys(roomMembers, false); const devices = await this._crypto.downloadKeys(roomMembers, false);
const blocked = {};
// remove any blocked devices // remove any blocked devices
for (const userId in devices) { for (const userId in devices) {
if (!devices.hasOwnProperty(userId)) { if (!devices.hasOwnProperty(userId)) {
@@ -690,26 +906,40 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
if (userDevices[deviceId].isBlocked() || if (userDevices[deviceId].isBlocked() ||
(userDevices[deviceId].isUnverified() && isBlacklisting) (userDevices[deviceId].isUnverified() && isBlacklisting)
) { ) {
if (!blocked[userId]) {
blocked[userId] = {};
}
const blockedInfo = userDevices[deviceId].isBlocked()
? {
code: "m.blacklisted",
reason: WITHHELD_MESSAGES["m.blacklisted"],
}
: {
code: "m.unverified",
reason: WITHHELD_MESSAGES["m.unverified"],
};
blockedInfo.deviceInfo = userDevices[deviceId];
blocked[userId][deviceId] = blockedInfo;
delete userDevices[deviceId]; delete userDevices[deviceId];
} }
} }
} }
return devices; return [devices, blocked];
}; };
/** /**
* Megolm decryption implementation * Megolm decryption implementation
* *
* @constructor * @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm} * @extends {module:crypto/algorithms/DecryptionAlgorithm}
* *
* @param {object} params parameters, as per * @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm} * {@link module:crypto/algorithms/DecryptionAlgorithm}
*/ */
function MegolmDecryption(params) { function MegolmDecryption(params) {
base.DecryptionAlgorithm.call(this, params); polyfillSuper(this, DecryptionAlgorithm, params);
// events which we couldn't decrypt due to unknown sessions / indexes: map from // events which we couldn't decrypt due to unknown sessions / indexes: map from
// senderKey|sessionId to Set of MatrixEvents // senderKey|sessionId to Set of MatrixEvents
@@ -718,7 +948,12 @@ function MegolmDecryption(params) {
// this gets stubbed out by the unit tests. // this gets stubbed out by the unit tests.
this.olmlib = olmlib; this.olmlib = olmlib;
} }
utils.inherits(MegolmDecryption, base.DecryptionAlgorithm); utils.inherits(MegolmDecryption, DecryptionAlgorithm);
const PROBLEM_DESCRIPTIONS = {
no_olm: "The sender was unable to establish a secure channel.",
unknown: "The secure channel with the sender was corrupted.",
};
/** /**
* @inheritdoc * @inheritdoc
@@ -736,7 +971,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
if (!content.sender_key || !content.session_id || if (!content.sender_key || !content.session_id ||
!content.ciphertext !content.ciphertext
) { ) {
throw new base.DecryptionError( throw new DecryptionError(
"MEGOLM_MISSING_FIELDS", "MEGOLM_MISSING_FIELDS",
"Missing fields in input", "Missing fields in input",
); );
@@ -756,6 +991,11 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
event.getId(), event.getTs(), event.getId(), event.getTs(),
); );
} catch (e) { } catch (e) {
if (e.name === "DecryptionError") {
// re-throw decryption errors as-is
throw e;
}
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR"; let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') { if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
@@ -764,7 +1004,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX'; errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX';
} }
throw new base.DecryptionError( throw new DecryptionError(
errorCode, errorCode,
e ? e.toString() : "Unknown Error: Error is undefined", { e ? e.toString() : "Unknown Error: Error is undefined", {
session: content.sender_key + '|' + content.session_id, session: content.sender_key + '|' + content.session_id,
@@ -781,7 +1021,29 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
// event is still in the pending list; if not, a retry will have been // event is still in the pending list; if not, a retry will have been
// scheduled, so we needn't send out the request here.) // scheduled, so we needn't send out the request here.)
this._requestKeysForEvent(event); this._requestKeysForEvent(event);
throw new base.DecryptionError(
// See if there was a problem with the olm session at the time the
// event was sent. Use a fuzz factor of 2 minutes.
const problem = await this._olmDevice.sessionMayHaveProblems(
content.sender_key, event.getTs() - 120000,
);
if (problem) {
let problemDescription = PROBLEM_DESCRIPTIONS[problem.type]
|| PROBLEM_DESCRIPTIONS.unknown;
if (problem.fixed) {
problemDescription +=
" Trying to create a new secure channel and re-requesting the keys.";
}
throw new DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
problemDescription,
{
session: content.sender_key + '|' + content.session_id,
},
);
}
throw new DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID", "MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
"The sender's device has not sent us the keys for this message.", "The sender's device has not sent us the keys for this message.",
{ {
@@ -800,7 +1062,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
// (this is somewhat redundant, since the megolm session is scoped to the // (this is somewhat redundant, since the megolm session is scoped to the
// room, so neither the sender nor a MITM can lie about the room_id). // room, so neither the sender nor a MITM can lie about the room_id).
if (payload.room_id !== event.getRoomId()) { if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError( throw new DecryptionError(
"MEGOLM_BAD_ROOM", "MEGOLM_BAD_ROOM",
"Message intended for room " + payload.room_id, "Message intended for room " + payload.room_id,
); );
@@ -836,11 +1098,16 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
*/ */
MegolmDecryption.prototype._addEventToPendingList = function(event) { MegolmDecryption.prototype._addEventToPendingList = function(event) {
const content = event.getWireContent(); const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id; const senderKey = content.sender_key;
if (!this._pendingEvents[k]) { const sessionId = content.session_id;
this._pendingEvents[k] = new Set(); if (!this._pendingEvents[senderKey]) {
this._pendingEvents[senderKey] = new Map();
} }
this._pendingEvents[k].add(event); const senderPendingEvents = this._pendingEvents[senderKey];
if (!senderPendingEvents.has(sessionId)) {
senderPendingEvents.set(sessionId, new Set());
}
senderPendingEvents.get(sessionId).add(event);
}; };
/** /**
@@ -852,14 +1119,20 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
*/ */
MegolmDecryption.prototype._removeEventFromPendingList = function(event) { MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
const content = event.getWireContent(); const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id; const senderKey = content.sender_key;
if (!this._pendingEvents[k]) { const sessionId = content.session_id;
const senderPendingEvents = this._pendingEvents[senderKey];
const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId);
if (!pendingEvents) {
return; return;
} }
this._pendingEvents[k].delete(event); pendingEvents.delete(event);
if (this._pendingEvents[k].size === 0) { if (pendingEvents.size === 0) {
delete this._pendingEvents[k]; senderPendingEvents.delete(senderKey);
}
if (senderPendingEvents.size === 0) {
delete this._pendingEvents[senderKey];
} }
}; };
@@ -963,6 +1236,78 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
}); });
}; };
/**
* @inheritdoc
*
* @param {module:models/event.MatrixEvent} event key event
*/
MegolmDecryption.prototype.onRoomKeyWithheldEvent = async function(event) {
const content = event.getContent();
const senderKey = content.sender_key;
if (content.code === "m.no_olm") {
const sender = event.getSender();
// if the sender says that they haven't been able to establish an olm
// session, let's proactively establish one
// Note: after we record that the olm session has had a problem, we
// trigger retrying decryption for all the messages from the sender's
// key, so that we can update the error message to indicate the olm
// session problem.
if (await this._olmDevice.getSessionIdForDevice(senderKey)) {
// a session has already been established, so we don't need to
// create a new one.
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
this.retryDecryptionFromSender(senderKey);
return;
}
const device = this._crypto._deviceList.getDeviceByIdentityKey(
content.algorithm, senderKey,
);
if (!device) {
logger.info(
"Couldn't find device for identity key " + senderKey +
": not establishing session",
);
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", false);
this.retryDecryptionFromSender(senderKey);
return;
}
await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, {[sender]: [device]}, false,
);
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
await olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._userId,
this._deviceId,
this._olmDevice,
sender,
device,
{type: "m.dummy"},
);
await this._olmDevice.recordSessionProblem(senderKey, "no_olm", true);
this.retryDecryptionFromSender(senderKey);
await this._baseApis.sendToDevice("m.room.encrypted", {
[sender]: {
[device.deviceId]: encryptedContent,
},
});
} else {
await this._olmDevice.addInboundGroupSessionWithheld(
content.room_id, senderKey, content.session_id, content.code,
content.reason,
);
}
};
/** /**
* @inheritdoc * @inheritdoc
*/ */
@@ -1106,13 +1451,20 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
* @return {Boolean} whether all messages were successfully decrypted * @return {Boolean} whether all messages were successfully decrypted
*/ */
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) { MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
const k = senderKey + "|" + sessionId; const senderPendingEvents = this._pendingEvents[senderKey];
const pending = this._pendingEvents[k]; if (!senderPendingEvents) {
return true;
}
const pending = senderPendingEvents.get(sessionId);
if (!pending) { if (!pending) {
return true; return true;
} }
delete this._pendingEvents[k]; pending.delete(sessionId);
if (pending.size === 0) {
this._pendingEvents[senderKey];
}
await Promise.all([...pending].map(async (ev) => { await Promise.all([...pending].map(async (ev) => {
try { try {
@@ -1122,9 +1474,34 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI
} }
})); }));
return !this._pendingEvents[k]; // ev.attemptDecryption will re-add to this._pendingEvents if an event
// couldn't be decrypted
return !((this._pendingEvents[senderKey] || {})[sessionId]);
}; };
base.registerAlgorithm( MegolmDecryption.prototype.retryDecryptionFromSender = async function(senderKey) {
const senderPendingEvents = this._pendingEvents[senderKey];
logger.warn(senderPendingEvents);
if (!senderPendingEvents) {
return true;
}
delete this._pendingEvents[senderKey];
await Promise.all([...senderPendingEvents].map(async ([_sessionId, pending]) => {
await Promise.all([...pending].map(async (ev) => {
try {
logger.warn(ev.getId());
await ev.attemptDecryption(this._crypto);
} catch (e) {
// don't die if something goes wrong
}
}));
}));
return !this._pendingEvents[senderKey];
};
registerAlgorithm(
olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption, olmlib.MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption,
); );

View File

@@ -13,36 +13,42 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* Defines m.olm encryption/decryption * Defines m.olm encryption/decryption
* *
* @module crypto/algorithms/olm * @module crypto/algorithms/olm
*/ */
import logger from '../../logger';
const utils = require("../../utils");
const olmlib = require("../olmlib");
const DeviceInfo = require("../deviceinfo");
const DeviceVerification = DeviceInfo.DeviceVerification;
const base = require("./base"); import {logger} from '../../logger';
import * as utils from "../../utils";
import {polyfillSuper} from "../../utils";
import * as olmlib from "../olmlib";
import {DeviceInfo} from "../deviceinfo";
import {
DecryptionAlgorithm,
DecryptionError,
EncryptionAlgorithm,
registerAlgorithm,
} from "./base";
const DeviceVerification = DeviceInfo.DeviceVerification;
/** /**
* Olm encryption implementation * Olm encryption implementation
* *
* @constructor * @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm} * @extends {module:crypto/algorithms/EncryptionAlgorithm}
* *
* @param {object} params parameters, as per * @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm} * {@link module:crypto/algorithms/EncryptionAlgorithm}
*/ */
function OlmEncryption(params) { function OlmEncryption(params) {
base.EncryptionAlgorithm.call(this, params); polyfillSuper(this, EncryptionAlgorithm, params);
this._sessionPrepared = false; this._sessionPrepared = false;
this._prepPromise = null; this._prepPromise = null;
} }
utils.inherits(OlmEncryption, base.EncryptionAlgorithm); utils.inherits(OlmEncryption, EncryptionAlgorithm);
/** /**
* @private * @private
@@ -143,14 +149,14 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
* Olm decryption implementation * Olm decryption implementation
* *
* @constructor * @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm} * @extends {module:crypto/algorithms/DecryptionAlgorithm}
* @param {object} params parameters, as per * @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm} * {@link module:crypto/algorithms/DecryptionAlgorithm}
*/ */
function OlmDecryption(params) { function OlmDecryption(params) {
base.DecryptionAlgorithm.call(this, params); polyfillSuper(this, DecryptionAlgorithm, params);
} }
utils.inherits(OlmDecryption, base.DecryptionAlgorithm); utils.inherits(OlmDecryption, DecryptionAlgorithm);
/** /**
* @inheritdoc * @inheritdoc
@@ -168,14 +174,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
const ciphertext = content.ciphertext; const ciphertext = content.ciphertext;
if (!ciphertext) { if (!ciphertext) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_MISSING_CIPHERTEXT", "OLM_MISSING_CIPHERTEXT",
"Missing ciphertext", "Missing ciphertext",
); );
} }
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) { if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_NOT_INCLUDED_IN_RECIPIENTS", "OLM_NOT_INCLUDED_IN_RECIPIENTS",
"Not included in recipients", "Not included in recipients",
); );
@@ -186,7 +192,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
try { try {
payloadString = await this._decryptMessage(deviceKey, message); payloadString = await this._decryptMessage(deviceKey, message);
} catch (e) { } catch (e) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_BAD_ENCRYPTED_MESSAGE", "OLM_BAD_ENCRYPTED_MESSAGE",
"Bad Encrypted Message", { "Bad Encrypted Message", {
sender: deviceKey, sender: deviceKey,
@@ -200,14 +206,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// check that we were the intended recipient, to avoid unknown-key attack // check that we were the intended recipient, to avoid unknown-key attack
// https://github.com/vector-im/vector-web/issues/2483 // https://github.com/vector-im/vector-web/issues/2483
if (payload.recipient != this._userId) { if (payload.recipient != this._userId) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_BAD_RECIPIENT", "OLM_BAD_RECIPIENT",
"Message was intented for " + payload.recipient, "Message was intented for " + payload.recipient,
); );
} }
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) { if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_BAD_RECIPIENT_KEY", "OLM_BAD_RECIPIENT_KEY",
"Message not intended for this device", { "Message not intended for this device", {
intended: payload.recipient_keys.ed25519, intended: payload.recipient_keys.ed25519,
@@ -221,7 +227,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// (this check is also provided via the sender's embedded ed25519 key, // (this check is also provided via the sender's embedded ed25519 key,
// which is checked elsewhere). // which is checked elsewhere).
if (payload.sender != event.getSender()) { if (payload.sender != event.getSender()) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_FORWARDED_MESSAGE", "OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, { "Message forwarded from " + payload.sender, {
reported_sender: event.getSender(), reported_sender: event.getSender(),
@@ -231,7 +237,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// Olm events intended for a room have a room_id. // Olm events intended for a room have a room_id.
if (payload.room_id !== event.getRoomId()) { if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError( throw new DecryptionError(
"OLM_BAD_ROOM", "OLM_BAD_ROOM",
"Message intended for room " + payload.room_id, { "Message intended for room " + payload.room_id, {
reported_room: event.room_id, reported_room: event.room_id,
@@ -334,4 +340,4 @@ OlmDecryption.prototype._decryptMessage = async function(
}; };
base.registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption); registerAlgorithm(olmlib.OLM_ALGORITHM, OlmEncryption, OlmDecryption);

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,8 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module crypto/deviceinfo * @module crypto/deviceinfo
@@ -44,7 +43,7 @@ limitations under the License.
* *
* @param {string} deviceId id of the device * @param {string} deviceId id of the device
*/ */
function DeviceInfo(deviceId) { export function DeviceInfo(deviceId) {
// you can't change the deviceId // you can't change the deviceId
Object.defineProperty(this, 'deviceId', { Object.defineProperty(this, 'deviceId', {
enumerable: true, enumerable: true,
@@ -167,5 +166,3 @@ DeviceInfo.DeviceVerification = {
const DeviceVerification = DeviceInfo.DeviceVerification; const DeviceVerification = DeviceInfo.DeviceVerification;
/** */
module.exports = DeviceInfo;

View File

@@ -2,7 +2,7 @@
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018-2019 New Vector Ltd Copyright 2018-2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019-2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -16,44 +16,41 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module crypto * @module crypto
*/ */
const anotherjson = require('another-json'); import anotherjson from "another-json";
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import ReEmitter from '../ReEmitter'; import {ReEmitter} from '../ReEmitter';
import {logger} from '../logger';
import logger from '../logger'; import * as utils from "../utils";
const utils = require("../utils"); import {sleep} from "../utils";
const OlmDevice = require("./OlmDevice"); import {OlmDevice} from "./OlmDevice";
const olmlib = require("./olmlib"); import * as olmlib from "./olmlib";
const algorithms = require("./algorithms"); import {DeviceList} from "./DeviceList";
const DeviceInfo = require("./deviceinfo"); import {DeviceInfo} from "./deviceinfo";
const DeviceVerification = DeviceInfo.DeviceVerification; import * as algorithms from "./algorithms";
const DeviceList = require('./DeviceList').default;
import { import {
CrossSigningInfo, CrossSigningInfo,
UserTrustLevel,
DeviceTrustLevel,
CrossSigningLevel, CrossSigningLevel,
DeviceTrustLevel,
UserTrustLevel,
} from './CrossSigning'; } from './CrossSigning';
import SecretStorage, { SECRET_STORAGE_ALGORITHM_V1 } from './SecretStorage'; import {SECRET_STORAGE_ALGORITHM_V1, SecretStorage} from './SecretStorage';
import {OutgoingRoomKeyRequestManager} from './OutgoingRoomKeyRequestManager';
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; import {ScanQRCode, ShowQRCode} from './verification/QRCode';
import {SAS} from './verification/SAS';
import {ShowQRCode, ScanQRCode} from './verification/QRCode'; import {keyFromPassphrase} from './key_passphrase';
import SAS from './verification/SAS'; import {encodeRecoveryKey} from './recoverykey';
import {sleep} from '../utils'; import {VerificationRequest} from "./verification/request/VerificationRequest";
import { keyFromPassphrase } from './key_passphrase';
import { encodeRecoveryKey } from './recoverykey';
import VerificationRequest from "./verification/request/VerificationRequest";
import {InRoomChannel, InRoomRequests} from "./verification/request/InRoomChannel"; import {InRoomChannel, InRoomRequests} from "./verification/request/InRoomChannel";
import {ToDeviceChannel, ToDeviceRequests} from "./verification/request/ToDeviceChannel"; import {ToDeviceChannel, ToDeviceRequests} from "./verification/request/ToDeviceChannel";
import * as httpApi from "../http-api";
const DeviceVerification = DeviceInfo.DeviceVerification;
const defaultVerificationMethods = { const defaultVerificationMethods = {
[ScanQRCode.NAME]: ScanQRCode, [ScanQRCode.NAME]: ScanQRCode,
@@ -107,7 +104,7 @@ const KEY_BACKUP_KEYS_PER_REQUEST = 200;
* Each element can either be a string from MatrixClient.verificationMethods * Each element can either be a string from MatrixClient.verificationMethods
* or a class that implements a verification method. * or a class that implements a verification method.
*/ */
export default function Crypto(baseApis, sessionStore, userId, deviceId, export function Crypto(baseApis, sessionStore, userId, deviceId,
clientStore, cryptoStore, roomList, verificationMethods) { clientStore, cryptoStore, roomList, verificationMethods) {
this._onDeviceListUserCrossSigningUpdated = this._onDeviceListUserCrossSigningUpdated =
this._onDeviceListUserCrossSigningUpdated.bind(this); this._onDeviceListUserCrossSigningUpdated.bind(this);
@@ -176,6 +173,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
this._deviceKeys = {}; this._deviceKeys = {};
this._globalBlacklistUnverifiedDevices = false; this._globalBlacklistUnverifiedDevices = false;
this._globalErrorOnUnknownDevices = true;
this._outgoingRoomKeyRequestManager = new OutgoingRoomKeyRequestManager( this._outgoingRoomKeyRequestManager = new OutgoingRoomKeyRequestManager(
baseApis, this._deviceId, this._cryptoStore, baseApis, this._deviceId, this._cryptoStore,
@@ -218,7 +216,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
); );
// Assuming no app-supplied callback, default to getting from SSSS. // Assuming no app-supplied callback, default to getting from SSSS.
if (!cryptoCallbacks.getCrossSigningKey) { if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) {
cryptoCallbacks.getCrossSigningKey = async (type) => { cryptoCallbacks.getCrossSigningKey = async (type) => {
return CrossSigningInfo.getFromSecretStorage(type, this._secretStorage); return CrossSigningInfo.getFromSecretStorage(type, this._secretStorage);
}; };
@@ -418,6 +416,25 @@ Crypto.prototype.bootstrapSecretStorage = async function({
// Add an entry for the backup key in SSSS as a 'passthrough' key // Add an entry for the backup key in SSSS as a 'passthrough' key
// (ie. the secret is the key itself). // (ie. the secret is the key itself).
this._secretStorage.storePassthrough('m.megolm_backup.v1', newKeyId); this._secretStorage.storePassthrough('m.megolm_backup.v1', newKeyId);
// if this key backup is trusted, sign it with the cross signing key
// so the key backup can be trusted via cross-signing.
const backupSigStatus = await this.checkKeyBackup(keyBackupInfo);
if (backupSigStatus.trustInfo.usable) {
console.log("Adding cross signing signature to key backup");
await this._crossSigningInfo.signObject(
keyBackupInfo.auth_data, "master",
);
await this._baseApis._http.authedRequest(
undefined, "PUT", "/room_keys/version/" + keyBackupInfo.version,
undefined, keyBackupInfo,
{prefix: httpApi.PREFIX_UNSTABLE},
);
} else {
console.log(
"Key backup is NOT TRUSTED: NOT adding cross signing signature",
);
}
} else { } else {
logger.log("Secret storage default key not found, creating new key"); logger.log("Secret storage default key not found, creating new key");
const keyOptions = await createSecretStorageKey(); const keyOptions = await createSecretStorageKey();
@@ -588,8 +605,8 @@ Crypto.prototype.resetCrossSigningKeys = async function(level, {
/** /**
* Run various follow-up actions after cross-signing keys have changed locally * Run various follow-up actions after cross-signing keys have changed locally
* (either by resetting the keys for the account or bye getting them from secret * (either by resetting the keys for the account or by getting them from secret
* storaoge), such as signing the current device, upgrading device * storage), such as signing the current device, upgrading device
* verifications, etc. * verifications, etc.
*/ */
Crypto.prototype._afterCrossSigningLocalKeyChange = async function() { Crypto.prototype._afterCrossSigningLocalKeyChange = async function() {
@@ -1068,6 +1085,9 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
); );
if (device) { if (device) {
sigInfo.device = device; sigInfo.device = device;
sigInfo.deviceTrust = await this.checkDeviceTrust(
this._userId, sigInfo.deviceId,
);
try { try {
await olmlib.verifySignature( await olmlib.verifySignature(
this._olmDevice, this._olmDevice,
@@ -1095,7 +1115,7 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
ret.usable = ret.sigs.some((s) => { ret.usable = ret.sigs.some((s) => {
return ( return (
s.valid && ( s.valid && (
(s.device && s.device.isVerified()) || (s.device && s.deviceTrust.isVerified()) ||
(s.crossSigningId) (s.crossSigningId)
) )
); );
@@ -1183,6 +1203,29 @@ Crypto.prototype.getGlobalBlacklistUnverifiedDevices = function() {
return this._globalBlacklistUnverifiedDevices; return this._globalBlacklistUnverifiedDevices;
}; };
/**
* Set whether sendMessage in a room with unknown and unverified devices
* should throw an error and not send them message. This has 'Global' for
* symmertry with setGlobalBlacklistUnverifiedDevices but there is currently
* no room-level equivalent for this setting.
*
* This API is currently UNSTABLE and may change or be removed without notice.
*
* @param {boolean} value whether error on unknown devices
*/
Crypto.prototype.setGlobalErrorOnUnknownDevices = function(value) {
this._globalErrorOnUnknownDevices = value;
};
/**
* @return {boolean} whether to error on unknown devices
*
* This API is currently UNSTABLE and may change or be removed without notice.
*/
Crypto.prototype.getGlobalErrorOnUnknownDevices = function() {
return this._globalErrorOnUnknownDevices;
};
/** /**
* Upload the device keys to the homeserver. * Upload the device keys to the homeserver.
* @return {object} A promise that will resolve when the keys are uploaded. * @return {object} A promise that will resolve when the keys are uploaded.
@@ -2381,6 +2424,8 @@ Crypto.prototype._onToDeviceEvent = function(event) {
this._secretStorage._onRequestReceived(event); this._secretStorage._onRequestReceived(event);
} else if (event.getType() === "m.secret.send") { } else if (event.getType() === "m.secret.send") {
this._secretStorage._onSecretReceived(event); this._secretStorage._onSecretReceived(event);
} else if (event.getType() === "org.matrix.room_key.withheld") {
this._onRoomKeyWithheldEvent(event);
} else if (event.getContent().transaction_id) { } else if (event.getContent().transaction_id) {
this._onKeyVerificationMessage(event); this._onKeyVerificationMessage(event);
} else if (event.getContent().msgtype === "m.bad.encrypted") { } else if (event.getContent().msgtype === "m.bad.encrypted") {
@@ -2420,6 +2465,42 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
alg.onRoomKeyEvent(event); alg.onRoomKeyEvent(event);
}; };
/**
* Handle a key withheld event
*
* @private
* @param {module:models/event.MatrixEvent} event key withheld event
*/
Crypto.prototype._onRoomKeyWithheldEvent = function(event) {
const content = event.getContent();
if ((content.code !== "m.no_olm" && (!content.room_id || !content.session_id))
|| !content.algorithm || !content.sender_key) {
logger.error("key withheld event is missing fields");
return;
}
logger.info(
`Got room key withheld event from ${event.getSender()} (${content.sender_key}) `
+ `for ${content.algorithm}/${content.room_id}/${content.session_id} `
+ `with reason ${content.code} (${content.reason})`,
);
const alg = this._getRoomDecryptor(content.room_id, content.algorithm);
if (alg.onRoomKeyWithheldEvent) {
alg.onRoomKeyWithheldEvent(event);
}
if (!content.room_id) {
// retry decryption for all events sent by the sender_key. This will
// update the events to show a message indicating that the olm session was
// wedged.
const roomDecryptors = this._getRoomDecryptors(content.algorithm);
for (const decryptor of roomDecryptors) {
decryptor.retryDecryptionFromSender(content.sender_key);
}
}
};
/** /**
* Handle a general key verification event. * Handle a general key verification event.
* *
@@ -2536,6 +2617,16 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
const algorithm = content.algorithm; const algorithm = content.algorithm;
const deviceKey = content.sender_key; const deviceKey = content.sender_key;
// retry decryption for all events sent by the sender_key. This will
// update the events to show a message indicating that the olm session was
// wedged.
const retryDecryption = () => {
const roomDecryptors = this._getRoomDecryptors(olmlib.MEGOLM_ALGORITHM);
for (const decryptor of roomDecryptors) {
decryptor.retryDecryptionFromSender(deviceKey);
}
};
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) { if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
return; return;
} }
@@ -2549,6 +2640,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"New session already forced with device " + sender + ":" + deviceKey + "New session already forced with device " + sender + ":" + deviceKey +
" at " + lastNewSessionForced + ": not forcing another", " at " + lastNewSessionForced + ": not forcing another",
); );
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
return; return;
} }
@@ -2562,6 +2655,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"Couldn't find device for identity key " + deviceKey + "Couldn't find device for identity key " + deviceKey +
": not re-establishing session", ": not re-establishing session",
); );
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", false);
retryDecryption();
return; return;
} }
const devicesByUser = {}; const devicesByUser = {};
@@ -2593,6 +2688,9 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
{type: "m.dummy"}, {type: "m.dummy"},
); );
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
await this._baseApis.sendToDevice("m.room.encrypted", { await this._baseApis.sendToDevice("m.room.encrypted", {
[sender]: { [sender]: {
[device.deviceId]: encryptedContent, [device.deviceId]: encryptedContent,
@@ -2874,6 +2972,24 @@ Crypto.prototype._getRoomDecryptor = function(roomId, algorithm) {
}; };
/**
* Get all the room decryptors for a given encryption algorithm.
*
* @param {string} algorithm The encryption algorithm
*
* @return {array} An array of room decryptors
*/
Crypto.prototype._getRoomDecryptors = function(algorithm) {
const decryptors = [];
for (const d of Object.values(this._roomDecryptors)) {
if (algorithm in d) {
decryptors.push(d[algorithm]);
}
}
return decryptors;
};
/** /**
* sign the given object with our ed25519 key * sign the given object with our ed25519 key
* *

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { randomString } from '../randomstring'; import {randomString} from '../randomstring';
const DEFAULT_ITERATIONS = 500000; const DEFAULT_ITERATIONS = 500000;

View File

@@ -22,25 +22,24 @@ limitations under the License.
* Utilities common to olm encryption algorithms * Utilities common to olm encryption algorithms
*/ */
const anotherjson = require('another-json'); import {logger} from '../logger';
import * as utils from "../utils";
import logger from '../logger'; import anotherjson from "another-json";
const utils = require("../utils");
/** /**
* matrix algorithm tag for olm * matrix algorithm tag for olm
*/ */
module.exports.OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2"; export const OLM_ALGORITHM = "m.olm.v1.curve25519-aes-sha2";
/** /**
* matrix algorithm tag for megolm * matrix algorithm tag for megolm
*/ */
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2"; export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
/** /**
* matrix algorithm tag for megolm backups * matrix algorithm tag for megolm backups
*/ */
module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2"; export const MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2";
/** /**
@@ -59,7 +58,7 @@ module.exports.MEGOLM_BACKUP_ALGORITHM = "m.megolm_backup.v1.curve25519-aes-sha2
* Returns a promise which resolves (to undefined) when the payload * Returns a promise which resolves (to undefined) when the payload
* has been encrypted into `resultsObject` * has been encrypted into `resultsObject`
*/ */
module.exports.encryptMessageForDevice = async function( export async function encryptMessageForDevice(
resultsObject, resultsObject,
ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice, ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice,
payloadFields, payloadFields,
@@ -112,7 +111,7 @@ module.exports.encryptMessageForDevice = async function(
resultsObject[deviceKey] = await olmDevice.encryptMessage( resultsObject[deviceKey] = await olmDevice.encryptMessage(
deviceKey, sessionId, JSON.stringify(payload), deviceKey, sessionId, JSON.stringify(payload),
); );
}; }
/** /**
* Try to make sure we have established olm sessions for the given devices. * Try to make sure we have established olm sessions for the given devices.
@@ -131,7 +130,7 @@ module.exports.encryptMessageForDevice = async function(
* an Object mapping from userId to deviceId to * an Object mapping from userId to deviceId to
* {@link module:crypto~OlmSessionResult} * {@link module:crypto~OlmSessionResult}
*/ */
module.exports.ensureOlmSessionsForDevices = async function( export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force, olmDevice, baseApis, devicesByUser, force,
) { ) {
const devicesWithoutSession = [ const devicesWithoutSession = [
@@ -263,12 +262,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
await Promise.all(promises); await Promise.all(promises);
return result; return result;
}; }
async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) { async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
const deviceId = deviceInfo.deviceId; const deviceId = deviceInfo.deviceId;
try { try {
await _verifySignature( await verifySignature(
olmDevice, oneTimeKey, userId, deviceId, olmDevice, oneTimeKey, userId, deviceId,
deviceInfo.getFingerprint(), deviceInfo.getFingerprint(),
); );
@@ -314,7 +313,7 @@ async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceIn
* Returns a promise which resolves (to undefined) if the the signature is good, * Returns a promise which resolves (to undefined) if the the signature is good,
* or rejects with an Error if it is bad. * or rejects with an Error if it is bad.
*/ */
const _verifySignature = module.exports.verifySignature = async function( export async function verifySignature(
olmDevice, obj, signingUserId, signingDeviceId, signingKey, olmDevice, obj, signingUserId, signingDeviceId, signingKey,
) { ) {
const signKeyId = "ed25519:" + signingDeviceId; const signKeyId = "ed25519:" + signingDeviceId;
@@ -335,7 +334,7 @@ const _verifySignature = module.exports.verifySignature = async function(
olmDevice.verifySignature( olmDevice.verifySignature(
signingKey, json, signature, signingKey, json, signature,
); );
}; }
/** /**
* Sign a JSON object using public key cryptography * Sign a JSON object using public key cryptography
@@ -347,7 +346,7 @@ const _verifySignature = module.exports.verifySignature = async function(
* @param {string} pubkey The public key (ignored if key is a seed) * @param {string} pubkey The public key (ignored if key is a seed)
* @returns {string} the signature for the object * @returns {string} the signature for the object
*/ */
module.exports.pkSign = function(obj, key, userId, pubkey) { export function pkSign(obj, key, userId, pubkey) {
let createdKey = false; let createdKey = false;
if (key instanceof Uint8Array) { if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning(); const keyObj = new global.Olm.PkSigning();
@@ -371,7 +370,7 @@ module.exports.pkSign = function(obj, key, userId, pubkey) {
key.free(); key.free();
} }
} }
}; }
/** /**
* Verify a signed JSON object * Verify a signed JSON object
@@ -379,7 +378,7 @@ module.exports.pkSign = function(obj, key, userId, pubkey) {
* @param {string} pubkey The public key to use to verify * @param {string} pubkey The public key to use to verify
* @param {string} userId The user ID who signed the object * @param {string} userId The user ID who signed the object
*/ */
module.exports.pkVerify = function(obj, pubkey, userId) { export function pkVerify(obj, pubkey, userId) {
const keyId = "ed25519:" + pubkey; const keyId = "ed25519:" + pubkey;
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) { if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
throw new Error("No signature"); throw new Error("No signature");
@@ -397,22 +396,22 @@ module.exports.pkVerify = function(obj, pubkey, userId) {
if (unsigned) obj.unsigned = unsigned; if (unsigned) obj.unsigned = unsigned;
util.free(); util.free();
} }
}; }
/** /**
* Encode a typed array of uint8 as base64. * Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode. * @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64. * @return {string} The base64.
*/ */
module.exports.encodeBase64 = function(uint8Array) { export function encodeBase64(uint8Array) {
return Buffer.from(uint8Array).toString("base64"); return Buffer.from(uint8Array).toString("base64");
}; }
/** /**
* Decode a base64 string to a typed array of uint8. * Decode a base64 string to a typed array of uint8.
* @param {string} base64 The base64 to decode. * @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data. * @return {Uint8Array} The decoded data.
*/ */
module.exports.decodeBase64 = function(base64) { export function decodeBase64(base64) {
return Buffer.from(base64, "base64"); return Buffer.from(base64, "base64");
}; }

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,10 +16,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../logger'; import {logger} from '../../logger';
import utils from '../../utils'; import * as utils from "../../utils";
export const VERSION = 7; export const VERSION = 9;
/** /**
* Implementation of a CryptoStore which is backed by an existing * Implementation of a CryptoStore which is backed by an existing
@@ -79,7 +80,7 @@ export class Backend {
`enqueueing key request for ${requestBody.room_id} / ` + `enqueueing key request for ${requestBody.room_id} / ` +
requestBody.session_id, requestBody.session_id,
); );
txn.oncomplete = () => { resolve(request); }; txn.oncomplete = () => {resolve(request);};
const store = txn.objectStore("outgoingRoomKeyRequests"); const store = txn.objectStore("outgoingRoomKeyRequests");
store.add(request); store.add(request);
}); });
@@ -425,17 +426,107 @@ export class Backend {
}); });
} }
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const txn = this._db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
objectStore.put({
deviceKey,
type,
fixed,
time: Date.now(),
});
return promiseifyTxn(txn);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
let result;
const txn = this._db.transaction("session_problems", "readwrite");
const objectStore = txn.objectStore("session_problems");
const index = objectStore.index("deviceKey");
const req = index.getAll(deviceKey);
req.onsuccess = (event) => {
const problems = req.result;
if (!problems.length) {
result = null;
return;
}
problems.sort((a, b) => {
return a.time - b.time;
});
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
result = Object.assign({}, problem, {fixed: lastProblem.fixed});
return;
}
}
if (lastProblem.fixed) {
result = null;
} else {
result = lastProblem;
}
};
await promiseifyTxn(txn);
return result;
}
// FIXME: we should probably prune this when devices get deleted
async filterOutNotifiedErrorDevices(devices) {
const txn = this._db.transaction("notified_error_devices", "readwrite");
const objectStore = txn.objectStore("notified_error_devices");
const ret = [];
await Promise.all(devices.map((device) => {
return new Promise((resolve) => {
const {userId, deviceInfo} = device;
const getReq = objectStore.get([userId, deviceInfo.deviceId]);
getReq.onsuccess = function() {
if (!getReq.result) {
objectStore.put({userId, deviceId: deviceInfo.deviceId});
ret.push(device);
}
resolve();
};
});
}));
return ret;
}
// Inbound group sessions // Inbound group sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
let session = false;
let withheld = false;
const objectStore = txn.objectStore("inbound_group_sessions"); const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.get([senderCurve25519Key, sessionId]); const getReq = objectStore.get([senderCurve25519Key, sessionId]);
getReq.onsuccess = function() { getReq.onsuccess = function() {
try { try {
if (getReq.result) { if (getReq.result) {
func(getReq.result.session); session = getReq.result.session;
} else { } else {
func(null); session = null;
}
if (withheld !== false) {
func(session, withheld);
}
} catch (e) {
abortWithException(txn, e);
}
};
const withheldObjectStore = txn.objectStore("inbound_group_sessions_withheld");
const withheldGetReq = withheldObjectStore.get([senderCurve25519Key, sessionId]);
withheldGetReq.onsuccess = function() {
try {
if (withheldGetReq.result) {
withheld = withheldGetReq.result.session;
} else {
withheld = null;
}
if (session !== false) {
func(session, withheld);
} }
} catch (e) { } catch (e) {
abortWithException(txn, e); abortWithException(txn, e);
@@ -499,6 +590,15 @@ export class Backend {
}); });
} }
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
const objectStore = txn.objectStore("inbound_group_sessions_withheld");
objectStore.put({
senderCurve25519Key, sessionId, session: sessionData,
});
}
getEndToEndDeviceData(txn, func) { getEndToEndDeviceData(txn, func) {
const objectStore = txn.objectStore("device_data"); const objectStore = txn.objectStore("device_data");
const getReq = objectStore.get("-"); const getReq = objectStore.get("-");
@@ -662,6 +762,21 @@ export function upgradeDatabase(db, oldVersion) {
keyPath: ["senderCurve25519Key", "sessionId"], keyPath: ["senderCurve25519Key", "sessionId"],
}); });
} }
if (oldVersion < 8) {
db.createObjectStore("inbound_group_sessions_withheld", {
keyPath: ["senderCurve25519Key", "sessionId"],
});
}
if (oldVersion < 9) {
const problemsStore = db.createObjectStore("session_problems", {
keyPath: ["deviceKey", "time"],
});
problemsStore.createIndex("deviceKey", "deviceKey");
db.createObjectStore("notified_error_devices", {
keyPath: ["userId", "deviceId"],
});
}
// Expand as needed. // Expand as needed.
} }

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,9 +16,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../logger'; import {logger} from '../../logger';
import LocalStorageCryptoStore from './localStorage-crypto-store'; import {LocalStorageCryptoStore} from './localStorage-crypto-store';
import MemoryCryptoStore from './memory-crypto-store'; import {MemoryCryptoStore} from './memory-crypto-store';
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend'; import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
import {InvalidCryptoStoreError} from '../../errors'; import {InvalidCryptoStoreError} from '../../errors';
import * as IndexedDBHelpers from "../../indexeddb-helpers"; import * as IndexedDBHelpers from "../../indexeddb-helpers";
@@ -34,7 +35,7 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers";
* *
* @implements {module:crypto/store/base~CryptoStore} * @implements {module:crypto/store/base~CryptoStore}
*/ */
export default class IndexedDBCryptoStore { export class IndexedDBCryptoStore {
/** /**
* Create a new IndexedDBCryptoStore * Create a new IndexedDBCryptoStore
* *
@@ -104,7 +105,10 @@ export default class IndexedDBCryptoStore {
// we can fall back to a different backend. // we can fall back to a different backend.
return backend.doTxn( return backend.doTxn(
'readonly', 'readonly',
[IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
],
(txn) => { (txn) => {
backend.getEndToEndInboundGroupSession('', '', txn, () => {}); backend.getEndToEndInboundGroupSession('', '', txn, () => {});
}).then(() => { }).then(() => {
@@ -405,6 +409,24 @@ export default class IndexedDBCryptoStore {
}); });
} }
storeEndToEndSessionProblem(deviceKey, type, fixed) {
return this._backendPromise.then(async (backend) => {
await backend.storeEndToEndSessionProblem(deviceKey, type, fixed);
});
}
getEndToEndSessionProblem(deviceKey, timestamp) {
return this._backendPromise.then(async (backend) => {
return await backend.getEndToEndSessionProblem(deviceKey, timestamp);
});
}
filterOutNotifiedErrorDevices(devices) {
return this._backendPromise.then(async (backend) => {
return await backend.filterOutNotifiedErrorDevices(devices);
});
}
// Inbound group sessions // Inbound group sessions
/** /**
@@ -471,6 +493,16 @@ export default class IndexedDBCryptoStore {
}); });
} }
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
this._backendPromise.then(backend => {
backend.storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
);
});
}
// End-to-end device tracking // End-to-end device tracking
/** /**
@@ -607,6 +639,8 @@ export default class IndexedDBCryptoStore {
IndexedDBCryptoStore.STORE_ACCOUNT = 'account'; IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
IndexedDBCryptoStore.STORE_SESSIONS = 'sessions'; IndexedDBCryptoStore.STORE_SESSIONS = 'sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions'; IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS = 'inbound_group_sessions';
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD
= 'inbound_group_sessions_withheld';
IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data'; IndexedDBCryptoStore.STORE_DEVICE_DATA = 'device_data';
IndexedDBCryptoStore.STORE_ROOMS = 'rooms'; IndexedDBCryptoStore.STORE_ROOMS = 'rooms';
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup'; IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017, 2018 New Vector Ltd Copyright 2017, 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -14,8 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../logger'; import {logger} from '../../logger';
import MemoryCryptoStore from './memory-crypto-store'; import {MemoryCryptoStore} from './memory-crypto-store';
/** /**
* Internal module. Partial localStorage backed storage for e2e. * Internal module. Partial localStorage backed storage for e2e.
@@ -30,8 +31,10 @@ import MemoryCryptoStore from './memory-crypto-store';
const E2E_PREFIX = "crypto."; const E2E_PREFIX = "crypto.";
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account"; const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys"; const KEY_CROSS_SIGNING_KEYS = E2E_PREFIX + "cross_signing_keys";
const KEY_NOTIFIED_ERROR_DEVICES = E2E_PREFIX + "notified_error_devices";
const KEY_DEVICE_DATA = E2E_PREFIX + "device_data"; const KEY_DEVICE_DATA = E2E_PREFIX + "device_data";
const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/"; const KEY_INBOUND_SESSION_PREFIX = E2E_PREFIX + "inboundgroupsessions/";
const KEY_INBOUND_SESSION_WITHHELD_PREFIX = E2E_PREFIX + "inboundgroupsessions.withheld/";
const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/"; const KEY_ROOMS_PREFIX = E2E_PREFIX + "rooms/";
const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup"; const KEY_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
@@ -39,10 +42,18 @@ function keyEndToEndSessions(deviceKey) {
return E2E_PREFIX + "sessions/" + deviceKey; return E2E_PREFIX + "sessions/" + deviceKey;
} }
function keyEndToEndSessionProblems(deviceKey) {
return E2E_PREFIX + "session.problems/" + deviceKey;
}
function keyEndToEndInboundGroupSession(senderKey, sessionId) { function keyEndToEndInboundGroupSession(senderKey, sessionId) {
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId; return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
} }
function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId;
}
function keyEndToEndRoomsPrefix(roomId) { function keyEndToEndRoomsPrefix(roomId) {
return KEY_ROOMS_PREFIX + roomId; return KEY_ROOMS_PREFIX + roomId;
} }
@@ -50,7 +61,7 @@ function keyEndToEndRoomsPrefix(roomId) {
/** /**
* @implements {module:crypto/store/base~CryptoStore} * @implements {module:crypto/store/base~CryptoStore}
*/ */
export default class LocalStorageCryptoStore extends MemoryCryptoStore { export class LocalStorageCryptoStore extends MemoryCryptoStore {
constructor(webStore) { constructor(webStore) {
super(); super();
this.store = webStore; this.store = webStore;
@@ -122,13 +133,71 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
); );
} }
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
problems.push({type, fixed, time: Date.now()});
problems.sort((a, b) => {
return a.time - b.time;
});
setJsonItem(this.store, key, problems);
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
const key = keyEndToEndSessionProblems(deviceKey);
const problems = getJsonItem(this.store, key) || [];
if (!problems.length) {
return null;
}
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
return Object.assign({}, problem, {fixed: lastProblem.fixed});
}
}
if (lastProblem.fixed) {
return null;
} else {
return lastProblem;
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices =
getJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES) || {};
const ret = [];
for (const device of devices) {
const {userId, deviceInfo} = device;
if (userId in notifiedErrorDevices) {
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
ret.push(device);
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
}
} else {
ret.push(device);
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
}
}
setJsonItem(this.store, KEY_NOTIFIED_ERROR_DEVICES, notifiedErrorDevices);
return ret;
}
// Inbound Group Sessions // Inbound Group Sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
func(getJsonItem( func(
this.store, getJsonItem(
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId), this.store,
)); keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
),
getJsonItem(
this.store,
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
),
);
} }
getAllEndToEndInboundGroupSessions(txn, func) { getAllEndToEndInboundGroupSessions(txn, func) {
@@ -170,6 +239,16 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
); );
} }
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
setJsonItem(
this.store,
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
sessionData,
);
}
getEndToEndDeviceData(txn, func) { getEndToEndDeviceData(txn, func) {
func(getJsonItem( func(getJsonItem(
this.store, KEY_DEVICE_DATA, this.store, KEY_DEVICE_DATA,

View File

@@ -1,6 +1,7 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -15,8 +16,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../logger'; import {logger} from '../../logger';
import utils from '../../utils'; import * as utils from "../../utils";
/** /**
* Internal module. in-memory storage for e2e. * Internal module. in-memory storage for e2e.
@@ -27,7 +28,7 @@ import utils from '../../utils';
/** /**
* @implements {module:crypto/store/base~CryptoStore} * @implements {module:crypto/store/base~CryptoStore}
*/ */
export default class MemoryCryptoStore { export class MemoryCryptoStore {
constructor() { constructor() {
this._outgoingRoomKeyRequests = []; this._outgoingRoomKeyRequests = [];
this._account = null; this._account = null;
@@ -35,8 +36,13 @@ export default class MemoryCryptoStore {
// Map of {devicekey -> {sessionId -> session pickle}} // Map of {devicekey -> {sessionId -> session pickle}}
this._sessions = {}; this._sessions = {};
// Map of {devicekey -> array of problems}
this._sessionProblems = {};
// Map of {userId -> deviceId -> true}
this._notifiedErrorDevices = {};
// Map of {senderCurve25519Key+'/'+sessionId -> session data object} // Map of {senderCurve25519Key+'/'+sessionId -> session data object}
this._inboundGroupSessions = {}; this._inboundGroupSessions = {};
this._inboundGroupSessionsWithheld = {};
// Opaque device data object // Opaque device data object
this._deviceData = null; this._deviceData = null;
// roomId -> Opaque roomInfo object // roomId -> Opaque roomInfo object
@@ -273,10 +279,61 @@ export default class MemoryCryptoStore {
deviceSessions[sessionId] = sessionInfo; deviceSessions[sessionId] = sessionInfo;
} }
async storeEndToEndSessionProblem(deviceKey, type, fixed) {
const problems = this._sessionProblems[deviceKey]
= this._sessionProblems[deviceKey] || [];
problems.push({type, fixed, time: Date.now()});
problems.sort((a, b) => {
return a.time - b.time;
});
}
async getEndToEndSessionProblem(deviceKey, timestamp) {
const problems = this._sessionProblems[deviceKey] || [];
if (!problems.length) {
return null;
}
const lastProblem = problems[problems.length - 1];
for (const problem of problems) {
if (problem.time > timestamp) {
return Object.assign({}, problem, {fixed: lastProblem.fixed});
}
}
if (lastProblem.fixed) {
return null;
} else {
return lastProblem;
}
}
async filterOutNotifiedErrorDevices(devices) {
const notifiedErrorDevices = this._notifiedErrorDevices;
const ret = [];
for (const device of devices) {
const {userId, deviceInfo} = device;
if (userId in notifiedErrorDevices) {
if (!(deviceInfo.deviceId in notifiedErrorDevices[userId])) {
ret.push(device);
notifiedErrorDevices[userId][deviceInfo.deviceId] = true;
}
} else {
ret.push(device);
notifiedErrorDevices[userId] = {[deviceInfo.deviceId]: true };
}
}
return ret;
}
// Inbound Group Sessions // Inbound Group Sessions
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) { getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
func(this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] || null); const k = senderCurve25519Key+'/'+sessionId;
func(
this._inboundGroupSessions[k] || null,
this._inboundGroupSessionsWithheld[k] || null,
);
} }
getAllEndToEndInboundGroupSessions(txn, func) { getAllEndToEndInboundGroupSessions(txn, func) {
@@ -306,6 +363,13 @@ export default class MemoryCryptoStore {
this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData; this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
} }
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
const k = senderCurve25519Key+'/'+sessionId;
this._inboundGroupSessionsWithheld[k] = sessionData;
}
// Device Data // Device Data
getEndToEndDeviceData(txn, func) { getEndToEndDeviceData(txn, func) {

View File

@@ -21,13 +21,13 @@ limitations under the License.
import {MatrixEvent} from '../../models/event'; import {MatrixEvent} from '../../models/event';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import logger from '../../logger'; import {logger} from '../../logger';
import DeviceInfo from '../deviceinfo'; import {DeviceInfo} from '../deviceinfo';
import {newTimeoutError} from "./Error"; import {newTimeoutError} from "./Error";
const timeoutException = new Error("Verification timed out"); const timeoutException = new Error("Verification timed out");
export default class VerificationBase extends EventEmitter { export class VerificationBase extends EventEmitter {
/** /**
* Base class for verification methods. * Base class for verification methods.
* *

View File

@@ -19,11 +19,11 @@ limitations under the License.
* @module crypto/verification/QRCode * @module crypto/verification/QRCode
*/ */
import Base from "./Base"; import {VerificationBase as Base} from "./Base";
import { import {
errorFactory, errorFactory,
newUserCancelledError,
newKeyMismatchError, newKeyMismatchError,
newUserCancelledError,
newUserMismatchError, newUserMismatchError,
} from './Error'; } from './Error';

View File

@@ -19,14 +19,14 @@ limitations under the License.
* @module crypto/verification/SAS * @module crypto/verification/SAS
*/ */
import Base from "./Base"; import {VerificationBase as Base} from "./Base";
import anotherjson from 'another-json'; import anotherjson from 'another-json';
import { import {
errorFactory, errorFactory,
newUserCancelledError,
newUnknownMethodError,
newKeyMismatchError,
newInvalidMessageError, newInvalidMessageError,
newKeyMismatchError,
newUnknownMethodError,
newUserCancelledError,
} from './Error'; } from './Error';
const EVENTS = [ const EVENTS = [
@@ -108,7 +108,7 @@ const emojiMapping = [
["✏️", "pencil"], // 43 ["✏️", "pencil"], // 43
["📎", "paperclip"], // 44 ["📎", "paperclip"], // 44
["✂️", "scissors"], // 45 ["✂️", "scissors"], // 45
["🔒", "padlock"], // 46 ["🔒", "lock"], // 46
["🔑", "key"], // 47 ["🔑", "key"], // 47
["🔨", "hammer"], // 48 ["🔨", "hammer"], // 48
["☎️", "telephone"], // 49 ["☎️", "telephone"], // 49
@@ -185,7 +185,11 @@ function intersection(anArray, aSet) {
* @alias module:crypto/verification/SAS * @alias module:crypto/verification/SAS
* @extends {module:crypto/verification/Base} * @extends {module:crypto/verification/Base}
*/ */
export default class SAS extends Base { export class SAS extends Base {
static get NAME() {
return "m.sas.v1";
}
get events() { get events() {
return EVENTS; return EVENTS;
} }
@@ -426,5 +430,3 @@ export default class SAS extends Base {
}); });
} }
} }
SAS.NAME = "m.sas.v1";

View File

@@ -15,11 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import VerificationRequest, { import {
VerificationRequest,
REQUEST_TYPE, REQUEST_TYPE,
READY_TYPE, READY_TYPE,
START_TYPE, START_TYPE,
} from "./VerificationRequest"; } from "./VerificationRequest";
const MESSAGE_TYPE = "m.room.message"; const MESSAGE_TYPE = "m.room.message";
const M_REFERENCE = "m.reference"; const M_REFERENCE = "m.reference";
const M_RELATES_TO = "m.relates_to"; const M_RELATES_TO = "m.relates_to";

View File

@@ -15,24 +15,19 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { randomString } from '../../../randomstring'; import {randomString} from '../../../randomstring';
import logger from '../../../logger'; import {logger} from '../../../logger';
import VerificationRequest, { import {
CANCEL_TYPE,
PHASE_STARTED, PHASE_STARTED,
PHASE_READY, PHASE_READY,
REQUEST_TYPE, REQUEST_TYPE,
READY_TYPE, READY_TYPE,
START_TYPE, START_TYPE,
CANCEL_TYPE, VerificationRequest,
} from "./VerificationRequest"; } from "./VerificationRequest";
import {errorFromEvent, newUnexpectedMessageError} from "../Error";
import { import {MatrixEvent} from "../../../models/event";
newUnexpectedMessageError,
errorFromEvent,
} from "../Error";
const MatrixEvent = require("../../../models/event").MatrixEvent;
/** /**
* A key verification channel that sends verification events over to_device messages. * A key verification channel that sends verification events over to_device messages.

View File

@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import logger from '../../../logger'; import {logger} from '../../../logger';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import { import {
newUnknownMethodError,
newUnexpectedMessageError,
errorFromEvent,
errorFactory, errorFactory,
errorFromEvent,
newUnexpectedMessageError,
newUnknownMethodError,
} from "../Error"; } from "../Error";
// the recommended amount of time before a verification request // the recommended amount of time before a verification request
@@ -56,7 +56,7 @@ export const PHASE_DONE = 6;
* send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`. * send and receive verification events are put in `InRoomChannel` or `ToDeviceChannel`.
* @event "change" whenever the state of the request object has changed. * @event "change" whenever the state of the request object has changed.
*/ */
export default class VerificationRequest extends EventEmitter { export class VerificationRequest extends EventEmitter {
constructor(channel, verificationMethods, client) { constructor(channel, verificationMethods, client) {
super(); super();
this.channel = channel; this.channel = channel;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module filter-component * @module filter-component
*/ */
@@ -45,7 +46,7 @@ function _matches_wildcard(actual_value, filter_value) {
* @constructor * @constructor
* @param {Object} filter_json the definition of this filter JSON, e.g. { 'contains_url': true } * @param {Object} filter_json the definition of this filter JSON, e.g. { 'contains_url': true }
*/ */
function FilterComponent(filter_json) { export function FilterComponent(filter_json) {
this.filter_json = filter_json; this.filter_json = filter_json;
this.types = filter_json.types || null; this.types = filter_json.types || null;
@@ -141,6 +142,3 @@ FilterComponent.prototype.filter = function(events) {
FilterComponent.prototype.limit = function() { FilterComponent.prototype.limit = function() {
return this.filter_json.limit !== undefined ? this.filter_json.limit : 10; return this.filter_json.limit !== undefined ? this.filter_json.limit : 10;
}; };
/** The FilterComponent class */
module.exports = FilterComponent;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,12 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module filter * @module filter
*/ */
const FilterComponent = require("./filter-component"); import {FilterComponent} from "./filter-component";
/** /**
* @param {Object} obj * @param {Object} obj
@@ -45,7 +46,7 @@ function setProp(obj, keyNesting, val) {
* @prop {string} userId The user ID of the filter * @prop {string} userId The user ID of the filter
* @prop {?string} filterId The filter ID * @prop {?string} filterId The filter ID
*/ */
function Filter(userId, filterId) { export function Filter(userId, filterId) {
this.userId = userId; this.userId = userId;
this.filterId = filterId; this.filterId = filterId;
this.definition = {}; this.definition = {};
@@ -198,6 +199,3 @@ Filter.fromJson = function(userId, filterId, jsonObj) {
filter.setDefinition(jsonObj); filter.setDefinition(jsonObj);
return filter; return filter;
}; };
/** The Filter class */
module.exports = Filter;

View File

@@ -14,20 +14,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* This is an internal module. See {@link MatrixHttpApi} for the public class. * This is an internal module. See {@link MatrixHttpApi} for the public class.
* @module http-api * @module http-api
*/ */
const parseContentType = require('content-type').parse;
const utils = require("./utils"); import {parse as parseContentType} from "content-type";
import logger from './logger'; import * as utils from "./utils";
import {logger} from './logger';
// we use our own implementation of setTimeout, so that if we get suspended in // we use our own implementation of setTimeout, so that if we get suspended in
// the middle of a /sync, we cancel the sync as soon as we awake, rather than // the middle of a /sync, we cancel the sync as soon as we awake, rather than
// waiting for the delay to elapse. // waiting for the delay to elapse.
const callbacks = require("./realtime-callbacks"); import * as callbacks from "./realtime-callbacks";
/* /*
TODO: TODO:
@@ -38,27 +38,27 @@ TODO:
/** /**
* A constant representing the URI path for release 0 of the Client-Server HTTP API. * A constant representing the URI path for release 0 of the Client-Server HTTP API.
*/ */
module.exports.PREFIX_R0 = "/_matrix/client/r0"; export const PREFIX_R0 = "/_matrix/client/r0";
/** /**
* A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs. * A constant representing the URI path for as-yet unspecified Client-Server HTTP APIs.
*/ */
module.exports.PREFIX_UNSTABLE = "/_matrix/client/unstable"; export const PREFIX_UNSTABLE = "/_matrix/client/unstable";
/** /**
* URI path for v1 of the the identity API * URI path for v1 of the the identity API
*/ */
module.exports.PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1"; export const PREFIX_IDENTITY_V1 = "/_matrix/identity/api/v1";
/** /**
* URI path for the v2 identity API * URI path for the v2 identity API
*/ */
module.exports.PREFIX_IDENTITY_V2 = "/_matrix/identity/v2"; export const PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
/** /**
* URI path for the media repo API * URI path for the media repo API
*/ */
module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0"; export const PREFIX_MEDIA_R0 = "/_matrix/media/r0";
/** /**
* Construct a MatrixHttpApi. * Construct a MatrixHttpApi.
@@ -85,16 +85,16 @@ module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use * @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
* Authorization header instead of query param to send the access token to the server. * Authorization header instead of query param to send the access token to the server.
*/ */
module.exports.MatrixHttpApi = function MatrixHttpApi(event_emitter, opts) { export function MatrixHttpApi(event_emitter, opts) {
utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]); utils.checkObjectHasKeys(opts, ["baseUrl", "request", "prefix"]);
opts.onlyData = opts.onlyData || false; opts.onlyData = opts.onlyData || false;
this.event_emitter = event_emitter; this.event_emitter = event_emitter;
this.opts = opts; this.opts = opts;
this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader); this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader);
this.uploads = []; this.uploads = [];
}; }
module.exports.MatrixHttpApi.prototype = { MatrixHttpApi.prototype = {
/** /**
* Sets the baase URL for the identity server * Sets the baase URL for the identity server
* @param {string} url The new base url * @param {string} url The new base url
@@ -698,7 +698,7 @@ module.exports.MatrixHttpApi.prototype = {
if (req && req.abort) { if (req && req.abort) {
req.abort(); req.abort();
} }
defer.reject(new module.exports.MatrixError({ defer.reject(new MatrixError({
error: "Locally timed out waiting for a response", error: "Locally timed out waiting for a response",
errcode: "ORG.MATRIX.JSSDK_TIMEOUT", errcode: "ORG.MATRIX.JSSDK_TIMEOUT",
timeout: localTimeoutMs, timeout: localTimeoutMs,
@@ -836,7 +836,7 @@ function parseErrorResponse(response, body) {
if (contentType) { if (contentType) {
if (contentType.type === 'application/json') { if (contentType.type === 'application/json') {
const jsonBody = typeof(body) === 'object' ? body : JSON.parse(body); const jsonBody = typeof(body) === 'object' ? body : JSON.parse(body);
err = new module.exports.MatrixError(jsonBody); err = new MatrixError(jsonBody);
} else if (contentType.type === 'text/plain') { } else if (contentType.type === 'text/plain') {
err = new Error(`Server returned ${httpStatus} error: ${body}`); err = new Error(`Server returned ${httpStatus} error: ${body}`);
} }
@@ -891,13 +891,12 @@ function getResponseContentType(response) {
* @prop {Object} data The raw Matrix error JSON used to construct this object. * @prop {Object} data The raw Matrix error JSON used to construct this object.
* @prop {integer} httpStatus The numeric HTTP status code given * @prop {integer} httpStatus The numeric HTTP status code given
*/ */
module.exports.MatrixError = function MatrixError(errorJson) { export function MatrixError(errorJson) {
errorJson = errorJson || {}; errorJson = errorJson || {};
this.errcode = errorJson.errcode; this.errcode = errorJson.errcode;
this.name = errorJson.errcode || "Unknown error code"; this.name = errorJson.errcode || "Unknown error code";
this.message = errorJson.error || "Unknown message"; this.message = errorJson.error || "Unknown message";
this.data = errorJson; this.data = errorJson;
}; }
module.exports.MatrixError.prototype = Object.create(Error.prototype); MatrixError.prototype = Object.create(Error.prototype);
/** */ MatrixError.prototype.constructor = MatrixError;
module.exports.MatrixError.prototype.constructor = module.exports.MatrixError;

24
src/index.ts Normal file
View File

@@ -0,0 +1,24 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as matrixcs from "./matrix";
import * as utils from "./utils";
matrixcs.request(import("request"));
utils.runPolyfills();
export * from "./matrix";
export default matrixcs;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 Vector Creations Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -20,5 +21,5 @@ limitations under the License.
*/ */
/** The {@link module:indexeddb-store-worker~IndexedDBStoreWorker} class. */ /** The {@link module:indexeddb-store-worker~IndexedDBStoreWorker} class. */
module.exports.IndexedDBStoreWorker = require("./store/indexeddb-store-worker.js"); export {IndexedDBStoreWorker} from "./store/indexeddb-store-worker";

View File

@@ -15,13 +15,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** @module interactive-auth */ /** @module interactive-auth */
const url = require("url");
const utils = require("./utils"); import url from "url";
import logger from './logger'; import * as utils from "./utils";
import {logger} from './logger';
const EMAIL_STAGE_TYPE = "m.login.email.identity"; const EMAIL_STAGE_TYPE = "m.login.email.identity";
const MSISDN_STAGE_TYPE = "m.login.msisdn"; const MSISDN_STAGE_TYPE = "m.login.msisdn";
@@ -103,7 +102,7 @@ const MSISDN_STAGE_TYPE = "m.login.msisdn";
* attemptAuth promise. * attemptAuth promise.
* *
*/ */
function InteractiveAuth(opts) { export function InteractiveAuth(opts) {
this._matrixClient = opts.matrixClient; this._matrixClient = opts.matrixClient;
this._data = opts.authData || {}; this._data = opts.authData || {};
this._requestCallback = opts.doRequest; this._requestCallback = opts.doRequest;
@@ -510,6 +509,3 @@ InteractiveAuth.prototype = {
}, },
}; };
/** */
module.exports = InteractiveAuth;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2018 André Jaenisch Copyright 2018 André Jaenisch
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -17,7 +18,8 @@ limitations under the License.
/** /**
* @module logger * @module logger
*/ */
const log = require("loglevel");
import log from "loglevel";
// This is to demonstrate, that you can use any namespace you want. // This is to demonstrate, that you can use any namespace you want.
// Namespaces allow you to turn on/off the logging for specific parts of the // Namespaces allow you to turn on/off the logging for specific parts of the
@@ -25,12 +27,12 @@ const log = require("loglevel");
// An idea would be to control this via an environment variable (on Node.js). // An idea would be to control this via an environment variable (on Node.js).
// See https://www.npmjs.com/package/debug to see how this could be implemented // See https://www.npmjs.com/package/debug to see how this could be implemented
// Part of #332 is introducing a logging library in the first place. // Part of #332 is introducing a logging library in the first place.
const DEFAULT_NAME_SPACE = "matrix"; const DEFAULT_NAMESPACE = "matrix";
const logger = log.getLogger(DEFAULT_NAME_SPACE);
logger.setLevel(log.levels.DEBUG);
/** /**
* Drop-in replacement for <code>console</code> using {@link https://www.npmjs.com/package/loglevel|loglevel}. * Drop-in replacement for <code>console</code> using {@link https://www.npmjs.com/package/loglevel|loglevel}.
* Can be tailored down to specific use cases if needed. * Can be tailored down to specific use cases if needed.
*/ */
module.exports = logger; export const logger = log.getLogger(DEFAULT_NAMESPACE);
logger.setLevel(log.levels.DEBUG);

View File

@@ -15,129 +15,66 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** The {@link module:ContentHelpers} object */ import {MemoryCryptoStore} from "./crypto/store/memory-crypto-store";
module.exports.ContentHelpers = require("./content-helpers"); import {MemoryStore} from "./store/memory";
/** The {@link module:models/event.MatrixEvent|MatrixEvent} class. */ import {MatrixScheduler} from "./scheduler";
module.exports.MatrixEvent = require("./models/event").MatrixEvent; import {MatrixClient} from "./client";
/** The {@link module:models/event.EventStatus|EventStatus} enum. */
module.exports.EventStatus = require("./models/event").EventStatus;
/** The {@link module:store/memory.MemoryStore|MemoryStore} class. */
module.exports.MemoryStore = require("./store/memory").MemoryStore;
/**
* The {@link module:store/memory.MemoryStore|MemoryStore} class was previously
* exported as `MatrixInMemoryStore`, so this is preserved for SDK consumers.
* @deprecated Prefer `MemoryStore` going forward.
*/
module.exports.MatrixInMemoryStore = module.exports.MemoryStore;
/** The {@link module:store/indexeddb.IndexedDBStore|IndexedDBStore} class. */
module.exports.IndexedDBStore = require("./store/indexeddb").IndexedDBStore;
/** The {@link module:store/indexeddb.IndexedDBStoreBackend|IndexedDBStoreBackend} class. */
module.exports.IndexedDBStoreBackend = require("./store/indexeddb").IndexedDBStoreBackend;
/** The {@link module:sync-accumulator.SyncAccumulator|SyncAccumulator} class. */
module.exports.SyncAccumulator = require("./sync-accumulator");
/** The {@link module:http-api.MatrixHttpApi|MatrixHttpApi} class. */
module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi;
/** The {@link module:http-api.MatrixError|MatrixError} class. */
module.exports.MatrixError = require("./http-api").MatrixError;
/** The {@link module:errors.InvalidStoreError|InvalidStoreError} class. */
module.exports.InvalidStoreError = require("./errors").InvalidStoreError;
/** The {@link module:client.MatrixClient|MatrixClient} class. */
module.exports.MatrixClient = require("./client").MatrixClient;
/** The {@link module:models/room|Room} class. */
module.exports.Room = require("./models/room");
/** The {@link module:models/group|Group} class. */
module.exports.Group = require("./models/group");
/** The {@link module:models/event-timeline~EventTimeline} class. */
module.exports.EventTimeline = require("./models/event-timeline");
/** The {@link module:models/event-timeline-set~EventTimelineSet} class. */
module.exports.EventTimelineSet = require("./models/event-timeline-set");
/** The {@link module:models/room-member|RoomMember} class. */
module.exports.RoomMember = require("./models/room-member");
/** The {@link module:models/room-state~RoomState|RoomState} class. */
module.exports.RoomState = require("./models/room-state");
/** The {@link module:models/user~User|User} class. */
module.exports.User = require("./models/user");
/** The {@link module:scheduler~MatrixScheduler|MatrixScheduler} class. */
module.exports.MatrixScheduler = require("./scheduler");
/** The {@link module:store/session/webstorage~WebStorageSessionStore|
* WebStorageSessionStore} class. <strong>Work in progress; unstable.</strong> */
module.exports.WebStorageSessionStore = require("./store/session/webstorage");
/** True if crypto libraries are being used on this client. */
module.exports.CRYPTO_ENABLED = require("./client").CRYPTO_ENABLED;
/** {@link module:content-repo|ContentRepo} utility functions. */
module.exports.ContentRepo = require("./content-repo");
/** The {@link module:filter~Filter|Filter} class. */
module.exports.Filter = require("./filter");
/** The {@link module:timeline-window~TimelineWindow} class. */
module.exports.TimelineWindow = require("./timeline-window").TimelineWindow;
/** The {@link module:interactive-auth} class. */
module.exports.InteractiveAuth = require("./interactive-auth");
/** The {@link module:auto-discovery|AutoDiscovery} class. */
module.exports.AutoDiscovery = require("./autodiscovery").AutoDiscovery;
module.exports.SERVICE_TYPES = require('./service-types').SERVICE_TYPES; export * from "./client";
export * from "./http-api";
module.exports.MemoryCryptoStore = export * from "./autodiscovery";
require("./crypto/store/memory-crypto-store").default; export * from "./sync-accumulator";
module.exports.IndexedDBCryptoStore = export * from "./errors";
require("./crypto/store/indexeddb-crypto-store").default; export * from "./models/event";
export * from "./models/room";
/** export * from "./models/group";
* Create a new Matrix Call. export * from "./models/event-timeline";
* @function export * from "./models/event-timeline-set";
* @param {module:client.MatrixClient} client The MatrixClient instance to use. export * from "./models/room-member";
* @param {string} roomId The room the call is in. export * from "./models/room-state";
* @return {module:webrtc/call~MatrixCall} The Matrix call or null if the browser export * from "./models/user";
* does not support WebRTC. export * from "./scheduler";
*/ export * from "./filter";
module.exports.createNewMatrixCall = require("./webrtc/call").createNewMatrixCall; export * from "./timeline-window";
export * from "./interactive-auth";
export * from "./service-types";
/** export * from "./store/memory";
* Set a preferred audio output device to use for MatrixCalls export * from "./store/indexeddb";
* @function export * from "./store/session/webstorage";
* @param {string=} deviceId the identifier for the device export * from "./crypto/store/memory-crypto-store";
* undefined treated as unset export * from "./crypto/store/indexeddb-crypto-store";
*/ export * from "./content-repo";
module.exports.setMatrixCallAudioOutput = require('./webrtc/call').setAudioOutput; export const ContentHelpers = import("./content-helpers");
/** export {
* Set a preferred audio input device to use for MatrixCalls createNewMatrixCall,
* @function setAudioOutput as setMatrixCallAudioOutput,
* @param {string=} deviceId the identifier for the device setAudioInput as setMatrixCallAudioInput,
* undefined treated as unset setVideoInput as setMatrixCallVideoInput,
*/ } from "./webrtc/call";
module.exports.setMatrixCallAudioInput = require('./webrtc/call').setAudioInput;
/**
* Set a preferred video input device to use for MatrixCalls
* @function
* @param {string=} deviceId the identifier for the device
* undefined treated as unset
*/
module.exports.setMatrixCallVideoInput = require('./webrtc/call').setVideoInput;
// expose the underlying request object so different environments can use // expose the underlying request object so different environments can use
// different request libs (e.g. request or browser-request) // different request libs (e.g. request or browser-request)
let request; let requestInstance;
/** /**
* The function used to perform HTTP requests. Only use this if you want to * The function used to perform HTTP requests. Only use this if you want to
* use a different HTTP library, e.g. Angular's <code>$http</code>. This should * use a different HTTP library, e.g. Angular's <code>$http</code>. This should
* be set prior to calling {@link createClient}. * be set prior to calling {@link createClient}.
* @param {requestFunction} r The request function to use. * @param {requestFunction} r The request function to use.
*/ */
module.exports.request = function(r) { export function request(r) {
request = r; requestInstance = r;
}; }
/** /**
* Return the currently-set request function. * Return the currently-set request function.
* @return {requestFunction} The current request function. * @return {requestFunction} The current request function.
*/ */
module.exports.getRequest = function() { export function getRequest() {
return request; return requestInstance;
}; }
/** /**
* Apply wrapping code around the request function. The wrapper function is * Apply wrapping code around the request function. The wrapper function is
@@ -145,15 +82,15 @@ module.exports.getRequest = function() {
* previous value, along with the options and callback arguments. * previous value, along with the options and callback arguments.
* @param {requestWrapperFunction} wrapper The wrapping function. * @param {requestWrapperFunction} wrapper The wrapping function.
*/ */
module.exports.wrapRequest = function(wrapper) { export function wrapRequest(wrapper) {
const origRequest = request; const origRequest = requestInstance;
request = function(options, callback) { requestInstance = function(options, callback) {
return wrapper(origRequest, options, callback); return wrapper(origRequest, options, callback);
}; };
}; }
let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore; let cryptoStoreFactory = () => new MemoryCryptoStore;
/** /**
* Configure a different factory to be used for creating crypto stores * Configure a different factory to be used for creating crypto stores
@@ -161,9 +98,9 @@ let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore;
* @param {Function} fac a function which will return a new * @param {Function} fac a function which will return a new
* {@link module:crypto.store.base~CryptoStore}. * {@link module:crypto.store.base~CryptoStore}.
*/ */
module.exports.setCryptoStoreFactory = function(fac) { export function setCryptoStoreFactory(fac) {
cryptoStoreFactory = fac; cryptoStoreFactory = fac;
}; }
/** /**
* Construct a Matrix Client. Similar to {@link module:client~MatrixClient} * Construct a Matrix Client. Similar to {@link module:client~MatrixClient}
@@ -188,20 +125,20 @@ module.exports.setCryptoStoreFactory = function(fac) {
* @see {@link module:client~MatrixClient} for the full list of options for * @see {@link module:client~MatrixClient} for the full list of options for
* <code>opts</code>. * <code>opts</code>.
*/ */
module.exports.createClient = function(opts) { export function createClient(opts) {
if (typeof opts === "string") { if (typeof opts === "string") {
opts = { opts = {
"baseUrl": opts, "baseUrl": opts,
}; };
} }
opts.request = opts.request || request; opts.request = opts.request || requestInstance;
opts.store = opts.store || new module.exports.MemoryStore({ opts.store = opts.store || new MemoryStore({
localStorage: global.localStorage, localStorage: global.localStorage,
}); });
opts.scheduler = opts.scheduler || new module.exports.MatrixScheduler(); opts.scheduler = opts.scheduler || new MatrixScheduler();
opts.cryptoStore = opts.cryptoStore || cryptoStoreFactory(); opts.cryptoStore = opts.cryptoStore || cryptoStoreFactory();
return new module.exports.MatrixClient(opts); return new MatrixClient(opts);
}; }
/** /**
* The request function interface for performing HTTP requests. This matches the * The request function interface for performing HTTP requests. This matches the

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module models/event-context * @module models/event-context
@@ -33,7 +33,7 @@ limitations under the License.
* *
* @constructor * @constructor
*/ */
function EventContext(ourEvent) { export function EventContext(ourEvent) {
this._timeline = [ourEvent]; this._timeline = [ourEvent];
this._ourEventIndex = 0; this._ourEventIndex = 0;
this._paginateTokens = {b: null, f: null}; this._paginateTokens = {b: null, f: null};
@@ -113,7 +113,3 @@ EventContext.prototype.addEvents = function(events, atStart) {
} }
}; };
/**
* The EventContext class
*/
module.exports = EventContext;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,16 +14,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module models/event-timeline-set * @module models/event-timeline-set
*/ */
const EventEmitter = require("events").EventEmitter;
const utils = require("../utils"); import {EventEmitter} from "events";
const EventTimeline = require("./event-timeline"); import {EventTimeline} from "./event-timeline";
import {EventStatus} from "./event"; import {EventStatus} from "./event";
import logger from '../logger'; import * as utils from "../utils";
import Relations from './relations'; import {logger} from '../logger';
import {Relations} from './relations';
// var DEBUG = false; // var DEBUG = false;
const DEBUG = true; const DEBUG = true;
@@ -71,7 +73,7 @@ if (DEBUG) {
* via `getRelationsForEvent`. * via `getRelationsForEvent`.
* This feature is currently unstable and the API may change without notice. * This feature is currently unstable and the API may change without notice.
*/ */
function EventTimelineSet(room, opts) { export function EventTimelineSet(room, opts) {
this.room = room; this.room = room;
this._timelineSupport = Boolean(opts.timelineSupport); this._timelineSupport = Boolean(opts.timelineSupport);
@@ -817,11 +819,6 @@ EventTimelineSet.prototype.aggregateRelations = function(event) {
} }
}; };
/**
* The EventTimelineSet class.
*/
module.exports = EventTimelineSet;
/** /**
* Fires whenever the timeline in a room is updated. * Fires whenever the timeline in a room is updated.
* @event module:client~MatrixClient#"Room.timeline" * @event module:client~MatrixClient#"Room.timeline"

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2016, 2017 OpenMarket Ltd Copyright 2016, 2017 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,13 +14,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* @module models/event-timeline * @module models/event-timeline
*/ */
const RoomState = require("./room-state"); import {RoomState} from "./room-state";
/** /**
* Construct a new EventTimeline * Construct a new EventTimeline
@@ -41,7 +41,7 @@ const RoomState = require("./room-state");
* @param {EventTimelineSet} eventTimelineSet the set of timelines this is part of * @param {EventTimelineSet} eventTimelineSet the set of timelines this is part of
* @constructor * @constructor
*/ */
function EventTimeline(eventTimelineSet) { export function EventTimeline(eventTimelineSet) {
this._eventTimelineSet = eventTimelineSet; this._eventTimelineSet = eventTimelineSet;
this._roomId = eventTimelineSet.room ? eventTimelineSet.room.roomId : null; this._roomId = eventTimelineSet.room ? eventTimelineSet.room.roomId : null;
this._events = []; this._events = [];
@@ -396,8 +396,3 @@ EventTimeline.prototype.toString = function() {
return this._name; return this._name;
}; };
/**
* The EventTimeline class
*/
module.exports = EventTimeline;

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -13,7 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
"use strict";
/** /**
* This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for * This is an internal module. See {@link MatrixEvent} and {@link RoomEvent} for
@@ -22,15 +22,15 @@ limitations under the License.
*/ */
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import utils from '../utils.js'; import * as utils from '../utils.js';
import logger from '../logger'; import {logger} from '../logger';
/** /**
* Enum for event statuses. * Enum for event statuses.
* @readonly * @readonly
* @enum {string} * @enum {string}
*/ */
const EventStatus = { export const EventStatus = {
/** The event was not sent and will no longer be retried. */ /** The event was not sent and will no longer be retried. */
NOT_SENT: "not_sent", NOT_SENT: "not_sent",
@@ -48,7 +48,6 @@ const EventStatus = {
/** The event was cancelled before it was successfully sent. */ /** The event was cancelled before it was successfully sent. */
CANCELLED: "cancelled", CANCELLED: "cancelled",
}; };
module.exports.EventStatus = EventStatus;
const interns = {}; const interns = {};
function intern(str) { function intern(str) {
@@ -81,7 +80,7 @@ function intern(str) {
* that getDirectionalContent() will return event.content and not event.prev_content. * that getDirectionalContent() will return event.content and not event.prev_content.
* Default: true. <strong>This property is experimental and may change.</strong> * Default: true. <strong>This property is experimental and may change.</strong>
*/ */
module.exports.MatrixEvent = function MatrixEvent( export const MatrixEvent = function(
event, event,
) { ) {
// intern the values of matrix events to force share strings and reduce the // intern the values of matrix events to force share strings and reduce the
@@ -162,10 +161,10 @@ module.exports.MatrixEvent = function MatrixEvent(
*/ */
this.verificationRequest = null; this.verificationRequest = null;
}; };
utils.inherits(module.exports.MatrixEvent, EventEmitter); utils.inherits(MatrixEvent, EventEmitter);
utils.extend(module.exports.MatrixEvent.prototype, { utils.extend(MatrixEvent.prototype, {
/** /**
* Get the event_id for this event. * Get the event_id for this event.

View File

@@ -1,5 +1,6 @@
/* /*
Copyright 2017 New Vector Ltd Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@@ -17,9 +18,9 @@ limitations under the License.
/** /**
* @module models/group * @module models/group
*/ */
const EventEmitter = require("events").EventEmitter;
const utils = require("../utils"); import * as utils from "../utils";
import {EventEmitter} from "events";
/** /**
* Construct a new Group. * Construct a new Group.
@@ -34,7 +35,7 @@ const utils = require("../utils");
* to the group, if myMembership is 'invite'. * to the group, if myMembership is 'invite'.
* @prop {string} inviter.userId The user ID of the inviter * @prop {string} inviter.userId The user ID of the inviter
*/ */
function Group(groupId) { export function Group(groupId) {
this.groupId = groupId; this.groupId = groupId;
this.name = null; this.name = null;
this.avatarUrl = null; this.avatarUrl = null;
@@ -70,8 +71,6 @@ Group.prototype.setInviter = function(inviter) {
this.inviter = inviter; this.inviter = inviter;
}; };
module.exports = Group;
/** /**
* Fires whenever a group's profile information is updated. * Fires whenever a group's profile information is updated.
* This means the 'name' and 'avatarUrl' properties. * This means the 'name' and 'avatarUrl' properties.

View File

@@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import EventEmitter from 'events'; import {EventEmitter} from 'events';
import { EventStatus } from '../models/event'; import {EventStatus} from '../models/event';
/** /**
* A container for relation events that supports easy access to common ways of * A container for relation events that supports easy access to common ways of
@@ -25,7 +25,7 @@ import { EventStatus } from '../models/event';
* The typical way to get one of these containers is via * The typical way to get one of these containers is via
* EventTimelineSet#getRelationsForEvent. * EventTimelineSet#getRelationsForEvent.
*/ */
export default class Relations extends EventEmitter { export class Relations extends EventEmitter {
/** /**
* @param {String} relationType * @param {String} relationType
* The type of relation involved, such as "m.annotation", "m.reference", * The type of relation involved, such as "m.annotation", "m.reference",

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