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": [
["@babel/preset-env", {
"targets": {
"browsers": [
"last 2 versions"
],
"node": 12
"node": 10
},
"modules": "commonjs"
}],

View File

@@ -1,11 +1,35 @@
steps:
- label: ":eslint: Lint"
- label: ":eslint: JS Lint"
command:
- "yarn install"
- "yarn lint"
- "yarn lint:js"
plugins:
- 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"
command:
@@ -13,7 +37,7 @@ steps:
- "yarn test"
plugins:
- docker#v3.0.1:
image: "node:10"
image: "node:12"
- label: "📃 Docs"
command:
@@ -21,7 +45,7 @@ steps:
- "yarn gendoc"
plugins:
- docker#v3.0.1:
image: "node:10"
image: "node:12"
- 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)
================================================================================================
[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
~~~~~~~~~~
The code-style for matrix-js-sdk is not formally documented, but contributors
are encouraged to read the code style document for matrix-react-sdk
The js-sdk aims to target TypeScript/ES6. All new files should be written in
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>`_)
and follow the principles set out there.

View File

@@ -9,12 +9,16 @@ Quickstart
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
``<script>`` to your page. There will be a global variable ``matrixcs``
attached to ``window`` through which you can access the SDK. See below for how to
include libolm to enable end-to-end-encryption.
The browser bundle supports recent versions of browsers. Typically this is ES2015
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.
In Node.js
@@ -22,13 +26,18 @@ In Node.js
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``
```javascript
var sdk = require("matrix-js-sdk");
var client = sdk.createClient("https://matrix.org");
import * as sdk from "matrix-js-sdk";
const client = sdk.createClient("https://matrix.org");
client.publicRooms(function(err, data) {
console.log("Public Rooms: %s", JSON.stringify(data));
});
@@ -59,7 +68,7 @@ client.once('sync', function(state, prevState, res) {
To send a message:
```javascript
var content = {
const content = {
"body": "message 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:
```javascript
var sdk = require("matrix-js-sdk");
var myUserId = "@example:localhost";
var myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
var matrixClient = sdk.createClient({
import * as sdk from "matrix-js-sdk";
const myUserId = "@example:localhost";
const myAccessToken = "QGV4YW1wbGU6bG9jYWxob3N0.qPEvLuYfNBjxikiCjP";
const matrixClient = sdk.createClient({
baseUrl: "http://localhost:8008",
accessToken: myAccessToken,
userId: myUserId
@@ -247,11 +256,11 @@ Output:
```javascript
matrixClient.on("RoomState.members", function(event, state, member) {
var room = matrixClient.getRoom(state.roomId);
const room = matrixClient.getRoom(state.roomId);
if (!room) {
return;
}
var memberList = state.getMembers();
const memberList = state.getMembers();
console.log(room.name);
console.log(Array(room.name.length + 1).join("=")); // underline
for (var i = 0; i < memberList.length; i++) {
@@ -351,11 +360,6 @@ To build a browser version from scratch when developing::
$ yarn build
```
To constantly do builds when files are modified (using ``watchify``)::
```
$ yarn watch
```
To run tests (Jasmine)::
```
$ 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");
var client = matrixcs.createClient("http://matrix.org");

View File

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

View File

@@ -1,4 +1,3 @@
"use strict";
console.log("Loading browser sdk");
var BASE_URL = "https://matrix.org";
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",
"version": "2.4.6",
"version": "3.0.0",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
"test:watch": "jest spec/ --coverage --testEnvironment node --watch",
"test": "jest spec/ --coverage --testEnvironment node",
"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\"",
"start": "echo THIS IS FOR LEGACY PURPOSES ONLY. && babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"dist": "echo 'This is for the release script so it can make assets (browser bundle).' && yarn build",
"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",
"dist": "yarn build",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
"lint": "eslint --max-warnings 93 src spec",
"prepare": "yarn clean && yarn build && git rev-parse HEAD > git-revision.txt"
"build": "yarn clean && git rev-parse HEAD > git-revision.txt && yarn build:compile && yarn build:compile-browser && yarn build:minify-browser && yarn build:types",
"build:types": "tsc --emitDeclarationOnly",
"build:compile": "babel -d lib --verbose --extensions \".ts,.js\" src",
"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",
"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": {
"type": "git",
@@ -24,28 +26,23 @@
"keywords": [
"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",
"license": "Apache-2.0",
"files": [
".babelrc",
".eslintrc.js",
"spec/.eslintrc.js",
"lib",
"src",
"git-revision.txt",
"CHANGELOG.md",
"CONTRIBUTING.rst",
"LICENSE",
"README.md",
"RELEASING.md",
"examples",
"git-hooks",
"git-revision.txt",
"index.js",
"browser-index.js",
"lib",
"package.json",
"release.sh",
"spec",
"src"
"release.sh"
],
"dependencies": {
"another-json": "^0.2.0",
@@ -69,11 +66,12 @@
"@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"@types/node": "12",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babelify": "^10.0.0",
"better-docs": "^1.4.7",
"browserify": "^16.2.3",
"browserify-shim": "^3.8.13",
"browserify": "^16.5.0",
"eslint": "^5.12.0",
"eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.3.0",
@@ -84,15 +82,12 @@
"matrix-mock-request": "^1.2.3",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"rimraf": "^3.0.0",
"source-map-support": "^0.5.13",
"sourceify": "^1.0.0",
"terser": "^4.3.8",
"typescript": "^3.7.3",
"watchify": "^3.11.1"
"terser": "^4.4.3",
"tsify": "^4.0.1",
"tslint": "^5.20.1",
"typescript": "^3.7.3"
},
"browserify": {
"transform": [
"sourceify"
]
"jest": {
"testEnvironment": "node"
}
}

View File

@@ -1,6 +1,6 @@
#!/bin/bash
#
# Script to perform a release of matrix-js-sdk.
# Script to perform a release of matrix-js-sdk and downstream projects.
#
# Requires:
# 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
# npm; typically installed by Node.js
# 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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@@ -18,7 +19,7 @@ limitations under the License.
* A mock implementation of the webstorage api
* @constructor
*/
function MockStorageApi() {
export function MockStorageApi() {
this.data = {};
this.keys = [];
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.
*/
"use strict";
// load olm before the sdk if possible
import './olm-loader';
import sdk from '..';
import testUtils from './test-utils';
import MockHttpBackend from 'matrix-mock-request';
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
import logger from '../lib/logger';
import {LocalStorageCryptoStore} from '../src/crypto/store/localStorage-crypto-store';
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
@@ -39,16 +39,16 @@ import logger from '../lib/logger';
* session store. If undefined, we will create a MockStorageApi.
* @param {object} options additional options to pass to the client
*/
export default function TestClient(
export function TestClient(
userId, deviceId, accessToken, sessionStoreBackend, options,
) {
this.userId = userId;
this.deviceId = deviceId;
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();
@@ -65,7 +65,7 @@ export default function TestClient(
this.cryptoStore = new LocalStorageCryptoStore(sessionStoreBackend);
options.cryptoStore = this.cryptoStore;
}
this.client = sdk.createClient(options);
this.client = createClient(options);
this.deviceKeys = null;
this.oneTimeKeys = {};
@@ -97,7 +97,7 @@ TestClient.prototype.start = function() {
return Promise.all([
this.httpBackend.flushAllExpected(),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
logger.log(this + ': started');
});
@@ -225,7 +225,7 @@ TestClient.prototype.flushSync = function() {
logger.log(`${this}: flushSync`);
return Promise.all([
this.httpBackend.flush('/sync', 1),
testUtils.syncPromise(this.client),
syncPromise(this.client),
]).then(() => {
logger.log(`${this}: flushSync completed`);
});

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations 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");
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.
*/
import TestClient from '../TestClient';
import testUtils from '../test-utils';
import logger from '../../lib/logger';
import {TestClient} from '../TestClient';
import * as testUtils from '../test-utils';
import {logger} from '../../src/logger';
const ROOM_ID = "!room:id";

View File

@@ -2,6 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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");
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`.
*/
"use strict";
import 'source-map-support/register';
// load olm before the sdk if possible
import '../olm-loader';
const sdk = require("../..");
const utils = require("../../lib/utils");
const testUtils = require("../test-utils");
const TestClient = require('../TestClient').default;
import logger from '../../lib/logger';
import {logger} from '../../src/logger';
import * as testUtils from "../test-utils";
import * as utils from "../../src/utils";
import {TestClient} from "../TestClient";
import {CRYPTO_ENABLED} from "../../src/client";
let aliTestClient;
const roomId = "!room:localhost";
@@ -400,7 +398,7 @@ function firstSync(testClient) {
describe("MatrixClient crypto", function() {
if (!sdk.CRYPTO_ENABLED) {
if (!CRYPTO_ENABLED) {
return;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
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.
@@ -14,14 +15,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
const anotherjson = require('another-json');
const utils = require('../../lib/utils');
const testUtils = require('../test-utils');
const TestClient = require('../TestClient').default;
import logger from '../../lib/logger';
import anotherjson from "another-json";
import * as utils from "../../src/utils";
import * as testUtils from "../test-utils";
import {TestClient} from "../TestClient";
import {logger} from "../../src/logger";
const ROOM_ID = "!room:id";
@@ -617,6 +615,9 @@ describe("megolm", function() {
).respond(200, {
event_id: '$event_id',
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
@@ -714,6 +715,9 @@ describe("megolm", function() {
event_id: '$event_id',
};
});
aliceTestClient.httpBackend.when(
'PUT', '/sendToDevice/org.matrix.room_key.withheld/',
).respond(200, {});
return Promise.all([
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test2'),

View File

@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector creations Ltd
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.
@@ -14,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import logger from '../lib/logger';
import {logger} from '../src/logger';
// try to load the olm library.
try {

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
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.
@@ -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
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 * as sdk from "../../src";
import {AutoDiscovery} from "../../src/autodiscovery";
describe("AutoDiscovery", function() {
let httpBackend = null;

View File

@@ -1,6 +1,4 @@
"use strict";
import 'source-map-support/register';
const ContentRepo = require("../../lib/content-repo");
import {getHttpUriForMxc, getIdenticonUri} from "../../src/content-repo";
describe("ContentRepo", function() {
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() {
const httpUrl = "http://example.com/image.jpeg";
expect(
ContentRepo.getHttpUriForMxc(
getHttpUriForMxc(
baseUrl, httpUrl, undefined, undefined, undefined, true,
),
).toEqual(httpUrl);
@@ -17,25 +15,25 @@ describe("ContentRepo", function() {
it("should return the empty string HTTP URLs by default", function() {
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",
function() {
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",
);
});
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",
function() {
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" +
"?width=32&height=64&method=crop",
);
@@ -44,7 +42,7 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs after any query parameters",
function() {
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" +
"?width=32#automade",
);
@@ -53,7 +51,7 @@ describe("ContentRepo", function() {
it("should put fragments from mxc:// URIs at the end of the HTTP URI",
function() {
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",
);
});
@@ -61,25 +59,25 @@ describe("ContentRepo", function() {
describe("getIdenticonUri", 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() {
expect(ContentRepo.getIdenticonUri(baseUrl, "foobar")).toEqual(
expect(getIdenticonUri(baseUrl, "foobar")).toEqual(
baseUrl + "/_matrix/media/unstable/identicon/foobar" +
"?width=96&height=96",
);
});
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" +
"?width=32&height=64",
);
});
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" +
"?width=32&height=64",
);

View File

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

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations 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");
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.
*/
import DeviceList from '../../../lib/crypto/DeviceList';
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
import utils from '../../../lib/utils';
import logger from '../../../lib/logger';
import {logger} from "../../../src/logger";
import * as utils from "../../../src/utils";
import {MemoryCryptoStore} from "../../../src/crypto/store/memory-crypto-store";
import {DeviceList} from "../../../src/crypto/DeviceList";
const signedDeviceList = {
"failures": {},

View File

@@ -1,15 +1,16 @@
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 MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
@@ -342,4 +343,346 @@ describe("MegolmDecryption", function() {
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 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.
@@ -15,14 +16,12 @@ limitations under the License.
*/
import '../../../olm-loader';
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
import MockStorageApi from '../../../MockStorageApi';
import logger from '../../../../lib/logger';
import OlmDevice from '../../../../lib/crypto/OlmDevice';
import olmlib from '../../../../lib/crypto/olmlib';
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
import {MemoryCryptoStore} from "../../../../src/crypto/store/memory-crypto-store";
import {MockStorageApi} from "../../../MockStorageApi";
import {logger} from "../../../../src/logger";
import {OlmDevice} from "../../../../src/crypto/OlmDevice";
import * as olmlib from "../../../../src/crypto/olmlib";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
function makeOlmDevice() {
const mockStorage = new MockStorageApi();

View File

@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
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.
@@ -15,23 +16,20 @@ limitations under the License.
*/
import '../../olm-loader';
import sdk from '../../..';
import algorithms from '../../../lib/crypto/algorithms';
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
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';
import olmlib from '../../../lib/crypto/olmlib';
import {logger} from "../../../src/logger";
import * as olmlib from "../../../src/crypto/olmlib";
import {MatrixClient} from "../../../src/client";
import {MatrixEvent} from "../../../src/models/event";
import * as algorithms from "../../../src/crypto/algorithms";
import {WebStorageSessionStore} from "../../../src/store/session/webstorage";
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";
const Olm = global.Olm;
const MatrixClient = sdk.MatrixClient;
const MatrixEvent = sdk.MatrixEvent;
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
const ROOM_ID = '!ROOM:ID';

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
/*
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");
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
limitations under the License.
*/
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
} 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';
import "../../../olm-loader";
import {logger} from "../../../../src/logger";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {ScanQRCode, ShowQRCode} from "../../../../src/crypto/verification/QRCode";
const Olm = global.Olm;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
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.
@@ -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
limitations under the License.
*/
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
import {verificationMethods} from '../../../../lib/crypto';
import SAS from '../../../../lib/crypto/verification/SAS';
import "../../../olm-loader";
import {verificationMethods} from "../../../../src/crypto";
import {logger} from "../../../../src/logger";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {makeTestClients} from './util';
const Olm = global.Olm;
import {makeTestClients} from './util';
describe("verification request", function() {
if (!global.Olm) {
logger.warn('Not running device verification unit tests: libolm not present');

View File

@@ -1,5 +1,6 @@
/*
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");
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
limitations under the License.
*/
import logger from '../../../../lib/logger';
try {
global.Olm = require('olm');
} catch (e) {
logger.warn("unable to run device verification tests: libolm not available");
}
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';
import "../../../olm-loader";
import {makeTestClients} from './util';
import {MatrixEvent} from "../../../../src/models/event";
import {SAS} from "../../../../src/crypto/verification/SAS";
import {DeviceInfo} from "../../../../src/crypto/deviceinfo";
import {verificationMethods} from "../../../../src/crypto";
import * as olmlib from "../../../../src/crypto/olmlib";
import {logger} from "../../../../src/logger";
const Olm = global.Olm;
const MatrixEvent = sdk.MatrixEvent;
import {makeTestClients} from './util';
let ALICE_DEVICES;
let BOB_DEVICES;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
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.
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import TestClient from '../../../TestClient';
import sdk from '../../../..';
const MatrixEvent = sdk.MatrixEvent;
import {TestClient} from '../../../TestClient';
import {MatrixEvent} from "../../../../src/models/event";
export async function makeTestClients(userInfos, options) {
const clients = [];

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
Copyright 2019 The Matrix.org Foundaction 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.
@@ -14,10 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import sdk from '../..';
const MatrixEvent = sdk.MatrixEvent;
import logger from '../../lib/logger';
import {logger} from "../../src/logger";
import {MatrixEvent} from "../../src/models/event";
describe("MatrixEvent", () => {
describe(".attemptDecryption", () => {

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
const sdk = require("../..");
const InteractiveAuth = sdk.InteractiveAuth;
const MatrixError = sdk.MatrixError;
import logger from '../../lib/logger';
import {logger} from "../../src/logger";
import {InteractiveAuth} from "../../src/interactive-auth";
import {MatrixError} from "../../src/http-api";
// Trivial client object to test interactive auth
// (we do not need TestClient here)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
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.
@@ -14,11 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
import 'source-map-support/register';
import sdk from "../..";
const SyncAccumulator = sdk.SyncAccumulator;
import {SyncAccumulator} from "../../src/sync-accumulator";
describe("SyncAccumulator", function() {
let sa;

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ limitations under the License.
/** @module auto-discovery */
import logger from './logger';
import {logger} from './logger';
import {URL as NodeURL} from "url";
// Dev note: Auto discovery is part of the spec.

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
limitations under the License.
*/
"use strict";
/**
* This is an internal module. MatrixBaseApis is currently only meant to be used
@@ -26,16 +25,21 @@ limitations under the License.
*/
import {SERVICE_TYPES} from './service-types';
import logger from './logger';
const httpApi = require("./http-api");
const utils = require("./utils");
const PushProcessor = require("./pushprocessor");
import {logger} from './logger';
import {PushProcessor} from "./pushprocessor";
import * as utils from "./utils";
import {
MatrixHttpApi,
PREFIX_IDENTITY_V1,
PREFIX_IDENTITY_V2,
PREFIX_R0,
PREFIX_UNSTABLE,
} from "./http-api";
function termsUrlForService(serviceType, baseUrl) {
switch (serviceType) {
case SERVICE_TYPES.IS:
return baseUrl + httpApi.PREFIX_IDENTITY_V2 + '/terms';
return baseUrl + PREFIX_IDENTITY_V2 + '/terms';
case SERVICE_TYPES.IM:
return baseUrl + '/_matrix/integrations/v1/terms';
default:
@@ -83,7 +87,7 @@ function termsUrlForService(serviceType, baseUrl) {
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
* 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"]);
this.baseUrl = opts.baseUrl;
@@ -95,13 +99,13 @@ function MatrixBaseApis(opts) {
idBaseUrl: opts.idBaseUrl,
accessToken: opts.accessToken,
request: opts.request,
prefix: httpApi.PREFIX_R0,
prefix: PREFIX_R0,
onlyData: true,
extraParams: opts.queryParams,
localTimeoutMs: opts.localTimeoutMs,
useAuthorizationHeader: opts.useAuthorizationHeader,
};
this._http = new httpApi.MatrixHttpApi(this, httpOpts);
this._http = new MatrixHttpApi(this, httpOpts);
this._txnCtr = 0;
}
@@ -369,7 +373,7 @@ MatrixBaseApis.prototype.getSsoLoginUrl = function(redirectUrl, loginType) {
}
return this._http.getUrl("/login/"+loginType+"/redirect", {
"redirectUrl": redirectUrl,
}, httpApi.PREFIX_R0);
}, PREFIX_R0);
};
/**
@@ -447,7 +451,7 @@ MatrixBaseApis.prototype.getFallbackAuthUrl = function(loginType, authSessionId)
return this._http.getUrl(path, {
session: authSessionId,
}, httpApi.PREFIX_R0);
}, PREFIX_R0);
};
// Room operations
@@ -499,7 +503,7 @@ MatrixBaseApis.prototype.fetchRelations =
});
const response = await this._http.authedRequest(
undefined, "GET", path, null, null, {
prefix: httpApi.PREFIX_UNSTABLE,
prefix: PREFIX_UNSTABLE,
},
);
return response;
@@ -1377,7 +1381,7 @@ MatrixBaseApis.prototype.addThreePid = function(creds, bind, callback) {
MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
const path = "/account/3pid/add";
const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix },
);
@@ -1400,7 +1404,7 @@ MatrixBaseApis.prototype.addThreePidOnly = async function(data) {
MatrixBaseApis.prototype.bindThreePid = async function(data) {
const path = "/account/3pid/bind";
const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix },
);
@@ -1425,7 +1429,7 @@ MatrixBaseApis.prototype.unbindThreePid = async function(medium, address) {
id_server: this.getIdentityServerUrl(true),
};
const prefix = await this.isVersionSupported("r0.6.0") ?
httpApi.PREFIX_R0 : httpApi.PREFIX_UNSTABLE;
PREFIX_R0 : PREFIX_UNSTABLE;
return this._http.authedRequest(
undefined, "POST", path, null, data, { prefix },
);
@@ -1722,7 +1726,7 @@ MatrixBaseApis.prototype.uploadKeySignatures = function(content) {
return this._http.authedRequest(
undefined, "POST", '/keys/signatures/upload', undefined,
content, {
prefix: httpApi.PREFIX_UNSTABLE,
prefix: PREFIX_UNSTABLE,
},
);
};
@@ -1815,7 +1819,7 @@ MatrixBaseApis.prototype.uploadDeviceSigningKeys = function(auth, keys) {
const data = Object.assign({}, keys, {auth});
return this._http.authedRequest(
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");
}
const uri = this.idBaseUrl + httpApi.PREFIX_IDENTITY_V2 + "/account/register";
const uri = this.idBaseUrl + PREFIX_IDENTITY_V2 + "/account/register";
return this._http.requestOtherUrl(
undefined, "POST", uri,
null, hsOpenIdToken,
@@ -1890,7 +1894,7 @@ MatrixBaseApis.prototype.requestEmailToken = async function(
try {
const response = await this._http.idServerRequest(
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
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");
return await this._http.idServerRequest(
callback, "POST", "/validate/email/requestToken",
params, httpApi.PREFIX_IDENTITY_V1,
params, PREFIX_IDENTITY_V1,
);
}
if (callback) callback(err);
@@ -1958,7 +1962,7 @@ MatrixBaseApis.prototype.requestMsisdnToken = async function(
try {
const response = await this._http.idServerRequest(
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
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");
return await this._http.idServerRequest(
callback, "POST", "/validate/msisdn/requestToken",
params, httpApi.PREFIX_IDENTITY_V1,
params, PREFIX_IDENTITY_V1,
);
}
if (callback) callback(err);
@@ -2013,7 +2017,7 @@ MatrixBaseApis.prototype.submitMsisdnToken = async function(
try {
return await this._http.idServerRequest(
undefined, "POST", "/validate/msisdn/submitToken",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
params, PREFIX_IDENTITY_V2, identityAccessToken,
);
} catch (err) {
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");
return await this._http.idServerRequest(
undefined, "POST", "/validate/msisdn/submitToken",
params, httpApi.PREFIX_IDENTITY_V1,
params, PREFIX_IDENTITY_V1,
);
}
throw err;
@@ -2074,7 +2078,7 @@ MatrixBaseApis.prototype.submitMsisdnTokenOtherUrl = function(
MatrixBaseApis.prototype.getIdentityHashDetails = function(identityAccessToken) {
return this._http.idServerRequest(
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(
undefined, "POST", "/lookup",
params, httpApi.PREFIX_IDENTITY_V2, identityAccessToken,
params, PREFIX_IDENTITY_V2, identityAccessToken,
);
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");
return await this._http.idServerRequest(
callback, "GET", "/lookup",
params, httpApi.PREFIX_IDENTITY_V1,
params, PREFIX_IDENTITY_V1,
);
}
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");
return await this._http.idServerRequest(
undefined, "POST", "/bulk_lookup", params,
httpApi.PREFIX_IDENTITY_V1, identityAccessToken,
PREFIX_IDENTITY_V1, identityAccessToken,
);
}
throw err;
@@ -2304,7 +2308,7 @@ MatrixBaseApis.prototype.getIdentityAccount = function(
) {
return this._http.idServerRequest(
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});
};
/**
* 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
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.
* @module client
*/
const EventEmitter = require("events").EventEmitter;
const url = require('url');
const httpApi = require("./http-api");
const MatrixEvent = require("./models/event").MatrixEvent;
const EventStatus = require("./models/event").EventStatus;
const EventTimeline = require("./models/event-timeline");
const SearchResult = require("./models/search-result");
const StubStore = require("./store/stub");
const webRtcCall = require("./webrtc/call");
const utils = require("./utils");
const contentRepo = require("./content-repo");
const Filter = require("./filter");
const SyncApi = require("./sync");
const MatrixBaseApis = require("./base-apis");
const MatrixError = httpApi.MatrixError;
const ContentHelpers = require("./content-helpers");
const olmlib = require("./crypto/olmlib");
import ReEmitter from './ReEmitter';
import RoomList from './crypto/RoomList';
import logger from './logger';
import Crypto from './crypto';
import { isCryptoAvailable } from './crypto';
import url from "url";
import {EventEmitter} from "events";
import {MatrixBaseApis} from "./base-apis";
import {Filter} from "./filter";
import {SyncApi} from "./sync";
import {EventStatus, MatrixEvent} from "./models/event";
import {EventTimeline} from "./models/event-timeline";
import {SearchResult} from "./models/search-result";
import {StubStore} from "./store/stub";
import {createNewMatrixCall} from "./webrtc/call";
import * as utils from './utils';
import {sleep} from './utils';
import {MatrixError, PREFIX_MEDIA_R0, PREFIX_UNSTABLE} from "./http-api";
import {getHttpUriForMxc} from "./content-repo";
import * as ContentHelpers from "./content-helpers";
import * as olmlib from "./crypto/olmlib";
import {ReEmitter} from './ReEmitter';
import {RoomList} from './crypto/RoomList';
import {logger} from './logger';
import {Crypto, isCryptoAvailable} from './crypto';
import {decodeRecoveryKey} from './crypto/recoverykey';
import {keyFromAuthData} from './crypto/key_passphrase';
import {randomString} from './randomstring';
import { encodeBase64, decodeBase64 } from '../lib/crypto/olmlib';
import {PushProcessor} from "./pushprocessor";
import {encodeBase64, decodeBase64} from "./crypto/olmlib";
const SCROLLBACK_DELAY_MS = 3000;
const CRYPTO_ENABLED = isCryptoAvailable();
export const CRYPTO_ENABLED = isCryptoAvailable();
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
function keysFromRecoverySession(sessions, decryptionKey, roomId) {
@@ -235,7 +229,7 @@ function keyFromRecoverySession(session, decryptionKey) {
* {DeviceTrustLevel} device_trust: The trust status of the device requesting
* the secret as returned by {@link module:client~MatrixClient#checkDeviceTrust}.
*/
function MatrixClient(opts) {
export function MatrixClient(opts) {
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
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
// 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;
if (call) {
setupCallEventHandler(this);
@@ -946,6 +940,35 @@ MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function() {
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
*
@@ -1346,7 +1369,7 @@ MatrixClient.prototype.checkKeyBackup = function() {
MatrixClient.prototype.getKeyBackupVersion = function() {
return this._http.authedRequest(
undefined, "GET", "/room_keys/version", undefined, undefined,
{prefix: httpApi.PREFIX_UNSTABLE},
{prefix: PREFIX_UNSTABLE},
).then((res) => {
if (res.algorithm !== olmlib.MEGOLM_BACKUP_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.
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
// 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");
}
const res = await this._http.authedRequest(
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
@@ -1544,7 +1573,7 @@ MatrixClient.prototype.deleteKeyBackupVersion = function(version) {
return this._http.authedRequest(
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);
return this._http.authedRequest(
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(
undefined, "GET", path.path, path.queryData, undefined,
{prefix: httpApi.PREFIX_UNSTABLE},
{prefix: PREFIX_UNSTABLE},
).then((res) => {
if (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;
const roomKeys = keysFromRecoverySession(
roomData.sessions, decryption, roomId, roomKeys,
roomData.sessions, decryption, roomId,
);
for (const k of roomKeys) {
k.room_id = roomId;
@@ -1770,7 +1799,7 @@ MatrixClient.prototype.deleteKeysFromBackup = function(roomId, sessionId, versio
const path = this._makeKeyBackupPath(roomId, sessionId, version);
return this._http.authedRequest(
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) {
return this._http.authedRequest(
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);
};
/**
* 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
* @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,
ts: ts,
}, undefined, {
prefix: httpApi.PREFIX_MEDIA_R0,
prefix: PREFIX_MEDIA_R0,
},
).then(function(response) {
// TODO: expire cache occasionally
@@ -3193,7 +3241,7 @@ MatrixClient.prototype.setAvatarUrl = function(url, callback) {
*/
MatrixClient.prototype.mxcUrlToHttp =
function(mxcUrl, width, height, resizeMethod, allowDirectLinks) {
return contentRepo.getHttpUriForMxc(
return getHttpUriForMxc(
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,
});
if (!call) {
@@ -4952,7 +5000,7 @@ function setupCallEventHandler(client) {
// if not live, store the fact that the call has ended because
// we're probably getting events backwards so
// the hangup will come before the invite
call = webRtcCall.createNewMatrixCall(client, event.getRoomId());
call = createNewMatrixCall(client, event.getRoomId());
if (call) {
call.callId = content.call_id;
call._initWithHangup(event);
@@ -5052,11 +5100,6 @@ MatrixClient.prototype.generateClientSecret = function() {
return randomString(32);
};
/** */
module.exports.MatrixClient = MatrixClient;
/** */
module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
// MatrixClient Event JSDocs
/**

View File

@@ -1,5 +1,6 @@
/*
Copyright 2018 New Vector Ltd
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.
@@ -13,24 +14,23 @@ 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.
*/
"use strict";
/** @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) {
export function makeHtmlMessage(body, htmlBody) {
return {
msgtype: "m.text",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
}
/**
* Generates the content for a HTML Notice event
@@ -38,14 +38,14 @@ module.exports = {
* @param {string} htmlBody the HTML representation of the notice
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlNotice: function(body, htmlBody) {
export function makeHtmlNotice(body, htmlBody) {
return {
msgtype: "m.notice",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
}
/**
* Generates the content for a HTML Emote event
@@ -53,48 +53,47 @@ module.exports = {
* @param {string} htmlBody the HTML representation of the emote
* @returns {{msgtype: string, format: string, body: string, formatted_body: string}}
*/
makeHtmlEmote: function(body, htmlBody) {
export function makeHtmlEmote(body, htmlBody) {
return {
msgtype: "m.emote",
format: "org.matrix.custom.html",
body: body,
formatted_body: htmlBody,
};
},
}
/**
* Generates the content for a Plaintext Message event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
makeTextMessage: function(body) {
export function makeTextMessage(body) {
return {
msgtype: "m.text",
body: body,
};
},
}
/**
* Generates the content for a Plaintext Notice event
* @param {string} body the plaintext body of the notice
* @returns {{msgtype: string, body: string}}
*/
makeNotice: function(body) {
export function makeNotice(body) {
return {
msgtype: "m.notice",
body: body,
};
},
}
/**
* Generates the content for a Plaintext Emote event
* @param {string} body the plaintext body of the emote
* @returns {{msgtype: string, body: string}}
*/
makeEmoteMessage: function(body) {
export function makeEmoteMessage(body) {
return {
msgtype: "m.emote",
body: body,
};
},
};
}

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@@ -16,10 +17,9 @@ limitations under the License.
/**
* @module content-repo
*/
const utils = require("./utils");
/** Content Repo utility functions */
module.exports = {
import * as utils from "./utils";
/**
* Get the HTTP URL for an MXC URI.
* @param {string} baseUrl The base homeserver url which has a content repo.
@@ -34,7 +34,7 @@ module.exports = {
* for such URLs.
* @return {string} The complete URL to the content.
*/
getHttpUriForMxc: function(baseUrl, mxc, width, height,
export function getHttpUriForMxc(baseUrl, mxc, width, height,
resizeMethod, allowDirectLinks) {
if (typeof mxc !== "string" || !mxc) {
return '';
@@ -74,7 +74,7 @@ module.exports = {
return baseUrl + prefix + serverAndMediaId +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params))) + fragment;
},
}
/**
* Get an identicon URL from an arbitrary string.
@@ -85,7 +85,7 @@ module.exports = {
* @return {string} The complete URL to the identicon.
* @deprecated This is no longer in the specification.
*/
getIdenticonUri: function(baseUrl, identiconString, width, height) {
export function getIdenticonUri(baseUrl, identiconString, width, height) {
if (!identiconString) {
return null;
}
@@ -106,5 +106,4 @@ module.exports = {
return baseUrl + path +
(utils.keys(params).length === 0 ? "" :
("?" + utils.encodeParams(params)));
},
};
}

View File

@@ -20,9 +20,9 @@ limitations under the License.
* @module crypto/CrossSigning
*/
import {pkSign, pkVerify, encodeBase64, decodeBase64} from './olmlib';
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
import {EventEmitter} from 'events';
import logger from '../logger';
import {logger} from '../logger';
function publicKeyFromKeyInfo(keyInfo) {
// `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
limitations under the License.
*/
"use strict";
/**
* @module crypto/DeviceList
@@ -24,12 +23,11 @@ limitations under the License.
*/
import {EventEmitter} from 'events';
import logger from '../logger';
import DeviceInfo from './deviceinfo';
import {logger} from '../logger';
import {DeviceInfo} from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import olmlib from './olmlib';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import * as olmlib from './olmlib';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils';
@@ -63,7 +61,7 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
/**
* @alias module:crypto/DeviceList
*/
export default class DeviceList extends EventEmitter {
export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
super();

View File

@@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket 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");
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.
*/
import logger from '../logger';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {logger} from '../logger';
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
// 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} deviceEd25519Key Ed25519 key for the account
*/
function OlmDevice(cryptoStore) {
export function OlmDevice(cryptoStore) {
this._cryptoStore = cryptoStore;
this._pickleKey = "DEFAULT_KEY";
@@ -671,6 +673,18 @@ OlmDevice.prototype.matchesSession = async function(
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
// ======================
@@ -818,9 +832,9 @@ OlmDevice.prototype._getInboundGroupSession = function(
roomId, senderKey, sessionId, txn, func,
) {
this._cryptoStore.getEndToEndInboundGroupSession(
senderKey, sessionId, txn, (sessionData) => {
senderKey, sessionId, txn, (sessionData, withheld) => {
if (sessionData === null) {
func(null);
func(null, null, withheld);
return;
}
@@ -834,7 +848,7 @@ OlmDevice.prototype._getInboundGroupSession = function(
}
this._unpickleInboundGroupSession(sessionData, (session) => {
func(session, sessionData);
func(session, sessionData, withheld);
});
},
);
@@ -859,7 +873,10 @@ OlmDevice.prototype.addInboundGroupSession = async function(
exportFormat,
) {
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 */
this._getInboundGroupSession(
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
*
@@ -934,16 +1005,49 @@ OlmDevice.prototype.decryptGroupMessage = async function(
roomId, senderKey, sessionId, body, eventId, timestamp,
) {
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(
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
'readwrite', [
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
], (txn) => {
this._getInboundGroupSession(
roomId, senderKey, sessionId, txn, (session, sessionData) => {
roomId, senderKey, sessionId, txn, (session, sessionData, withheld) => {
if (session === null) {
if (withheld) {
error = new algorithms.DecryptionError(
"MEGOLM_UNKNOWN_INBOUND_SESSION_ID",
_calculateWithheldMessage(withheld),
{
session: senderKey + '|' + sessionId,
},
);
}
result = null;
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;
if (plaintext === undefined) {
@@ -965,10 +1069,11 @@ OlmDevice.prototype.decryptGroupMessage = async function(
msgInfo.id !== eventId ||
msgInfo.timestamp !== timestamp
) {
throw new Error(
error = new Error(
"Duplicate message index, possible replay attack: " +
messageIndexKey,
);
return;
}
}
this._inboundGroupSessionMessageIndexes[messageIndexKey] = {
@@ -994,6 +1099,9 @@ OlmDevice.prototype.decryptGroupMessage = async function(
},
);
if (error) {
throw error;
}
return result;
};
@@ -1009,7 +1117,10 @@ OlmDevice.prototype.decryptGroupMessage = async function(
OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, sessionId) {
let result;
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(
senderKey, sessionId, txn, (sessionData) => {
if (sessionData === null) {
@@ -1060,7 +1171,10 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
) {
let result;
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(
roomId, senderKey, sessionId, txn, (session, sessionData) => {
if (session === null) {
@@ -1139,6 +1253,3 @@ OlmDevice.prototype.verifySignature = function(
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.
*/
import logger from '../logger';
import utils from '../utils';
import {logger} from '../logger';
import * as utils from '../utils';
/**
* Internal module. Management of outgoing room key requests.
@@ -75,7 +75,7 @@ const ROOM_KEY_REQUEST_STATES = {
CANCELLATION_PENDING_AND_WILL_RESEND: 3,
};
export default class OutgoingRoomKeyRequestManager {
export class OutgoingRoomKeyRequestManager {
constructor(baseApis, deviceId, cryptoStore) {
this._baseApis = baseApis;
this._deviceId = deviceId;

View File

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

View File

@@ -15,10 +15,10 @@ limitations under the License.
*/
import {EventEmitter} from 'events';
import logger from '../logger';
import olmlib from './olmlib';
import { randomString } from '../randomstring';
import {logger} from '../logger';
import * as olmlib from './olmlib';
import {pkVerify} from './olmlib';
import {randomString} from '../randomstring';
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)
* @module crypto/SecretStorage
*/
export default class SecretStorage extends EventEmitter {
export class SecretStorage extends EventEmitter {
constructor(baseApis, cryptoCallbacks, crossSigningInfo) {
super();
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 {object} params.config The body of the m.room.encryption event
*/
class EncryptionAlgorithm {
export class EncryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._deviceId = params.deviceId;
@@ -84,7 +84,6 @@ class EncryptionAlgorithm {
onRoomMembership(event, member, oldMembership) {
}
}
export {EncryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
/**
* 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
* from. Null for to-device events.
*/
class DecryptionAlgorithm {
export class DecryptionAlgorithm {
constructor(params) {
this._userId = params.userId;
this._crypto = params.crypto;
@@ -159,8 +158,17 @@ class DecryptionAlgorithm {
shareKeysWithDevice(keyRequest) {
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
@@ -173,7 +181,7 @@ export {DecryptionAlgorithm}; // https://github.com/jsdoc3/jsdoc/issues/1272
*
* @extends Error
*/
class DecryptionError extends Error {
export class DecryptionError extends Error {
constructor(code, msg, details) {
super(msg);
this.code = code;
@@ -181,7 +189,6 @@ class DecryptionError extends Error {
this.detailedString = _detailedStringForDecryptionError(this, details);
}
}
export {DecryptionError}; // https://github.com/jsdoc3/jsdoc/issues/1272
function _detailedStringForDecryptionError(err, details) {
let result = err.name + '[msg: ' + err.message;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module crypto/algorithms
*/
const base = require("./base");
import "./olm";
import "./megolm";
require("./olm");
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;
export * from "./base";

View File

@@ -1,6 +1,7 @@
/*
Copyright 2015, 2016 OpenMarket 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");
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
limitations under the License.
*/
"use strict";
/**
* Defines m.olm encryption/decryption
@@ -22,11 +22,19 @@ limitations under the License.
* @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");
const olmlib = require("../olmlib");
const base = require("./base");
import {WITHHELD_MESSAGES} from '../OlmDevice';
/**
* @private
@@ -47,6 +55,7 @@ function OutboundSessionInfo(sessionId) {
this.useCount = 0;
this.creationTime = new Date().getTime();
this.sharedWithDevices = {};
this.blockedDevicesNotified = {};
}
@@ -84,6 +93,15 @@ OutboundSessionInfo.prototype.markSharedWithDevice = function(
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
* have been.
@@ -128,13 +146,13 @@ OutboundSessionInfo.prototype.sharedWithTooManyDevices = function(
* Megolm encryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
*
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
* {@link module:crypto/algorithms/EncryptionAlgorithm}
*/
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 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;
}
}
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
utils.inherits(MegolmEncryption, EncryptionAlgorithm);
/**
* @private
*
* @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
* OutboundSessionInfo when setup is complete.
*/
MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
MegolmEncryption.prototype._ensureOutboundSession = async function(
devicesInRoom, blocked,
) {
const self = this;
let session;
@@ -237,9 +258,50 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
}
}
return self._shareKeyWithDevices(
session, shareMap,
const errorDevices = [];
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
@@ -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
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
@@ -299,12 +365,16 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* 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>>}
*/
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
const mapSlices = [];
@@ -334,6 +404,8 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
// to claim a one-time-key for dead devices on every message.
session.markSharedWithDevice(userId, deviceId, chainIndex);
errorDevices.push({userId, deviceInfo});
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue;
@@ -343,11 +415,6 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
"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]) {
mapSlices[currentSliceId] = [];
}
@@ -359,6 +426,61 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
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;
};
@@ -381,15 +503,15 @@ MegolmEncryption.prototype._splitUserDeviceMap = function(
MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
session, chainIndex, userDeviceMap, payload,
) {
const contentMap = {};
const promises = [];
for (let i = 0; i < userDeviceMap.length; i++) {
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
const contentMap = {};
const promises = [];
for (let i = 0; i < userDeviceMap.length; i++) {
const val = userDeviceMap[i];
const userId = val.userId;
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
* sent to them.
@@ -523,8 +692,14 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* 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 payload = {
type: "m.room_key",
@@ -542,7 +717,7 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
);
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++) {
@@ -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
*
@@ -570,17 +781,19 @@ MegolmEncryption.prototype._shareKeyWithDevices = async function(session, device
*
* @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;
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.
if (this._crypto.getGlobalErrorOnUnknownDevices()) {
self._checkForUnknownDevices(devicesInRoom);
}
return self._ensureOutboundSession(devicesInRoom);
}).then(function(session) {
const session = await self._ensureOutboundSession(devicesInRoom, blocked);
const payloadJson = {
room_id: self._roomId,
type: eventType,
@@ -605,7 +818,6 @@ MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
session.useCount++;
return encryptedContent;
});
};
/**
@@ -643,7 +855,7 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
if (Object.keys(unknownDevices).length) {
// 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. " +
"We strongly recommend you verify them before continuing.", unknownDevices);
}
@@ -654,8 +866,11 @@ MegolmEncryption.prototype._checkForUnknownDevices = function(devicesInRoom) {
*
* @param {module:models/room} room
*
* @return {module:client.Promise} Promise which resolves to a map
* from userId to deviceId to deviceInfo
* @return {module:client.Promise} Promise which resolves to an array whose
* 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) {
const members = await room.getEncryptionTargetMembers();
@@ -676,6 +891,7 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
// using all the device_lists changes and left fields.
// See https://github.com/vector-im/riot-web/issues/2305 for details.
const devices = await this._crypto.downloadKeys(roomMembers, false);
const blocked = {};
// remove any blocked devices
for (const userId in devices) {
if (!devices.hasOwnProperty(userId)) {
@@ -691,25 +907,39 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
if (userDevices[deviceId].isBlocked() ||
(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];
}
}
}
return devices;
return [devices, blocked];
};
/**
* Megolm decryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
*
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
* {@link module:crypto/algorithms/DecryptionAlgorithm}
*/
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
// senderKey|sessionId to Set of MatrixEvents
@@ -718,7 +948,12 @@ function MegolmDecryption(params) {
// this gets stubbed out by the unit tests.
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
@@ -736,7 +971,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
if (!content.sender_key || !content.session_id ||
!content.ciphertext
) {
throw new base.DecryptionError(
throw new DecryptionError(
"MEGOLM_MISSING_FIELDS",
"Missing fields in input",
);
@@ -756,6 +991,11 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
event.getId(), event.getTs(),
);
} catch (e) {
if (e.name === "DecryptionError") {
// re-throw decryption errors as-is
throw e;
}
let errorCode = "OLM_DECRYPT_GROUP_MESSAGE_ERROR";
if (e && e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
@@ -764,7 +1004,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
errorCode = 'OLM_UNKNOWN_MESSAGE_INDEX';
}
throw new base.DecryptionError(
throw new DecryptionError(
errorCode,
e ? e.toString() : "Unknown Error: Error is undefined", {
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
// scheduled, so we needn't send out the request here.)
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",
"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
// room, so neither the sender nor a MITM can lie about the room_id).
if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError(
throw new DecryptionError(
"MEGOLM_BAD_ROOM",
"Message intended for room " + payload.room_id,
);
@@ -836,11 +1098,16 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
*/
MegolmDecryption.prototype._addEventToPendingList = function(event) {
const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id;
if (!this._pendingEvents[k]) {
this._pendingEvents[k] = new Set();
const senderKey = content.sender_key;
const sessionId = content.session_id;
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) {
const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id;
if (!this._pendingEvents[k]) {
const senderKey = content.sender_key;
const sessionId = content.session_id;
const senderPendingEvents = this._pendingEvents[senderKey];
const pendingEvents = senderPendingEvents && senderPendingEvents.get(sessionId);
if (!pendingEvents) {
return;
}
this._pendingEvents[k].delete(event);
if (this._pendingEvents[k].size === 0) {
delete this._pendingEvents[k];
pendingEvents.delete(event);
if (pendingEvents.size === 0) {
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
*/
@@ -1106,13 +1451,20 @@ MegolmDecryption.prototype.importRoomKey = function(session) {
* @return {Boolean} whether all messages were successfully decrypted
*/
MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionId) {
const k = senderKey + "|" + sessionId;
const pending = this._pendingEvents[k];
const senderPendingEvents = this._pendingEvents[senderKey];
if (!senderPendingEvents) {
return true;
}
const pending = senderPendingEvents.get(sessionId);
if (!pending) {
return true;
}
delete this._pendingEvents[k];
pending.delete(sessionId);
if (pending.size === 0) {
this._pendingEvents[senderKey];
}
await Promise.all([...pending].map(async (ev) => {
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,
);

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
limitations under the License.
*/
"use strict";
/**
* Defines m.olm encryption/decryption
*
* @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
*
* @constructor
* @extends {module:crypto/algorithms/base.EncryptionAlgorithm}
* @extends {module:crypto/algorithms/EncryptionAlgorithm}
*
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.EncryptionAlgorithm}
* {@link module:crypto/algorithms/EncryptionAlgorithm}
*/
function OlmEncryption(params) {
base.EncryptionAlgorithm.call(this, params);
polyfillSuper(this, EncryptionAlgorithm, params);
this._sessionPrepared = false;
this._prepPromise = null;
}
utils.inherits(OlmEncryption, base.EncryptionAlgorithm);
utils.inherits(OlmEncryption, EncryptionAlgorithm);
/**
* @private
@@ -143,14 +149,14 @@ OlmEncryption.prototype.encryptMessage = async function(room, eventType, content
* Olm decryption implementation
*
* @constructor
* @extends {module:crypto/algorithms/base.DecryptionAlgorithm}
* @extends {module:crypto/algorithms/DecryptionAlgorithm}
* @param {object} params parameters, as per
* {@link module:crypto/algorithms/base.DecryptionAlgorithm}
* {@link module:crypto/algorithms/DecryptionAlgorithm}
*/
function OlmDecryption(params) {
base.DecryptionAlgorithm.call(this, params);
polyfillSuper(this, DecryptionAlgorithm, params);
}
utils.inherits(OlmDecryption, base.DecryptionAlgorithm);
utils.inherits(OlmDecryption, DecryptionAlgorithm);
/**
* @inheritdoc
@@ -168,14 +174,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
const ciphertext = content.ciphertext;
if (!ciphertext) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_MISSING_CIPHERTEXT",
"Missing ciphertext",
);
}
if (!(this._olmDevice.deviceCurve25519Key in ciphertext)) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_NOT_INCLUDED_IN_RECIPIENTS",
"Not included in recipients",
);
@@ -186,7 +192,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
try {
payloadString = await this._decryptMessage(deviceKey, message);
} catch (e) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ENCRYPTED_MESSAGE",
"Bad Encrypted Message", {
sender: deviceKey,
@@ -200,14 +206,14 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// check that we were the intended recipient, to avoid unknown-key attack
// https://github.com/vector-im/vector-web/issues/2483
if (payload.recipient != this._userId) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT",
"Message was intented for " + payload.recipient,
);
}
if (payload.recipient_keys.ed25519 != this._olmDevice.deviceEd25519Key) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_RECIPIENT_KEY",
"Message not intended for this device", {
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,
// which is checked elsewhere).
if (payload.sender != event.getSender()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_FORWARDED_MESSAGE",
"Message forwarded from " + payload.sender, {
reported_sender: event.getSender(),
@@ -231,7 +237,7 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
// Olm events intended for a room have a room_id.
if (payload.room_id !== event.getRoomId()) {
throw new base.DecryptionError(
throw new DecryptionError(
"OLM_BAD_ROOM",
"Message intended for room " + payload.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 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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module crypto/deviceinfo
@@ -44,7 +43,7 @@ limitations under the License.
*
* @param {string} deviceId id of the device
*/
function DeviceInfo(deviceId) {
export function DeviceInfo(deviceId) {
// you can't change the deviceId
Object.defineProperty(this, 'deviceId', {
enumerable: true,
@@ -167,5 +166,3 @@ DeviceInfo.DeviceVerification = {
const DeviceVerification = DeviceInfo.DeviceVerification;
/** */
module.exports = DeviceInfo;

View File

@@ -2,7 +2,7 @@
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations 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");
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
limitations under the License.
*/
"use strict";
/**
* @module crypto
*/
const anotherjson = require('another-json');
import anotherjson from "another-json";
import {EventEmitter} from 'events';
import ReEmitter from '../ReEmitter';
import logger from '../logger';
const utils = require("../utils");
const OlmDevice = require("./OlmDevice");
const olmlib = require("./olmlib");
const algorithms = require("./algorithms");
const DeviceInfo = require("./deviceinfo");
const DeviceVerification = DeviceInfo.DeviceVerification;
const DeviceList = require('./DeviceList').default;
import {ReEmitter} from '../ReEmitter';
import {logger} from '../logger';
import * as utils from "../utils";
import {sleep} from "../utils";
import {OlmDevice} from "./OlmDevice";
import * as olmlib from "./olmlib";
import {DeviceList} from "./DeviceList";
import {DeviceInfo} from "./deviceinfo";
import * as algorithms from "./algorithms";
import {
CrossSigningInfo,
UserTrustLevel,
DeviceTrustLevel,
CrossSigningLevel,
DeviceTrustLevel,
UserTrustLevel,
} from './CrossSigning';
import SecretStorage, { SECRET_STORAGE_ALGORITHM_V1 } from './SecretStorage';
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
import {ShowQRCode, ScanQRCode} from './verification/QRCode';
import SAS from './verification/SAS';
import {sleep} from '../utils';
import {SECRET_STORAGE_ALGORITHM_V1, SecretStorage} from './SecretStorage';
import {OutgoingRoomKeyRequestManager} from './OutgoingRoomKeyRequestManager';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {ScanQRCode, ShowQRCode} from './verification/QRCode';
import {SAS} from './verification/SAS';
import {keyFromPassphrase} from './key_passphrase';
import {encodeRecoveryKey} from './recoverykey';
import VerificationRequest from "./verification/request/VerificationRequest";
import {VerificationRequest} from "./verification/request/VerificationRequest";
import {InRoomChannel, InRoomRequests} from "./verification/request/InRoomChannel";
import {ToDeviceChannel, ToDeviceRequests} from "./verification/request/ToDeviceChannel";
import * as httpApi from "../http-api";
const DeviceVerification = DeviceInfo.DeviceVerification;
const defaultVerificationMethods = {
[ScanQRCode.NAME]: ScanQRCode,
@@ -107,7 +104,7 @@ const KEY_BACKUP_KEYS_PER_REQUEST = 200;
* Each element can either be a string from MatrixClient.verificationMethods
* 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) {
this._onDeviceListUserCrossSigningUpdated =
this._onDeviceListUserCrossSigningUpdated.bind(this);
@@ -176,6 +173,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
this._deviceKeys = {};
this._globalBlacklistUnverifiedDevices = false;
this._globalErrorOnUnknownDevices = true;
this._outgoingRoomKeyRequestManager = new OutgoingRoomKeyRequestManager(
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.
if (!cryptoCallbacks.getCrossSigningKey) {
if (!cryptoCallbacks.getCrossSigningKey && cryptoCallbacks.getSecretStorageKey) {
cryptoCallbacks.getCrossSigningKey = async (type) => {
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
// (ie. the secret is the key itself).
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 {
logger.log("Secret storage default key not found, creating new key");
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
* (either by resetting the keys for the account or bye getting them from secret
* storaoge), such as signing the current device, upgrading device
* (either by resetting the keys for the account or by getting them from secret
* storage), such as signing the current device, upgrading device
* verifications, etc.
*/
Crypto.prototype._afterCrossSigningLocalKeyChange = async function() {
@@ -1068,6 +1085,9 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
);
if (device) {
sigInfo.device = device;
sigInfo.deviceTrust = await this.checkDeviceTrust(
this._userId, sigInfo.deviceId,
);
try {
await olmlib.verifySignature(
this._olmDevice,
@@ -1095,7 +1115,7 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
ret.usable = ret.sigs.some((s) => {
return (
s.valid && (
(s.device && s.device.isVerified()) ||
(s.device && s.deviceTrust.isVerified()) ||
(s.crossSigningId)
)
);
@@ -1183,6 +1203,29 @@ Crypto.prototype.getGlobalBlacklistUnverifiedDevices = function() {
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.
* @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);
} else if (event.getType() === "m.secret.send") {
this._secretStorage._onSecretReceived(event);
} else if (event.getType() === "org.matrix.room_key.withheld") {
this._onRoomKeyWithheldEvent(event);
} else if (event.getContent().transaction_id) {
this._onKeyVerificationMessage(event);
} else if (event.getContent().msgtype === "m.bad.encrypted") {
@@ -2420,6 +2465,42 @@ Crypto.prototype._onRoomKeyEvent = function(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.
*
@@ -2536,6 +2617,16 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
const algorithm = content.algorithm;
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) {
return;
}
@@ -2549,6 +2640,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"New session already forced with device " + sender + ":" + deviceKey +
" at " + lastNewSessionForced + ": not forcing another",
);
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
return;
}
@@ -2562,6 +2655,8 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
"Couldn't find device for identity key " + deviceKey +
": not re-establishing session",
);
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", false);
retryDecryption();
return;
}
const devicesByUser = {};
@@ -2593,6 +2688,9 @@ Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
{type: "m.dummy"},
);
await this._olmDevice.recordSessionProblem(deviceKey, "wedged", true);
retryDecryption();
await this._baseApis.sendToDevice("m.room.encrypted", {
[sender]: {
[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
*

View File

@@ -22,25 +22,24 @@ limitations under the License.
* Utilities common to olm encryption algorithms
*/
const anotherjson = require('another-json');
import logger from '../logger';
const utils = require("../utils");
import {logger} from '../logger';
import * as utils from "../utils";
import anotherjson from "another-json";
/**
* 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
*/
module.exports.MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
export const MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
/**
* 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
* has been encrypted into `resultsObject`
*/
module.exports.encryptMessageForDevice = async function(
export async function encryptMessageForDevice(
resultsObject,
ourUserId, ourDeviceId, olmDevice, recipientUserId, recipientDevice,
payloadFields,
@@ -112,7 +111,7 @@ module.exports.encryptMessageForDevice = async function(
resultsObject[deviceKey] = await olmDevice.encryptMessage(
deviceKey, sessionId, JSON.stringify(payload),
);
};
}
/**
* 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
* {@link module:crypto~OlmSessionResult}
*/
module.exports.ensureOlmSessionsForDevices = async function(
export async function ensureOlmSessionsForDevices(
olmDevice, baseApis, devicesByUser, force,
) {
const devicesWithoutSession = [
@@ -263,12 +262,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
await Promise.all(promises);
return result;
};
}
async function _verifyKeyAndStartSession(olmDevice, oneTimeKey, userId, deviceInfo) {
const deviceId = deviceInfo.deviceId;
try {
await _verifySignature(
await verifySignature(
olmDevice, oneTimeKey, userId, deviceId,
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,
* 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,
) {
const signKeyId = "ed25519:" + signingDeviceId;
@@ -335,7 +334,7 @@ const _verifySignature = module.exports.verifySignature = async function(
olmDevice.verifySignature(
signingKey, json, signature,
);
};
}
/**
* 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)
* @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;
if (key instanceof Uint8Array) {
const keyObj = new global.Olm.PkSigning();
@@ -371,7 +370,7 @@ module.exports.pkSign = function(obj, key, userId, pubkey) {
key.free();
}
}
};
}
/**
* 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} 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;
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
throw new Error("No signature");
@@ -397,22 +396,22 @@ module.exports.pkVerify = function(obj, pubkey, userId) {
if (unsigned) obj.unsigned = unsigned;
util.free();
}
};
}
/**
* Encode a typed array of uint8 as base64.
* @param {Uint8Array} uint8Array The data to encode.
* @return {string} The base64.
*/
module.exports.encodeBase64 = function(uint8Array) {
export function encodeBase64(uint8Array) {
return Buffer.from(uint8Array).toString("base64");
};
}
/**
* Decode a base64 string to a typed array of uint8.
* @param {string} base64 The base64 to decode.
* @return {Uint8Array} The decoded data.
*/
module.exports.decodeBase64 = function(base64) {
export function decodeBase64(base64) {
return Buffer.from(base64, "base64");
};
}

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations 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");
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.
*/
import logger from '../../logger';
import utils from '../../utils';
import {logger} from '../../logger';
import * as utils from "../../utils";
export const VERSION = 7;
export const VERSION = 9;
/**
* Implementation of a CryptoStore which is backed by an existing
@@ -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
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
let session = false;
let withheld = false;
const objectStore = txn.objectStore("inbound_group_sessions");
const getReq = objectStore.get([senderCurve25519Key, sessionId]);
getReq.onsuccess = function() {
try {
if (getReq.result) {
func(getReq.result.session);
session = getReq.result.session;
} 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) {
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) {
const objectStore = txn.objectStore("device_data");
const getReq = objectStore.get("-");
@@ -662,6 +762,21 @@ export function upgradeDatabase(db, oldVersion) {
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.
}

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations 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");
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.
*/
import logger from '../../logger';
import LocalStorageCryptoStore from './localStorage-crypto-store';
import MemoryCryptoStore from './memory-crypto-store';
import {logger} from '../../logger';
import {LocalStorageCryptoStore} from './localStorage-crypto-store';
import {MemoryCryptoStore} from './memory-crypto-store';
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
import {InvalidCryptoStoreError} from '../../errors';
import * as IndexedDBHelpers from "../../indexeddb-helpers";
@@ -34,7 +35,7 @@ import * as IndexedDBHelpers from "../../indexeddb-helpers";
*
* @implements {module:crypto/store/base~CryptoStore}
*/
export default class IndexedDBCryptoStore {
export class IndexedDBCryptoStore {
/**
* Create a new IndexedDBCryptoStore
*
@@ -104,7 +105,10 @@ export default class IndexedDBCryptoStore {
// we can fall back to a different backend.
return backend.doTxn(
'readonly',
[IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS],
[
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS,
IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS_WITHHELD,
],
(txn) => {
backend.getEndToEndInboundGroupSession('', '', txn, () => {});
}).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
/**
@@ -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
/**
@@ -607,6 +639,8 @@ export default class IndexedDBCryptoStore {
IndexedDBCryptoStore.STORE_ACCOUNT = 'account';
IndexedDBCryptoStore.STORE_SESSIONS = '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_ROOMS = 'rooms';
IndexedDBCryptoStore.STORE_BACKUP = 'sessions_needing_backup';

View File

@@ -1,5 +1,6 @@
/*
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");
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.
*/
import logger from '../../logger';
import MemoryCryptoStore from './memory-crypto-store';
import {logger} from '../../logger';
import {MemoryCryptoStore} from './memory-crypto-store';
/**
* Internal module. Partial localStorage backed storage for e2e.
@@ -30,8 +31,10 @@ import MemoryCryptoStore from './memory-crypto-store';
const E2E_PREFIX = "crypto.";
const KEY_END_TO_END_ACCOUNT = E2E_PREFIX + "account";
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_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_SESSIONS_NEEDING_BACKUP = E2E_PREFIX + "sessionsneedingbackup";
@@ -39,10 +42,18 @@ function keyEndToEndSessions(deviceKey) {
return E2E_PREFIX + "sessions/" + deviceKey;
}
function keyEndToEndSessionProblems(deviceKey) {
return E2E_PREFIX + "session.problems/" + deviceKey;
}
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
return KEY_INBOUND_SESSION_PREFIX + senderKey + "/" + sessionId;
}
function keyEndToEndInboundGroupSessionWithheld(senderKey, sessionId) {
return KEY_INBOUND_SESSION_WITHHELD_PREFIX + senderKey + "/" + sessionId;
}
function keyEndToEndRoomsPrefix(roomId) {
return KEY_ROOMS_PREFIX + roomId;
}
@@ -50,7 +61,7 @@ function keyEndToEndRoomsPrefix(roomId) {
/**
* @implements {module:crypto/store/base~CryptoStore}
*/
export default class LocalStorageCryptoStore extends MemoryCryptoStore {
export class LocalStorageCryptoStore extends MemoryCryptoStore {
constructor(webStore) {
super();
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
getEndToEndInboundGroupSession(senderCurve25519Key, sessionId, txn, func) {
func(getJsonItem(
func(
getJsonItem(
this.store,
keyEndToEndInboundGroupSession(senderCurve25519Key, sessionId),
));
),
getJsonItem(
this.store,
keyEndToEndInboundGroupSessionWithheld(senderCurve25519Key, sessionId),
),
);
}
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) {
func(getJsonItem(
this.store, KEY_DEVICE_DATA,

View File

@@ -1,6 +1,7 @@
/*
Copyright 2017 Vector Creations 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");
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.
*/
import logger from '../../logger';
import utils from '../../utils';
import {logger} from '../../logger';
import * as utils from "../../utils";
/**
* Internal module. in-memory storage for e2e.
@@ -27,7 +28,7 @@ import utils from '../../utils';
/**
* @implements {module:crypto/store/base~CryptoStore}
*/
export default class MemoryCryptoStore {
export class MemoryCryptoStore {
constructor() {
this._outgoingRoomKeyRequests = [];
this._account = null;
@@ -35,8 +36,13 @@ export default class MemoryCryptoStore {
// Map of {devicekey -> {sessionId -> session pickle}}
this._sessions = {};
// Map of {devicekey -> array of problems}
this._sessionProblems = {};
// Map of {userId -> deviceId -> true}
this._notifiedErrorDevices = {};
// Map of {senderCurve25519Key+'/'+sessionId -> session data object}
this._inboundGroupSessions = {};
this._inboundGroupSessionsWithheld = {};
// Opaque device data object
this._deviceData = null;
// roomId -> Opaque roomInfo object
@@ -273,10 +279,61 @@ export default class MemoryCryptoStore {
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
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) {
@@ -306,6 +363,13 @@ export default class MemoryCryptoStore {
this._inboundGroupSessions[senderCurve25519Key+'/'+sessionId] = sessionData;
}
storeEndToEndInboundGroupSessionWithheld(
senderCurve25519Key, sessionId, sessionData, txn,
) {
const k = senderCurve25519Key+'/'+sessionId;
this._inboundGroupSessionsWithheld[k] = sessionData;
}
// Device Data
getEndToEndDeviceData(txn, func) {

View File

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

View File

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

View File

@@ -19,14 +19,14 @@ limitations under the License.
* @module crypto/verification/SAS
*/
import Base from "./Base";
import {VerificationBase as Base} from "./Base";
import anotherjson from 'another-json';
import {
errorFactory,
newUserCancelledError,
newUnknownMethodError,
newKeyMismatchError,
newInvalidMessageError,
newKeyMismatchError,
newUnknownMethodError,
newUserCancelledError,
} from './Error';
const EVENTS = [
@@ -108,7 +108,7 @@ const emojiMapping = [
["✏️", "pencil"], // 43
["📎", "paperclip"], // 44
["✂️", "scissors"], // 45
["🔒", "padlock"], // 46
["🔒", "lock"], // 46
["🔑", "key"], // 47
["🔨", "hammer"], // 48
["☎️", "telephone"], // 49
@@ -185,7 +185,11 @@ function intersection(anArray, aSet) {
* @alias module:crypto/verification/SAS
* @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() {
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.
*/
import VerificationRequest, {
import {
VerificationRequest,
REQUEST_TYPE,
READY_TYPE,
START_TYPE,
} from "./VerificationRequest";
const MESSAGE_TYPE = "m.room.message";
const M_REFERENCE = "m.reference";
const M_RELATES_TO = "m.relates_to";

View File

@@ -16,23 +16,18 @@ limitations under the License.
*/
import {randomString} from '../../../randomstring';
import logger from '../../../logger';
import VerificationRequest, {
import {logger} from '../../../logger';
import {
CANCEL_TYPE,
PHASE_STARTED,
PHASE_READY,
REQUEST_TYPE,
READY_TYPE,
START_TYPE,
CANCEL_TYPE,
VerificationRequest,
} from "./VerificationRequest";
import {
newUnexpectedMessageError,
errorFromEvent,
} from "../Error";
const MatrixEvent = require("../../../models/event").MatrixEvent;
import {errorFromEvent, newUnexpectedMessageError} from "../Error";
import {MatrixEvent} from "../../../models/event";
/**
* 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.
*/
import logger from '../../../logger';
import {logger} from '../../../logger';
import {EventEmitter} from 'events';
import {
newUnknownMethodError,
newUnexpectedMessageError,
errorFromEvent,
errorFactory,
errorFromEvent,
newUnexpectedMessageError,
newUnknownMethodError,
} from "../Error";
// 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`.
* @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) {
super();
this.channel = channel;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module filter-component
*/
@@ -45,7 +46,7 @@ function _matches_wildcard(actual_value, filter_value) {
* @constructor
* @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.types = filter_json.types || null;
@@ -141,6 +142,3 @@ FilterComponent.prototype.filter = function(events) {
FilterComponent.prototype.limit = function() {
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 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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module filter
*/
const FilterComponent = require("./filter-component");
import {FilterComponent} from "./filter-component";
/**
* @param {Object} obj
@@ -45,7 +46,7 @@ function setProp(obj, keyNesting, val) {
* @prop {string} userId The user ID of the filter
* @prop {?string} filterId The filter ID
*/
function Filter(userId, filterId) {
export function Filter(userId, filterId) {
this.userId = userId;
this.filterId = filterId;
this.definition = {};
@@ -198,6 +199,3 @@ Filter.fromJson = function(userId, filterId, jsonObj) {
filter.setDefinition(jsonObj);
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
limitations under the License.
*/
"use strict";
/**
* This is an internal module. See {@link MatrixHttpApi} for the public class.
* @module http-api
*/
const parseContentType = require('content-type').parse;
const utils = require("./utils");
import logger from './logger';
import {parse as parseContentType} from "content-type";
import * as utils from "./utils";
import {logger} from './logger';
// 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
// waiting for the delay to elapse.
const callbacks = require("./realtime-callbacks");
import * as callbacks from "./realtime-callbacks";
/*
TODO:
@@ -38,27 +38,27 @@ TODO:
/**
* 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.
*/
module.exports.PREFIX_UNSTABLE = "/_matrix/client/unstable";
export const PREFIX_UNSTABLE = "/_matrix/client/unstable";
/**
* 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
*/
module.exports.PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
export const PREFIX_IDENTITY_V2 = "/_matrix/identity/v2";
/**
* 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.
@@ -85,16 +85,16 @@ module.exports.PREFIX_MEDIA_R0 = "/_matrix/media/r0";
* @param {boolean} [opts.useAuthorizationHeader = false] Set to true to use
* 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"]);
opts.onlyData = opts.onlyData || false;
this.event_emitter = event_emitter;
this.opts = opts;
this.useAuthorizationHeader = Boolean(opts.useAuthorizationHeader);
this.uploads = [];
};
}
module.exports.MatrixHttpApi.prototype = {
MatrixHttpApi.prototype = {
/**
* Sets the baase URL for the identity server
* @param {string} url The new base url
@@ -698,7 +698,7 @@ module.exports.MatrixHttpApi.prototype = {
if (req && req.abort) {
req.abort();
}
defer.reject(new module.exports.MatrixError({
defer.reject(new MatrixError({
error: "Locally timed out waiting for a response",
errcode: "ORG.MATRIX.JSSDK_TIMEOUT",
timeout: localTimeoutMs,
@@ -836,7 +836,7 @@ function parseErrorResponse(response, body) {
if (contentType) {
if (contentType.type === 'application/json') {
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') {
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 {integer} httpStatus The numeric HTTP status code given
*/
module.exports.MatrixError = function MatrixError(errorJson) {
export function MatrixError(errorJson) {
errorJson = errorJson || {};
this.errcode = errorJson.errcode;
this.name = errorJson.errcode || "Unknown error code";
this.message = errorJson.error || "Unknown message";
this.data = errorJson;
};
module.exports.MatrixError.prototype = Object.create(Error.prototype);
/** */
module.exports.MatrixError.prototype.constructor = module.exports.MatrixError;
}
MatrixError.prototype = Object.create(Error.prototype);
MatrixError.prototype.constructor = 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 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.
@@ -20,5 +21,5 @@ limitations under the License.
*/
/** 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
limitations under the License.
*/
"use strict";
/** @module interactive-auth */
const url = require("url");
const utils = require("./utils");
import logger from './logger';
import url from "url";
import * as utils from "./utils";
import {logger} from './logger';
const EMAIL_STAGE_TYPE = "m.login.email.identity";
const MSISDN_STAGE_TYPE = "m.login.msisdn";
@@ -103,7 +102,7 @@ const MSISDN_STAGE_TYPE = "m.login.msisdn";
* attemptAuth promise.
*
*/
function InteractiveAuth(opts) {
export function InteractiveAuth(opts) {
this._matrixClient = opts.matrixClient;
this._data = opts.authData || {};
this._requestCallback = opts.doRequest;
@@ -510,6 +509,3 @@ InteractiveAuth.prototype = {
},
};
/** */
module.exports = InteractiveAuth;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2018 André Jaenisch
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.
@@ -17,7 +18,8 @@ limitations under the License.
/**
* @module logger
*/
const log = require("loglevel");
import log from "loglevel";
// 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
@@ -25,12 +27,12 @@ const log = require("loglevel");
// 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
// Part of #332 is introducing a logging library in the first place.
const DEFAULT_NAME_SPACE = "matrix";
const logger = log.getLogger(DEFAULT_NAME_SPACE);
logger.setLevel(log.levels.DEBUG);
const DEFAULT_NAMESPACE = "matrix";
/**
* 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.
*/
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
limitations under the License.
*/
"use strict";
/** The {@link module:ContentHelpers} object */
module.exports.ContentHelpers = require("./content-helpers");
/** The {@link module:models/event.MatrixEvent|MatrixEvent} class. */
module.exports.MatrixEvent = require("./models/event").MatrixEvent;
/** 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;
import {MemoryCryptoStore} from "./crypto/store/memory-crypto-store";
import {MemoryStore} from "./store/memory";
import {MatrixScheduler} from "./scheduler";
import {MatrixClient} from "./client";
module.exports.SERVICE_TYPES = require('./service-types').SERVICE_TYPES;
module.exports.MemoryCryptoStore =
require("./crypto/store/memory-crypto-store").default;
module.exports.IndexedDBCryptoStore =
require("./crypto/store/indexeddb-crypto-store").default;
/**
* Create a new Matrix Call.
* @function
* @param {module:client.MatrixClient} client The MatrixClient instance to use.
* @param {string} roomId The room the call is in.
* @return {module:webrtc/call~MatrixCall} The Matrix call or null if the browser
* does not support WebRTC.
*/
module.exports.createNewMatrixCall = require("./webrtc/call").createNewMatrixCall;
/**
* Set a preferred audio output device to use for MatrixCalls
* @function
* @param {string=} deviceId the identifier for the device
* undefined treated as unset
*/
module.exports.setMatrixCallAudioOutput = require('./webrtc/call').setAudioOutput;
/**
* Set a preferred audio input device to use for MatrixCalls
* @function
* @param {string=} deviceId the identifier for the device
* undefined treated as unset
*/
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;
export * from "./client";
export * from "./http-api";
export * from "./autodiscovery";
export * from "./sync-accumulator";
export * from "./errors";
export * from "./models/event";
export * from "./models/room";
export * from "./models/group";
export * from "./models/event-timeline";
export * from "./models/event-timeline-set";
export * from "./models/room-member";
export * from "./models/room-state";
export * from "./models/user";
export * from "./scheduler";
export * from "./filter";
export * from "./timeline-window";
export * from "./interactive-auth";
export * from "./service-types";
export * from "./store/memory";
export * from "./store/indexeddb";
export * from "./store/session/webstorage";
export * from "./crypto/store/memory-crypto-store";
export * from "./crypto/store/indexeddb-crypto-store";
export * from "./content-repo";
export const ContentHelpers = import("./content-helpers");
export {
createNewMatrixCall,
setAudioOutput as setMatrixCallAudioOutput,
setAudioInput as setMatrixCallAudioInput,
setVideoInput as setMatrixCallVideoInput,
} from "./webrtc/call";
// expose the underlying request object so different environments can use
// 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
* use a different HTTP library, e.g. Angular's <code>$http</code>. This should
* be set prior to calling {@link createClient}.
* @param {requestFunction} r The request function to use.
*/
module.exports.request = function(r) {
request = r;
};
export function request(r) {
requestInstance = r;
}
/**
* Return the currently-set request function.
* @return {requestFunction} The current request function.
*/
module.exports.getRequest = function() {
return request;
};
export function getRequest() {
return requestInstance;
}
/**
* 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.
* @param {requestWrapperFunction} wrapper The wrapping function.
*/
module.exports.wrapRequest = function(wrapper) {
const origRequest = request;
request = function(options, callback) {
export function wrapRequest(wrapper) {
const origRequest = requestInstance;
requestInstance = function(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
@@ -161,9 +98,9 @@ let cryptoStoreFactory = () => new module.exports.MemoryCryptoStore;
* @param {Function} fac a function which will return a new
* {@link module:crypto.store.base~CryptoStore}.
*/
module.exports.setCryptoStoreFactory = function(fac) {
export function setCryptoStoreFactory(fac) {
cryptoStoreFactory = fac;
};
}
/**
* 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
* <code>opts</code>.
*/
module.exports.createClient = function(opts) {
export function createClient(opts) {
if (typeof opts === "string") {
opts = {
"baseUrl": opts,
};
}
opts.request = opts.request || request;
opts.store = opts.store || new module.exports.MemoryStore({
opts.request = opts.request || requestInstance;
opts.store = opts.store || new MemoryStore({
localStorage: global.localStorage,
});
opts.scheduler = opts.scheduler || new module.exports.MatrixScheduler();
opts.scheduler = opts.scheduler || new MatrixScheduler();
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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module models/event-context
@@ -33,7 +33,7 @@ limitations under the License.
*
* @constructor
*/
function EventContext(ourEvent) {
export function EventContext(ourEvent) {
this._timeline = [ourEvent];
this._ourEventIndex = 0;
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 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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module models/event-timeline-set
*/
const EventEmitter = require("events").EventEmitter;
const utils = require("../utils");
const EventTimeline = require("./event-timeline");
import {EventEmitter} from "events";
import {EventTimeline} from "./event-timeline";
import {EventStatus} from "./event";
import logger from '../logger';
import Relations from './relations';
import * as utils from "../utils";
import {logger} from '../logger';
import {Relations} from './relations';
// var DEBUG = false;
const DEBUG = true;
@@ -71,7 +73,7 @@ if (DEBUG) {
* via `getRelationsForEvent`.
* 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._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.
* @event module:client~MatrixClient#"Room.timeline"

View File

@@ -1,5 +1,6 @@
/*
Copyright 2016, 2017 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* @module models/event-timeline
*/
const RoomState = require("./room-state");
import {RoomState} from "./room-state";
/**
* Construct a new EventTimeline
@@ -41,7 +41,7 @@ const RoomState = require("./room-state");
* @param {EventTimelineSet} eventTimelineSet the set of timelines this is part of
* @constructor
*/
function EventTimeline(eventTimelineSet) {
export function EventTimeline(eventTimelineSet) {
this._eventTimelineSet = eventTimelineSet;
this._roomId = eventTimelineSet.room ? eventTimelineSet.room.roomId : null;
this._events = [];
@@ -396,8 +396,3 @@ EventTimeline.prototype.toString = function() {
return this._name;
};
/**
* The EventTimeline class
*/
module.exports = EventTimeline;

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@@ -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
limitations under the License.
*/
"use strict";
/**
* 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 utils from '../utils.js';
import logger from '../logger';
import * as utils from '../utils.js';
import {logger} from '../logger';
/**
* Enum for event statuses.
* @readonly
* @enum {string}
*/
const EventStatus = {
export const EventStatus = {
/** The event was not sent and will no longer be retried. */
NOT_SENT: "not_sent",
@@ -48,7 +48,6 @@ const EventStatus = {
/** The event was cancelled before it was successfully sent. */
CANCELLED: "cancelled",
};
module.exports.EventStatus = EventStatus;
const interns = {};
function intern(str) {
@@ -81,7 +80,7 @@ function intern(str) {
* that getDirectionalContent() will return event.content and not event.prev_content.
* Default: true. <strong>This property is experimental and may change.</strong>
*/
module.exports.MatrixEvent = function MatrixEvent(
export const MatrixEvent = function(
event,
) {
// 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;
};
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.

View File

@@ -1,5 +1,6 @@
/*
Copyright 2017 New Vector Ltd
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.
@@ -17,9 +18,9 @@ limitations under the License.
/**
* @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.
@@ -34,7 +35,7 @@ const utils = require("../utils");
* to the group, if myMembership is 'invite'.
* @prop {string} inviter.userId The user ID of the inviter
*/
function Group(groupId) {
export function Group(groupId) {
this.groupId = groupId;
this.name = null;
this.avatarUrl = null;
@@ -70,8 +71,6 @@ Group.prototype.setInviter = function(inviter) {
this.inviter = inviter;
};
module.exports = Group;
/**
* Fires whenever a group's profile information is updated.
* This means the 'name' and 'avatarUrl' properties.

View File

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

View File

@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@@ -13,14 +14,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
"use strict";
/**
* @module models/room-member
*/
const EventEmitter = require("events").EventEmitter;
const ContentRepo = require("../content-repo");
const utils = require("../utils");
import {EventEmitter} from "events";
import {getHttpUriForMxc, getIdenticonUri} from "../content-repo";
import * as utils from "../utils";
/**
* Construct a new room member.
@@ -45,7 +46,7 @@ const utils = require("../utils");
* @prop {Object} events The events describing this RoomMember.
* @prop {MatrixEvent} events.member The m.room.member event for this RoomMember.
*/
function RoomMember(roomId, userId) {
export function RoomMember(roomId, userId) {
this.roomId = roomId;
this.userId = userId;
this.typing = false;
@@ -268,13 +269,13 @@ RoomMember.prototype.getAvatarUrl =
if (!rawUrl && !allowDefault) {
return null;
}
const httpUrl = ContentRepo.getHttpUriForMxc(
const httpUrl = getHttpUriForMxc(
baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks,
);
if (httpUrl) {
return httpUrl;
} else if (allowDefault) {
return ContentRepo.getIdenticonUri(
return getIdenticonUri(
baseUrl, this.userId, width, height,
);
}
@@ -311,10 +312,18 @@ function calculateDisplayName(selfUserId, displayName, roomState) {
// Next check if the name contains something that look like a mxid
// If it does, it may be someone trying to impersonate someone else
// Show full mxid in this case
let disambiguate = /@.+:.+/.test(displayName);
if (!disambiguate) {
// Also show mxid if the display name contains any LTR/RTL characters as these
// make it very difficult for us to find similar *looking* display names
// E.g "Mark" could be cloned by writing "kraM" but in RTL.
disambiguate = /[\u200E\u200F\u202A-\u202F]/.test(displayName);
}
if (!disambiguate) {
// Also show mxid if there are other people with the same or similar
// displayname, after hidden character removal.
let disambiguate = /@.+:.+/.test(displayName);
if (!disambiguate) {
const userIds = roomState.getUserIdsWithDisplayName(displayName);
disambiguate = userIds.some((u) => u !== selfUserId);
}
@@ -325,11 +334,6 @@ function calculateDisplayName(selfUserId, displayName, roomState) {
return displayName;
}
/**
* The RoomMember class.
*/
module.exports = RoomMember;
/**
* Fires whenever any room member's name changes.
* @event module:client~MatrixClient#"RoomMember.name"

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