Compare commits
65 Commits
v5.3.1-rc.1
...
v6.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a1baf39299 | |||
| 9f0f1bcc68 | |||
| 246963e181 | |||
| e3134ab0de | |||
| b38d52da72 | |||
| 411c4f40d9 | |||
| 694a85b652 | |||
| c84e72f53a | |||
| 135a76febd | |||
| fae2856e7e | |||
| 98426b6186 | |||
| a63d9d2ccd | |||
| c567139e28 | |||
| f3f6f3e39a | |||
| f905586dbd | |||
| f393cea2c2 | |||
| e937998b40 | |||
| 7ff68f3844 | |||
| 04529bd524 | |||
| dca60ae4ae | |||
| ff46a8fa9e | |||
| a351ee9f76 | |||
| a5b14092cd | |||
| 90f1de9c3f | |||
| 7dc926596f | |||
| e775515c38 | |||
| c116f2b1bc | |||
| 1cb6134758 | |||
| a255b8e450 | |||
| 324f036b35 | |||
| 479a284e10 | |||
| e0660ce01c | |||
| 3a3f6cb7ca | |||
| 9797e6021b | |||
| a9212d33b1 | |||
| d32d3fd864 | |||
| a19cdd06cf | |||
| f4d0f89fda | |||
| 39836f115b | |||
| 7cc61a887c | |||
| 67fb027d39 | |||
| c6b61ea0ea | |||
| b3860f3754 | |||
| f42fa7e791 | |||
| b592c41f96 | |||
| 7b7fdb0a65 | |||
| be91c8580d | |||
| befaa782f6 | |||
| b3752bb2c6 | |||
| 02e145b0de | |||
| 26cae5543b | |||
| b0573dec77 | |||
| c5f4e762e5 | |||
| c3903f2796 | |||
| 18564553ad | |||
| fe3e87a9d6 | |||
| 78c734d2e9 | |||
| 5afa16d454 | |||
| 149f59e9be | |||
| 761892d6b1 | |||
| faf18b1996 | |||
| 218deddd00 | |||
| d6fe650c4c | |||
| 951191d99a | |||
| 2654f4bf0f |
@@ -1,3 +1,76 @@
|
||||
Changes in [6.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.0.0) (2020-05-05)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.0.0-rc.2...v6.0.0)
|
||||
|
||||
* Add progress callback for key backups
|
||||
[\#1368](https://github.com/matrix-org/matrix-js-sdk/pull/1368)
|
||||
|
||||
Changes in [6.0.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.0.0-rc.2) (2020-05-01)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.0.0-rc.1...v6.0.0-rc.2)
|
||||
|
||||
* Emit event when a trusted self-key is stored
|
||||
[\#1365](https://github.com/matrix-org/matrix-js-sdk/pull/1365)
|
||||
|
||||
Changes in [6.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.0.0-rc.1) (2020-04-30)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.3.1-rc.4...v6.0.0-rc.1)
|
||||
|
||||
BREAKING CHANGES
|
||||
---
|
||||
|
||||
* client.getStoredDevicesForUser and client.getStoredDevices are no longer async
|
||||
|
||||
All Changes
|
||||
---
|
||||
|
||||
* Add initialFetch param to willUpdateDevices / devicesUpdated
|
||||
[\#1362](https://github.com/matrix-org/matrix-js-sdk/pull/1362)
|
||||
* Fix race between sending .request and receiving .ready over to_device
|
||||
[\#1361](https://github.com/matrix-org/matrix-js-sdk/pull/1361)
|
||||
* Handle race between sending and await next event from other party
|
||||
[\#1358](https://github.com/matrix-org/matrix-js-sdk/pull/1358)
|
||||
* Add crypto.willUpdateDevices event and make
|
||||
getStoredDevices/getStoredDevicesForUser synchronous
|
||||
[\#1356](https://github.com/matrix-org/matrix-js-sdk/pull/1356)
|
||||
* Remove redundant key backup setup path
|
||||
[\#1355](https://github.com/matrix-org/matrix-js-sdk/pull/1355)
|
||||
|
||||
Changes in [5.3.1-rc.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.3.1-rc.4) (2020-04-23)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.3.1-rc.3...v5.3.1-rc.4)
|
||||
|
||||
* Retry account data upload requests
|
||||
[\#1347](https://github.com/matrix-org/matrix-js-sdk/pull/1347)
|
||||
* Fix: handle filter not found
|
||||
[\#1341](https://github.com/matrix-org/matrix-js-sdk/pull/1341)
|
||||
* Make getAccountDataFromServer return null if not found
|
||||
[\#1339](https://github.com/matrix-org/matrix-js-sdk/pull/1339)
|
||||
* Fix setDefaultKeyId to fail if the request fails
|
||||
[\#1337](https://github.com/matrix-org/matrix-js-sdk/pull/1337)
|
||||
* Fix: don't do extra /filter request when enabling lazy loading of members
|
||||
[\#1333](https://github.com/matrix-org/matrix-js-sdk/pull/1333)
|
||||
* Reject attemptAuth promise if no auth flow found
|
||||
[\#1331](https://github.com/matrix-org/matrix-js-sdk/pull/1331)
|
||||
* Serialise Olm prekey decryptions
|
||||
[\#1330](https://github.com/matrix-org/matrix-js-sdk/pull/1330)
|
||||
|
||||
Changes in [5.3.1-rc.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.3.1-rc.3) (2020-04-17)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.3.1-rc.2...v5.3.1-rc.3)
|
||||
|
||||
* Fix cross-signing/SSSS reset
|
||||
[\#1323](https://github.com/matrix-org/matrix-js-sdk/pull/1323)
|
||||
* Fix: crash when backup key needs fixing from corruption issue
|
||||
[\#1325](https://github.com/matrix-org/matrix-js-sdk/pull/1325)
|
||||
|
||||
Changes in [5.3.1-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.3.1-rc.2) (2020-04-16)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.3.1-rc.1...v5.3.1-rc.2)
|
||||
|
||||
* Implement QR code reciprocate for self-verification with untrusted MSK
|
||||
[\#1321](https://github.com/matrix-org/matrix-js-sdk/pull/1321)
|
||||
|
||||
Changes in [5.3.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.3.1-rc.1) (2020-04-15)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.3.0-rc.1...v5.3.1-rc.1)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "5.3.1-rc.1",
|
||||
"version": "6.0.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
|
||||
@@ -3,6 +3,7 @@ import HttpBackend from "matrix-mock-request";
|
||||
import {MatrixClient} from "../../src/matrix";
|
||||
import {MatrixScheduler} from "../../src/scheduler";
|
||||
import {MemoryStore} from "../../src/store/memory";
|
||||
import {MatrixError} from "../../src/http-api";
|
||||
|
||||
describe("MatrixClient opts", function() {
|
||||
const baseUrl = "http://localhost.or.something";
|
||||
@@ -132,10 +133,10 @@ describe("MatrixClient opts", function() {
|
||||
});
|
||||
|
||||
it("shouldn't retry sending events", function(done) {
|
||||
httpBackend.when("PUT", "/txn1").fail(500, {
|
||||
httpBackend.when("PUT", "/txn1").fail(500, new MatrixError({
|
||||
errcode: "M_SOMETHING",
|
||||
error: "Ruh roh",
|
||||
});
|
||||
}));
|
||||
client.sendTextMessage("!foo:bar", "a body", "txn1").then(function(res) {
|
||||
expect(false).toBe(true, "sendTextMessage resolved but shouldn't");
|
||||
}, function(err) {
|
||||
|
||||
@@ -143,4 +143,33 @@ describe("InteractiveAuth", function() {
|
||||
expect(stateUpdated).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should start an auth stage and reject if no auth flow", function() {
|
||||
const doRequest = jest.fn();
|
||||
const stateUpdated = jest.fn();
|
||||
|
||||
const ia = new InteractiveAuth({
|
||||
matrixClient: new FakeClient(),
|
||||
doRequest: doRequest,
|
||||
stateUpdated: stateUpdated,
|
||||
});
|
||||
|
||||
doRequest.mockImplementation(function(authData) {
|
||||
logger.log("request1", authData);
|
||||
expect(authData).toEqual({});
|
||||
const err = new MatrixError({
|
||||
session: "sessionId",
|
||||
flows: [],
|
||||
params: {
|
||||
"logintype": { param: "aa" },
|
||||
},
|
||||
});
|
||||
err.httpStatus = 401;
|
||||
throw err;
|
||||
});
|
||||
|
||||
return ia.attemptAuth().catch(function(error) {
|
||||
expect(error.message).toBe('No appropriate authentication flow found');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+93
-57
@@ -35,7 +35,12 @@ import {StubStore} from "./store/stub";
|
||||
import {createNewMatrixCall} from "./webrtc/call";
|
||||
import * as utils from './utils';
|
||||
import {sleep} from './utils';
|
||||
import {MatrixError, PREFIX_MEDIA_R0, PREFIX_UNSTABLE} from "./http-api";
|
||||
import {
|
||||
MatrixError,
|
||||
PREFIX_MEDIA_R0,
|
||||
PREFIX_UNSTABLE,
|
||||
retryNetworkOperation,
|
||||
} from "./http-api";
|
||||
import {getHttpUriForMxc} from "./content-repo";
|
||||
import * as ContentHelpers from "./content-helpers";
|
||||
import * as olmlib from "./crypto/olmlib";
|
||||
@@ -732,6 +737,7 @@ MatrixClient.prototype.initCrypto = async function() {
|
||||
"crypto.roomKeyRequestCancellation",
|
||||
"crypto.warning",
|
||||
"crypto.devicesUpdated",
|
||||
"crypto.willUpdateDevices",
|
||||
"deviceVerificationChanged",
|
||||
"userTrustStatusChanged",
|
||||
"crossSigning.keysChanged",
|
||||
@@ -820,9 +826,9 @@ MatrixClient.prototype.downloadKeys = function(userIds, forceDownload) {
|
||||
*
|
||||
* @param {string} userId the user to list keys for.
|
||||
*
|
||||
* @return {Promise<module:crypto/deviceinfo[]>} list of devices
|
||||
* @return {module:crypto/deviceinfo[]} list of devices
|
||||
*/
|
||||
MatrixClient.prototype.getStoredDevicesForUser = async function(userId) {
|
||||
MatrixClient.prototype.getStoredDevicesForUser = function(userId) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -835,9 +841,9 @@ MatrixClient.prototype.getStoredDevicesForUser = async function(userId) {
|
||||
* @param {string} userId the user to list keys for.
|
||||
* @param {string} deviceId unique identifier for the device
|
||||
*
|
||||
* @return {Promise<?module:crypto/deviceinfo>} device or null
|
||||
* @return {module:crypto/deviceinfo} device or null
|
||||
*/
|
||||
MatrixClient.prototype.getStoredDevice = async function(userId, deviceId) {
|
||||
MatrixClient.prototype.getStoredDevice = function(userId, deviceId) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -1430,15 +1436,17 @@ MatrixClient.prototype.exportRoomKeys = function() {
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
* @param {Object} opts
|
||||
* @param {Function} opts.progressCallback called with an object that has a "stage" param
|
||||
*
|
||||
* @return {Promise} a promise which resolves when the keys
|
||||
* have been imported
|
||||
*/
|
||||
MatrixClient.prototype.importRoomKeys = function(keys) {
|
||||
MatrixClient.prototype.importRoomKeys = function(keys, opts) {
|
||||
if (!this._crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
return this._crypto.importRoomKeys(keys);
|
||||
return this._crypto.importRoomKeys(keys, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1822,7 +1830,7 @@ MatrixClient.prototype.restoreKeyBackupWithSecretStorage = async function(
|
||||
// store the fixed version
|
||||
const fixedKey = fixBackupKey(storedKey);
|
||||
if (fixedKey) {
|
||||
const [keyId] = await this.getSecretStorageKey();
|
||||
const [keyId] = await this._crypto.getSecretStorageKey();
|
||||
await this.storeSecret("m.megolm_backup.v1", fixedKey, [keyId]);
|
||||
}
|
||||
|
||||
@@ -1881,7 +1889,10 @@ MatrixClient.prototype.restoreKeyBackupWithCache = async function(
|
||||
|
||||
MatrixClient.prototype._restoreKeyBackup = function(
|
||||
privKey, targetRoomId, targetSessionId, backupInfo,
|
||||
{ cacheCompleteCallback }={}, // For sequencing during tests
|
||||
{
|
||||
cacheCompleteCallback, // For sequencing during tests
|
||||
progressCallback,
|
||||
}={},
|
||||
) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
@@ -1916,6 +1927,12 @@ MatrixClient.prototype._restoreKeyBackup = function(
|
||||
console.warn("Error caching session backup key:", e);
|
||||
}).then(cacheCompleteCallback);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
stage: "fetch",
|
||||
});
|
||||
}
|
||||
|
||||
return this._http.authedRequest(
|
||||
undefined, "GET", path.path, path.queryData, undefined,
|
||||
{prefix: PREFIX_UNSTABLE},
|
||||
@@ -1950,7 +1967,7 @@ MatrixClient.prototype._restoreKeyBackup = function(
|
||||
}
|
||||
}
|
||||
|
||||
return this.importRoomKeys(keys);
|
||||
return this.importRoomKeys(keys, { progressCallback });
|
||||
}).then(() => {
|
||||
return this._crypto.setTrustedBackupPubKey(backupPubKey);
|
||||
}).then(() => {
|
||||
@@ -2086,6 +2103,7 @@ MatrixClient.prototype.getUsers = function() {
|
||||
|
||||
/**
|
||||
* Set account data event for the current user.
|
||||
* It will retry the request up to 5 times.
|
||||
* @param {string} eventType The event type
|
||||
* @param {Object} contents the contents object for the event
|
||||
* @param {module:client.callback} callback Optional.
|
||||
@@ -2097,9 +2115,13 @@ MatrixClient.prototype.setAccountData = function(eventType, contents, callback)
|
||||
$userId: this.credentials.userId,
|
||||
$type: eventType,
|
||||
});
|
||||
return this._http.authedRequest(
|
||||
callback, "PUT", path, undefined, contents,
|
||||
);
|
||||
const promise = retryNetworkOperation(5, () => {
|
||||
return this._http.authedRequest(undefined, "PUT", path, undefined, contents);
|
||||
});
|
||||
if (callback) {
|
||||
promise.then(result => callback(null, result), callback);
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2134,9 +2156,17 @@ MatrixClient.prototype.getAccountDataFromServer = async function(eventType) {
|
||||
$userId: this.credentials.userId,
|
||||
$type: eventType,
|
||||
});
|
||||
return this._http.authedRequest(
|
||||
undefined, "GET", path, undefined,
|
||||
);
|
||||
try {
|
||||
const result = await this._http.authedRequest(
|
||||
undefined, "GET", path, undefined,
|
||||
);
|
||||
return result;
|
||||
} catch (e) {
|
||||
if (e.data && e.data.errcode === 'M_NOT_FOUND') {
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -4510,64 +4540,54 @@ MatrixClient.prototype.getFilter = function(userId, filterId, allowCached) {
|
||||
* @param {Filter} filter
|
||||
* @return {Promise<String>} Filter ID
|
||||
*/
|
||||
MatrixClient.prototype.getOrCreateFilter = function(filterName, filter) {
|
||||
MatrixClient.prototype.getOrCreateFilter = async function(filterName, filter) {
|
||||
const filterId = this.store.getFilterIdByName(filterName);
|
||||
let promise = Promise.resolve();
|
||||
const self = this;
|
||||
let existingId = undefined;
|
||||
|
||||
if (filterId) {
|
||||
// check that the existing filter matches our expectations
|
||||
promise = self.getFilter(self.credentials.userId,
|
||||
filterId, true,
|
||||
).then(function(existingFilter) {
|
||||
const oldDef = existingFilter.getDefinition();
|
||||
const newDef = filter.getDefinition();
|
||||
try {
|
||||
const existingFilter =
|
||||
await this.getFilter(this.credentials.userId, filterId, true);
|
||||
if (existingFilter) {
|
||||
const oldDef = existingFilter.getDefinition();
|
||||
const newDef = filter.getDefinition();
|
||||
|
||||
if (utils.deepCompare(oldDef, newDef)) {
|
||||
// super, just use that.
|
||||
// debuglog("Using existing filter ID %s: %s", filterId,
|
||||
// JSON.stringify(oldDef));
|
||||
return Promise.resolve(filterId);
|
||||
if (utils.deepCompare(oldDef, newDef)) {
|
||||
// super, just use that.
|
||||
// debuglog("Using existing filter ID %s: %s", filterId,
|
||||
// JSON.stringify(oldDef));
|
||||
existingId = filterId;
|
||||
}
|
||||
}
|
||||
// debuglog("Existing filter ID %s: %s; new filter: %s",
|
||||
// filterId, JSON.stringify(oldDef), JSON.stringify(newDef));
|
||||
self.store.setFilterIdByName(filterName, undefined);
|
||||
return undefined;
|
||||
}, function(error) {
|
||||
} catch (error) {
|
||||
// Synapse currently returns the following when the filter cannot be found:
|
||||
// {
|
||||
// errcode: "M_UNKNOWN",
|
||||
// name: "M_UNKNOWN",
|
||||
// message: "No row found",
|
||||
// data: Object, httpStatus: 404
|
||||
// }
|
||||
if (error.httpStatus === 404 &&
|
||||
(error.errcode === "M_UNKNOWN" || error.errcode === "M_NOT_FOUND")) {
|
||||
// Clear existing filterId from localStorage
|
||||
// if it no longer exists on the server
|
||||
self.store.setFilterIdByName(filterName, undefined);
|
||||
// Return a undefined value for existingId further down the promise chain
|
||||
return undefined;
|
||||
} else {
|
||||
if (error.errcode !== "M_UNKNOWN" && error.errcode !== "M_NOT_FOUND") {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
// if the filter doesn't exist anymore on the server, remove from store
|
||||
if (!existingId) {
|
||||
this.store.setFilterIdByName(filterName, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(function(existingId) {
|
||||
if (existingId) {
|
||||
return existingId;
|
||||
}
|
||||
if (existingId) {
|
||||
return existingId;
|
||||
}
|
||||
|
||||
// create a new filter
|
||||
return self.createFilter(filter.getDefinition(),
|
||||
).then(function(createdFilter) {
|
||||
// debuglog("Created new filter ID %s: %s", createdFilter.filterId,
|
||||
// JSON.stringify(createdFilter.getDefinition()));
|
||||
self.store.setFilterIdByName(filterName, createdFilter.filterId);
|
||||
return createdFilter.filterId;
|
||||
});
|
||||
});
|
||||
// create a new filter
|
||||
const createdFilter = await this.createFilter(filter.getDefinition());
|
||||
|
||||
// debuglog("Created new filter ID %s: %s", createdFilter.filterId,
|
||||
// JSON.stringify(createdFilter.getDefinition()));
|
||||
this.store.setFilterIdByName(filterName, createdFilter.filterId);
|
||||
return createdFilter.filterId;
|
||||
};
|
||||
|
||||
|
||||
@@ -5578,6 +5598,22 @@ MatrixClient.prototype.generateClientSecret = function() {
|
||||
* });
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires whenever the stored devices for a user have changed
|
||||
* @event module:client~MatrixClient#"crypto.devicesUpdated"
|
||||
* @param {String[]} users A list of user IDs that were updated
|
||||
* @param {bool} initialFetch If true, the store was empty (apart
|
||||
* from our own device) and has been seeded.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires whenever the stored devices for a user will be updated
|
||||
* @event module:client~MatrixClient#"crypto.willUpdateDevices"
|
||||
* @param {String[]} users A list of user IDs that will be updated
|
||||
* @param {bool} initialFetch If true, the store is empty (apart
|
||||
* from our own device) and is being seeded.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires whenever the status of e2e key backup changes, as returned by getKeyBackupEnabled()
|
||||
* @event module:client~MatrixClient#"crypto.keyBackupStatus"
|
||||
|
||||
@@ -109,6 +109,9 @@ export class DeviceList extends EventEmitter {
|
||||
this._savePromiseTime = null;
|
||||
// The timer used to delay the save
|
||||
this._saveTimer = null;
|
||||
// True if we have fetched data from the server or loaded a non-empty
|
||||
// set of device data from the store
|
||||
this._hasFetched = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,6 +121,7 @@ export class DeviceList extends EventEmitter {
|
||||
await this._cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||
this._hasFetched = Boolean(deviceData && deviceData.devices);
|
||||
this._devices = deviceData ? deviceData.devices : {},
|
||||
this._crossSigningInfo = deviceData ?
|
||||
deviceData.crossSigningInfo || {} : {};
|
||||
@@ -652,6 +656,7 @@ export class DeviceList extends EventEmitter {
|
||||
});
|
||||
|
||||
const finished = (success) => {
|
||||
this.emit("crypto.willUpdateDevices", users, !this._hasFetched);
|
||||
users.forEach((u) => {
|
||||
this._dirty = true;
|
||||
|
||||
@@ -677,7 +682,8 @@ export class DeviceList extends EventEmitter {
|
||||
}
|
||||
});
|
||||
this.saveIfDirty();
|
||||
this.emit("crypto.devicesUpdated", users);
|
||||
this.emit("crypto.devicesUpdated", users, !this._hasFetched);
|
||||
this._hasFetched = true;
|
||||
};
|
||||
|
||||
return prom;
|
||||
|
||||
@@ -105,6 +105,9 @@ export function OlmDevice(cryptoStore) {
|
||||
// Keep track of sessions that we're starting, so that we don't start
|
||||
// multiple sessions for the same device at the same time.
|
||||
this._sessionsInProgress = {};
|
||||
|
||||
// Used by olm to serialise prekey message decryptions
|
||||
this._olmPrekeyPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,7 +53,7 @@ export class SecretStorage extends EventEmitter {
|
||||
}
|
||||
|
||||
setDefaultKeyId(keyId) {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const listener = (ev) => {
|
||||
if (
|
||||
ev.getType() === 'm.secret_storage.default_key' &&
|
||||
@@ -65,10 +65,15 @@ export class SecretStorage extends EventEmitter {
|
||||
};
|
||||
this._baseApis.on('accountData', listener);
|
||||
|
||||
this._baseApis.setAccountData(
|
||||
'm.secret_storage.default_key',
|
||||
{ key: keyId },
|
||||
);
|
||||
try {
|
||||
await this._baseApis.setAccountData(
|
||||
'm.secret_storage.default_key',
|
||||
{ key: keyId },
|
||||
);
|
||||
} catch (e) {
|
||||
this._baseApis.removeListener('accountData', listener);
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -592,7 +597,7 @@ export class SecretStorage extends EventEmitter {
|
||||
this._baseApis,
|
||||
{
|
||||
[sender]: [
|
||||
await this._baseApis.getStoredDevice(sender, deviceId),
|
||||
this._baseApis.getStoredDevice(sender, deviceId),
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -602,7 +607,7 @@ export class SecretStorage extends EventEmitter {
|
||||
this._baseApis.deviceId,
|
||||
this._baseApis._crypto._olmDevice,
|
||||
sender,
|
||||
this._baseApis._crypto.getStoredDevice(sender, deviceId),
|
||||
this._baseApis.getStoredDevice(sender, deviceId),
|
||||
payload,
|
||||
);
|
||||
const contentMap = {
|
||||
|
||||
@@ -264,6 +264,25 @@ OlmDecryption.prototype.decryptEvent = async function(event) {
|
||||
*/
|
||||
OlmDecryption.prototype._decryptMessage = async function(
|
||||
theirDeviceIdentityKey, message,
|
||||
) {
|
||||
// This is a wrapper that serialises decryptions of prekey messages, because
|
||||
// otherwise we race between deciding we have no active sessions for the message
|
||||
// and creating a new one, which we can only do once because it removes the OTK.
|
||||
if (message.type !== 0) {
|
||||
// not a prekey message: we can safely just try & decrypt it
|
||||
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
|
||||
} else {
|
||||
const myPromise = this._olmDevice._olmPrekeyPromise.then(() => {
|
||||
return this._reallyDecryptMessage(theirDeviceIdentityKey, message);
|
||||
});
|
||||
// we want the error, but don't propagate it to the next decryption
|
||||
this._olmDevice._olmPrekeyPromise = myPromise.catch(() => {});
|
||||
return await myPromise;
|
||||
}
|
||||
};
|
||||
|
||||
OlmDecryption.prototype._reallyDecryptMessage = async function(
|
||||
theirDeviceIdentityKey, message,
|
||||
) {
|
||||
const sessionIds = await this._olmDevice.getSessionIdsForDevice(
|
||||
theirDeviceIdentityKey,
|
||||
|
||||
+44
-14
@@ -169,7 +169,9 @@ export function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||
this._deviceList.on(
|
||||
'userCrossSigningUpdated', this._onDeviceListUserCrossSigningUpdated,
|
||||
);
|
||||
this._reEmitter.reEmit(this._deviceList, ["crypto.devicesUpdated"]);
|
||||
this._reEmitter.reEmit(this._deviceList, [
|
||||
"crypto.devicesUpdated", "crypto.willUpdateDevices",
|
||||
]);
|
||||
|
||||
// the last time we did a check for the number of one-time-keys on the
|
||||
// server.
|
||||
@@ -579,9 +581,16 @@ Crypto.prototype.bootstrapSecretStorage = async function({
|
||||
|
||||
await resetCrossSigning();
|
||||
|
||||
if (!oldKeyInfo || oldKeyInfo.algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
// if we already have a usable default SSSS key, just use it.
|
||||
if (
|
||||
setupNewSecretStorage ||
|
||||
!oldKeyInfo ||
|
||||
oldKeyInfo.algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES
|
||||
) {
|
||||
// if we already have a usable default SSSS key and aren't resetting SSSS just use it.
|
||||
// otherwise, create a new one
|
||||
// Note: we leave the old SSSS key in place: there could be other secrets using it, in theory.
|
||||
// We could move them to the new key but a) that would mean we'd need to prompt for the old
|
||||
// passphrase, and b) it's not clear that would be the right thing to do anyway.
|
||||
const { keyInfo, privateKey } = await createSecretStorageKey();
|
||||
newKeyId = await createSSSS(keyInfo, privateKey);
|
||||
}
|
||||
@@ -589,14 +598,6 @@ Crypto.prototype.bootstrapSecretStorage = async function({
|
||||
if (oldKeyInfo && oldKeyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
await ensureCanCheckPassphrase(oldKeyId, oldKeyInfo);
|
||||
}
|
||||
|
||||
if (setupNewKeyBackup) {
|
||||
const info = await this._baseApis.prepareKeyBackupVersion(
|
||||
null /* random key */,
|
||||
{ secureSecretStorage: true },
|
||||
);
|
||||
await this._baseApis.createKeyBackupVersion(info);
|
||||
}
|
||||
} else if (!inStorage && keyBackupInfo) {
|
||||
// we have an existing backup, but no SSSS
|
||||
|
||||
@@ -2020,6 +2021,10 @@ Crypto.prototype.setDeviceVerification = async function(
|
||||
|
||||
if (!this._crossSigningInfo.getId() && userId === this._crossSigningInfo.userId) {
|
||||
this._storeTrustedSelfKeys(xsk.keys);
|
||||
// This will cause our own user trust to change, so emit the event
|
||||
this.emit(
|
||||
"userTrustStatusChanged", this._userId, this.checkUserTrust(userId),
|
||||
);
|
||||
}
|
||||
|
||||
// Now sign the master key with our user signing key (unless it's ourself)
|
||||
@@ -2162,7 +2167,8 @@ Crypto.prototype.requestVerification = function(userId, devices) {
|
||||
if (existingRequest) {
|
||||
return Promise.resolve(existingRequest);
|
||||
}
|
||||
const channel = new ToDeviceChannel(this._baseApis, userId, devices);
|
||||
const channel = new ToDeviceChannel(this._baseApis, userId, devices,
|
||||
ToDeviceChannel.makeTransactionId());
|
||||
return this._requestVerificationWithChannel(
|
||||
userId,
|
||||
channel,
|
||||
@@ -2175,6 +2181,10 @@ Crypto.prototype._requestVerificationWithChannel = async function(
|
||||
) {
|
||||
let request = new VerificationRequest(
|
||||
channel, this._verificationMethods, this._baseApis);
|
||||
// if transaction id is already known, add request
|
||||
if (channel.transactionId) {
|
||||
requestsMap.setRequestByChannel(channel, request);
|
||||
}
|
||||
await request.sendRequest();
|
||||
// don't replace the request created by a racing remote echo
|
||||
const racingRequest = requestsMap.getRequestByChannel(channel);
|
||||
@@ -2542,17 +2552,37 @@ Crypto.prototype.exportRoomKeys = async function() {
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
* @param {Object} opts
|
||||
* @param {Function} opts.progressCallback called with an object which has a stage param
|
||||
* @return {Promise} a promise which resolves once the keys have been imported
|
||||
*/
|
||||
Crypto.prototype.importRoomKeys = function(keys) {
|
||||
Crypto.prototype.importRoomKeys = function(keys, opts = {}) {
|
||||
let successes = 0;
|
||||
let failures = 0;
|
||||
const total = keys.length;
|
||||
|
||||
function updateProgress() {
|
||||
opts.progressCallback({
|
||||
stage: "load_keys",
|
||||
successes,
|
||||
failures,
|
||||
total,
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all(keys.map((key) => {
|
||||
if (!key.room_id || !key.algorithm) {
|
||||
logger.warn("ignoring room key entry with missing fields", key);
|
||||
failures++;
|
||||
if (opts.progressCallback) { updateProgress(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
const alg = this._getRoomDecryptor(key.room_id, key.algorithm);
|
||||
return alg.importRoomKey(key);
|
||||
return alg.importRoomKey(key).finally((r) => {
|
||||
successes++;
|
||||
if (opts.progressCallback) { updateProgress(); }
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
@@ -122,6 +122,11 @@ export class VerificationBase extends EventEmitter {
|
||||
if (this._done) {
|
||||
return Promise.reject(new Error("Verification is already done"));
|
||||
}
|
||||
const existingEvent = this.request.getEventFromOtherParty(type);
|
||||
if (existingEvent) {
|
||||
return Promise.resolve(existingEvent);
|
||||
}
|
||||
|
||||
this._expectedEvent = type;
|
||||
return new Promise((resolve, reject) => {
|
||||
this._resolveEvent = resolve;
|
||||
@@ -370,7 +375,7 @@ export class VerificationBase extends EventEmitter {
|
||||
|
||||
for (const [keyId, keyInfo] of Object.entries(keys)) {
|
||||
const deviceId = keyId.split(':', 2)[1];
|
||||
const device = await this._baseApis.getStoredDevice(userId, deviceId);
|
||||
const device = this._baseApis.getStoredDevice(userId, deviceId);
|
||||
if (device) {
|
||||
await verifier(keyId, device, keyInfo);
|
||||
verifiedDevices.push(deviceId);
|
||||
|
||||
@@ -65,20 +65,29 @@ export class ReciprocateQRCode extends Base {
|
||||
this.emit("show_reciprocate_qr", this.reciprocateQREvent);
|
||||
});
|
||||
|
||||
// 3. determine key to sign
|
||||
// 3. determine key to sign / mark as trusted
|
||||
const keys = {};
|
||||
if (qrCodeData.mode === MODE_VERIFY_OTHER_USER) {
|
||||
// add master key to keys to be signed, only if we're not doing self-verification
|
||||
const masterKey = qrCodeData.otherUserMasterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey;
|
||||
} else if (qrCodeData.mode === MODE_VERIFY_SELF_TRUSTED) {
|
||||
const deviceId = this.request.targetDevice.deviceId;
|
||||
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey;
|
||||
} else {
|
||||
// TODO: not sure if MODE_VERIFY_SELF_UNTRUSTED makes sense to sign anything here?
|
||||
|
||||
switch (qrCodeData.mode) {
|
||||
case MODE_VERIFY_OTHER_USER: {
|
||||
// add master key to keys to be signed, only if we're not doing self-verification
|
||||
const masterKey = qrCodeData.otherUserMasterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey;
|
||||
break;
|
||||
}
|
||||
case MODE_VERIFY_SELF_TRUSTED: {
|
||||
const deviceId = this.request.targetDevice.deviceId;
|
||||
keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey;
|
||||
break;
|
||||
}
|
||||
case MODE_VERIFY_SELF_UNTRUSTED: {
|
||||
const masterKey = qrCodeData.myMasterKey;
|
||||
keys[`ed25519:${masterKey}`] = masterKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. sign the key
|
||||
// 4. sign the key (or mark own MSK as verified in case of MODE_VERIFY_SELF_TRUSTED)
|
||||
await this._verifyKeys(this.userId, keys, (keyId, device, keyInfo) => {
|
||||
// make sure the device has the expected keys
|
||||
const targetKey = keys[keyId];
|
||||
@@ -108,11 +117,15 @@ const MODE_VERIFY_SELF_TRUSTED = 0x01; // We trust the master key
|
||||
const MODE_VERIFY_SELF_UNTRUSTED = 0x02; // We do not trust the master key
|
||||
|
||||
export class QRCodeData {
|
||||
constructor(mode, sharedSecret, otherUserMasterKey, otherDeviceKey, buffer) {
|
||||
constructor(
|
||||
mode, sharedSecret, otherUserMasterKey,
|
||||
otherDeviceKey, myMasterKey, buffer,
|
||||
) {
|
||||
this._sharedSecret = sharedSecret;
|
||||
this._mode = mode;
|
||||
this._otherUserMasterKey = otherUserMasterKey;
|
||||
this._otherDeviceKey = otherDeviceKey;
|
||||
this._myMasterKey = myMasterKey;
|
||||
this._buffer = buffer;
|
||||
}
|
||||
|
||||
@@ -121,22 +134,28 @@ export class QRCodeData {
|
||||
const mode = QRCodeData._determineMode(request, client);
|
||||
let otherUserMasterKey = null;
|
||||
let otherDeviceKey = null;
|
||||
let myMasterKey = null;
|
||||
if (mode === MODE_VERIFY_OTHER_USER) {
|
||||
const otherUserCrossSigningInfo =
|
||||
client.getStoredCrossSigningForUser(request.otherUserId);
|
||||
otherUserMasterKey = otherUserCrossSigningInfo.getId("master");
|
||||
} else if (mode === MODE_VERIFY_SELF_TRUSTED) {
|
||||
otherDeviceKey = await QRCodeData._getOtherDeviceKey(request, client);
|
||||
} else if (mode === MODE_VERIFY_SELF_UNTRUSTED) {
|
||||
const myUserId = client.getUserId();
|
||||
const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
|
||||
myMasterKey = myCrossSigningInfo.getId("master");
|
||||
}
|
||||
const qrData = QRCodeData._generateQrData(
|
||||
request, client, mode,
|
||||
sharedSecret,
|
||||
otherUserMasterKey,
|
||||
otherDeviceKey,
|
||||
myMasterKey,
|
||||
);
|
||||
const buffer = QRCodeData._generateBuffer(qrData);
|
||||
return new QRCodeData(mode, sharedSecret,
|
||||
otherUserMasterKey, otherDeviceKey, buffer);
|
||||
otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
|
||||
}
|
||||
|
||||
get buffer() {
|
||||
@@ -147,14 +166,30 @@ export class QRCodeData {
|
||||
return this._mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* only set when mode is MODE_VERIFY_SELF_TRUSTED
|
||||
* @return {string} device key of other party at time of generating QR code
|
||||
*/
|
||||
get otherDeviceKey() {
|
||||
return this._otherDeviceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* only set when mode is MODE_VERIFY_OTHER_USER
|
||||
* @return {string} master key of other party at time of generating QR code
|
||||
*/
|
||||
get otherUserMasterKey() {
|
||||
return this._otherUserMasterKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* only set when mode is MODE_VERIFY_SELF_UNTRUSTED
|
||||
* @return {string} own master key at time of generating QR code
|
||||
*/
|
||||
get myMasterKey() {
|
||||
return this._myMasterKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unpadded base64 encoded shared secret.
|
||||
*/
|
||||
@@ -172,7 +207,7 @@ export class QRCodeData {
|
||||
const myUserId = client.getUserId();
|
||||
const otherDevice = request.targetDevice;
|
||||
const otherDeviceId = otherDevice ? otherDevice.deviceId : null;
|
||||
const device = await client.getStoredDevice(myUserId, otherDeviceId);
|
||||
const device = client.getStoredDevice(myUserId, otherDeviceId);
|
||||
if (!device) {
|
||||
throw new Error("could not find device " + otherDeviceId);
|
||||
}
|
||||
@@ -198,7 +233,8 @@ export class QRCodeData {
|
||||
}
|
||||
|
||||
static _generateQrData(request, client, mode,
|
||||
encodedSharedSecret, otherUserMasterKey, otherDeviceKey,
|
||||
encodedSharedSecret, otherUserMasterKey,
|
||||
otherDeviceKey, myMasterKey,
|
||||
) {
|
||||
const myUserId = client.getUserId();
|
||||
const transactionId = request.channel.transactionId;
|
||||
@@ -213,16 +249,15 @@ export class QRCodeData {
|
||||
};
|
||||
|
||||
const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
|
||||
const myMasterKey = myCrossSigningInfo.getId("master");
|
||||
|
||||
if (mode === MODE_VERIFY_OTHER_USER) {
|
||||
// First key is our master cross signing key
|
||||
qrData.firstKeyB64 = myMasterKey;
|
||||
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
// Second key is the other user's master cross signing key
|
||||
qrData.secondKeyB64 = otherUserMasterKey;
|
||||
} else if (mode === MODE_VERIFY_SELF_TRUSTED) {
|
||||
// First key is our master cross signing key
|
||||
qrData.firstKeyB64 = myMasterKey;
|
||||
qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
|
||||
qrData.secondKeyB64 = otherDeviceKey;
|
||||
} else if (mode === MODE_VERIFY_SELF_UNTRUSTED) {
|
||||
// First key is our device's key
|
||||
|
||||
@@ -877,4 +877,8 @@ export class VerificationRequest extends EventEmitter {
|
||||
this._setPhase(newTransitions[newTransitions.length - 1].phase);
|
||||
}
|
||||
}
|
||||
|
||||
getEventFromOtherParty(type) {
|
||||
return this._eventsByThem.get(type);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -56,13 +56,6 @@ Filter.LAZY_LOADING_MESSAGES_FILTER = {
|
||||
lazy_load_members: true,
|
||||
};
|
||||
|
||||
Filter.LAZY_LOADING_SYNC_FILTER = {
|
||||
room: {
|
||||
state: Filter.LAZY_LOADING_MESSAGES_FILTER,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the ID of this filter on your homeserver (if known)
|
||||
* @return {?Number} The filter ID
|
||||
@@ -96,6 +89,7 @@ Filter.prototype.setDefinition = function(definition) {
|
||||
// "state": {
|
||||
// "types": ["m.room.*"],
|
||||
// "not_rooms": ["!726s6s6q:example.com"],
|
||||
// "lazy_load_members": true,
|
||||
// },
|
||||
// "timeline": {
|
||||
// "limit": 10,
|
||||
@@ -177,6 +171,10 @@ Filter.prototype.setTimelineLimit = function(limit) {
|
||||
setProp(this.definition, "room.timeline.limit", limit);
|
||||
};
|
||||
|
||||
Filter.prototype.setLazyLoadMembers = function(enabled) {
|
||||
setProp(this.definition, "room.state.lazy_load_members", !!enabled);
|
||||
};
|
||||
|
||||
/**
|
||||
* Control whether left rooms should be included in responses.
|
||||
* @param {boolean} includeLeave True to make rooms the user has left appear
|
||||
|
||||
+86
-8
@@ -276,6 +276,9 @@ MatrixHttpApi.prototype = {
|
||||
callbacks.clearTimeout(xhr.timeout_timer);
|
||||
var resp;
|
||||
try {
|
||||
if (xhr.status === 0) {
|
||||
throw new AbortError();
|
||||
}
|
||||
if (!xhr.responseText) {
|
||||
throw new Error('No response body.');
|
||||
}
|
||||
@@ -789,6 +792,17 @@ const requestCallback = function(
|
||||
userDefinedCallback = userDefinedCallback || function() {};
|
||||
|
||||
return function(err, response, body) {
|
||||
if (err) {
|
||||
// the unit tests use matrix-mock-request, which throw the string "aborted" when aborting a request.
|
||||
// See https://github.com/matrix-org/matrix-mock-request/blob/3276d0263a561b5b8326b47bae720578a2c7473a/src/index.js#L48
|
||||
const aborted = err.name === "AbortError" || err === "aborted";
|
||||
if (!aborted && !(err instanceof MatrixError)) {
|
||||
// browser-request just throws normal Error objects,
|
||||
// not `TypeError`s like fetch does. So just assume any
|
||||
// error is due to the connection.
|
||||
err = new ConnectionError("request failed", err);
|
||||
}
|
||||
}
|
||||
if (!err) {
|
||||
try {
|
||||
if (response.statusCode >= 400) {
|
||||
@@ -892,12 +906,76 @@ function getResponseContentType(response) {
|
||||
* @prop {Object} data The raw Matrix error JSON used to construct this object.
|
||||
* @prop {integer} httpStatus The numeric HTTP status code given
|
||||
*/
|
||||
export function MatrixError(errorJson) {
|
||||
errorJson = errorJson || {};
|
||||
this.errcode = errorJson.errcode;
|
||||
this.name = errorJson.errcode || "Unknown error code";
|
||||
this.message = errorJson.error || "Unknown message";
|
||||
this.data = errorJson;
|
||||
export class MatrixError extends Error {
|
||||
constructor(errorJson) {
|
||||
errorJson = errorJson || {};
|
||||
super(`MatrixError: ${errorJson.errcode}`);
|
||||
this.errcode = errorJson.errcode;
|
||||
this.name = errorJson.errcode || "Unknown error code";
|
||||
this.message = errorJson.error || "Unknown message";
|
||||
this.data = errorJson;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a ConnectionError. This is a JavaScript Error indicating
|
||||
* that a request failed because of some error with the connection, either
|
||||
* CORS was not correctly configured on the server, the server didn't response,
|
||||
* the request timed out, or the internet connection on the client side went down.
|
||||
* @constructor
|
||||
*/
|
||||
export class ConnectionError extends Error {
|
||||
constructor(message, cause = undefined) {
|
||||
super(message + (cause ? `: ${cause.message}` : ""));
|
||||
this._cause = cause;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "ConnectionError";
|
||||
}
|
||||
|
||||
get cause() {
|
||||
return this._cause;
|
||||
}
|
||||
}
|
||||
|
||||
export class AbortError extends Error {
|
||||
constructor() {
|
||||
super("Operation aborted");
|
||||
}
|
||||
|
||||
get name() {
|
||||
return "AbortError";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries a network operation run in a callback.
|
||||
* @param {number} maxAttempts maximum attempts to try
|
||||
* @param {Function} callback callback that returns a promise of the network operation. If rejected with ConnectionError, it will be retried by calling the callback again.
|
||||
* @return {any} the result of the network operation
|
||||
* @throws {ConnectionError} If after maxAttempts the callback still throws ConnectionError
|
||||
*/
|
||||
export async function retryNetworkOperation(maxAttempts, callback) {
|
||||
let attempts = 0;
|
||||
let lastConnectionError = null;
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
if (attempts > 0) {
|
||||
const timeout = 1000 * Math.pow(2, attempts);
|
||||
console.log(`network operation failed ${attempts} times,` +
|
||||
` retrying in ${timeout}ms...`);
|
||||
await new Promise(r => setTimeout(r, timeout));
|
||||
}
|
||||
return await callback();
|
||||
} catch (err) {
|
||||
if (err instanceof ConnectionError) {
|
||||
attempts += 1;
|
||||
lastConnectionError = err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastConnectionError;
|
||||
}
|
||||
MatrixError.prototype = Object.create(Error.prototype);
|
||||
MatrixError.prototype.constructor = MatrixError;
|
||||
|
||||
@@ -351,7 +351,13 @@ InteractiveAuth.prototype = {
|
||||
error.data.session = this._data.session;
|
||||
}
|
||||
this._data = error.data;
|
||||
this._startNextAuthStage();
|
||||
try {
|
||||
this._startNextAuthStage();
|
||||
} catch (e) {
|
||||
this._rejectFunc(e);
|
||||
this._resolveFunc = null;
|
||||
this._rejectFunc = null;
|
||||
}
|
||||
|
||||
if (
|
||||
!this._emailSid &&
|
||||
|
||||
+1
-1
@@ -675,7 +675,7 @@ Room.prototype.hasUnverifiedDevices = async function() {
|
||||
}
|
||||
const e2eMembers = await this.getEncryptionTargetMembers();
|
||||
for (const member of e2eMembers) {
|
||||
const devices = await this._client.getStoredDevicesForUser(member.userId);
|
||||
const devices = this._client.getStoredDevicesForUser(member.userId);
|
||||
if (devices.some((device) => device.isUnverified())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+25
-2
@@ -25,6 +25,15 @@ limitations under the License.
|
||||
import {User} from "../models/user";
|
||||
import * as utils from "../utils";
|
||||
|
||||
function isValidFilterId(filterId) {
|
||||
const isValidStr = typeof filterId === "string" &&
|
||||
!!filterId &&
|
||||
filterId !== "undefined" && // exclude these as we've serialized undefined in localStorage before
|
||||
filterId !== "null";
|
||||
|
||||
return isValidStr || typeof filterId === "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new in-memory data store for the Matrix Client.
|
||||
* @constructor
|
||||
@@ -273,8 +282,17 @@ MemoryStore.prototype = {
|
||||
if (!this.localStorage) {
|
||||
return null;
|
||||
}
|
||||
const key = "mxjssdk_memory_filter_" + filterName;
|
||||
// XXX Storage.getItem doesn't throw ...
|
||||
// or are we using something different
|
||||
// than window.localStorage in some cases
|
||||
// that does throw?
|
||||
// that would be very naughty
|
||||
try {
|
||||
return this.localStorage.getItem("mxjssdk_memory_filter_" + filterName);
|
||||
const value = this.localStorage.getItem(key);
|
||||
if (isValidFilterId(value)) {
|
||||
return value;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
},
|
||||
@@ -288,8 +306,13 @@ MemoryStore.prototype = {
|
||||
if (!this.localStorage) {
|
||||
return;
|
||||
}
|
||||
const key = "mxjssdk_memory_filter_" + filterName;
|
||||
try {
|
||||
this.localStorage.setItem("mxjssdk_memory_filter_" + filterName, filterId);
|
||||
if (isValidFilterId(filterId)) {
|
||||
this.localStorage.setItem(key, filterId);
|
||||
} else {
|
||||
this.localStorage.removeItem(key);
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
|
||||
+11
-14
@@ -511,6 +511,12 @@ SyncApi.prototype.sync = function() {
|
||||
checkLazyLoadStatus(); // advance to the next stage
|
||||
}
|
||||
|
||||
function buildDefaultFilter() {
|
||||
const filter = new Filter(client.credentials.userId);
|
||||
filter.setTimelineLimit(self.opts.initialSyncLimit);
|
||||
return filter;
|
||||
}
|
||||
|
||||
const checkLazyLoadStatus = async () => {
|
||||
debuglog("Checking lazy load status...");
|
||||
if (this.opts.lazyLoadMembers && client.isGuest()) {
|
||||
@@ -520,19 +526,11 @@ SyncApi.prototype.sync = function() {
|
||||
debuglog("Checking server lazy load support...");
|
||||
const supported = await client.doesServerSupportLazyLoading();
|
||||
if (supported) {
|
||||
try {
|
||||
debuglog("Creating and storing lazy load sync filter...");
|
||||
this.opts.filter = await client.createFilter(
|
||||
Filter.LAZY_LOADING_SYNC_FILTER,
|
||||
);
|
||||
debuglog("Created and stored lazy load sync filter");
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
"Creating and storing lazy load sync filter failed",
|
||||
err,
|
||||
);
|
||||
throw err;
|
||||
debuglog("Enabling lazy load on sync filter...");
|
||||
if (!this.opts.filter) {
|
||||
this.opts.filter = buildDefaultFilter();
|
||||
}
|
||||
this.opts.filter.setLazyLoadMembers(true);
|
||||
} else {
|
||||
debuglog("LL: lazy loading requested but not supported " +
|
||||
"by server, so disabling");
|
||||
@@ -575,8 +573,7 @@ SyncApi.prototype.sync = function() {
|
||||
if (self.opts.filter) {
|
||||
filter = self.opts.filter;
|
||||
} else {
|
||||
filter = new Filter(client.credentials.userId);
|
||||
filter.setTimelineLimit(self.opts.initialSyncLimit);
|
||||
filter = buildDefaultFilter();
|
||||
}
|
||||
|
||||
let filterId;
|
||||
|
||||
@@ -21,6 +21,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import unhomoglyph from 'unhomoglyph';
|
||||
import {ConnectionError} from "./http-api";
|
||||
|
||||
/**
|
||||
* Encode a dictionary of query parameters.
|
||||
|
||||
Reference in New Issue
Block a user