Compare commits
43 Commits
v1.0.2-rc.1
...
v1.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
| e2c17528c2 | |||
| e00f565f37 | |||
| 085e797c30 | |||
| d753db590b | |||
| feac096dc2 | |||
| 67985d449a | |||
| a6de59c198 | |||
| 01eeb98e35 | |||
| 49a7defbf0 | |||
| c1ba5de686 | |||
| 81428f23d1 | |||
| 8af86bb746 | |||
| 8513f5c413 | |||
| eadec35093 | |||
| d6dbd621b8 | |||
| 7168f76614 | |||
| 1cda95f23c | |||
| bb1cd2bbce | |||
| 3f90ac5712 | |||
| 8d249a843c | |||
| 858b41d835 | |||
| 61aea05af0 | |||
| e7c764d5f5 | |||
| 09a9afe4e7 | |||
| 5a26503da7 | |||
| 5faf5ea1f8 | |||
| 0754c29c22 | |||
| d5c6dcf111 | |||
| 6a57ddd33c | |||
| bd711cdc1f | |||
| e669e493c9 | |||
| 48f290196c | |||
| f8985dbb39 | |||
| ef594d52e4 | |||
| 23bbb2f8c6 | |||
| 73c7733ebc | |||
| 87f7f9443e | |||
| af6bbbc59b | |||
| 5b35a364a9 | |||
| d56ebadbc4 | |||
| 04accdeddc | |||
| 8c2001adbf | |||
| 79ca235e7c |
@@ -0,0 +1,24 @@
|
||||
steps:
|
||||
- label: ":eslint: Lint"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn lint"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
|
||||
- label: ":karma: Tests"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn test"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
|
||||
- label: "📃 Docs"
|
||||
command:
|
||||
- "yarn install"
|
||||
- "yarn gendoc"
|
||||
plugins:
|
||||
- docker#v3.0.1:
|
||||
image: "node:10"
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
node_modules
|
||||
/.npmrc
|
||||
/*.log
|
||||
package-lock.json
|
||||
.lock-wscript
|
||||
build/Release
|
||||
coverage
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10.11.0"
|
||||
before_install:
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
script:
|
||||
- ./travis.sh
|
||||
@@ -1,3 +1,43 @@
|
||||
Changes in [1.0.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.3) (2019-04-01)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.3-rc.1...v1.0.3)
|
||||
|
||||
* Add existence check to local storage based crypto store
|
||||
[\#874](https://github.com/matrix-org/matrix-js-sdk/pull/874)
|
||||
|
||||
Changes in [1.0.3-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.3-rc.1) (2019-03-27)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.2...v1.0.3-rc.1)
|
||||
|
||||
* Add IndexedDB existence checks
|
||||
[\#871](https://github.com/matrix-org/matrix-js-sdk/pull/871)
|
||||
* Emit sync errors for capturing by clients
|
||||
[\#869](https://github.com/matrix-org/matrix-js-sdk/pull/869)
|
||||
* Add functions for getting room upgrade history and leaving those rooms
|
||||
[\#868](https://github.com/matrix-org/matrix-js-sdk/pull/868)
|
||||
* Clarify the meaning of 'real name' for contribution
|
||||
[\#867](https://github.com/matrix-org/matrix-js-sdk/pull/867)
|
||||
* Remove `sessionStore` to `cryptoStore` migration path
|
||||
[\#865](https://github.com/matrix-org/matrix-js-sdk/pull/865)
|
||||
* Add debugging for spurious room version warnings
|
||||
[\#866](https://github.com/matrix-org/matrix-js-sdk/pull/866)
|
||||
* Add investigation notes for browser storage
|
||||
[\#864](https://github.com/matrix-org/matrix-js-sdk/pull/864)
|
||||
* make sure resolve object is defined before calling it
|
||||
[\#862](https://github.com/matrix-org/matrix-js-sdk/pull/862)
|
||||
* Rename `MatrixInMemoryStore` to `MemoryStore`
|
||||
[\#861](https://github.com/matrix-org/matrix-js-sdk/pull/861)
|
||||
* Use Buildkite for CI
|
||||
[\#859](https://github.com/matrix-org/matrix-js-sdk/pull/859)
|
||||
* only create one session at a time per device
|
||||
[\#857](https://github.com/matrix-org/matrix-js-sdk/pull/857)
|
||||
|
||||
Changes in [1.0.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.2) (2019-03-18)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.2-rc.1...v1.0.2)
|
||||
|
||||
* No changes since rc.1
|
||||
|
||||
Changes in [1.0.2-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.2-rc.1) (2019-03-13)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.1...v1.0.2-rc.1)
|
||||
|
||||
+13
-9
@@ -24,7 +24,7 @@ works. Develop is the unstable branch where all the development actually
|
||||
happens: the workflow is that contributors should fork the develop branch to
|
||||
make a 'feature' branch for a particular contribution, and then make a pull
|
||||
request to merge this back into the matrix.org 'official' develop branch. We
|
||||
use github's pull request workflow to review the contribution, and either ask
|
||||
use GitHub's pull request workflow to review the contribution, and either ask
|
||||
you to make any refinements needed or merge it and make them ourselves. The
|
||||
changes will then land on master when we next do a release.
|
||||
|
||||
@@ -60,8 +60,8 @@ Sign off
|
||||
~~~~~~~~
|
||||
|
||||
In order to have a concrete record that your contribution is intentional
|
||||
and you agree to license it under the same terms as the project's license, we've adopted the
|
||||
same lightweight approach that the Linux Kernel
|
||||
and you agree to license it under the same terms as the project's license, we've
|
||||
adopted the same lightweight approach that the Linux Kernel
|
||||
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
|
||||
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
|
||||
projects use: the DCO (Developer Certificate of Origin:
|
||||
@@ -109,12 +109,16 @@ include the line in your commit or pull request comment::
|
||||
|
||||
Signed-off-by: Your Name <your@email.example.org>
|
||||
|
||||
...using your real name; unfortunately pseudonyms and anonymous contributions
|
||||
can't be accepted. Git makes this trivial - just use the -s flag when you do
|
||||
``git commit``, having first set ``user.name`` and ``user.email`` git configs
|
||||
(which you should have done anyway :)
|
||||
We accept contributions under a legally identifiable name, such as your name on
|
||||
government documentation or common-law names (names claimed by legitimate usage
|
||||
or repute). Unfortunately, we cannot accept anonymous contributions at this
|
||||
time.
|
||||
|
||||
If you forgot to sign off your commits before making your pull request and are on git 2.17+
|
||||
you can mass signoff using rebase::
|
||||
Git allows you to add this signoff automatically when using the ``-s`` flag to
|
||||
``git commit``, which uses the name and email set in your ``user.name`` and
|
||||
``user.email`` git configs.
|
||||
|
||||
If you forgot to sign off your commits before making your pull request and are
|
||||
on Git 2.17+ you can mass signoff using rebase::
|
||||
|
||||
git rebase --signoff origin/develop
|
||||
|
||||
@@ -80,7 +80,7 @@ client.on("Room.timeline", function(event, room, toStartOfTimeline) {
|
||||
});
|
||||
```
|
||||
|
||||
By default, the `matrix-js-sdk` client uses the `MatrixInMemoryStore` to store events as they are received. For example to iterate through the currently stored timeline for a room:
|
||||
By default, the `matrix-js-sdk` client uses the `MemoryStore` to store events as they are received. For example to iterate through the currently stored timeline for a room:
|
||||
|
||||
```javascript
|
||||
Object.keys(client.store.rooms).forEach((roomId) => {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Browser Storage Notes
|
||||
|
||||
## Overview
|
||||
|
||||
Browsers examined: Firefox 67, Chrome 75
|
||||
|
||||
The examination below applies to the default, non-persistent storage policy.
|
||||
|
||||
## Quota Measurement
|
||||
|
||||
Browsers appear to enforce and measure the quota in terms of space on disk, not
|
||||
data stored, so you may be able to store more data than the simple sum of all
|
||||
input data depending on how compressible your data is.
|
||||
|
||||
## Quota Limit
|
||||
|
||||
Specs and documentation suggest we should consistently receive
|
||||
`QuotaExceededError` when we're near space limits, but the reality is a bit
|
||||
blurrier.
|
||||
|
||||
When we are low on disk space overall or near the group limit / origin quota:
|
||||
|
||||
* Chrome
|
||||
* Log database may fail to start with AbortError
|
||||
* IndexedDB fails to start for crypto: AbortError in connect from
|
||||
indexeddb-store-worker
|
||||
* When near the quota, QuotaExceededError is used more consistently
|
||||
* Firefox
|
||||
* The first error will be QuotaExceededError
|
||||
* Future write attempts will fail with various errors when space is low,
|
||||
including nonsense like "InvalidStateError: A mutation operation was
|
||||
attempted on a database that did not allow mutations."
|
||||
* Once you start getting errors, the DB is effectively wedged in read-only
|
||||
mode
|
||||
* Can revive access if you reopen the DB
|
||||
|
||||
## Cache Eviction
|
||||
|
||||
While the Storage Standard says all storage for an origin group should be
|
||||
limited by a single quota, in practice, browsers appear to handle `localStorage`
|
||||
separately from the others, so it has a separate quota limit and isn't evicted
|
||||
when low on space.
|
||||
|
||||
* Chrome, Firefox
|
||||
* IndexedDB for origin deleted
|
||||
* Local Storage remains in place
|
||||
|
||||
## Persistent Storage
|
||||
|
||||
Storage Standard offers a `navigator.storage.persist` API that can be used to
|
||||
request persistent storage that won't be deleted by the browser because of low
|
||||
space.
|
||||
|
||||
* Chrome
|
||||
* Chrome 75 seems to grant this without any prompt based on [interaction
|
||||
criteria](https://developers.google.com/web/updates/2016/06/persistent-storage)
|
||||
* Firefox
|
||||
* Firefox 67 shows a prompt to grant
|
||||
* Reverting persistent seems to require revoking permission _and_ clearing
|
||||
site data
|
||||
|
||||
## Storage Estimation
|
||||
|
||||
Storage Standard offers a `navigator.storage.estimate` API to get some clue of
|
||||
how much space remains.
|
||||
|
||||
* Chrome, Firefox
|
||||
* Can run this at any time to request an estimate of space remaining
|
||||
* Firefox
|
||||
* Returns `0` for `usage` if a site is persisted
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "1.0.2-rc.1",
|
||||
"version": "1.0.3",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -79,7 +79,7 @@
|
||||
"istanbul": "^0.4.5",
|
||||
"jsdoc": "^3.5.5",
|
||||
"lolex": "^1.5.2",
|
||||
"matrix-mock-request": "^1.2.2",
|
||||
"matrix-mock-request": "^1.2.3",
|
||||
"mocha": "^5.2.0",
|
||||
"mocha-jenkins-reporter": "^0.4.0",
|
||||
"olm": "https://matrix.org/packages/npm/olm/olm-3.1.0-pre1.tgz",
|
||||
|
||||
@@ -4,7 +4,7 @@ const sdk = require("../..");
|
||||
const HttpBackend = require("matrix-mock-request");
|
||||
const publicGlobals = require("../../lib/matrix");
|
||||
const Room = publicGlobals.Room;
|
||||
const MatrixInMemoryStore = publicGlobals.MatrixInMemoryStore;
|
||||
const MemoryStore = publicGlobals.MemoryStore;
|
||||
const Filter = publicGlobals.Filter;
|
||||
const utils = require("../test-utils");
|
||||
const MockStorageApi = require("../MockStorageApi");
|
||||
@@ -23,7 +23,7 @@ describe("MatrixClient", function() {
|
||||
beforeEach(function() {
|
||||
utils.beforeEach(this); // eslint-disable-line no-invalid-this
|
||||
httpBackend = new HttpBackend();
|
||||
store = new MatrixInMemoryStore();
|
||||
store = new MemoryStore();
|
||||
|
||||
const mockStorage = new MockStorageApi();
|
||||
sessionStore = new sdk.WebStorageSessionStore(mockStorage);
|
||||
|
||||
@@ -128,7 +128,7 @@ describe("MatrixClient opts", function() {
|
||||
beforeEach(function() {
|
||||
client = new MatrixClient({
|
||||
request: httpBackend.requestFn,
|
||||
store: new sdk.MatrixInMemoryStore(),
|
||||
store: new sdk.MemoryStore(),
|
||||
baseUrl: baseUrl,
|
||||
userId: userId,
|
||||
accessToken: accessToken,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,6 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import DeviceList from '../../../lib/crypto/DeviceList';
|
||||
import MockStorageApi from '../../MockStorageApi';
|
||||
import WebStorageSessionStore from '../../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import testUtils from '../../test-utils';
|
||||
import utils from '../../../lib/utils';
|
||||
@@ -57,7 +55,6 @@ const signedDeviceList = {
|
||||
|
||||
describe('DeviceList', function() {
|
||||
let downloadSpy;
|
||||
let sessionStore;
|
||||
let cryptoStore;
|
||||
let deviceLists = [];
|
||||
|
||||
@@ -67,8 +64,6 @@ describe('DeviceList', function() {
|
||||
deviceLists = [];
|
||||
|
||||
downloadSpy = expect.createSpy();
|
||||
const mockStorage = new MockStorageApi();
|
||||
sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
cryptoStore = new MemoryCryptoStore();
|
||||
});
|
||||
|
||||
@@ -85,7 +80,7 @@ describe('DeviceList', function() {
|
||||
const mockOlm = {
|
||||
verifySignature: function(key, message, signature) {},
|
||||
};
|
||||
const dl = new DeviceList(baseApis, cryptoStore, sessionStore, mockOlm);
|
||||
const dl = new DeviceList(baseApis, cryptoStore, mockOlm);
|
||||
deviceLists.push(dl);
|
||||
return dl;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import Promise from 'bluebird';
|
||||
|
||||
import sdk from '../../../..';
|
||||
import algorithms from '../../../../lib/crypto/algorithms';
|
||||
import WebStorageSessionStore from '../../../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../../../MockStorageApi';
|
||||
import testUtils from '../../../test-utils';
|
||||
@@ -40,10 +39,9 @@ describe("MegolmDecryption", function() {
|
||||
mockBaseApis = {};
|
||||
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
const olmDevice = new OlmDevice(cryptoStore);
|
||||
|
||||
megolmDecryption = new MegolmDecryption({
|
||||
userId: '@user:id',
|
||||
@@ -264,10 +262,9 @@ describe("MegolmDecryption", function() {
|
||||
|
||||
it("re-uses sessions for sequential messages", async function() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
const olmDevice = new OlmDevice(cryptoStore);
|
||||
olmDevice.verifySignature = expect.createSpy();
|
||||
await olmDevice.init();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2018,2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,18 +17,18 @@ limitations under the License.
|
||||
import '../../../olm-loader';
|
||||
|
||||
import expect from 'expect';
|
||||
import WebStorageSessionStore from '../../../../lib/store/session/webstorage';
|
||||
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||
import MockStorageApi from '../../../MockStorageApi';
|
||||
import testUtils from '../../../test-utils';
|
||||
|
||||
import OlmDevice from '../../../../lib/crypto/OlmDevice';
|
||||
import olmlib from '../../../../lib/crypto/olmlib';
|
||||
import DeviceInfo from '../../../../lib/crypto/deviceinfo';
|
||||
|
||||
function makeOlmDevice() {
|
||||
const mockStorage = new MockStorageApi();
|
||||
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
const olmDevice = new OlmDevice(cryptoStore);
|
||||
return olmDevice;
|
||||
}
|
||||
|
||||
@@ -82,5 +82,61 @@ describe("OlmDecryption", function() {
|
||||
"The olm or proteus is an aquatic salamander in the family Proteidae",
|
||||
);
|
||||
});
|
||||
|
||||
it("creates only one session at a time", async function() {
|
||||
// if we call ensureOlmSessionsForDevices multiple times, it should
|
||||
// only try to create one session at a time, even if the server is
|
||||
// slow
|
||||
let count = 0;
|
||||
const baseApis = {
|
||||
claimOneTimeKeys: () => {
|
||||
// simulate a very slow server (.5 seconds to respond)
|
||||
count++;
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(reject, 500);
|
||||
});
|
||||
},
|
||||
};
|
||||
const devicesByUser = {
|
||||
"@bob:example.com": [
|
||||
DeviceInfo.fromStorage({
|
||||
keys: {
|
||||
"curve25519:ABCDEFG": "akey",
|
||||
},
|
||||
}, "ABCDEFG"),
|
||||
],
|
||||
};
|
||||
function alwaysSucceed(promise) {
|
||||
// swallow any exception thrown by a promise, so that
|
||||
// Promise.all doesn't abort
|
||||
return promise.catch(() => {});
|
||||
}
|
||||
|
||||
// start two tasks that try to ensure that there's an olm session
|
||||
const promises = Promise.all([
|
||||
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
|
||||
aliceOlmDevice, baseApis, devicesByUser,
|
||||
)),
|
||||
alwaysSucceed(olmlib.ensureOlmSessionsForDevices(
|
||||
aliceOlmDevice, baseApis, devicesByUser,
|
||||
)),
|
||||
]);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 200);
|
||||
});
|
||||
|
||||
// after .2s, both tasks should have started, but one should be
|
||||
// waiting on the other before trying to create a session, so
|
||||
// claimOneTimeKeys should have only been called once
|
||||
expect(count).toBe(1);
|
||||
|
||||
await promises;
|
||||
|
||||
// after waiting for both tasks to complete, the first task should
|
||||
// have failed, so the second task should have tried to create a
|
||||
// new session and will have called claimOneTimeKeys
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ describe("MegolmBackup", function() {
|
||||
sessionStore = new WebStorageSessionStore(mockStorage);
|
||||
cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||
|
||||
olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
olmDevice = new OlmDevice(cryptoStore);
|
||||
|
||||
// we stub out the olm encryption bits
|
||||
mockOlmLib = {};
|
||||
|
||||
+137
-12
@@ -106,19 +106,26 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
*
|
||||
* @param {string} opts.userId The user ID for this user.
|
||||
*
|
||||
* @param {Object=} opts.store The data store to use. If not specified,
|
||||
* this client will not store any HTTP responses.
|
||||
* @param {Object=} opts.store
|
||||
* The data store used for sync data from the homeserver. If not specified,
|
||||
* this client will not store any HTTP responses. The `createClient` helper
|
||||
* will create a default store if needed.
|
||||
*
|
||||
* @param {module:store/session/webstorage~WebStorageSessionStore} opts.sessionStore
|
||||
* A store to be used for end-to-end crypto session data. Most data has been
|
||||
* migrated out of here to `cryptoStore` instead. If not specified,
|
||||
* end-to-end crypto will be disabled. The `createClient` helper
|
||||
* _will not_ create this store at the moment.
|
||||
*
|
||||
* @param {module:crypto.store.base~CryptoStore} opts.cryptoStore
|
||||
* A store to be used for end-to-end crypto session data. If not specified,
|
||||
* end-to-end crypto will be disabled. The `createClient` helper will create
|
||||
* a default store if needed.
|
||||
*
|
||||
* @param {string=} opts.deviceId A unique identifier for this device; used for
|
||||
* tracking things like crypto keys and access tokens. If not specified,
|
||||
* end-to-end crypto will be disabled.
|
||||
*
|
||||
* @param {Object=} opts.sessionStore A store to be used for end-to-end crypto
|
||||
* session data. This should be a {@link
|
||||
* module:store/session/webstorage~WebStorageSessionStore|WebStorageSessionStore},
|
||||
* or an object implementing the same interface. If not specified,
|
||||
* end-to-end crypto will be disabled.
|
||||
*
|
||||
* @param {Object} opts.scheduler Optional. The scheduler to use. If not
|
||||
* specified, this client will not retry requests on failure. This client
|
||||
* will supply its own processing function to
|
||||
@@ -141,9 +148,6 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* maintain support for back-paginating the live timeline after a '/sync'
|
||||
* result with a gap.
|
||||
*
|
||||
* @param {module:crypto.store.base~CryptoStore} opts.cryptoStore
|
||||
* crypto store implementation.
|
||||
*
|
||||
* @param {Array} [opts.verificationMethods] Optional. The verification method
|
||||
* that the application can handle. Each element should be an item from {@link
|
||||
* module:crypto~verificationMethods verificationMethods}, or a class that
|
||||
@@ -220,7 +224,7 @@ function MatrixClient(opts) {
|
||||
// List of which rooms have encryption enabled: separate from crypto because
|
||||
// we still want to know which rooms are encrypted even if crypto is disabled:
|
||||
// we don't want to start sending unencrypted events to them.
|
||||
this._roomList = new RoomList(this._cryptoStore, this._sessionStore);
|
||||
this._roomList = new RoomList(this._cryptoStore);
|
||||
|
||||
// The pushprocessor caches useful things, so keep one and re-use it
|
||||
this._pushProcessor = new PushProcessor(this);
|
||||
@@ -431,6 +435,7 @@ MatrixClient.prototype.getCapabilities = function() {
|
||||
if (this._cachedCapabilities) {
|
||||
const now = new Date().getTime();
|
||||
if (now - this._cachedCapabilities.lastUpdated <= CAPABILITIES_CACHE_MS) {
|
||||
console.log("Returning cached capabilities");
|
||||
return Promise.resolve(this._cachedCapabilities.capabilities);
|
||||
}
|
||||
}
|
||||
@@ -445,6 +450,8 @@ MatrixClient.prototype.getCapabilities = function() {
|
||||
capabilities: capabilities,
|
||||
lastUpdated: new Date().getTime(),
|
||||
};
|
||||
|
||||
console.log("Caching capabilities: ", capabilities);
|
||||
return capabilities;
|
||||
});
|
||||
};
|
||||
@@ -2123,6 +2130,80 @@ MatrixClient.prototype.sendTyping = function(roomId, isTyping, timeoutMs, callba
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the history of room upgrades for a given room, as far as the
|
||||
* client can see. Returns an array of Rooms where the first entry is the
|
||||
* oldest and the last entry is the newest (likely current) room. If the
|
||||
* provided room is not found, this returns an empty list. This works in
|
||||
* both directions, looking for older and newer rooms of the given room.
|
||||
* @param {string} roomId The room ID to search from
|
||||
* @param {boolean} verifyLinks If true, the function will only return rooms
|
||||
* which can be proven to be linked. For example, rooms which have a create
|
||||
* event pointing to an old room which the client is not aware of or doesn't
|
||||
* have a matching tombstone would not be returned.
|
||||
* @return {Room[]} An array of rooms representing the upgrade
|
||||
* history.
|
||||
*/
|
||||
MatrixClient.prototype.getRoomUpgradeHistory = function(roomId, verifyLinks=false) {
|
||||
let currentRoom = this.getRoom(roomId);
|
||||
if (!currentRoom) return [];
|
||||
|
||||
const upgradeHistory = [currentRoom];
|
||||
|
||||
// Work backwards first, looking at create events.
|
||||
let createEvent = currentRoom.currentState.getStateEvents("m.room.create", "");
|
||||
while (createEvent) {
|
||||
console.log(`Looking at ${createEvent.getId()}`);
|
||||
const predecessor = createEvent.getContent()['predecessor'];
|
||||
if (predecessor && predecessor['room_id']) {
|
||||
console.log(`Looking at predecessor ${predecessor['room_id']}`);
|
||||
const refRoom = this.getRoom(predecessor['room_id']);
|
||||
if (!refRoom) break; // end of the chain
|
||||
|
||||
if (verifyLinks) {
|
||||
const tombstone = refRoom.currentState
|
||||
.getStateEvents("m.room.tombstone", "");
|
||||
|
||||
if (!tombstone
|
||||
|| tombstone.getContent()['replacement_room'] !== refRoom.roomId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert at the front because we're working backwards from the currentRoom
|
||||
upgradeHistory.splice(0, 0, refRoom);
|
||||
createEvent = refRoom.currentState.getStateEvents("m.room.create", "");
|
||||
} else {
|
||||
// No further create events to look at
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Work forwards next, looking at tombstone events
|
||||
let tombstoneEvent = currentRoom.currentState.getStateEvents("m.room.tombstone", "");
|
||||
while (tombstoneEvent) {
|
||||
const refRoom = this.getRoom(tombstoneEvent.getContent()['replacement_room']);
|
||||
if (!refRoom) break; // end of the chain
|
||||
|
||||
if (verifyLinks) {
|
||||
createEvent = refRoom.currentState.getStateEvents("m.room.create", "");
|
||||
if (!createEvent || !createEvent.getContent()['predecessor']) break;
|
||||
|
||||
const predecessor = createEvent.getContent()['predecessor'];
|
||||
if (predecessor['room_id'] !== currentRoom.roomId) break;
|
||||
}
|
||||
|
||||
// Push to the end because we're looking forwards
|
||||
upgradeHistory.push(refRoom);
|
||||
|
||||
// Set the current room to the reference room so we know where we're at
|
||||
currentRoom = refRoom;
|
||||
tombstoneEvent = currentRoom.currentState.getStateEvents("m.room.tombstone", "");
|
||||
}
|
||||
|
||||
return upgradeHistory;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {string} userId
|
||||
@@ -2190,6 +2271,50 @@ MatrixClient.prototype.leave = function(roomId, callback) {
|
||||
callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaves all rooms in the chain of room upgrades based on the given room. By
|
||||
* default, this will leave all the previous and upgraded rooms, including the
|
||||
* given room. To only leave the given room and any previous rooms, keeping the
|
||||
* upgraded (modern) rooms untouched supply `false` to `includeFuture`.
|
||||
* @param {string} roomId The room ID to start leaving at
|
||||
* @param {boolean} includeFuture If true, the whole chain (past and future) of
|
||||
* upgraded rooms will be left.
|
||||
* @return {module:client.Promise} Resolves when completed with an object keyed
|
||||
* by room ID and value of the error encountered when leaving or null.
|
||||
*/
|
||||
MatrixClient.prototype.leaveRoomChain = function(roomId, includeFuture=true) {
|
||||
const upgradeHistory = this.getRoomUpgradeHistory(roomId);
|
||||
|
||||
let eligibleToLeave = upgradeHistory;
|
||||
if (!includeFuture) {
|
||||
eligibleToLeave = [];
|
||||
for (const room of upgradeHistory) {
|
||||
eligibleToLeave.push(room);
|
||||
if (room.roomId === roomId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const populationResults = {}; // {roomId: Error}
|
||||
const promises = [];
|
||||
|
||||
const doLeave = (roomId) => {
|
||||
return this.leave(roomId).then(() => {
|
||||
populationResults[roomId] = null;
|
||||
}).catch((err) => {
|
||||
populationResults[roomId] = err;
|
||||
return null; // suppress error
|
||||
});
|
||||
};
|
||||
|
||||
for (const room of eligibleToLeave) {
|
||||
promises.push(doLeave(room.roomId));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => populationResults);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} roomId
|
||||
* @param {string} userId
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -61,9 +61,8 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
|
||||
* @alias module:crypto/DeviceList
|
||||
*/
|
||||
export default class DeviceList {
|
||||
constructor(baseApis, cryptoStore, sessionStore, olmDevice) {
|
||||
constructor(baseApis, cryptoStore, olmDevice) {
|
||||
this._cryptoStore = cryptoStore;
|
||||
this._sessionStore = sessionStore;
|
||||
|
||||
// userId -> {
|
||||
// deviceId -> {
|
||||
@@ -108,30 +107,13 @@ export default class DeviceList {
|
||||
* Load the device tracking state from storage
|
||||
*/
|
||||
async load() {
|
||||
let shouldDeleteSessionStore = false;
|
||||
await this._cryptoStore.doTxn(
|
||||
// migrate from session store if there's data there and not here
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||
if (deviceData === null) {
|
||||
logger.log("Migrating e2e device data...");
|
||||
this._devices = this._sessionStore.getAllEndToEndDevices() || {};
|
||||
this._deviceTrackingStatus = (
|
||||
this._sessionStore.getEndToEndDeviceTrackingStatus() || {}
|
||||
);
|
||||
this._syncToken = this._sessionStore.getEndToEndDeviceSyncToken();
|
||||
this._cryptoStore.storeEndToEndDeviceData({
|
||||
devices: this._devices,
|
||||
trackingStatus: this._deviceTrackingStatus,
|
||||
syncToken: this._syncToken,
|
||||
}, txn);
|
||||
shouldDeleteSessionStore = true;
|
||||
} else {
|
||||
this._devices = deviceData ? deviceData.devices : {},
|
||||
this._deviceTrackingStatus = deviceData ?
|
||||
deviceData.trackingStatus : {};
|
||||
this._syncToken = deviceData ? deviceData.syncToken : null;
|
||||
}
|
||||
this._devices = deviceData ? deviceData.devices : {},
|
||||
this._deviceTrackingStatus = deviceData ?
|
||||
deviceData.trackingStatus : {};
|
||||
this._syncToken = deviceData ? deviceData.syncToken : null;
|
||||
this._userByIdentityKey = {};
|
||||
for (const user of Object.keys(this._devices)) {
|
||||
const userDevices = this._devices[user];
|
||||
@@ -146,11 +128,6 @@ export default class DeviceList {
|
||||
},
|
||||
);
|
||||
|
||||
if (shouldDeleteSessionStore) {
|
||||
// migrated data is now safely persisted: remove from old store
|
||||
this._sessionStore.removeEndToEndDeviceData();
|
||||
}
|
||||
|
||||
for (const u of Object.keys(this._deviceTrackingStatus)) {
|
||||
// if a download was in progress when we got shut down, it isn't any more.
|
||||
if (this._deviceTrackingStatus[u] == TRACKING_STATUS_DOWNLOAD_IN_PROGRESS) {
|
||||
|
||||
+42
-105
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd
|
||||
Copyright 2017, 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -59,20 +59,17 @@ function checkPayloadLength(payloadString) {
|
||||
* Manages the olm cryptography functions. Each OlmDevice has a single
|
||||
* OlmAccount and a number of OlmSessions.
|
||||
*
|
||||
* Accounts and sessions are kept pickled in a sessionStore.
|
||||
* Accounts and sessions are kept pickled in the cryptoStore.
|
||||
*
|
||||
* @constructor
|
||||
* @alias module:crypto/OlmDevice
|
||||
*
|
||||
* @param {Object} sessionStore A store to be used for data in end-to-end
|
||||
* crypto. This is deprecated and being replaced by cryptoStore.
|
||||
* @param {Object} cryptoStore A store for crypto data
|
||||
*
|
||||
* @property {string} deviceCurve25519Key Curve25519 key for the account
|
||||
* @property {string} deviceEd25519Key Ed25519 key for the account
|
||||
*/
|
||||
function OlmDevice(sessionStore, cryptoStore) {
|
||||
this._sessionStore = sessionStore;
|
||||
function OlmDevice(cryptoStore) {
|
||||
this._cryptoStore = cryptoStore;
|
||||
this._pickleKey = "DEFAULT_KEY";
|
||||
|
||||
@@ -81,7 +78,7 @@ function OlmDevice(sessionStore, cryptoStore) {
|
||||
this.deviceEd25519Key = null;
|
||||
this._maxOneTimeKeys = null;
|
||||
|
||||
// we don't bother stashing outboundgroupsessions in the sessionstore -
|
||||
// we don't bother stashing outboundgroupsessions in the cryptoStore -
|
||||
// instead we keep them here.
|
||||
this._outboundGroupSessionStore = {};
|
||||
|
||||
@@ -102,6 +99,10 @@ function OlmDevice(sessionStore, cryptoStore) {
|
||||
// Keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
||||
// Values are objects of the form "{id: <event id>, timestamp: <ts>}"
|
||||
this._inboundGroupSessionMessageIndexes = {};
|
||||
|
||||
// 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 = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,14 +115,10 @@ function OlmDevice(sessionStore, cryptoStore) {
|
||||
* Reads the device keys from the OlmAccount object.
|
||||
*/
|
||||
OlmDevice.prototype.init = async function() {
|
||||
await this._migrateFromSessionStore();
|
||||
|
||||
let e2eKeys;
|
||||
const account = new global.Olm.Account();
|
||||
try {
|
||||
await _initialiseAccount(
|
||||
this._sessionStore, this._cryptoStore, this._pickleKey, account,
|
||||
);
|
||||
await _initialiseAccount(this._cryptoStore, this._pickleKey, account);
|
||||
e2eKeys = JSON.parse(account.identity_keys());
|
||||
|
||||
this._maxOneTimeKeys = account.max_number_of_one_time_keys();
|
||||
@@ -133,7 +130,7 @@ OlmDevice.prototype.init = async function() {
|
||||
this.deviceEd25519Key = e2eKeys.ed25519;
|
||||
};
|
||||
|
||||
async function _initialiseAccount(sessionStore, cryptoStore, pickleKey, account) {
|
||||
async function _initialiseAccount(cryptoStore, pickleKey, account) {
|
||||
await cryptoStore.doTxn('readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
cryptoStore.getAccount(txn, (pickledAccount) => {
|
||||
if (pickledAccount !== null) {
|
||||
@@ -154,95 +151,6 @@ OlmDevice.getOlmVersion = function() {
|
||||
return global.Olm.get_library_version();
|
||||
};
|
||||
|
||||
OlmDevice.prototype._migrateFromSessionStore = async function() {
|
||||
// account
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
this._cryptoStore.getAccount(txn, (pickledAccount) => {
|
||||
if (pickledAccount === null) {
|
||||
// Migrate from sessionStore
|
||||
pickledAccount = this._sessionStore.getEndToEndAccount();
|
||||
if (pickledAccount !== null) {
|
||||
logger.log("Migrating account from session store");
|
||||
this._cryptoStore.storeAccount(txn, pickledAccount);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// remove the old account now the transaction has completed. Either we've
|
||||
// migrated it or decided not to, either way we want to blow away the old data.
|
||||
this._sessionStore.removeEndToEndAccount();
|
||||
|
||||
// sessions
|
||||
const sessions = this._sessionStore.getAllEndToEndSessions();
|
||||
if (Object.keys(sessions).length > 0) {
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS], (txn) => {
|
||||
// Don't migrate sessions from localstorage if we already have sessions
|
||||
// in indexeddb, since this means we've already migrated and an old version
|
||||
// has run against the same localstorage and created some spurious sessions.
|
||||
this._cryptoStore.countEndToEndSessions(txn, (count) => {
|
||||
if (count) {
|
||||
logger.log("Crypto store already has sessions: not migrating");
|
||||
return;
|
||||
}
|
||||
let numSessions = 0;
|
||||
for (const deviceKey of Object.keys(sessions)) {
|
||||
for (const sessionId of Object.keys(sessions[deviceKey])) {
|
||||
numSessions++;
|
||||
this._cryptoStore.storeEndToEndSession(
|
||||
deviceKey, sessionId, sessions[deviceKey][sessionId], txn,
|
||||
);
|
||||
}
|
||||
}
|
||||
logger.log(
|
||||
"Migrating " + numSessions + " sessions from session store",
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
this._sessionStore.removeAllEndToEndSessions();
|
||||
}
|
||||
|
||||
// inbound group sessions
|
||||
const ibGroupSessions = this._sessionStore.getAllEndToEndInboundGroupSessionKeys();
|
||||
if (Object.keys(ibGroupSessions).length > 0) {
|
||||
let numIbSessions = 0;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_INBOUND_GROUP_SESSIONS], (txn) => {
|
||||
// We always migrate inbound group sessions, even if we already have some
|
||||
// in the new store. They should be be safe to migrate.
|
||||
for (const s of ibGroupSessions) {
|
||||
try {
|
||||
this._cryptoStore.addEndToEndInboundGroupSession(
|
||||
s.senderKey, s.sessionId,
|
||||
JSON.parse(
|
||||
this._sessionStore.getEndToEndInboundGroupSession(
|
||||
s.senderKey, s.sessionId,
|
||||
),
|
||||
), txn,
|
||||
);
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
"Failed to migrate session " + s.senderKey + "/" +
|
||||
s.sessionId + ": " + e.stack || e,
|
||||
);
|
||||
}
|
||||
++numIbSessions;
|
||||
}
|
||||
logger.log(
|
||||
"Migrated " + numIbSessions +
|
||||
" inbound group sessions from session store",
|
||||
);
|
||||
},
|
||||
);
|
||||
this._sessionStore.removeAllEndToEndInboundGroupSessions();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* extract our OlmAccount from the crypto store and call the given function
|
||||
* with the account object
|
||||
@@ -553,6 +461,15 @@ OlmDevice.prototype.createInboundSession = async function(
|
||||
* @return {Promise<string[]>} a list of known session ids for the device
|
||||
*/
|
||||
OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityKey) {
|
||||
if (this._sessionsInProgress[theirDeviceIdentityKey]) {
|
||||
console.log("waiting for session to be created");
|
||||
try {
|
||||
await this._sessionsInProgress[theirDeviceIdentityKey];
|
||||
} catch (e) {
|
||||
// if the session failed to be created, just fall through and
|
||||
// return an empty result
|
||||
}
|
||||
}
|
||||
let sessionIds;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||
@@ -573,10 +490,18 @@ OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityK
|
||||
*
|
||||
* @param {string} theirDeviceIdentityKey Curve25519 identity key for the
|
||||
* remote device
|
||||
* @param {boolean} nowait Don't wait for an in-progress session to complete.
|
||||
* This should only be set to true of the calling function is the function
|
||||
* that marked the session as being in-progress.
|
||||
* @return {Promise<?string>} session id, or null if no established session
|
||||
*/
|
||||
OlmDevice.prototype.getSessionIdForDevice = async function(theirDeviceIdentityKey) {
|
||||
const sessionInfos = await this.getSessionInfoForDevice(theirDeviceIdentityKey);
|
||||
OlmDevice.prototype.getSessionIdForDevice = async function(
|
||||
theirDeviceIdentityKey, nowait,
|
||||
) {
|
||||
const sessionInfos = await this.getSessionInfoForDevice(
|
||||
theirDeviceIdentityKey, nowait,
|
||||
);
|
||||
|
||||
if (sessionInfos.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -611,9 +536,21 @@ OlmDevice.prototype.getSessionIdForDevice = async function(theirDeviceIdentityKe
|
||||
* message and is therefore past the pre-key stage), and 'sessionId'.
|
||||
*
|
||||
* @param {string} deviceIdentityKey Curve25519 identity key for the device
|
||||
* @param {boolean} nowait Don't wait for an in-progress session to complete.
|
||||
* This should only be set to true of the calling function is the function
|
||||
* that marked the session as being in-progress.
|
||||
* @return {Array.<{sessionId: string, hasReceivedMessage: Boolean}>}
|
||||
*/
|
||||
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey) {
|
||||
OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey, nowait) {
|
||||
if (this._sessionsInProgress[deviceIdentityKey] && !nowait) {
|
||||
logger.log("waiting for session to be created");
|
||||
try {
|
||||
await this._sessionsInProgress[deviceIdentityKey];
|
||||
} catch (e) {
|
||||
// if the session failed to be created, then just fall through and
|
||||
// return an empty result
|
||||
}
|
||||
}
|
||||
const info = [];
|
||||
|
||||
await this._cryptoStore.doTxn(
|
||||
|
||||
+3
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -26,40 +26,21 @@ import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||
* @alias module:crypto/RoomList
|
||||
*/
|
||||
export default class RoomList {
|
||||
constructor(cryptoStore, sessionStore) {
|
||||
constructor(cryptoStore) {
|
||||
this._cryptoStore = cryptoStore;
|
||||
this._sessionStore = sessionStore;
|
||||
|
||||
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
|
||||
this._roomEncryption = {};
|
||||
}
|
||||
|
||||
async init() {
|
||||
let removeSessionStoreRooms = false;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||
this._cryptoStore.getEndToEndRooms(txn, (result) => {
|
||||
if (result === null || Object.keys(result).length === 0) {
|
||||
// migrate from session store, if there's data there
|
||||
const sessStoreRooms = this._sessionStore.getAllEndToEndRooms();
|
||||
if (sessStoreRooms !== null) {
|
||||
for (const roomId of Object.keys(sessStoreRooms)) {
|
||||
this._cryptoStore.storeEndToEndRoom(
|
||||
roomId, sessStoreRooms[roomId], txn,
|
||||
);
|
||||
}
|
||||
}
|
||||
this._roomEncryption = sessStoreRooms;
|
||||
removeSessionStoreRooms = true;
|
||||
} else {
|
||||
this._roomEncryption = result;
|
||||
}
|
||||
this._roomEncryption = result;
|
||||
});
|
||||
},
|
||||
);
|
||||
if (removeSessionStoreRooms) {
|
||||
this._sessionStore.removeAllEndToEndRooms();
|
||||
}
|
||||
}
|
||||
|
||||
getRoomEncryption(roomId) {
|
||||
|
||||
+4
-34
@@ -134,9 +134,9 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
||||
this._checkedForBackup = false; // Have we checked the server for a backup we can use?
|
||||
this._sendingBackups = false; // Are we currently sending backups?
|
||||
|
||||
this._olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||
this._olmDevice = new OlmDevice(cryptoStore);
|
||||
this._deviceList = new DeviceList(
|
||||
baseApis, cryptoStore, sessionStore, this._olmDevice,
|
||||
baseApis, cryptoStore, this._olmDevice,
|
||||
);
|
||||
|
||||
// the last time we did a check for the number of one-time-keys on the
|
||||
@@ -198,25 +198,6 @@ utils.inherits(Crypto, EventEmitter);
|
||||
*/
|
||||
Crypto.prototype.init = async function() {
|
||||
await global.Olm.init();
|
||||
|
||||
const sessionStoreHasAccount = Boolean(this._sessionStore.getEndToEndAccount());
|
||||
let cryptoStoreHasAccount;
|
||||
await this._cryptoStore.doTxn(
|
||||
'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => {
|
||||
this._cryptoStore.getAccount(txn, (pickledAccount) => {
|
||||
cryptoStoreHasAccount = Boolean(pickledAccount);
|
||||
});
|
||||
},
|
||||
);
|
||||
if (sessionStoreHasAccount && !cryptoStoreHasAccount) {
|
||||
// we're about to migrate to the crypto store
|
||||
this.emit("crypto.warning", 'CRYPTO_WARNING_ACCOUNT_MIGRATED');
|
||||
} else if (sessionStoreHasAccount && cryptoStoreHasAccount) {
|
||||
// There's an account in both stores: an old version of
|
||||
// the code has been run against this store.
|
||||
this.emit("crypto.warning", 'CRYPTO_WARNING_OLD_VERSION_DETECTED');
|
||||
}
|
||||
|
||||
await this._olmDevice.init();
|
||||
await this._deviceList.load();
|
||||
|
||||
@@ -235,7 +216,7 @@ Crypto.prototype.init = async function() {
|
||||
}
|
||||
|
||||
if (!myDevices[this._deviceId]) {
|
||||
// add our own deviceinfo to the sessionstore
|
||||
// add our own deviceinfo to the cryptoStore
|
||||
const deviceInfo = {
|
||||
keys: this._deviceKeys,
|
||||
algorithms: this._supportedAlgorithms,
|
||||
@@ -942,7 +923,7 @@ Crypto.prototype.forceDiscardSession = function(roomId) {
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 cryptoStore).
|
||||
*
|
||||
* @param {string} roomId The room ID to enable encryption in.
|
||||
*
|
||||
@@ -2358,17 +2339,6 @@ class IncomingRoomKeyRequestCancellation {
|
||||
* Fires when the app may wish to warn the user about something related
|
||||
* the end-to-end crypto.
|
||||
*
|
||||
* Comes with a type which is one of:
|
||||
* * CRYPTO_WARNING_ACCOUNT_MIGRATED: Account data has been migrated from an older
|
||||
* version of the store in such a way that older clients will no longer be
|
||||
* able to read it. The app may wish to warn the user against going back to
|
||||
* an older version of the app.
|
||||
* * CRYPTO_WARNING_OLD_VERSION_DETECTED: js-sdk has detected that an older version
|
||||
* of js-sdk has been run against the same store after a migration has been
|
||||
* performed. This is likely have caused unexpected behaviour in the old
|
||||
* version. For example, the old version and the new version may have two
|
||||
* different identity keys.
|
||||
*
|
||||
* @event module:client~MatrixClient#"crypto.warning"
|
||||
* @param {string} type One of the strings listed above
|
||||
*/
|
||||
|
||||
+59
-14
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -137,6 +138,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
// [userId, deviceId], ...
|
||||
];
|
||||
const result = {};
|
||||
const resolveSession = {};
|
||||
|
||||
for (const userId in devicesByUser) {
|
||||
if (!devicesByUser.hasOwnProperty(userId)) {
|
||||
@@ -148,7 +150,36 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
const deviceInfo = devices[j];
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
const sessionId = await olmDevice.getSessionIdForDevice(key);
|
||||
if (!olmDevice._sessionsInProgress[key]) {
|
||||
// pre-emptively mark the session as in-progress to avoid race
|
||||
// conditions. If we find that we already have a session, then
|
||||
// we'll resolve
|
||||
olmDevice._sessionsInProgress[key] = new Promise(
|
||||
(resolve, reject) => {
|
||||
resolveSession[key] = {
|
||||
resolve: (...args) => {
|
||||
delete olmDevice._sessionsInProgress[key];
|
||||
resolve(...args);
|
||||
},
|
||||
reject: (...args) => {
|
||||
delete olmDevice._sessionsInProgress[key];
|
||||
reject(...args);
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
const sessionId = await olmDevice.getSessionIdForDevice(
|
||||
key, resolveSession[key],
|
||||
);
|
||||
if (sessionId !== null && resolveSession[key]) {
|
||||
// we found a session, but we had marked the session as
|
||||
// in-progress, so unmark it and unblock anything that was
|
||||
// waiting
|
||||
delete olmDevice._sessionsInProgress[key];
|
||||
resolveSession[key].resolve();
|
||||
delete resolveSession[key];
|
||||
}
|
||||
if (sessionId === null || force) {
|
||||
devicesWithoutSession.push([userId, deviceId]);
|
||||
}
|
||||
@@ -163,16 +194,19 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: this has a race condition - if we try to send another message
|
||||
// while we are claiming a key, we will end up claiming two and setting up
|
||||
// two sessions.
|
||||
//
|
||||
// That should eventually resolve itself, but it's poor form.
|
||||
|
||||
const oneTimeKeyAlgorithm = "signed_curve25519";
|
||||
const res = await baseApis.claimOneTimeKeys(
|
||||
devicesWithoutSession, oneTimeKeyAlgorithm,
|
||||
);
|
||||
let res;
|
||||
try {
|
||||
res = await baseApis.claimOneTimeKeys(
|
||||
devicesWithoutSession, oneTimeKeyAlgorithm,
|
||||
);
|
||||
} catch (e) {
|
||||
for (const resolver of Object.values(resolveSession)) {
|
||||
resolver.resolve();
|
||||
}
|
||||
logger.log("failed to claim one-time keys", e, devicesWithoutSession);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const otk_res = res.one_time_keys || {};
|
||||
const promises = [];
|
||||
@@ -185,6 +219,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
for (let j = 0; j < devices.length; j++) {
|
||||
const deviceInfo = devices[j];
|
||||
const deviceId = deviceInfo.deviceId;
|
||||
const key = deviceInfo.getIdentityKey();
|
||||
if (result[userId][deviceId].sessionId && !force) {
|
||||
// we already have a result for this device
|
||||
continue;
|
||||
@@ -199,10 +234,12 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
}
|
||||
|
||||
if (!oneTimeKey) {
|
||||
logger.warn(
|
||||
"No one-time keys (alg=" + oneTimeKeyAlgorithm +
|
||||
") for device " + userId + ":" + deviceId,
|
||||
);
|
||||
const msg = "No one-time keys (alg=" + oneTimeKeyAlgorithm +
|
||||
") for device " + userId + ":" + deviceId;
|
||||
logger.warn(msg);
|
||||
if (resolveSession[key]) {
|
||||
resolveSession[key].resolve();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -210,7 +247,15 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
||||
_verifyKeyAndStartSession(
|
||||
olmDevice, oneTimeKey, userId, deviceInfo,
|
||||
).then((sid) => {
|
||||
if (resolveSession[key]) {
|
||||
resolveSession[key].resolve(sid);
|
||||
}
|
||||
result[userId][deviceId].sessionId = sid;
|
||||
}, (e) => {
|
||||
if (resolveSession[key]) {
|
||||
resolveSession[key].resolve();
|
||||
}
|
||||
throw e;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import LocalStorageCryptoStore from './localStorage-crypto-store';
|
||||
import MemoryCryptoStore from './memory-crypto-store';
|
||||
import * as IndexedDBCryptoStoreBackend from './indexeddb-crypto-store-backend';
|
||||
import {InvalidCryptoStoreError} from '../../errors';
|
||||
import * as IndexedDBHelpers from "../../indexeddb-helpers";
|
||||
|
||||
/**
|
||||
* Internal module. indexeddb storage for e2e.
|
||||
@@ -48,6 +49,10 @@ export default class IndexedDBCryptoStore {
|
||||
this._backendPromise = null;
|
||||
}
|
||||
|
||||
static exists(indexedDB, dbName) {
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the database exists and is up-to-date, or fall back to
|
||||
* a local storage or in-memory store.
|
||||
|
||||
@@ -57,6 +57,16 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
||||
this.store = webStore;
|
||||
}
|
||||
|
||||
static exists(webStore) {
|
||||
const length = webStore.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (webStore.key(i).startsWith(E2E_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Olm Sessions
|
||||
|
||||
countEndToEndSessions(txn, func) {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
|
||||
/**
|
||||
* Check if an IndexedDB database exists. The only way to do so is to try opening it, so
|
||||
* we do that and then delete it did not exist before.
|
||||
*
|
||||
* @param {Object} indexedDB The `indexedDB` interface
|
||||
* @param {string} dbName The database name to test for
|
||||
* @returns {boolean} Whether the database exists
|
||||
*/
|
||||
export function exists(indexedDB, dbName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let exists = true;
|
||||
const req = indexedDB.open(dbName);
|
||||
req.onupgradeneeded = () => {
|
||||
// Since we did not provide an explicit version when opening, this event
|
||||
// should only fire if the DB did not exist before at any version.
|
||||
exists = false;
|
||||
};
|
||||
req.onblocked = () => reject();
|
||||
req.onsuccess = () => {
|
||||
const db = req.result;
|
||||
db.close();
|
||||
if (!exists) {
|
||||
// The DB did not exist before, but has been created as part of this
|
||||
// existence check. Delete it now to restore previous state. Delete can
|
||||
// actually take a while to complete in some browsers, so don't wait for
|
||||
// it. This won't block future open calls that a store might issue next to
|
||||
// properly set up the DB.
|
||||
indexedDB.deleteDatabase(dbName);
|
||||
}
|
||||
resolve(exists);
|
||||
};
|
||||
req.onerror = ev => reject(ev.target.error);
|
||||
});
|
||||
}
|
||||
+10
-4
@@ -22,8 +22,14 @@ module.exports.ContentHelpers = require("./content-helpers");
|
||||
module.exports.MatrixEvent = require("./models/event").MatrixEvent;
|
||||
/** The {@link module:models/event.EventStatus|EventStatus} enum. */
|
||||
module.exports.EventStatus = require("./models/event").EventStatus;
|
||||
/** The {@link module:store/memory.MatrixInMemoryStore|MatrixInMemoryStore} class. */
|
||||
module.exports.MatrixInMemoryStore = require("./store/memory").MatrixInMemoryStore;
|
||||
/** The {@link module:store/memory.MemoryStore|MemoryStore} class. */
|
||||
module.exports.MemoryStore = require("./store/memory").MemoryStore;
|
||||
/**
|
||||
* The {@link module:store/memory.MemoryStore|MemoryStore} class was previously
|
||||
* exported as `MatrixInMemoryStore`, so this is preserved for SDK consumers.
|
||||
* @deprecated Prefer `MemoryStore` going forward.
|
||||
*/
|
||||
module.exports.MatrixInMemoryStore = module.exports.MemoryStore;
|
||||
/** The {@link module:store/indexeddb.IndexedDBStore|IndexedDBStore} class. */
|
||||
module.exports.IndexedDBStore = require("./store/indexeddb").IndexedDBStore;
|
||||
/** The {@link module:store/indexeddb.IndexedDBStoreBackend|IndexedDBStoreBackend} class. */
|
||||
@@ -164,7 +170,7 @@ module.exports.setCryptoStoreFactory = function(fac) {
|
||||
* this is a string, it is assumed to be the base URL. These configuration
|
||||
* options will be passed directly to {@link module:client~MatrixClient}.
|
||||
* @param {Object} opts.store If not set, defaults to
|
||||
* {@link module:store/memory.MatrixInMemoryStore}.
|
||||
* {@link module:store/memory.MemoryStore}.
|
||||
* @param {Object} opts.scheduler If not set, defaults to
|
||||
* {@link module:scheduler~MatrixScheduler}.
|
||||
* @param {requestFunction} opts.request If not set, defaults to the function
|
||||
@@ -187,7 +193,7 @@ module.exports.createClient = function(opts) {
|
||||
};
|
||||
}
|
||||
opts.request = opts.request || request;
|
||||
opts.store = opts.store || new module.exports.MatrixInMemoryStore({
|
||||
opts.store = opts.store || new module.exports.MemoryStore({
|
||||
localStorage: global.localStorage,
|
||||
});
|
||||
opts.scheduler = opts.scheduler || new module.exports.MatrixScheduler();
|
||||
|
||||
@@ -260,6 +260,8 @@ Room.prototype.getRecommendedVersion = async function() {
|
||||
}
|
||||
|
||||
const currentVersion = this.getVersion();
|
||||
console.log(`[${this.roomId}] Current version: ${currentVersion}`);
|
||||
console.log(`[${this.roomId}] Version capability: `, versionCap);
|
||||
|
||||
const result = {
|
||||
version: currentVersion,
|
||||
@@ -280,6 +282,11 @@ Room.prototype.getRecommendedVersion = async function() {
|
||||
result.version = versionCap.default;
|
||||
result.needsUpgrade = true;
|
||||
result.urgent = !!this.getVersion().match(/^[0-9]+[0-9.]*$/g);
|
||||
if (result.urgent) {
|
||||
console.warn(`URGENT upgrade required on ${this.roomId}`);
|
||||
} else {
|
||||
console.warn(`Non-urgent upgrade required on ${this.roomId}`);
|
||||
}
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
import Promise from 'bluebird';
|
||||
import SyncAccumulator from "../sync-accumulator";
|
||||
import utils from "../utils";
|
||||
import * as IndexedDBHelpers from "../indexeddb-helpers";
|
||||
|
||||
const VERSION = 3;
|
||||
|
||||
@@ -132,6 +133,10 @@ const LocalIndexedDBStoreBackend = function LocalIndexedDBStoreBackend(
|
||||
this._isNewlyCreated = false;
|
||||
};
|
||||
|
||||
LocalIndexedDBStoreBackend.exists = function(indexedDB, dbName) {
|
||||
dbName = "matrix-js-sdk:" + (dbName || "default");
|
||||
return IndexedDBHelpers.exists(indexedDB, dbName);
|
||||
};
|
||||
|
||||
LocalIndexedDBStoreBackend.prototype = {
|
||||
/**
|
||||
|
||||
+11
-7
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import Promise from 'bluebird';
|
||||
import {MatrixInMemoryStore} from "./memory";
|
||||
import {MemoryStore} from "./memory";
|
||||
import utils from "../utils";
|
||||
import LocalIndexedDBStoreBackend from "./indexeddb-local-backend.js";
|
||||
import RemoteIndexedDBStoreBackend from "./indexeddb-remote-backend.js";
|
||||
@@ -37,9 +37,9 @@ const WRITE_DELAY_MS = 1000 * 60 * 5; // once every 5 minutes
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new Indexed Database store, which extends MatrixInMemoryStore.
|
||||
* Construct a new Indexed Database store, which extends MemoryStore.
|
||||
*
|
||||
* This store functions like a MatrixInMemoryStore except it periodically persists
|
||||
* This store functions like a MemoryStore except it periodically persists
|
||||
* the contents of the store to an IndexedDB backend.
|
||||
*
|
||||
* All data is still kept in-memory but can be loaded from disk by calling
|
||||
@@ -62,7 +62,7 @@ const WRITE_DELAY_MS = 1000 * 60 * 5; // once every 5 minutes
|
||||
* </pre>
|
||||
*
|
||||
* @constructor
|
||||
* @extends MatrixInMemoryStore
|
||||
* @extends MemoryStore
|
||||
* @param {Object} opts Options object.
|
||||
* @param {Object} opts.indexedDB The Indexed DB interface e.g.
|
||||
* <code>window.indexedDB</code>
|
||||
@@ -79,7 +79,7 @@ const WRITE_DELAY_MS = 1000 * 60 * 5; // once every 5 minutes
|
||||
* database.
|
||||
*/
|
||||
const IndexedDBStore = function IndexedDBStore(opts) {
|
||||
MatrixInMemoryStore.call(this, opts);
|
||||
MemoryStore.call(this, opts);
|
||||
|
||||
if (!opts.indexedDB) {
|
||||
throw new Error('Missing required option: indexedDB');
|
||||
@@ -109,7 +109,11 @@ const IndexedDBStore = function IndexedDBStore(opts) {
|
||||
// user_id : timestamp
|
||||
};
|
||||
};
|
||||
utils.inherits(IndexedDBStore, MatrixInMemoryStore);
|
||||
utils.inherits(IndexedDBStore, MemoryStore);
|
||||
|
||||
IndexedDBStore.exists = function(indexedDB, dbName) {
|
||||
return LocalIndexedDBStoreBackend.exists(indexedDB, dbName);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Promise} Resolved when loaded from indexed db.
|
||||
@@ -164,7 +168,7 @@ IndexedDBStore.prototype.getSavedSyncToken = function() {
|
||||
* @return {Promise} Resolves if the data was deleted from the database.
|
||||
*/
|
||||
IndexedDBStore.prototype.deleteAllData = function() {
|
||||
MatrixInMemoryStore.prototype.deleteAllData.call(this);
|
||||
MemoryStore.prototype.deleteAllData.call(this);
|
||||
return this.backend.clearDatabase().then(() => {
|
||||
console.log("Deleted indexeddb data.");
|
||||
}, (err) => {
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
*/
|
||||
"use strict";
|
||||
/**
|
||||
* This is an internal module. See {@link MatrixInMemoryStore} for the public class.
|
||||
* This is an internal module. See {@link MemoryStore} for the public class.
|
||||
* @module store/memory
|
||||
*/
|
||||
const utils = require("../utils");
|
||||
@@ -31,7 +31,7 @@ import Promise from 'bluebird';
|
||||
* @param {LocalStorage} opts.localStorage The local storage instance to persist
|
||||
* some forms of data such as tokens. Rooms will NOT be stored.
|
||||
*/
|
||||
module.exports.MatrixInMemoryStore = function MatrixInMemoryStore(opts) {
|
||||
module.exports.MemoryStore = function MemoryStore(opts) {
|
||||
opts = opts || {};
|
||||
this.rooms = {
|
||||
// roomId: Room
|
||||
@@ -58,7 +58,7 @@ module.exports.MatrixInMemoryStore = function MatrixInMemoryStore(opts) {
|
||||
this._clientOptions = {};
|
||||
};
|
||||
|
||||
module.exports.MatrixInMemoryStore.prototype = {
|
||||
module.exports.MemoryStore.prototype = {
|
||||
|
||||
/**
|
||||
* Retrieve the token to stream from.
|
||||
|
||||
@@ -724,6 +724,9 @@ SyncApi.prototype._sync = async function(syncOptions) {
|
||||
// log the exception with stack if we have it, else fall back
|
||||
// to the plain description
|
||||
console.error("Caught /sync error", e.stack || e);
|
||||
|
||||
// Emit the exception for client handling
|
||||
this.client.emit("sync.unexpectedError", e);
|
||||
}
|
||||
|
||||
// update this as it may have changed
|
||||
|
||||
@@ -3117,10 +3117,10 @@ math-random@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
|
||||
integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w=
|
||||
|
||||
matrix-mock-request@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-1.2.2.tgz#2710bec0e009bb1562d92cdd7f0b77d5946b2ebd"
|
||||
integrity sha512-9u86m6rOsKekNkqUUkStWXNULrY9G+9ibwolfrmgqTmgR76EGCr9fovM+JPWn4U+TbrewvHMALpPw8OxRg0ExA==
|
||||
matrix-mock-request@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/matrix-mock-request/-/matrix-mock-request-1.2.3.tgz#56b15d86e2601a9b48a854844396d18caab649c8"
|
||||
integrity sha512-Tr7LDHweTW8Ql4C8XhGQFGMzuh+HmPjOcQqrHH1qfSesq0cwdPWanvdnllNjeHoAMcZ43HpMFMzFZfNW1/6HYg==
|
||||
dependencies:
|
||||
bluebird "^3.5.0"
|
||||
expect "^1.20.2"
|
||||
|
||||
Reference in New Issue
Block a user