You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Merge remote-tracking branch 'origin/develop' into uhoreg-e2e_backups
This commit is contained in:
92
CHANGELOG.md
92
CHANGELOG.md
@@ -1,15 +1,93 @@
|
|||||||
BREAKING CHANGE
|
Changes in [0.11.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.0) (2018-09-10)
|
||||||
---------------
|
|
||||||
|
|
||||||
* `MatrixClient::startClient` now returns a Promise. No method should be called on the client before that promise resolves. Before this method didn't return anything.
|
|
||||||
|
|
||||||
Changes in [0.11.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.0) (TDB)
|
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.6...v0.11.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.11.0-rc.1...v0.11.0)
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
----------------
|
||||||
|
* v0.11.0-rc.1 introduced some breaking changes - see the respective release notes.
|
||||||
|
|
||||||
|
No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [0.11.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.11.0-rc.1) (2018-09-07)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.9...v0.11.0-rc.1)
|
||||||
|
|
||||||
* Support for lazy loading members. This should improve performance for
|
* Support for lazy loading members. This should improve performance for
|
||||||
users who joined big rooms a lot. Pass to `lazyLoadMembers = true` option when calling `startClient`.
|
users who joined big rooms a lot. Pass to `lazyLoadMembers = true` option when calling `startClient`.
|
||||||
|
|
||||||
|
BREAKING CHANGES
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* `MatrixClient::startClient` now returns a Promise. No method should be called on the client before that promise resolves. Before this method didn't return anything.
|
||||||
|
* A new `CATCHUP` sync state, emitted by `MatrixClient#"sync"` and returned by `MatrixClient::getSyncState()`, when doing initial sync after the `ERROR` state. See `MatrixClient` documentation for details.
|
||||||
|
* `RoomState::maySendEvent('m.room.message', userId)` & `RoomState::maySendMessage(userId)` do not check the membership of the user anymore, only the power level. To check if the syncing user is allowed to write in a room, use `Room::maySendMessage()` as `RoomState` is not always aware of the syncing user's membership anymore, in case lazy loading of members is enabled.
|
||||||
|
|
||||||
|
All Changes
|
||||||
|
-----------
|
||||||
|
|
||||||
|
* Only emit CATCHUP if recovering from conn error
|
||||||
|
[\#727](https://github.com/matrix-org/matrix-js-sdk/pull/727)
|
||||||
|
* Fix docstring for sync data.error
|
||||||
|
[\#725](https://github.com/matrix-org/matrix-js-sdk/pull/725)
|
||||||
|
* Re-apply "Don't rely on members to query if syncing user can post to room"
|
||||||
|
[\#723](https://github.com/matrix-org/matrix-js-sdk/pull/723)
|
||||||
|
* Revert "Don't rely on members to query if syncing user can post to room"
|
||||||
|
[\#721](https://github.com/matrix-org/matrix-js-sdk/pull/721)
|
||||||
|
* Don't rely on members to query if syncing user can post to room
|
||||||
|
[\#717](https://github.com/matrix-org/matrix-js-sdk/pull/717)
|
||||||
|
* Fixes for room.guessDMUserId
|
||||||
|
[\#719](https://github.com/matrix-org/matrix-js-sdk/pull/719)
|
||||||
|
* Fix filepanel also filtering main timeline with LL turned on.
|
||||||
|
[\#716](https://github.com/matrix-org/matrix-js-sdk/pull/716)
|
||||||
|
* Remove lazy loaded members when leaving room
|
||||||
|
[\#711](https://github.com/matrix-org/matrix-js-sdk/pull/711)
|
||||||
|
* Fix: show spinner again while recovering from connection error
|
||||||
|
[\#702](https://github.com/matrix-org/matrix-js-sdk/pull/702)
|
||||||
|
* Add method to query LL state in client
|
||||||
|
[\#714](https://github.com/matrix-org/matrix-js-sdk/pull/714)
|
||||||
|
* Fix: also load invited members when lazy loading members
|
||||||
|
[\#707](https://github.com/matrix-org/matrix-js-sdk/pull/707)
|
||||||
|
* Pass through function to discard megolm session
|
||||||
|
[\#704](https://github.com/matrix-org/matrix-js-sdk/pull/704)
|
||||||
|
|
||||||
|
Changes in [0.10.9](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.10.9) (2018-09-03)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.9-rc.2...v0.10.9)
|
||||||
|
|
||||||
|
* No changes since rc.2
|
||||||
|
|
||||||
|
Changes in [0.10.9-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.10.9-rc.2) (2018-08-31)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.9-rc.1...v0.10.9-rc.2)
|
||||||
|
|
||||||
|
* Fix for "otherMember.getAvatarUrl is not a function"
|
||||||
|
[\#708](https://github.com/matrix-org/matrix-js-sdk/pull/708)
|
||||||
|
|
||||||
|
Changes in [0.10.9-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.10.9-rc.1) (2018-08-30)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.8...v0.10.9-rc.1)
|
||||||
|
|
||||||
|
* Fix DM avatar
|
||||||
|
[\#706](https://github.com/matrix-org/matrix-js-sdk/pull/706)
|
||||||
|
* Lazy loading: avoid loading members at initial sync for e2e rooms
|
||||||
|
[\#699](https://github.com/matrix-org/matrix-js-sdk/pull/699)
|
||||||
|
* Improve setRoomEncryption guard against multiple m.room.encryption st…
|
||||||
|
[\#700](https://github.com/matrix-org/matrix-js-sdk/pull/700)
|
||||||
|
* Revert "Lazy loading: don't block on setting up room crypto"
|
||||||
|
[\#698](https://github.com/matrix-org/matrix-js-sdk/pull/698)
|
||||||
|
* Lazy loading: don't block on setting up room crypto
|
||||||
|
[\#696](https://github.com/matrix-org/matrix-js-sdk/pull/696)
|
||||||
|
* Add getVisibleRooms()
|
||||||
|
[\#695](https://github.com/matrix-org/matrix-js-sdk/pull/695)
|
||||||
|
* Add wrapper around getJoinedMemberCount()
|
||||||
|
[\#697](https://github.com/matrix-org/matrix-js-sdk/pull/697)
|
||||||
|
* Api to fetch events via /room/.../event/..
|
||||||
|
[\#694](https://github.com/matrix-org/matrix-js-sdk/pull/694)
|
||||||
|
* Support for room upgrades
|
||||||
|
[\#693](https://github.com/matrix-org/matrix-js-sdk/pull/693)
|
||||||
|
* Lazy loading of room members
|
||||||
|
[\#691](https://github.com/matrix-org/matrix-js-sdk/pull/691)
|
||||||
|
|
||||||
Changes in [0.10.8](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.10.8) (2018-08-20)
|
Changes in [0.10.8](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.10.8) (2018-08-20)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.8-rc.1...v0.10.8)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.10.8-rc.1...v0.10.8)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "matrix-js-sdk",
|
"name": "matrix-js-sdk",
|
||||||
"version": "0.10.8",
|
"version": "0.11.0",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ describe("MatrixClient", function() {
|
|||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should transition ERROR -> PREPARED after /sync if prev failed",
|
it("should transition ERROR -> CATCHUP after /sync if prev failed",
|
||||||
function(done) {
|
function(done) {
|
||||||
const expectedStates = [];
|
const expectedStates = [];
|
||||||
acceptKeepalives = false;
|
acceptKeepalives = false;
|
||||||
@@ -403,7 +403,7 @@ describe("MatrixClient", function() {
|
|||||||
|
|
||||||
expectedStates.push(["RECONNECTING", null]);
|
expectedStates.push(["RECONNECTING", null]);
|
||||||
expectedStates.push(["ERROR", "RECONNECTING"]);
|
expectedStates.push(["ERROR", "RECONNECTING"]);
|
||||||
expectedStates.push(["PREPARED", "ERROR"]);
|
expectedStates.push(["CATCHUP", "ERROR"]);
|
||||||
client.on("sync", syncChecker(expectedStates, done));
|
client.on("sync", syncChecker(expectedStates, done));
|
||||||
client.startClient();
|
client.startClient();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -447,13 +447,6 @@ describe("RoomState", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("maySendStateEvent", function() {
|
describe("maySendStateEvent", function() {
|
||||||
it("should say non-joined members may not send state",
|
|
||||||
function() {
|
|
||||||
expect(state.maySendStateEvent(
|
|
||||||
'm.room.name', "@nobody:nowhere",
|
|
||||||
)).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should say any member may send state with no power level event",
|
it("should say any member may send state with no power level event",
|
||||||
function() {
|
function() {
|
||||||
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
|
expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
|
||||||
@@ -640,14 +633,6 @@ describe("RoomState", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("maySendEvent", function() {
|
describe("maySendEvent", function() {
|
||||||
it("should say non-joined members may not send events",
|
|
||||||
function() {
|
|
||||||
expect(state.maySendEvent(
|
|
||||||
'm.room.message', "@nobody:nowhere",
|
|
||||||
)).toEqual(false);
|
|
||||||
expect(state.maySendMessage("@nobody:nowhere")).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should say any member may send events with no power level event",
|
it("should say any member may send events with no power level event",
|
||||||
function() {
|
function() {
|
||||||
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
|
||||||
|
|||||||
@@ -1318,6 +1318,9 @@ describe("Room", function() {
|
|||||||
// events should already be MatrixEvents
|
// events should already be MatrixEvents
|
||||||
return function(event) {return event;};
|
return function(event) {return event;};
|
||||||
},
|
},
|
||||||
|
isRoomEncrypted: function() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
_http: {
|
_http: {
|
||||||
serverResponse,
|
serverResponse,
|
||||||
authedRequest: function() {
|
authedRequest: function() {
|
||||||
@@ -1397,7 +1400,7 @@ describe("Room", function() {
|
|||||||
|
|
||||||
describe("getMyMembership", function() {
|
describe("getMyMembership", function() {
|
||||||
it("should return synced membership if membership isn't available yet",
|
it("should return synced membership if membership isn't available yet",
|
||||||
async function() {
|
function() {
|
||||||
const room = new Room(roomId, null, userA);
|
const room = new Room(roomId, null, userA);
|
||||||
room.setSyncedMembership("invite");
|
room.setSyncedMembership("invite");
|
||||||
expect(room.getMyMembership()).toEqual("invite");
|
expect(room.getMyMembership()).toEqual("invite");
|
||||||
@@ -1408,4 +1411,40 @@ describe("Room", function() {
|
|||||||
expect(room.getMyMembership()).toEqual("join");
|
expect(room.getMyMembership()).toEqual("join");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("guessDMUserId", function() {
|
||||||
|
it("should return first hero id",
|
||||||
|
function() {
|
||||||
|
const room = new Room(roomId, null, userA);
|
||||||
|
room.setSummary({'m.heroes': [userB]});
|
||||||
|
expect(room.guessDMUserId()).toEqual(userB);
|
||||||
|
});
|
||||||
|
it("should return first member that isn't self",
|
||||||
|
function() {
|
||||||
|
const room = new Room(roomId, null, userA);
|
||||||
|
room.addLiveEvents([utils.mkMembership({
|
||||||
|
user: userB, mship: "join",
|
||||||
|
room: roomId, event: true,
|
||||||
|
})]);
|
||||||
|
expect(room.guessDMUserId()).toEqual(userB);
|
||||||
|
});
|
||||||
|
it("should return self if only member present",
|
||||||
|
function() {
|
||||||
|
const room = new Room(roomId, null, userA);
|
||||||
|
expect(room.guessDMUserId()).toEqual(userA);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("maySendMessage", function() {
|
||||||
|
it("should return false if synced membership not join",
|
||||||
|
function() {
|
||||||
|
const room = new Room(roomId, null, userA);
|
||||||
|
room.setSyncedMembership("invite");
|
||||||
|
expect(room.maySendMessage()).toEqual(false);
|
||||||
|
room.setSyncedMembership("leave");
|
||||||
|
expect(room.maySendMessage()).toEqual(false);
|
||||||
|
room.setSyncedMembership("join");
|
||||||
|
expect(room.maySendMessage()).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -703,6 +703,21 @@ MatrixClient.prototype.isRoomEncrypted = function(roomId) {
|
|||||||
return this._roomList.isRoomEncrypted(roomId);
|
return this._roomList.isRoomEncrypted(roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the current outbound group session to be discarded such
|
||||||
|
* that another one will be created next time an event is sent.
|
||||||
|
*
|
||||||
|
* @param {string} roomId The ID of the room to discard the session for
|
||||||
|
*
|
||||||
|
* This should not normally be necessary.
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.forceDiscardSession = function(roomId) {
|
||||||
|
if (!this._crypto) {
|
||||||
|
throw new Error("End-to-End encryption disabled");
|
||||||
|
}
|
||||||
|
this._crypto.forceDiscardSession(roomId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list containing all of the room keys
|
* Get a list containing all of the room keys
|
||||||
*
|
*
|
||||||
@@ -2318,7 +2333,9 @@ function(roomId, fromToken, limit, dir, timelineFilter = undefined) {
|
|||||||
|
|
||||||
let filter = null;
|
let filter = null;
|
||||||
if (this._clientOpts.lazyLoadMembers) {
|
if (this._clientOpts.lazyLoadMembers) {
|
||||||
filter = LAZY_LOADING_MESSAGES_FILTER;
|
// create a shallow copy of LAZY_LOADING_MESSAGES_FILTER,
|
||||||
|
// so the timelineFilter doesn't get written into it below
|
||||||
|
filter = Object.assign({}, LAZY_LOADING_MESSAGES_FILTER);
|
||||||
}
|
}
|
||||||
if (timelineFilter) {
|
if (timelineFilter) {
|
||||||
// XXX: it's horrific that /messages' filter parameter doesn't match
|
// XXX: it's horrific that /messages' filter parameter doesn't match
|
||||||
@@ -3286,6 +3303,10 @@ MatrixClient.prototype.startClient = async function(opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.lazyLoadMembers && this._crypto) {
|
||||||
|
this._crypto.enableLazyLoading();
|
||||||
|
}
|
||||||
|
|
||||||
opts.crypto = this._crypto;
|
opts.crypto = this._crypto;
|
||||||
opts.canResetEntireTimeline = (roomId) => {
|
opts.canResetEntireTimeline = (roomId) => {
|
||||||
if (!this._canResetTimelineCallback) {
|
if (!this._canResetTimelineCallback) {
|
||||||
@@ -3343,6 +3364,14 @@ MatrixClient.prototype.doesServerSupportLazyLoading = async function() {
|
|||||||
return this._serverSupportsLazyLoading;
|
return this._serverSupportsLazyLoading;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get if lazy loading members is being used.
|
||||||
|
* @return {boolean} Whether or not members are lazy loaded by this client
|
||||||
|
*/
|
||||||
|
MatrixClient.prototype.hasLazyLoadMembersEnabled = function() {
|
||||||
|
return !!this._clientOpts.lazyLoadMembers;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set a function which is called when /sync returns a 'limited' response.
|
* Set a function which is called when /sync returns a 'limited' response.
|
||||||
* It is called with a room ID and returns a boolean. It should return 'true' if the SDK
|
* It is called with a room ID and returns a boolean. It should return 'true' if the SDK
|
||||||
@@ -3688,6 +3717,12 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
|||||||
* a state of SYNCING. <i>This is the equivalent of "syncComplete" in the
|
* a state of SYNCING. <i>This is the equivalent of "syncComplete" in the
|
||||||
* previous API.</i></li>
|
* previous API.</i></li>
|
||||||
*
|
*
|
||||||
|
* <li>CATCHUP: The client has detected the connection to the server might be
|
||||||
|
* available again and will now try to do a sync again. As this sync might take
|
||||||
|
* a long time (depending how long ago was last synced, and general server
|
||||||
|
* performance) the client is put in this mode so the UI can reflect trying
|
||||||
|
* to catch up with the server after losing connection.</li>
|
||||||
|
*
|
||||||
* <li>SYNCING : The client is currently polling for new events from the server.
|
* <li>SYNCING : The client is currently polling for new events from the server.
|
||||||
* This will be called <i>after</i> processing latest events from a sync.</li>
|
* This will be called <i>after</i> processing latest events from a sync.</li>
|
||||||
*
|
*
|
||||||
@@ -3712,10 +3747,10 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
|||||||
* |
|
* |
|
||||||
* +----->PREPARED -------> SYNCING <--+
|
* +----->PREPARED -------> SYNCING <--+
|
||||||
* | ^ | ^ |
|
* | ^ | ^ |
|
||||||
* | | | | |
|
* | CATCHUP ----------+ | | |
|
||||||
* | | V | |
|
* | ^ V | |
|
||||||
* null ------+ | +--------RECONNECTING |
|
* null ------+ | +------- RECONNECTING |
|
||||||
* | | V |
|
* | V V |
|
||||||
* +------->ERROR ---------------------+
|
* +------->ERROR ---------------------+
|
||||||
*
|
*
|
||||||
* NB: 'null' will never be emitted by this event.
|
* NB: 'null' will never be emitted by this event.
|
||||||
@@ -3765,7 +3800,7 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
|||||||
*
|
*
|
||||||
* @param {?Object} data Data about this transition.
|
* @param {?Object} data Data about this transition.
|
||||||
*
|
*
|
||||||
* @param {MatrixError} data.err The matrix error if <code>state=ERROR</code>.
|
* @param {MatrixError} data.error The matrix error if <code>state=ERROR</code>.
|
||||||
*
|
*
|
||||||
* @param {String} data.oldSyncToken The 'since' token passed to /sync.
|
* @param {String} data.oldSyncToken The 'since' token passed to /sync.
|
||||||
* <code>null</code> for the first successful sync since this client was
|
* <code>null</code> for the first successful sync since this client was
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ export default class RoomList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setRoomEncryption(roomId, roomInfo) {
|
async setRoomEncryption(roomId, roomInfo) {
|
||||||
|
// important that this happens before calling into the store
|
||||||
|
// as it prevents the Crypto::setRoomEncryption from calling
|
||||||
|
// this twice for consecutive m.room.encryption events
|
||||||
this._roomEncryption[roomId] = roomInfo;
|
this._roomEncryption[roomId] = roomInfo;
|
||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -488,6 +489,8 @@ MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
|||||||
session_id: session.sessionId,
|
session_id: session.sessionId,
|
||||||
// Include our device ID so that recipients can send us a
|
// Include our device ID so that recipients can send us a
|
||||||
// m.new_device message if they don't have our session key.
|
// m.new_device message if they don't have our session key.
|
||||||
|
// XXX: Do we still need this now that m.new_device messages
|
||||||
|
// no longer exist since #483?
|
||||||
device_id: self._deviceId,
|
device_id: self._deviceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -496,6 +499,16 @@ MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the current outbound group session to be discarded such
|
||||||
|
* that another one will be created next time an event is sent.
|
||||||
|
*
|
||||||
|
* This should not normally be necessary.
|
||||||
|
*/
|
||||||
|
MegolmEncryption.prototype.forceDiscardSession = function() {
|
||||||
|
this._setupPromise = this._setupPromise.then(() => null);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the devices we're about to send to and see if any are entirely
|
* Checks the devices we're about to send to and see if any are entirely
|
||||||
* unknown to the user. If so, warn the user, and mark them as known to
|
* unknown to the user. If so, warn the user, and mark them as known to
|
||||||
@@ -550,12 +563,9 @@ MegolmEncryption.prototype._getDevicesInRoom = async function(room) {
|
|||||||
// We are happy to use a cached version here: we assume that if we already
|
// We are happy to use a cached version here: we assume that if we already
|
||||||
// have a list of the user's devices, then we already share an e2e room
|
// have a list of the user's devices, then we already share an e2e room
|
||||||
// with them, which means that they will have announced any new devices via
|
// with them, which means that they will have announced any new devices via
|
||||||
// an m.new_device.
|
// device_lists in their /sync response. This cache should then be maintained
|
||||||
//
|
// using all the device_lists changes and left fields.
|
||||||
// XXX: what if the cache is stale, and the user left the room we had in
|
// See https://github.com/vector-im/riot-web/issues/2305 for details.
|
||||||
// common and then added new devices before joining this one? --Matthew
|
|
||||||
//
|
|
||||||
// yup, see https://github.com/vector-im/riot-web/issues/2305 --richvdh
|
|
||||||
const devices = await this._crypto.downloadKeys(roomMembers, false);
|
const devices = await this._crypto.downloadKeys(roomMembers, false);
|
||||||
// remove any blocked devices
|
// remove any blocked devices
|
||||||
for (const userId in devices) {
|
for (const userId in devices) {
|
||||||
|
|||||||
@@ -111,6 +111,15 @@ function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
this._receivedRoomKeyRequestCancellations = [];
|
this._receivedRoomKeyRequestCancellations = [];
|
||||||
// true if we are currently processing received room key requests
|
// true if we are currently processing received room key requests
|
||||||
this._processingRoomKeyRequests = false;
|
this._processingRoomKeyRequests = false;
|
||||||
|
// controls whether device tracking is delayed
|
||||||
|
// until calling encryptEvent or trackRoomDevices,
|
||||||
|
// or done immediately upon enabling room encryption.
|
||||||
|
this._lazyLoadMembers = false;
|
||||||
|
// in case _lazyLoadMembers is true,
|
||||||
|
// track if an initial tracking of all the room members
|
||||||
|
// has happened for a given room. This is delayed
|
||||||
|
// to avoid loading room members as long as possible.
|
||||||
|
this._roomDeviceTrackingState = {};
|
||||||
}
|
}
|
||||||
utils.inherits(Crypto, EventEmitter);
|
utils.inherits(Crypto, EventEmitter);
|
||||||
|
|
||||||
@@ -172,6 +181,12 @@ Crypto.prototype.init = async function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
Crypto.prototype.enableLazyLoading = function() {
|
||||||
|
this._lazyLoadMembers = true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tell the crypto module to register for MatrixClient events which it needs to
|
* Tell the crypto module to register for MatrixClient events which it needs to
|
||||||
* listen for
|
* listen for
|
||||||
@@ -611,6 +626,23 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forces the current outbound group session to be discarded such
|
||||||
|
* that another one will be created next time an event is sent.
|
||||||
|
*
|
||||||
|
* @param {string} roomId The ID of the room to discard the session for
|
||||||
|
*
|
||||||
|
* This should not normally be necessary.
|
||||||
|
*/
|
||||||
|
Crypto.prototype.forceDiscardSession = function(roomId) {
|
||||||
|
const alg = this._roomEncryptors[roomId];
|
||||||
|
if (alg === undefined) throw new Error("Room not encrypted");
|
||||||
|
if (alg.forceDiscardSession === undefined) {
|
||||||
|
throw new Error("Room encryption algorithm doesn't support session discarding");
|
||||||
|
}
|
||||||
|
alg.forceDiscardSession();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
* Configure a room to use encryption (ie, save a flag in the sessionstore).
|
||||||
*
|
*
|
||||||
@@ -619,25 +651,49 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) {
|
|||||||
* @param {object} config The encryption config for the room.
|
* @param {object} config The encryption config for the room.
|
||||||
*
|
*
|
||||||
* @param {boolean=} inhibitDeviceQuery true to suppress device list query for
|
* @param {boolean=} inhibitDeviceQuery true to suppress device list query for
|
||||||
* users in the room (for now)
|
* users in the room (for now). In case lazy loading is enabled,
|
||||||
|
* the device query is always inhibited as the members are not tracked.
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDeviceQuery) {
|
||||||
// if we already have encryption in this room, we should ignore this event
|
// if state is being replayed from storage, we might already have a configuration
|
||||||
// (for now at least. maybe we should alert the user somehow?)
|
// for this room as they are persisted as well.
|
||||||
|
// We just need to make sure the algorithm is initialized in this case.
|
||||||
|
// However, if the new config is different,
|
||||||
|
// we should bail out as room encryption can't be changed once set.
|
||||||
const existingConfig = this._roomList.getRoomEncryption(roomId);
|
const existingConfig = this._roomList.getRoomEncryption(roomId);
|
||||||
if (existingConfig && JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
if (existingConfig) {
|
||||||
|
if (JSON.stringify(existingConfig) != JSON.stringify(config)) {
|
||||||
console.error("Ignoring m.room.encryption event which requests " +
|
console.error("Ignoring m.room.encryption event which requests " +
|
||||||
"a change of config in " + roomId);
|
"a change of config in " + roomId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// if we already have encryption in this room, we should ignore this event,
|
||||||
|
// as it would reset the encryption algorithm.
|
||||||
|
// This is at least expected to be called twice, as sync calls onCryptoEvent
|
||||||
|
// for both the timeline and state sections in the /sync response,
|
||||||
|
// the encryption event would appear in both.
|
||||||
|
// If it's called more than twice though,
|
||||||
|
// it signals a bug on client or server.
|
||||||
|
const existingAlg = this._roomEncryptors[roomId];
|
||||||
|
if (existingAlg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// _roomList.getRoomEncryption will not race with _roomList.setRoomEncryption
|
||||||
|
// because it first stores in memory. We should await the promise only
|
||||||
|
// after all the in-memory state (_roomEncryptors and _roomList) has been updated
|
||||||
|
// to avoid races when calling this method multiple times. Hence keep a hold of the promise.
|
||||||
|
let storeConfigPromise = null;
|
||||||
|
if(!existingConfig) {
|
||||||
|
storeConfigPromise = this._roomList.setRoomEncryption(roomId, config);
|
||||||
|
}
|
||||||
|
|
||||||
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
const AlgClass = algorithms.ENCRYPTION_CLASSES[config.algorithm];
|
||||||
if (!AlgClass) {
|
if (!AlgClass) {
|
||||||
throw new Error("Unable to encrypt with " + config.algorithm);
|
throw new Error("Unable to encrypt with " + config.algorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._roomList.setRoomEncryption(roomId, config);
|
|
||||||
|
|
||||||
const alg = new AlgClass({
|
const alg = new AlgClass({
|
||||||
userId: this._userId,
|
userId: this._userId,
|
||||||
deviceId: this._deviceId,
|
deviceId: this._deviceId,
|
||||||
@@ -649,23 +705,58 @@ Crypto.prototype.setRoomEncryption = async function(roomId, config, inhibitDevic
|
|||||||
});
|
});
|
||||||
this._roomEncryptors[roomId] = alg;
|
this._roomEncryptors[roomId] = alg;
|
||||||
|
|
||||||
// make sure we are tracking the device lists for all users in this room.
|
if (storeConfigPromise) {
|
||||||
console.log("Enabling encryption in " + roomId + "; " +
|
await storeConfigPromise;
|
||||||
"starting to track device lists for all users therein");
|
|
||||||
const room = this._clientStore.getRoom(roomId);
|
|
||||||
if (!room) {
|
|
||||||
throw new Error(`Unable to enable encryption in unknown room ${roomId}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._lazyLoadMembers) {
|
||||||
|
console.log("Enabling encryption in " + roomId + "; " +
|
||||||
|
"starting to track device lists for all users therein");
|
||||||
|
|
||||||
|
await this.trackRoomDevices(roomId);
|
||||||
|
// TODO: this flag is only not used from MatrixClient::setRoomEncryption
|
||||||
|
// which is never used (inside riot at least)
|
||||||
|
// but didn't want to remove it as it technically would
|
||||||
|
// be a breaking change.
|
||||||
|
if(!this.inhibitDeviceQuery) {
|
||||||
|
this._deviceList.refreshOutdatedDeviceLists();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Enabling encryption in " + roomId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure we are tracking the device lists for all users in this room.
|
||||||
|
*
|
||||||
|
* @param {string} roomId The room ID to start tracking devices in.
|
||||||
|
* @returns {Promise} when all devices for the room have been fetched and marked to track
|
||||||
|
*/
|
||||||
|
Crypto.prototype.trackRoomDevices = function(roomId) {
|
||||||
|
const trackMembers = async () => {
|
||||||
|
// not an encrypted room
|
||||||
|
if (!this._roomEncryptors[roomId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const room = this._clientStore.getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
throw new Error(`Unable to start tracking devices in unknown room ${roomId}`);
|
||||||
|
}
|
||||||
|
console.log(`Starting to track devices for room ${roomId} ...`);
|
||||||
const members = await room.getEncryptionTargetMembers();
|
const members = await room.getEncryptionTargetMembers();
|
||||||
members.forEach((m) => {
|
members.forEach((m) => {
|
||||||
this._deviceList.startTrackingDeviceList(m.userId);
|
this._deviceList.startTrackingDeviceList(m.userId);
|
||||||
});
|
});
|
||||||
if (!inhibitDeviceQuery) {
|
};
|
||||||
this._deviceList.refreshOutdatedDeviceLists();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let promise = this._roomDeviceTrackingState[roomId];
|
||||||
|
if (!promise) {
|
||||||
|
promise = trackMembers();
|
||||||
|
this._roomDeviceTrackingState[roomId] = promise;
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} module:crypto~OlmSessionResult
|
* @typedef {Object} module:crypto~OlmSessionResult
|
||||||
@@ -757,7 +848,7 @@ Crypto.prototype.importRoomKeys = function(keys) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
/* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307
|
||||||
/**
|
/**
|
||||||
* Encrypt an event according to the configuration of the room.
|
* Encrypt an event according to the configuration of the room.
|
||||||
*
|
*
|
||||||
@@ -768,7 +859,8 @@ Crypto.prototype.importRoomKeys = function(keys) {
|
|||||||
* @return {module:client.Promise?} Promise which resolves when the event has been
|
* @return {module:client.Promise?} Promise which resolves when the event has been
|
||||||
* encrypted, or null if nothing was needed
|
* encrypted, or null if nothing was needed
|
||||||
*/
|
*/
|
||||||
Crypto.prototype.encryptEvent = function(event, room) {
|
/* eslint-enable valid-jsdoc */
|
||||||
|
Crypto.prototype.encryptEvent = async function(event, room) {
|
||||||
if (!room) {
|
if (!room) {
|
||||||
throw new Error("Cannot send encrypted messages in unknown rooms");
|
throw new Error("Cannot send encrypted messages in unknown rooms");
|
||||||
}
|
}
|
||||||
@@ -786,6 +878,12 @@ Crypto.prototype.encryptEvent = function(event, room) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._roomDeviceTrackingState[roomId]) {
|
||||||
|
this.trackRoomDevices(roomId);
|
||||||
|
}
|
||||||
|
// wait for all the room devices to be loaded
|
||||||
|
await this._roomDeviceTrackingState[roomId];
|
||||||
|
|
||||||
let content = event.getContent();
|
let content = event.getContent();
|
||||||
// If event has an m.relates_to then we need
|
// If event has an m.relates_to then we need
|
||||||
// to put this on the wrapping event instead
|
// to put this on the wrapping event instead
|
||||||
@@ -796,9 +894,9 @@ Crypto.prototype.encryptEvent = function(event, room) {
|
|||||||
delete content['m.relates_to'];
|
delete content['m.relates_to'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return alg.encryptMessage(
|
const encryptedContent = await alg.encryptMessage(
|
||||||
room, event.getType(), content,
|
room, event.getType(), content);
|
||||||
).then((encryptedContent) => {
|
|
||||||
if (mRelatesTo) {
|
if (mRelatesTo) {
|
||||||
encryptedContent['m.relates_to'] = mRelatesTo;
|
encryptedContent['m.relates_to'] = mRelatesTo;
|
||||||
}
|
}
|
||||||
@@ -809,7 +907,6 @@ Crypto.prototype.encryptEvent = function(event, room) {
|
|||||||
this._olmDevice.deviceCurve25519Key,
|
this._olmDevice.deviceCurve25519Key,
|
||||||
this._olmDevice.deviceEd25519Key,
|
this._olmDevice.deviceEd25519Key,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -924,6 +1021,7 @@ Crypto.prototype.onSyncWillProcess = async function(syncData) {
|
|||||||
// at which point we'll start tracking all the users of that room.
|
// at which point we'll start tracking all the users of that room.
|
||||||
console.log("Initial sync performed - resetting device tracking state");
|
console.log("Initial sync performed - resetting device tracking state");
|
||||||
this._deviceList.stopTrackingAllDeviceLists();
|
this._deviceList.stopTrackingAllDeviceLists();
|
||||||
|
this._roomDeviceTrackingState = {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -969,11 +1067,12 @@ Crypto.prototype._evalDeviceListChanges = async function(deviceLists) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceLists.left && Array.isArray(deviceLists.left)) {
|
if (deviceLists.left && Array.isArray(deviceLists.left) &&
|
||||||
|
deviceLists.left.length) {
|
||||||
// Check we really don't share any rooms with these users
|
// Check we really don't share any rooms with these users
|
||||||
// any more: the server isn't required to give us the
|
// any more: the server isn't required to give us the
|
||||||
// exact correct set.
|
// exact correct set.
|
||||||
const e2eUserIds = new Set(await this._getE2eUsers());
|
const e2eUserIds = new Set(await this._getTrackedE2eUsers());
|
||||||
|
|
||||||
deviceLists.left.forEach((u) => {
|
deviceLists.left.forEach((u) => {
|
||||||
if (!e2eUserIds.has(u)) {
|
if (!e2eUserIds.has(u)) {
|
||||||
@@ -985,12 +1084,13 @@ Crypto.prototype._evalDeviceListChanges = async function(deviceLists) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of all the IDs of users we share an e2e room with
|
* Get a list of all the IDs of users we share an e2e room with
|
||||||
|
* for which we are tracking devices already
|
||||||
*
|
*
|
||||||
* @returns {string[]} List of user IDs
|
* @returns {string[]} List of user IDs
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._getE2eUsers = async function() {
|
Crypto.prototype._getTrackedE2eUsers = async function() {
|
||||||
const e2eUserIds = [];
|
const e2eUserIds = [];
|
||||||
for (const room of this._getE2eRooms()) {
|
for (const room of this._getTrackedE2eRooms()) {
|
||||||
const members = await room.getEncryptionTargetMembers();
|
const members = await room.getEncryptionTargetMembers();
|
||||||
for (const member of members) {
|
for (const member of members) {
|
||||||
e2eUserIds.push(member.userId);
|
e2eUserIds.push(member.userId);
|
||||||
@@ -1000,17 +1100,21 @@ Crypto.prototype._getE2eUsers = async function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of the e2e-enabled rooms we are members of
|
* Get a list of the e2e-enabled rooms we are members of,
|
||||||
|
* and for which we are already tracking the devices
|
||||||
*
|
*
|
||||||
* @returns {module:models.Room[]}
|
* @returns {module:models.Room[]}
|
||||||
*/
|
*/
|
||||||
Crypto.prototype._getE2eRooms = function() {
|
Crypto.prototype._getTrackedE2eRooms = function() {
|
||||||
return this._clientStore.getRooms().filter((room) => {
|
return this._clientStore.getRooms().filter((room) => {
|
||||||
// check for rooms with encryption enabled
|
// check for rooms with encryption enabled
|
||||||
const alg = this._roomEncryptors[room.roomId];
|
const alg = this._roomEncryptors[room.roomId];
|
||||||
if (!alg) {
|
if (!alg) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!this._roomDeviceTrackingState[room.roomId]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore any rooms which we have left
|
// ignore any rooms which we have left
|
||||||
const myMembership = room.getMyMembership();
|
const myMembership = room.getMyMembership();
|
||||||
@@ -1079,7 +1183,11 @@ Crypto.prototype._onRoomMembership = function(event, member, oldMembership) {
|
|||||||
// not encrypting in this room
|
// not encrypting in this room
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// only mark users in this room as tracked if we already started tracking in this room
|
||||||
|
// this way we don't start device queries after sync on behalf of this room which we won't use
|
||||||
|
// the result of anyway, as we'll need to do a query again once all the members are fetched
|
||||||
|
// by calling _trackRoomDevices
|
||||||
|
if (this._roomDeviceTrackingState[roomId]) {
|
||||||
if (member.membership == 'join') {
|
if (member.membership == 'join') {
|
||||||
console.log('Join event for ' + member.userId + ' in ' + roomId);
|
console.log('Join event for ' + member.userId + ' in ' + roomId);
|
||||||
// make sure we are tracking the deviceList for this user
|
// make sure we are tracking the deviceList for this user
|
||||||
@@ -1089,6 +1197,7 @@ Crypto.prototype._onRoomMembership = function(event, member, oldMembership) {
|
|||||||
console.log('Invite event for ' + member.userId + ' in ' + roomId);
|
console.log('Invite event for ' + member.userId + ' in ' + roomId);
|
||||||
this._deviceList.startTrackingDeviceList(member.userId);
|
this._deviceList.startTrackingDeviceList(member.userId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
alg.onRoomMembership(event, member, oldMembership);
|
alg.onRoomMembership(event, member, oldMembership);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -443,6 +443,22 @@ RoomState.prototype.markOutOfBandMembersFailed = function() {
|
|||||||
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the loaded out-of-band members
|
||||||
|
*/
|
||||||
|
RoomState.prototype.clearOutOfBandMembers = function() {
|
||||||
|
let count = 0;
|
||||||
|
Object.keys(this.members).forEach((userId) => {
|
||||||
|
const member = this.members[userId];
|
||||||
|
if (member.isOutOfBand()) {
|
||||||
|
++count;
|
||||||
|
delete this.members[userId];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(`LL: RoomState removed ${count} members...`);
|
||||||
|
this._oobMemberFlags.status = OOB_STATUS_NOTSTARTED;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the loaded out-of-band members.
|
* Sets the loaded out-of-band members.
|
||||||
* @param {MatrixEvent[]} stateEvents array of membership state events
|
* @param {MatrixEvent[]} stateEvents array of membership state events
|
||||||
@@ -495,7 +511,7 @@ RoomState.prototype._setOutOfBandMember = function(stateEvent) {
|
|||||||
|
|
||||||
this._setStateEvent(stateEvent);
|
this._setStateEvent(stateEvent);
|
||||||
this._updateMember(member);
|
this._updateMember(member);
|
||||||
this.emit("RoomState.members", {}, stateEvent, member);
|
this.emit("RoomState.members", stateEvent, this, member);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -651,11 +667,6 @@ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) {
|
|||||||
* according to the room's state.
|
* according to the room's state.
|
||||||
*/
|
*/
|
||||||
RoomState.prototype._maySendEventOfType = function(eventType, userId, state) {
|
RoomState.prototype._maySendEventOfType = function(eventType, userId, state) {
|
||||||
const member = this.getMember(userId);
|
|
||||||
if (!member || member.membership == 'leave') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const power_levels_event = this.getStateEvents('m.room.power_levels', '');
|
const power_levels_event = this.getStateEvents('m.room.power_levels', '');
|
||||||
|
|
||||||
let power_levels;
|
let power_levels;
|
||||||
@@ -663,25 +674,34 @@ RoomState.prototype._maySendEventOfType = function(eventType, userId, state) {
|
|||||||
|
|
||||||
let state_default = 0;
|
let state_default = 0;
|
||||||
let events_default = 0;
|
let events_default = 0;
|
||||||
|
let powerLevel = 0;
|
||||||
if (power_levels_event) {
|
if (power_levels_event) {
|
||||||
power_levels = power_levels_event.getContent();
|
power_levels = power_levels_event.getContent();
|
||||||
events_levels = power_levels.events || {};
|
events_levels = power_levels.events || {};
|
||||||
|
|
||||||
if (utils.isNumber(power_levels.state_default)) {
|
if (Number.isFinite(power_levels.state_default)) {
|
||||||
state_default = power_levels.state_default;
|
state_default = power_levels.state_default;
|
||||||
} else {
|
} else {
|
||||||
state_default = 50;
|
state_default = 50;
|
||||||
}
|
}
|
||||||
if (utils.isNumber(power_levels.events_default)) {
|
|
||||||
|
const userPowerLevel = power_levels.users && power_levels.users[userId];
|
||||||
|
if (Number.isFinite(userPowerLevel)) {
|
||||||
|
powerLevel = userPowerLevel;
|
||||||
|
} else if(Number.isFinite(power_levels.users_default)) {
|
||||||
|
powerLevel = power_levels.users_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isFinite(power_levels.events_default)) {
|
||||||
events_default = power_levels.events_default;
|
events_default = power_levels.events_default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let required_level = state ? state_default : events_default;
|
let required_level = state ? state_default : events_default;
|
||||||
if (utils.isNumber(events_levels[eventType])) {
|
if (Number.isFinite(events_levels[eventType])) {
|
||||||
required_level = events_levels[eventType];
|
required_level = events_levels[eventType];
|
||||||
}
|
}
|
||||||
return member.powerLevel >= required_level;
|
return powerLevel >= required_level;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const EventEmitter = require("events").EventEmitter;
|
|||||||
|
|
||||||
const EventStatus = require("./event").EventStatus;
|
const EventStatus = require("./event").EventStatus;
|
||||||
const RoomSummary = require("./room-summary");
|
const RoomSummary = require("./room-summary");
|
||||||
|
const RoomMember = require("./room-member");
|
||||||
const MatrixEvent = require("./event").MatrixEvent;
|
const MatrixEvent = require("./event").MatrixEvent;
|
||||||
const utils = require("../utils");
|
const utils = require("../utils");
|
||||||
const ContentRepo = require("../content-repo");
|
const ContentRepo = require("../content-repo");
|
||||||
@@ -279,14 +280,84 @@ Room.prototype.getDMInviter = function() {
|
|||||||
}
|
}
|
||||||
if (this._syncedMembership === "invite") {
|
if (this._syncedMembership === "invite") {
|
||||||
// fall back to summary information
|
// fall back to summary information
|
||||||
const memberCount = this.currentState.getJoinedMemberCount() +
|
const memberCount = this.getInvitedAndJoinedMemberCount();
|
||||||
this.currentState.getInvitedMemberCount();
|
|
||||||
if (memberCount == 2 && this._summaryHeroes.length) {
|
if (memberCount == 2 && this._summaryHeroes.length) {
|
||||||
return this._summaryHeroes[0];
|
return this._summaryHeroes[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assuming this room is a DM room, tries to guess with which user.
|
||||||
|
* @return {string} user id of the other member (could be syncing user)
|
||||||
|
*/
|
||||||
|
Room.prototype.guessDMUserId = function() {
|
||||||
|
const me = this.getMember(this.myUserId);
|
||||||
|
if (me) {
|
||||||
|
const inviterId = me.getDMInviter();
|
||||||
|
if (inviterId) {
|
||||||
|
return inviterId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remember, we're assuming this room is a DM,
|
||||||
|
// so returning the first member we find should be fine
|
||||||
|
const hasHeroes = Array.isArray(this._summaryHeroes) &&
|
||||||
|
this._summaryHeroes.length;
|
||||||
|
if (hasHeroes) {
|
||||||
|
return this._summaryHeroes[0];
|
||||||
|
}
|
||||||
|
const members = this.currentState.getMembers();
|
||||||
|
const anyMember = members.find((m) => m.userId !== this.myUserId);
|
||||||
|
if (anyMember) {
|
||||||
|
return anyMember.userId;
|
||||||
|
}
|
||||||
|
// it really seems like I'm the only user in the room
|
||||||
|
// so I probably created a room with just me in it
|
||||||
|
// and marked it as a DM. Ok then
|
||||||
|
return this.myUserId;
|
||||||
|
};
|
||||||
|
|
||||||
|
Room.prototype.getAvatarFallbackMember = function() {
|
||||||
|
const memberCount = this.getInvitedAndJoinedMemberCount();
|
||||||
|
if (memberCount > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hasHeroes = Array.isArray(this._summaryHeroes) &&
|
||||||
|
this._summaryHeroes.length;
|
||||||
|
if (hasHeroes) {
|
||||||
|
const availableMember = this._summaryHeroes.map((userId) => {
|
||||||
|
return this.getMember(userId);
|
||||||
|
}).find((member) => !!member);
|
||||||
|
if (availableMember) {
|
||||||
|
return availableMember;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const members = this.currentState.getMembers();
|
||||||
|
// could be different than memberCount
|
||||||
|
// as this includes left members
|
||||||
|
if (members.length <= 2) {
|
||||||
|
const availableMember = members.find((m) => {
|
||||||
|
return m.userId !== this.myUserId;
|
||||||
|
});
|
||||||
|
if (availableMember) {
|
||||||
|
return availableMember;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if all else fails, try falling back to a user,
|
||||||
|
// and create a one-off member for it
|
||||||
|
if (hasHeroes) {
|
||||||
|
const availableUser = this._summaryHeroes.map((userId) => {
|
||||||
|
return this._client.getUser(userId);
|
||||||
|
}).find((user) => !!user);
|
||||||
|
if (availableUser) {
|
||||||
|
const member = new RoomMember(
|
||||||
|
this.roomId, availableUser.userId);
|
||||||
|
member.user = availableUser;
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the membership this room was received as during sync
|
* Sets the membership this room was received as during sync
|
||||||
* @param {string} membership join | leave | invite
|
* @param {string} membership join | leave | invite
|
||||||
@@ -298,7 +369,6 @@ Room.prototype.setSyncedMembership = function(membership) {
|
|||||||
Room.prototype._loadMembersFromServer = async function() {
|
Room.prototype._loadMembersFromServer = async function() {
|
||||||
const lastSyncToken = this._client.store.getSyncToken();
|
const lastSyncToken = this._client.store.getSyncToken();
|
||||||
const queryString = utils.encodeParams({
|
const queryString = utils.encodeParams({
|
||||||
membership: "join",
|
|
||||||
not_membership: "leave",
|
not_membership: "leave",
|
||||||
at: lastSyncToken,
|
at: lastSyncToken,
|
||||||
});
|
});
|
||||||
@@ -343,8 +413,21 @@ Room.prototype.loadMembersIfNeeded = function() {
|
|||||||
// the OOB members
|
// the OOB members
|
||||||
this.currentState.markOutOfBandMembersStarted();
|
this.currentState.markOutOfBandMembersStarted();
|
||||||
|
|
||||||
const promise = this._loadMembers().then(({memberEvents, fromServer}) => {
|
const inMemoryUpdate = this._loadMembers().then((result) => {
|
||||||
this.currentState.setOutOfBandMembers(memberEvents);
|
this.currentState.setOutOfBandMembers(result.memberEvents);
|
||||||
|
// now the members are loaded, start to track the e2e devices if needed
|
||||||
|
if (this._client.isRoomEncrypted(this.roomId)) {
|
||||||
|
this._client._crypto.trackRoomDevices(this.roomId);
|
||||||
|
}
|
||||||
|
return result.fromServer;
|
||||||
|
}).catch((err) => {
|
||||||
|
// allow retries on fail
|
||||||
|
this._membersPromise = null;
|
||||||
|
this.currentState.markOutOfBandMembersFailed();
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
// update members in storage, but don't wait for it
|
||||||
|
inMemoryUpdate.then((fromServer) => {
|
||||||
if (fromServer) {
|
if (fromServer) {
|
||||||
const oobMembers = this.currentState.getMembers()
|
const oobMembers = this.currentState.getMembers()
|
||||||
.filter((m) => m.isOutOfBand())
|
.filter((m) => m.isOutOfBand())
|
||||||
@@ -361,16 +444,40 @@ Room.prototype.loadMembersIfNeeded = function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// allow retries on fail
|
// as this is not awaited anywhere,
|
||||||
this._membersPromise = null;
|
// at least show the error in the console
|
||||||
this.currentState.markOutOfBandMembersFailed();
|
console.error(err);
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._membersPromise = promise;
|
this._membersPromise = inMemoryUpdate;
|
||||||
|
|
||||||
return this._membersPromise;
|
return this._membersPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the lazily loaded members from storage if needed
|
||||||
|
*/
|
||||||
|
Room.prototype.clearLoadedMembersIfNeeded = async function() {
|
||||||
|
if (this._opts.lazyLoadMembers && this._membersPromise) {
|
||||||
|
await this.loadMembersIfNeeded();
|
||||||
|
await this._client.store.clearOutOfBandMembers(this.roomId);
|
||||||
|
this.currentState.clearOutOfBandMembers();
|
||||||
|
this._membersPromise = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
this.clearLoadedMembersIfNeeded().catch((err) => {
|
||||||
|
console.error(`error after clearing loaded members from ` +
|
||||||
|
`room ${this.roomId} after leaving`);
|
||||||
|
console.dir(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the live timeline of all timelineSets, and start new ones.
|
* Reset the live timeline of all timelineSets, and start new ones.
|
||||||
*
|
*
|
||||||
@@ -485,8 +592,13 @@ Room.prototype.setSummary = function(summary) {
|
|||||||
if (Number.isInteger(invitedCount)) {
|
if (Number.isInteger(invitedCount)) {
|
||||||
this.currentState.setInvitedMemberCount(invitedCount);
|
this.currentState.setInvitedMemberCount(invitedCount);
|
||||||
}
|
}
|
||||||
if (heroes) {
|
if (Array.isArray(heroes)) {
|
||||||
this._summaryHeroes = heroes;
|
// be cautious about trusting server values,
|
||||||
|
// and make sure heroes doesn't contain our own id
|
||||||
|
// just to be sure
|
||||||
|
this._summaryHeroes = heroes.filter((userId) => {
|
||||||
|
return userId !== this.myUserId;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -644,6 +756,14 @@ Room.prototype.getInvitedMemberCount = function() {
|
|||||||
return this.currentState.getInvitedMemberCount();
|
return this.currentState.getInvitedMemberCount();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of invited + joined members in this room
|
||||||
|
* @return {integer} The number of members in this room whose membership is 'invite' or 'join'
|
||||||
|
*/
|
||||||
|
Room.prototype.getInvitedAndJoinedMemberCount = function() {
|
||||||
|
return this.getInvitedMemberCount() + this.getJoinedMemberCount();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of members with given membership state.
|
* Get a list of members with given membership state.
|
||||||
* @param {string} membership The membership state.
|
* @param {string} membership The membership state.
|
||||||
@@ -1373,6 +1493,17 @@ Room.prototype.getAccountData = function(type) {
|
|||||||
return this.accountData[type];
|
return this.accountData[type];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns wheter the syncing user has permission to send a message in the room
|
||||||
|
* @return {boolean} true if the user should be permitted to send
|
||||||
|
* message events into the room.
|
||||||
|
*/
|
||||||
|
Room.prototype.maySendMessage = function() {
|
||||||
|
return this.getMyMembership() === 'join' &&
|
||||||
|
this.currentState.maySendEvent('m.room.message', this.myUserId);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is an internal method. Calculates the name of the room from the current
|
* This is an internal method. Calculates the name of the room from the current
|
||||||
* room state.
|
* room state.
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function selectQuery(store, keyRange, resultMapper) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function promiseifyTxn(txn) {
|
function txnAsPromise(txn) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
txn.oncomplete = function(event) {
|
txn.oncomplete = function(event) {
|
||||||
resolve(event);
|
resolve(event);
|
||||||
@@ -82,7 +82,7 @@ function promiseifyTxn(txn) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function promiseifyRequest(req) {
|
function reqAsEventPromise(req) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
req.onsuccess = function(event) {
|
req.onsuccess = function(event) {
|
||||||
resolve(event);
|
resolve(event);
|
||||||
@@ -93,6 +93,17 @@ function promiseifyRequest(req) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reqAsPromise(req) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
req.onsuccess = () => resolve(req);
|
||||||
|
req.onerror = (err) => reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reqAsCursorPromise(req) {
|
||||||
|
return reqAsEventPromise(req).then((event) => event.target.result);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual reading from and writing to the indexeddb
|
* Does the actual reading from and writing to the indexeddb
|
||||||
*
|
*
|
||||||
@@ -159,7 +170,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
console.log(
|
console.log(
|
||||||
`LocalIndexedDBStoreBackend.connect: awaiting connection...`,
|
`LocalIndexedDBStoreBackend.connect: awaiting connection...`,
|
||||||
);
|
);
|
||||||
return promiseifyRequest(req).then((ev) => {
|
return reqAsEventPromise(req).then((ev) => {
|
||||||
console.log(
|
console.log(
|
||||||
`LocalIndexedDBStoreBackend.connect: connected`,
|
`LocalIndexedDBStoreBackend.connect: connected`,
|
||||||
);
|
);
|
||||||
@@ -254,22 +265,14 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
* marked as fetched, and getOutOfBandMembers will return an empty array instead of null
|
* marked as fetched, and getOutOfBandMembers will return an empty array instead of null
|
||||||
* @param {string} roomId
|
* @param {string} roomId
|
||||||
* @param {event[]} membershipEvents the membership events to store
|
* @param {event[]} membershipEvents the membership events to store
|
||||||
* @returns {Promise} when all members have been stored
|
|
||||||
*/
|
*/
|
||||||
setOutOfBandMembers: function(roomId, membershipEvents) {
|
setOutOfBandMembers: async function(roomId, membershipEvents) {
|
||||||
console.log(`LL: backend about to store ${membershipEvents.length}` +
|
console.log(`LL: backend about to store ${membershipEvents.length}` +
|
||||||
` members for ${roomId}`);
|
` members for ${roomId}`);
|
||||||
function ignoreResult() {}
|
|
||||||
// run everything in a promise so anything that throws will reject
|
|
||||||
return new Promise((resolve) =>{
|
|
||||||
const tx = this.db.transaction(["oob_membership_events"], "readwrite");
|
const tx = this.db.transaction(["oob_membership_events"], "readwrite");
|
||||||
const store = tx.objectStore("oob_membership_events");
|
const store = tx.objectStore("oob_membership_events");
|
||||||
const eventPuts = membershipEvents.map((e) => {
|
membershipEvents.forEach((e) => {
|
||||||
const putPromise = promiseifyRequest(store.put(e));
|
store.put(e);
|
||||||
// ignoring the result makes sure we discard the IDB success event
|
|
||||||
// ASAP, and not create a potentially big array containing them
|
|
||||||
// unneccesarily later on by calling Promise.all.
|
|
||||||
return putPromise.then(ignoreResult);
|
|
||||||
});
|
});
|
||||||
// aside from all the events, we also write a marker object to the store
|
// aside from all the events, we also write a marker object to the store
|
||||||
// to mark the fact that OOB members have been written for this room.
|
// to mark the fact that OOB members have been written for this room.
|
||||||
@@ -281,15 +284,47 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
oob_written: true,
|
oob_written: true,
|
||||||
state_key: 0,
|
state_key: 0,
|
||||||
};
|
};
|
||||||
const markerPut = promiseifyRequest(store.put(markerObject));
|
store.put(markerObject);
|
||||||
const allPuts = eventPuts.concat(markerPut);
|
await txnAsPromise(tx);
|
||||||
// ignore the empty array Promise.all creates
|
|
||||||
// as this method should just resolve
|
|
||||||
// to undefined on success
|
|
||||||
resolve(Promise.all(allPuts).then(ignoreResult));
|
|
||||||
}).then(() => {
|
|
||||||
console.log(`LL: backend done storing for ${roomId}!`);
|
console.log(`LL: backend done storing for ${roomId}!`);
|
||||||
});
|
},
|
||||||
|
|
||||||
|
clearOutOfBandMembers: async function(roomId) {
|
||||||
|
// the approach to delete all members for a room
|
||||||
|
// is to get the min and max state key from the index
|
||||||
|
// for that room, and then delete between those
|
||||||
|
// keys in the store.
|
||||||
|
// this should be way faster than deleting every member
|
||||||
|
// individually for a large room.
|
||||||
|
const readTx = this.db.transaction(
|
||||||
|
["oob_membership_events"],
|
||||||
|
"readonly");
|
||||||
|
const store = readTx.objectStore("oob_membership_events");
|
||||||
|
const roomIndex = store.index("room");
|
||||||
|
const roomRange = IDBKeyRange.only(roomId);
|
||||||
|
|
||||||
|
const minStateKeyProm = reqAsCursorPromise(
|
||||||
|
roomIndex.openKeyCursor(roomRange, "next"),
|
||||||
|
).then((cursor) => cursor && cursor.primaryKey[1]);
|
||||||
|
const maxStateKeyProm = reqAsCursorPromise(
|
||||||
|
roomIndex.openKeyCursor(roomRange, "prev"),
|
||||||
|
).then((cursor) => cursor && cursor.primaryKey[1]);
|
||||||
|
const [minStateKey, maxStateKey] = await Promise.all(
|
||||||
|
[minStateKeyProm, maxStateKeyProm]);
|
||||||
|
|
||||||
|
const writeTx = this.db.transaction(
|
||||||
|
["oob_membership_events"],
|
||||||
|
"readwrite");
|
||||||
|
const writeStore = writeTx.objectStore("oob_membership_events");
|
||||||
|
const membersKeyRange = IDBKeyRange.bound(
|
||||||
|
[roomId, minStateKey],
|
||||||
|
[roomId, maxStateKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`LL: Deleting all users + marker in storage for ` +
|
||||||
|
`room ${roomId}, with key range:`,
|
||||||
|
[roomId, minStateKey], [roomId, maxStateKey]);
|
||||||
|
await reqAsPromise(writeStore.delete(membersKeyRange));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -389,7 +424,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
roomsData: roomsData,
|
roomsData: roomsData,
|
||||||
groupsData: groupsData,
|
groupsData: groupsData,
|
||||||
}); // put == UPSERT
|
}); // put == UPSERT
|
||||||
return promiseifyTxn(txn);
|
return txnAsPromise(txn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -406,7 +441,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
for (let i = 0; i < accountData.length; i++) {
|
for (let i = 0; i < accountData.length; i++) {
|
||||||
store.put(accountData[i]); // put == UPSERT
|
store.put(accountData[i]); // put == UPSERT
|
||||||
}
|
}
|
||||||
return promiseifyTxn(txn);
|
return txnAsPromise(txn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -428,7 +463,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
|||||||
event: tuple[1],
|
event: tuple[1],
|
||||||
}); // put == UPSERT
|
}); // put == UPSERT
|
||||||
}
|
}
|
||||||
return promiseifyTxn(txn);
|
return txnAsPromise(txn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ RemoteIndexedDBStoreBackend.prototype = {
|
|||||||
return this._doCmd('setOutOfBandMembers', [roomId, membershipEvents]);
|
return this._doCmd('setOutOfBandMembers', [roomId, membershipEvents]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearOutOfBandMembers: function(roomId) {
|
||||||
|
return this._doCmd('clearOutOfBandMembers', [roomId]);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all user presence events from the database. This is not cached.
|
* Load all user presence events from the database. This is not cached.
|
||||||
* @return {Promise<Object[]>} A list of presence events in their raw form.
|
* @return {Promise<Object[]>} A list of presence events in their raw form.
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ class IndexedDBStoreWorker {
|
|||||||
case 'getOutOfBandMembers':
|
case 'getOutOfBandMembers':
|
||||||
prom = this.backend.getOutOfBandMembers(msg.args[0]);
|
prom = this.backend.getOutOfBandMembers(msg.args[0]);
|
||||||
break;
|
break;
|
||||||
|
case 'clearOutOfBandMembers':
|
||||||
|
prom = this.backend.clearOutOfBandMembers(msg.args[0]);
|
||||||
|
break;
|
||||||
case 'setOutOfBandMembers':
|
case 'setOutOfBandMembers':
|
||||||
prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]);
|
prom = this.backend.setOutOfBandMembers(msg.args[0], msg.args[1]);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -242,4 +242,8 @@ IndexedDBStore.prototype.setOutOfBandMembers = function(roomId, membershipEvents
|
|||||||
return this.backend.setOutOfBandMembers(roomId, membershipEvents);
|
return this.backend.setOutOfBandMembers(roomId, membershipEvents);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IndexedDBStore.prototype.clearOutOfBandMembers = function(roomId) {
|
||||||
|
return this.backend.clearOutOfBandMembers(roomId);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports.IndexedDBStore = IndexedDBStore;
|
module.exports.IndexedDBStore = IndexedDBStore;
|
||||||
|
|||||||
@@ -272,6 +272,10 @@ StubStore.prototype = {
|
|||||||
setOutOfBandMembers: function() {
|
setOutOfBandMembers: function() {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearOutOfBandMembers: function() {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Stub Store class. */
|
/** Stub Store class. */
|
||||||
|
|||||||
29
src/sync.js
29
src/sync.js
@@ -779,7 +779,18 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) {
|
|||||||
// erroneous. We set the state to 'reconnecting'
|
// erroneous. We set the state to 'reconnecting'
|
||||||
// instead, so that clients can observe this state
|
// instead, so that clients can observe this state
|
||||||
// if they wish.
|
// if they wish.
|
||||||
this._startKeepAlives().then(() => {
|
this._startKeepAlives().then((connDidFail) => {
|
||||||
|
// Only emit CATCHUP if we detected a connectivity error: if we didn't,
|
||||||
|
// it's quite likely the sync will fail again for the same reason and we
|
||||||
|
// want to stay in ERROR rather than keep flip-flopping between ERROR
|
||||||
|
// and CATCHUP.
|
||||||
|
if (connDidFail && this.getSyncState() === 'ERROR') {
|
||||||
|
this._updateSyncState("CATCHUP", {
|
||||||
|
oldSyncToken: null,
|
||||||
|
nextSyncToken: null,
|
||||||
|
catchingUp: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
this._sync(syncOptions);
|
this._sync(syncOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1145,6 +1156,8 @@ SyncApi.prototype._processSyncResponse = async function(
|
|||||||
accountDataEvents.forEach(function(e) {
|
accountDataEvents.forEach(function(e) {
|
||||||
client.emit("event", e);
|
client.emit("event", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
room.onLeft();
|
||||||
});
|
});
|
||||||
|
|
||||||
// update the notification timeline, if appropriate.
|
// update the notification timeline, if appropriate.
|
||||||
@@ -1217,13 +1230,16 @@ SyncApi.prototype._startKeepAlives = function(delay) {
|
|||||||
*
|
*
|
||||||
* On failure, schedules a call back to itself. On success, resolves
|
* On failure, schedules a call back to itself. On success, resolves
|
||||||
* this._connectionReturnedDefer.
|
* this._connectionReturnedDefer.
|
||||||
|
*
|
||||||
|
* @param {bool} connDidFail True if a connectivity failure has been detected. Optional.
|
||||||
*/
|
*/
|
||||||
SyncApi.prototype._pokeKeepAlive = function() {
|
SyncApi.prototype._pokeKeepAlive = function(connDidFail) {
|
||||||
|
if (connDidFail === undefined) connDidFail = false;
|
||||||
const self = this;
|
const self = this;
|
||||||
function success() {
|
function success() {
|
||||||
clearTimeout(self._keepAliveTimer);
|
clearTimeout(self._keepAliveTimer);
|
||||||
if (self._connectionReturnedDefer) {
|
if (self._connectionReturnedDefer) {
|
||||||
self._connectionReturnedDefer.resolve();
|
self._connectionReturnedDefer.resolve(connDidFail);
|
||||||
self._connectionReturnedDefer = null;
|
self._connectionReturnedDefer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1240,7 +1256,7 @@ SyncApi.prototype._pokeKeepAlive = function() {
|
|||||||
).done(function() {
|
).done(function() {
|
||||||
success();
|
success();
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.httpStatus == 400) {
|
if (err.httpStatus == 400 || err.httpStatus == 404) {
|
||||||
// treat this as a success because the server probably just doesn't
|
// treat this as a success because the server probably just doesn't
|
||||||
// support /versions: point is, we're getting a response.
|
// support /versions: point is, we're getting a response.
|
||||||
// We wait a short time though, just in case somehow the server
|
// We wait a short time though, just in case somehow the server
|
||||||
@@ -1248,8 +1264,9 @@ SyncApi.prototype._pokeKeepAlive = function() {
|
|||||||
// responses fail, this will mean we don't hammer in a loop.
|
// responses fail, this will mean we don't hammer in a loop.
|
||||||
self._keepAliveTimer = setTimeout(success, 2000);
|
self._keepAliveTimer = setTimeout(success, 2000);
|
||||||
} else {
|
} else {
|
||||||
|
connDidFail = true;
|
||||||
self._keepAliveTimer = setTimeout(
|
self._keepAliveTimer = setTimeout(
|
||||||
self._pokeKeepAlive.bind(self),
|
self._pokeKeepAlive.bind(self, connDidFail),
|
||||||
5000 + Math.floor(Math.random() * 5000),
|
5000 + Math.floor(Math.random() * 5000),
|
||||||
);
|
);
|
||||||
// A keepalive has failed, so we emit the
|
// A keepalive has failed, so we emit the
|
||||||
@@ -1257,7 +1274,7 @@ SyncApi.prototype._pokeKeepAlive = function() {
|
|||||||
// first failure).
|
// first failure).
|
||||||
// Note we do this after setting the timer:
|
// Note we do this after setting the timer:
|
||||||
// this lets the unit tests advance the mock
|
// this lets the unit tests advance the mock
|
||||||
// clock when the get the error.
|
// clock when they get the error.
|
||||||
self._updateSyncState("ERROR", { error: err });
|
self._updateSyncState("ERROR", { error: err });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user