You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-05 17:02:07 +03:00
Merge remote-tracking branch 'origin/develop' into jryans/4s-new-key-backup
This commit is contained in:
5
.babelrc
5
.babelrc
@@ -3,11 +3,6 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"transform-class-properties",
|
"transform-class-properties",
|
||||||
|
|
||||||
// this transforms async functions into generator functions, which
|
|
||||||
// are then made to use the regenerator module by babel's
|
|
||||||
// transform-regnerator plugin (which is enabled by es2015).
|
|
||||||
"transform-async-to-bluebird",
|
|
||||||
|
|
||||||
// This makes sure that the regenerator runtime is available to
|
// This makes sure that the regenerator runtime is available to
|
||||||
// the transpiled code.
|
// the transpiled code.
|
||||||
"transform-runtime",
|
"transform-runtime",
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,3 +1,48 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
* No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [2.4.6-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.6-rc.1) (2019-12-04)
|
||||||
|
==========================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.5...v2.4.6-rc.1)
|
||||||
|
|
||||||
|
* Update alias handling
|
||||||
|
[\#1102](https://github.com/matrix-org/matrix-js-sdk/pull/1102)
|
||||||
|
* increase timeout on flush to fix failing unit test
|
||||||
|
[\#1096](https://github.com/matrix-org/matrix-js-sdk/pull/1096)
|
||||||
|
* Disable broken cross-signing test
|
||||||
|
[\#1095](https://github.com/matrix-org/matrix-js-sdk/pull/1095)
|
||||||
|
* Fix a couple SAS tests
|
||||||
|
[\#1094](https://github.com/matrix-org/matrix-js-sdk/pull/1094)
|
||||||
|
* Fix Olm unwedging test
|
||||||
|
[\#1093](https://github.com/matrix-org/matrix-js-sdk/pull/1093)
|
||||||
|
* Fix empty string handling in push notifications
|
||||||
|
[\#1089](https://github.com/matrix-org/matrix-js-sdk/pull/1089)
|
||||||
|
* expand e2ee logging to better debug UISIs
|
||||||
|
[\#1090](https://github.com/matrix-org/matrix-js-sdk/pull/1090)
|
||||||
|
* Remove Bluebird: phase 2
|
||||||
|
[\#1087](https://github.com/matrix-org/matrix-js-sdk/pull/1087)
|
||||||
|
* Relax identity server discovery checks to FAIL_PROMPT
|
||||||
|
[\#1062](https://github.com/matrix-org/matrix-js-sdk/pull/1062)
|
||||||
|
* Fix incorrect return value of MatrixClient.prototype.uploadKeys
|
||||||
|
[\#1061](https://github.com/matrix-org/matrix-js-sdk/pull/1061)
|
||||||
|
* Fix calls in e2e rooms
|
||||||
|
[\#1086](https://github.com/matrix-org/matrix-js-sdk/pull/1086)
|
||||||
|
* Monitor verification request over DM as well
|
||||||
|
[\#1085](https://github.com/matrix-org/matrix-js-sdk/pull/1085)
|
||||||
|
* Remove 'check' npm script
|
||||||
|
[\#1084](https://github.com/matrix-org/matrix-js-sdk/pull/1084)
|
||||||
|
* Always process call events in batches
|
||||||
|
[\#1083](https://github.com/matrix-org/matrix-js-sdk/pull/1083)
|
||||||
|
* Fix ringing chirp on loading
|
||||||
|
[\#1082](https://github.com/matrix-org/matrix-js-sdk/pull/1082)
|
||||||
|
* Remove *most* bluebird specific things
|
||||||
|
[\#1081](https://github.com/matrix-org/matrix-js-sdk/pull/1081)
|
||||||
|
* Switch to Jest
|
||||||
|
[\#1080](https://github.com/matrix-org/matrix-js-sdk/pull/1080)
|
||||||
|
|
||||||
Changes in [2.4.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.5) (2019-11-27)
|
Changes in [2.4.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v2.4.5) (2019-11-27)
|
||||||
================================================================================================
|
================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.4...v2.4.5)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v2.4.4...v2.4.5)
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ which will be fulfilled in the future.
|
|||||||
The typical usage is something like:
|
The typical usage is something like:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
matrixClient.someMethod(arg1, arg2).done(function(result) {
|
matrixClient.someMethod(arg1, arg2).then(function(result) {
|
||||||
...
|
...
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -206,7 +206,7 @@ core functionality of the SDK. These examples assume the SDK is setup like this:
|
|||||||
```javascript
|
```javascript
|
||||||
matrixClient.on("RoomMember.membership", function(event, member) {
|
matrixClient.on("RoomMember.membership", function(event, member) {
|
||||||
if (member.membership === "invite" && member.userId === myUserId) {
|
if (member.membership === "invite" && member.userId === myUserId) {
|
||||||
matrixClient.joinRoom(member.roomId).done(function() {
|
matrixClient.joinRoom(member.roomId).then(function() {
|
||||||
console.log("Auto-joined %s", member.roomId);
|
console.log("Auto-joined %s", member.roomId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,7 +297,7 @@ End-to-end encryption support
|
|||||||
=============================
|
=============================
|
||||||
|
|
||||||
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
|
The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
|
||||||
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
||||||
application to make libolm available, via the ``Olm`` global.
|
application to make libolm available, via the ``Olm`` global.
|
||||||
|
|
||||||
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
|
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
|
||||||
@@ -319,7 +319,7 @@ To provide the Olm library in a browser application:
|
|||||||
|
|
||||||
* download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
|
* download the transpiled libolm (from https://packages.matrix.org/npm/olm/).
|
||||||
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
|
* load ``olm.js`` as a ``<script>`` *before* ``browser-matrix.js``.
|
||||||
|
|
||||||
To provide the Olm library in a node.js application:
|
To provide the Olm library in a node.js application:
|
||||||
|
|
||||||
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
|
* ``yarn add https://packages.matrix.org/npm/olm/olm-3.1.4.tgz``
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ rl.on('line', function(line) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (notSentEvent) {
|
if (notSentEvent) {
|
||||||
matrixClient.resendEvent(notSentEvent, viewingRoom).done(function() {
|
matrixClient.resendEvent(notSentEvent, viewingRoom).then(function() {
|
||||||
printMessages();
|
printMessages();
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
@@ -70,7 +70,7 @@ rl.on('line', function(line) {
|
|||||||
}
|
}
|
||||||
else if (line.indexOf("/more ") === 0) {
|
else if (line.indexOf("/more ") === 0) {
|
||||||
var amount = parseInt(line.split(" ")[1]) || 20;
|
var amount = parseInt(line.split(" ")[1]) || 20;
|
||||||
matrixClient.scrollback(viewingRoom, amount).done(function(room) {
|
matrixClient.scrollback(viewingRoom, amount).then(function(room) {
|
||||||
printMessages();
|
printMessages();
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
@@ -79,7 +79,7 @@ rl.on('line', function(line) {
|
|||||||
}
|
}
|
||||||
else if (line.indexOf("/invite ") === 0) {
|
else if (line.indexOf("/invite ") === 0) {
|
||||||
var userId = line.split(" ")[1].trim();
|
var userId = line.split(" ")[1].trim();
|
||||||
matrixClient.invite(viewingRoom.roomId, userId).done(function() {
|
matrixClient.invite(viewingRoom.roomId, userId).then(function() {
|
||||||
printMessages();
|
printMessages();
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
@@ -92,7 +92,7 @@ rl.on('line', function(line) {
|
|||||||
matrixClient.uploadContent({
|
matrixClient.uploadContent({
|
||||||
stream: stream,
|
stream: stream,
|
||||||
name: filename
|
name: filename
|
||||||
}).done(function(url) {
|
}).then(function(url) {
|
||||||
var content = {
|
var content = {
|
||||||
msgtype: "m.file",
|
msgtype: "m.file",
|
||||||
body: filename,
|
body: filename,
|
||||||
@@ -116,7 +116,7 @@ rl.on('line', function(line) {
|
|||||||
viewingRoom = roomList[roomIndex];
|
viewingRoom = roomList[roomIndex];
|
||||||
if (viewingRoom.getMember(myUserId).membership === "invite") {
|
if (viewingRoom.getMember(myUserId).membership === "invite") {
|
||||||
// join the room first
|
// join the room first
|
||||||
matrixClient.joinRoom(viewingRoom.roomId).done(function(room) {
|
matrixClient.joinRoom(viewingRoom.roomId).then(function(room) {
|
||||||
setRoomList();
|
setRoomList();
|
||||||
viewingRoom = room;
|
viewingRoom = room;
|
||||||
printMessages();
|
printMessages();
|
||||||
@@ -128,7 +128,7 @@ rl.on('line', function(line) {
|
|||||||
else {
|
else {
|
||||||
printMessages();
|
printMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rl.prompt();
|
rl.prompt();
|
||||||
});
|
});
|
||||||
@@ -281,8 +281,8 @@ function printMemberList(room) {
|
|||||||
member.membership + new Array(10 - member.membership.length).join(" ")
|
member.membership + new Array(10 - member.membership.length).join(" ")
|
||||||
);
|
);
|
||||||
print(
|
print(
|
||||||
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
|
"%s"+fmt(" :: ")+"%s"+fmt(" (")+"%s"+fmt(")"),
|
||||||
membershipWithPadding, member.name,
|
membershipWithPadding, member.name,
|
||||||
(member.userId === myUserId ? "Me" : member.userId),
|
(member.userId === myUserId ? "Me" : member.userId),
|
||||||
fmt
|
fmt
|
||||||
);
|
);
|
||||||
@@ -295,7 +295,7 @@ function printRoomInfo(room) {
|
|||||||
var sendHeader = " Sender ";
|
var sendHeader = " Sender ";
|
||||||
// pad content to 100
|
// pad content to 100
|
||||||
var restCount = (
|
var restCount = (
|
||||||
100 - "Content".length - " | ".length - " | ".length -
|
100 - "Content".length - " | ".length - " | ".length -
|
||||||
eTypeHeader.length - sendHeader.length
|
eTypeHeader.length - sendHeader.length
|
||||||
);
|
);
|
||||||
var padSide = new Array(Math.floor(restCount/2)).join(" ");
|
var padSide = new Array(Math.floor(restCount/2)).join(" ");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "matrix-js-sdk",
|
"name": "matrix-js-sdk",
|
||||||
"version": "2.4.5",
|
"version": "2.4.6",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -50,7 +50,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"another-json": "^0.2.0",
|
"another-json": "^0.2.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bluebird": "3.5.5",
|
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"content-type": "^1.0.2",
|
"content-type": "^1.0.2",
|
||||||
@@ -63,7 +62,6 @@
|
|||||||
"babel-cli": "^6.18.0",
|
"babel-cli": "^6.18.0",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-jest": "^23.6.0",
|
"babel-jest": "^23.6.0",
|
||||||
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
|
||||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-preset-es2015": "^6.18.0",
|
"babel-preset-es2015": "^6.18.0",
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import './olm-loader';
|
|||||||
import sdk from '..';
|
import sdk from '..';
|
||||||
import testUtils from './test-utils';
|
import testUtils from './test-utils';
|
||||||
import MockHttpBackend from 'matrix-mock-request';
|
import MockHttpBackend from 'matrix-mock-request';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
|
import LocalStorageCryptoStore from '../lib/crypto/store/localStorage-crypto-store';
|
||||||
import logger from '../src/logger';
|
import logger from '../src/logger';
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import TestClient from '../TestClient';
|
import TestClient from '../TestClient';
|
||||||
import testUtils from '../test-utils';
|
import testUtils from '../test-utils';
|
||||||
import logger from '../../src/logger';
|
import logger from '../../src/logger';
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import 'source-map-support/register';
|
|||||||
import '../olm-loader';
|
import '../olm-loader';
|
||||||
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
import Promise from 'bluebird';
|
|
||||||
const utils = require("../../lib/utils");
|
const utils = require("../../lib/utils");
|
||||||
const testUtils = require("../test-utils");
|
const testUtils = require("../test-utils");
|
||||||
const TestClient = require('../TestClient').default;
|
const TestClient = require('../TestClient').default;
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ const sdk = require("../..");
|
|||||||
const HttpBackend = require("matrix-mock-request");
|
const HttpBackend = require("matrix-mock-request");
|
||||||
const utils = require("../test-utils");
|
const utils = require("../test-utils");
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
describe("MatrixClient events", function() {
|
describe("MatrixClient events", function() {
|
||||||
const baseUrl = "http://localhost.or.something";
|
const baseUrl = "http://localhost.or.something";
|
||||||
let client;
|
let client;
|
||||||
@@ -162,7 +160,7 @@ describe("MatrixClient events", function() {
|
|||||||
});
|
});
|
||||||
client.startClient();
|
client.startClient();
|
||||||
|
|
||||||
httpBackend.flushAllExpected().done(function() {
|
httpBackend.flushAllExpected().then(function() {
|
||||||
expect(fired).toBe(true, "User.presence didn't fire.");
|
expect(fired).toBe(true, "User.presence didn't fire.");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
const HttpBackend = require("matrix-mock-request");
|
const HttpBackend = require("matrix-mock-request");
|
||||||
const utils = require("../test-utils");
|
const utils = require("../test-utils");
|
||||||
@@ -356,7 +355,7 @@ describe("MatrixClient event timelines", function() {
|
|||||||
.toEqual("start_token");
|
.toEqual("start_token");
|
||||||
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
|
// expect(tl.getPaginationToken(EventTimeline.FORWARDS))
|
||||||
// .toEqual("s_5_4");
|
// .toEqual("s_5_4");
|
||||||
}).done(resolve, reject);
|
}).then(resolve, reject);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ describe("MatrixClient", function() {
|
|||||||
event_format: "client",
|
event_format: "client",
|
||||||
});
|
});
|
||||||
store.storeFilter(filter);
|
store.storeFilter(filter);
|
||||||
client.getFilter(userId, filterId, true).done(function(gotFilter) {
|
client.getFilter(userId, filterId, true).then(function(gotFilter) {
|
||||||
expect(gotFilter).toEqual(filter);
|
expect(gotFilter).toEqual(filter);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -202,7 +202,7 @@ describe("MatrixClient", function() {
|
|||||||
event_format: "client",
|
event_format: "client",
|
||||||
});
|
});
|
||||||
store.storeFilter(storeFilter);
|
store.storeFilter(storeFilter);
|
||||||
client.getFilter(userId, filterId, false).done(function(gotFilter) {
|
client.getFilter(userId, filterId, false).then(function(gotFilter) {
|
||||||
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -220,7 +220,7 @@ describe("MatrixClient", function() {
|
|||||||
httpBackend.when(
|
httpBackend.when(
|
||||||
"GET", "/user/" + encodeURIComponent(userId) + "/filter/" + filterId,
|
"GET", "/user/" + encodeURIComponent(userId) + "/filter/" + filterId,
|
||||||
).respond(200, httpFilterDefinition);
|
).respond(200, httpFilterDefinition);
|
||||||
client.getFilter(userId, filterId, true).done(function(gotFilter) {
|
client.getFilter(userId, filterId, true).then(function(gotFilter) {
|
||||||
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
expect(gotFilter.getDefinition()).toEqual(httpFilterDefinition);
|
||||||
expect(store.getFilter(userId, filterId)).toBeTruthy();
|
expect(store.getFilter(userId, filterId)).toBeTruthy();
|
||||||
done();
|
done();
|
||||||
@@ -248,7 +248,7 @@ describe("MatrixClient", function() {
|
|||||||
filter_id: filterId,
|
filter_id: filterId,
|
||||||
});
|
});
|
||||||
|
|
||||||
client.createFilter(filterDefinition).done(function(gotFilter) {
|
client.createFilter(filterDefinition).then(function(gotFilter) {
|
||||||
expect(gotFilter.getDefinition()).toEqual(filterDefinition);
|
expect(gotFilter.getDefinition()).toEqual(filterDefinition);
|
||||||
expect(store.getFilter(userId, filterId)).toEqual(gotFilter);
|
expect(store.getFilter(userId, filterId)).toEqual(gotFilter);
|
||||||
done();
|
done();
|
||||||
@@ -295,7 +295,7 @@ describe("MatrixClient", function() {
|
|||||||
});
|
});
|
||||||
}).respond(200, response);
|
}).respond(200, response);
|
||||||
|
|
||||||
httpBackend.flush().done(function() {
|
httpBackend.flush().then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ const MatrixClient = sdk.MatrixClient;
|
|||||||
const HttpBackend = require("matrix-mock-request");
|
const HttpBackend = require("matrix-mock-request");
|
||||||
const utils = require("../test-utils");
|
const utils = require("../test-utils");
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
describe("MatrixClient opts", function() {
|
describe("MatrixClient opts", function() {
|
||||||
const baseUrl = "http://localhost.or.something";
|
const baseUrl = "http://localhost.or.something";
|
||||||
let client = null;
|
let client = null;
|
||||||
@@ -86,7 +84,7 @@ describe("MatrixClient opts", function() {
|
|||||||
httpBackend.when("PUT", "/txn1").respond(200, {
|
httpBackend.when("PUT", "/txn1").respond(200, {
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
});
|
});
|
||||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||||
expect(res.event_id).toEqual(eventId);
|
expect(res.event_id).toEqual(eventId);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -139,7 +137,7 @@ describe("MatrixClient opts", function() {
|
|||||||
errcode: "M_SOMETHING",
|
errcode: "M_SOMETHING",
|
||||||
error: "Ruh roh",
|
error: "Ruh roh",
|
||||||
});
|
});
|
||||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||||
expect(false).toBe(true, "sendTextMessage resolved but shouldn't");
|
expect(false).toBe(true, "sendTextMessage resolved but shouldn't");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
expect(err.errcode).toEqual("M_SOMETHING");
|
expect(err.errcode).toEqual("M_SOMETHING");
|
||||||
@@ -157,16 +155,16 @@ describe("MatrixClient opts", function() {
|
|||||||
});
|
});
|
||||||
let sentA = false;
|
let sentA = false;
|
||||||
let sentB = false;
|
let sentB = false;
|
||||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||||
sentA = true;
|
sentA = true;
|
||||||
expect(sentB).toBe(true);
|
expect(sentB).toBe(true);
|
||||||
});
|
});
|
||||||
client.sendTextMessage("!foo:bar", "b body", "txn2").done(function(res) {
|
client.sendTextMessage("!foo:bar", "b body", "txn2").then(function(res) {
|
||||||
sentB = true;
|
sentB = true;
|
||||||
expect(sentA).toBe(false);
|
expect(sentA).toBe(false);
|
||||||
});
|
});
|
||||||
httpBackend.flush("/txn2", 1).done(function() {
|
httpBackend.flush("/txn2", 1).then(function() {
|
||||||
httpBackend.flush("/txn1", 1).done(function() {
|
httpBackend.flush("/txn1", 1).then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -176,7 +174,7 @@ describe("MatrixClient opts", function() {
|
|||||||
httpBackend.when("PUT", "/txn1").respond(200, {
|
httpBackend.when("PUT", "/txn1").respond(200, {
|
||||||
event_id: "foo",
|
event_id: "foo",
|
||||||
});
|
});
|
||||||
client.sendTextMessage("!foo:bar", "a body", "txn1").done(function(res) {
|
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||||
expect(res.event_id).toEqual("foo");
|
expect(res.event_id).toEqual("foo");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
const HttpBackend = require("matrix-mock-request");
|
const HttpBackend = require("matrix-mock-request");
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ const EventStatus = sdk.EventStatus;
|
|||||||
const HttpBackend = require("matrix-mock-request");
|
const HttpBackend = require("matrix-mock-request");
|
||||||
const utils = require("../test-utils");
|
const utils = require("../test-utils");
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
describe("MatrixClient room timelines", function() {
|
describe("MatrixClient room timelines", function() {
|
||||||
const baseUrl = "http://localhost.or.something";
|
const baseUrl = "http://localhost.or.something";
|
||||||
let client = null;
|
let client = null;
|
||||||
@@ -151,7 +149,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
expect(member.userId).toEqual(userId);
|
expect(member.userId).toEqual(userId);
|
||||||
expect(member.name).toEqual(userName);
|
expect(member.name).toEqual(userName);
|
||||||
|
|
||||||
httpBackend.flush("/sync", 1).done(function() {
|
httpBackend.flush("/sync", 1).then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -177,10 +175,10 @@ describe("MatrixClient room timelines", function() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
client.sendTextMessage(roomId, "I am a fish", "txn1").done(
|
client.sendTextMessage(roomId, "I am a fish", "txn1").then(
|
||||||
function() {
|
function() {
|
||||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||||
httpBackend.flush("/sync", 1).done(function() {
|
httpBackend.flush("/sync", 1).then(function() {
|
||||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -210,10 +208,10 @@ describe("MatrixClient room timelines", function() {
|
|||||||
}
|
}
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
|
const promise = client.sendTextMessage(roomId, "I am a fish", "txn1");
|
||||||
httpBackend.flush("/sync", 1).done(function() {
|
httpBackend.flush("/sync", 1).then(function() {
|
||||||
expect(room.timeline.length).toEqual(2);
|
expect(room.timeline.length).toEqual(2);
|
||||||
httpBackend.flush("/txn1", 1);
|
httpBackend.flush("/txn1", 1);
|
||||||
promise.done(function() {
|
promise.then(function() {
|
||||||
expect(room.timeline.length).toEqual(2);
|
expect(room.timeline.length).toEqual(2);
|
||||||
expect(room.timeline[1].getId()).toEqual(eventId);
|
expect(room.timeline[1].getId()).toEqual(eventId);
|
||||||
done();
|
done();
|
||||||
@@ -248,7 +246,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
expect(room.timeline.length).toEqual(1);
|
expect(room.timeline.length).toEqual(1);
|
||||||
|
|
||||||
client.scrollback(room).done(function() {
|
client.scrollback(room).then(function() {
|
||||||
expect(room.timeline.length).toEqual(1);
|
expect(room.timeline.length).toEqual(1);
|
||||||
expect(room.oldState.paginationToken).toBe(null);
|
expect(room.oldState.paginationToken).toBe(null);
|
||||||
|
|
||||||
@@ -312,7 +310,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
// sync response
|
// sync response
|
||||||
expect(room.timeline.length).toEqual(1);
|
expect(room.timeline.length).toEqual(1);
|
||||||
|
|
||||||
client.scrollback(room).done(function() {
|
client.scrollback(room).then(function() {
|
||||||
expect(room.timeline.length).toEqual(5);
|
expect(room.timeline.length).toEqual(5);
|
||||||
const joinMsg = room.timeline[0];
|
const joinMsg = room.timeline[0];
|
||||||
expect(joinMsg.sender.name).toEqual("Old Alice");
|
expect(joinMsg.sender.name).toEqual("Old Alice");
|
||||||
@@ -350,7 +348,7 @@ describe("MatrixClient room timelines", function() {
|
|||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
expect(room.timeline.length).toEqual(1);
|
expect(room.timeline.length).toEqual(1);
|
||||||
|
|
||||||
client.scrollback(room).done(function() {
|
client.scrollback(room).then(function() {
|
||||||
expect(room.timeline.length).toEqual(3);
|
expect(room.timeline.length).toEqual(3);
|
||||||
expect(room.timeline[0].event).toEqual(sbEvents[1]);
|
expect(room.timeline[0].event).toEqual(sbEvents[1]);
|
||||||
expect(room.timeline[1].event).toEqual(sbEvents[0]);
|
expect(room.timeline[1].event).toEqual(sbEvents[0]);
|
||||||
@@ -381,11 +379,11 @@ describe("MatrixClient room timelines", function() {
|
|||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
expect(room.oldState.paginationToken).toBeTruthy();
|
expect(room.oldState.paginationToken).toBeTruthy();
|
||||||
|
|
||||||
client.scrollback(room, 1).done(function() {
|
client.scrollback(room, 1).then(function() {
|
||||||
expect(room.oldState.paginationToken).toEqual(sbEndTok);
|
expect(room.oldState.paginationToken).toEqual(sbEndTok);
|
||||||
});
|
});
|
||||||
|
|
||||||
httpBackend.flush("/messages", 1).done(function() {
|
httpBackend.flush("/messages", 1).then(function() {
|
||||||
// still have a sync to flush
|
// still have a sync to flush
|
||||||
httpBackend.flush("/sync", 1).then(() => {
|
httpBackend.flush("/sync", 1).then(() => {
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ const utils = require("../test-utils");
|
|||||||
const MatrixEvent = sdk.MatrixEvent;
|
const MatrixEvent = sdk.MatrixEvent;
|
||||||
const EventTimeline = sdk.EventTimeline;
|
const EventTimeline = sdk.EventTimeline;
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
describe("MatrixClient syncing", function() {
|
describe("MatrixClient syncing", function() {
|
||||||
const baseUrl = "http://localhost.or.something";
|
const baseUrl = "http://localhost.or.something";
|
||||||
let client = null;
|
let client = null;
|
||||||
@@ -51,7 +49,7 @@ describe("MatrixClient syncing", function() {
|
|||||||
|
|
||||||
client.startClient();
|
client.startClient();
|
||||||
|
|
||||||
httpBackend.flushAllExpected().done(function() {
|
httpBackend.flushAllExpected().then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -65,7 +63,7 @@ describe("MatrixClient syncing", function() {
|
|||||||
|
|
||||||
client.startClient();
|
client.startClient();
|
||||||
|
|
||||||
httpBackend.flushAllExpected().done(function() {
|
httpBackend.flushAllExpected().then(function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const anotherjson = require('another-json');
|
const anotherjson = require('another-json');
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
const utils = require('../../lib/utils');
|
const utils = require('../../lib/utils');
|
||||||
const testUtils = require('../test-utils');
|
const testUtils = require('../test-utils');
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
// load olm before the sdk if possible
|
// load olm before the sdk if possible
|
||||||
import './olm-loader';
|
import './olm-loader';
|
||||||
|
|
||||||
@@ -256,7 +254,7 @@ HttpResponse.prototype.request = function HttpResponse(
|
|||||||
if (!next) { // no more things to return
|
if (!next) { // no more things to return
|
||||||
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
||||||
logger.log("MatrixClient[UT] Ignoring.");
|
logger.log("MatrixClient[UT] Ignoring.");
|
||||||
return Promise.defer().promise;
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
if (this.pendingLookup) {
|
if (this.pendingLookup) {
|
||||||
if (this.pendingLookup.method === method
|
if (this.pendingLookup.method === method
|
||||||
@@ -271,7 +269,7 @@ HttpResponse.prototype.request = function HttpResponse(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.pendingLookup = {
|
this.pendingLookup = {
|
||||||
promise: Promise.defer().promise,
|
promise: new Promise(() => {}),
|
||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
};
|
};
|
||||||
@@ -308,10 +306,10 @@ HttpResponse.prototype.request = function HttpResponse(
|
|||||||
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
} else if (method === "GET" && path === "/sync" && this.ignoreUnhandledSync) {
|
||||||
logger.log("MatrixClient[UT] Ignoring.");
|
logger.log("MatrixClient[UT] Ignoring.");
|
||||||
this.httpLookups.unshift(next);
|
this.httpLookups.unshift(next);
|
||||||
return Promise.defer().promise;
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
expect(true).toBe(false, "Expected different request. " + logLine);
|
expect(true).toBe(false, "Expected different request. " + logLine);
|
||||||
return Promise.defer().promise;
|
return new Promise(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
HttpResponse.KEEP_ALIVE_PATH = "/_matrix/client/versions";
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ limitations under the License.
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
|
|
||||||
const AutoDiscovery = sdk.AutoDiscovery;
|
const AutoDiscovery = sdk.AutoDiscovery;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import TestClient from '../TestClient';
|
|||||||
import {MatrixEvent} from '../../lib/models/event';
|
import {MatrixEvent} from '../../lib/models/event';
|
||||||
import Room from '../../lib/models/room';
|
import Room from '../../lib/models/room';
|
||||||
import olmlib from '../../lib/crypto/olmlib';
|
import olmlib from '../../lib/crypto/olmlib';
|
||||||
|
import {sleep} from "../../src/utils";
|
||||||
|
|
||||||
const EventEmitter = require("events").EventEmitter;
|
const EventEmitter = require("events").EventEmitter;
|
||||||
|
|
||||||
@@ -18,8 +19,6 @@ const sdk = require("../..");
|
|||||||
|
|
||||||
const Olm = global.Olm;
|
const Olm = global.Olm;
|
||||||
|
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
describe("Crypto", function() {
|
describe("Crypto", function() {
|
||||||
if (!sdk.CRYPTO_ENABLED) {
|
if (!sdk.CRYPTO_ENABLED) {
|
||||||
return;
|
return;
|
||||||
@@ -270,7 +269,8 @@ describe("Crypto", function() {
|
|||||||
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
await bobDecryptor.onRoomKeyEvent(ksEvent);
|
||||||
await eventPromise;
|
await eventPromise;
|
||||||
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
|
expect(events[0].getContent().msgtype).not.toBe("m.bad.encrypted");
|
||||||
// the room key request should be gone since we've now decypted everything
|
await sleep(1);
|
||||||
|
// the room key request should be gone since we've now decrypted everything
|
||||||
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
|
expect(await cryptoStore.getOutgoingRoomKeyRequest(roomKeyRequestBody))
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
},
|
},
|
||||||
@@ -301,6 +301,8 @@ describe("Crypto", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses a new txnid for re-requesting keys", async function() {
|
it("uses a new txnid for re-requesting keys", async function() {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
const event = new MatrixEvent({
|
const event = new MatrixEvent({
|
||||||
sender: "@bob:example.com",
|
sender: "@bob:example.com",
|
||||||
room_id: "!someroom",
|
room_id: "!someroom",
|
||||||
@@ -310,52 +312,31 @@ describe("Crypto", function() {
|
|||||||
sender_key: "senderkey",
|
sender_key: "senderkey",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
/* return a promise and a function. When the function is called,
|
// replace Alice's sendToDevice function with a mock
|
||||||
* the promise will be resolved.
|
aliceClient.sendToDevice = jest.fn().mockResolvedValue(undefined);
|
||||||
*/
|
|
||||||
function awaitFunctionCall() {
|
|
||||||
let func;
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
func = function(...args) {
|
|
||||||
resolve(args);
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// give us some time to process the result before
|
|
||||||
// continuing
|
|
||||||
global.setTimeout(resolve, 1);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {func, promise};
|
|
||||||
}
|
|
||||||
|
|
||||||
aliceClient.startClient();
|
aliceClient.startClient();
|
||||||
|
|
||||||
let promise;
|
|
||||||
// make a room key request, and record the transaction ID for the
|
// make a room key request, and record the transaction ID for the
|
||||||
// sendToDevice call
|
// sendToDevice call
|
||||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
|
||||||
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
let args = await promise;
|
await Promise.resolve();
|
||||||
const txnId = args[2];
|
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
|
||||||
jest.runAllTimers();
|
const txnId = aliceClient.sendToDevice.mock.calls[0][2];
|
||||||
|
|
||||||
// give the room key request manager time to update the state
|
// give the room key request manager time to update the state
|
||||||
// of the request
|
// of the request
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
|
|
||||||
// cancel and resend the room key request
|
// cancel and resend the room key request
|
||||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
|
||||||
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
await aliceClient.cancelAndResendEventRoomKeyRequest(event);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
|
await Promise.resolve();
|
||||||
|
// cancelAndResend will call sendToDevice twice:
|
||||||
// the first call to sendToDevice will be the cancellation
|
// the first call to sendToDevice will be the cancellation
|
||||||
args = await promise;
|
|
||||||
// the second call to sendToDevice will be the key request
|
// the second call to sendToDevice will be the key request
|
||||||
({promise, func: aliceClient.sendToDevice} = awaitFunctionCall());
|
expect(aliceClient.sendToDevice).toBeCalledTimes(3);
|
||||||
jest.runAllTimers();
|
expect(aliceClient.sendToDevice.mock.calls[2][2]).not.toBe(txnId);
|
||||||
args = await promise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
expect(args[2]).not.toBe(txnId);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js'
|
|||||||
import utils from '../../../lib/utils';
|
import utils from '../../../lib/utils';
|
||||||
import logger from '../../../src/logger';
|
import logger from '../../../src/logger';
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
const signedDeviceList = {
|
const signedDeviceList = {
|
||||||
"failures": {},
|
"failures": {},
|
||||||
"device_keys": {
|
"device_keys": {
|
||||||
@@ -87,7 +85,7 @@ describe('DeviceList', function() {
|
|||||||
|
|
||||||
dl.startTrackingDeviceList('@test1:sw1v.org');
|
dl.startTrackingDeviceList('@test1:sw1v.org');
|
||||||
|
|
||||||
const queryDefer1 = Promise.defer();
|
const queryDefer1 = utils.defer();
|
||||||
downloadSpy.mockReturnValue(queryDefer1.promise);
|
downloadSpy.mockReturnValue(queryDefer1.promise);
|
||||||
|
|
||||||
const prom1 = dl.refreshOutdatedDeviceLists();
|
const prom1 = dl.refreshOutdatedDeviceLists();
|
||||||
@@ -106,7 +104,7 @@ describe('DeviceList', function() {
|
|||||||
|
|
||||||
dl.startTrackingDeviceList('@test1:sw1v.org');
|
dl.startTrackingDeviceList('@test1:sw1v.org');
|
||||||
|
|
||||||
const queryDefer1 = Promise.defer();
|
const queryDefer1 = utils.defer();
|
||||||
downloadSpy.mockReturnValue(queryDefer1.promise);
|
downloadSpy.mockReturnValue(queryDefer1.promise);
|
||||||
|
|
||||||
const prom1 = dl.refreshOutdatedDeviceLists();
|
const prom1 = dl.refreshOutdatedDeviceLists();
|
||||||
@@ -114,7 +112,7 @@ describe('DeviceList', function() {
|
|||||||
downloadSpy.mockReset();
|
downloadSpy.mockReset();
|
||||||
|
|
||||||
// outdated notif arrives while the request is in flight.
|
// outdated notif arrives while the request is in flight.
|
||||||
const queryDefer2 = Promise.defer();
|
const queryDefer2 = utils.defer();
|
||||||
downloadSpy.mockReturnValue(queryDefer2.promise);
|
downloadSpy.mockReturnValue(queryDefer2.promise);
|
||||||
|
|
||||||
dl.invalidateUserDeviceList('@test1:sw1v.org');
|
dl.invalidateUserDeviceList('@test1:sw1v.org');
|
||||||
@@ -134,7 +132,7 @@ describe('DeviceList', function() {
|
|||||||
logger.log("Creating new devicelist to simulate app reload");
|
logger.log("Creating new devicelist to simulate app reload");
|
||||||
downloadSpy.mockReset();
|
downloadSpy.mockReset();
|
||||||
const dl2 = createTestDeviceList();
|
const dl2 = createTestDeviceList();
|
||||||
const queryDefer3 = Promise.defer();
|
const queryDefer3 = utils.defer();
|
||||||
downloadSpy.mockReturnValue(queryDefer3.promise);
|
downloadSpy.mockReturnValue(queryDefer3.promise);
|
||||||
|
|
||||||
const prom3 = dl2.refreshOutdatedDeviceLists();
|
const prom3 = dl2.refreshOutdatedDeviceLists();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import '../../../olm-loader';
|
import '../../../olm-loader';
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import sdk from '../../../..';
|
import sdk from '../../../..';
|
||||||
import algorithms from '../../../../lib/crypto/algorithms';
|
import algorithms from '../../../../lib/crypto/algorithms';
|
||||||
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||||
@@ -56,7 +54,7 @@ describe("MegolmDecryption", function() {
|
|||||||
mockOlmLib = {};
|
mockOlmLib = {};
|
||||||
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
||||||
mockOlmLib.encryptMessageForDevice =
|
mockOlmLib.encryptMessageForDevice =
|
||||||
jest.fn().mockReturnValue(Promise.resolve());
|
jest.fn().mockResolvedValue(undefined);
|
||||||
megolmDecryption.olmlib = mockOlmLib;
|
megolmDecryption.olmlib = mockOlmLib;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,11 +134,11 @@ describe("MegolmDecryption", function() {
|
|||||||
const deviceInfo = {};
|
const deviceInfo = {};
|
||||||
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
|
mockCrypto.getStoredDevice.mockReturnValue(deviceInfo);
|
||||||
|
|
||||||
mockOlmLib.ensureOlmSessionsForDevices.mockReturnValue(
|
mockOlmLib.ensureOlmSessionsForDevices.mockResolvedValue({
|
||||||
Promise.resolve({'@alice:foo': {'alidevice': {
|
'@alice:foo': {'alidevice': {
|
||||||
sessionId: 'alisession',
|
sessionId: 'alisession',
|
||||||
}}}),
|
}},
|
||||||
);
|
});
|
||||||
|
|
||||||
const awaitEncryptForDevice = new Promise((res, rej) => {
|
const awaitEncryptForDevice = new Promise((res, rej) => {
|
||||||
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
|
mockOlmLib.encryptMessageForDevice.mockImplementation(() => {
|
||||||
@@ -282,7 +280,7 @@ describe("MegolmDecryption", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
mockBaseApis.sendToDevice = jest.fn().mockReturnValue(Promise.resolve());
|
mockBaseApis.sendToDevice = jest.fn().mockResolvedValue(undefined);
|
||||||
|
|
||||||
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
|
mockCrypto.downloadKeys.mockReturnValue(Promise.resolve({
|
||||||
'@alice:home.server': {
|
'@alice:home.server': {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ limitations under the License.
|
|||||||
|
|
||||||
import '../../olm-loader';
|
import '../../olm-loader';
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import sdk from '../../..';
|
import sdk from '../../..';
|
||||||
import algorithms from '../../../lib/crypto/algorithms';
|
import algorithms from '../../../lib/crypto/algorithms';
|
||||||
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
|
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
|
||||||
@@ -157,7 +155,7 @@ describe("MegolmBackup", function() {
|
|||||||
mockOlmLib = {};
|
mockOlmLib = {};
|
||||||
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
mockOlmLib.ensureOlmSessionsForDevices = jest.fn();
|
||||||
mockOlmLib.encryptMessageForDevice =
|
mockOlmLib.encryptMessageForDevice =
|
||||||
jest.fn().mockReturnValue(Promise.resolve());
|
jest.fn().mockResolvedValue(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("backup", function() {
|
describe("backup", function() {
|
||||||
|
|||||||
@@ -789,6 +789,9 @@ describe("Cross Signing", function() {
|
|||||||
upgradeResolveFunc = resolve;
|
upgradeResolveFunc = resolve;
|
||||||
});
|
});
|
||||||
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
alice._crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
alice._crypto.on("userTrustStatusChanged", resolve);
|
||||||
|
});
|
||||||
await upgradePromise;
|
await upgradePromise;
|
||||||
|
|
||||||
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
|
const bobTrust3 = alice.checkUserTrust("@bob:example.com");
|
||||||
|
|||||||
@@ -39,7 +39,10 @@ describe("QR code verification", function() {
|
|||||||
|
|
||||||
describe("showing", function() {
|
describe("showing", function() {
|
||||||
it("should emit an event to show a QR code", async function() {
|
it("should emit an event to show a QR code", async function() {
|
||||||
const qrCode = new ShowQRCode({
|
const channel = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
const qrCode = new ShowQRCode(channel, {
|
||||||
getUserId: () => "@alice:example.com",
|
getUserId: () => "@alice:example.com",
|
||||||
deviceId: "ABCDEFG",
|
deviceId: "ABCDEFG",
|
||||||
getDeviceEd25519Key: function() {
|
getDeviceEd25519Key: function() {
|
||||||
@@ -79,7 +82,10 @@ describe("QR code verification", function() {
|
|||||||
getStoredDevice: jest.fn().mockReturnValue(device),
|
getStoredDevice: jest.fn().mockReturnValue(device),
|
||||||
setDeviceVerified: jest.fn(),
|
setDeviceVerified: jest.fn(),
|
||||||
};
|
};
|
||||||
const qrCode = new ScanQRCode(client);
|
const channel = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
const qrCode = new ScanQRCode(channel, client);
|
||||||
qrCode.on("confirm_user_id", ({userId, confirm}) => {
|
qrCode.on("confirm_user_id", ({userId, confirm}) => {
|
||||||
if (userId === "@alice:example.com") {
|
if (userId === "@alice:example.com") {
|
||||||
confirm();
|
confirm();
|
||||||
@@ -102,13 +108,17 @@ describe("QR code verification", function() {
|
|||||||
getStoredDevice: jest.fn(),
|
getStoredDevice: jest.fn(),
|
||||||
setDeviceVerified: jest.fn(),
|
setDeviceVerified: jest.fn(),
|
||||||
};
|
};
|
||||||
const qrCode = new ScanQRCode(client, "@bob:example.com", "ABCDEFG");
|
const channel = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
const qrCode = new ScanQRCode(channel, client, "@bob:example.com", "ABCDEFG");
|
||||||
qrCode.on("scan", ({done}) => {
|
qrCode.on("scan", ({done}) => {
|
||||||
done(QR_CODE_URL);
|
done(QR_CODE_URL);
|
||||||
});
|
});
|
||||||
const spy = jest.fn();
|
const spy = jest.fn();
|
||||||
await qrCode.verify().catch(spy);
|
await qrCode.verify().catch(spy);
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(channel.send).toHaveBeenCalled();
|
||||||
expect(client.getStoredDevice).not.toHaveBeenCalled();
|
expect(client.getStoredDevice).not.toHaveBeenCalled();
|
||||||
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -131,13 +141,18 @@ describe("QR code verification", function() {
|
|||||||
getStoredDevice: jest.fn().mockReturnValue(device),
|
getStoredDevice: jest.fn().mockReturnValue(device),
|
||||||
setDeviceVerified: jest.fn(),
|
setDeviceVerified: jest.fn(),
|
||||||
};
|
};
|
||||||
const qrCode = new ScanQRCode(client, "@alice:example.com", "ABCDEFG");
|
const channel = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
const qrCode = new ScanQRCode(
|
||||||
|
channel, client, "@alice:example.com", "ABCDEFG");
|
||||||
qrCode.on("scan", ({done}) => {
|
qrCode.on("scan", ({done}) => {
|
||||||
done(QR_CODE_URL);
|
done(QR_CODE_URL);
|
||||||
});
|
});
|
||||||
const spy = jest.fn();
|
const spy = jest.fn();
|
||||||
await qrCode.verify().catch(spy);
|
await qrCode.verify().catch(spy);
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(channel.send).toHaveBeenCalled();
|
||||||
expect(client.getStoredDevice).toHaveBeenCalled();
|
expect(client.getStoredDevice).toHaveBeenCalled();
|
||||||
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
expect(client.setDeviceVerified).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -430,32 +430,26 @@ describe("SAS verification", function() {
|
|||||||
bobSasEvent = null;
|
bobSasEvent = null;
|
||||||
|
|
||||||
bobPromise = new Promise((resolve, reject) => {
|
bobPromise = new Promise((resolve, reject) => {
|
||||||
bob.client.on("event", async (event) => {
|
bob.client.on("crypto.verification.request", async (request) => {
|
||||||
const content = event.getContent();
|
const verifier = request.beginKeyVerification(SAS.NAME);
|
||||||
if (event.getType() === "m.room.message"
|
verifier.on("show_sas", (e) => {
|
||||||
&& content.msgtype === "m.key.verification.request") {
|
if (!e.sas.emoji || !e.sas.decimal) {
|
||||||
expect(content.methods).toContain(SAS.NAME);
|
e.cancel();
|
||||||
expect(content.to).toBe(bob.client.getUserId());
|
} else if (!aliceSasEvent) {
|
||||||
const verifier = bob.client.acceptVerificationDM(event, SAS.NAME);
|
bobSasEvent = e;
|
||||||
verifier.on("show_sas", (e) => {
|
} else {
|
||||||
if (!e.sas.emoji || !e.sas.decimal) {
|
try {
|
||||||
e.cancel();
|
expect(e.sas).toEqual(aliceSasEvent.sas);
|
||||||
} else if (!aliceSasEvent) {
|
e.confirm();
|
||||||
bobSasEvent = e;
|
aliceSasEvent.confirm();
|
||||||
} else {
|
} catch (error) {
|
||||||
try {
|
e.mismatch();
|
||||||
expect(e.sas).toEqual(aliceSasEvent.sas);
|
aliceSasEvent.mismatch();
|
||||||
e.confirm();
|
|
||||||
aliceSasEvent.confirm();
|
|
||||||
} catch (error) {
|
|
||||||
e.mismatch();
|
|
||||||
aliceSasEvent.mismatch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
await verifier.verify();
|
});
|
||||||
resolve();
|
await verifier.verify();
|
||||||
}
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -479,6 +473,12 @@ describe("SAS verification", function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
afterEach(async function() {
|
||||||
|
await Promise.all([
|
||||||
|
alice.stop(),
|
||||||
|
bob.stop(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should verify a key", async function() {
|
it("should verify a key", async function() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export async function makeTestClients(userInfos, options) {
|
|||||||
});
|
});
|
||||||
for (const tc of clients) {
|
for (const tc of clients) {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => tc.client.emit("event", event),
|
() => tc.client.emit("Room.timeline", event),
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
import sdk from '../..';
|
import sdk from '../..';
|
||||||
const MatrixEvent = sdk.MatrixEvent;
|
const MatrixEvent = sdk.MatrixEvent;
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import logger from '../../src/logger';
|
import logger from '../../src/logger';
|
||||||
|
|
||||||
describe("MatrixEvent", () => {
|
describe("MatrixEvent", () => {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ limitations under the License.
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
|
|
||||||
const InteractiveAuth = sdk.InteractiveAuth;
|
const InteractiveAuth = sdk.InteractiveAuth;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
const MatrixClient = sdk.MatrixClient;
|
const MatrixClient = sdk.MatrixClient;
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@ describe("MatrixClient", function() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
pendingLookup = {
|
pendingLookup = {
|
||||||
promise: Promise.defer().promise,
|
promise: new Promise(() => {}),
|
||||||
method: method,
|
method: method,
|
||||||
path: path,
|
path: path,
|
||||||
};
|
};
|
||||||
@@ -119,7 +118,7 @@ describe("MatrixClient", function() {
|
|||||||
return Promise.resolve(next.data);
|
return Promise.resolve(next.data);
|
||||||
}
|
}
|
||||||
expect(true).toBe(false, "Expected different request. " + logLine);
|
expect(true).toBe(false, "Expected different request. " + logLine);
|
||||||
return Promise.defer().promise;
|
return new Promise(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
@@ -171,7 +170,7 @@ describe("MatrixClient", function() {
|
|||||||
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
// a DIFFERENT test (pollution between tests!) - we return unresolved
|
||||||
// promises to stop the client from continuing to run.
|
// promises to stop the client from continuing to run.
|
||||||
client._http.authedRequest.mockImplementation(function() {
|
client._http.authedRequest.mockImplementation(function() {
|
||||||
return Promise.defer().promise;
|
return new Promise(() => {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/* eslint new-cap: "off" */
|
/* eslint new-cap: "off" */
|
||||||
|
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
import {defer} from '../../src/utils';
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
const MatrixScheduler = sdk.MatrixScheduler;
|
const MatrixScheduler = sdk.MatrixScheduler;
|
||||||
const MatrixError = sdk.MatrixError;
|
const MatrixError = sdk.MatrixError;
|
||||||
@@ -14,7 +14,7 @@ describe("MatrixScheduler", function() {
|
|||||||
let scheduler;
|
let scheduler;
|
||||||
let retryFn;
|
let retryFn;
|
||||||
let queueFn;
|
let queueFn;
|
||||||
let defer;
|
let deferred;
|
||||||
const roomId = "!foo:bar";
|
const roomId = "!foo:bar";
|
||||||
const eventA = utils.mkMessage({
|
const eventA = utils.mkMessage({
|
||||||
user: "@alice:bar", room: roomId, event: true,
|
user: "@alice:bar", room: roomId, event: true,
|
||||||
@@ -37,7 +37,7 @@ describe("MatrixScheduler", function() {
|
|||||||
});
|
});
|
||||||
retryFn = null;
|
retryFn = null;
|
||||||
queueFn = null;
|
queueFn = null;
|
||||||
defer = Promise.defer();
|
deferred = defer();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should process events in a queue in a FIFO manner", async function() {
|
it("should process events in a queue in a FIFO manner", async function() {
|
||||||
@@ -47,8 +47,8 @@ describe("MatrixScheduler", function() {
|
|||||||
queueFn = function() {
|
queueFn = function() {
|
||||||
return "one_big_queue";
|
return "one_big_queue";
|
||||||
};
|
};
|
||||||
const deferA = Promise.defer();
|
const deferA = defer();
|
||||||
const deferB = Promise.defer();
|
const deferB = defer();
|
||||||
let yieldedA = false;
|
let yieldedA = false;
|
||||||
scheduler.setProcessFunction(function(event) {
|
scheduler.setProcessFunction(function(event) {
|
||||||
if (yieldedA) {
|
if (yieldedA) {
|
||||||
@@ -74,7 +74,7 @@ describe("MatrixScheduler", function() {
|
|||||||
it("should invoke the retryFn on failure and wait the amount of time specified",
|
it("should invoke the retryFn on failure and wait the amount of time specified",
|
||||||
async function() {
|
async function() {
|
||||||
const waitTimeMs = 1500;
|
const waitTimeMs = 1500;
|
||||||
const retryDefer = Promise.defer();
|
const retryDefer = defer();
|
||||||
retryFn = function() {
|
retryFn = function() {
|
||||||
retryDefer.resolve();
|
retryDefer.resolve();
|
||||||
return waitTimeMs;
|
return waitTimeMs;
|
||||||
@@ -88,9 +88,9 @@ describe("MatrixScheduler", function() {
|
|||||||
procCount += 1;
|
procCount += 1;
|
||||||
if (procCount === 1) {
|
if (procCount === 1) {
|
||||||
expect(ev).toEqual(eventA);
|
expect(ev).toEqual(eventA);
|
||||||
return defer.promise;
|
return deferred.promise;
|
||||||
} else if (procCount === 2) {
|
} else if (procCount === 2) {
|
||||||
// don't care about this defer
|
// don't care about this deferred
|
||||||
return new Promise();
|
return new Promise();
|
||||||
}
|
}
|
||||||
expect(procCount).toBeLessThan(3);
|
expect(procCount).toBeLessThan(3);
|
||||||
@@ -101,7 +101,7 @@ describe("MatrixScheduler", function() {
|
|||||||
// wait just long enough before it does
|
// wait just long enough before it does
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
defer.reject({});
|
deferred.reject({});
|
||||||
await retryDefer.promise;
|
await retryDefer.promise;
|
||||||
expect(procCount).toEqual(1);
|
expect(procCount).toEqual(1);
|
||||||
jest.advanceTimersByTime(waitTimeMs);
|
jest.advanceTimersByTime(waitTimeMs);
|
||||||
@@ -121,8 +121,8 @@ describe("MatrixScheduler", function() {
|
|||||||
return "yep";
|
return "yep";
|
||||||
};
|
};
|
||||||
|
|
||||||
const deferA = Promise.defer();
|
const deferA = defer();
|
||||||
const deferB = Promise.defer();
|
const deferB = defer();
|
||||||
let procCount = 0;
|
let procCount = 0;
|
||||||
scheduler.setProcessFunction(function(ev) {
|
scheduler.setProcessFunction(function(ev) {
|
||||||
procCount += 1;
|
procCount += 1;
|
||||||
@@ -177,14 +177,14 @@ describe("MatrixScheduler", function() {
|
|||||||
const expectOrder = [
|
const expectOrder = [
|
||||||
eventA.getId(), eventB.getId(), eventD.getId(),
|
eventA.getId(), eventB.getId(), eventD.getId(),
|
||||||
];
|
];
|
||||||
const deferA = Promise.defer();
|
const deferA = defer();
|
||||||
scheduler.setProcessFunction(function(event) {
|
scheduler.setProcessFunction(function(event) {
|
||||||
const id = expectOrder.shift();
|
const id = expectOrder.shift();
|
||||||
expect(id).toEqual(event.getId());
|
expect(id).toEqual(event.getId());
|
||||||
if (expectOrder.length === 0) {
|
if (expectOrder.length === 0) {
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
return id === eventA.getId() ? deferA.promise : defer.promise;
|
return id === eventA.getId() ? deferA.promise : deferred.promise;
|
||||||
});
|
});
|
||||||
scheduler.queueEvent(eventA);
|
scheduler.queueEvent(eventA);
|
||||||
scheduler.queueEvent(eventB);
|
scheduler.queueEvent(eventB);
|
||||||
@@ -298,7 +298,7 @@ describe("MatrixScheduler", function() {
|
|||||||
scheduler.setProcessFunction(function(ev) {
|
scheduler.setProcessFunction(function(ev) {
|
||||||
procCount += 1;
|
procCount += 1;
|
||||||
expect(ev).toEqual(eventA);
|
expect(ev).toEqual(eventA);
|
||||||
return defer.promise;
|
return deferred.promise;
|
||||||
});
|
});
|
||||||
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
// as queueing doesn't start processing synchronously anymore (see commit bbdb5ac)
|
||||||
// wait just long enough before it does
|
// wait just long enough before it does
|
||||||
@@ -314,7 +314,7 @@ describe("MatrixScheduler", function() {
|
|||||||
let procCount = 0;
|
let procCount = 0;
|
||||||
scheduler.setProcessFunction(function(ev) {
|
scheduler.setProcessFunction(function(ev) {
|
||||||
procCount += 1;
|
procCount += 1;
|
||||||
return defer.promise;
|
return deferred.promise;
|
||||||
});
|
});
|
||||||
expect(procCount).toEqual(0);
|
expect(procCount).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Promise from 'bluebird';
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
const EventTimeline = sdk.EventTimeline;
|
const EventTimeline = sdk.EventTimeline;
|
||||||
const TimelineWindow = sdk.TimelineWindow;
|
const TimelineWindow = sdk.TimelineWindow;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
|
|
||||||
/** @module auto-discovery */
|
/** @module auto-discovery */
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import { URL as NodeURL } from "url";
|
import { URL as NodeURL } from "url";
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {sleep} from './utils';
|
|||||||
* @module client
|
* @module client
|
||||||
*/
|
*/
|
||||||
const EventEmitter = require("events").EventEmitter;
|
const EventEmitter = require("events").EventEmitter;
|
||||||
import Promise from 'bluebird';
|
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
const httpApi = require("./http-api");
|
const httpApi = require("./http-api");
|
||||||
@@ -55,11 +54,6 @@ import { decodeRecoveryKey } from './crypto/recoverykey';
|
|||||||
import { keyFromAuthData } from './crypto/key_passphrase';
|
import { keyFromAuthData } from './crypto/key_passphrase';
|
||||||
import { randomString } from './randomstring';
|
import { randomString } from './randomstring';
|
||||||
|
|
||||||
// Disable warnings for now: we use deprecated bluebird functions
|
|
||||||
// and need to migrate, but they spam the console with warnings.
|
|
||||||
Promise.config({warnings: false});
|
|
||||||
|
|
||||||
|
|
||||||
const SCROLLBACK_DELAY_MS = 3000;
|
const SCROLLBACK_DELAY_MS = 3000;
|
||||||
const CRYPTO_ENABLED = isCryptoAvailable();
|
const CRYPTO_ENABLED = isCryptoAvailable();
|
||||||
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
|
const CAPABILITIES_CACHE_MS = 21600000; // 6 hours - an arbitrary value
|
||||||
@@ -1965,7 +1959,7 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
|
|||||||
// return syncApi.syncRoom(room);
|
// return syncApi.syncRoom(room);
|
||||||
}
|
}
|
||||||
return Promise.resolve(room);
|
return Promise.resolve(room);
|
||||||
}).done(function(room) {
|
}).then(function(room) {
|
||||||
_resolve(callback, resolve, room);
|
_resolve(callback, resolve, room);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
_reject(callback, reject, err);
|
_reject(callback, reject, err);
|
||||||
@@ -3297,7 +3291,7 @@ MatrixClient.prototype.scrollback = function(room, limit, callback) {
|
|||||||
room.oldState.paginationToken,
|
room.oldState.paginationToken,
|
||||||
limit,
|
limit,
|
||||||
'b');
|
'b');
|
||||||
}).done(function(res) {
|
}).then(function(res) {
|
||||||
const matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
|
const matrixEvents = utils.map(res.chunk, _PojoToMatrixEventMapper(self));
|
||||||
if (res.state) {
|
if (res.state) {
|
||||||
const stateEvents = utils.map(res.state, _PojoToMatrixEventMapper(self));
|
const stateEvents = utils.map(res.state, _PojoToMatrixEventMapper(self));
|
||||||
@@ -3945,10 +3939,10 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
|||||||
// This is a workaround to SYN-590 (Push rule update fails)
|
// This is a workaround to SYN-590 (Push rule update fails)
|
||||||
deferred = utils.defer();
|
deferred = utils.defer();
|
||||||
this.deletePushRule(scope, "room", roomPushRule.rule_id)
|
this.deletePushRule(scope, "room", roomPushRule.rule_id)
|
||||||
.done(function() {
|
.then(function() {
|
||||||
self.addPushRule(scope, "room", roomId, {
|
self.addPushRule(scope, "room", roomId, {
|
||||||
actions: ["dont_notify"],
|
actions: ["dont_notify"],
|
||||||
}).done(function() {
|
}).then(function() {
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
deferred.reject(err);
|
deferred.reject(err);
|
||||||
@@ -3964,8 +3958,8 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
|||||||
if (deferred) {
|
if (deferred) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Update this.pushRules when the operation completes
|
// Update this.pushRules when the operation completes
|
||||||
deferred.done(function() {
|
deferred.then(function() {
|
||||||
self.getPushRules().done(function(result) {
|
self.getPushRules().then(function(result) {
|
||||||
self.pushRules = result;
|
self.pushRules = result;
|
||||||
resolve();
|
resolve();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
@@ -3974,7 +3968,7 @@ MatrixClient.prototype.setRoomMutePushRule = function(scope, roomId, mute) {
|
|||||||
}, function(err) {
|
}, function(err) {
|
||||||
// Update it even if the previous operation fails. This can help the
|
// Update it even if the previous operation fails. This can help the
|
||||||
// app to recover when push settings has been modifed from another client
|
// app to recover when push settings has been modifed from another client
|
||||||
self.getPushRules().done(function(result) {
|
self.getPushRules().then(function(result) {
|
||||||
self.pushRules = result;
|
self.pushRules = result;
|
||||||
reject(err);
|
reject(err);
|
||||||
}, function(err2) {
|
}, function(err2) {
|
||||||
@@ -4468,7 +4462,7 @@ MatrixClient.prototype.startClient = async function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._crypto) {
|
if (this._crypto) {
|
||||||
this._crypto.uploadDeviceKeys().done();
|
this._crypto.uploadDeviceKeys();
|
||||||
this._crypto.start();
|
this._crypto.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4927,7 +4921,7 @@ function checkTurnServers(client) {
|
|||||||
return; // guests can't access TURN servers
|
return; // guests can't access TURN servers
|
||||||
}
|
}
|
||||||
|
|
||||||
client.turnServer().done(function(res) {
|
client.turnServer().then(function(res) {
|
||||||
if (res.uris) {
|
if (res.uris) {
|
||||||
logger.log("Got TURN URIs: " + res.uris + " refresh in " +
|
logger.log("Got TURN URIs: " + res.uris + " refresh in " +
|
||||||
res.ttl + " secs");
|
res.ttl + " secs");
|
||||||
@@ -5427,5 +5421,4 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
|||||||
* @property {Function} then promise.then(onFulfilled, onRejected, onProgress)
|
* @property {Function} then promise.then(onFulfilled, onRejected, onProgress)
|
||||||
* @property {Function} catch promise.catch(onRejected)
|
* @property {Function} catch promise.catch(onRejected)
|
||||||
* @property {Function} finally promise.finally(callback)
|
* @property {Function} finally promise.finally(callback)
|
||||||
* @property {Function} done promise.done(onFulfilled, onRejected, onProgress)
|
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ limitations under the License.
|
|||||||
* Manages the list of other users' devices
|
* Manages the list of other users' devices
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
@@ -31,7 +30,7 @@ import DeviceInfo from './deviceinfo';
|
|||||||
import {CrossSigningInfo} from './CrossSigning';
|
import {CrossSigningInfo} from './CrossSigning';
|
||||||
import olmlib from './olmlib';
|
import olmlib from './olmlib';
|
||||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
import {sleep} from '../utils';
|
import {defer, sleep} from '../utils';
|
||||||
|
|
||||||
|
|
||||||
/* State transition diagram for DeviceList._deviceTrackingStatus
|
/* State transition diagram for DeviceList._deviceTrackingStatus
|
||||||
@@ -712,7 +711,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!this._queuedQueryDeferred) {
|
if (!this._queuedQueryDeferred) {
|
||||||
this._queuedQueryDeferred = Promise.defer();
|
this._queuedQueryDeferred = defer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always take the new sync token and just use the latest one we've
|
// We always take the new sync token and just use the latest one we've
|
||||||
@@ -777,7 +776,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return prom;
|
return prom;
|
||||||
}).done(() => {
|
}).then(() => {
|
||||||
logger.log('Completed key download for ' + downloadUsers);
|
logger.log('Completed key download for ' + downloadUsers);
|
||||||
|
|
||||||
this._downloadInProgress = false;
|
this._downloadInProgress = false;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import utils from '../utils';
|
import utils from '../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ limitations under the License.
|
|||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* map of registered encryption algorithm classes. A map from string to {@link
|
* map of registered encryption algorithm classes. A map from string to {@link
|
||||||
* module:crypto/algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class
|
* module:crypto/algorithms/base.EncryptionAlgorithm|EncryptionAlgorithm} class
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ limitations under the License.
|
|||||||
* @module crypto/algorithms/megolm
|
* @module crypto/algorithms/megolm
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
|
|
||||||
const utils = require("../../utils");
|
const utils = require("../../utils");
|
||||||
@@ -508,7 +507,7 @@ MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
|||||||
userId,
|
userId,
|
||||||
device,
|
device,
|
||||||
payload,
|
payload,
|
||||||
),
|
);
|
||||||
|
|
||||||
await this._baseApis.sendToDevice("m.room.encrypted", {
|
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||||
[userId]: {
|
[userId]: {
|
||||||
@@ -1036,7 +1035,7 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) {
|
|||||||
// TODO: retries
|
// TODO: retries
|
||||||
return this._baseApis.sendToDevice("m.room.encrypted", contentMap);
|
return this._baseApis.sendToDevice("m.room.encrypted", contentMap);
|
||||||
});
|
});
|
||||||
}).done();
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
|
MegolmDecryption.prototype._buildKeyForwardingMessage = async function(
|
||||||
|
|||||||
@@ -20,15 +20,12 @@ limitations under the License.
|
|||||||
*
|
*
|
||||||
* @module crypto/algorithms/olm
|
* @module crypto/algorithms/olm
|
||||||
*/
|
*/
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
const utils = require("../../utils");
|
const utils = require("../../utils");
|
||||||
const olmlib = require("../olmlib");
|
const olmlib = require("../olmlib");
|
||||||
const DeviceInfo = require("../deviceinfo");
|
const DeviceInfo = require("../deviceinfo");
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
|
|
||||||
|
|
||||||
const base = require("./base");
|
const base = require("./base");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const anotherjson = require('another-json');
|
const anotherjson = require('another-json');
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import ReEmitter from '../ReEmitter';
|
import ReEmitter from '../ReEmitter';
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ const algorithms = require("./algorithms");
|
|||||||
const DeviceInfo = require("./deviceinfo");
|
const DeviceInfo = require("./deviceinfo");
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
const DeviceList = require('./DeviceList').default;
|
const DeviceList = require('./DeviceList').default;
|
||||||
import { randomString } from '../randomstring';
|
|
||||||
import {
|
import {
|
||||||
CrossSigningInfo,
|
CrossSigningInfo,
|
||||||
UserTrustLevel,
|
UserTrustLevel,
|
||||||
@@ -49,15 +47,14 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
|||||||
|
|
||||||
import {ShowQRCode, ScanQRCode} from './verification/QRCode';
|
import {ShowQRCode, ScanQRCode} from './verification/QRCode';
|
||||||
import SAS from './verification/SAS';
|
import SAS from './verification/SAS';
|
||||||
import {
|
|
||||||
newUserCancelledError,
|
|
||||||
newUnexpectedMessageError,
|
|
||||||
newUnknownMethodError,
|
|
||||||
} from './verification/Error';
|
|
||||||
import {sleep} from '../utils';
|
import {sleep} from '../utils';
|
||||||
import { keyFromPassphrase } from './key_passphrase';
|
import { keyFromPassphrase } from './key_passphrase';
|
||||||
import { encodeRecoveryKey } from './recoverykey';
|
import { encodeRecoveryKey } from './recoverykey';
|
||||||
|
|
||||||
|
import VerificationRequest from "./verification/request/VerificationRequest";
|
||||||
|
import InRoomChannel from "./verification/request/InRoomChannel";
|
||||||
|
import ToDeviceChannel from "./verification/request/ToDeviceChannel";
|
||||||
|
|
||||||
const defaultVerificationMethods = {
|
const defaultVerificationMethods = {
|
||||||
[ScanQRCode.NAME]: ScanQRCode,
|
[ScanQRCode.NAME]: ScanQRCode,
|
||||||
[ShowQRCode.NAME]: ShowQRCode,
|
[ShowQRCode.NAME]: ShowQRCode,
|
||||||
@@ -73,44 +70,10 @@ export const verificationMethods = {
|
|||||||
SAS: SAS.NAME,
|
SAS: SAS.NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
// the recommended amount of time before a verification request
|
|
||||||
// should be (automatically) cancelled without user interaction
|
|
||||||
// and ignored.
|
|
||||||
const VERIFICATION_REQUEST_TIMEOUT = 5 * 60 * 1000; //5m
|
|
||||||
// to avoid almost expired verification notifications
|
|
||||||
// from showing a notification and almost immediately
|
|
||||||
// disappearing, also ignore verification requests that
|
|
||||||
// are this amount of time away from expiring.
|
|
||||||
const VERIFICATION_REQUEST_MARGIN = 3 * 1000; //3s
|
|
||||||
|
|
||||||
export function isCryptoAvailable() {
|
export function isCryptoAvailable() {
|
||||||
return Boolean(global.Olm);
|
return Boolean(global.Olm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* subscribes to timeline events / to_device events for SAS verification */
|
|
||||||
function listenForEvents(client, roomId, listener) {
|
|
||||||
let isEncrypted = false;
|
|
||||||
if (roomId) {
|
|
||||||
isEncrypted = client.isRoomEncrypted(roomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEncrypted) {
|
|
||||||
client.on("Event.decrypted", listener);
|
|
||||||
}
|
|
||||||
client.on("event", listener);
|
|
||||||
let subscribed = true;
|
|
||||||
return function() {
|
|
||||||
if (subscribed) {
|
|
||||||
if (isEncrypted) {
|
|
||||||
client.off("Event.decrypted", listener);
|
|
||||||
}
|
|
||||||
client.off("event", listener);
|
|
||||||
subscribed = false;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const MIN_FORCE_SESSION_INTERVAL_MS = 60 * 60 * 1000;
|
const MIN_FORCE_SESSION_INTERVAL_MS = 60 * 60 * 1000;
|
||||||
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
|
||||||
|
|
||||||
@@ -243,7 +206,8 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
// }
|
// }
|
||||||
this._lastNewSessionForced = {};
|
this._lastNewSessionForced = {};
|
||||||
|
|
||||||
this._verificationTransactions = new Map();
|
this._toDeviceVerificationRequests = new Map();
|
||||||
|
this._inRoomVerificationRequests = new Map();
|
||||||
|
|
||||||
const cryptoCallbacks = this._baseApis._cryptoCallbacks || {};
|
const cryptoCallbacks = this._baseApis._cryptoCallbacks || {};
|
||||||
|
|
||||||
@@ -1326,7 +1290,7 @@ function _maybeUploadOneTimeKeys(crypto) {
|
|||||||
// it will be set again on the next /sync-response
|
// it will be set again on the next /sync-response
|
||||||
crypto._oneTimeKeyCount = undefined;
|
crypto._oneTimeKeyCount = undefined;
|
||||||
crypto._oneTimeKeyCheckInProgress = false;
|
crypto._oneTimeKeyCheckInProgress = false;
|
||||||
}).done();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a promise which resolves to the response
|
// returns a promise which resolves to the response
|
||||||
@@ -1517,203 +1481,100 @@ Crypto.prototype.setDeviceVerification = async function(
|
|||||||
return deviceObj;
|
return deviceObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function verificationEventHandler(target, userId, roomId, eventId) {
|
|
||||||
return function(event) {
|
|
||||||
// listen for events related to this verification
|
|
||||||
if (event.getRoomId() !== roomId
|
|
||||||
|| event.getSender() !== userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ignore events that haven't been decrypted yet.
|
|
||||||
// we also listen for undecrypted events, just in case
|
|
||||||
// the other side would be sending unencrypted events in an e2ee room
|
|
||||||
if (event.getType() === "m.room.encrypted") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const relatesTo = event.getRelation();
|
|
||||||
if (!relatesTo
|
|
||||||
|| !relatesTo.rel_type
|
|
||||||
|| relatesTo.rel_type !== "m.reference"
|
|
||||||
|| !relatesTo.event_id
|
|
||||||
|| relatesTo.event_id !== eventId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the event seems to be related to this verification, so pass it on to
|
|
||||||
// the verification handler
|
|
||||||
target.handleEvent(event);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Crypto.prototype.requestVerificationDM = async function(userId, roomId, methods) {
|
Crypto.prototype.requestVerificationDM = async function(userId, roomId, methods) {
|
||||||
let methodMap;
|
const channel = new InRoomChannel(this._baseApis, roomId, userId);
|
||||||
if (methods) {
|
const request = await this._requestVerificationWithChannel(
|
||||||
methodMap = new Map();
|
userId,
|
||||||
for (const method of methods) {
|
methods,
|
||||||
if (typeof method === "string") {
|
channel,
|
||||||
methodMap.set(method, defaultVerificationMethods[method]);
|
this._inRoomVerificationRequests,
|
||||||
} else if (method.NAME) {
|
|
||||||
methodMap.set(method.NAME, method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
methodMap = this._baseApis._crypto._verificationMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
let eventId = undefined;
|
|
||||||
const listenPromise = new Promise((_resolve, _reject) => {
|
|
||||||
const listener = (event) => {
|
|
||||||
// listen for events related to this verification
|
|
||||||
if (event.getRoomId() !== roomId
|
|
||||||
|| event.getSender() !== userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const relatesTo = event.getRelation();
|
|
||||||
if (!relatesTo || !relatesTo.rel_type
|
|
||||||
|| relatesTo.rel_type !== "m.reference"
|
|
||||||
|| !relatesTo.event_id
|
|
||||||
|| relatesTo.event_id !== eventId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = event.getContent();
|
|
||||||
// the event seems to be related to this verification
|
|
||||||
switch (event.getType()) {
|
|
||||||
case "m.key.verification.start": {
|
|
||||||
const verifier = new (methodMap.get(content.method))(
|
|
||||||
this._baseApis, userId, content.from_device, eventId,
|
|
||||||
roomId, event,
|
|
||||||
);
|
|
||||||
verifier.handler = verificationEventHandler(
|
|
||||||
verifier, userId, roomId, eventId,
|
|
||||||
);
|
|
||||||
// this handler gets removed when the verification finishes
|
|
||||||
// (see the verify method of crypto/verification/Base.js)
|
|
||||||
const subscription =
|
|
||||||
listenForEvents(this._baseApis, roomId, verifier.handler);
|
|
||||||
verifier.setEventsSubscription(subscription);
|
|
||||||
resolve(verifier);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "m.key.verification.cancel": {
|
|
||||||
reject(event);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let initialResponseSubscription =
|
|
||||||
listenForEvents(this._baseApis, roomId, listener);
|
|
||||||
|
|
||||||
const resolve = (...args) => {
|
|
||||||
if (initialResponseSubscription) {
|
|
||||||
initialResponseSubscription = initialResponseSubscription();
|
|
||||||
}
|
|
||||||
_resolve(...args);
|
|
||||||
};
|
|
||||||
const reject = (...args) => {
|
|
||||||
if (initialResponseSubscription) {
|
|
||||||
initialResponseSubscription = initialResponseSubscription();
|
|
||||||
}
|
|
||||||
_reject(...args);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await this._baseApis.sendEvent(
|
|
||||||
roomId, "m.room.message",
|
|
||||||
{
|
|
||||||
body: this._baseApis.getUserId() + " is requesting to verify " +
|
|
||||||
"your key, but your client does not support in-chat key " +
|
|
||||||
"verification. You will need to use legacy key " +
|
|
||||||
"verification to verify keys.",
|
|
||||||
msgtype: "m.key.verification.request",
|
|
||||||
to: userId,
|
|
||||||
from_device: this._baseApis.getDeviceId(),
|
|
||||||
methods: [...methodMap.keys()],
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
eventId = res.event_id;
|
return await request.waitForVerifier();
|
||||||
|
|
||||||
return listenPromise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto.prototype.acceptVerificationDM = function(event, Method) {
|
Crypto.prototype.acceptVerificationDM = function(event, method) {
|
||||||
if (typeof(Method) === "string") {
|
if(!InRoomChannel.validateEvent(event, this._baseApis)) {
|
||||||
Method = defaultVerificationMethods[Method];
|
return;
|
||||||
}
|
}
|
||||||
const content = event.getContent();
|
|
||||||
const verifier = new Method(
|
const sender = event.getSender();
|
||||||
this._baseApis, event.getSender(), content.from_device, event.getId(),
|
const requestsByTxnId = this._inRoomVerificationRequests.get(sender);
|
||||||
event.getRoomId(),
|
if (!requestsByTxnId) {
|
||||||
);
|
return;
|
||||||
verifier.handler = verificationEventHandler(
|
}
|
||||||
verifier, event.getSender(), event.getRoomId(), event.getId(),
|
const transactionId = InRoomChannel.getTransactionId(event);
|
||||||
);
|
const request = requestsByTxnId.get(transactionId);
|
||||||
const subscription = listenForEvents(
|
if (!request) {
|
||||||
this._baseApis, event.getRoomId(), verifier.handler);
|
return;
|
||||||
verifier.setEventsSubscription(subscription);
|
}
|
||||||
return verifier;
|
|
||||||
|
return request.beginKeyVerification(method);
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto.prototype.requestVerification = function(userId, methods, devices) {
|
Crypto.prototype.requestVerification = async function(userId, methods, devices) {
|
||||||
|
if (!devices) {
|
||||||
|
devices = Object.keys(this._deviceList.getRawStoredDevicesForUser(userId));
|
||||||
|
}
|
||||||
|
const channel = new ToDeviceChannel(this._baseApis, userId, devices);
|
||||||
|
const request = await this._requestVerificationWithChannel(
|
||||||
|
userId,
|
||||||
|
methods,
|
||||||
|
channel,
|
||||||
|
this._toDeviceVerificationRequests,
|
||||||
|
);
|
||||||
|
return await request.waitForVerifier();
|
||||||
|
};
|
||||||
|
|
||||||
|
Crypto.prototype._requestVerificationWithChannel = async function(
|
||||||
|
userId, methods, channel, requestsMap,
|
||||||
|
) {
|
||||||
if (!methods) {
|
if (!methods) {
|
||||||
// .keys() returns an iterator, so we need to explicitly turn it into an array
|
// .keys() returns an iterator, so we need to explicitly turn it into an array
|
||||||
methods = [...this._verificationMethods.keys()];
|
methods = [...this._verificationMethods.keys()];
|
||||||
}
|
}
|
||||||
if (!devices) {
|
// TODO: filter by given methods
|
||||||
devices = Object.keys(this._deviceList.getRawStoredDevicesForUser(userId));
|
const request = new VerificationRequest(
|
||||||
}
|
channel, this._verificationMethods, userId, this._baseApis);
|
||||||
if (!this._verificationTransactions.has(userId)) {
|
await request.sendRequest();
|
||||||
this._verificationTransactions.set(userId, new Map);
|
|
||||||
|
let requestsByTxnId = requestsMap.get(userId);
|
||||||
|
if (!requestsByTxnId) {
|
||||||
|
requestsByTxnId = new Map();
|
||||||
|
requestsMap.set(userId, requestsByTxnId);
|
||||||
}
|
}
|
||||||
|
// TODO: we're only adding the request to the map once it has been sent
|
||||||
|
// but if the other party is really fast they could potentially respond to the
|
||||||
|
// request before the server tells us the event got sent, and we would probably
|
||||||
|
// create a new request object
|
||||||
|
requestsByTxnId.set(channel.transactionId, request);
|
||||||
|
|
||||||
const transactionId = randomString(32);
|
return request;
|
||||||
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
this._verificationTransactions.get(userId).set(transactionId, {
|
|
||||||
request: {
|
|
||||||
methods: methods,
|
|
||||||
devices: devices,
|
|
||||||
resolve: resolve,
|
|
||||||
reject: reject,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
transaction_id: transactionId,
|
|
||||||
from_device: this._baseApis.deviceId,
|
|
||||||
methods: methods,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
const msgMap = {};
|
|
||||||
for (const deviceId of devices) {
|
|
||||||
msgMap[deviceId] = message;
|
|
||||||
}
|
|
||||||
this._baseApis.sendToDevice("m.key.verification.request", {[userId]: msgMap});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Crypto.prototype.beginKeyVerification = function(
|
Crypto.prototype.beginKeyVerification = function(
|
||||||
method, userId, deviceId, transactionId,
|
method, userId, deviceId, transactionId = null,
|
||||||
) {
|
) {
|
||||||
if (!this._verificationTransactions.has(userId)) {
|
let requestsByTxnId = this._toDeviceVerificationRequests.get(userId);
|
||||||
this._verificationTransactions.set(userId, new Map());
|
if (!requestsByTxnId) {
|
||||||
|
requestsByTxnId = new Map();
|
||||||
|
this._toDeviceVerificationRequests.set(userId, requestsByTxnId);
|
||||||
}
|
}
|
||||||
transactionId = transactionId || randomString(32);
|
let request;
|
||||||
if (this._verificationMethods.has(method)) {
|
if (transactionId) {
|
||||||
const verifier = new (this._verificationMethods.get(method))(
|
request = requestsByTxnId.get(transactionId);
|
||||||
this._baseApis, userId, deviceId, transactionId,
|
|
||||||
);
|
|
||||||
if (!this._verificationTransactions.get(userId).has(transactionId)) {
|
|
||||||
this._verificationTransactions.get(userId).set(transactionId, {});
|
|
||||||
}
|
|
||||||
this._verificationTransactions.get(userId).get(transactionId).verifier = verifier;
|
|
||||||
return verifier;
|
|
||||||
} else {
|
} else {
|
||||||
throw newUnknownMethodError();
|
transactionId = ToDeviceChannel.makeTransactionId();
|
||||||
|
const channel = new ToDeviceChannel(
|
||||||
|
this._baseApis, userId, [deviceId], transactionId, deviceId);
|
||||||
|
request = new VerificationRequest(
|
||||||
|
channel, this._verificationMethods, userId, this._baseApis);
|
||||||
|
requestsByTxnId.set(transactionId, request);
|
||||||
}
|
}
|
||||||
|
if (!request) {
|
||||||
|
throw new Error(
|
||||||
|
`No request found for user ${userId} with transactionId ${transactionId}`);
|
||||||
|
}
|
||||||
|
return request.beginKeyVerification(method, {userId, deviceId});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -2340,7 +2201,7 @@ Crypto.prototype.requestRoomKey = function(requestBody, recipients, resend=false
|
|||||||
logger.error(
|
logger.error(
|
||||||
'Error requesting key for event', e,
|
'Error requesting key for event', e,
|
||||||
);
|
);
|
||||||
}).done();
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2353,7 +2214,7 @@ Crypto.prototype.cancelRoomKeyRequest = function(requestBody) {
|
|||||||
this._outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
|
this._outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
logger.warn("Error clearing pending room key requests", e);
|
logger.warn("Error clearing pending room key requests", e);
|
||||||
}).done();
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2510,10 +2371,6 @@ Crypto.prototype._onToDeviceEvent = function(event) {
|
|||||||
this._secretStorage._onRequestReceived(event);
|
this._secretStorage._onRequestReceived(event);
|
||||||
} else if (event.getType() === "m.secret.send") {
|
} else if (event.getType() === "m.secret.send") {
|
||||||
this._secretStorage._onSecretReceived(event);
|
this._secretStorage._onSecretReceived(event);
|
||||||
} else if (event.getType() === "m.key.verification.request") {
|
|
||||||
this._onKeyVerificationRequest(event);
|
|
||||||
} else if (event.getType() === "m.key.verification.start") {
|
|
||||||
this._onKeyVerificationStart(event);
|
|
||||||
} else if (event.getContent().transaction_id) {
|
} else if (event.getContent().transaction_id) {
|
||||||
this._onKeyVerificationMessage(event);
|
this._onKeyVerificationMessage(event);
|
||||||
} else if (event.getContent().msgtype === "m.bad.encrypted") {
|
} else if (event.getContent().msgtype === "m.bad.encrypted") {
|
||||||
@@ -2553,258 +2410,6 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
|
|||||||
alg.onRoomKeyEvent(event);
|
alg.onRoomKeyEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a key verification request event.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {module:models/event.MatrixEvent} event verification request event
|
|
||||||
*/
|
|
||||||
Crypto.prototype._onKeyVerificationRequest = function(event) {
|
|
||||||
if (event.isCancelled()) {
|
|
||||||
logger.warn("Ignoring flagged verification request from " + event.getSender());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = event.getContent();
|
|
||||||
if (!("from_device" in content) || typeof content.from_device !== "string"
|
|
||||||
|| !("transaction_id" in content)
|
|
||||||
|| !("methods" in content) || !Array.isArray(content.methods)
|
|
||||||
|| !("timestamp" in content) || typeof content.timestamp !== "number") {
|
|
||||||
logger.warn("received invalid verification request from " + event.getSender());
|
|
||||||
// ignore event if malformed
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
if (now < content.timestamp - (5 * 60 * 1000)
|
|
||||||
|| now > content.timestamp + (10 * 60 * 1000)) {
|
|
||||||
// ignore if event is too far in the past or too far in the future
|
|
||||||
logger.log("received verification that is too old or from the future");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = event.getSender();
|
|
||||||
if (sender === this._userId && content.from_device === this._deviceId) {
|
|
||||||
// ignore requests from ourselves, because it doesn't make sense for a
|
|
||||||
// device to verify itself
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._verificationTransactions.has(sender)) {
|
|
||||||
if (this._verificationTransactions.get(sender).has(content.transaction_id)) {
|
|
||||||
// transaction already exists: cancel it and drop the existing
|
|
||||||
// request because someone has gotten confused
|
|
||||||
const err = newUnexpectedMessageError({
|
|
||||||
transaction_id: content.transaction_id,
|
|
||||||
});
|
|
||||||
if (this._verificationTransactions.get(sender).get(content.transaction_id)
|
|
||||||
.verifier) {
|
|
||||||
this._verificationTransactions.get(sender).get(content.transaction_id)
|
|
||||||
.verifier.cancel(err);
|
|
||||||
} else {
|
|
||||||
this._verificationTransactions.get(sender).get(content.transaction_id)
|
|
||||||
.reject(err);
|
|
||||||
this.sendToDevice("m.key.verification.cancel", {
|
|
||||||
[sender]: {
|
|
||||||
[content.from_device]: err.getContent(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._verificationTransactions.get(sender).delete(content.transaction_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._verificationTransactions.set(sender, new Map());
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine what requested methods we support
|
|
||||||
const methods = [];
|
|
||||||
for (const method of content.methods) {
|
|
||||||
if (typeof method !== "string") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (this._verificationMethods.has(method)) {
|
|
||||||
methods.push(method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (methods.length === 0) {
|
|
||||||
this._baseApis.emit(
|
|
||||||
"crypto.verification.request.unknown",
|
|
||||||
event.getSender(),
|
|
||||||
() => {
|
|
||||||
this.sendToDevice("m.key.verification.cancel", {
|
|
||||||
[sender]: {
|
|
||||||
[content.from_device]: newUserCancelledError({
|
|
||||||
transaction_id: content.transaction_id,
|
|
||||||
}).getContent(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// notify the application of the verification request, so it can
|
|
||||||
// decide what to do with it
|
|
||||||
const request = {
|
|
||||||
timeout: VERIFICATION_REQUEST_TIMEOUT,
|
|
||||||
event: event,
|
|
||||||
methods: methods,
|
|
||||||
beginKeyVerification: (method) => {
|
|
||||||
const verifier = this.beginKeyVerification(
|
|
||||||
method,
|
|
||||||
sender,
|
|
||||||
content.from_device,
|
|
||||||
content.transaction_id,
|
|
||||||
);
|
|
||||||
this._verificationTransactions.get(sender).get(content.transaction_id)
|
|
||||||
.verifier = verifier;
|
|
||||||
return verifier;
|
|
||||||
},
|
|
||||||
cancel: () => {
|
|
||||||
this._baseApis.sendToDevice("m.key.verification.cancel", {
|
|
||||||
[sender]: {
|
|
||||||
[content.from_device]: newUserCancelledError({
|
|
||||||
transaction_id: content.transaction_id,
|
|
||||||
}).getContent(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this._verificationTransactions.get(sender).set(content.transaction_id, {
|
|
||||||
request: request,
|
|
||||||
});
|
|
||||||
this._baseApis.emit("crypto.verification.request", request);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle a key verification start event.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {module:models/event.MatrixEvent} event verification start event
|
|
||||||
*/
|
|
||||||
Crypto.prototype._onKeyVerificationStart = function(event) {
|
|
||||||
if (event.isCancelled()) {
|
|
||||||
logger.warn("Ignoring flagged verification start from " + event.getSender());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sender = event.getSender();
|
|
||||||
const content = event.getContent();
|
|
||||||
const transactionId = content.transaction_id;
|
|
||||||
const deviceId = content.from_device;
|
|
||||||
if (!transactionId || !deviceId) {
|
|
||||||
// invalid request, and we don't have enough information to send a
|
|
||||||
// cancellation, so just ignore it
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let handler = this._verificationTransactions.has(sender)
|
|
||||||
&& this._verificationTransactions.get(sender).get(transactionId);
|
|
||||||
// if the verification start message is invalid, send a cancel message to
|
|
||||||
// the other side, and also send a cancellation event
|
|
||||||
const cancel = (err) => {
|
|
||||||
if (handler.verifier) {
|
|
||||||
handler.verifier.cancel(err);
|
|
||||||
} else if (handler.request && handler.request.cancel) {
|
|
||||||
handler.request.cancel(err);
|
|
||||||
}
|
|
||||||
this.sendToDevice(
|
|
||||||
"m.key.verification.cancel", {
|
|
||||||
[sender]: {
|
|
||||||
[deviceId]: err.getContent(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if (!this._verificationMethods.has(content.method)) {
|
|
||||||
cancel(newUnknownMethodError({
|
|
||||||
transaction_id: content.transactionId,
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const verifier = new (this._verificationMethods.get(content.method))(
|
|
||||||
this._baseApis, sender, deviceId, content.transaction_id,
|
|
||||||
event, handler && handler.request,
|
|
||||||
);
|
|
||||||
if (!handler) {
|
|
||||||
if (!this._verificationTransactions.has(sender)) {
|
|
||||||
this._verificationTransactions.set(sender, new Map());
|
|
||||||
}
|
|
||||||
handler = this._verificationTransactions.get(sender).set(transactionId, {
|
|
||||||
verifier: verifier,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!handler.verifier) {
|
|
||||||
handler.verifier = verifier;
|
|
||||||
if (handler.request) {
|
|
||||||
// the verification start was sent as a response to a
|
|
||||||
// verification request
|
|
||||||
|
|
||||||
if (!handler.request.devices.includes(deviceId)) {
|
|
||||||
// didn't send a request to that device, so it
|
|
||||||
// shouldn't have responded
|
|
||||||
cancel(newUnexpectedMessageError({
|
|
||||||
transaction_id: content.transactionId,
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!handler.request.methods.includes(content.method)) {
|
|
||||||
// verification method wasn't one that was requested
|
|
||||||
cancel(newUnknownMethodError({
|
|
||||||
transaction_id: content.transactionId,
|
|
||||||
}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send cancellation messages to all the other devices that
|
|
||||||
// the request was sent to
|
|
||||||
const message = {
|
|
||||||
transaction_id: transactionId,
|
|
||||||
code: "m.accepted",
|
|
||||||
reason: "Verification request accepted by another device",
|
|
||||||
};
|
|
||||||
const msgMap = {};
|
|
||||||
for (const devId of handler.request.devices) {
|
|
||||||
if (devId !== deviceId) {
|
|
||||||
msgMap[devId] = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._baseApis.sendToDevice("m.key.verification.cancel", {
|
|
||||||
[sender]: msgMap,
|
|
||||||
});
|
|
||||||
|
|
||||||
handler.request.resolve(verifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._baseApis.emit("crypto.verification.start", verifier);
|
|
||||||
|
|
||||||
// Riot does not implement `m.key.verification.request` while
|
|
||||||
// verifying over to_device messages, but the protocol is made to
|
|
||||||
// work when starting with a `m.key.verification.start` event straight
|
|
||||||
// away as well. Verification over DM *does* start with a request event first,
|
|
||||||
// and to expose a uniform api to monitor verification requests, we mock
|
|
||||||
// the request api here for to_device messages.
|
|
||||||
// "crypto.verification.start" is kept for backwards compatibility.
|
|
||||||
|
|
||||||
// ahh, this will create 2 request notifications for clients that do support the request event
|
|
||||||
// so maybe we should remove emitting the request when actually receiving it *sigh*
|
|
||||||
const requestAdapter = {
|
|
||||||
event,
|
|
||||||
timeout: VERIFICATION_REQUEST_TIMEOUT,
|
|
||||||
methods: [content.method],
|
|
||||||
beginKeyVerification: (method) => {
|
|
||||||
if (content.method === method) {
|
|
||||||
return verifier;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancel: () => {
|
|
||||||
return verifier.cancel("User cancelled");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this._baseApis.emit("crypto.verification.request", requestAdapter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a general key verification event.
|
* Handle a general key verification event.
|
||||||
*
|
*
|
||||||
@@ -2812,26 +2417,30 @@ Crypto.prototype._onKeyVerificationStart = function(event) {
|
|||||||
* @param {module:models/event.MatrixEvent} event verification start event
|
* @param {module:models/event.MatrixEvent} event verification start event
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._onKeyVerificationMessage = function(event) {
|
Crypto.prototype._onKeyVerificationMessage = function(event) {
|
||||||
const sender = event.getSender();
|
if (!ToDeviceChannel.validateEvent(event, this._baseApis)) {
|
||||||
const transactionId = event.getContent().transaction_id;
|
|
||||||
const handler = this._verificationTransactions.has(sender)
|
|
||||||
&& this._verificationTransactions.get(sender).get(transactionId);
|
|
||||||
if (!handler) {
|
|
||||||
return;
|
return;
|
||||||
} else if (event.getType() === "m.key.verification.cancel") {
|
|
||||||
logger.log(event);
|
|
||||||
if (handler.verifier) {
|
|
||||||
handler.verifier.cancel(event);
|
|
||||||
} else if (handler.request && handler.request.cancel) {
|
|
||||||
handler.request.cancel(event);
|
|
||||||
}
|
|
||||||
} else if (handler.verifier) {
|
|
||||||
const verifier = handler.verifier;
|
|
||||||
if (verifier.events
|
|
||||||
&& verifier.events.includes(event.getType())) {
|
|
||||||
verifier.handleEvent(event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const transactionId = ToDeviceChannel.getTransactionId(event);
|
||||||
|
const createRequest = event => {
|
||||||
|
if (!ToDeviceChannel.canCreateRequest(ToDeviceChannel.getEventType(event))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const content = event.getContent();
|
||||||
|
const deviceId = content && content.from_device;
|
||||||
|
if (!deviceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const userId = event.getSender();
|
||||||
|
const channel = new ToDeviceChannel(
|
||||||
|
this._baseApis,
|
||||||
|
userId,
|
||||||
|
[deviceId],
|
||||||
|
);
|
||||||
|
return new VerificationRequest(
|
||||||
|
channel, this._verificationMethods, userId, this._baseApis);
|
||||||
|
};
|
||||||
|
this._handleVerificationEvent(event, transactionId,
|
||||||
|
this._toDeviceVerificationRequests, createRequest);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2841,44 +2450,65 @@ Crypto.prototype._onKeyVerificationMessage = function(event) {
|
|||||||
* @param {module:models/event.MatrixEvent} event the timeline event
|
* @param {module:models/event.MatrixEvent} event the timeline event
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._onTimelineEvent = function(event) {
|
Crypto.prototype._onTimelineEvent = function(event) {
|
||||||
if (event.getType() !== "m.room.message") {
|
if (!InRoomChannel.validateEvent(event, this._baseApis)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = event.getContent();
|
const transactionId = InRoomChannel.getTransactionId(event);
|
||||||
if (content.msgtype !== "m.key.verification.request") {
|
const createRequest = event => {
|
||||||
return;
|
if (!InRoomChannel.canCreateRequest(InRoomChannel.getEventType(event))) {
|
||||||
}
|
return;
|
||||||
// ignore event if malformed
|
}
|
||||||
if (!("from_device" in content) || typeof content.from_device !== "string"
|
const userId = event.getSender();
|
||||||
|| !("methods" in content) || !Array.isArray(content.methods)
|
const channel = new InRoomChannel(
|
||||||
|| !("to" in content) || typeof content.to !== "string") {
|
this._baseApis,
|
||||||
logger.warn("received invalid verification request over DM from "
|
event.getRoomId(),
|
||||||
+ event.getSender());
|
userId,
|
||||||
return;
|
);
|
||||||
}
|
return new VerificationRequest(
|
||||||
// check the request was directed to the syncing user
|
channel, this._verificationMethods, userId, this._baseApis);
|
||||||
if (content.to !== this._baseApis.getUserId()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = VERIFICATION_REQUEST_TIMEOUT - VERIFICATION_REQUEST_MARGIN;
|
|
||||||
if (event.getLocalAge() >= timeout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const request = {
|
|
||||||
event,
|
|
||||||
timeout: VERIFICATION_REQUEST_TIMEOUT,
|
|
||||||
methods: content.methods,
|
|
||||||
beginKeyVerification: (method) => {
|
|
||||||
const verifier = this.acceptVerificationDM(event, method);
|
|
||||||
return verifier;
|
|
||||||
},
|
|
||||||
cancel: () => {
|
|
||||||
const verifier = this.acceptVerificationDM(event, content.methods[0]);
|
|
||||||
verifier.cancel("User declined");
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
this._baseApis.emit("crypto.verification.request", request);
|
this._handleVerificationEvent(event, transactionId,
|
||||||
|
this._inRoomVerificationRequests, createRequest);
|
||||||
|
};
|
||||||
|
|
||||||
|
Crypto.prototype._handleVerificationEvent = async function(
|
||||||
|
event, transactionId, requestsMap, createRequest,
|
||||||
|
) {
|
||||||
|
const sender = event.getSender();
|
||||||
|
let requestsByTxnId = requestsMap.get(sender);
|
||||||
|
let isNewRequest = false;
|
||||||
|
let request = requestsByTxnId && requestsByTxnId.get(transactionId);
|
||||||
|
if (!request) {
|
||||||
|
request = createRequest(event);
|
||||||
|
// a request could not be made from this event, so ignore event
|
||||||
|
if (!request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isNewRequest = true;
|
||||||
|
if (!requestsByTxnId) {
|
||||||
|
requestsByTxnId = new Map();
|
||||||
|
requestsMap.set(sender, requestsByTxnId);
|
||||||
|
}
|
||||||
|
requestsByTxnId.set(transactionId, request);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const hadVerifier = !!request.verifier;
|
||||||
|
await request.channel.handleEvent(event, request);
|
||||||
|
// emit start event when verifier got set
|
||||||
|
if (!hadVerifier && request.verifier) {
|
||||||
|
this._baseApis.emit("crypto.verification.start", request.verifier);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("error while handling verification event", event, err);
|
||||||
|
}
|
||||||
|
if (!request.pending) {
|
||||||
|
requestsByTxnId.delete(transactionId);
|
||||||
|
if (requestsByTxnId.size === 0) {
|
||||||
|
requestsMap.delete(sender);
|
||||||
|
}
|
||||||
|
} else if (isNewRequest && !request.initiatedByMe) {
|
||||||
|
this._baseApis.emit("crypto.verification.request", request);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ limitations under the License.
|
|||||||
* Utilities common to olm encryption algorithms
|
* Utilities common to olm encryption algorithms
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
const anotherjson = require('another-json');
|
const anotherjson = require('another-json');
|
||||||
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import LocalStorageCryptoStore from './localStorage-crypto-store';
|
import LocalStorageCryptoStore from './localStorage-crypto-store';
|
||||||
import MemoryCryptoStore from './memory-crypto-store';
|
import MemoryCryptoStore from './memory-crypto-store';
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import MemoryCryptoStore from './memory-crypto-store';
|
import MemoryCryptoStore from './memory-crypto-store';
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import utils from '../../utils';
|
import utils from '../../utils';
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ export default class MemoryCryptoStore {
|
|||||||
getOrAddOutgoingRoomKeyRequest(request) {
|
getOrAddOutgoingRoomKeyRequest(request) {
|
||||||
const requestBody = request.requestBody;
|
const requestBody = request.requestBody;
|
||||||
|
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
// first see if we already have an entry for this request.
|
// first see if we already have an entry for this request.
|
||||||
const existing = this._getOutgoingRoomKeyRequest(requestBody);
|
const existing = this._getOutgoingRoomKeyRequest(requestBody);
|
||||||
|
|
||||||
|
|||||||
@@ -40,51 +40,36 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @class
|
* @class
|
||||||
*
|
*
|
||||||
|
* @param {module:base-apis~Channel} channel the verification channel to send verification messages over.
|
||||||
|
*
|
||||||
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
|
* @param {module:base-apis~MatrixBaseApis} baseApis base matrix api interface
|
||||||
*
|
*
|
||||||
* @param {string} userId the user ID that is being verified
|
* @param {string} userId the user ID that is being verified
|
||||||
*
|
*
|
||||||
* @param {string} deviceId the device ID that is being verified
|
* @param {string} deviceId the device ID that is being verified
|
||||||
*
|
*
|
||||||
* @param {string} transactionId the transaction ID to be used when sending events
|
|
||||||
*
|
|
||||||
* @param {string} [roomId] the room to use for verification
|
|
||||||
*
|
|
||||||
* @param {object} [startEvent] the m.key.verification.start event that
|
* @param {object} [startEvent] the m.key.verification.start event that
|
||||||
* initiated this verification, if any
|
* initiated this verification, if any
|
||||||
*
|
*
|
||||||
* @param {object} [request] the key verification request object related to
|
* @param {object} [request] the key verification request object related to
|
||||||
* this verification, if any
|
* this verification, if any
|
||||||
*/
|
*/
|
||||||
constructor(baseApis, userId, deviceId, transactionId, roomId, startEvent, request) {
|
constructor(channel, baseApis, userId, deviceId, startEvent, request) {
|
||||||
super();
|
super();
|
||||||
|
this._channel = channel;
|
||||||
this._baseApis = baseApis;
|
this._baseApis = baseApis;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.transactionId = transactionId;
|
this.startEvent = startEvent;
|
||||||
if (typeof(roomId) === "string" || roomId instanceof String) {
|
this.request = request;
|
||||||
this.roomId = roomId;
|
|
||||||
this.startEvent = startEvent;
|
|
||||||
this.request = request;
|
|
||||||
} else {
|
|
||||||
// if room ID was omitted, but start event and request were not
|
|
||||||
this.startEvent= roomId;
|
|
||||||
this.request = startEvent;
|
|
||||||
}
|
|
||||||
this.cancelled = false;
|
this.cancelled = false;
|
||||||
this._done = false;
|
this._done = false;
|
||||||
this._promise = null;
|
this._promise = null;
|
||||||
this._transactionTimeoutTimer = null;
|
this._transactionTimeoutTimer = null;
|
||||||
this._eventsSubscription = null;
|
|
||||||
|
|
||||||
// At this point, the verification request was received so start the timeout timer.
|
// At this point, the verification request was received so start the timeout timer.
|
||||||
this._resetTimer();
|
this._resetTimer();
|
||||||
|
|
||||||
if (this.roomId) {
|
|
||||||
this._sendWithTxnId = this._sendMessage;
|
|
||||||
} else {
|
|
||||||
this._sendWithTxnId = this._sendToDevice;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_resetTimer() {
|
_resetTimer() {
|
||||||
@@ -107,55 +92,8 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_contentFromEventWithTxnId(event) {
|
_send(type, uncompletedContent) {
|
||||||
if (this.roomId) { // verification as timeline event
|
return this._channel.send(type, uncompletedContent);
|
||||||
// ensure m.related_to is included in e2ee rooms
|
|
||||||
// as the field is excluded from encryption
|
|
||||||
const content = Object.assign({}, event.getContent());
|
|
||||||
content["m.relates_to"] = event.getRelation();
|
|
||||||
return content;
|
|
||||||
} else { // verification as to_device event
|
|
||||||
return event.getContent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* creates a content object with the transaction id added to it */
|
|
||||||
_contentWithTxnId(content) {
|
|
||||||
const copy = Object.assign({}, content);
|
|
||||||
if (this.roomId) { // verification as timeline event
|
|
||||||
copy["m.relates_to"] = {
|
|
||||||
rel_type: "m.reference",
|
|
||||||
event_id: this.transactionId,
|
|
||||||
};
|
|
||||||
} else { // verification as to_device event
|
|
||||||
copy.transaction_id = this.transactionId;
|
|
||||||
}
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
_send(type, contentWithoutTxnId) {
|
|
||||||
const content = this._contentWithTxnId(contentWithoutTxnId);
|
|
||||||
return this._sendWithTxnId(type, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* send a message to the other participant, using to-device messages
|
|
||||||
*/
|
|
||||||
_sendToDevice(type, content) {
|
|
||||||
if (this._done) {
|
|
||||||
return Promise.reject(new Error("Verification is already done"));
|
|
||||||
}
|
|
||||||
return this._baseApis.sendToDevice(type, {
|
|
||||||
[this.userId]: { [this.deviceId]: content },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* send a message to the other participant, using in-roomm messages
|
|
||||||
*/
|
|
||||||
_sendMessage(type, content) {
|
|
||||||
if (this._done) {
|
|
||||||
return Promise.reject(new Error("Verification is already done"));
|
|
||||||
}
|
|
||||||
return this._baseApis.sendEvent(this.roomId, type, content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitForEvent(type) {
|
_waitForEvent(type) {
|
||||||
@@ -173,10 +111,14 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return;
|
return;
|
||||||
} else if (e.getType() === this._expectedEvent) {
|
} else if (e.getType() === this._expectedEvent) {
|
||||||
this._expectedEvent = undefined;
|
// if we receive an expected m.key.verification.done, then just
|
||||||
this._rejectEvent = undefined;
|
// ignore it, since we don't need to do anything about it
|
||||||
this._resetTimer();
|
if (this._expectedEvent !== "m.key.verification.done") {
|
||||||
this._resolveEvent(e);
|
this._expectedEvent = undefined;
|
||||||
|
this._rejectEvent = undefined;
|
||||||
|
this._resetTimer();
|
||||||
|
this._resolveEvent(e);
|
||||||
|
}
|
||||||
} else if (e.getType() === "m.key.verification.cancel") {
|
} else if (e.getType() === "m.key.verification.cancel") {
|
||||||
const reject = this._reject;
|
const reject = this._reject;
|
||||||
this._reject = undefined;
|
this._reject = undefined;
|
||||||
@@ -199,7 +141,7 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
done() {
|
done() {
|
||||||
this._endTimer(); // always kill the activity timer
|
this._endTimer(); // always kill the activity timer
|
||||||
if (!this._done) {
|
if (!this._done) {
|
||||||
if (this.roomId) {
|
if (this._channel.needsDoneMessage) {
|
||||||
// verification in DM requires a done message
|
// verification in DM requires a done message
|
||||||
this._send("m.key.verification.done", {});
|
this._send("m.key.verification.done", {});
|
||||||
}
|
}
|
||||||
@@ -211,7 +153,7 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
this._endTimer(); // always kill the activity timer
|
this._endTimer(); // always kill the activity timer
|
||||||
if (!this._done) {
|
if (!this._done) {
|
||||||
this.cancelled = true;
|
this.cancelled = true;
|
||||||
if (this.userId && this.deviceId && this.transactionId) {
|
if (this.userId && this.deviceId) {
|
||||||
// send a cancellation to the other user (if it wasn't
|
// send a cancellation to the other user (if it wasn't
|
||||||
// cancelled by the other user)
|
// cancelled by the other user)
|
||||||
if (e === timeoutException) {
|
if (e === timeoutException) {
|
||||||
@@ -225,13 +167,11 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
content.code = content.code || "m.unknown";
|
content.code = content.code || "m.unknown";
|
||||||
content.reason = content.reason || content.body
|
content.reason = content.reason || content.body
|
||||||
|| "Unknown reason";
|
|| "Unknown reason";
|
||||||
content.transaction_id = this.transactionId;
|
|
||||||
this._send("m.key.verification.cancel", content);
|
this._send("m.key.verification.cancel", content);
|
||||||
} else {
|
} else {
|
||||||
this._send("m.key.verification.cancel", {
|
this._send("m.key.verification.cancel", {
|
||||||
code: "m.unknown",
|
code: "m.unknown",
|
||||||
reason: content.body || "Unknown reason",
|
reason: content.body || "Unknown reason",
|
||||||
transaction_id: this.transactionId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +179,6 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
this._send("m.key.verification.cancel", {
|
this._send("m.key.verification.cancel", {
|
||||||
code: "m.unknown",
|
code: "m.unknown",
|
||||||
reason: e.toString(),
|
reason: e.toString(),
|
||||||
transaction_id: this.transactionId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,10 +187,6 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
// but no reject function. If cancel is called again, we'd error.
|
// but no reject function. If cancel is called again, we'd error.
|
||||||
if (this._reject) this._reject(e);
|
if (this._reject) this._reject(e);
|
||||||
} else {
|
} else {
|
||||||
// unsubscribe from events, this happens in _reject usually but we don't have one here
|
|
||||||
if (this._eventsSubscription) {
|
|
||||||
this._eventsSubscription = this._eventsSubscription();
|
|
||||||
}
|
|
||||||
// FIXME: this causes an "Uncaught promise" console message
|
// FIXME: this causes an "Uncaught promise" console message
|
||||||
// if nothing ends up chaining this promise.
|
// if nothing ends up chaining this promise.
|
||||||
this._promise = Promise.reject(e);
|
this._promise = Promise.reject(e);
|
||||||
@@ -275,23 +210,11 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
this._resolve = (...args) => {
|
this._resolve = (...args) => {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
this._endTimer();
|
this._endTimer();
|
||||||
if (this.handler) {
|
|
||||||
// these listeners are attached in Crypto.acceptVerificationDM
|
|
||||||
if (this._eventsSubscription) {
|
|
||||||
this._eventsSubscription = this._eventsSubscription();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolve(...args);
|
resolve(...args);
|
||||||
};
|
};
|
||||||
this._reject = (...args) => {
|
this._reject = (...args) => {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
this._endTimer();
|
this._endTimer();
|
||||||
if (this.handler) {
|
|
||||||
// these listeners are attached in Crypto.acceptVerificationDM
|
|
||||||
if (this._eventsSubscription) {
|
|
||||||
this._eventsSubscription = this._eventsSubscription();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reject(...args);
|
reject(...args);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -344,8 +267,4 @@ export default class VerificationBase extends EventEmitter {
|
|||||||
await this._baseApis.setDeviceVerified(userId, deviceId);
|
await this._baseApis.setDeviceVerified(userId, deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setEventsSubscription(subscription) {
|
|
||||||
this._eventsSubscription = subscription;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,3 +85,13 @@ export const newUserMismatchError = errorFactory("m.user_error", "User mismatch"
|
|||||||
export const newInvalidMessageError = errorFactory(
|
export const newInvalidMessageError = errorFactory(
|
||||||
"m.invalid_message", "Invalid message",
|
"m.invalid_message", "Invalid message",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function errorFromEvent(event) {
|
||||||
|
const content = event.getContent();
|
||||||
|
if (content) {
|
||||||
|
const {code, reason} = content;
|
||||||
|
return {code, reason};
|
||||||
|
} else {
|
||||||
|
return {code: "Unknown error", reason: "m.unknown"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ export default class SAS extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _doSendVerification() {
|
async _doSendVerification() {
|
||||||
const initialMessage = this._contentWithTxnId({
|
const type = "m.key.verification.start";
|
||||||
|
const initialMessage = this._channel.completeContent(type, {
|
||||||
method: SAS.NAME,
|
method: SAS.NAME,
|
||||||
from_device: this._baseApis.deviceId,
|
from_device: this._baseApis.deviceId,
|
||||||
key_agreement_protocols: KEY_AGREEMENT_LIST,
|
key_agreement_protocols: KEY_AGREEMENT_LIST,
|
||||||
@@ -216,8 +217,7 @@ export default class SAS extends Base {
|
|||||||
});
|
});
|
||||||
// add the transaction id to the message beforehand because
|
// add the transaction id to the message beforehand because
|
||||||
// it needs to be included in the commitment hash later on
|
// it needs to be included in the commitment hash later on
|
||||||
this._sendWithTxnId("m.key.verification.start", initialMessage);
|
this._channel.sendCompleted(type, initialMessage);
|
||||||
|
|
||||||
|
|
||||||
let e = await this._waitForEvent("m.key.verification.accept");
|
let e = await this._waitForEvent("m.key.verification.accept");
|
||||||
let content = e.getContent();
|
let content = e.getContent();
|
||||||
@@ -254,7 +254,7 @@ export default class SAS extends Base {
|
|||||||
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
||||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||||
+ this.userId + this.deviceId
|
+ this.userId + this.deviceId
|
||||||
+ this.transactionId;
|
+ this._channel.transactionId;
|
||||||
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
||||||
const verifySAS = new Promise((resolve, reject) => {
|
const verifySAS = new Promise((resolve, reject) => {
|
||||||
this.emit("show_sas", {
|
this.emit("show_sas", {
|
||||||
@@ -270,7 +270,14 @@ export default class SAS extends Base {
|
|||||||
|
|
||||||
|
|
||||||
[e] = await Promise.all([
|
[e] = await Promise.all([
|
||||||
this._waitForEvent("m.key.verification.mac"),
|
this._waitForEvent("m.key.verification.mac")
|
||||||
|
.then((e) => {
|
||||||
|
// we don't expect any more messages from the other
|
||||||
|
// party, and they may send a m.key.verification.done
|
||||||
|
// when they're done on their end
|
||||||
|
this._expectedEvent = "m.key.verification.done";
|
||||||
|
return e;
|
||||||
|
}),
|
||||||
verifySAS,
|
verifySAS,
|
||||||
]);
|
]);
|
||||||
content = e.getContent();
|
content = e.getContent();
|
||||||
@@ -283,7 +290,7 @@ export default class SAS extends Base {
|
|||||||
async _doRespondVerification() {
|
async _doRespondVerification() {
|
||||||
// as m.related_to is not included in the encrypted content in e2e rooms,
|
// as m.related_to is not included in the encrypted content in e2e rooms,
|
||||||
// we need to make sure it is added
|
// we need to make sure it is added
|
||||||
let content = this._contentFromEventWithTxnId(this.startEvent);
|
let content = this._channel.completedContentFromEvent(this.startEvent);
|
||||||
|
|
||||||
// Note: we intersect using our pre-made lists, rather than the sets,
|
// Note: we intersect using our pre-made lists, rather than the sets,
|
||||||
// so that the result will be in our order of preference. Then
|
// so that the result will be in our order of preference. Then
|
||||||
@@ -331,7 +338,7 @@ export default class SAS extends Base {
|
|||||||
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
const sasInfo = "MATRIX_KEY_VERIFICATION_SAS"
|
||||||
+ this.userId + this.deviceId
|
+ this.userId + this.deviceId
|
||||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||||
+ this.transactionId;
|
+ this._channel.transactionId;
|
||||||
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
const sasBytes = olmSAS.generate_bytes(sasInfo, 6);
|
||||||
const verifySAS = new Promise((resolve, reject) => {
|
const verifySAS = new Promise((resolve, reject) => {
|
||||||
this.emit("show_sas", {
|
this.emit("show_sas", {
|
||||||
@@ -347,7 +354,14 @@ export default class SAS extends Base {
|
|||||||
|
|
||||||
|
|
||||||
[e] = await Promise.all([
|
[e] = await Promise.all([
|
||||||
this._waitForEvent("m.key.verification.mac"),
|
this._waitForEvent("m.key.verification.mac")
|
||||||
|
.then((e) => {
|
||||||
|
// we don't expect any more messages from the other
|
||||||
|
// party, and they may send a m.key.verification.done
|
||||||
|
// when they're done on their end
|
||||||
|
this._expectedEvent = "m.key.verification.done";
|
||||||
|
return e;
|
||||||
|
}),
|
||||||
verifySAS,
|
verifySAS,
|
||||||
]);
|
]);
|
||||||
content = e.getContent();
|
content = e.getContent();
|
||||||
@@ -363,7 +377,7 @@ export default class SAS extends Base {
|
|||||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||||
+ this.userId + this.deviceId
|
+ this.userId + this.deviceId
|
||||||
+ this.transactionId;
|
+ this._channel.transactionId;
|
||||||
|
|
||||||
const deviceKeyId = `ed25519:${this._baseApis.deviceId}`;
|
const deviceKeyId = `ed25519:${this._baseApis.deviceId}`;
|
||||||
mac[deviceKeyId] = olmSAS[macMethods[method]](
|
mac[deviceKeyId] = olmSAS[macMethods[method]](
|
||||||
@@ -393,7 +407,7 @@ export default class SAS extends Base {
|
|||||||
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
const baseInfo = "MATRIX_KEY_VERIFICATION_MAC"
|
||||||
+ this.userId + this.deviceId
|
+ this.userId + this.deviceId
|
||||||
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
+ this._baseApis.getUserId() + this._baseApis.deviceId
|
||||||
+ this.transactionId;
|
+ this._channel.transactionId;
|
||||||
|
|
||||||
if (content.keys !== olmSAS[macMethods[method]](
|
if (content.keys !== olmSAS[macMethods[method]](
|
||||||
Object.keys(content.mac).sort().join(","),
|
Object.keys(content.mac).sort().join(","),
|
||||||
|
|||||||
233
src/crypto/verification/request/InRoomChannel.js
Normal file
233
src/crypto/verification/request/InRoomChannel.js
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
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 VerificationRequest, {REQUEST_TYPE, START_TYPE} from "./VerificationRequest";
|
||||||
|
const MESSAGE_TYPE = "m.room.message";
|
||||||
|
const M_REFERENCE = "m.reference";
|
||||||
|
const M_RELATES_TO = "m.relates_to";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key verification channel that sends verification events in the timeline of a room.
|
||||||
|
* Uses the event id of the initial m.key.verification.request event as a transaction id.
|
||||||
|
*/
|
||||||
|
export default class InRoomChannel {
|
||||||
|
/**
|
||||||
|
* @param {MatrixClient} client the matrix client, to send messages with and get current user & device from.
|
||||||
|
* @param {string} roomId id of the room where verification events should be posted in, should be a DM with the given user.
|
||||||
|
* @param {string} userId id of user that the verification request is directed at, should be present in the room.
|
||||||
|
*/
|
||||||
|
constructor(client, roomId, userId) {
|
||||||
|
this._client = client;
|
||||||
|
this._roomId = roomId;
|
||||||
|
this._userId = userId;
|
||||||
|
this._requestEventId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether this channel needs m.key.verification.done messages to be sent after a successful verification */
|
||||||
|
get needsDoneMessage() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The transaction id generated/used by this verification channel */
|
||||||
|
get transactionId() {
|
||||||
|
return this._requestEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {MatrixEvent} event the event to get the timestamp of
|
||||||
|
* @return {number} the timestamp when the event was sent
|
||||||
|
*/
|
||||||
|
static getTimestamp(event) {
|
||||||
|
return event.getTs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
|
||||||
|
* @param {string} type the event type to check
|
||||||
|
* @returns {bool} boolean flag
|
||||||
|
*/
|
||||||
|
static canCreateRequest(type) {
|
||||||
|
return type === REQUEST_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the transaction id used by a given key verification event, if any
|
||||||
|
* @param {MatrixEvent} event the event
|
||||||
|
* @returns {string} the transaction id
|
||||||
|
*/
|
||||||
|
static getTransactionId(event) {
|
||||||
|
if (InRoomChannel.getEventType(event) === REQUEST_TYPE) {
|
||||||
|
return event.getId();
|
||||||
|
} else {
|
||||||
|
const relation = event.getRelation();
|
||||||
|
if (relation && relation.rel_type === M_REFERENCE) {
|
||||||
|
return relation.event_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this event is a well-formed key verification event.
|
||||||
|
* This only does checks that don't rely on the current state of a potentially already channel
|
||||||
|
* so we can prevent channels being created by invalid events.
|
||||||
|
* `handleEvent` can do more checks and choose to ignore invalid events.
|
||||||
|
* @param {MatrixEvent} event the event to validate
|
||||||
|
* @param {MatrixClient} client the client to get the current user and device id from
|
||||||
|
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||||
|
*/
|
||||||
|
static validateEvent(event, client) {
|
||||||
|
const txnId = InRoomChannel.getTransactionId(event);
|
||||||
|
if (typeof txnId !== "string" || txnId.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const type = InRoomChannel.getEventType(event);
|
||||||
|
const content = event.getContent();
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
if (typeof content.to !== "string" || !content.to.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ownUserId = client.getUserId();
|
||||||
|
// ignore requests that are not direct to or sent by the syncing user
|
||||||
|
if (event.getSender() !== ownUserId && content.to !== ownUserId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VerificationRequest.validateEvent(
|
||||||
|
type, event, InRoomChannel.getTimestamp(event), client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* As m.key.verification.request events are as m.room.message events with the InRoomChannel
|
||||||
|
* to have a fallback message in non-supporting clients, we map the real event type
|
||||||
|
* to the symbolic one to keep things in unison with ToDeviceChannel
|
||||||
|
* @param {MatrixEvent} event the event to get the type of
|
||||||
|
* @returns {string} the "symbolic" event type
|
||||||
|
*/
|
||||||
|
static getEventType(event) {
|
||||||
|
const type = event.getType();
|
||||||
|
if (type === MESSAGE_TYPE) {
|
||||||
|
const content = event.getContent();
|
||||||
|
if (content) {
|
||||||
|
const {msgtype} = content;
|
||||||
|
if (msgtype === REQUEST_TYPE) {
|
||||||
|
return REQUEST_TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the state of the channel, request, and verifier in response to a key verification event.
|
||||||
|
* @param {MatrixEvent} event to handle
|
||||||
|
* @param {VerificationRequest} request the request to forward handling to
|
||||||
|
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||||
|
*/
|
||||||
|
async handleEvent(event, request) {
|
||||||
|
const type = InRoomChannel.getEventType(event);
|
||||||
|
// do validations that need state (roomId, userId),
|
||||||
|
// ignore if invalid
|
||||||
|
if (event.getRoomId() !== this._roomId || event.getSender() !== this._userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// set transactionId when receiving a .request
|
||||||
|
if (!this._requestEventId && type === REQUEST_TYPE) {
|
||||||
|
this._requestEventId = event.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await request.handleEvent(type, event, InRoomChannel.getTimestamp(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the transaction id (relation) back to a received event
|
||||||
|
* so it has the same format as returned by `completeContent` before sending.
|
||||||
|
* The relation can not appear on the event content because of encryption,
|
||||||
|
* relations are excluded from encryption.
|
||||||
|
* @param {MatrixEvent} event the received event
|
||||||
|
* @returns {Object} the content object with the relation added again
|
||||||
|
*/
|
||||||
|
completedContentFromEvent(event) {
|
||||||
|
// ensure m.related_to is included in e2ee rooms
|
||||||
|
// as the field is excluded from encryption
|
||||||
|
const content = Object.assign({}, event.getContent());
|
||||||
|
content[M_RELATES_TO] = event.getRelation();
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Add all the fields to content needed for sending it over this channel.
|
||||||
|
* This is public so verification methods (SAS uses this) can get the exact
|
||||||
|
* content that will be sent independent of the used channel,
|
||||||
|
* as they need to calculate the hash of it.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} content the (incomplete) content
|
||||||
|
* @returns {object} the complete content, as it will be sent.
|
||||||
|
*/
|
||||||
|
completeContent(type, content) {
|
||||||
|
content = Object.assign({}, content);
|
||||||
|
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||||
|
content.from_device = this._client.getDeviceId();
|
||||||
|
}
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
// type is mapped to m.room.message in the send method
|
||||||
|
content = {
|
||||||
|
body: this._client.getUserId() + " is requesting to verify " +
|
||||||
|
"your key, but your client does not support in-chat key " +
|
||||||
|
"verification. You will need to use legacy key " +
|
||||||
|
"verification to verify keys.",
|
||||||
|
msgtype: REQUEST_TYPE,
|
||||||
|
to: this._userId,
|
||||||
|
from_device: content.from_device,
|
||||||
|
methods: content.methods,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
content[M_RELATES_TO] = {
|
||||||
|
rel_type: M_REFERENCE,
|
||||||
|
event_id: this.transactionId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event over the channel with the content not having gone through `completeContent`.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} uncompletedContent the (incomplete) content
|
||||||
|
* @returns {Promise} the promise of the request
|
||||||
|
*/
|
||||||
|
send(type, uncompletedContent) {
|
||||||
|
const content = this.completeContent(type, uncompletedContent);
|
||||||
|
return this.sendCompleted(type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event over the channel with the content having gone through `completeContent` already.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} content
|
||||||
|
* @returns {Promise} the promise of the request
|
||||||
|
*/
|
||||||
|
async sendCompleted(type, content) {
|
||||||
|
let sendType = type;
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
sendType = MESSAGE_TYPE;
|
||||||
|
}
|
||||||
|
const response = await this._client.sendEvent(this._roomId, sendType, content);
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
this._requestEventId = response.event_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/crypto/verification/request/RequestCallbackChannel.js
Normal file
59
src/crypto/verification/request/RequestCallbackChannel.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** a key verification channel that wraps over an actual channel to pass it to a verifier,
|
||||||
|
* to notify the VerificationRequest when the verifier tries to send anything over the channel.
|
||||||
|
* This way, the VerificationRequest can update its state based on events sent by the verifier.
|
||||||
|
* Anything that is not sending is just routing through to the wrapped channel.
|
||||||
|
*/
|
||||||
|
export default class RequestCallbackChannel {
|
||||||
|
constructor(request, channel) {
|
||||||
|
this._request = request;
|
||||||
|
this._channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get transactionId() {
|
||||||
|
return this._channel.transactionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get needsDoneMessage() {
|
||||||
|
return this._channel.needsDoneMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event, request) {
|
||||||
|
return this._channel.handleEvent(event, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
completedContentFromEvent(event) {
|
||||||
|
return this._channel.completedContentFromEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
completeContent(type, content) {
|
||||||
|
return this._channel.completeContent(type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
async send(type, uncompletedContent) {
|
||||||
|
this._request.handleVerifierSend(type, uncompletedContent);
|
||||||
|
const result = await this._channel.send(type, uncompletedContent);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendCompleted(type, content) {
|
||||||
|
this._request.handleVerifierSend(type, content);
|
||||||
|
const result = await this._channel.sendCompleted(type, content);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
249
src/crypto/verification/request/ToDeviceChannel.js
Normal file
249
src/crypto/verification/request/ToDeviceChannel.js
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
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 { randomString } from '../../../randomstring';
|
||||||
|
import logger from '../../../logger';
|
||||||
|
import VerificationRequest, {
|
||||||
|
PHASE_STARTED,
|
||||||
|
REQUEST_TYPE,
|
||||||
|
START_TYPE,
|
||||||
|
CANCEL_TYPE,
|
||||||
|
} from "./VerificationRequest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
newUnexpectedMessageError,
|
||||||
|
errorFromEvent,
|
||||||
|
} from "../Error";
|
||||||
|
/**
|
||||||
|
* A key verification channel that sends verification events over to_device messages.
|
||||||
|
* Generates its own transaction ids.
|
||||||
|
*/
|
||||||
|
export default class ToDeviceChannel {
|
||||||
|
// userId and devices of user we're about to verify
|
||||||
|
constructor(client, userId, devices, transactionId = null, deviceId = null) {
|
||||||
|
this._client = client;
|
||||||
|
this._userId = userId;
|
||||||
|
this._devices = devices;
|
||||||
|
this.transactionId = transactionId;
|
||||||
|
this._deviceId = deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getEventType(event) {
|
||||||
|
return event.getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the transaction id used by a given key verification event, if any
|
||||||
|
* @param {MatrixEvent} event the event
|
||||||
|
* @returns {string} the transaction id
|
||||||
|
*/
|
||||||
|
static getTransactionId(event) {
|
||||||
|
const content = event.getContent();
|
||||||
|
return content && content.transaction_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given event type should be allowed to initiate a new VerificationRequest over this channel
|
||||||
|
* @param {string} type the event type to check
|
||||||
|
* @returns {bool} boolean flag
|
||||||
|
*/
|
||||||
|
static canCreateRequest(type) {
|
||||||
|
return type === REQUEST_TYPE || type === START_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether this event is a well-formed key verification event.
|
||||||
|
* This only does checks that don't rely on the current state of a potentially already channel
|
||||||
|
* so we can prevent channels being created by invalid events.
|
||||||
|
* `handleEvent` can do more checks and choose to ignore invalid events.
|
||||||
|
* @param {MatrixEvent} event the event to validate
|
||||||
|
* @param {MatrixClient} client the client to get the current user and device id from
|
||||||
|
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||||
|
*/
|
||||||
|
static validateEvent(event, client) {
|
||||||
|
if (event.isCancelled()) {
|
||||||
|
logger.warn("Ignoring flagged verification request from "
|
||||||
|
+ event.getSender());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const content = event.getContent();
|
||||||
|
if (!content) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content.transaction_id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = event.getType();
|
||||||
|
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
if (!Number.isFinite(content.timestamp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (event.getSender() === client.getUserId() &&
|
||||||
|
content.from_device == client.getDeviceId()
|
||||||
|
) {
|
||||||
|
// ignore requests from ourselves, because it doesn't make sense for a
|
||||||
|
// device to verify itself
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return VerificationRequest.validateEvent(
|
||||||
|
type, event, ToDeviceChannel.getTimestamp(event), client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {MatrixEvent} event the event to get the timestamp of
|
||||||
|
* @return {number} the timestamp when the event was sent
|
||||||
|
*/
|
||||||
|
static getTimestamp(event) {
|
||||||
|
const content = event.getContent();
|
||||||
|
return content && content.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the state of the channel, request, and verifier in response to a key verification event.
|
||||||
|
* @param {MatrixEvent} event to handle
|
||||||
|
* @param {VerificationRequest} request the request to forward handling to
|
||||||
|
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||||
|
*/
|
||||||
|
async handleEvent(event, request) {
|
||||||
|
const type = event.getType();
|
||||||
|
const content = event.getContent();
|
||||||
|
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||||
|
if (!this.transactionId) {
|
||||||
|
this.transactionId = content.transaction_id;
|
||||||
|
}
|
||||||
|
const deviceId = content.from_device;
|
||||||
|
// adopt deviceId if not set before and valid
|
||||||
|
if (!this._deviceId && this._devices.includes(deviceId)) {
|
||||||
|
this._deviceId = deviceId;
|
||||||
|
}
|
||||||
|
// if no device id or different from addopted one, cancel with sender
|
||||||
|
if (!this._deviceId || this._deviceId !== deviceId) {
|
||||||
|
// also check that message came from the device we sent the request to earlier on
|
||||||
|
// and do send a cancel message to that device
|
||||||
|
// (but don't cancel the request for the device we should be talking to)
|
||||||
|
const cancelContent =
|
||||||
|
this.completeContent(errorFromEvent(newUnexpectedMessageError()));
|
||||||
|
return this._sendToDevices(CANCEL_TYPE, cancelContent, [deviceId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasStarted = request.phase === PHASE_STARTED;
|
||||||
|
await request.handleEvent(
|
||||||
|
event.getType(), event, ToDeviceChannel.getTimestamp(event));
|
||||||
|
const isStarted = request.phase === PHASE_STARTED;
|
||||||
|
|
||||||
|
// the request has picked a start event, tell the other devices about it
|
||||||
|
if (type === START_TYPE && !wasStarted && isStarted && this._deviceId) {
|
||||||
|
const nonChosenDevices = this._devices.filter(d => d !== this._deviceId);
|
||||||
|
if (nonChosenDevices.length) {
|
||||||
|
const message = this.completeContent({
|
||||||
|
code: "m.accepted",
|
||||||
|
reason: "Verification request accepted by another device",
|
||||||
|
});
|
||||||
|
await this._sendToDevices(CANCEL_TYPE, message, nonChosenDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {InRoomChannel.completedContentFromEvent} why this is needed.
|
||||||
|
* @param {MatrixEvent} event the received event
|
||||||
|
* @returns {Object} the content object
|
||||||
|
*/
|
||||||
|
completedContentFromEvent(event) {
|
||||||
|
return event.getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the fields to content needed for sending it over this channel.
|
||||||
|
* This is public so verification methods (SAS uses this) can get the exact
|
||||||
|
* content that will be sent independent of the used channel,
|
||||||
|
* as they need to calculate the hash of it.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} content the (incomplete) content
|
||||||
|
* @returns {object} the complete content, as it will be sent.
|
||||||
|
*/
|
||||||
|
completeContent(type, content) {
|
||||||
|
// make a copy
|
||||||
|
content = Object.assign({}, content);
|
||||||
|
if (this.transactionId) {
|
||||||
|
content.transaction_id = this.transactionId;
|
||||||
|
}
|
||||||
|
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||||
|
content.from_device = this._client.getDeviceId();
|
||||||
|
}
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
content.timestamp = Date.now();
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event over the channel with the content not having gone through `completeContent`.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} uncompletedContent the (incomplete) content
|
||||||
|
* @returns {Promise} the promise of the request
|
||||||
|
*/
|
||||||
|
send(type, uncompletedContent = {}) {
|
||||||
|
// create transaction id when sending request
|
||||||
|
if ((type === REQUEST_TYPE || type === START_TYPE) && !this.transactionId) {
|
||||||
|
this.transactionId = ToDeviceChannel.makeTransactionId();
|
||||||
|
}
|
||||||
|
const content = this.completeContent(type, uncompletedContent);
|
||||||
|
return this.sendCompleted(type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event over the channel with the content having gone through `completeContent` already.
|
||||||
|
* @param {string} type the event type
|
||||||
|
* @param {object} content
|
||||||
|
* @returns {Promise} the promise of the request
|
||||||
|
*/
|
||||||
|
sendCompleted(type, content) {
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
return this._sendToDevices(type, content, this._devices);
|
||||||
|
} else {
|
||||||
|
return this._sendToDevices(type, content, [this._deviceId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendToDevices(type, content, devices) {
|
||||||
|
if (devices.length) {
|
||||||
|
const msgMap = {};
|
||||||
|
for (const deviceId of devices) {
|
||||||
|
msgMap[deviceId] = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._client.sendToDevice(type, {[this._userId]: msgMap});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow Crypto module to create and know the transaction id before the .start event gets sent.
|
||||||
|
* @returns {string} the transaction id
|
||||||
|
*/
|
||||||
|
static makeTransactionId() {
|
||||||
|
return randomString(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
414
src/crypto/verification/request/VerificationRequest.js
Normal file
414
src/crypto/verification/request/VerificationRequest.js
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
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 logger from '../../../logger';
|
||||||
|
import RequestCallbackChannel from "./RequestCallbackChannel";
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
import {
|
||||||
|
newUnknownMethodError,
|
||||||
|
newUnexpectedMessageError,
|
||||||
|
errorFromEvent,
|
||||||
|
errorFactory,
|
||||||
|
} from "../Error";
|
||||||
|
|
||||||
|
// the recommended amount of time before a verification request
|
||||||
|
// should be (automatically) cancelled without user interaction
|
||||||
|
// and ignored.
|
||||||
|
const VERIFICATION_REQUEST_TIMEOUT = 10 * 60 * 1000; //10m
|
||||||
|
// to avoid almost expired verification notifications
|
||||||
|
// from showing a notification and almost immediately
|
||||||
|
// disappearing, also ignore verification requests that
|
||||||
|
// are this amount of time away from expiring.
|
||||||
|
const VERIFICATION_REQUEST_MARGIN = 3 * 1000; //3s
|
||||||
|
|
||||||
|
|
||||||
|
export const EVENT_PREFIX = "m.key.verification.";
|
||||||
|
export const REQUEST_TYPE = EVENT_PREFIX + "request";
|
||||||
|
export const START_TYPE = EVENT_PREFIX + "start";
|
||||||
|
export const CANCEL_TYPE = EVENT_PREFIX + "cancel";
|
||||||
|
export const DONE_TYPE = EVENT_PREFIX + "done";
|
||||||
|
// export const READY_TYPE = EVENT_PREFIX + "ready";
|
||||||
|
|
||||||
|
export const PHASE_UNSENT = 1;
|
||||||
|
export const PHASE_REQUESTED = 2;
|
||||||
|
// const PHASE_READY = 3;
|
||||||
|
export const PHASE_STARTED = 4;
|
||||||
|
export const PHASE_CANCELLED = 5;
|
||||||
|
export const PHASE_DONE = 6;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State machine for verification requests.
|
||||||
|
* Things that differ based on what channel is used to
|
||||||
|
* 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 {
|
||||||
|
constructor(channel, verificationMethods, userId, client) {
|
||||||
|
super();
|
||||||
|
this.channel = channel;
|
||||||
|
this._verificationMethods = verificationMethods;
|
||||||
|
this._client = client;
|
||||||
|
this._commonMethods = [];
|
||||||
|
this._setPhase(PHASE_UNSENT, false);
|
||||||
|
this._requestEvent = null;
|
||||||
|
this._otherUserId = userId;
|
||||||
|
this._initiatedByMe = null;
|
||||||
|
this._startTimestamp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stateless validation logic not specific to the channel.
|
||||||
|
* Invoked by the same static method in either channel.
|
||||||
|
* @param {string} type the "symbolic" event type, as returned by the `getEventType` function on the channel.
|
||||||
|
* @param {MatrixEvent} event the event to validate. Don't call getType() on it but use the `type` parameter instead.
|
||||||
|
* @param {number} timestamp the timestamp in milliseconds when this event was sent.
|
||||||
|
* @param {MatrixClient} client the client to get the current user and device id from
|
||||||
|
* @returns {bool} whether the event is valid and should be passed to handleEvent
|
||||||
|
*/
|
||||||
|
static validateEvent(type, event, timestamp, client) {
|
||||||
|
const content = event.getContent();
|
||||||
|
|
||||||
|
if (!type.startsWith(EVENT_PREFIX)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
if (!Array.isArray(content.methods)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||||
|
if (typeof content.from_device !== "string" ||
|
||||||
|
content.from_device.length === 0
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a timestamp is not provided on all to_device events
|
||||||
|
if (Number.isFinite(timestamp)) {
|
||||||
|
const elapsed = Date.now() - timestamp;
|
||||||
|
// ignore if event is too far in the past or too far in the future
|
||||||
|
if (elapsed > (VERIFICATION_REQUEST_TIMEOUT - VERIFICATION_REQUEST_MARGIN) ||
|
||||||
|
elapsed < -(VERIFICATION_REQUEST_TIMEOUT / 2)) {
|
||||||
|
logger.log("received verification that is too old or from the future");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** once the phase is PHASE_STARTED, common methods supported by both sides */
|
||||||
|
get methods() {
|
||||||
|
return this._commonMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the timeout of the request, provided for compatibility with previous verification code */
|
||||||
|
get timeout() {
|
||||||
|
const elapsed = Date.now() - this._startTimestamp;
|
||||||
|
return Math.max(0, VERIFICATION_REQUEST_TIMEOUT - elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the m.key.verification.request event that started this request, provided for compatibility with previous verification code */
|
||||||
|
get event() {
|
||||||
|
return this._requestEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** current phase of the request. Some properties might only be defined in a current phase. */
|
||||||
|
get phase() {
|
||||||
|
return this._phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The verifier to do the actual verification, once the method has been established. Only defined when the `phase` is PHASE_STARTED. */
|
||||||
|
get verifier() {
|
||||||
|
return this._verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** whether this request has sent it's initial event and needs more events to complete */
|
||||||
|
get pending() {
|
||||||
|
return this._phase !== PHASE_UNSENT
|
||||||
|
&& this._phase !== PHASE_DONE
|
||||||
|
&& this._phase !== PHASE_CANCELLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether this request was initiated by the syncing user.
|
||||||
|
* For InRoomChannel, this is who sent the .request event.
|
||||||
|
* For ToDeviceChannel, this is who sent the .start event
|
||||||
|
*/
|
||||||
|
get initiatedByMe() {
|
||||||
|
return this._initiatedByMe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the id of the user that initiated the request */
|
||||||
|
get requestingUserId() {
|
||||||
|
if (this.initiatedByMe) {
|
||||||
|
return this._client.getUserId();
|
||||||
|
} else {
|
||||||
|
return this._otherUserId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** the id of the user that (will) receive(d) the request */
|
||||||
|
get receivingUserId() {
|
||||||
|
if (this.initiatedByMe) {
|
||||||
|
return this._otherUserId;
|
||||||
|
} else {
|
||||||
|
return this._client.getUserId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start the key verification, creating a verifier and sending a .start event.
|
||||||
|
* If no previous events have been sent, pass in `targetDevice` to set who to direct this request to.
|
||||||
|
* @param {string} method the name of the verification method to use.
|
||||||
|
* @param {string?} targetDevice.userId the id of the user to direct this request to
|
||||||
|
* @param {string?} targetDevice.deviceId the id of the device to direct this request to
|
||||||
|
* @returns {VerifierBase} the verifier of the given method
|
||||||
|
*/
|
||||||
|
beginKeyVerification(method, targetDevice = null) {
|
||||||
|
// need to allow also when unsent in case of to_device
|
||||||
|
if (!this._verifier) {
|
||||||
|
if (this._hasValidPreStartPhase()) {
|
||||||
|
// when called on a request that was initiated with .request event
|
||||||
|
// check the method is supported by both sides
|
||||||
|
if (this._commonMethods.length && !this._commonMethods.includes(method)) {
|
||||||
|
throw newUnknownMethodError();
|
||||||
|
}
|
||||||
|
this._verifier = this._createVerifier(method, null, targetDevice);
|
||||||
|
if (!this._verifier) {
|
||||||
|
throw newUnknownMethodError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sends the initial .request event.
|
||||||
|
* @returns {Promise} resolves when the event has been sent.
|
||||||
|
*/
|
||||||
|
async sendRequest() {
|
||||||
|
if (this._phase === PHASE_UNSENT) {
|
||||||
|
this._initiatedByMe = true;
|
||||||
|
this._setPhase(PHASE_REQUESTED, false);
|
||||||
|
const methods = [...this._verificationMethods.keys()];
|
||||||
|
await this.channel.send(REQUEST_TYPE, {methods});
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the request, sending a cancellation to the other party
|
||||||
|
* @param {string?} error.reason the error reason to send the cancellation with
|
||||||
|
* @param {string?} error.code the error code to send the cancellation with
|
||||||
|
* @returns {Promise} resolves when the event has been sent.
|
||||||
|
*/
|
||||||
|
async cancel({reason = "User declined", code = "m.user"} = {}) {
|
||||||
|
if (this._phase !== PHASE_CANCELLED) {
|
||||||
|
if (this._verifier) {
|
||||||
|
return this._verifier.cancel(errorFactory(code, reason));
|
||||||
|
} else {
|
||||||
|
this._setPhase(PHASE_CANCELLED, false);
|
||||||
|
await this.channel.send(CANCEL_TYPE, {code, reason});
|
||||||
|
}
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Promise} with the verifier once it becomes available. Can be used after calling `sendRequest`. */
|
||||||
|
waitForVerifier() {
|
||||||
|
if (this.verifier) {
|
||||||
|
return Promise.resolve(this.verifier);
|
||||||
|
} else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const checkVerifier = () => {
|
||||||
|
if (this.verifier) {
|
||||||
|
this.off("change", checkVerifier);
|
||||||
|
resolve(this.verifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.on("change", checkVerifier);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setPhase(phase, notify = true) {
|
||||||
|
this._phase = phase;
|
||||||
|
if (notify) {
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the state of the request and verifier in response to a key verification event.
|
||||||
|
* @param {string} type the "symbolic" event type, as returned by the `getEventType` function on the channel.
|
||||||
|
* @param {MatrixEvent} event the event to handle. Don't call getType() on it but use the `type` parameter instead.
|
||||||
|
* @param {number} timestamp the timestamp in milliseconds when this event was sent.
|
||||||
|
* @returns {Promise} a promise that resolves when any requests as an anwser to the passed-in event are sent.
|
||||||
|
*/
|
||||||
|
async handleEvent(type, event, timestamp) {
|
||||||
|
const content = event.getContent();
|
||||||
|
if (type === REQUEST_TYPE || type === START_TYPE) {
|
||||||
|
if (this._startTimestamp === null) {
|
||||||
|
this._startTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type === REQUEST_TYPE) {
|
||||||
|
await this._handleRequest(content, event);
|
||||||
|
} else if (type === START_TYPE) {
|
||||||
|
await this._handleStart(content, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._verifier) {
|
||||||
|
if (type === CANCEL_TYPE || (this._verifier.events
|
||||||
|
&& this._verifier.events.includes(type))) {
|
||||||
|
this._verifier.handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === CANCEL_TYPE) {
|
||||||
|
this._handleCancel();
|
||||||
|
} else if (type === DONE_TYPE) {
|
||||||
|
this._handleDone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleRequest(content, event) {
|
||||||
|
if (this._phase === PHASE_UNSENT) {
|
||||||
|
const otherMethods = content.methods;
|
||||||
|
this._commonMethods = otherMethods.
|
||||||
|
filter(m => this._verificationMethods.has(m));
|
||||||
|
this._requestEvent = event;
|
||||||
|
this._initiatedByMe = this._wasSentByMe(event);
|
||||||
|
this._setPhase(PHASE_REQUESTED);
|
||||||
|
} else if (this._phase !== PHASE_REQUESTED) {
|
||||||
|
logger.warn("Ignoring flagged verification request from " +
|
||||||
|
event.getSender());
|
||||||
|
await this.cancel(errorFromEvent(newUnexpectedMessageError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_hasValidPreStartPhase() {
|
||||||
|
return this._phase === PHASE_REQUESTED ||
|
||||||
|
(
|
||||||
|
this.channel.constructor.canCreateRequest(START_TYPE) &&
|
||||||
|
this._phase === PHASE_UNSENT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleStart(content, event) {
|
||||||
|
if (this._hasValidPreStartPhase()) {
|
||||||
|
const {method} = content;
|
||||||
|
if (!this._verificationMethods.has(method)) {
|
||||||
|
await this.cancel(errorFromEvent(newUnknownMethodError()));
|
||||||
|
} else {
|
||||||
|
// if not in requested phase
|
||||||
|
if (this.phase === PHASE_UNSENT) {
|
||||||
|
this._initiatedByMe = this._wasSentByMe(event);
|
||||||
|
}
|
||||||
|
this._verifier = this._createVerifier(method, event);
|
||||||
|
this._setPhase(PHASE_STARTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by RequestCallbackChannel when the verifier sends an event
|
||||||
|
* @param {string} type the "symbolic" event type
|
||||||
|
* @param {object} content the completed or uncompleted content for the event to be sent
|
||||||
|
*/
|
||||||
|
handleVerifierSend(type, content) {
|
||||||
|
if (type === CANCEL_TYPE) {
|
||||||
|
this._handleCancel();
|
||||||
|
} else if (type === START_TYPE) {
|
||||||
|
if (this._phase === PHASE_UNSENT || this._phase === PHASE_REQUESTED) {
|
||||||
|
// if unsent, we're sending a (first) .start event and hence requesting the verification.
|
||||||
|
// in any other situation, the request was initiated by the other party.
|
||||||
|
this._initiatedByMe = this.phase === PHASE_UNSENT;
|
||||||
|
this._setPhase(PHASE_STARTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleCancel() {
|
||||||
|
if (this._phase !== PHASE_CANCELLED) {
|
||||||
|
this._setPhase(PHASE_CANCELLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleDone() {
|
||||||
|
if (this._phase === PHASE_STARTED) {
|
||||||
|
this._setPhase(PHASE_DONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_createVerifier(method, startEvent = null, targetDevice = null) {
|
||||||
|
const startSentByMe = startEvent && this._wasSentByMe(startEvent);
|
||||||
|
const {userId, deviceId} = this._getVerifierTarget(startEvent, targetDevice);
|
||||||
|
|
||||||
|
const VerifierCtor = this._verificationMethods.get(method);
|
||||||
|
if (!VerifierCtor) {
|
||||||
|
console.warn("could not find verifier constructor for method", method);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// invokes handleVerifierSend when verifier sends something
|
||||||
|
const callbackMedium = new RequestCallbackChannel(this, this.channel);
|
||||||
|
return new VerifierCtor(
|
||||||
|
callbackMedium,
|
||||||
|
this._client,
|
||||||
|
userId,
|
||||||
|
deviceId,
|
||||||
|
startSentByMe ? null : startEvent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getVerifierTarget(startEvent, targetDevice) {
|
||||||
|
// targetDevice should be set when creating a verifier for to_device before the .start event has been sent,
|
||||||
|
// so the userId and deviceId are provided
|
||||||
|
if (targetDevice) {
|
||||||
|
return targetDevice;
|
||||||
|
} else {
|
||||||
|
let targetEvent;
|
||||||
|
if (startEvent && !this._wasSentByMe(startEvent)) {
|
||||||
|
targetEvent = startEvent;
|
||||||
|
} else if (this._requestEvent && !this._wasSentByMe(this._requestEvent)) {
|
||||||
|
targetEvent = this._requestEvent;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"can't determine who the verifier should be targeted at. " +
|
||||||
|
"No .request or .start event and no targetDevice");
|
||||||
|
}
|
||||||
|
const userId = targetEvent.getSender();
|
||||||
|
const content = targetEvent.getContent();
|
||||||
|
const deviceId = content && content.from_device;
|
||||||
|
return {userId, deviceId};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only for .request and .start
|
||||||
|
_wasSentByMe(event) {
|
||||||
|
if (event.getSender() !== this._client.getUserId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const content = event.getContent();
|
||||||
|
if (!content || content.from_device !== this._client.getDeviceId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,6 @@ limitations under the License.
|
|||||||
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
* This is an internal module. See {@link MatrixHttpApi} for the public class.
|
||||||
* @module http-api
|
* @module http-api
|
||||||
*/
|
*/
|
||||||
import Promise from 'bluebird';
|
|
||||||
const parseContentType = require('content-type').parse;
|
const parseContentType = require('content-type').parse;
|
||||||
|
|
||||||
const utils = require("./utils");
|
const utils = require("./utils");
|
||||||
@@ -256,7 +255,7 @@ module.exports.MatrixHttpApi.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (global.XMLHttpRequest) {
|
if (global.XMLHttpRequest) {
|
||||||
const defer = Promise.defer();
|
const defer = utils.defer();
|
||||||
const xhr = new global.XMLHttpRequest();
|
const xhr = new global.XMLHttpRequest();
|
||||||
upload.xhr = xhr;
|
upload.xhr = xhr;
|
||||||
const cb = requestCallback(defer, opts.callback, this.opts.onlyData);
|
const cb = requestCallback(defer, opts.callback, this.opts.onlyData);
|
||||||
@@ -418,7 +417,7 @@ module.exports.MatrixHttpApi.prototype = {
|
|||||||
opts.headers['Authorization'] = `Bearer ${accessToken}`;
|
opts.headers['Authorization'] = `Bearer ${accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defer = Promise.defer();
|
const defer = utils.defer();
|
||||||
this.opts.request(
|
this.opts.request(
|
||||||
opts,
|
opts,
|
||||||
requestCallback(defer, callback, this.opts.onlyData),
|
requestCallback(defer, callback, this.opts.onlyData),
|
||||||
@@ -682,7 +681,7 @@ module.exports.MatrixHttpApi.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defer = Promise.defer();
|
const defer = utils.defer();
|
||||||
|
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
let timedOut = false;
|
let timedOut = false;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if an IndexedDB database exists. The only way to do so is to try opening it, so
|
* Check if an IndexedDB database exists. The only way to do so is to try opening it, so
|
||||||
* we do that and then delete it did not exist before.
|
* we do that and then delete it did not exist before.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ limitations under the License.
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/** @module interactive-auth */
|
/** @module interactive-auth */
|
||||||
import Promise from 'bluebird';
|
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
|
|
||||||
const utils = require("./utils");
|
const utils = require("./utils");
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ limitations under the License.
|
|||||||
* @module models/event
|
* @module models/event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import utils from '../utils.js';
|
import utils from '../utils.js';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ limitations under the License.
|
|||||||
* @module scheduler
|
* @module scheduler
|
||||||
*/
|
*/
|
||||||
const utils = require("./utils");
|
const utils = require("./utils");
|
||||||
import Promise from 'bluebird';
|
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
const DEBUG = false; // set true to enable console logging.
|
const DEBUG = false; // set true to enable console logging.
|
||||||
@@ -121,7 +120,7 @@ MatrixScheduler.prototype.queueEvent = function(event) {
|
|||||||
if (!this._queues[queueName]) {
|
if (!this._queues[queueName]) {
|
||||||
this._queues[queueName] = [];
|
this._queues[queueName] = [];
|
||||||
}
|
}
|
||||||
const defer = Promise.defer();
|
const defer = utils.defer();
|
||||||
this._queues[queueName].push({
|
this._queues[queueName].push({
|
||||||
event: event,
|
event: event,
|
||||||
defer: defer,
|
defer: defer,
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import SyncAccumulator from "../sync-accumulator";
|
import SyncAccumulator from "../sync-accumulator";
|
||||||
import utils from "../utils";
|
import utils from "../utils";
|
||||||
import * as IndexedDBHelpers from "../indexeddb-helpers";
|
import * as IndexedDBHelpers from "../indexeddb-helpers";
|
||||||
@@ -436,7 +435,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
*/
|
*/
|
||||||
_persistSyncData: function(nextBatch, roomsData, groupsData) {
|
_persistSyncData: function(nextBatch, roomsData, groupsData) {
|
||||||
logger.log("Persisting sync data up to ", nextBatch);
|
logger.log("Persisting sync data up to ", nextBatch);
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["sync"], "readwrite");
|
const txn = this.db.transaction(["sync"], "readwrite");
|
||||||
const store = txn.objectStore("sync");
|
const store = txn.objectStore("sync");
|
||||||
store.put({
|
store.put({
|
||||||
@@ -456,7 +455,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
* @return {Promise} Resolves if the events were persisted.
|
* @return {Promise} Resolves if the events were persisted.
|
||||||
*/
|
*/
|
||||||
_persistAccountData: function(accountData) {
|
_persistAccountData: function(accountData) {
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["accountData"], "readwrite");
|
const txn = this.db.transaction(["accountData"], "readwrite");
|
||||||
const store = txn.objectStore("accountData");
|
const store = txn.objectStore("accountData");
|
||||||
for (let i = 0; i < accountData.length; i++) {
|
for (let i = 0; i < accountData.length; i++) {
|
||||||
@@ -475,7 +474,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
* @return {Promise} Resolves if the users were persisted.
|
* @return {Promise} Resolves if the users were persisted.
|
||||||
*/
|
*/
|
||||||
_persistUserPresenceEvents: function(tuples) {
|
_persistUserPresenceEvents: function(tuples) {
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["users"], "readwrite");
|
const txn = this.db.transaction(["users"], "readwrite");
|
||||||
const store = txn.objectStore("users");
|
const store = txn.objectStore("users");
|
||||||
for (const tuple of tuples) {
|
for (const tuple of tuples) {
|
||||||
@@ -495,7 +494,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
* @return {Promise<Object[]>} A list of presence events in their raw form.
|
* @return {Promise<Object[]>} A list of presence events in their raw form.
|
||||||
*/
|
*/
|
||||||
getUserPresenceEvents: function() {
|
getUserPresenceEvents: function() {
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["users"], "readonly");
|
const txn = this.db.transaction(["users"], "readonly");
|
||||||
const store = txn.objectStore("users");
|
const store = txn.objectStore("users");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
@@ -512,7 +511,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
logger.log(
|
logger.log(
|
||||||
`LocalIndexedDBStoreBackend: loading account data...`,
|
`LocalIndexedDBStoreBackend: loading account data...`,
|
||||||
);
|
);
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["accountData"], "readonly");
|
const txn = this.db.transaction(["accountData"], "readonly");
|
||||||
const store = txn.objectStore("accountData");
|
const store = txn.objectStore("accountData");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
@@ -534,7 +533,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
logger.log(
|
logger.log(
|
||||||
`LocalIndexedDBStoreBackend: loading sync data...`,
|
`LocalIndexedDBStoreBackend: loading sync data...`,
|
||||||
);
|
);
|
||||||
return Promise.try(() => {
|
return utils.promiseTry(() => {
|
||||||
const txn = this.db.transaction(["sync"], "readonly");
|
const txn = this.db.transaction(["sync"], "readonly");
|
||||||
const store = txn.objectStore("sync");
|
const store = txn.objectStore("sync");
|
||||||
return selectQuery(store, undefined, (cursor) => {
|
return selectQuery(store, undefined, (cursor) => {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import {defer} from '../utils';
|
import {defer} from '../utils';
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
|
import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
@@ -123,7 +122,7 @@ class IndexedDBStoreWorker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prom.done((ret) => {
|
prom.then((ret) => {
|
||||||
this.postMessage.call(null, {
|
this.postMessage.call(null, {
|
||||||
command: 'cmd_success',
|
command: 'cmd_success',
|
||||||
seq: msg.seq,
|
seq: msg.seq,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
|
|
||||||
/* eslint-disable babel/no-invalid-this */
|
/* eslint-disable babel/no-invalid-this */
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
import {MemoryStore} from "./memory";
|
import {MemoryStore} from "./memory";
|
||||||
import utils from "../utils";
|
import utils from "../utils";
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
const User = require("../models/user");
|
const User = require("../models/user");
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new in-memory data store for the Matrix Client.
|
* Construct a new in-memory data store for the Matrix Client.
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
"use strict";
|
"use strict";
|
||||||
import Promise from 'bluebird';
|
|
||||||
/**
|
/**
|
||||||
* This is an internal module.
|
* This is an internal module.
|
||||||
* @module store/stub
|
* @module store/stub
|
||||||
|
|||||||
15
src/sync.js
15
src/sync.js
@@ -25,7 +25,6 @@ limitations under the License.
|
|||||||
* an alternative syncing API, we may want to have a proper syncing interface
|
* an alternative syncing API, we may want to have a proper syncing interface
|
||||||
* for HTTP and WS at some point.
|
* for HTTP and WS at some point.
|
||||||
*/
|
*/
|
||||||
import Promise from 'bluebird';
|
|
||||||
const User = require("./models/user");
|
const User = require("./models/user");
|
||||||
const Room = require("./models/room");
|
const Room = require("./models/room");
|
||||||
const Group = require('./models/group');
|
const Group = require('./models/group');
|
||||||
@@ -360,7 +359,7 @@ SyncApi.prototype._peekPoll = function(peekRoom, token) {
|
|||||||
room_id: peekRoom.roomId,
|
room_id: peekRoom.roomId,
|
||||||
timeout: 30 * 1000,
|
timeout: 30 * 1000,
|
||||||
from: token,
|
from: token,
|
||||||
}, undefined, 50 * 1000).done(function(res) {
|
}, undefined, 50 * 1000).then(function(res) {
|
||||||
if (self._peekRoomId !== peekRoom.roomId) {
|
if (self._peekRoomId !== peekRoom.roomId) {
|
||||||
debuglog("Stopped peeking in room %s", peekRoom.roomId);
|
debuglog("Stopped peeking in room %s", peekRoom.roomId);
|
||||||
return;
|
return;
|
||||||
@@ -1150,7 +1149,7 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Handle joins
|
// Handle joins
|
||||||
await Promise.mapSeries(joinRooms, async function(joinObj) {
|
await utils.promiseMapSeries(joinRooms, async function(joinObj) {
|
||||||
const room = joinObj.room;
|
const room = joinObj.room;
|
||||||
const stateEvents = self._mapSyncEventsFormat(joinObj.state, room);
|
const stateEvents = self._mapSyncEventsFormat(joinObj.state, room);
|
||||||
const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room);
|
const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room);
|
||||||
@@ -1278,8 +1277,8 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.mapSeries(stateEvents, processRoomEvent);
|
await utils.promiseMapSeries(stateEvents, processRoomEvent);
|
||||||
await Promise.mapSeries(timelineEvents, processRoomEvent);
|
await utils.promiseMapSeries(timelineEvents, processRoomEvent);
|
||||||
ephemeralEvents.forEach(function(e) {
|
ephemeralEvents.forEach(function(e) {
|
||||||
client.emit("event", e);
|
client.emit("event", e);
|
||||||
});
|
});
|
||||||
@@ -1383,7 +1382,7 @@ SyncApi.prototype._startKeepAlives = function(delay) {
|
|||||||
self._pokeKeepAlive();
|
self._pokeKeepAlive();
|
||||||
}
|
}
|
||||||
if (!this._connectionReturnedDefer) {
|
if (!this._connectionReturnedDefer) {
|
||||||
this._connectionReturnedDefer = Promise.defer();
|
this._connectionReturnedDefer = utils.defer();
|
||||||
}
|
}
|
||||||
return this._connectionReturnedDefer.promise;
|
return this._connectionReturnedDefer.promise;
|
||||||
};
|
};
|
||||||
@@ -1417,7 +1416,7 @@ SyncApi.prototype._pokeKeepAlive = function(connDidFail) {
|
|||||||
prefix: '',
|
prefix: '',
|
||||||
localTimeoutMs: 15 * 1000,
|
localTimeoutMs: 15 * 1000,
|
||||||
},
|
},
|
||||||
).done(function() {
|
).then(function() {
|
||||||
success();
|
success();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.httpStatus == 400 || err.httpStatus == 404) {
|
if (err.httpStatus == 400 || err.httpStatus == 404) {
|
||||||
@@ -1541,7 +1540,7 @@ SyncApi.prototype._resolveInvites = function(room) {
|
|||||||
} else {
|
} else {
|
||||||
promise = client.getProfileInfo(member.userId);
|
promise = client.getProfileInfo(member.userId);
|
||||||
}
|
}
|
||||||
promise.done(function(info) {
|
promise.then(function(info) {
|
||||||
// slightly naughty by doctoring the invite event but this means all
|
// slightly naughty by doctoring the invite event but this means all
|
||||||
// the code paths remain the same between invite/join display name stuff
|
// the code paths remain the same between invite/join display name stuff
|
||||||
// which is a worthy trade-off for some minor pollution.
|
// which is a worthy trade-off for some minor pollution.
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ limitations under the License.
|
|||||||
|
|
||||||
/** @module timeline-window */
|
/** @module timeline-window */
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
const EventTimeline = require("./models/event-timeline");
|
const EventTimeline = require("./models/event-timeline");
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
|
||||||
|
|||||||
11
src/utils.js
11
src/utils.js
@@ -21,7 +21,6 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const unhomoglyph = require('unhomoglyph');
|
const unhomoglyph = require('unhomoglyph');
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a dictionary of query parameters.
|
* Encode a dictionary of query parameters.
|
||||||
@@ -731,3 +730,13 @@ module.exports.defer = () => {
|
|||||||
|
|
||||||
return {resolve, reject, promise};
|
return {resolve, reject, promise};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.promiseMapSeries = async (promises, fn) => {
|
||||||
|
for (const o of await promises) {
|
||||||
|
await fn(await o);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.promiseTry = (fn) => {
|
||||||
|
return new Promise((resolve) => resolve(fn()));
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user