1
0
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:
J. Ryan Stinnett
2019-12-10 17:54:02 +00:00
65 changed files with 1392 additions and 886 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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);
}); });
} }

View File

@@ -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();

View File

@@ -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",

View File

@@ -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';

View File

@@ -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';

View File

@@ -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;

View File

@@ -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();
}); });

View File

@@ -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);
}); });
}); });

View File

@@ -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();
}); });
}); });

View File

@@ -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();
}); });

View File

@@ -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");

View File

@@ -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();

View File

@@ -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();
}); });
}); });

View File

@@ -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');

View File

@@ -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";

View File

@@ -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;

View File

@@ -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);
}); });
}); });
}); });

View File

@@ -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();

View File

@@ -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': {

View File

@@ -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() {

View File

@@ -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");

View File

@@ -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();
}); });

View File

@@ -430,13 +430,8 @@ 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"
&& content.msgtype === "m.key.verification.request") {
expect(content.methods).toContain(SAS.NAME);
expect(content.to).toBe(bob.client.getUserId());
const verifier = bob.client.acceptVerificationDM(event, SAS.NAME);
verifier.on("show_sas", (e) => { verifier.on("show_sas", (e) => {
if (!e.sas.emoji || !e.sas.decimal) { if (!e.sas.emoji || !e.sas.decimal) {
e.cancel(); e.cancel();
@@ -455,7 +450,6 @@ describe("SAS verification", function() {
}); });
await verifier.verify(); await verifier.verify();
resolve(); 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([

View File

@@ -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,
); );
} }

View File

@@ -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", () => {

View File

@@ -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;

View File

@@ -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(() => {});
}); });
}); });

View File

@@ -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);
}); });

View File

@@ -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;

View File

@@ -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";

View File

@@ -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)
*/ */

View File

@@ -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;

View File

@@ -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';

View File

@@ -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

View File

@@ -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(

View File

@@ -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");
/** /**

View File

@@ -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); return await request.waitForVerifier();
} };
}
} else {
methodMap = this._baseApis._crypto._verificationMethods;
}
let eventId = undefined; Crypto.prototype.acceptVerificationDM = function(event, method) {
const listenPromise = new Promise((_resolve, _reject) => { if(!InRoomChannel.validateEvent(event, this._baseApis)) {
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; return;
} }
const content = event.getContent(); const sender = event.getSender();
// the event seems to be related to this verification const requestsByTxnId = this._inRoomVerificationRequests.get(sender);
switch (event.getType()) { if (!requestsByTxnId) {
case "m.key.verification.start": { return;
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": { const transactionId = InRoomChannel.getTransactionId(event);
reject(event); const request = requestsByTxnId.get(transactionId);
break; if (!request) {
return;
} }
}
};
let initialResponseSubscription =
listenForEvents(this._baseApis, roomId, listener);
const resolve = (...args) => { return request.beginKeyVerification(method);
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 listenPromise;
}; };
Crypto.prototype.acceptVerificationDM = function(event, Method) { Crypto.prototype.requestVerification = async function(userId, methods, devices) {
if (typeof(Method) === "string") { if (!devices) {
Method = defaultVerificationMethods[Method]; devices = Object.keys(this._deviceList.getRawStoredDevicesForUser(userId));
} }
const content = event.getContent(); const channel = new ToDeviceChannel(this._baseApis, userId, devices);
const verifier = new Method( const request = await this._requestVerificationWithChannel(
this._baseApis, event.getSender(), content.from_device, event.getId(), userId,
event.getRoomId(), methods,
channel,
this._toDeviceVerificationRequests,
); );
verifier.handler = verificationEventHandler( return await request.waitForVerifier();
verifier, event.getSender(), event.getRoomId(), event.getId(),
);
const subscription = listenForEvents(
this._baseApis, event.getRoomId(), verifier.handler);
verifier.setEventsSubscription(subscription);
return verifier;
}; };
Crypto.prototype.requestVerification = function(userId, methods, devices) { 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 transactionId = ToDeviceChannel.getTransactionId(event);
const verifier = handler.verifier; const createRequest = event => {
if (verifier.events if (!ToDeviceChannel.canCreateRequest(ToDeviceChannel.getEventType(event))) {
&& verifier.events.includes(event.getType())) { return;
verifier.handleEvent(event);
} }
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 => {
if (!InRoomChannel.canCreateRequest(InRoomChannel.getEventType(event))) {
return; return;
} }
// ignore event if malformed const userId = event.getSender();
if (!("from_device" in content) || typeof content.from_device !== "string" const channel = new InRoomChannel(
|| !("methods" in content) || !Array.isArray(content.methods) this._baseApis,
|| !("to" in content) || typeof content.to !== "string") { event.getRoomId(),
logger.warn("received invalid verification request over DM from " userId,
+ event.getSender()); );
return; return new VerificationRequest(
} channel, this._verificationMethods, userId, this._baseApis);
// check the request was directed to the syncing user
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._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); this._baseApis.emit("crypto.verification.request", request);
}
}; };
/** /**

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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);

View File

@@ -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;
if (typeof(roomId) === "string" || roomId instanceof String) {
this.roomId = roomId;
this.startEvent = startEvent; this.startEvent = startEvent;
this.request = request; 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) {
// if we receive an expected m.key.verification.done, then just
// ignore it, since we don't need to do anything about it
if (this._expectedEvent !== "m.key.verification.done") {
this._expectedEvent = undefined; this._expectedEvent = undefined;
this._rejectEvent = undefined; this._rejectEvent = undefined;
this._resetTimer(); this._resetTimer();
this._resolveEvent(e); 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;
}
} }

View File

@@ -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"};
}
}

View File

@@ -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(","),

View 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;
}
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View File

@@ -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;

View File

@@ -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.

View File

@@ -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");

View File

@@ -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';

View File

@@ -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,

View File

@@ -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) => {

View File

@@ -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';

View File

@@ -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,

View File

@@ -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';

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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';

View File

@@ -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()));
};