From 6570402b95d015a5b2f8e2b362d317c93e145f81 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 3 May 2018 13:42:33 +0100 Subject: [PATCH 01/57] Add getMediaLimits to client --- src/client.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/client.js b/src/client.js index 892e144f1..4af13ba44 100644 --- a/src/client.js +++ b/src/client.js @@ -727,6 +727,20 @@ MatrixClient.prototype.getGroups = function() { return this.store.getGroups(); }; +/** + * Get the room for the given room ID. + * This function will return a valid room for any room for which a Room event + * has been emitted. Note in particular that other events, eg. RoomState.members + * will be emitted for a room before this function will return the given room. + * @param {module:client.callback} callback Optional. + * @return {module:client.Promise} Resolves with an object containing the limits. + */ +MatrixClient.prototype.getMediaLimits = function(callback) { + return return this._http.requestWithPrefix( + callback, "GET", "/limits", undefined, undefined, httpApi.PREFIX_MEDIA_R0, + ) +}; + // Room ops // ======== From 9596087959ee941c53f2f36c8fdca495ce39d602 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 3 May 2018 17:31:04 +0100 Subject: [PATCH 02/57] Remove extra return --- src/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index 4af13ba44..5ca65d5b5 100644 --- a/src/client.js +++ b/src/client.js @@ -736,9 +736,9 @@ MatrixClient.prototype.getGroups = function() { * @return {module:client.Promise} Resolves with an object containing the limits. */ MatrixClient.prototype.getMediaLimits = function(callback) { - return return this._http.requestWithPrefix( + return this._http.requestWithPrefix( callback, "GET", "/limits", undefined, undefined, httpApi.PREFIX_MEDIA_R0, - ) + ); }; // Room ops From 8dd425f8ff0b67ce0dbca326d2a66eac3637be2d Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 20 Jun 2018 17:24:45 +0100 Subject: [PATCH 03/57] Media/limits => /config --- src/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 5ca65d5b5..25e8314f3 100644 --- a/src/client.js +++ b/src/client.js @@ -737,7 +737,7 @@ MatrixClient.prototype.getGroups = function() { */ MatrixClient.prototype.getMediaLimits = function(callback) { return this._http.requestWithPrefix( - callback, "GET", "/limits", undefined, undefined, httpApi.PREFIX_MEDIA_R0, + callback, "GET", "/config", undefined, undefined, httpApi.PREFIX_MEDIA_R0, ); }; From fde00b1c627e174ee7dbb76efc06bec1a46f52d6 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Sat, 23 Jun 2018 12:57:29 +0100 Subject: [PATCH 04/57] getMediaLimits -> getMediaConfig --- src/client.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/client.js b/src/client.js index 25e8314f3..a684c5e01 100644 --- a/src/client.js +++ b/src/client.js @@ -728,14 +728,11 @@ MatrixClient.prototype.getGroups = function() { }; /** - * Get the room for the given room ID. - * This function will return a valid room for any room for which a Room event - * has been emitted. Note in particular that other events, eg. RoomState.members - * will be emitted for a room before this function will return the given room. + * Get the config for the media repository. * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves with an object containing the limits. + * @return {module:client.Promise} Resolves with an object containing the config. */ -MatrixClient.prototype.getMediaLimits = function(callback) { +MatrixClient.prototype.getMediaConfig = function(callback) { return this._http.requestWithPrefix( callback, "GET", "/config", undefined, undefined, httpApi.PREFIX_MEDIA_R0, ); From 98e448acddd6314071439ddbcfa61e048c5bbd33 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 11 Sep 2018 14:13:35 +0200 Subject: [PATCH 05/57] state events from context response were not wrapped in a MatrixEvent --- src/client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index bf1edf30b..8f0ca893d 100644 --- a/src/client.js +++ b/src/client.js @@ -2102,7 +2102,8 @@ MatrixClient.prototype.getEventTimeline = function(timelineSet, eventId) { self.getEventMapper())); timeline.getState(EventTimeline.FORWARDS).paginationToken = res.end; } else { - timeline.getState(EventTimeline.BACKWARDS).setUnknownStateEvents(res.state); + const stateEvents = utils.map(res.state, self.getEventMapper()); + timeline.getState(EventTimeline.BACKWARDS).setUnknownStateEvents(stateEvents); } timelineSet.addEventsToTimeline(matrixEvents, true, timeline, res.start); From daa7af06050d3a93f4319928f502239c3ff07f22 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 09:52:21 +0200 Subject: [PATCH 06/57] room name should only take canonical alias into account --- src/models/room.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index b5ce503cb..e5d794f0d 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1525,14 +1525,6 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) { } let alias = room.getCanonicalAlias(); - - if (!alias) { - const aliases = room.getAliases(); - - if (aliases.length) { - alias = aliases[0]; - } - } if (alias) { return alias; } From b829a39cd2efaf874c4c742a1d0bce11bf0e05bb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 09:59:20 +0200 Subject: [PATCH 07/57] fix tests --- spec/unit/room.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 774217c41..3937613c9 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -863,24 +863,24 @@ describe("Room", function() { expect(name.indexOf(userB)).toNotEqual(-1, name); }); - it("should show the room alias if one exists for private " + + it("should not show the room alias if one exists for private " + "(invite join_rules) rooms if a room name doesn't exist.", function() { const alias = "#room_alias:here"; setJoinRule("invite"); setAliases([alias, "#another:one"]); room.recalculate(); const name = room.name; - expect(name).toEqual(alias); + expect(name).toEqual("Empty room"); }); - it("should show the room alias if one exists for public " + + it("should not show the room alias if one exists for public " + "(public join_rules) rooms if a room name doesn't exist.", function() { const alias = "#room_alias:here"; setJoinRule("public"); setAliases([alias, "#another:one"]); room.recalculate(); const name = room.name; - expect(name).toEqual(alias); + expect(name).toEqual("Empty room"); }); it("should show the room name if one exists for private " + From 907e9fc476ad45ba2d4a527e76408019a872680e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 13 Sep 2018 10:04:31 +0200 Subject: [PATCH 08/57] fix lint --- src/models/room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/room.js b/src/models/room.js index e5d794f0d..bdbf1c52d 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1524,7 +1524,7 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) { } } - let alias = room.getCanonicalAlias(); + const alias = room.getCanonicalAlias(); if (alias) { return alias; } From eb690e14e44b67ebc0e13ab807d427e95a202d49 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Sep 2018 18:20:49 +0200 Subject: [PATCH 09/57] introduce Room.myMembership event As you don't always have your own member with lazy loading of members enabled, looking at the sync response section where a room appears is the most reliable way of determining the syncing user's membership in a room. Before we already used this to read the current room membership with `room.getMyMembership()`, but we were still using the `RoomMember.membership` event to detect when the syncing user's membership changed. This event will help make those checks work well with LL enabled. --- src/models/room.js | 19 +++++++++++++------ src/sync.js | 13 +++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index b5ce503cb..818d0e263 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -178,7 +178,7 @@ function Room(roomId, client, myUserId, opts) { // read by megolm; boolean value - null indicates "use global value" this._blacklistUnverifiedDevices = null; - this._syncedMembership = null; + this._selfMembership = null; this._summaryHeroes = null; // awaited by getEncryptionTargetMembers while room members are loading @@ -263,7 +263,7 @@ Room.prototype.getMyMembership = function() { return me.membership; } } - return this._syncedMembership; + return this._selfMembership; }; /** @@ -278,7 +278,7 @@ Room.prototype.getDMInviter = function() { return me.getDMInviter(); } } - if (this._syncedMembership === "invite") { + if (this._selfMembership === "invite") { // fall back to summary information const memberCount = this.getInvitedAndJoinedMemberCount(); if (memberCount == 2 && this._summaryHeroes.length) { @@ -362,8 +362,15 @@ Room.prototype.getAvatarFallbackMember = function() { * Sets the membership this room was received as during sync * @param {string} membership join | leave | invite */ -Room.prototype.setSyncedMembership = function(membership) { - this._syncedMembership = membership; +Room.prototype.updateMyMembership = function(membership) { + const prevMembership = this._selfMembership; + this._selfMembership = membership; + if (prevMembership !== membership) { + if (membership === "leave") { + this._cleanupAfterLeaving(); + } + this.emit("Room.myMembership", this, membership, prevMembership); + } }; Room.prototype._loadMembersFromServer = async function() { @@ -470,7 +477,7 @@ Room.prototype.clearLoadedMembersIfNeeded = async function() { * called when sync receives this room in the leave section * to do cleanup after leaving a room. Possibly called multiple times. */ -Room.prototype.onLeft = function() { +Room.prototype._cleanupAfterLeaving = function() { this.clearLoadedMembersIfNeeded().catch((err) => { console.error(`error after clearing loaded members from ` + `room ${this.roomId} after leaving`); diff --git a/src/sync.js b/src/sync.js index bcb131c59..39e82abe4 100644 --- a/src/sync.js +++ b/src/sync.js @@ -123,6 +123,7 @@ SyncApi.prototype.createRoom = function(roomId) { "Room.timelineReset", "Room.localEchoUpdated", "Room.accountData", + "Room.myMembership", ]); this._registerStateListeners(room); return room; @@ -976,9 +977,10 @@ SyncApi.prototype._processSyncResponse = async function( // Handle invites inviteRooms.forEach(function(inviteObj) { const room = inviteObj.room; - room.setSyncedMembership("invite"); const stateEvents = self._mapSyncEventsFormat(inviteObj.invite_state, room); + + room.updateMyMembership("invite"); self._processRoomEvents(room, stateEvents); if (inviteObj.isBrandNewRoom) { room.recalculate(); @@ -993,7 +995,6 @@ SyncApi.prototype._processSyncResponse = async function( // Handle joins await Promise.mapSeries(joinRooms, async function(joinObj) { const room = joinObj.room; - room.setSyncedMembership("join"); const stateEvents = self._mapSyncEventsFormat(joinObj.state, room); const timelineEvents = self._mapSyncEventsFormat(joinObj.timeline, room); const ephemeralEvents = self._mapSyncEventsFormat(joinObj.ephemeral); @@ -1009,6 +1010,8 @@ SyncApi.prototype._processSyncResponse = async function( ); } + room.updateMyMembership("join"); + joinObj.timeline = joinObj.timeline || {}; if (joinObj.isBrandNewRoom) { @@ -1116,8 +1119,6 @@ SyncApi.prototype._processSyncResponse = async function( // Handle leaves (e.g. kicked rooms) leaveRooms.forEach(function(leaveObj) { const room = leaveObj.room; - room.setSyncedMembership("leave"); - const stateEvents = self._mapSyncEventsFormat(leaveObj.state, room); const timelineEvents = @@ -1125,6 +1126,8 @@ SyncApi.prototype._processSyncResponse = async function( const accountDataEvents = self._mapSyncEventsFormat(leaveObj.account_data); + room.updateMyMembership("leave"); + self._processRoomEvents(room, stateEvents, timelineEvents); room.addAccountData(accountDataEvents); @@ -1145,8 +1148,6 @@ SyncApi.prototype._processSyncResponse = async function( accountDataEvents.forEach(function(e) { client.emit("event", e); }); - - room.onLeft(); }); // update the notification timeline, if appropriate. From 4630733b5591e1f4a598ed4a929e06af04c5910c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Sep 2018 18:23:48 +0200 Subject: [PATCH 10/57] don't fall back anymore to member, as this is more reliable --- src/models/room.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 818d0e263..c99d87ba5 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -257,12 +257,6 @@ Room.prototype.getLiveTimeline = function() { * @return {string} the membership type (join | leave | invite) for the logged in user */ Room.prototype.getMyMembership = function() { - if (this.myUserId) { - const me = this.getMember(this.myUserId); - if (me) { - return me.membership; - } - } return this._selfMembership; }; From 84e41c2ade6129b17922de2b6d168d33bff913f7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Sep 2018 18:28:07 +0200 Subject: [PATCH 11/57] fix tests --- spec/unit/room.spec.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 774217c41..a7d315575 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -1402,13 +1402,8 @@ describe("Room", function() { it("should return synced membership if membership isn't available yet", function() { const room = new Room(roomId, null, userA); - room.setSyncedMembership("invite"); + room.updateMyMembership("invite"); expect(room.getMyMembership()).toEqual("invite"); - room.addLiveEvents([utils.mkMembership({ - user: userA, mship: "join", - room: roomId, event: true, - })]); - expect(room.getMyMembership()).toEqual("join"); }); }); @@ -1439,11 +1434,11 @@ describe("Room", function() { it("should return false if synced membership not join", function() { const room = new Room(roomId, null, userA); - room.setSyncedMembership("invite"); + room.updateMyMembership("invite"); expect(room.maySendMessage()).toEqual(false); - room.setSyncedMembership("leave"); + room.updateMyMembership("leave"); expect(room.maySendMessage()).toEqual(false); - room.setSyncedMembership("join"); + room.updateMyMembership("join"); expect(room.maySendMessage()).toEqual(true); }); }); From fc3a00054f23261c0578c1441d086da28df0a2fe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Sep 2018 19:33:36 +0200 Subject: [PATCH 12/57] add test for new event --- spec/unit/room.spec.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index a7d315575..4a8ea7e59 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -1405,6 +1405,23 @@ describe("Room", function() { room.updateMyMembership("invite"); expect(room.getMyMembership()).toEqual("invite"); }); + it("should emit a Room.myMembership event on a change", + function() { + const room = new Room(roomId, null, userA); + const events = []; + room.on("Room.myMembership", (_room, membership, oldMembership) => { + events.push({membership, oldMembership}); + }); + room.updateMyMembership("invite"); + expect(room.getMyMembership()).toEqual("invite"); + expect(events[0]).toEqual({membership: "invite", oldMembership: null}); + events.splice(0); //clear + room.updateMyMembership("invite"); + expect(events.length).toEqual(0); + room.updateMyMembership("join"); + expect(room.getMyMembership()).toEqual("join"); + expect(events[0]).toEqual({membership: "join", oldMembership: "invite"}); + }); }); describe("guessDMUserId", function() { From a91fa5917465bdfb1d2026d3af51bc5f2e5af152 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Sep 2018 18:14:18 +0200 Subject: [PATCH 13/57] fix display name disambiguation with LL --- src/models/room-state.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/models/room-state.js b/src/models/room-state.js index 85d8a744f..52bc5ed36 100644 --- a/src/models/room-state.js +++ b/src/models/room-state.js @@ -180,7 +180,7 @@ RoomState.prototype.getSentinelMember = function(userId) { sentinel = new RoomMember(this.roomId, userId); const member = this.members[userId]; if (member) { - sentinel.setMembershipEvent(member.events.member); + sentinel.setMembershipEvent(member.events.member, this); } this._sentinels[userId] = sentinel; } @@ -501,7 +501,7 @@ RoomState.prototype._setOutOfBandMember = function(stateEvent) { } const member = this._getOrCreateMember(userId, stateEvent); - member.setMembershipEvent(stateEvent); + member.setMembershipEvent(stateEvent, this); // needed to know which members need to be stored seperately // as the are not part of the sync accumulator // this is cleared by setMembershipEvent so when it's updated through /sync From a08a3078dae5da90f5a2edbf380a8bbbbff44b28 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 20 Sep 2018 11:20:49 +0100 Subject: [PATCH 14/57] Revert "room name should only take canonical alias into account" --- spec/unit/room.spec.js | 8 ++++---- src/models/room.js | 10 +++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 6aac51626..4a8ea7e59 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -863,24 +863,24 @@ describe("Room", function() { expect(name.indexOf(userB)).toNotEqual(-1, name); }); - it("should not show the room alias if one exists for private " + + it("should show the room alias if one exists for private " + "(invite join_rules) rooms if a room name doesn't exist.", function() { const alias = "#room_alias:here"; setJoinRule("invite"); setAliases([alias, "#another:one"]); room.recalculate(); const name = room.name; - expect(name).toEqual("Empty room"); + expect(name).toEqual(alias); }); - it("should not show the room alias if one exists for public " + + it("should show the room alias if one exists for public " + "(public join_rules) rooms if a room name doesn't exist.", function() { const alias = "#room_alias:here"; setJoinRule("public"); setAliases([alias, "#another:one"]); room.recalculate(); const name = room.name; - expect(name).toEqual("Empty room"); + expect(name).toEqual(alias); }); it("should show the room name if one exists for private " + diff --git a/src/models/room.js b/src/models/room.js index fd9fcc044..c99d87ba5 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1525,7 +1525,15 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) { } } - const alias = room.getCanonicalAlias(); + let alias = room.getCanonicalAlias(); + + if (!alias) { + const aliases = room.getAliases(); + + if (aliases.length) { + alias = aliases[0]; + } + } if (alias) { return alias; } From 76175abea2d4c3630d4a96ed78274686179989ea Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Sep 2018 17:45:33 +0100 Subject: [PATCH 15/57] allow storing client options in indexeddb so we can tell what options the sync data was created with --- src/store/indexeddb-local-backend.js | 35 ++++++++++++++++++++++++++- src/store/indexeddb-remote-backend.js | 8 ++++++ src/store/indexeddb-store-worker.js | 6 +++++ src/store/indexeddb.js | 8 ++++++ src/store/memory.js | 9 +++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index 07d9f8139..67d2185b8 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -19,7 +19,7 @@ import Promise from 'bluebird'; import SyncAccumulator from "../sync-accumulator"; import utils from "../utils"; -const VERSION = 2; +const VERSION = 3; function createDatabase(db) { // Make user store, clobber based on user ID. (userId property of User objects) @@ -41,6 +41,12 @@ function upgradeSchemaV2(db) { oobMembersStore.createIndex("room", "room_id"); } +function upgradeSchemaV3(db) { + const clientOptionsStore = db.createObjectStore( + "client_options", { keyPath: ["clobber"]}); +} + + /** * Helper method to collect results from a Cursor and promiseify it. * @param {ObjectStore|Index} store The store to perform openCursor on. @@ -158,6 +164,9 @@ LocalIndexedDBStoreBackend.prototype = { if (oldVersion < 2) { upgradeSchemaV2(db); } + if (oldVersion < 3) { + upgradeSchemaV3(db); + } // Expand as needed. }; @@ -529,6 +538,30 @@ LocalIndexedDBStoreBackend.prototype = { }); }); }, + + getClientOptions: function() { + return Promise.resolve().then(() => { + const txn = this.db.transaction(["client_options"], "readonly"); + const store = txn.objectStore("client_options"); + return selectQuery(store, undefined, (cursor) => { + if (cursor.value && cursor.value && cursor.value.options) { + return cursor.value.options; + } + }).then((results) => results[0]); + }); + }, + + storeClientOptions: function(options) { + return Promise.resolve().then(() => { + const txn = this.db.transaction(["client_options"], "readwrite"); + const store = txn.objectStore("client_options"); + store.put({ + clobber: "-", // constant key so will always clobber + options: options + }); // put == UPSERT + return txnAsPromise(txn); + }); + } }; export default LocalIndexedDBStoreBackend; diff --git a/src/store/indexeddb-remote-backend.js b/src/store/indexeddb-remote-backend.js index 85f07f86b..751d155b4 100644 --- a/src/store/indexeddb-remote-backend.js +++ b/src/store/indexeddb-remote-backend.js @@ -114,6 +114,14 @@ RemoteIndexedDBStoreBackend.prototype = { return this._doCmd('clearOutOfBandMembers', [roomId]); }, + getClientOptions: function() { + return this._doCmd('getClientOptions'); + }, + + storeClientOptions: function(options) { + return this._doCmd('storeClientOptions', [options]); + }, + /** * Load all user presence events from the database. This is not cached. * @return {Promise} A list of presence events in their raw form. diff --git a/src/store/indexeddb-store-worker.js b/src/store/indexeddb-store-worker.js index adfc3535b..060d3c8f8 100644 --- a/src/store/indexeddb-store-worker.js +++ b/src/store/indexeddb-store-worker.js @@ -101,6 +101,12 @@ class IndexedDBStoreWorker { case 'setOutOfBandMembers': prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]); break; + case 'getClientOptions': + prom = this.backend.getClientOptions(); + break; + case 'storeClientOptions': + prom = this.backend.storeClientOptions(msg.args[0]); + break; } if (prom === undefined) { diff --git a/src/store/indexeddb.js b/src/store/indexeddb.js index 0de57c89a..479b79e8c 100644 --- a/src/store/indexeddb.js +++ b/src/store/indexeddb.js @@ -246,4 +246,12 @@ IndexedDBStore.prototype.clearOutOfBandMembers = function(roomId) { return this.backend.clearOutOfBandMembers(roomId); }; +IndexedDBStore.prototype.getClientOptions = function() { + return this.backend.getClientOptions(); +}; + +IndexedDBStore.prototype.storeClientOptions = function(options) { + return this.backend.storeClientOptions(options); +}; + module.exports.IndexedDBStore = IndexedDBStore; diff --git a/src/store/memory.js b/src/store/memory.js index a5f72af4f..a4d2ddb6b 100644 --- a/src/store/memory.js +++ b/src/store/memory.js @@ -55,6 +55,7 @@ module.exports.MatrixInMemoryStore = function MatrixInMemoryStore(opts) { this._oobMembers = { // roomId: [member events] }; + this._clientOptions = {}; }; module.exports.MatrixInMemoryStore.prototype = { @@ -402,4 +403,12 @@ module.exports.MatrixInMemoryStore.prototype = { this._oobMembers[roomId] = membershipEvents; return Promise.resolve(); }, + + getClientOptions: function() { + return this._clientOptions; + }, + + storeClientOptions: function(options) { + return this._clientOptions = Object.assign({}, options); + }, }; From 28184b4a296fe8695d54129bc4023b0c2f7e7e58 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Sep 2018 15:32:10 +0100 Subject: [PATCH 16/57] check if lazy loading was enabled before in startClient --- src/client.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 8f0ca893d..973263429 100644 --- a/src/client.js +++ b/src/client.js @@ -3115,7 +3115,16 @@ MatrixClient.prototype.startClient = async function(opts) { opts.lazyLoadMembers = false; } } - + // need to vape the store when enabling LL and wasn't enabled before + let hadLLEnabledBefore = false; + const prevClientOptions = await this.store.getClientOptions(); + if (prevClientOptions) { + hadLLEnabledBefore = !!prevClientOptions.lazyLoadMembers; + } + if (!hadLLEnabledBefore && opts.lazyLoadMembers) { + await this.store.deleteAllData(); + throw new Error("vaped the store, you need to resync"); + } if (opts.lazyLoadMembers && this._crypto) { this._crypto.enableLazyLoading(); } @@ -3128,6 +3137,7 @@ MatrixClient.prototype.startClient = async function(opts) { return this._canResetTimelineCallback(roomId); }; this._clientOpts = opts; + await this.store.storeClientOptions(this._clientOpts); this._syncApi = new SyncApi(this, opts); this._syncApi.sync(); From 0519c4c6b1a2b4696899b923c57c5207b9ef7602 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Sep 2018 15:53:14 +0100 Subject: [PATCH 17/57] await startClient and use promises also so error gets shown --- spec/integ/matrix-client-opts.spec.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/spec/integ/matrix-client-opts.spec.js b/spec/integ/matrix-client-opts.spec.js index c7388678a..201ae7960 100644 --- a/spec/integ/matrix-client-opts.spec.js +++ b/spec/integ/matrix-client-opts.spec.js @@ -94,7 +94,7 @@ describe("MatrixClient opts", function() { httpBackend.flush("/txn1", 1); }); - it("should be able to sync / get new events", function(done) { + it("should be able to sync / get new events", async function() { const expectedEventTypes = [ // from /initialSync "m.room.message", "m.room.name", "m.room.member", "m.room.member", "m.room.create", @@ -110,20 +110,16 @@ describe("MatrixClient opts", function() { httpBackend.when("GET", "/pushrules").respond(200, {}); httpBackend.when("POST", "/filter").respond(200, { filter_id: "foo" }); httpBackend.when("GET", "/sync").respond(200, syncData); - client.startClient(); - httpBackend.flush("/pushrules", 1).then(function() { - return httpBackend.flush("/filter", 1); - }).then(function() { - return Promise.all([ - httpBackend.flush("/sync", 1), - utils.syncPromise(client), - ]); - }).done(function() { - expect(expectedEventTypes.length).toEqual( - 0, "Expected to see event types: " + expectedEventTypes, - ); - done(); - }); + await client.startClient(); + await httpBackend.flush("/pushrules", 1); + await httpBackend.flush("/filter", 1); + await Promise.all([ + httpBackend.flush("/sync", 1), + utils.syncPromise(client), + ]); + expect(expectedEventTypes.length).toEqual( + 0, "Expected to see event types: " + expectedEventTypes, + ); }); }); From b0dbb20e22d32e64c805a16ed7a62233cfb564d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Sep 2018 15:53:40 +0100 Subject: [PATCH 18/57] fixup of in memory stores --- src/store/memory.js | 5 +++-- src/store/stub.js | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/store/memory.js b/src/store/memory.js index a4d2ddb6b..33c4ff9c6 100644 --- a/src/store/memory.js +++ b/src/store/memory.js @@ -405,10 +405,11 @@ module.exports.MatrixInMemoryStore.prototype = { }, getClientOptions: function() { - return this._clientOptions; + return Promise.resolve(this._clientOptions); }, storeClientOptions: function(options) { - return this._clientOptions = Object.assign({}, options); + this._clientOptions = Object.assign({}, options); + return Promise.resolve(); }, }; diff --git a/src/store/stub.js b/src/store/stub.js index d0c2cabc5..2582a9802 100644 --- a/src/store/stub.js +++ b/src/store/stub.js @@ -276,6 +276,14 @@ StubStore.prototype = { clearOutOfBandMembers: function() { return Promise.resolve(); }, + + getClientOptions: function() { + return Promise.resolve(); + }, + + storeClientOptions: function() { + return Promise.resolve(); + }, }; /** Stub Store class. */ From 8f2824186a153fe27627ea4bf2f2a6a0d4059f72 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Sep 2018 10:09:58 -0600 Subject: [PATCH 19/57] Split npm start into an init and watch script This is to better support riot-web's build process without losing the functionality supplied by `npm start`. The watch script no longer performs an initial build and thus `start:init` has been created for this purpose. --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f1ecfc14..e59fdc090 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "test": "npm run test:build && npm run test:run", "check": "npm run test:build && _mocha --recursive specbuild --colors", "gendoc": "babel --no-babelrc -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc", - "start": "babel -s -w -d lib src", + "start": "npm run start:init && npm run start:watch", + "start:watch": "babel -s -w --skip-initial-build -d lib src", + "start:init": "babel -s -d lib src", "clean": "rimraf lib dist", "build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && uglifyjs -c -m -o dist/browser-matrix.min.js --source-map dist/browser-matrix.min.js.map --in-source-map dist/browser-matrix.js.map dist/browser-matrix.js", "dist": "npm run build", From c556ca40b159e141b90e542cd8f82329fdce6043 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Sep 2018 17:49:54 +0100 Subject: [PATCH 20/57] Support Olm with WebAssembly wasm Olm has a new interface: it now has an init method that needs to be called and the promise it returns waited on before the Olm module is used. Support that, and allow Crypto etc to be imported whether Olm is enabled or not. Change whether olm is enabled to be async since now it will be unavailable if the async module init fails. Don't call getOlmVersion() until the Olm.init() is done. --- spec/unit/crypto.spec.js | 13 ++++---- spec/unit/crypto/algorithms/megolm.spec.js | 15 ++++------ src/client.js | 35 ++++++++++------------ src/crypto/OlmDevice.js | 3 -- src/crypto/index.js | 15 +++++++--- 5 files changed, 40 insertions(+), 41 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 4d949e05e..734941cb5 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -1,20 +1,23 @@ "use strict"; import 'source-map-support/register'; +import Crypto from '../../lib/crypto'; +import expect from 'expect'; const sdk = require("../.."); -let Crypto; -if (sdk.CRYPTO_ENABLED) { - Crypto = require("../../lib/crypto"); -} -import expect from 'expect'; describe("Crypto", function() { if (!sdk.CRYPTO_ENABLED) { return; } + + beforeEach(function(done) { + Olm.init().then(done); + }); + it("Crypto exposes the correct olm library version", function() { + console.log(Crypto); expect(Crypto.getOlmVersion()[0]).toEqual(2); }); }); diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js index cf8e58f2e..db28e3a51 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.js +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -13,14 +13,8 @@ import WebStorageSessionStore from '../../../../lib/store/session/webstorage'; import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js'; import MockStorageApi from '../../../MockStorageApi'; import testUtils from '../../../test-utils'; - -// Crypto and OlmDevice won't import unless we have global.Olm -let OlmDevice; -let Crypto; -if (global.Olm) { - OlmDevice = require('../../../../lib/crypto/OlmDevice'); - Crypto = require('../../../../lib/crypto'); -} +import OlmDevice from '../../../../lib/crypto/OlmDevice'; +import Crypto from '../../../../lib/crypto'; const MatrixEvent = sdk.MatrixEvent; const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; @@ -69,7 +63,8 @@ describe("MegolmDecryption", function() { describe('receives some keys:', function() { let groupSession; - beforeEach(function() { + beforeEach(async function() { + await Olm.init(); groupSession = new global.Olm.OutboundGroupSession(); groupSession.create(); @@ -98,7 +93,7 @@ describe("MegolmDecryption", function() { }, }; - return event.attemptDecryption(mockCrypto).then(() => { + await event.attemptDecryption(mockCrypto).then(() => { megolmDecryption.onRoomKeyEvent(event); }); }); diff --git a/src/client.js b/src/client.js index 8f0ca893d..8ca115a38 100644 --- a/src/client.js +++ b/src/client.js @@ -45,6 +45,8 @@ const ContentHelpers = require("./content-helpers"); import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; +import Crypto from './crypto'; +import { isCryptoAvailable } from './crypto'; const LAZY_LOADING_MESSAGES_FILTER = { lazy_load_members: true, @@ -58,14 +60,7 @@ const LAZY_LOADING_SYNC_FILTER = { const SCROLLBACK_DELAY_MS = 3000; -let CRYPTO_ENABLED = false; - -try { - var Crypto = require("./crypto"); - CRYPTO_ENABLED = true; -} catch (e) { - console.warn("Unable to load crypto module: crypto will be disabled: " + e); -} +let CRYPTO_ENABLED = isCryptoAvailable(); /** * Construct a Matrix Client. Only directly construct this if you want to use @@ -140,6 +135,8 @@ function MatrixClient(opts) { MatrixBaseApis.call(this, opts); + this.olmVersion = null; // Populated after initCrypto is done + this.reEmitter = new ReEmitter(this); this.store = opts.store || new StubStore(); @@ -192,10 +189,6 @@ function MatrixClient(opts) { this._forceTURN = opts.forceTURN || false; - if (CRYPTO_ENABLED) { - this.olmVersion = Crypto.getOlmVersion(); - } - // List of which rooms have encryption enabled: separate from crypto because // we still want to know which rooms are encrypted even if crypto is disabled: // we don't want to start sending unencrypted events to them. @@ -385,6 +378,14 @@ MatrixClient.prototype.setNotifTimelineSet = function(notifTimelineSet) { * successfully initialised. */ MatrixClient.prototype.initCrypto = async function() { + if (!isCryptoAvailable()) { + throw new Error( + `End-to-end encryption not supported in this js-sdk build: did ` + + `you remember to load the olm library?`, + ); + return; + } + if (this._crypto) { console.warn("Attempt to re-initialise e2e encryption on MatrixClient"); return; @@ -402,13 +403,6 @@ MatrixClient.prototype.initCrypto = async function() { // initialise the list of encrypted rooms (whether or not crypto is enabled) await this._roomList.init(); - if (!CRYPTO_ENABLED) { - throw new Error( - `End-to-end encryption not supported in this js-sdk build: did ` + - `you remember to load the olm library?`, - ); - } - const userId = this.getUserId(); if (userId === null) { throw new Error( @@ -440,6 +434,9 @@ MatrixClient.prototype.initCrypto = async function() { await crypto.init(); + this.olmVersion = Crypto.getOlmVersion(); + + // if crypto initialisation was successful, tell it to attach its event // handlers. crypto.registerEventHandlers(this); diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index cda14779c..b8c70cd0c 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -23,9 +23,6 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; * @module crypto/OlmDevice */ const Olm = global.Olm; -if (!Olm) { - throw new Error("global.Olm is not defined"); -} // The maximum size of an event is 65K, and we base64 the content, so this is a diff --git a/src/crypto/index.js b/src/crypto/index.js index f00477d4b..0fe33663f 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -36,6 +36,12 @@ const DeviceList = require('./DeviceList').default; import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; +const Olm = global.Olm; + +export function isCryptoAvailable() { + return Boolean(Olm); +} + /** * Cryptography bits * @@ -62,7 +68,7 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; * * @param {RoomList} roomList An initialised RoomList object */ -function Crypto(baseApis, sessionStore, userId, deviceId, +export default function Crypto(baseApis, sessionStore, userId, deviceId, clientStore, cryptoStore, roomList) { this._baseApis = baseApis; this._sessionStore = sessionStore; @@ -124,6 +130,10 @@ utils.inherits(Crypto, EventEmitter); * Returns a promise which resolves once the crypto module is ready for use. */ Crypto.prototype.init = async function() { + // Olm is just an object with a .then, not a fully-fledged promise, so + // pass it into bluebird to make it a proper promise. + await Olm.init(); + const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount()); let cryptoStoreHasAccount; await this._cryptoStore.doTxn( @@ -1518,6 +1528,3 @@ class IncomingRoomKeyRequestCancellation { * @event module:client~MatrixClient#"crypto.warning" * @param {string} type One of the strings listed above */ - -/** */ -module.exports = Crypto; From 63cc3fd890b9927c489d65e50895a0ba74196ca4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 25 Sep 2018 18:14:11 +0100 Subject: [PATCH 21/57] lint --- spec/unit/crypto.spec.js | 1 + spec/unit/crypto/algorithms/megolm.spec.js | 2 ++ src/client.js | 3 +-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 734941cb5..1b28ad683 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -6,6 +6,7 @@ import expect from 'expect'; const sdk = require("../.."); +const Olm = global.Olm; describe("Crypto", function() { if (!sdk.CRYPTO_ENABLED) { diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js index db28e3a51..6c777859e 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.js +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -21,6 +21,8 @@ const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const ROOM_ID = '!ROOM:ID'; +const Olm = global.Olm; + describe("MegolmDecryption", function() { if (!global.Olm) { console.warn('Not running megolm unit tests: libolm not present'); diff --git a/src/client.js b/src/client.js index 8ca115a38..1a05998eb 100644 --- a/src/client.js +++ b/src/client.js @@ -60,7 +60,7 @@ const LAZY_LOADING_SYNC_FILTER = { const SCROLLBACK_DELAY_MS = 3000; -let CRYPTO_ENABLED = isCryptoAvailable(); +const CRYPTO_ENABLED = isCryptoAvailable(); /** * Construct a Matrix Client. Only directly construct this if you want to use @@ -383,7 +383,6 @@ MatrixClient.prototype.initCrypto = async function() { `End-to-end encryption not supported in this js-sdk build: did ` + `you remember to load the olm library?`, ); - return; } if (this._crypto) { From 1fd8c43d942edd0d3685ca3fb39db3a56f5d037c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Sep 2018 18:50:09 +0100 Subject: [PATCH 22/57] fix tests --- spec/unit/matrix-client.spec.js | 49 ++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index 2fc5d2cf0..dae9aaa3a 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -139,6 +139,8 @@ describe("MatrixClient", function() { store.getSavedSync = expect.createSpy().andReturn(Promise.resolve(null)); store.getSavedSyncToken = expect.createSpy().andReturn(Promise.resolve(null)); store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); + store.getClientOptions = expect.createSpy().andReturn(Promise.resolve(null)); + store.storeClientOptions = expect.createSpy().andReturn(Promise.resolve(null)); client = new MatrixClient({ baseUrl: "https://my.home.server", idBaseUrl: identityServerUrl, @@ -182,7 +184,7 @@ describe("MatrixClient", function() { }); }); - it("should not POST /filter if a matching filter already exists", function(done) { + it("should not POST /filter if a matching filter already exists", async function() { httpLookups = []; httpLookups.push(PUSH_RULES_RESPONSE); httpLookups.push(SYNC_RESPONSE); @@ -191,15 +193,19 @@ describe("MatrixClient", function() { const filter = new sdk.Filter(0, filterId); filter.setDefinition({"room": {"timeline": {"limit": 8}}}); store.getFilter.andReturn(filter); - client.startClient(); - - client.on("sync", function syncListener(state) { - if (state === "SYNCING") { - expect(httpLookups.length).toEqual(0); - client.removeListener("sync", syncListener); - done(); - } + const syncPromise = new Promise((resolve, reject) => { + client.on("sync", function syncListener(state) { + if (state === "SYNCING") { + expect(httpLookups.length).toEqual(0); + client.removeListener("sync", syncListener); + resolve(); + } else if (state === "ERROR") { + reject(new Error("sync error")); + } + }); }); + await client.startClient(); + await syncPromise; }); describe("getSyncState", function() { @@ -207,15 +213,20 @@ describe("MatrixClient", function() { expect(client.getSyncState()).toBe(null); }); - it("should return the same sync state as emitted sync events", function(done) { - client.on("sync", function syncListener(state) { - expect(state).toEqual(client.getSyncState()); - if (state === "SYNCING") { - client.removeListener("sync", syncListener); - done(); - } + it("should return the same sync state as emitted sync events", async function() { + /* const syncingPromise = new Promise((resolve) => { + throw new Error("fail!!"); + client.on("sync", function syncListener(state) { + expect(state).toEqual(client.getSyncState()); + if (state === "SYNCING") { + client.removeListener("sync", syncListener); + resolve(); + } + }); }); - client.startClient(); + */ + await client.startClient(); + // await syncingPromise; }); }); @@ -258,8 +269,8 @@ describe("MatrixClient", function() { }); describe("retryImmediately", function() { - it("should return false if there is no request waiting", function() { - client.startClient(); + it("should return false if there is no request waiting", async function() { + await client.startClient(); expect(client.retryImmediately()).toBe(false); }); From 19be3dd852c10c33c2b47b3fc353dcdd2ff2f289 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 10:13:40 +0100 Subject: [PATCH 23/57] fix lint --- src/store/indexeddb-local-backend.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index 67d2185b8..7a184e1d1 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -42,8 +42,8 @@ function upgradeSchemaV2(db) { } function upgradeSchemaV3(db) { - const clientOptionsStore = db.createObjectStore( - "client_options", { keyPath: ["clobber"]}); + db.createObjectStore("client_options", + { keyPath: ["clobber"]}); } @@ -557,11 +557,11 @@ LocalIndexedDBStoreBackend.prototype = { const store = txn.objectStore("client_options"); store.put({ clobber: "-", // constant key so will always clobber - options: options + options: options, }); // put == UPSERT return txnAsPromise(txn); }); - } + }, }; export default LocalIndexedDBStoreBackend; From 2560ba2980b843cc109e390be3a16a0ed9274784 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 10:37:52 +0100 Subject: [PATCH 24/57] dont clear the store if its a brand new one --- spec/unit/matrix-client.spec.js | 1 + src/client.js | 30 ++++++++++++++++++++------- src/store/indexeddb-local-backend.js | 6 ++++++ src/store/indexeddb-remote-backend.js | 5 ++++- src/store/indexeddb-store-worker.js | 3 +++ src/store/indexeddb.js | 5 +++++ src/store/memory.js | 4 ++++ src/store/stub.js | 5 +++++ 8 files changed, 50 insertions(+), 9 deletions(-) diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index dae9aaa3a..c37f99582 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -141,6 +141,7 @@ describe("MatrixClient", function() { store.setSyncData = expect.createSpy().andReturn(Promise.resolve(null)); store.getClientOptions = expect.createSpy().andReturn(Promise.resolve(null)); store.storeClientOptions = expect.createSpy().andReturn(Promise.resolve(null)); + store.isNewlyCreated = expect.createSpy().andReturn(Promise.resolve(true)); client = new MatrixClient({ baseUrl: "https://my.home.server", idBaseUrl: identityServerUrl, diff --git a/src/client.js b/src/client.js index 973263429..fe7892bf1 100644 --- a/src/client.js +++ b/src/client.js @@ -3116,14 +3116,12 @@ MatrixClient.prototype.startClient = async function(opts) { } } // need to vape the store when enabling LL and wasn't enabled before - let hadLLEnabledBefore = false; - const prevClientOptions = await this.store.getClientOptions(); - if (prevClientOptions) { - hadLLEnabledBefore = !!prevClientOptions.lazyLoadMembers; - } - if (!hadLLEnabledBefore && opts.lazyLoadMembers) { - await this.store.deleteAllData(); - throw new Error("vaped the store, you need to resync"); + if (opts.lazyLoadMembers) { + const shouldClear = await this._shouldClearSyncDataIfLL(); + if (shouldClear) { + await this.store.deleteAllData(); + throw new Error("vaped the store, you need to resync"); + } } if (opts.lazyLoadMembers && this._crypto) { this._crypto.enableLazyLoading(); @@ -3143,6 +3141,22 @@ MatrixClient.prototype.startClient = async function(opts) { this._syncApi.sync(); }; +/** @return {bool} need to clear the store when enabling LL and wasn't enabled before? */ +MatrixClient.prototype._shouldClearSyncDataIfLL = async function() { + let hadLLEnabledBefore = false; + const isStoreNewlyCreated = await this.store.isNewlyCreated(); + if (!isStoreNewlyCreated) { + const prevClientOptions = await this.store.getClientOptions(); + if (prevClientOptions) { + hadLLEnabledBefore = !!prevClientOptions.lazyLoadMembers; + } + if (!hadLLEnabledBefore) { + return true; + } + } + return false; +}; + /** * High level helper method to stop the client from polling and allow a * clean shutdown. diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index 7a184e1d1..db256dead 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -129,6 +129,7 @@ const LocalIndexedDBStoreBackend = function LocalIndexedDBStoreBackend( this.db = null; this._disconnected = true; this._syncAccumulator = new SyncAccumulator(); + this._isNewlyCreated = false; }; @@ -159,6 +160,7 @@ LocalIndexedDBStoreBackend.prototype = { `LocalIndexedDBStoreBackend.connect: upgrading from ${oldVersion}`, ); if (oldVersion < 1) { // The database did not previously exist. + this._isNewlyCreated = true; createDatabase(db); } if (oldVersion < 2) { @@ -194,6 +196,10 @@ LocalIndexedDBStoreBackend.prototype = { return this._init(); }); }, + /** @return {bool} whether or not the database was newly created in this session. */ + isNewlyCreated: function() { + return this._isNewlyCreated; + }, /** * Having connected, load initial data from the database and prepare for use diff --git a/src/store/indexeddb-remote-backend.js b/src/store/indexeddb-remote-backend.js index 751d155b4..07ee18516 100644 --- a/src/store/indexeddb-remote-backend.js +++ b/src/store/indexeddb-remote-backend.js @@ -65,7 +65,10 @@ RemoteIndexedDBStoreBackend.prototype = { clearDatabase: function() { return this._ensureStarted().then(() => this._doCmd('clearDatabase')); }, - + /** @return {Promise} whether or not the database was newly created in this session. */ + isNewlyCreated: function() { + return this._doCmd('isNewlyCreated'); + }, /** * @return {Promise} Resolves with a sync response to restore the * client state to where it was at the last save, or null if there diff --git a/src/store/indexeddb-store-worker.js b/src/store/indexeddb-store-worker.js index 060d3c8f8..d9f53a053 100644 --- a/src/store/indexeddb-store-worker.js +++ b/src/store/indexeddb-store-worker.js @@ -67,6 +67,9 @@ class IndexedDBStoreWorker { case 'connect': prom = this.backend.connect(); break; + case 'isNewlyCreated': + prom = this.backend.isNewlyCreated(); + break; case 'clearDatabase': prom = this.backend.clearDatabase().then((result) => { // This returns special classes which can't be cloned diff --git a/src/store/indexeddb.js b/src/store/indexeddb.js index 479b79e8c..b8f47ad89 100644 --- a/src/store/indexeddb.js +++ b/src/store/indexeddb.js @@ -146,6 +146,11 @@ IndexedDBStore.prototype.getSavedSync = function() { return this.backend.getSavedSync(); }; +/** @return {Promise} whether or not the database was newly created in this session. */ +IndexedDBStore.prototype.isNewlyCreated = function() { + return this.backend.isNewlyCreated(); +}; + /** * @return {Promise} If there is a saved sync, the nextBatch token * for this sync, otherwise null. diff --git a/src/store/memory.js b/src/store/memory.js index 33c4ff9c6..006742223 100644 --- a/src/store/memory.js +++ b/src/store/memory.js @@ -68,6 +68,10 @@ module.exports.MatrixInMemoryStore.prototype = { return this.syncToken; }, + /** @return {Promise} whether or not the database was newly created in this session. */ + isNewlyCreated: function() { + return Promise.resolve(true); + }, /** * Set the token to stream from. diff --git a/src/store/stub.js b/src/store/stub.js index 2582a9802..f09dad6d7 100644 --- a/src/store/stub.js +++ b/src/store/stub.js @@ -32,6 +32,11 @@ function StubStore() { StubStore.prototype = { + /** @return {Promise} whether or not the database was newly created in this session. */ + isNewlyCreated: function() { + return Promise.resolve(true); + }, + /** * Get the sync token. * @return {string} From 1d0791142cf2cbba54532011affd9c40d32e4bbe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 11:32:11 +0100 Subject: [PATCH 25/57] all store methods should return a promise --- src/store/indexeddb-local-backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index db256dead..f527768a2 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -198,7 +198,7 @@ LocalIndexedDBStoreBackend.prototype = { }, /** @return {bool} whether or not the database was newly created in this session. */ isNewlyCreated: function() { - return this._isNewlyCreated; + return Promise.resolve(this._isNewlyCreated); }, /** From 4e0af3eafe69b7d94200eed186c483d1a10c8d68 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 11:32:43 +0100 Subject: [PATCH 26/57] don't return the IDBEvent from storeClientOptions as it's not needed and not cloneable --- src/store/indexeddb-local-backend.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index f527768a2..e0ada7904 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -557,16 +557,14 @@ LocalIndexedDBStoreBackend.prototype = { }); }, - storeClientOptions: function(options) { - return Promise.resolve().then(() => { - const txn = this.db.transaction(["client_options"], "readwrite"); - const store = txn.objectStore("client_options"); - store.put({ - clobber: "-", // constant key so will always clobber - options: options, - }); // put == UPSERT - return txnAsPromise(txn); - }); + storeClientOptions: async function(options) { + const txn = this.db.transaction(["client_options"], "readwrite"); + const store = txn.objectStore("client_options"); + store.put({ + clobber: "-", // constant key so will always clobber + options: options, + }); // put == UPSERT + await txnAsPromise(txn); }, }; From 6dd5c6c31728c4b46c4492fe0c7014708d4ea506 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 11:33:19 +0100 Subject: [PATCH 27/57] fix existing missing this --- src/store/indexeddb-store-worker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/indexeddb-store-worker.js b/src/store/indexeddb-store-worker.js index d9f53a053..bf2c1184d 100644 --- a/src/store/indexeddb-store-worker.js +++ b/src/store/indexeddb-store-worker.js @@ -113,7 +113,7 @@ class IndexedDBStoreWorker { } if (prom === undefined) { - postMessage({ + this.postMessage({ command: 'cmd_fail', seq: msg.seq, // Can't be an Error because they're not structured cloneable From 58e3c72446c2024dfa9f1e4d6ed2e75ba07cce63 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 11:34:58 +0100 Subject: [PATCH 28/57] only store serializable options (string, boolean, number) --- src/client.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index fe7892bf1..d2fa6af91 100644 --- a/src/client.js +++ b/src/client.js @@ -3135,8 +3135,7 @@ MatrixClient.prototype.startClient = async function(opts) { return this._canResetTimelineCallback(roomId); }; this._clientOpts = opts; - await this.store.storeClientOptions(this._clientOpts); - + await this._storeClientOptions(this._clientOpts); this._syncApi = new SyncApi(this, opts); this._syncApi.sync(); }; @@ -3157,6 +3156,25 @@ MatrixClient.prototype._shouldClearSyncDataIfLL = async function() { return false; }; +/** + * store client options with boolean/string/numeric values + * to know in the next session what flags the sync data was + * created with (e.g. lazy loading) + * @param {object} opts the complete set of client options + * @return {Promise} for store operation */ +MatrixClient.prototype._storeClientOptions = function(opts) { + const primTypes = ["boolean", "string", "number"]; + const serializableOpts = Object.entries(opts) + .filter(([key, value]) => { + return primTypes.includes(typeof value); + }) + .reduce((obj, [key, value]) => { + obj[key] = value; + return obj; + }, {}); + return this.store.storeClientOptions(serializableOpts); +}; + /** * High level helper method to stop the client from polling and allow a * clean shutdown. From fe21972f4a9f31637bef5b1fab7d0d8972b7e444 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 26 Sep 2018 11:36:06 +0100 Subject: [PATCH 29/57] Update mocha to v5 Mostly to get the non-vulnerable version of node-growl --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e59fdc090..376b28ec6 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,8 @@ "jsdoc": "^3.5.5", "lolex": "^1.5.2", "matrix-mock-request": "^1.2.0", - "mocha": "^3.2.0", - "mocha-jenkins-reporter": "^0.3.6", + "mocha": "^5.2.0", + "mocha-jenkins-reporter": "^0.4.0", "rimraf": "^2.5.4", "source-map-support": "^0.4.11", "sourceify": "^0.1.0", From 54bff814708ccab4d1ab3a3154150101702c276c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 12:39:56 +0100 Subject: [PATCH 30/57] clear sync data on toggling LL,also throw spec. error and delegate clear the sync data needs to be cleared toggling in both directions: not LL -> LL: you want to get rid of all the excess state in the sync data to get the RAM benefits LL -> not LL: you want to fill the sync data state again because getOutOfBandMembers won't be called --- src/client.js | 26 +++++++++++++------------- src/errors.js | 23 +++++++++++++++++++++++ src/matrix.js | 2 ++ 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 src/errors.js diff --git a/src/client.js b/src/client.js index d2fa6af91..300e747c4 100644 --- a/src/client.js +++ b/src/client.js @@ -44,6 +44,7 @@ const ContentHelpers = require("./content-helpers"); import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; +import {InvalidStoreError} from './errors'; const LAZY_LOADING_MESSAGES_FILTER = { @@ -3116,12 +3117,10 @@ MatrixClient.prototype.startClient = async function(opts) { } } // need to vape the store when enabling LL and wasn't enabled before - if (opts.lazyLoadMembers) { - const shouldClear = await this._shouldClearSyncDataIfLL(); - if (shouldClear) { - await this.store.deleteAllData(); - throw new Error("vaped the store, you need to resync"); - } + const shouldClear = await this._shouldClearSyncDataIfLLToggled(opts.lazyLoadMembers); + if (shouldClear) { + const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; + throw new InvalidStoreError(reason); } if (opts.lazyLoadMembers && this._crypto) { this._crypto.enableLazyLoading(); @@ -3140,18 +3139,19 @@ MatrixClient.prototype.startClient = async function(opts) { this._syncApi.sync(); }; -/** @return {bool} need to clear the store when enabling LL and wasn't enabled before? */ -MatrixClient.prototype._shouldClearSyncDataIfLL = async function() { - let hadLLEnabledBefore = false; +/** @return {bool} need to clear the store when toggling LL compared to previous session? */ +MatrixClient.prototype._shouldClearSyncDataIfLLToggled = async function(lazyLoadMembers) { + lazyLoadMembers = !!lazyLoadMembers; + // assume it was turned off before + // if we don't know any better + let lazyLoadMembersBefore = false; const isStoreNewlyCreated = await this.store.isNewlyCreated(); if (!isStoreNewlyCreated) { const prevClientOptions = await this.store.getClientOptions(); if (prevClientOptions) { - hadLLEnabledBefore = !!prevClientOptions.lazyLoadMembers; - } - if (!hadLLEnabledBefore) { - return true; + lazyLoadMembersBefore = !!prevClientOptions.lazyLoadMembers; } + return lazyLoadMembersBefore !== lazyLoadMembers; } return false; }; diff --git a/src/errors.js b/src/errors.js new file mode 100644 index 000000000..71e91ee3c --- /dev/null +++ b/src/errors.js @@ -0,0 +1,23 @@ +// can't just do InvalidStoreError extends Error +// because of http://babeljs.io/docs/usage/caveats/#classes +function InvalidStoreError(reason) { + const message = `Store is invalid because ${reason}, please delete all data and retry`; + const instance = Reflect.construct(Error, [message]); + Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this)); + instance.reason = reason; + return instance; +} + +InvalidStoreError.TOGGLED_LAZY_LOADING = "TOGGLED_LAZY_LOADING"; + +InvalidStoreError.prototype = Object.create(Error.prototype, { + constructor: { + value: Error, + enumerable: false, + writable: true, + configurable: true + } +}); +Reflect.setPrototypeOf(InvalidStoreError, Error); + +module.exports.InvalidStoreError = InvalidStoreError; diff --git a/src/matrix.js b/src/matrix.js index bcc88e0e9..47791472e 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -34,6 +34,8 @@ module.exports.SyncAccumulator = require("./sync-accumulator"); module.exports.MatrixHttpApi = require("./http-api").MatrixHttpApi; /** The {@link module:http-api.MatrixError|MatrixError} class. */ module.exports.MatrixError = require("./http-api").MatrixError; +/** The {@link module:errors.InvalidStoreError|InvalidStoreError} class. */ +module.exports.InvalidStoreError = require("./errors").InvalidStoreError; /** The {@link module:client.MatrixClient|MatrixClient} class. */ module.exports.MatrixClient = require("./client").MatrixClient; /** The {@link module:models/room|Room} class. */ From b7b9c67259efe5828c3fed2f342d9057cea0fc8a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 12:49:26 +0100 Subject: [PATCH 31/57] fix lint --- src/errors.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/errors.js b/src/errors.js index 71e91ee3c..d26a8647f 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,7 +1,8 @@ // can't just do InvalidStoreError extends Error // because of http://babeljs.io/docs/usage/caveats/#classes function InvalidStoreError(reason) { - const message = `Store is invalid because ${reason}, please delete all data and retry`; + const message = `Store is invalid because ${reason}, ` + + `please delete all data and retry`; const instance = Reflect.construct(Error, [message]); Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this)); instance.reason = reason; @@ -15,8 +16,8 @@ InvalidStoreError.prototype = Object.create(Error.prototype, { value: Error, enumerable: false, writable: true, - configurable: true - } + configurable: true, + }, }); Reflect.setPrototypeOf(InvalidStoreError, Error); From 78a5a886385a30d10068679f54889b99ad9374f3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 12:49:33 +0100 Subject: [PATCH 32/57] fix jsdoc lint and better naming --- src/client.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/client.js b/src/client.js index 300e747c4..ee501ec0a 100644 --- a/src/client.js +++ b/src/client.js @@ -3117,7 +3117,7 @@ MatrixClient.prototype.startClient = async function(opts) { } } // need to vape the store when enabling LL and wasn't enabled before - const shouldClear = await this._shouldClearSyncDataIfLLToggled(opts.lazyLoadMembers); + const shouldClear = await this._wasLazyLoadingToggled(opts.lazyLoadMembers); if (shouldClear) { const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; throw new InvalidStoreError(reason); @@ -3139,8 +3139,11 @@ MatrixClient.prototype.startClient = async function(opts) { this._syncApi.sync(); }; -/** @return {bool} need to clear the store when toggling LL compared to previous session? */ -MatrixClient.prototype._shouldClearSyncDataIfLLToggled = async function(lazyLoadMembers) { +/** + * Is the lazy loading option different than in previous session? + * @param {bool} lazyLoadMembers current options for lazy loading + * @return {bool} whether or not the option has changed compared to the previous session */ +MatrixClient.prototype._wasLazyLoadingToggled = async function(lazyLoadMembers) { lazyLoadMembers = !!lazyLoadMembers; // assume it was turned off before // if we don't know any better From cba1e95d0a357ad9a10394bbb330d7bccd47d7a2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 26 Sep 2018 09:11:28 -0600 Subject: [PATCH 33/57] Revert "Add getMediaLimits to client" --- src/client.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/client.js b/src/client.js index 821add2d9..bf1edf30b 100644 --- a/src/client.js +++ b/src/client.js @@ -771,17 +771,6 @@ MatrixClient.prototype.getGroups = function() { return this.store.getGroups(); }; -/** - * Get the config for the media repository. - * @param {module:client.callback} callback Optional. - * @return {module:client.Promise} Resolves with an object containing the config. - */ -MatrixClient.prototype.getMediaConfig = function(callback) { - return this._http.requestWithPrefix( - callback, "GET", "/config", undefined, undefined, httpApi.PREFIX_MEDIA_R0, - ); -}; - // Room ops // ======== From 2d5eb920b84e7f3ab5ba247c0288307bd2a0448f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 16:12:30 +0100 Subject: [PATCH 34/57] pass lazy loading flag into error, to format message based on it --- src/client.js | 2 +- src/errors.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index ee501ec0a..d5ba14e58 100644 --- a/src/client.js +++ b/src/client.js @@ -3120,7 +3120,7 @@ MatrixClient.prototype.startClient = async function(opts) { const shouldClear = await this._wasLazyLoadingToggled(opts.lazyLoadMembers); if (shouldClear) { const reason = InvalidStoreError.TOGGLED_LAZY_LOADING; - throw new InvalidStoreError(reason); + throw new InvalidStoreError(reason, !!opts.lazyLoadMembers); } if (opts.lazyLoadMembers && this._crypto) { this._crypto.enableLazyLoading(); diff --git a/src/errors.js b/src/errors.js index d26a8647f..04e14f2c8 100644 --- a/src/errors.js +++ b/src/errors.js @@ -1,11 +1,12 @@ // can't just do InvalidStoreError extends Error // because of http://babeljs.io/docs/usage/caveats/#classes -function InvalidStoreError(reason) { +function InvalidStoreError(reason, value) { const message = `Store is invalid because ${reason}, ` + `please delete all data and retry`; const instance = Reflect.construct(Error, [message]); Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this)); instance.reason = reason; + instance.value = value; return instance; } From 33ad65a105d379b6208a950679e3c5bb80525698 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 26 Sep 2018 16:39:22 +0100 Subject: [PATCH 35/57] Don't assume Olm will be available from start By doing `Olm = global.Olm` on script load, we require that Olm is available right from the start, which isn't really necessary. As long as it appears some time before we actually want to use it, this is fine (we can probably assume it's not going to go away again..?) This means Riot doesn't need to faff about making sure olm is loaded before starting anything else. --- src/crypto/OlmDevice.js | 30 +++++++++++------------------- src/crypto/index.js | 6 ++---- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index b8c70cd0c..043905c09 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -17,14 +17,6 @@ limitations under the License. import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; -/** - * olm.js wrapper - * - * @module crypto/OlmDevice - */ -const Olm = global.Olm; - - // The maximum size of an event is 65K, and we base64 the content, so this is a // reasonable approximation to the biggest plaintext we can encrypt. const MAX_PLAINTEXT_LENGTH = 65536 * 3 / 4; @@ -124,7 +116,7 @@ OlmDevice.prototype.init = async function() { await this._migrateFromSessionStore(); let e2eKeys; - const account = new Olm.Account(); + const account = new global.Olm.Account(); try { await _initialiseAccount( this._sessionStore, this._cryptoStore, this._pickleKey, account, @@ -158,7 +150,7 @@ async function _initialiseAccount(sessionStore, cryptoStore, pickleKey, account) * @return {array} The version of Olm. */ OlmDevice.getOlmVersion = function() { - return Olm.get_library_version(); + return global.Olm.get_library_version(); }; OlmDevice.prototype._migrateFromSessionStore = async function() { @@ -265,7 +257,7 @@ OlmDevice.prototype._migrateFromSessionStore = async function() { */ OlmDevice.prototype._getAccount = function(txn, func) { this._cryptoStore.getAccount(txn, (pickledAccount) => { - const account = new Olm.Account(); + const account = new global.Olm.Account(); try { account.unpickle(this._pickleKey, pickledAccount); func(account); @@ -318,7 +310,7 @@ OlmDevice.prototype._getSession = function(deviceKey, sessionId, txn, func) { * @private */ OlmDevice.prototype._unpickleSession = function(pickledSession, func) { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.unpickle(this._pickleKey, pickledSession); func(session); @@ -351,7 +343,7 @@ OlmDevice.prototype._saveSession = function(deviceKey, session, txn) { * @private */ OlmDevice.prototype._getUtility = function(func) { - const utility = new Olm.Utility(); + const utility = new global.Olm.Utility(); try { return func(utility); } finally { @@ -463,7 +455,7 @@ OlmDevice.prototype.createOutboundSession = async function( ], (txn) => { this._getAccount(txn, (account) => { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.create_outbound(account, theirIdentityKey, theirOneTimeKey); newSessionId = session.session_id(); @@ -507,7 +499,7 @@ OlmDevice.prototype.createInboundSession = async function( ], (txn) => { this._getAccount(txn, (account) => { - const session = new Olm.Session(); + const session = new global.Olm.Session(); try { session.create_inbound_from( account, theirDeviceIdentityKey, ciphertext, @@ -725,7 +717,7 @@ OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) { throw new Error("Unknown outbound group session " + sessionId); } - const session = new Olm.OutboundGroupSession(); + const session = new global.Olm.OutboundGroupSession(); try { session.unpickle(this._pickleKey, pickled); return func(session); @@ -741,7 +733,7 @@ OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) { * @return {string} sessionId for the outbound session. */ OlmDevice.prototype.createOutboundGroupSession = function() { - const session = new Olm.OutboundGroupSession(); + const session = new global.Olm.OutboundGroupSession(); try { session.create(); this._saveOutboundGroupSession(session); @@ -813,7 +805,7 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) { * @return {*} result of func */ OlmDevice.prototype._unpickleInboundGroupSession = function(sessionData, func) { - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { session.unpickle(this._pickleKey, sessionData.session); return func(session); @@ -894,7 +886,7 @@ OlmDevice.prototype.addInboundGroupSession = async function( } // new session. - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { if (exportFormat) { session.import_session(sessionKey); diff --git a/src/crypto/index.js b/src/crypto/index.js index 0fe33663f..9ed3c7113 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -36,10 +36,8 @@ const DeviceList = require('./DeviceList').default; import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager'; import IndexedDBCryptoStore from './store/indexeddb-crypto-store'; -const Olm = global.Olm; - export function isCryptoAvailable() { - return Boolean(Olm); + return Boolean(global.Olm); } /** @@ -132,7 +130,7 @@ utils.inherits(Crypto, EventEmitter); Crypto.prototype.init = async function() { // Olm is just an object with a .then, not a fully-fledged promise, so // pass it into bluebird to make it a proper promise. - await Olm.init(); + await global.Olm.init(); const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount()); let cryptoStoreHasAccount; From ba39b64ced72780202d95204f2676d2058871fd8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Sep 2018 18:00:40 +0100 Subject: [PATCH 36/57] re-enable test --- spec/unit/matrix-client.spec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index c37f99582..a59e0af43 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -215,8 +215,7 @@ describe("MatrixClient", function() { }); it("should return the same sync state as emitted sync events", async function() { - /* const syncingPromise = new Promise((resolve) => { - throw new Error("fail!!"); + const syncingPromise = new Promise((resolve) => { client.on("sync", function syncListener(state) { expect(state).toEqual(client.getSyncState()); if (state === "SYNCING") { @@ -225,9 +224,8 @@ describe("MatrixClient", function() { } }); }); - */ await client.startClient(); - // await syncingPromise; + await syncingPromise; }); }); From cf6c555e6a875c46a6d1237145e72bfd477354c0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:20:21 +0100 Subject: [PATCH 37/57] Prepare changelog for v0.11.1-rc.1 --- CHANGELOG.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b51feeb9..e42811b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1-rc.1) (2018-09-27) +============================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0...v0.11.1-rc.1) + + * Detect when lazy loading has been toggled in client.startClient + [\#746](https://github.com/matrix-org/matrix-js-sdk/pull/746) + * Add getMediaLimits to client + [\#644](https://github.com/matrix-org/matrix-js-sdk/pull/644) + * Split npm start into an init and watch script + [\#742](https://github.com/matrix-org/matrix-js-sdk/pull/742) + * Revert "room name should only take canonical alias into account" + [\#738](https://github.com/matrix-org/matrix-js-sdk/pull/738) + * fix display name disambiguation with LL + [\#737](https://github.com/matrix-org/matrix-js-sdk/pull/737) + * Introduce Room.myMembership event + [\#735](https://github.com/matrix-org/matrix-js-sdk/pull/735) + * room name should only take canonical alias into account + [\#733](https://github.com/matrix-org/matrix-js-sdk/pull/733) + * state events from context response were not wrapped in a MatrixEvent + [\#732](https://github.com/matrix-org/matrix-js-sdk/pull/732) + * Reduce amount of promises created when inserting members + [\#724](https://github.com/matrix-org/matrix-js-sdk/pull/724) + * dont wait for LL members to be stored to resolve the members + [\#726](https://github.com/matrix-org/matrix-js-sdk/pull/726) + * RoomState.members emitted with wrong argument order for OOB members + [\#728](https://github.com/matrix-org/matrix-js-sdk/pull/728) + Changes in [0.11.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.0) (2018-09-10) ================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0-rc.1...v0.11.0) From 847d40e5674b19e947711cbf50aa75f554f17310 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:20:21 +0100 Subject: [PATCH 38/57] v0.11.1-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e59fdc090..6c3728706 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.0", + "version": "0.11.1-rc.1", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From e8e1b431ad6d36b7a986e6ece9deaade63eb13b6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:28:01 +0100 Subject: [PATCH 39/57] make usage of hub compatible with latest version (2.5) --- release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.sh b/release.sh index 5a0538a58..ab2eae788 100755 --- a/release.sh +++ b/release.sh @@ -245,7 +245,7 @@ release_text=`mktemp` echo "$tag" > "${release_text}" echo >> "${release_text}" cat "${latest_changes}" >> "${release_text}" -hub release create $hubflags $assets -f "${release_text}" "$tag" +hub release create $hubflags $assets -F "${release_text}" "$tag" if [ $dodist -eq 0 ]; then rm -rf "$builddir" From 056479d45063077c8c25a838615a3594f23f567a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:35:52 +0100 Subject: [PATCH 40/57] Revert "v0.11.1-rc.1" This reverts commit 847d40e5674b19e947711cbf50aa75f554f17310. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c3728706..e59fdc090 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.1-rc.1", + "version": "0.11.0", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From e8b307dc4f9e79cdc6c3435251dae30a838a01e4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:38:25 +0100 Subject: [PATCH 41/57] Prepare changelog for v0.11.1-rc.1 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42811b5f..d6f8fbdac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1-rc.1) (2018-09-27) +============================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0...v0.11.1-rc.1) + + * make usage of hub compatible with latest version (2.5) + [\#747](https://github.com/matrix-org/matrix-js-sdk/pull/747) + * Detect when lazy loading has been toggled in client.startClient + [\#746](https://github.com/matrix-org/matrix-js-sdk/pull/746) + * Add getMediaLimits to client + [\#644](https://github.com/matrix-org/matrix-js-sdk/pull/644) + * Split npm start into an init and watch script + [\#742](https://github.com/matrix-org/matrix-js-sdk/pull/742) + * Revert "room name should only take canonical alias into account" + [\#738](https://github.com/matrix-org/matrix-js-sdk/pull/738) + * fix display name disambiguation with LL + [\#737](https://github.com/matrix-org/matrix-js-sdk/pull/737) + * Introduce Room.myMembership event + [\#735](https://github.com/matrix-org/matrix-js-sdk/pull/735) + * room name should only take canonical alias into account + [\#733](https://github.com/matrix-org/matrix-js-sdk/pull/733) + * state events from context response were not wrapped in a MatrixEvent + [\#732](https://github.com/matrix-org/matrix-js-sdk/pull/732) + * Reduce amount of promises created when inserting members + [\#724](https://github.com/matrix-org/matrix-js-sdk/pull/724) + * dont wait for LL members to be stored to resolve the members + [\#726](https://github.com/matrix-org/matrix-js-sdk/pull/726) + * RoomState.members emitted with wrong argument order for OOB members + [\#728](https://github.com/matrix-org/matrix-js-sdk/pull/728) + Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1-rc.1) (2018-09-27) ============================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0...v0.11.1-rc.1) From 090c15fe197955c48532de96e04541bb1f38efa4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:38:25 +0100 Subject: [PATCH 42/57] v0.11.1-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e59fdc090..6c3728706 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.0", + "version": "0.11.1-rc.1", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From 19f023e0eeb0d708cb0ff112dcc84813b3f2e337 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:42:19 +0100 Subject: [PATCH 43/57] Revert "v0.11.1-rc.1" This reverts commit 090c15fe197955c48532de96e04541bb1f38efa4. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c3728706..e59fdc090 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.1-rc.1", + "version": "0.11.0", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From 5e4973a1dc2f62b8c08df4261ee441c0e908cc5b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:45:33 +0100 Subject: [PATCH 44/57] changelog --- CHANGELOG.md | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f8fbdac..b920f350c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,33 +27,6 @@ Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/ta * RoomState.members emitted with wrong argument order for OOB members [\#728](https://github.com/matrix-org/matrix-js-sdk/pull/728) -Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1-rc.1) (2018-09-27) -============================================================================================================ -[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0...v0.11.1-rc.1) - - * Detect when lazy loading has been toggled in client.startClient - [\#746](https://github.com/matrix-org/matrix-js-sdk/pull/746) - * Add getMediaLimits to client - [\#644](https://github.com/matrix-org/matrix-js-sdk/pull/644) - * Split npm start into an init and watch script - [\#742](https://github.com/matrix-org/matrix-js-sdk/pull/742) - * Revert "room name should only take canonical alias into account" - [\#738](https://github.com/matrix-org/matrix-js-sdk/pull/738) - * fix display name disambiguation with LL - [\#737](https://github.com/matrix-org/matrix-js-sdk/pull/737) - * Introduce Room.myMembership event - [\#735](https://github.com/matrix-org/matrix-js-sdk/pull/735) - * room name should only take canonical alias into account - [\#733](https://github.com/matrix-org/matrix-js-sdk/pull/733) - * state events from context response were not wrapped in a MatrixEvent - [\#732](https://github.com/matrix-org/matrix-js-sdk/pull/732) - * Reduce amount of promises created when inserting members - [\#724](https://github.com/matrix-org/matrix-js-sdk/pull/724) - * dont wait for LL members to be stored to resolve the members - [\#726](https://github.com/matrix-org/matrix-js-sdk/pull/726) - * RoomState.members emitted with wrong argument order for OOB members - [\#728](https://github.com/matrix-org/matrix-js-sdk/pull/728) - Changes in [0.11.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.0) (2018-09-10) ================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0-rc.1...v0.11.0) From 634596257d791c91cc3d8b2ed7d50a193837b0b6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 11:47:28 +0100 Subject: [PATCH 45/57] v0.11.1-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e59fdc090..6c3728706 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.0", + "version": "0.11.1-rc.1", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From 1c348f0cdb148cbdc891ae622b178e09a99c18ac Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Sep 2018 18:55:21 +0100 Subject: [PATCH 46/57] disable lazy loading for guests as they cant create filters --- src/client.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client.js b/src/client.js index d11d701d0..b267b8f26 100644 --- a/src/client.js +++ b/src/client.js @@ -3117,6 +3117,9 @@ MatrixClient.prototype.startClient = async function(opts) { // shallow-copy the opts dict before modifying and storing it opts = Object.assign({}, opts); + if (opts.lazyLoadMembers && this.isGuest()) { + opts.lazyLoadMembers = false; + } if (opts.lazyLoadMembers) { const supported = await this.doesServerSupportLazyLoading(); if (supported) { From fd1b3329f55408eccd257a34e9a06a563a776b44 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 1 Oct 2018 15:23:37 +0200 Subject: [PATCH 47/57] Prepare changelog for v0.11.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b920f350c..86c9b82dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [0.11.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1) (2018-10-01) +================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.1-rc.1...v0.11.1) + + * No changes since rc.1 + Changes in [0.11.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.1-rc.1) (2018-09-27) ============================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0...v0.11.1-rc.1) From 1e1358fcef92b1388cffadde6983c82fa485dbfe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 1 Oct 2018 15:23:37 +0200 Subject: [PATCH 48/57] v0.11.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c3728706..b21aae3d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "0.11.1-rc.1", + "version": "0.11.1", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From 0a88d419c6fc2143bd51874811ee21c841e9fd53 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 1 Oct 2018 17:21:01 +0200 Subject: [PATCH 49/57] allow non-ff merge from release branch into master --- release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.sh b/release.sh index ab2eae788..0f4ab4086 100755 --- a/release.sh +++ b/release.sh @@ -281,7 +281,7 @@ fi echo "updating master branch" git checkout master git pull -git merge --ff-only "$rel_branch" +git merge "$rel_branch" # push master and docs (if generated) to github git push origin master From bd2da08c4eedfd6f90d3ea512692a5eeb45c125e Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Oct 2018 16:48:27 +0100 Subject: [PATCH 50/57] Reject with the actual error on indexeddb error Rather than the event --- src/store/indexeddb-local-backend.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index e0ada7904..e3ae1c1ae 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -83,7 +83,7 @@ function txnAsPromise(txn) { resolve(event); }; txn.onerror = function(event) { - reject(event); + reject(event.target.error); }; }); } @@ -94,7 +94,7 @@ function reqAsEventPromise(req) { resolve(event); }; req.onerror = function(event) { - reject(event); + reject(event.target.error); }; }); } From 7cd101d8cb06a036b5c67737c47e01249955f621 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Oct 2018 19:22:10 +0100 Subject: [PATCH 51/57] Fix recovery key format --- package.json | 1 + src/client.js | 23 +++++++---------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index db0b14bb1..4ae0b36a7 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "dependencies": { "another-json": "^0.2.0", "babel-runtime": "^6.26.0", + "base58check": "^2.0.0", "bluebird": "^3.5.0", "browser-request": "^0.3.3", "content-type": "^1.0.2", diff --git a/src/client.js b/src/client.js index ceeeff426..951dbcdbb 100644 --- a/src/client.js +++ b/src/client.js @@ -49,6 +49,7 @@ import {InvalidStoreError} from './errors'; import Crypto from './crypto'; import { isCryptoAvailable } from './crypto'; +import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey'; const LAZY_LOADING_MESSAGES_FILTER = { lazy_load_members: true, @@ -882,9 +883,7 @@ MatrixClient.prototype.prepareKeyBackupVersion = function() { auth_data: { public_key: publicKey, }, - // FIXME: pickle isn't the right thing to use, but we don't have - // anything else yet, so use it for now - recovery_key: decryption.pickle("secret_key"), + recovery_key: encodeRecoveryKey(decryption.get_private_key()), }; } finally { decryption.free(); @@ -991,26 +990,17 @@ MatrixClient.prototype.backupAllGroupSessions = function(version) { return this._crypto.backupAllGroupSessions(version); }; -MatrixClient.prototype.isValidRecoveryKey = function(decryptionKey) { - if (this._crypto === null) { - throw new Error("End-to-end encryption disabled"); - } - - const decryption = new global.Olm.PkDecryption(); +MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) { try { - // FIXME: see the FIXME in createKeyBackupVersion - decryption.unpickle("secret_key", decryptionKey); + decodeRecoveryKey(recoveryKey); return true; } catch (e) { - console.log(e); return false; - } finally { - decryption.free(); } }; MatrixClient.prototype.restoreKeyBackups = function( - decryptionKey, targetRoomId, targetSessionId, version, + recoveryKey, targetRoomId, targetSessionId, version, ) { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); @@ -1021,9 +1011,10 @@ MatrixClient.prototype.restoreKeyBackups = function( const path = this._makeKeyBackupPath(targetRoomId, targetSessionId, version); // FIXME: see the FIXME in createKeyBackupVersion + const privkey = decodeRecoveryKey(recoveryKey); const decryption = new global.Olm.PkDecryption(); try { - decryption.unpickle("secret_key", decryptionKey); + decryption.init_with_private_key(privkey); } catch(e) { decryption.free(); throw e; From 262ace1773f22bb917078e74fb72c6e1dabe38da Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Oct 2018 10:20:57 +0100 Subject: [PATCH 52/57] commit the recovery key util file --- src/crypto/recoverykey.js | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/crypto/recoverykey.js diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js new file mode 100644 index 000000000..82de3a158 --- /dev/null +++ b/src/crypto/recoverykey.js @@ -0,0 +1,44 @@ +/* +Copyright 2018 New Vector Ltd + +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 base58check from 'base58check'; + +// picked arbitrarily but to try & avoid clashing with any bitcoin ones +const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; + +export function encodeRecoveryKey(key) { + const base58key = base58check.encode(Buffer.from(OLM_RECOVERY_KEY_PREFIX), Buffer.from(key)); + return base58key.match(/.{1,4}/g).join(" "); +} + +export function decodeRecoveryKey(recoverykey) { + const result = base58check.decode(recoverykey.replace(/ /, '')); + // the encoding doesn't include the length of the prefix, so the + // decoder assumes it's 1 byte. sigh. + const prefix = Buffer.concat([result.prefix, result.data.slice(0, OLM_RECOVERY_KEY_PREFIX.length - 1)]); + + if (!prefix.equals(Buffer.from(OLM_RECOVERY_KEY_PREFIX))) { + throw new Error("Incorrect prefix"); + } + + const key = result.data.slice(OLM_RECOVERY_KEY_PREFIX.length - 1); + + if (key.length !== global.Olm.PRIVATE_KEY_LENGTH) { + throw new Error("Incorrect length"); + } + + return key; +} From d9fe194111984eced513f0e6a54a82772d0fe006 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Oct 2018 13:42:07 -0600 Subject: [PATCH 53/57] Default to a room version of 1 when there is no room create event Fixes https://github.com/vector-im/riot-web/issues/7331 There is something to be worried about when there is no room create event, however. This should always be available, although due to cache problems or servers that don't provide the event we can't be sure of this. --- src/models/room.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/models/room.js b/src/models/room.js index c99d87ba5..4b685c878 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -198,7 +198,10 @@ utils.inherits(Room, EventEmitter); */ Room.prototype.getVersion = function() { const createEvent = this.currentState.getStateEvents("m.room.create", ""); - if (!createEvent) return null; + if (!createEvent) { + console.warn("Room " + this.room_id + " does not have an m.room.create event"); + return '1'; + } const ver = createEvent.getContent()['room_version']; if (ver === undefined) return '1'; return ver; From caba350b33973aedc041b59ef172c677456d22f8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 5 Oct 2018 11:52:55 +0200 Subject: [PATCH 54/57] throw error with same name and message over idb worker boundary instead of string currently thrown. This allows handling error from the main thread. --- src/store/indexeddb-remote-backend.js | 4 +++- src/store/indexeddb-store-worker.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/store/indexeddb-remote-backend.js b/src/store/indexeddb-remote-backend.js index 07ee18516..035a821ff 100644 --- a/src/store/indexeddb-remote-backend.js +++ b/src/store/indexeddb-remote-backend.js @@ -184,7 +184,9 @@ RemoteIndexedDBStoreBackend.prototype = { if (msg.command == 'cmd_success') { def.resolve(msg.result); } else { - def.reject(msg.error); + const error = new Error(msg.error.message); + error.name = msg.error.name; + def.reject(error); } } else { console.warn("Unrecognised message from worker: " + msg); diff --git a/src/store/indexeddb-store-worker.js b/src/store/indexeddb-store-worker.js index bf2c1184d..4e66f51aa 100644 --- a/src/store/indexeddb-store-worker.js +++ b/src/store/indexeddb-store-worker.js @@ -135,7 +135,10 @@ class IndexedDBStoreWorker { command: 'cmd_fail', seq: msg.seq, // Just send a string because Error objects aren't cloneable - error: "Error running command", + error: { + message: err.message, + name: err.name, + }, }); }); } From 264b20535e8759314647d60b881299006679978f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Oct 2018 12:13:05 +0100 Subject: [PATCH 55/57] Silence bluebird warnings --- src/client.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/client.js b/src/client.js index d5923ee0e..577cf5ba8 100644 --- a/src/client.js +++ b/src/client.js @@ -46,6 +46,9 @@ import ReEmitter from './ReEmitter'; import RoomList from './crypto/RoomList'; import {InvalidStoreError} from './errors'; +// 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 LAZY_LOADING_MESSAGES_FILTER = { lazy_load_members: true, From 59e60665795ecbd3684d1ada12ced5c9341c7f11 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 9 Oct 2018 14:15:03 +0100 Subject: [PATCH 56/57] Replace base58check with a simple parity check base58check seems way overcomplicated for this purpose (plus the module was exporting an es6 file, breaking the js-sdk build). A parity check empirically detects single substitution and transposition errors. Another option would be Luhn's algorithm. --- package.json | 2 +- src/crypto/recoverykey.js | 42 +++++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4ae0b36a7..17c3ec5ce 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "dependencies": { "another-json": "^0.2.0", "babel-runtime": "^6.26.0", - "base58check": "^2.0.0", "bluebird": "^3.5.0", "browser-request": "^0.3.3", + "bs58": "^4.0.1", "content-type": "^1.0.2", "request": "^2.53.0" }, diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index 82de3a158..69dc8ae6e 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -14,31 +14,51 @@ See the License for the specific language governing permissions and limitations under the License. */ -import base58check from 'base58check'; +import bs58 from 'bs58'; // picked arbitrarily but to try & avoid clashing with any bitcoin ones +// (also base58 encoded, albeit with a lot of hashing) const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; export function encodeRecoveryKey(key) { - const base58key = base58check.encode(Buffer.from(OLM_RECOVERY_KEY_PREFIX), Buffer.from(key)); + const buf = new Uint8Array(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1); + buf.set(OLM_RECOVERY_KEY_PREFIX, 0); + buf.set(key, OLM_RECOVERY_KEY_PREFIX.length); + + let parity = 0; + for (let i = 0; i < buf.length - 1; ++i) { + parity ^= buf[i]; + } + buf[buf.length - 1] = parity; + const base58key = bs58.encode(buf); + + return base58key.match(/.{1,4}/g).join(" "); } export function decodeRecoveryKey(recoverykey) { - const result = base58check.decode(recoverykey.replace(/ /, '')); - // the encoding doesn't include the length of the prefix, so the - // decoder assumes it's 1 byte. sigh. - const prefix = Buffer.concat([result.prefix, result.data.slice(0, OLM_RECOVERY_KEY_PREFIX.length - 1)]); + const result = bs58.decode(recoverykey.replace(/ /g, '')); - if (!prefix.equals(Buffer.from(OLM_RECOVERY_KEY_PREFIX))) { - throw new Error("Incorrect prefix"); + let parity = 0; + for (const b of result) { + parity ^= b; + } + if (parity !== 0) { + throw new Error("Incorrect parity"); } - const key = result.data.slice(OLM_RECOVERY_KEY_PREFIX.length - 1); + for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) { + if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) { + throw new Error("Incorrect prefix"); + } + } - if (key.length !== global.Olm.PRIVATE_KEY_LENGTH) { + if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1) { throw new Error("Incorrect length"); } - return key; + return result.slice( + OLM_RECOVERY_KEY_PREFIX.length, + OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH, + ); } From ada4b6ef16a3557e24b54e494d5b5a6f644c64f3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 9 Oct 2018 15:46:12 +0100 Subject: [PATCH 57/57] Lint --- spec/unit/crypto/backup.spec.js | 9 +++++---- src/client.js | 4 +++- src/crypto/index.js | 2 +- src/crypto/recoverykey.js | 5 ++++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 25f6c112a..f1da584f4 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -43,13 +43,14 @@ const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; const ROOM_ID = '!ROOM:ID'; +const SESSION_ID = 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc'; const ENCRYPTED_EVENT = new MatrixEvent({ type: 'm.room.encrypted', room_id: '!ROOM:ID', content: { algorithm: 'm.megolm.v1.aes-sha2', sender_key: 'SENDER_CURVE25519', - session_id: 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + session_id: SESSION_ID, ciphertext: 'AwgAEjD+VwXZ7PoGPRS/H4kwpAsMp/g+WPvJVtPEKE8fmM9IcT/N' + 'CiwPb8PehecDKP0cjm1XO88k6Bw3D17aGiBHr5iBoP7oSw8CXULXAMTkBl' + 'mkufRQq2+d0Giy1s4/Cg5n13jSVrSb2q7VTSv1ZHAFjUCsLSfR0gxqcQs', @@ -222,7 +223,7 @@ describe("MegolmBackup", function() { "qx37WTQrjZLz5tId/uBX9B3/okqAbV1ofl9UnHKno1eipByCpXleAAlAZoJgYnCDOQZD" + "QWzo3luTSfkF9pU1mOILCbbouubs6TVeDyPfgGD9i86J8irHjA", ROOM_ID, - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc', + SESSION_ID, ).then(() => { return megolmDecryption.decryptEvent(ENCRYPTED_EVENT); }).then((res) => { @@ -236,10 +237,10 @@ describe("MegolmBackup", function() { rooms: { [ROOM_ID]: { sessions: { - 'o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc': KEY_BACKUP_DATA, + SESSION_ID: KEY_BACKUP_DATA, }, }, - } + }, }); }; return client.restoreKeyBackups( diff --git a/src/client.js b/src/client.js index 5a9481122..9c3a86911 100644 --- a/src/client.js +++ b/src/client.js @@ -1032,7 +1032,9 @@ MatrixClient.prototype.restoreKeyBackups = function( if (!roomData.sessions) continue; totalKeyCount += Object.keys(roomData.sessions).length; - const roomKeys = keysFromRecoverySession(roomData.sessions, decryption, roomId, roomKeys); + const roomKeys = keysFromRecoverySession( + roomData.sessions, decryption, roomId, roomKeys, + ); for (const k of roomKeys) { k.room_id = roomId; keys.push(k); diff --git a/src/crypto/index.js b/src/crypto/index.js index 7aa76e37e..b6926b837 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -974,7 +974,7 @@ Crypto.prototype._backupPayloadForSession = function( exportFormat, ) { // new session. - const session = new Olm.InboundGroupSession(); + const session = new global.Olm.InboundGroupSession(); try { if (exportFormat) { session.import_session(sessionKey); diff --git a/src/crypto/recoverykey.js b/src/crypto/recoverykey.js index 69dc8ae6e..bb85697e8 100644 --- a/src/crypto/recoverykey.js +++ b/src/crypto/recoverykey.js @@ -53,7 +53,10 @@ export function decodeRecoveryKey(recoverykey) { } } - if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1) { + if ( + result.length !== + OLM_RECOVERY_KEY_PREFIX.length + global.Olm.PRIVATE_KEY_LENGTH + 1 + ) { throw new Error("Incorrect length"); }