diff options
46 files changed, 2288 insertions, 1140 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 684bc03d..af2765c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,254 @@ +Changes in [6.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.2.0) (2020-06-04) +================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.2.0-rc.1...v6.2.0) + + * No changes since rc.1 + +Changes in [6.2.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.2.0-rc.1) (2020-06-02) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.1.0...v6.2.0-rc.1) + + * Make auth argument in the register request compliant with r0.6.0 + [\#1304](https://github.com/matrix-org/matrix-js-sdk/pull/1304) + * Send the wrong auth params with the right auth params + [\#1393](https://github.com/matrix-org/matrix-js-sdk/pull/1393) + * encrypt cached keys with pickle key + [\#1387](https://github.com/matrix-org/matrix-js-sdk/pull/1387) + * Fix replying to key share requests + [\#1385](https://github.com/matrix-org/matrix-js-sdk/pull/1385) + * Add dist to package.json files so CDNs can serve it + [\#1384](https://github.com/matrix-org/matrix-js-sdk/pull/1384) + * Fix getVersion warning saying undefined room + [\#1382](https://github.com/matrix-org/matrix-js-sdk/pull/1382) + * Combine the two places we processed client-level default push rules + [\#1379](https://github.com/matrix-org/matrix-js-sdk/pull/1379) + * make MAC check robust against unpadded vs padded base64 differences + [\#1378](https://github.com/matrix-org/matrix-js-sdk/pull/1378) + * Remove key backup format migration + [\#1375](https://github.com/matrix-org/matrix-js-sdk/pull/1375) + * Add simple browserify browser-matrix.js tests + [\#1241](https://github.com/matrix-org/matrix-js-sdk/pull/1241) + * support new key agreement method for SAS + [\#1376](https://github.com/matrix-org/matrix-js-sdk/pull/1376) + +Changes in [6.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.1.0) (2020-05-19) +================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.1.0-rc.1...v6.1.0) + + * No changes since rc.1 + +Changes in [6.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v6.1.0-rc.1) (2020-05-14) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v6.0.0...v6.1.0-rc.1) + + * Remove support for asymmetric 4S encryption + [\#1373](https://github.com/matrix-org/matrix-js-sdk/pull/1373) + * Increase timeout for 2nd phase of Olm session creation + [\#1367](https://github.com/matrix-org/matrix-js-sdk/pull/1367) + * Add logging on decryption retries + [\#1366](https://github.com/matrix-org/matrix-js-sdk/pull/1366) + * Emit event when a trusted self-key is stored + [\#1364](https://github.com/matrix-org/matrix-js-sdk/pull/1364) + * Customize error payload for oversized messages + [\#1352](https://github.com/matrix-org/matrix-js-sdk/pull/1352) + * Return null for key backup state when we haven't checked yet + [\#1363](https://github.com/matrix-org/matrix-js-sdk/pull/1363) + * Added a progressCallback for backup key loading + [\#1351](https://github.com/matrix-org/matrix-js-sdk/pull/1351) + * Add initialFetch param to willUpdateDevices / devicesUpdated + [\#1360](https://github.com/matrix-org/matrix-js-sdk/pull/1360) + * Fix race between sending .request and receiving .ready over to_device + [\#1359](https://github.com/matrix-org/matrix-js-sdk/pull/1359) + * Handle race between sending and await next event from other party + [\#1357](https://github.com/matrix-org/matrix-js-sdk/pull/1357) + * Add crypto.willUpdateDevices event and make + getStoredDevices/getStoredDevicesForUser synchronous + [\#1354](https://github.com/matrix-org/matrix-js-sdk/pull/1354) + * Fix sender of local echo events in unsigned redactions + [\#1350](https://github.com/matrix-org/matrix-js-sdk/pull/1350) + * Remove redundant key backup setup path + [\#1353](https://github.com/matrix-org/matrix-js-sdk/pull/1353) + * Remove some dead code from _retryDecryption + [\#1349](https://github.com/matrix-org/matrix-js-sdk/pull/1349) + * Don't send key requests until after sync processing is finished + [\#1348](https://github.com/matrix-org/matrix-js-sdk/pull/1348) + * Prevent attempts to send olm messages to ourselves + [\#1346](https://github.com/matrix-org/matrix-js-sdk/pull/1346) + * Retry account data upload requests + [\#1345](https://github.com/matrix-org/matrix-js-sdk/pull/1345) + * Log first known index with megolm session updates + [\#1344](https://github.com/matrix-org/matrix-js-sdk/pull/1344) + * Prune to_device messages to avoid sending empty messages + [\#1343](https://github.com/matrix-org/matrix-js-sdk/pull/1343) + * Convert bunch of things to TypeScript + [\#1335](https://github.com/matrix-org/matrix-js-sdk/pull/1335) + * Add logging when making new Olm sessions + [\#1342](https://github.com/matrix-org/matrix-js-sdk/pull/1342) + * Fix: handle filter not found + [\#1340](https://github.com/matrix-org/matrix-js-sdk/pull/1340) + * Make getAccountDataFromServer return null if not found + [\#1338](https://github.com/matrix-org/matrix-js-sdk/pull/1338) + * Fix setDefaultKeyId to fail if the request fails + [\#1336](https://github.com/matrix-org/matrix-js-sdk/pull/1336) + * Document setRoomEncryption not modifying room state + [\#1328](https://github.com/matrix-org/matrix-js-sdk/pull/1328) + * Fix: don't do extra /filter request when enabling lazy loading of members + [\#1332](https://github.com/matrix-org/matrix-js-sdk/pull/1332) + * Reject attemptAuth promise if no auth flow found + [\#1329](https://github.com/matrix-org/matrix-js-sdk/pull/1329) + * Fix FilterComponent allowed_values check + [\#1327](https://github.com/matrix-org/matrix-js-sdk/pull/1327) + * Serialise Olm prekey decryptions + [\#1326](https://github.com/matrix-org/matrix-js-sdk/pull/1326) + * Fix: crash when backup key needs fixing from corruption issue + [\#1324](https://github.com/matrix-org/matrix-js-sdk/pull/1324) + * Fix cross-signing/SSSS reset + [\#1322](https://github.com/matrix-org/matrix-js-sdk/pull/1322) + * Implement QR code reciprocate for self-verification with untrusted MSK + [\#1320](https://github.com/matrix-org/matrix-js-sdk/pull/1320) + +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) + + * Adapt release script for riot-desktop + [\#1319](https://github.com/matrix-org/matrix-js-sdk/pull/1319) + * Fix: prevent spurious notifications from indexer + [\#1318](https://github.com/matrix-org/matrix-js-sdk/pull/1318) + * Always create our own user object + [\#1317](https://github.com/matrix-org/matrix-js-sdk/pull/1317) + * Fix incorrect backup key format in SSSS + [\#1311](https://github.com/matrix-org/matrix-js-sdk/pull/1311) + * Fix e2ee crash after refreshing after having received a cross-singing key + reset + [\#1315](https://github.com/matrix-org/matrix-js-sdk/pull/1315) + * Fix: catch send errors in SAS verifier + [\#1314](https://github.com/matrix-org/matrix-js-sdk/pull/1314) + * Clear cross-signing keys when detecting the keys have changed + [\#1312](https://github.com/matrix-org/matrix-js-sdk/pull/1312) + * Upgrade deps + [\#1310](https://github.com/matrix-org/matrix-js-sdk/pull/1310) + +Changes in [5.3.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.3.0-rc.1) (2020-04-08) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.2.0...v5.3.0-rc.1) + + * Store key backup key in cache as Uint8Array + [\#1308](https://github.com/matrix-org/matrix-js-sdk/pull/1308) + * Use the correct request body for the /keys/query endpoint. + [\#1307](https://github.com/matrix-org/matrix-js-sdk/pull/1307) + * Avoid creating two devices on registration + [\#1305](https://github.com/matrix-org/matrix-js-sdk/pull/1305) + * Lower max-warnings to 81 + [\#1306](https://github.com/matrix-org/matrix-js-sdk/pull/1306) + * Move key backup key creation before caching + [\#1303](https://github.com/matrix-org/matrix-js-sdk/pull/1303) + * Expose function to force-reset outgoing room key requests + [\#1298](https://github.com/matrix-org/matrix-js-sdk/pull/1298) + * Add isSelfVerification property to VerificationRequest + [\#1302](https://github.com/matrix-org/matrix-js-sdk/pull/1302) + * QR code reciprocation + [\#1297](https://github.com/matrix-org/matrix-js-sdk/pull/1297) + * Add ability to check symmetric SSSS key before we try to use it + [\#1294](https://github.com/matrix-org/matrix-js-sdk/pull/1294) + * Add some debug logging for events stuck to bottom of timeline + [\#1296](https://github.com/matrix-org/matrix-js-sdk/pull/1296) + * Fix: spontanous verification request cancellation under some circumstances + [\#1295](https://github.com/matrix-org/matrix-js-sdk/pull/1295) + * Receive private key for caching from the app layer + [\#1293](https://github.com/matrix-org/matrix-js-sdk/pull/1293) + * Track whether we have verified a user before + [\#1292](https://github.com/matrix-org/matrix-js-sdk/pull/1292) + * Fix: error during tests + [\#1222](https://github.com/matrix-org/matrix-js-sdk/pull/1222) + * Send .done event for to_device verification + [\#1288](https://github.com/matrix-org/matrix-js-sdk/pull/1288) + * Request the key backup key & restore backup + [\#1291](https://github.com/matrix-org/matrix-js-sdk/pull/1291) + * Make screen sharing works on Chrome using getDisplayMedia() + [\#1276](https://github.com/matrix-org/matrix-js-sdk/pull/1276) + * Fix isVerified returning false + [\#1289](https://github.com/matrix-org/matrix-js-sdk/pull/1289) + * Fix: verification gets cancelled when event gets duplicated + [\#1286](https://github.com/matrix-org/matrix-js-sdk/pull/1286) + * Use requestSecret on the client to request secrets + [\#1287](https://github.com/matrix-org/matrix-js-sdk/pull/1287) + * Allow guests to fetch TURN servers + [\#1277](https://github.com/matrix-org/matrix-js-sdk/pull/1277) + Changes in [5.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v5.2.0) (2020-03-30) ================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v5.2.0-rc.1...v5.2.0) diff --git a/package.json b/package.json index 5e7f80bd..f19df47a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "5.2.0", + "version": "6.2.0", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepare": "yarn build", @@ -35,6 +35,7 @@ "author": "matrix.org", "license": "Apache-2.0", "files": [ + "dist", "lib", "src", "git-revision.txt", @@ -68,6 +69,7 @@ "@babel/preset-typescript": "^7.7.4", "@babel/register": "^7.7.4", "@types/node": "12", + "@types/request": "^2.48.4", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", "babelify": "^10.0.0", @@ -38,6 +38,7 @@ $USAGE -c changelog_file: specify name of file containing changelog -x: skip updating the changelog -z: skip generating the jsdoc + -n: skip publish to NPM EOF } @@ -60,9 +61,10 @@ fi skip_changelog= skip_jsdoc= +skip_npm= changelog_file="CHANGELOG.md" expected_npm_user="matrixdotorg" -while getopts hc:u:xz f; do +while getopts hc:u:xzn f; do case $f in h) help @@ -77,6 +79,9 @@ while getopts hc:u:xz f; do z) skip_jsdoc=1 ;; + n) + skip_npm=1 + ;; u) expected_npm_user="$OPTARG" ;; @@ -96,10 +101,12 @@ fi # Login and publish continues to use `npm`, as it seems to have more clearly # defined options and semantics than `yarn` for writing to the registry. -actual_npm_user=`npm whoami`; -if [ $expected_npm_user != $actual_npm_user ]; then - echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2 - exit 1 +if [ -z "$skip_npm" ]; then + actual_npm_user=`npm whoami`; + if [ $expected_npm_user != $actual_npm_user ]; then + echo "you need to be logged into npm as $expected_npm_user, but you are logged in as $actual_npm_user" >&2 + exit 1 + fi fi # ignore leading v on release @@ -298,11 +305,13 @@ rm "${latest_changes}" # defined options and semantics than `yarn` for writing to the registry. # Tag both releases and prereleases as `next` so the last stable release remains # the default. -npm publish --tag next -if [ $prerelease -eq 0 ]; then - # For a release, also add the default `latest` tag. - package=$(cat package.json | jq -er .name) - npm dist-tag add "[email protected]$release" latest +if [ -z "$skip_npm" ]; then + npm publish --tag next + if [ $prerelease -eq 0 ]; then + # For a release, also add the default `latest` tag. + package=$(cat package.json | jq -er .name) + npm dist-tag add "[email protected]$release" latest + fi fi if [ -z "$skip_jsdoc" ]; then @@ -338,8 +347,10 @@ if [ -z "$skip_jsdoc" ]; then git push origin gh-pages fi -# finally, merge master back onto develop -git checkout develop -git pull -git merge master -git push origin develop +# finally, merge master back onto develop (if it exists) +if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then + git checkout develop + git pull + git merge master + git push origin develop +fi diff --git a/spec/browserify/setupTests.js b/spec/browserify/setupTests.js new file mode 100644 index 00000000..16120f78 --- /dev/null +++ b/spec/browserify/setupTests.js @@ -0,0 +1,23 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// stub for browser-matrix browserify tests +global.XMLHttpRequest = jest.fn(); + +afterAll(() => { + // clean up XMLHttpRequest mock + global.XMLHttpRequest = undefined; +}); diff --git a/spec/browserify/sync-browserify.spec.js b/spec/browserify/sync-browserify.spec.js new file mode 100644 index 00000000..c2054d7f --- /dev/null +++ b/spec/browserify/sync-browserify.spec.js @@ -0,0 +1,103 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// load XmlHttpRequest mock +import "./setupTests"; +import "../../dist/browser-matrix"; // uses browser-matrix instead of the src +import {MockStorageApi} from "../MockStorageApi"; +import {WebStorageSessionStore} from "../../src/store/session/webstorage"; +import MockHttpBackend from "matrix-mock-request"; +import {LocalStorageCryptoStore} from "../../src/crypto/store/localStorage-crypto-store"; +import * as utils from "../test-utils"; + +const USER_ID = "@user:test.server"; +const DEVICE_ID = "device_id"; +const ACCESS_TOKEN = "access_token"; +const ROOM_ID = "!room_id:server.test"; + +/* global matrixcs */ + +describe("Browserify Test", function() { + let client; + let httpBackend; + + async function createTestClient() { + const sessionStoreBackend = new MockStorageApi(); + const sessionStore = new WebStorageSessionStore(sessionStoreBackend); + const httpBackend = new MockHttpBackend(); + + const options = { + baseUrl: "http://" + USER_ID + ".test.server", + userId: USER_ID, + accessToken: ACCESS_TOKEN, + deviceId: DEVICE_ID, + sessionStore: sessionStore, + request: httpBackend.requestFn, + cryptoStore: new LocalStorageCryptoStore(sessionStoreBackend), + }; + + const client = matrixcs.createClient(options); + + httpBackend.when("GET", "/pushrules").respond(200, {}); + httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" }); + + return { client, httpBackend }; + } + + beforeEach(async () => { + ({client, httpBackend} = await createTestClient()); + await client.startClient(); + }); + + afterEach(async () => { + client.stopClient(); + await httpBackend.stop(); + }); + + it("Sync", async function() { + const event = utils.mkMembership({ + room: ROOM_ID, + mship: "join", + user: "@other_user:server.test", + name: "Displayname", + }); + + const syncData = { + next_batch: "batch1", + rooms: { + join: {}, + }, + }; + syncData.rooms.join[ROOM_ID] = { + timeline: { + events: [ + event, + ], + limited: false, + }, + }; + + httpBackend.when("GET", "/sync").respond(200, syncData); + await Promise.race([ + Promise.all([ + httpBackend.flushAllExpected(), + ]), + new Promise((_, reject) => { + client.once("sync.unexpectedError", reject); + }), + ]); + }, 10000); +}); diff --git a/spec/integ/matrix-client-opts.spec.js b/spec/integ/matrix-client-opts.spec.js index 606674e4..7c256fd5 100644 --- a/spec/integ/matrix-client-opts.spec.js +++ b/spec/integ/matrix-client-opts.spec.js @@ -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) { diff --git a/spec/olm-loader.js b/spec/olm-loader.js index b06ecdff..22f049ae 100644 --- a/spec/olm-loader.js +++ b/spec/olm-loader.js @@ -16,6 +16,7 @@ limitations under the License. */ import {logger} from '../src/logger'; +import * as utils from "../src/utils"; // try to load the olm library. try { @@ -24,3 +25,11 @@ try { } catch (e) { logger.warn("unable to run crypto tests: libolm not available"); } + +// also try to set node crypto +try { + const crypto = require('crypto'); + utils.setCrypto(crypto); +} catch (err) { + console.log('nodejs was compiled without crypto support: some tests will fail'); +} diff --git a/spec/unit/crypto.spec.js b/spec/unit/crypto.spec.js index 9652f4b3..18cc930b 100644 --- a/spec/unit/crypto.spec.js +++ b/spec/unit/crypto.spec.js @@ -313,6 +313,10 @@ describe("Crypto", function() { // make a room key request, and record the transaction ID for the // sendToDevice call await aliceClient.cancelAndResendEventRoomKeyRequest(event); + // key requests get queued until the sync has finished, but we don't + // let the client set up enough for that to happen, so gut-wrench a bit + // to force it to send now. + aliceClient._crypto._outgoingRoomKeyRequestManager.sendQueuedRequests(); jest.runAllTimers(); await Promise.resolve(); expect(aliceClient.sendToDevice).toBeCalledTimes(1); diff --git a/spec/unit/crypto/CrossSigningInfo.spec.js b/spec/unit/crypto/CrossSigningInfo.spec.js index c49451ed..4c746147 100644 --- a/spec/unit/crypto/CrossSigningInfo.spec.js +++ b/spec/unit/crypto/CrossSigningInfo.spec.js @@ -25,6 +25,7 @@ import { import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store'; import 'fake-indexeddb/auto'; import 'jest-localstorage-mock'; +import {OlmDevice} from "../../../src/crypto/OlmDevice"; const userId = "@alice:example.com"; @@ -233,8 +234,9 @@ describe.each([ it("should cache data to the store and retrieve it", async () => { await store.startup(); + const olmDevice = new OlmDevice(store); const { getCrossSigningKeyCache, storeCrossSigningKeyCache } = - createCryptoStoreCacheCallbacks(store); + createCryptoStoreCacheCallbacks(store, olmDevice); await storeCrossSigningKeyCache("self_signing", testKey); // If we've not saved anything, don't expect anything @@ -243,6 +245,6 @@ describe.each([ expect(nokey).toBeNull(); const key = await getCrossSigningKeyCache("self_signing", ""); - expect(key).toEqual(testKey); + expect(new Uint8Array(key)).toEqual(testKey); }); }); diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index a7ee344a..e03460fe 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -546,7 +546,7 @@ describe("MegolmBackup", function() { const key = Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]); await client._crypto.storeSessionBackupPrivateKey(key); const result = await client._crypto.getSessionBackupPrivateKey(); - expect(result).toEqual(key); + expect(new Uint8Array(result)).toEqual(key); }); it('caches session backup keys as it encounters them', async function() { diff --git a/spec/unit/crypto/secrets.spec.js b/spec/unit/crypto/secrets.spec.js index 0a045935..74552a2e 100644 --- a/spec/unit/crypto/secrets.spec.js +++ b/spec/unit/crypto/secrets.spec.js @@ -49,6 +49,13 @@ async function makeTestClient(userInfo, options) { return client; } +// Wrapper around pkSign to return a signed object. pkSign returns the +// signature, rather than the signed object. +function sign(obj, key, userId) { + olmlib.pkSign(obj, key, userId); + return obj; +} + describe("Secrets", function() { if (!global.Olm) { console.warn('Not running megolm backup unit tests: libolm not present'); @@ -266,104 +273,259 @@ describe("Secrets", function() { expect(secret).toBe("bar"); }); - it("bootstraps when no storage or cross-signing keys locally", async function() { - const key = new Uint8Array(16); - for (let i = 0; i < 16; i++) key[i] = i; - const getKey = jest.fn(e => { - return [Object.keys(e.keys)[0], key]; - }); - - const bob = await makeTestClient( - { - userId: "@bob:example.com", - deviceId: "bob1", - }, - { - cryptoCallbacks: { - getSecretStorageKey: getKey, - }, - }, + describe("bootstrap", function() { + // keys used in some of the tests + const XSK = new Uint8Array( + olmlib.decodeBase64("3lo2YdJugHjfE+Or7KJ47NuKbhE7AAGLgQ/dc19913Q="), + ); + const XSPubKey = "DRb8pFVJyEJ9OWvXeUoM0jq/C2Wt+NxzBZVuk2nRb+0"; + const USK = new Uint8Array( + olmlib.decodeBase64("lKWi3hJGUie5xxHgySoz8PHFnZv6wvNaud/p2shN9VU="), + ); + const USPubKey = "CUpoiTtHiyXpUmd+3ohb7JVxAlUaOG1NYs9Jlx8soQU"; + const SSK = new Uint8Array( + olmlib.decodeBase64("1R6JVlXX99UcfUZzKuCDGQgJTw8ur1/ofgPD8pp+96M="), + ); + const SSPubKey = "0DfNsRDzEvkCLA0gD3m7VAGJ5VClhjEsewI35xq873Q"; + const SSSSKey = new Uint8Array( + olmlib.decodeBase64( + "XrmITOOdBhw6yY5Bh7trb/bgp1FRdIGyCUxxMP873R0=", + ), ); - bob.uploadDeviceSigningKeys = async () => {}; - bob.uploadKeySignatures = async () => {}; - bob.setAccountData = async function(eventType, contents, callback) { - const event = new MatrixEvent({ - type: eventType, - content: contents, - }); - this.store.storeAccountDataEvents([ - event, - ]); - this.emit("accountData", event); - }; - - await bob.bootstrapSecretStorage(); - const crossSigning = bob._crypto._crossSigningInfo; - const secretStorage = bob._crypto._secretStorage; + it("bootstraps when no storage or cross-signing keys locally", async function() { + const key = new Uint8Array(16); + for (let i = 0; i < 16; i++) key[i] = i; + const getKey = jest.fn(e => { + return [Object.keys(e.keys)[0], key]; + }); - expect(crossSigning.getId()).toBeTruthy(); - expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy(); - expect(await secretStorage.hasKey()).toBeTruthy(); - }); + const bob = await makeTestClient( + { + userId: "@bob:example.com", + deviceId: "bob1", + }, + { + cryptoCallbacks: { + getSecretStorageKey: getKey, + }, + }, + ); + bob.uploadDeviceSigningKeys = async () => {}; + bob.uploadKeySignatures = async () => {}; + bob.setAccountData = async function(eventType, contents, callback) { + const event = new MatrixEvent({ + type: eventType, + content: contents, + }); + this.store.storeAccountDataEvents([ + event, + ]); + this.emit("accountData", event); + }; + + await bob.bootstrapSecretStorage(); + + const crossSigning = bob._crypto._crossSigningInfo; + const secretStorage = bob._crypto._secretStorage; + + expect(crossSigning.getId()).toBeTruthy(); + expect(await crossSigning.isStoredInSecretStorage(secretStorage)) + .toBeTruthy(); + expect(await secretStorage.hasKey()).toBeTruthy(); + }); - it("bootstraps when cross-signing keys in secret storage", async function() { - const decryption = new global.Olm.PkDecryption(); - const storagePublicKey = decryption.generate_key(); - const storagePrivateKey = decryption.get_private_key(); + it("bootstraps when cross-signing keys in secret storage", async function() { + const decryption = new global.Olm.PkDecryption(); + const storagePublicKey = decryption.generate_key(); + const storagePrivateKey = decryption.get_private_key(); - const bob = await makeTestClient( - { - userId: "@bob:example.com", - deviceId: "bob1", - }, - { - cryptoCallbacks: { - getSecretStorageKey: async request => { - const defaultKeyId = await bob.getDefaultSecretStorageKeyId(); - expect(Object.keys(request.keys)).toEqual([defaultKeyId]); - return [defaultKeyId, storagePrivateKey]; + const bob = await makeTestClient( + { + userId: "@bob:example.com", + deviceId: "bob1", + }, + { + cryptoCallbacks: { + getSecretStorageKey: async request => { + const defaultKeyId = await bob.getDefaultSecretStorageKeyId(); + expect(Object.keys(request.keys)).toEqual([defaultKeyId]); + return [defaultKeyId, storagePrivateKey]; + }, }, }, - }, - ); + ); - bob.uploadDeviceSigningKeys = async () => {}; - bob.uploadKeySignatures = async () => {}; - bob.setAccountData = async function(eventType, contents, callback) { - const event = new MatrixEvent({ - type: eventType, - content: contents, + bob.uploadDeviceSigningKeys = async () => {}; + bob.uploadKeySignatures = async () => {}; + bob.setAccountData = async function(eventType, contents, callback) { + const event = new MatrixEvent({ + type: eventType, + content: contents, + }); + this.store.storeAccountDataEvents([ + event, + ]); + this.emit("accountData", event); + }; + bob._crypto.checkKeyBackup = async () => {}; + + const crossSigning = bob._crypto._crossSigningInfo; + const secretStorage = bob._crypto._secretStorage; + + // Set up cross-signing keys from scratch with specific storage key + await bob.bootstrapSecretStorage({ + createSecretStorageKey: async () => ({ + // `pubkey` not used anymore with symmetric 4S + keyInfo: { pubkey: storagePublicKey }, + privateKey: storagePrivateKey, + }), }); - this.store.storeAccountDataEvents([ - event, - ]); - this.emit("accountData", event); - }; - bob._crypto.checkKeyBackup = async () => {}; - const crossSigning = bob._crypto._crossSigningInfo; - const secretStorage = bob._crypto._secretStorage; - - // Set up cross-signing keys from scratch with specific storage key - await bob.bootstrapSecretStorage({ - createSecretStorageKey: async () => ({ - // `pubkey` not used anymore with symmetric 4S - keyInfo: { pubkey: storagePublicKey }, - privateKey: storagePrivateKey, - }), + // Clear local cross-signing keys and read from secret storage + bob._crypto._deviceList.storeCrossSigningForUser( + "@bob:example.com", + crossSigning.toStorage(), + ); + crossSigning.keys = {}; + await bob.bootstrapSecretStorage(); + + expect(crossSigning.getId()).toBeTruthy(); + expect(await crossSigning.isStoredInSecretStorage(secretStorage)) + .toBeTruthy(); + expect(await secretStorage.hasKey()).toBeTruthy(); }); - // Clear local cross-signing keys and read from secret storage - bob._crypto._deviceList.storeCrossSigningForUser( - "@bob:example.com", - crossSigning.toStorage(), - ); - crossSigning.keys = {}; - await bob.bootstrapSecretStorage(); - - expect(crossSigning.getId()).toBeTruthy(); - expect(await crossSigning.isStoredInSecretStorage(secretStorage)).toBeTruthy(); - expect(await secretStorage.hasKey()).toBeTruthy(); + it("adds passphrase checking if it's lacking", async function() { + let crossSigningKeys = { + master: XSK, + user_signing: USK, + self_signing: SSK, + }; + const secretStorageKeys = { + key_id: SSSSKey, + }; + const alice = await makeTestClient( + {userId: "@alice:example.com", deviceId: "Osborne2"}, + { + cryptoCallbacks: { + getCrossSigningKey: t => crossSigningKeys[t], + saveCrossSigningKeys: k => crossSigningKeys = k, + getSecretStorageKey: ({keys}, name) => { + for (const keyId of Object.keys(keys)) { + if (secretStorageKeys[keyId]) { + return [keyId, secretStorageKeys[keyId]]; + } + } + }, + }, + }, + ); + alice.store.storeAccountDataEvents([ + new MatrixEvent({ + type: "m.secret_storage.default_key", + content: { + key: "key_id", + }, + }), + new MatrixEvent({ + type: "m.secret_storage.key.key_id", + content: { + algorithm: "m.secret_storage.v1.aes-hmac-sha2", + passphrase: { + algorithm: "m.pbkdf2", + iterations: 500000, + salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK", + }, + }, + }), + // we never use these values, other than checking that they + // exist, so just use dummy values + new MatrixEvent({ + type: "m.cross_signing.master", + content: { + encrypted: { + key_id: {ciphertext: "bla", mac: "bla", iv: "bla"}, + }, + }, + }), + new MatrixEvent({ + type: "m.cross_signing.self_signing", + content: { + encrypted: { + key_id: {ciphertext: "bla", mac: "bla", iv: "bla"}, + }, + }, + }), + new MatrixEvent({ + type: "m.cross_signing.user_signing", + content: { + encrypted: { + key_id: {ciphertext: "bla", mac: "bla", iv: "bla"}, + }, + }, + }), + ]); + alice._crypto._deviceList.storeCrossSigningForUser("@alice:example.com", { + keys: { + master: { + user_id: "@alice:example.com", + usage: ["master"], + keys: { + [`ed25519:${XSPubKey}`]: XSPubKey, + }, + }, + self_signing: sign({ + user_id: "@alice:example.com", + usage: ["self_signing"], + keys: { + [`ed25519:${SSPubKey}`]: SSPubKey, + }, + }, XSK, "@alice:example.com"), + user_signing: sign({ + user_id: "@alice:example.com", + usage: ["user_signing"], + keys: { + [`ed25519:${USPubKey}`]: USPubKey, + }, + }, XSK, "@alice:example.com"), + }, + }); + alice.getKeyBackupVersion = async () => { + return { + version: "1", + algorithm: "m.megolm_backup.v1.curve25519-aes-sha2", + auth_data: sign({ + public_key: "pxEXhg+4vdMf/kFwP4bVawFWdb0EmytL3eFJx++zQ0A", + }, XSK, "@alice:example.com"), + }; + }; + alice.setAccountData = async function(name, data) { + const event = new MatrixEvent({ + type: name, + content: data, + }); + alice.store.storeAccountDataEvents([event]); + this.emit("accountData", event); + }; + + await alice.bootstrapSecretStorage(); + + expect(alice.getAccountData("m.secret_storage.default_key").getContent()) + .toEqual({key: "key_id"}); + const keyInfo = alice.getAccountData("m.secret_storage.key.key_id") + .getContent(); + expect(keyInfo.algorithm) + .toEqual("m.secret_storage.v1.aes-hmac-sha2"); + expect(keyInfo.passphrase).toEqual({ + algorithm: "m.pbkdf2", + iterations: 500000, + salt: "GbkvwKHVMveo1zGVSb2GMMdCinG2npJK", + }); + expect(keyInfo).toHaveProperty("iv"); + expect(keyInfo).toHaveProperty("mac"); + expect(alice.checkSecretStorageKey(secretStorageKeys.key_id, keyInfo)) + .toBeTruthy(); + }); }); }); diff --git a/spec/unit/crypto/verification/sas.spec.js b/spec/unit/crypto/verification/sas.spec.js index bea6c450..88f424fe 100644 --- a/spec/unit/crypto/verification/sas.spec.js +++ b/spec/unit/crypto/verification/sas.spec.js @@ -44,7 +44,16 @@ describe("SAS verification", function() { }); it("should error on an unexpected event", async function() { - const sas = new SAS({}, "@alice:example.com", "ABCDEFG"); + //channel, baseApis, userId, deviceId, startEvent, request + const request = { + onVerifierCancelled: function() {}, + }; + const channel = { + send: function() { + return Promise.resolve(); + }, + }; + const sas = new SAS(channel, {}, "@alice:example.com", "ABCDEFG", null, request); sas.handleEvent(new MatrixEvent({ sender: "@alice:example.com", type: "es.inquisition", @@ -172,11 +181,14 @@ describe("SAS verification", function() { it("should verify a key", async () => { let macMethod; + let keyAgreement; const origSendToDevice = bob.client.sendToDevice.bind(bob.client); bob.client.sendToDevice = function(type, map) { if (type === "m.key.verification.accept") { macMethod = map[alice.client.getUserId()][alice.client.deviceId] .message_authentication_code; + keyAgreement = map[alice.client.getUserId()][alice.client.deviceId] + .key_agreement_protocol; } return origSendToDevice(type, map); }; @@ -203,6 +215,7 @@ describe("SAS verification", function() { // make sure that it uses the preferred method expect(macMethod).toBe("hkdf-hmac-sha256"); + expect(keyAgreement).toBe("curve25519-hkdf-sha256"); // make sure Alice and Bob verified each other const bobDevice diff --git a/spec/unit/crypto/verification/verification_request.spec.js b/spec/unit/crypto/verification/verification_request.spec.js index ac9b0f8d..a90d0482 100644 --- a/spec/unit/crypto/verification/verification_request.spec.js +++ b/spec/unit/crypto/verification/verification_request.spec.js @@ -119,6 +119,8 @@ async function distributeEvent(ownRequest, theirRequest, event) { await theirRequest.channel.handleEvent(event, theirRequest, true); } +jest.useFakeTimers(); + describe("verification request unit tests", function() { beforeAll(function() { setupWebcrypto(); @@ -246,4 +248,38 @@ describe("verification request unit tests", function() { expect(bob1Request.done).toBe(true); expect(bob2Request.done).toBe(true); }); + + it("request times out after 10 minutes", async function() { + const alice = makeMockClient("@alice:matrix.tld", "device1"); + const bob = makeMockClient("@bob:matrix.tld", "device1"); + const aliceRequest = new VerificationRequest( + new InRoomChannel(alice, "!room", bob.getUserId()), new Map(), alice); + await aliceRequest.sendRequest(); + const [requestEvent] = alice.popEvents(); + await aliceRequest.channel.handleEvent(requestEvent, aliceRequest, true, + true, true); + + expect(aliceRequest.cancelled).toBe(false); + expect(aliceRequest._cancellingUserId).toBe(undefined); + jest.advanceTimersByTime(10 * 60 * 1000); + expect(aliceRequest._cancellingUserId).toBe(alice.getUserId()); + }); + + it("request times out 2 minutes after receipt", async function() { + const alice = makeMockClient("@alice:matrix.tld", "device1"); + const bob = makeMockClient("@bob:matrix.tld", "device1"); + const aliceRequest = new VerificationRequest( + new InRoomChannel(alice, "!room", bob.getUserId()), new Map(), alice); + await aliceRequest.sendRequest(); + const [requestEvent] = alice.popEvents(); + const bobRequest = new VerificationRequest( + new InRoomChannel(bob, "!room"), new Map(), bob); + + await bobRequest.channel.handleEvent(requestEvent, bobRequest, true); + + expect(bobRequest.cancelled).toBe(false); + expect(bobRequest._cancellingUserId).toBe(undefined); + jest.advanceTimersByTime(2 * 60 * 1000); + expect(bobRequest._cancellingUserId).toBe(bob.getUserId()); + }); }); diff --git a/spec/unit/filter-component.spec.js b/spec/unit/filter-component.spec.js new file mode 100644 index 00000000..9f43b15f --- /dev/null +++ b/spec/unit/filter-component.spec.js @@ -0,0 +1,34 @@ +import {FilterComponent} from "../../src/filter-component"; +import {mkEvent} from '../test-utils'; + +describe("Filter Component", function() { + describe("types", function() { + it("should filter out events with other types", function() { + const filter = new FilterComponent({ types: ['m.room.message'] }); + const event = mkEvent({ + type: 'm.room.member', + content: { }, + room: 'roomId', + event: true, + }); + + const checkResult = filter.check(event); + + expect(checkResult).toBe(false); + }); + + it("should validate events with the same type", function() { + const filter = new FilterComponent({ types: ['m.room.message'] }); + const event = mkEvent({ + type: 'm.room.message', + content: { }, + room: 'roomId', + event: true, + }); + + const checkResult = filter.check(event); + + expect(checkResult).toBe(true); + }); + }); +}); diff --git a/spec/unit/interactive-auth.spec.js b/spec/unit/interactive-auth.spec.js index 46008b90..609d21eb 100644 --- a/spec/unit/interactive-auth.spec.js +++ b/spec/unit/interactive-auth.spec.js @@ -97,7 +97,7 @@ describe("InteractiveAuth", function() { // first we expect a call to doRequest doRequest.mockImplementation(function(authData) { logger.log("request1", authData); - expect(authData).toEqual({}); + expect(authData).toEqual(null); // first request should be null const err = new MatrixError({ session: "sessionId", flows: [ @@ -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(null); // first request should be null + 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'); + }); + }); }); diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts new file mode 100644 index 00000000..c8a13144 --- /dev/null +++ b/src/@types/global.d.ts @@ -0,0 +1,25 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export {}; + +declare global { + namespace NodeJS { + interface Global { + localStorage: Storage; + } + } +} diff --git a/src/base-apis.js b/src/base-apis.js index ce74ce8e..4b6a77b6 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -206,9 +206,6 @@ MatrixBaseApis.prototype.register = function( inhibitLogin = undefined; } - if (auth === undefined || auth === null) { - auth = {}; - } if (sessionId) { auth.session = sessionId; } diff --git a/src/client.js b/src/client.js index 64174c99..d390441e 100644 --- a/src/client.js +++ b/src/client.js @@ -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"; @@ -48,6 +53,8 @@ import {keyFromAuthData} from './crypto/key_passphrase'; import {randomString} from './randomstring'; import {PushProcessor} from "./pushprocessor"; import {encodeBase64, decodeBase64} from "./crypto/olmlib"; +import { User } from "./models/user"; +import {AutoDiscovery} from "./autodiscovery"; const SCROLLBACK_DELAY_MS = 3000; export const CRYPTO_ENABLED = isCryptoAvailable(); @@ -104,6 +111,9 @@ function keyFromRecoverySession(session, decryptionKey) { * If provided, opts.deviceId and opts.userId should **NOT** be provided * (they are present in the exported data). * + * @param {string} opts.pickleKey Key used to pickle olm objects or other + * sensitive data. + * * @param {IdentityServerProvider} [opts.identityServer] * Optional. A provider object with one function `getAccessToken`, which is a * callback that returns a Promise<String> of an identity access token to supply @@ -278,6 +288,8 @@ export function MatrixClient(opts) { // will be used during async initialization of the crypto this._exportedOlmDeviceToImport = opts.deviceToImport.olmDevice; } + } else if (opts.pickleKey) { + this.pickleKey = opts.pickleKey; } this.scheduler = opts.scheduler; @@ -318,7 +330,7 @@ export function MatrixClient(opts) { this._isGuest = false; this._ongoingScrollbacks = {}; this.timelineSupport = Boolean(opts.timelineSupport); - this.urlPreviewCache = {}; + this.urlPreviewCache = {}; // key=preview key, value=Promise for preview (may be an error) this._notifTimelineSet = null; this.unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation; @@ -731,6 +743,7 @@ MatrixClient.prototype.initCrypto = async function() { "crypto.roomKeyRequestCancellation", "crypto.warning", "crypto.devicesUpdated", + "crypto.willUpdateDevices", "deviceVerificationChanged", "userTrustStatusChanged", "crossSigning.keysChanged", @@ -739,6 +752,7 @@ MatrixClient.prototype.initCrypto = async function() { logger.log("Crypto: initialising crypto object..."); await crypto.init({ exportedOlmDevice: this._exportedOlmDeviceToImport, + pickleKey: this.pickleKey, }); delete this._exportedOlmDeviceToImport; @@ -819,9 +833,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"); } @@ -834,9 +848,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"); } @@ -1303,7 +1317,6 @@ wrapCryptoFuncs(MatrixClient, [ "bootstrapSecretStorage", "addSecretStorageKey", "hasSecretStorageKey", - "secretStorageKeyNeedsUpgrade", "storeSecret", "getSecret", "isSecretStored", @@ -1357,7 +1370,8 @@ MatrixClient.prototype.cancelAndResendEventRoomKeyRequest = function(event) { }; /** - * Enable end-to-end encryption for a room. + * Enable end-to-end encryption for a room. This does not modify room state. + * Any messages sent before the returned promise resolves will be sent unencrypted. * @param {string} roomId The room ID to enable encryption in. * @param {object} config The encryption config for the room. * @return {Promise} A promise that will resolve when encryption is set up. @@ -1429,15 +1443,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); }; /** @@ -1497,12 +1513,16 @@ MatrixClient.prototype.isKeyBackupTrusted = function(info) { /** * @returns {bool} true if the client is configured to back up keys to - * the server, otherwise false. + * the server, otherwise false. If we haven't completed a successful check + * of key backup status yet, returns null. */ MatrixClient.prototype.getKeyBackupEnabled = function() { if (this._crypto === null) { throw new Error("End-to-end encryption disabled"); } + if (!this._crypto._checkedForBackup) { + return null; + } return Boolean(this._crypto.backupKey); }; @@ -1870,7 +1890,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"); @@ -1905,6 +1928,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}, @@ -1939,7 +1968,7 @@ MatrixClient.prototype._restoreKeyBackup = function( } } - return this.importRoomKeys(keys); + return this.importRoomKeys(keys, { progressCallback }); }).then(() => { return this._crypto.setTrustedBackupPubKey(backupPubKey); }).then(() => { @@ -2075,6 +2104,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. @@ -2086,9 +2116,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; }; /** @@ -2123,9 +2157,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; + } }; /** @@ -2439,6 +2481,7 @@ MatrixClient.prototype._sendCompleteEvent = function(roomId, eventObject, txnId, const localEvent = new MatrixEvent(Object.assign(eventObject, { event_id: "~" + roomId + ":" + txnId, user_id: this.credentials.userId, + sender: this.credentials.userId, room_id: roomId, origin_server_ts: new Date().getTime(), })); @@ -2949,25 +2992,32 @@ MatrixClient.prototype.setRoomReadMarkers = async function( * May return synthesized attributes if the URL lacked OG meta. */ MatrixClient.prototype.getUrlPreview = function(url, ts, callback) { + // bucket the timestamp to the nearest minute to prevent excessive spam to the server + // Surely 60-second accuracy is enough for anyone. + ts = Math.floor(ts / 60000) * 60000; + const key = ts + "_" + url; - const og = this.urlPreviewCache[key]; - if (og) { - return Promise.resolve(og); + + // If there's already a request in flight (or we've handled it), return that instead. + const cachedPreview = this.urlPreviewCache[key]; + if (cachedPreview) { + if (callback) { + cachedPreview.then(callback).catch(callback); + } + return cachedPreview; } - const self = this; - return this._http.authedRequest( + const resp = this._http.authedRequest( callback, "GET", "/preview_url", { url: url, ts: ts, }, undefined, { prefix: PREFIX_MEDIA_R0, }, - ).then(function(response) { - // TODO: expire cache occasionally - self.urlPreviewCache[key] = response; - return response; - }); + ); + // TODO: Expire the URL preview cache sometimes + this.urlPreviewCache[key] = resp; + return resp; }; /** @@ -3469,46 +3519,6 @@ MatrixClient.prototype.setPresence = function(opts, callback) { ); }; -function _presenceList(callback, client, opts, method) { - const path = utils.encodeUri("/presence/list/$userId", { - $userId: client.credentials.userId, - }); - return client._http.authedRequest(callback, method, path, undefined, opts); -} - -/** -* Retrieve current user presence list. -* @param {module:client.callback} callback Optional. -* @return {Promise} Resolves: TODO -* @return {module:http-api.MatrixError} Rejects: with an error response. -*/ -MatrixClient.prototype.getPresenceList = function(callback) { - return _presenceList(callback, this, undefined, "GET"); -}; - -/** -* Add users to the current user presence list. -* @param {module:client.callback} callback Optional. -* @param {string[]} userIds -* @return {Promise} Resolves: TODO -* @return {module:http-api.MatrixError} Rejects: with an error response. -*/ -MatrixClient.prototype.inviteToPresenceList = function(callback, userIds) { - const opts = {"invite": userIds}; - return _presenceList(callback, this, opts, "POST"); -}; - -/** -* Drop users from the current user presence list. -* @param {module:client.callback} callback Optional. -* @param {string[]} userIds -* @return {Promise} Resolves: TODO -* @return {module:http-api.MatrixError} Rejects: with an error response. -**/ -MatrixClient.prototype.dropFromPresenceList = function(callback, userIds) { - const opts = {"drop": userIds}; - return _presenceList(callback, this, opts, "POST"); -}; /** * Retrieve older messages from the given room and put them in the timeline. @@ -4499,64 +4509,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(); - - if (utils.deepCompare(oldDef, newDef)) { - // super, just use that. - // debuglog("Using existing filter ID %s: %s", filterId, - // JSON.stringify(oldDef)); - return Promise.resolve(filterId); + 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)); + 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; }; @@ -4720,6 +4720,9 @@ MatrixClient.prototype.deactivateSynapseUser = function(userId) { * @param {Boolean=} opts.lazyLoadMembers True to not load all membership events during * initial sync but fetch them when needed by calling `loadOutOfBandMembers` * This will override the filter option at this moment. + * @param {Number=} opts.clientWellKnownPollPeriod The number of seconds between polls + * to /.well-known/matrix/client, undefined to disable. This should be in the order of hours. + * Default: undefined. */ MatrixClient.prototype.startClient = async function(opts) { if (this.clientRunning) { @@ -4734,6 +4737,13 @@ MatrixClient.prototype.startClient = async function(opts) { }; } + // Create our own user object artificially (instead of waiting for sync) + // so it's always available, even if the user is not in any rooms etc. + const userId = this.getUserId(); + if (userId) { + this.store.storeUser(new User(userId)); + } + if (this._crypto) { this._crypto.uploadDeviceKeys(); this._crypto.start(); @@ -4761,6 +4771,29 @@ MatrixClient.prototype.startClient = async function(opts) { this._clientOpts = opts; this._syncApi = new SyncApi(this, opts); this._syncApi.sync(); + + if (opts.clientWellKnownPollPeriod !== undefined) { + this._clientWellKnownIntervalID = + setInterval(() => { + this._fetchClientWellKnown(); + }, 1000 * opts.clientWellKnownPollPeriod); + this._fetchClientWellKnown(); + } +}; + +MatrixClient.prototype._fetchClientWellKnown = async function() { + try { + this._clientWellKnown = await AutoDiscovery.getRawClientConfig(this.getDomain()); + this.emit("WellKnown.client", this._clientWellKnown); + } catch (err) { + logger.error("Failed to get client well-known", err); + this._clientWellKnown = undefined; + this.emit("WellKnown.error", err); + } +}; + +MatrixClient.prototype.getClientWellKnown = function() { + return this._clientWellKnown; }; /** @@ -4802,6 +4835,9 @@ MatrixClient.prototype.stopClient = function() { this._peekSync.stopPeeking(); } global.clearTimeout(this._checkTurnServersTimeoutID); + if (this._clientWellKnownIntervalID !== undefined) { + global.clearInterval(this._clientWellKnownIntervalID); + } }; /** @@ -5247,17 +5283,20 @@ function _resolve(callback, resolve, res) { resolve(res); } -function _PojoToMatrixEventMapper(client) { +function _PojoToMatrixEventMapper(client, options) { + const preventReEmit = Boolean(options && options.preventReEmit); function mapper(plainOldJsObject) { const event = new MatrixEvent(plainOldJsObject); if (event.isEncrypted()) { - client.reEmitter.reEmit(event, [ - "Event.decrypted", - ]); + if (!preventReEmit) { + client.reEmitter.reEmit(event, [ + "Event.decrypted", + ]); + } event.attemptDecryption(client._crypto); } const room = client.getRoom(event.getRoomId()); - if (room) { + if (room && !preventReEmit) { room.reEmitter.reEmit(event, ["Event.replaced"]); } return event; @@ -5266,10 +5305,12 @@ function _PojoToMatrixEventMapper(client) { } /** + * @param {object} [options] + * @param {bool} options.preventReEmit don't reemit events emitted on an event mapped by this mapper on the client * @return {Function} */ -MatrixClient.prototype.getEventMapper = function() { - return _PojoToMatrixEventMapper(this); +MatrixClient.prototype.getEventMapper = function(options = undefined) { + return _PojoToMatrixEventMapper(this, options); }; /** @@ -5556,6 +5597,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" * @param {bool} enabled true if key backup has been enabled, otherwise false diff --git a/src/crypto/CrossSigning.js b/src/crypto/CrossSigning.js index c0633e95..ff5bc708 100644 --- a/src/crypto/CrossSigning.js +++ b/src/crypto/CrossSigning.js @@ -24,6 +24,7 @@ import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib'; import {EventEmitter} from 'events'; import {logger} from '../logger'; import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store'; +import {decryptAES, encryptAES} from './aes'; function publicKeyFromKeyInfo(keyInfo) { // `keys` is an object with { [`ed25519:${pubKey}`]: pubKey } @@ -310,6 +311,13 @@ export class CrossSigningInfo extends EventEmitter { } } + /** + * unsets the keys, used when another session has reset the keys, to disable cross-signing + */ + clearKeys() { + this.keys = {}; + } + setKeys(keys) { const signingKeys = {}; if (keys.master) { @@ -630,10 +638,10 @@ export class DeviceTrustLevel { } } -export function createCryptoStoreCacheCallbacks(store) { +export function createCryptoStoreCacheCallbacks(store, olmdevice) { return { - getCrossSigningKeyCache: function(type, _expectedPublicKey) { - return new Promise((resolve) => { + getCrossSigningKeyCache: async function(type, _expectedPublicKey) { + const key = await new Promise((resolve) => { return store.doTxn( 'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], @@ -642,13 +650,23 @@ export function createCryptoStoreCacheCallbacks(store) { }, ); }); + + if (key && key.ciphertext) { + const pickleKey = Buffer.from(olmdevice._pickleKey); + const decrypted = await decryptAES(key, pickleKey, type); + return decodeBase64(decrypted); + } else { + return key; + } }, - storeCrossSigningKeyCache: function(type, key) { + storeCrossSigningKeyCache: async function(type, key) { if (!(key instanceof Uint8Array)) { throw new Error( `storeCrossSigningKeyCache expects Uint8Array, got ${key}`, ); } + const pickleKey = Buffer.from(olmdevice._pickleKey); + key = await encryptAES(encodeBase64(key), pickleKey, type); return store.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], diff --git a/src/crypto/DeviceList.js b/src/crypto/DeviceList.js index 3cf40f69..5925f8ad 100644 --- a/src/crypto/DeviceList.js +++ b/src/crypto/DeviceList.js @@ -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; diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 5b6f1183..6300609a 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -36,9 +36,15 @@ function checkPayloadLength(payloadString) { // Note that even if we manage to do the encryption, the message send may fail, // because by the time we've wrapped the ciphertext in the event object, it may // exceed 65K. But at least we won't just fail with "abort()" in that case. - throw new Error("Message too long (" + payloadString.length + " bytes). " + + const err = new Error("Message too long (" + payloadString.length + " bytes). " + "The maximum for an encrypted message is " + MAX_PLAINTEXT_LENGTH + " bytes."); + // TODO: [TypeScript] We should have our own error types + err.data = { + errcode: "M_TOO_LARGE", + error: "Payload too large for encrypted message", + }; + throw err; } } @@ -105,6 +111,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(); } /** @@ -1029,6 +1038,11 @@ OlmDevice.prototype.addInboundGroupSession = async function( } } + logger.info( + "Storing megolm session " + senderKey + "/" + sessionId + + " with first index " + session.first_known_index(), + ); + const sessionData = { room_id: roomId, session: session.pickle(this._pickleKey), diff --git a/src/crypto/OutgoingRoomKeyRequestManager.js b/src/crypto/OutgoingRoomKeyRequestManager.js index 15dd09c2..c3f314f7 100644 --- a/src/crypto/OutgoingRoomKeyRequestManager.js +++ b/src/crypto/OutgoingRoomKeyRequestManager.js @@ -97,10 +97,6 @@ export class OutgoingRoomKeyRequestManager { */ start() { this._clientRunning = true; - - // set the timer going, to handle any requests which didn't get sent - // on the previous run of the client. - this._startTimer(); } /** @@ -113,7 +109,14 @@ export class OutgoingRoomKeyRequestManager { } /** - * Send off a room key request, if we haven't already done so. + * Send any requests that have been queued + */ + sendQueuedRequests() { + this._startTimer(); + } + + /** + * Queue up a room key request, if we haven't already queued or sent one. * * The `requestBody` is compared (with a deep-equality check) against * previous queued or sent requests and if it matches, no change is made. @@ -129,7 +132,7 @@ export class OutgoingRoomKeyRequestManager { * pending list (or we have established that a similar request already * exists) */ - async sendRoomKeyRequest(requestBody, recipients, resend=false) { + async queueRoomKeyRequest(requestBody, recipients, resend=false) { const req = await this._cryptoStore.getOutgoingRoomKeyRequest( requestBody, ); @@ -184,7 +187,7 @@ export class OutgoingRoomKeyRequestManager { // in state ROOM_KEY_REQUEST_STATES.SENT, so we must have // raced with another tab to mark the request cancelled. // Try again, to make sure the request is resent. - return await this.sendRoomKeyRequest( + return await this.queueRoomKeyRequest( requestBody, recipients, resend, ); } @@ -220,9 +223,6 @@ export class OutgoingRoomKeyRequestManager { throw new Error('unhandled state: ' + req.state); } } - // some of the branches require the timer to be started. Just start it - // all the time, because it doesn't hurt to start it. - this._startTimer(); } /** @@ -332,14 +332,14 @@ export class OutgoingRoomKeyRequestManager { * This is intended for situations where something substantial has changed, and we * don't really expect the other end to even care about the cancellation. * For example, after initialization or self-verification. - * @return {Promise} An array of `sendRoomKeyRequest` outputs. + * @return {Promise} An array of `queueRoomKeyRequest` outputs. */ async cancelAndResendAllOutgoingRequests() { const outgoings = await this._cryptoStore.getAllOutgoingRoomKeyRequestsByState( ROOM_KEY_REQUEST_STATES.SENT, ); return Promise.all(outgoings.map(({ requestBody, recipients }) => - this.sendRoomKeyRequest(requestBody, recipients, true))); + this.queueRoomKeyRequest(requestBody, recipients, true))); } // start the background timer to send queued requests, if the timer isn't @@ -381,15 +381,12 @@ export class OutgoingRoomKeyRequestManager { return Promise.resolve(); } - logger.log("Looking for queued outgoing room key requests"); - return this._cryptoStore.getOutgoingRoomKeyRequestByState([ ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING, ROOM_KEY_REQUEST_STATES.CANCELLATION_PENDING_AND_WILL_RESEND, ROOM_KEY_REQUEST_STATES.UNSENT, ]).then((req) => { if (!req) { - logger.log("No more outgoing room key requests"); this._sendOutgoingRoomKeyRequestsTimer = null; return; } @@ -413,7 +410,6 @@ export class OutgoingRoomKeyRequestManager { }).catch((e) => { logger.error("Error sending room key request; will retry later.", e); this._sendOutgoingRoomKeyRequestsTimer = null; - this._startTimer(); }); }); } diff --git a/src/crypto/SecretStorage.js b/src/crypto/SecretStorage.js index fba19f47..ecfa96db 100644 --- a/src/crypto/SecretStorage.js +++ b/src/crypto/SecretStorage.js @@ -17,15 +17,12 @@ limitations under the License. import {EventEmitter} from 'events'; import {logger} from '../logger'; import * as olmlib from './olmlib'; -import {pkVerify} from './olmlib'; import {randomString} from '../randomstring'; import {encryptAES, decryptAES} from './aes'; +import {encodeBase64} from "./olmlib"; export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2"; -// don't use curve25519 for writing data. -export const SECRET_STORAGE_ALGORITHM_V1_CURVE25519 - = "m.secret_storage.v1.curve25519-aes-sha2"; const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; @@ -34,11 +31,10 @@ const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0 * @module crypto/SecretStorage */ export class SecretStorage extends EventEmitter { - constructor(baseApis, cryptoCallbacks, crossSigningInfo) { + constructor(baseApis, cryptoCallbacks) { super(); this._baseApis = baseApis; this._cryptoCallbacks = cryptoCallbacks; - this._crossSigningInfo = crossSigningInfo; this._requests = {}; this._incomingRequests = {}; } @@ -52,7 +48,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' && @@ -64,10 +60,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); + } }); } @@ -91,20 +92,16 @@ export class SecretStorage extends EventEmitter { keyData.name = opts.name; } - switch (algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: - { + if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { if (opts.passphrase) { keyData.passphrase = opts.passphrase; } if (opts.key) { - const {iv, mac} = await encryptAES(ZERO_STR, opts.key, ""); + const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key); keyData.iv = iv; keyData.mac = mac; } - break; - } - default: + } else { throw new Error(`Unknown key algorithm ${opts.algorithm}`); } @@ -118,8 +115,6 @@ export class SecretStorage extends EventEmitter { ); } - await this._crossSigningInfo.signObject(keyData, 'master'); - await this._baseApis.setAccountData( `m.secret_storage.key.${keyId}`, keyData, ); @@ -128,33 +123,6 @@ export class SecretStorage extends EventEmitter { } /** - * Signs a given secret storage key with the cross-signing master key. - * - * @param {string} [keyId = default key's ID] The ID of the key to sign. - * Defaults to the default key ID if not provided. - */ - async signKey(keyId) { - if (!keyId) { - keyId = await this.getDefaultKeyId(); - } - if (!keyId) { - throw new Error("signKey requires a key ID"); - } - - const keyInfo = await this._baseApis.getAccountDataFromServer( - `m.secret_storage.key.${keyId}`, - ); - if (!keyInfo) { - throw new Error(`Key ${keyId} does not exist in account data`); - } - - await this._crossSigningInfo.signObject(keyInfo, 'master'); - await this._baseApis.setAccountData( - `m.secret_storage.key.${keyId}`, keyInfo, - ); - } - - /** * Get the key information for a given ID. * * @param {string} [keyId = default key's ID] The ID of the key to check @@ -187,15 +155,6 @@ export class SecretStorage extends EventEmitter { return !!(await this.getKey(keyId)); } - async keyNeedsUpgrade(keyId) { - const keyInfo = await this.getKey(keyId); - if (keyInfo && keyInfo[1].algorithm === SECRET_STORAGE_ALGORITHM_V1_CURVE25519) { - return true; - } else { - return false; - } - } - /** * Check whether a key matches what we expect based on the key info * @@ -205,36 +164,23 @@ export class SecretStorage extends EventEmitter { * @return {boolean} whether or not the key matches */ async checkKey(key, info) { - switch (info.algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: - { + if (info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { if (info.mac) { - const {mac} = await encryptAES(ZERO_STR, key, "", info.iv); - return info.mac === mac; + const {mac} = await SecretStorage._calculateKeyCheck(key, info.iv); + return info.mac.replace(/=+$/g, '') === mac.replace(/=+$/g, ''); } else { // if we have no information, we have to assume the key is right return true; } - } - case SECRET_STORAGE_ALGORITHM_V1_CURVE25519: - { - let decryption = null; - try { - decryption = new global.Olm.PkDecryption(); - const gotPubkey = decryption.init_with_private_key(key); - // make sure it agrees with the given pubkey - return gotPubkey === info.pubkey; - } catch (e) { - return false; - } finally { - if (decryption) decryption.free(); - } - } - default: + } else { throw new Error("Unknown algorithm"); } } + static async _calculateKeyCheck(key, iv) { + return await encryptAES(ZERO_STR, key, "", iv); + } + /** * Store an encrypted secret on the server * @@ -268,15 +214,11 @@ export class SecretStorage extends EventEmitter { } // encrypt secret, based on the algorithm - switch (keyInfo.algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: - { + if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { const keys = {[keyId]: keyInfo}; const [, encryption] = await this._getSecretStorageKey(keys, name); encrypted[keyId] = await encryption.encrypt(secret); - break; - } - default: + } else { logger.warn("unknown algorithm for secret storage key " + keyId + ": " + keyInfo.algorithm); // do nothing if we don't understand the encryption algorithm @@ -342,24 +284,11 @@ export class SecretStorage extends EventEmitter { "m.secret_storage.key." + keyId, ); const encInfo = secretInfo.encrypted[keyId]; - switch (keyInfo.algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: + // only use keys we understand the encryption algorithm of + if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { if (encInfo.iv && encInfo.ciphertext && encInfo.mac) { keys[keyId] = keyInfo; } - break; - case SECRET_STORAGE_ALGORITHM_V1_CURVE25519: - if ( - keyInfo.pubkey && ( - (encInfo.ciphertext && encInfo.mac && encInfo.ephemeral) || - encInfo.passthrough - ) - ) { - keys[keyId] = keyInfo; - } - break; - default: - // do nothing if we don't understand the encryption algorithm } } @@ -377,8 +306,9 @@ export class SecretStorage extends EventEmitter { const encInfo = secretInfo.encrypted[keyId]; // We don't actually need the decryption object if it's a passthrough - // since we just want to return the key itself. - if (encInfo.passthrough) return decryption.get_private_key(); + // since we just want to return the key itself. It must be base64 + // encoded, since this is how a key would normally be stored. + if (encInfo.passthrough) return encodeBase64(decryption.get_private_key()); return await decryption.decrypt(encInfo); } finally { @@ -412,8 +342,7 @@ export class SecretStorage extends EventEmitter { const ret = {}; - // check if secret is encrypted by a known/trusted secret and - // encryption looks sane + // filter secret encryption keys with supported algorithm for (const keyId of Object.keys(secretInfo.encrypted)) { // get key information from key storage const keyInfo = await this._baseApis.getAccountDataFromServer( @@ -422,49 +351,11 @@ export class SecretStorage extends EventEmitter { if (!keyInfo) continue; const encInfo = secretInfo.encrypted[keyId]; - // We don't actually need the decryption object if it's a passthrough - // since we just want to return the key itself. - if (encInfo.passthrough) { - try { - pkVerify( - keyInfo, - this._crossSigningInfo.getId('master'), - this._crossSigningInfo.userId, - ); - } catch (e) { - // not trusted, so move on to the next key - continue; - } - ret[keyId] = keyInfo; - continue; - } - - switch (keyInfo.algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: + // only use keys we understand the encryption algorithm of + if (keyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { if (encInfo.iv && encInfo.ciphertext && encInfo.mac) { ret[keyId] = keyInfo; } - break; - case SECRET_STORAGE_ALGORITHM_V1_CURVE25519: - if (keyInfo.pubkey && encInfo.ciphertext && encInfo.mac - && encInfo.ephemeral) { - if (checkKey) { - try { - pkVerify( - keyInfo, - this._crossSigningInfo.getId('master'), - this._crossSigningInfo.userId, - ); - } catch (e) { - // not trusted, so move on to the next key - continue; - } - } - ret[keyId] = keyInfo; - } - break; - default: - // do nothing if we don't understand the encryption algorithm } } return Object.keys(ret).length ? ret : null; @@ -591,7 +482,7 @@ export class SecretStorage extends EventEmitter { this._baseApis, { [sender]: [ - await this._baseApis.getStoredDevice(sender, deviceId), + this._baseApis.getStoredDevice(sender, deviceId), ], }, ); @@ -601,7 +492,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 = { @@ -668,9 +559,7 @@ export class SecretStorage extends EventEmitter { throw new Error("App returned unknown key from getSecretStorageKey!"); } - switch (keys[keyId].algorithm) { - case SECRET_STORAGE_ALGORITHM_V1_AES: - { + if (keys[keyId].algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { const decryption = { encrypt: async function(secret) { return await encryptAES(secret, privateKey, name); @@ -680,36 +569,7 @@ export class SecretStorage extends EventEmitter { }, }; return [keyId, decryption]; - } - case SECRET_STORAGE_ALGORITHM_V1_CURVE25519: - { - const pkDecryption = new global.Olm.PkDecryption(); - let pubkey; - try { - pubkey = pkDecryption.init_with_private_key(privateKey); - } catch (e) { - pkDecryption.free(); - throw new Error("getSecretStorageKey callback returned invalid key"); - } - if (pubkey !== keys[keyId].pubkey) { - pkDecryption.free(); - throw new Error( - "getSecretStorageKey callback returned incorrect key", - ); - } - const decryption = { - free: pkDecryption.free.bind(pkDecryption), - decrypt: async function(encInfo) { - return pkDecryption.decrypt( - encInfo.ephemeral, encInfo.mac, encInfo.ciphertext, - ); - }, - // needed for passthrough - get_private_key: pkDecryption.get_private_key.bind(pkDecryption), - }; - return [keyId, decryption]; - } - default: + } else { throw new Error("Unknown key type: " + keys[keyId].algorithm); } } diff --git a/src/crypto/aes.js b/src/crypto/aes.js index 6c351330..1556413f 100644 --- a/src/crypto/aes.js +++ b/src/crypto/aes.js @@ -84,9 +84,9 @@ async function decryptNode(data, key, name) { const [aesKey, hmacKey] = deriveKeysNode(key, name); const hmac = crypto.createHmac("sha256", hmacKey) - .update(data.ciphertext, "base64").digest("base64"); + .update(data.ciphertext, "base64").digest("base64").replace(/=+$/g, ''); - if (hmac !== data.mac) { + if (hmac !== data.mac.replace(/=+$/g, '')) { throw new Error(`Error decrypting secret ${name}: bad MAC`); } diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 5d9a9bf7..0976302a 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -311,7 +311,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function( } await this._shareKeyWithDevices( - session, key, payload, retryDevices, failedDevices, + session, key, payload, retryDevices, failedDevices, 30000, ); await this._notifyFailedOlmDevices(session, key, failedDevices); @@ -521,6 +521,33 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function( } return Promise.all(promises).then(() => { + // prune out any devices that encryptMessageForDevice could not encrypt for, + // in which case it will have just not added anything to the ciphertext object. + // There's no point sending messages to devices if we couldn't encrypt to them, + // since that's effectively a blank message. + for (const userId of Object.keys(contentMap)) { + for (const deviceId of Object.keys(contentMap[userId])) { + if (Object.keys(contentMap[userId][deviceId].ciphertext).length === 0) { + logger.log( + "No ciphertext for device " + + userId + ":" + deviceId + ": pruning", + ); + delete contentMap[userId][deviceId]; + } + } + // No devices left for that user? Strip that too. + if (Object.keys(contentMap[userId]).length === 0) { + logger.log("Pruned all devices for user " + userId); + delete contentMap[userId]; + } + } + + // Is there anything left? + if (Object.keys(contentMap).length === 0) { + logger.log("No users left to send to: aborting"); + return; + } + return this._baseApis.sendToDevice("m.room.encrypted", contentMap).then(() => { // store that we successfully uploaded the keys of the current slice for (const userId of Object.keys(contentMap)) { @@ -1296,7 +1323,6 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { keysClaimed = event.getKeysClaimed(); } - logger.log(`Received and adding key for megolm session ${senderKey}|${sessionId}`); return this._olmDevice.addInboundGroupSession( content.room_id, senderKey, forwardingKeyChain, sessionId, content.session_key, keysClaimed, @@ -1555,7 +1581,8 @@ MegolmDecryption.prototype.importRoomKey = function(session) { }; /** - * Have another go at decrypting events after we receive a key + * Have another go at decrypting events after we receive a key. Resolves once + * decryption has been re-attempted on all events. * * @private * @param {String} senderKey @@ -1574,21 +1601,17 @@ MegolmDecryption.prototype._retryDecryption = async function(senderKey, sessionI return true; } - pending.delete(sessionId); - if (pending.size === 0) { - this._pendingEvents[senderKey]; - } + logger.debug("Retrying decryption on events", [...pending]); await Promise.all([...pending].map(async (ev) => { try { - await ev.attemptDecryption(this._crypto); + await ev.attemptDecryption(this._crypto, true); } catch (e) { // don't die if something goes wrong } })); - // ev.attemptDecryption will re-add to this._pendingEvents if an event - // couldn't be decrypted + // If decrypted successfully, they'll have been removed from _pendingEvents return !((this._pendingEvents[senderKey] || {})[sessionId]); }; diff --git a/src/crypto/algorithms/olm.js b/src/crypto/algorithms/olm.js index 5c30a178..6add378e 100644 --- a/src/crypto/algorithms/olm.js +++ b/src/crypto/algorithms/olm.js @@ -265,6 +265,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, ); diff --git a/src/crypto/index.js b/src/crypto/index.js index b8b02fba..06cc2add 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -56,6 +56,7 @@ import {ToDeviceChannel, ToDeviceRequests} from "./verification/request/ToDevice import * as httpApi from "../http-api"; import {IllegalMethod} from "./verification/IllegalMethod"; import {KeySignatureUploadError} from "../errors"; +import {decryptAES, encryptAES} from './aes'; const DeviceVerification = DeviceInfo.DeviceVerification; @@ -169,7 +170,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. @@ -223,8 +226,13 @@ export function Crypto(baseApis, sessionStore, userId, deviceId, this._toDeviceVerificationRequests = new ToDeviceRequests(); this._inRoomVerificationRequests = new InRoomRequests(); + // This flag will be unset whilst the client processes a sync response + // so that we don't start requesting keys until we've actually finished + // processing the response. + this._sendKeyRequestsImmediately = false; + const cryptoCallbacks = this._baseApis._cryptoCallbacks || {}; - const cacheCallbacks = createCryptoStoreCacheCallbacks(cryptoStore); + const cacheCallbacks = createCryptoStoreCacheCallbacks(cryptoStore, this._olmDevice); this._crossSigningInfo = new CrossSigningInfo( userId, @@ -233,7 +241,7 @@ export function Crypto(baseApis, sessionStore, userId, deviceId, ); this._secretStorage = new SecretStorage( - baseApis, cryptoCallbacks, this._crossSigningInfo, + baseApis, cryptoCallbacks, ); // Assuming no app-supplied callback, default to getting from SSSS. @@ -257,6 +265,7 @@ utils.inherits(Crypto, EventEmitter); Crypto.prototype.init = async function(opts) { const { exportedOlmDevice, + pickleKey, } = opts || {}; logger.log("Crypto: initialising Olm..."); @@ -266,7 +275,7 @@ Crypto.prototype.init = async function(opts) { ? "Crypto: initialising Olm device from exported device..." : "Crypto: initialising Olm device...", ); - await this._olmDevice.init({ fromExportedDevice: exportedOlmDevice }); + await this._olmDevice.init({ fromExportedDevice: exportedOlmDevice, pickleKey }); logger.log("Crypto: loading device list..."); await this._deviceList.load(); @@ -306,7 +315,8 @@ Crypto.prototype.init = async function(opts) { 'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { this._cryptoStore.getCrossSigningKeys(txn, (keys) => { - if (keys) { + // can be an empty object after resetting cross-signing keys, see _storeTrustedSelfKeys + if (keys && Object.keys(keys).length !== 0) { logger.log("Loaded cross-signing public keys from crypto store"); this._crossSigningInfo.setKeys(keys); } @@ -432,6 +442,14 @@ Crypto.prototype.isCrossSigningReady = async function() { * up, then no changes are made, so this is safe to run to ensure secret storage * is ready for use. * + * This function + * - creates a new Secure Secret Storage key if no default key exists + * - if a key backup exists, it is migrated to store the key in the Secret + * Storage + * - creates a backup if none exists, and one is requested + * - migrates Secure Secret Storage to use the latest algorithm, if an outdated + * algorithm is found + * * @param {function} [opts.authUploadDeviceSigningKeys] Optional. Function * called to await an interactive auth flow when uploading device signing keys. * Args: @@ -501,172 +519,160 @@ Crypto.prototype.bootstrapSecretStorage = async function({ return key; }; + // create a new SSSS key and set it as default + const createSSSS = async (opts, privateKey) => { + opts = opts || {}; + if (privateKey) { + opts.key = privateKey; + } + + const keyId = await this.addSecretStorageKey( + SECRET_STORAGE_ALGORITHM_V1_AES, opts, + ); + await this.setDefaultSecretStorageKeyId(keyId); + + if (privateKey) { + // cache the private key so that we can access it again + ssssKeys[keyId] = privateKey; + } + return keyId; + }; + + // reset the cross-signing keys + const resetCrossSigning = async () => { + this._baseApis._cryptoCallbacks.saveCrossSigningKeys = + keys => Object.assign(crossSigningPrivateKeys, keys); + this._baseApis._cryptoCallbacks.getCrossSigningKey = + name => crossSigningPrivateKeys[name]; + await this.resetCrossSigningKeys( + CrossSigningLevel.MASTER, + { authUploadDeviceSigningKeys }, + ); + }; + + const ensureCanCheckPassphrase = async (keyId, keyInfo) => { + if (!keyInfo.mac) { + const key = await this._baseApis._cryptoCallbacks.getSecretStorageKey( + {keys: {[keyId]: keyInfo}}, "", + ); + if (key) { + const keyData = key[1]; + ssssKeys[keyId] = keyData; + const {iv, mac} = await SecretStorage._calculateKeyCheck(keyData); + keyInfo.iv = iv; + keyInfo.mac = mac; + + await this._baseApis.setAccountData( + `m.secret_storage.key.${keyId}`, keyInfo, + ); + } + } + }; + try { + const oldSSSSKey = await this.getSecretStorageKey(); + const [oldKeyId, oldKeyInfo] = oldSSSSKey || [null, null]; const decryptionKeys = await this._crossSigningInfo.isStoredInSecretStorage(this._secretStorage); const inStorage = !setupNewSecretStorage && decryptionKeys; - if (decryptionKeys && !(Object.values(decryptionKeys).some( - info => info.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES, - ))) { - // we already have cross-signing keys, but they're encrypted using - // the old algorithm - logger.log("Switching to symmetric"); - const keys = {}; - // fetch the cross-signing private keys (needed to sign the new - // SSSS key). We store the cross-signing keys, and temporarily set - // a callback so that when the private key is needed while setting - // things up, we can provide it. - this._baseApis._cryptoCallbacks.getCrossSigningKey = - name => crossSigningPrivateKeys[name]; - for (const type of ["master", "self_signing", "user_signing"]) { - const secretName = `m.cross_signing.${type}`; - const secret = await this.getSecret(secretName); - keys[type] = secret; - crossSigningPrivateKeys[type] = olmlib.decodeBase64(secret); - } - await this.checkOwnCrossSigningTrust(); - const opts = {}; - let oldKeyId = null; - for (const [keyId, keyInfo] of Object.entries(decryptionKeys)) { - // See if the old key was generated from a passphrase. If - // yes, use the same settings. - if (keyId in ssssKeys) { - oldKeyId = keyId; - if (keyInfo.passphrase) { - opts.passphrase = keyInfo.passphrase; - } - break; - } - } - if (oldKeyId) { - opts.key = ssssKeys[oldKeyId]; - } - // create new symmetric SSSS key and set it as default - newKeyId = await this.addSecretStorageKey( - SECRET_STORAGE_ALGORITHM_V1_AES, opts, + + if (!inStorage && !keyBackupInfo) { + // either we don't have anything, or we've been asked to restart + // from scratch + logger.log( + "Cross-signing private keys not found in secret storage, " + + "creating new keys", ); - if (oldKeyId) { - ssssKeys[newKeyId] = ssssKeys[oldKeyId]; + + await resetCrossSigning(); + + 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); } - await this.setDefaultSecretStorageKeyId(newKeyId); - // re-encrypt all the keys with the new key - for (const type of ["master", "self_signing", "user_signing"]) { - const secretName = `m.cross_signing.${type}`; - await this.storeSecret(secretName, keys[type], [newKeyId]); + + if (oldKeyInfo && oldKeyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { + await ensureCanCheckPassphrase(oldKeyId, oldKeyInfo); } - } else if (!this._crossSigningInfo.getId() || !inStorage) { - // create new cross-signing keys if necessary. - logger.log( - "Cross-signing public and/or private keys not found, " + - "checking secret storage for private keys", - ); - if (inStorage) { - logger.log("Cross-signing private keys found in secret storage"); - await this.checkOwnCrossSigningTrust(); - } else { - logger.log( - "Cross-signing private keys not found in secret storage, " + - "creating new keys", - ); - this._baseApis._cryptoCallbacks.saveCrossSigningKeys = - keys => Object.assign(crossSigningPrivateKeys, keys); - this._baseApis._cryptoCallbacks.getCrossSigningKey = - name => crossSigningPrivateKeys[name]; - await this.resetCrossSigningKeys( - CrossSigningLevel.MASTER, - { authUploadDeviceSigningKeys }, - ); + } else if (!inStorage && keyBackupInfo) { + // we have an existing backup, but no SSSS + + logger.log("Secret storage default key not found, using key backup key"); + + // if we have the backup key already cached, use it; otherwise use the + // callback to prompt for the key + const backupKey = await this.getSessionBackupPrivateKey() || + await getKeyBackupPassphrase(); + + // create new cross-signing keys + await resetCrossSigning(); + + // create a new SSSS key and use the backup key as the new SSSS key + const opts = {}; + + if ( + keyBackupInfo.auth_data.private_key_salt && + keyBackupInfo.auth_data.private_key_iterations + ) { + opts.passphrase = { + algorithm: "m.pbkdf2", + iterations: keyBackupInfo.auth_data.private_key_iterations, + salt: keyBackupInfo.auth_data.private_key_salt, + bits: 256, + }; } - } else { - logger.log("Cross signing keys are present in secret storage"); - } - // Check if we need to create a new secret storage key - // - we're resetting secret storage - // - we don't have a default secret storage key yet - // - our default secret storage key is using an older algorithm - // We will also run this part if we created a new secret storage key - // above, so that we can (re-)encrypt the backup with it. - const defaultSSSSKey = await this.getSecretStorageKey(); - if (setupNewSecretStorage || newKeyId || !defaultSSSSKey - || defaultSSSSKey[1].algorithm !== SECRET_STORAGE_ALGORITHM_V1_AES) { - if (keyBackupInfo) { - // if we already have a backup key, use the same key as the - // secret storage key - logger.log("Secret storage default key not found, using key backup key"); - - const backupKey = await getKeyBackupPassphrase(); - - if (!newKeyId) { - const opts = {}; + newKeyId = await createSSSS(opts, backupKey); - if ( - keyBackupInfo.auth_data.private_key_salt && - keyBackupInfo.auth_data.private_key_iterations - ) { - opts.passphrase = { - algorithm: "m.pbkdf2", - iterations: keyBackupInfo.auth_data.private_key_iterations, - salt: keyBackupInfo.auth_data.private_key_salt, - bits: 256, - }; - } + // store the backup key in secret storage + await this.storeSecret( + "m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId], + ); - // use the backup key as the new ssss key - ssssKeys[newKeyId] = backupKey; - opts.key = backupKey; + // The backup is trusted because the user provided the private key. + // Sign the backup with the cross signing key so the key backup can + // be trusted via cross-signing. + logger.log("Adding cross signing signature to key backup"); + await this._crossSigningInfo.signObject( + keyBackupInfo.auth_data, "master", + ); + await this._baseApis._http.authedRequest( + undefined, "PUT", "/room_keys/version/" + keyBackupInfo.version, + undefined, keyBackupInfo, + {prefix: httpApi.PREFIX_UNSTABLE}, + ); + } else if (!this._crossSigningInfo.getId()) { + // we have SSSS, but we don't know if the server's cross-signing + // keys should be trusted + logger.log("Cross-signing private keys found in secret storage"); - newKeyId = await this.addSecretStorageKey( - SECRET_STORAGE_ALGORITHM_V1_AES, opts, - ); - await this.setDefaultSecretStorageKeyId(newKeyId); - } + // fetch the private keys and set up our local copy of the keys for + // use + await this.checkOwnCrossSigningTrust(); - // if this key backup is trusted, sign it with the cross signing key - // so the key backup can be trusted via cross-signing. - const backupSigStatus = await this.checkKeyBackup(keyBackupInfo); - if (backupSigStatus.trustInfo.usable) { - logger.log("Adding cross signing signature to key backup"); - await this._crossSigningInfo.signObject( - keyBackupInfo.auth_data, "master", - ); - await this._baseApis._http.authedRequest( - undefined, "PUT", "/room_keys/version/" + keyBackupInfo.version, - undefined, keyBackupInfo, - {prefix: httpApi.PREFIX_UNSTABLE}, - ); - await this.storeSecret( - "m.megolm_backup.v1", olmlib.encodeBase64(backupKey), [newKeyId], - ); - } else { - logger.log( - "Key backup is NOT TRUSTED: NOT adding cross signing signature", - ); - } - } else { - if (!newKeyId) { - logger.log("Secret storage default key not found, creating new key"); - const { keyInfo, privateKey } = await createSecretStorageKey(); - if (keyInfo && privateKey) { - keyInfo.key = privateKey; - } - newKeyId = await this.addSecretStorageKey( - SECRET_STORAGE_ALGORITHM_V1_AES, - keyInfo, - ); - await this.setDefaultSecretStorageKeyId(newKeyId); - ssssKeys[newKeyId] = privateKey; - } - if (await this.isSecretStored("m.megolm_backup.v1")) { - // we created a new SSSS, and we previously encrypted the - // backup key with the old SSSS key, so re-encrypt with the - // new key - const backupKey = await this.getSecret("m.megolm_backup.v1"); - await this.storeSecret("m.megolm_backup.v1", backupKey, [newKeyId]); - } + if (oldKeyInfo && oldKeyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { + // make sure that the default key has the information needed to + // check the passphrase + await ensureCanCheckPassphrase(oldKeyId, oldKeyInfo); } } else { - logger.log("Have secret storage key"); + // we have SSSS and we cross-signing is already set up + logger.log("Cross signing keys are present in secret storage"); + + if (oldKeyInfo && oldKeyInfo.algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) { + // make sure that the default key has the information needed to + // check the passphrase + await ensureCanCheckPassphrase(oldKeyId, oldKeyInfo); + } } // If cross-signing keys were reset, store them in Secure Secret Storage. @@ -676,11 +682,6 @@ Crypto.prototype.bootstrapSecretStorage = async function({ // See also https://github.com/vector-im/riot-web/issues/11635 if (Object.keys(crossSigningPrivateKeys).length) { logger.log("Storing cross-signing private keys in secret storage"); - // SSSS expects its keys to be signed by cross-signing master key. - // Since we have just reset cross-signing keys, we need to re-sign the - // SSSS default key with the new cross-signing master key so that the - // following storage step can proceed. - await this._secretStorage.signKey(); // Assuming no app-supplied callback, default to storing in SSSS. if (!appCallbacks.saveCrossSigningKeys) { await CrossSigningInfo.storeInSecretStorage( @@ -711,8 +712,9 @@ Crypto.prototype.bootstrapSecretStorage = async function({ const sessionBackupKey = await this.getSecret('m.megolm_backup.v1'); if (sessionBackupKey) { logger.info("Got session backup key from secret storage: caching"); - const decoded = olmlib.decodeBase64(sessionBackupKey); - await this.storeSessionBackupPrivateKey(decoded); + const decodedBackupKey = + new Uint8Array(olmlib.decodeBase64(sessionBackupKey)); + await this.storeSessionBackupPrivateKey(decodedBackupKey); } } finally { // Restore the original callbacks. NB. we must do this by manipulating @@ -737,10 +739,6 @@ Crypto.prototype.hasSecretStorageKey = function(keyID) { return this._secretStorage.hasKey(keyID); }; -Crypto.prototype.secretStorageKeyNeedsUpgrade = function(keyID) { - return this._secretStorage.keyNeedsUpgrade(keyID); -}; - Crypto.prototype.getSecretStorageKey = function(keyID) { return this._secretStorage.getKey(keyID); }; @@ -802,7 +800,7 @@ Crypto.prototype.checkSecretStoragePrivateKey = function(privateKey, expectedPub * @returns {Promise} the key, if any, or null */ Crypto.prototype.getSessionBackupPrivateKey = async function() { - return new Promise((resolve) => { + const key = await new Promise((resolve) => { this._cryptoStore.doTxn( 'readonly', [IndexedDBCryptoStore.STORE_ACCOUNT], @@ -815,6 +813,14 @@ Crypto.prototype.getSessionBackupPrivateKey = async function() { }, ); }); + + if (key && key.ciphertext) { + const pickleKey = Buffer.from(this._olmDevice._pickleKey); + const decrypted = await decryptAES(key, pickleKey, "m.megolm_backup.v1"); + return olmlib.decodeBase64(decrypted); + } else { + return key; + } }; /** @@ -826,6 +832,8 @@ Crypto.prototype.storeSessionBackupPrivateKey = async function(key) { if (!(key instanceof Uint8Array)) { throw new Error(`storeSessionBackupPrivateKey expects Uint8Array, got ${key}`); } + const pickleKey = Buffer.from(this._olmDevice._pickleKey); + key = await encryptAES(olmlib.encodeBase64(key), pickleKey, "m.megolm_backup.v1"); return this._cryptoStore.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], @@ -1145,13 +1153,18 @@ Crypto.prototype._onDeviceListUserCrossSigningUpdated = async function(userId) { // If it's not changed, just make sure everything is up to date await this.checkOwnCrossSigningTrust(); } else { - this.emit("crossSigning.keysChanged", {}); // We'll now be in a state where cross-signing on the account is not trusted // because our locally stored cross-signing keys will not match the ones - // on the server for our account. The app must call checkOwnCrossSigningTrust() - // to fix this. - // XXX: Do we need to do something to emit events saying every device has become - // untrusted? + // on the server for our account. So we clear our own stored cross-signing keys, + // effectively disabling cross-signing until the user gets verified by the device + // that reset the keys + this._storeTrustedSelfKeys(null); + // emit cross-signing has been disabled + this.emit("crossSigning.keysChanged", {}); + // as the trust for our own user has changed, + // also emit an event for this + this.emit("userTrustStatusChanged", + this._userId, this.checkUserTrust(userId)); } } else { await this._checkDeviceVerifications(userId); @@ -1307,7 +1320,11 @@ Crypto.prototype.checkOwnCrossSigningTrust = async function() { * @param {object} keys The new trusted set of keys */ Crypto.prototype._storeTrustedSelfKeys = async function(keys) { - this._crossSigningInfo.setKeys(keys); + if (keys) { + this._crossSigningInfo.setKeys(keys); + } else { + this._crossSigningInfo.clearKeys(); + } await this._cryptoStore.doTxn( 'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT], (txn) => { @@ -1372,8 +1389,9 @@ Crypto.prototype._checkAndStartKeyBackup = async function() { backupInfo = await this._baseApis.getKeyBackupVersion(); } catch (e) { logger.log("Error checking for active key backup", e); - if (e.httpStatus / 100 === 4) { - // well that's told us. we won't try again. + if (e.httpStatus === 404) { + // 404 is returned when the key backup does not exist, so that + // counts as successfully checking. this._checkedForBackup = true; } return null; @@ -1916,6 +1934,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) @@ -2058,7 +2080,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, @@ -2071,6 +2094,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); @@ -2438,17 +2465,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(); } + }); })); }; @@ -2764,9 +2811,13 @@ Crypto.prototype.handleDeviceListChanges = async function(syncData, syncDeviceLi * @return {Promise} a promise that resolves when the key request is queued */ Crypto.prototype.requestRoomKey = function(requestBody, recipients, resend=false) { - return this._outgoingRoomKeyRequestManager.sendRoomKeyRequest( + return this._outgoingRoomKeyRequestManager.queueRoomKeyRequest( requestBody, recipients, resend, - ).catch((e) => { + ).then(() => { + if (this._sendKeyRequestsImmediately) { + this._outgoingRoomKeyRequestManager.sendQueuedRequests(); + } + }).catch((e) => { // this normally means we couldn't talk to the store logger.error( 'Error requesting key for event', e, @@ -2831,6 +2882,8 @@ Crypto.prototype.onSyncWillProcess = async function(syncData) { this._deviceList.startTrackingDeviceList(this._userId); this._roomDeviceTrackingState = {}; } + + this._sendKeyRequestsImmediately = false; }; /** @@ -2862,6 +2915,14 @@ Crypto.prototype.onSyncCompleted = async function(syncData) { if (!syncData.catchingUp) { _maybeUploadOneTimeKeys(this); this._processReceivedRoomKeyRequests(); + + // likewise don't start requesting keys until we've caught up + // on to_device messages, otherwise we'll request keys that we're + // just about to get. + this._outgoingRoomKeyRequestManager.sendQueuedRequests(); + + // Sync has finished so send key requests straight away. + this._sendKeyRequestsImmediately = true; } }; @@ -3381,6 +3442,19 @@ Crypto.prototype._processReceivedRoomKeyRequest = async function(req) { return; } + if (deviceId === this._deviceId) { + // We'll always get these because we send room key requests to + // '*' (ie. 'all devices') which includes the sending device, + // so ignore requests from ourself because apart from it being + // very silly, it won't work because an Olm session cannot send + // messages to itself. + // The log here is probably superfluous since we know this will + // always happen, but let's log anyway for now just in case it + // causes issues. + logger.log("Ignoring room key request from ourselves"); + return; + } + // todo: should we queue up requests we don't yet have keys for, // in case they turn up later? diff --git a/src/crypto/olmlib.js b/src/crypto/olmlib.js index fd8bee94..dbdf07db 100644 --- a/src/crypto/olmlib.js +++ b/src/crypto/olmlib.js @@ -207,6 +207,25 @@ export async function ensureOlmSessionsForDevices( for (const deviceInfo of devices) { const deviceId = deviceInfo.deviceId; const key = deviceInfo.getIdentityKey(); + + if (key === olmDevice.deviceCurve25519Key) { + // We should never be trying to start a session with ourself. + // Apart from talking to yourself being the first sign of madness, + // olm sessions can't do this because they get confused when + // they get a message and see that the 'other side' has started a + // new chain when this side has an active sender chain. + // If you see this message being logged in the wild, we should find + // the thing that is trying to send Olm messages to itself and fix it. + logger.info("Attempted to start session with ourself! Ignoring"); + // We must fill in the section in the return value though, as callers + // expect it to be there. + result[userId][deviceId] = { + device: deviceInfo, + sessionId: null, + }; + continue; + } + 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 @@ -238,6 +257,11 @@ export async function ensureOlmSessionsForDevices( delete resolveSession[key]; } if (sessionId === null || force) { + if (force) { + logger.info("Forcing new Olm session for " + userId + ":" + deviceId); + } else { + logger.info("Making new Olm session for " + userId + ":" + deviceId); + } devicesWithoutSession.push([userId, deviceId]); } result[userId][deviceId] = { @@ -277,6 +301,14 @@ export async function ensureOlmSessionsForDevices( const deviceInfo = devices[j]; const deviceId = deviceInfo.deviceId; const key = deviceInfo.getIdentityKey(); + + if (key === olmDevice.deviceCurve25519Key) { + // We've already logged about this above. Skip here too + // otherwise we'll log saying there are no one-time keys + // which will be confusing. + continue; + } + if (result[userId][deviceId].sessionId && !force) { // we already have a result for this device continue; diff --git a/src/crypto/store/localStorage-crypto-store.js b/src/crypto/store/localStorage-crypto-store.js index 1e63affe..3e673168 100644 --- a/src/crypto/store/localStorage-crypto-store.js +++ b/src/crypto/store/localStorage-crypto-store.js @@ -369,7 +369,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { getSecretStorePrivateKey(txn, func, type) { const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`); - func(key ? Uint8Array.from(key) : key); + func(key); } storeCrossSigningKeys(txn, keys) { @@ -380,7 +380,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore { storeSecretStorePrivateKey(txn, type, key) { setJsonItem( - this.store, E2E_PREFIX + `ssss_cache.${type}`, Array.from(key), + this.store, E2E_PREFIX + `ssss_cache.${type}`, key, ); } diff --git a/src/crypto/verification/Base.js b/src/crypto/verification/Base.js index 33284086..0b6cf31d 100644 --- a/src/crypto/verification/Base.js +++ b/src/crypto/verification/Base.js @@ -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; @@ -287,6 +292,7 @@ export class VerificationBase extends EventEmitter { this._endTimer(); // always kill the activity timer if (!this._done) { this.cancelled = true; + this.request.onVerifierCancelled(); if (this.userId && this.deviceId) { // send a cancellation to the other user (if it wasn't // cancelled by the other user) @@ -369,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); diff --git a/src/crypto/verification/QRCode.js b/src/crypto/verification/QRCode.js index 34d51016..7be1c06a 100644 --- a/src/crypto/verification/QRCode.js +++ b/src/crypto/verification/QRCode.js @@ -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,15 +166,31 @@ 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. */ get encodedSharedSecret() { @@ -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 diff --git a/src/crypto/verification/SAS.js b/src/crypto/verification/SAS.js index a00ab3d7..7aba7622 100644 --- a/src/crypto/verification/SAS.js +++ b/src/crypto/verification/SAS.js @@ -175,11 +175,33 @@ function calculateMAC(olmSAS, method) { }; } +const calculateKeyAgreement = { + "curve25519-hkdf-sha256": function(sas, olmSAS, bytes) { + const ourInfo = `${sas._baseApis.getUserId()}|${sas._baseApis.deviceId}|` + + `${sas.ourSASPubKey}|`; + const theirInfo = `${sas.userId}|${sas.deviceId}|${sas.theirSASPubKey}|`; + const sasInfo = + "MATRIX_KEY_VERIFICATION_SAS|" + + (sas.initiatedByMe ? ourInfo + theirInfo : theirInfo + ourInfo) + + sas._channel.transactionId; + return olmSAS.generate_bytes(sasInfo, bytes); + }, + "curve25519": function(sas, olmSAS, bytes) { + const ourInfo = `${sas._baseApis.getUserId()}${sas._baseApis.deviceId}`; + const theirInfo = `${sas.userId}${sas.deviceId}`; + const sasInfo = + "MATRIX_KEY_VERIFICATION_SAS" + + (sas.initiatedByMe ? ourInfo + theirInfo : theirInfo + ourInfo) + + sas._channel.transactionId; + return olmSAS.generate_bytes(sasInfo, bytes); + }, +}; + /* lists of algorithms/methods that are supported. The key agreement, hashes, * and MAC lists should be sorted in order of preference (most preferred * first). */ -const KEY_AGREEMENT_LIST = ["curve25519"]; +const KEY_AGREEMENT_LIST = ["curve25519-hkdf-sha256", "curve25519"]; const HASHES_LIST = ["sha256"]; const MAC_LIST = ["hkdf-hmac-sha256", "hmac-sha256"]; const SAS_LIST = Object.keys(sasGenerators); @@ -291,12 +313,14 @@ export class SAS extends Base { if (typeof content.commitment !== "string") { throw newInvalidMessageError(); } + const keyAgreement = content.key_agreement_protocol; const macMethod = content.message_authentication_code; const hashCommitment = content.commitment; const olmSAS = new global.Olm.SAS(); try { - this._send("m.key.verification.key", { - key: olmSAS.get_pubkey(), + this.ourSASPubKey = olmSAS.get_pubkey(); + await this._send("m.key.verification.key", { + key: this.ourSASPubKey, }); @@ -308,19 +332,20 @@ export class SAS extends Base { if (olmutil.sha256(commitmentStr) !== hashCommitment) { throw newMismatchedCommitmentError(); } + this.theirSASPubKey = content.key; olmSAS.set_their_key(content.key); - const sasInfo = "MATRIX_KEY_VERIFICATION_SAS" - + this._baseApis.getUserId() + this._baseApis.deviceId - + this.userId + this.deviceId - + this._channel.transactionId; - const sasBytes = olmSAS.generate_bytes(sasInfo, 6); + const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6); const verifySAS = new Promise((resolve, reject) => { this.sasEvent = { sas: generateSas(sasBytes, sasMethods), - confirm: () => { - this._sendMAC(olmSAS, macMethod); - resolve(); + confirm: async () => { + try { + await this._sendMAC(olmSAS, macMethod); + resolve(); + } catch (err) { + reject(err); + } }, cancel: () => reject(newUserCancelledError()), mismatch: () => reject(newMismatchedSASError()), @@ -377,7 +402,7 @@ export class SAS extends Base { const olmSAS = new global.Olm.SAS(); try { const commitmentStr = olmSAS.get_pubkey() + anotherjson.stringify(content); - this._send("m.key.verification.accept", { + await this._send("m.key.verification.accept", { key_agreement_protocol: keyAgreement, hash: hashMethod, message_authentication_code: macMethod, @@ -390,22 +415,24 @@ export class SAS extends Base { let e = await this._waitForEvent("m.key.verification.key"); // FIXME: make sure event is properly formed content = e.getContent(); + this.theirSASPubKey = content.key; olmSAS.set_their_key(content.key); - this._send("m.key.verification.key", { - key: olmSAS.get_pubkey(), + this.ourSASPubKey = olmSAS.get_pubkey(); + await this._send("m.key.verification.key", { + key: this.ourSASPubKey, }); - const sasInfo = "MATRIX_KEY_VERIFICATION_SAS" - + this.userId + this.deviceId - + this._baseApis.getUserId() + this._baseApis.deviceId - + this._channel.transactionId; - const sasBytes = olmSAS.generate_bytes(sasInfo, 6); + const sasBytes = calculateKeyAgreement[keyAgreement](this, olmSAS, 6); const verifySAS = new Promise((resolve, reject) => { this.sasEvent = { sas: generateSas(sasBytes, sasMethods), - confirm: () => { - this._sendMAC(olmSAS, macMethod); - resolve(); + confirm: async () => { + try { + await this._sendMAC(olmSAS, macMethod); + resolve(); + } catch(err) { + reject(err); + } }, cancel: () => reject(newUserCancelledError()), mismatch: () => reject(newMismatchedSASError()), @@ -461,7 +488,7 @@ export class SAS extends Base { keyList.sort().join(","), baseInfo + "KEY_IDS", ); - this._send("m.key.verification.mac", { mac, keys }); + return this._send("m.key.verification.mac", { mac, keys }); } async _checkMAC(olmSAS, content, method) { diff --git a/src/crypto/verification/request/VerificationRequest.js b/src/crypto/verification/request/VerificationRequest.js index afb0d51a..a77b8c96 100644 --- a/src/crypto/verification/request/VerificationRequest.js +++ b/src/crypto/verification/request/VerificationRequest.js @@ -25,15 +25,17 @@ import { } from "../Error"; import {QRCodeData, SCAN_QR_CODE_METHOD} from "../QRCode"; -// the recommended amount of time before a verification request -// should be (automatically) cancelled without user interaction -// and ignored. -const VERIFICATION_REQUEST_TIMEOUT = 10 * 60 * 1000; //10m +// How long after the event's timestamp that the request times out +const TIMEOUT_FROM_EVENT_TS = 10 * 60 * 1000; // 10 minutes + +// How long after we receive the event that the request times out +const TIMEOUT_FROM_EVENT_RECEIPT = 2 * 60 * 1000; // 2 minutes + // to avoid almost expired verification notifications // from showing a notification and almost immediately // disappearing, also ignore verification requests that // are this amount of time away from expiring. -const VERIFICATION_REQUEST_MARGIN = 3 * 1000; //3s +const VERIFICATION_REQUEST_MARGIN = 3 * 1000; // 3 seconds export const EVENT_PREFIX = "m.key.verification."; @@ -73,12 +75,16 @@ export class VerificationRequest extends EventEmitter { this._accepting = false; this._declining = false; this._verifierHasFinished = false; + this._cancelled = false; this._chosenMethod = null; // we keep a copy of the QR Code data (including other user master key) around // for QR reciprocate verification, to protect against // cross-signing identity reset between the .ready and .start event // and signing the wrong key after .start this._qrCodeData = null; + + // The timestamp when we received the request event from the other side + this._requestReceivedAt = null; } /** @@ -164,12 +170,26 @@ export class VerificationRequest extends EventEmitter { return this._chosenMethod; } + calculateEventTimeout(event) { + let effectiveExpiresAt = this.channel.getTimestamp(event) + + TIMEOUT_FROM_EVENT_TS; + + if (this._requestReceivedAt && !this.initiatedByMe && + this.phase <= PHASE_REQUESTED + ) { + const expiresAtByReceipt = this._requestReceivedAt + + TIMEOUT_FROM_EVENT_RECEIPT; + effectiveExpiresAt = Math.min(effectiveExpiresAt, expiresAtByReceipt); + } + + return Math.max(0, effectiveExpiresAt - Date.now()); + } + /** The current remaining amount of ms before the request should be automatically cancelled */ get timeout() { const requestEvent = this._getEventByEither(REQUEST_TYPE); if (requestEvent) { - const elapsed = Date.now() - this.channel.getTimestamp(requestEvent); - return Math.max(0, VERIFICATION_REQUEST_TIMEOUT - elapsed); + return this.calculateEventTimeout(requestEvent); } return 0; } @@ -525,7 +545,7 @@ export class VerificationRequest extends EventEmitter { } const cancelEvent = this._getEventByEither(CANCEL_TYPE); - if (cancelEvent && phase() !== PHASE_DONE) { + if ((this._cancelled || cancelEvent) && phase() !== PHASE_DONE) { transitions.push({phase: PHASE_CANCELLED, event: cancelEvent}); return transitions; } @@ -734,7 +754,7 @@ export class VerificationRequest extends EventEmitter { _setupTimeout(phase) { const shouldTimeout = !this._timeoutTimer && !this.observeOnly && - phase === PHASE_REQUESTED && this.initiatedByMe; + phase === PHASE_REQUESTED; if (shouldTimeout) { this._timeoutTimer = setTimeout(this._cancelOnTimeout, this.timeout); @@ -753,7 +773,17 @@ export class VerificationRequest extends EventEmitter { _cancelOnTimeout = () => { try { - this.cancel({reason: "Other party didn't accept in time", code: "m.timeout"}); + if (this.initiatedByMe) { + this.cancel({ + reason: "Other party didn't accept in time", + code: "m.timeout", + }); + } else { + this.cancel({ + reason: "User didn't accept in time", + code: "m.timeout", + }); + } } catch (err) { logger.error("Error while cancelling verification request", err); } @@ -790,16 +820,8 @@ export class VerificationRequest extends EventEmitter { if (!isLiveEvent) { this._observeOnly = true; } - // a timestamp is not provided on all to_device events - const timestamp = this.channel.getTimestamp(event); - if (Number.isFinite(timestamp)) { - const elapsed = Date.now() - timestamp; - // don't allow interaction on old requests - if (elapsed > (VERIFICATION_REQUEST_TIMEOUT - VERIFICATION_REQUEST_MARGIN) || - elapsed < -(VERIFICATION_REQUEST_TIMEOUT / 2) - ) { - this._observeOnly = true; - } + if (this.calculateEventTimeout(event) < VERIFICATION_REQUEST_MARGIN) { + this._observeOnly = true; } } @@ -818,6 +840,8 @@ export class VerificationRequest extends EventEmitter { this._eventsByThem.delete(type); } } + // also remember when we received the request event + this._requestReceivedAt = Date.now(); } } @@ -858,6 +882,15 @@ export class VerificationRequest extends EventEmitter { return true; } + onVerifierCancelled() { + this._cancelled = true; + // move to cancelled phase + const newTransitions = this._applyPhaseTransitions(); + if (newTransitions.length) { + this._setPhase(newTransitions[newTransitions.length - 1].phase); + } + } + onVerifierFinished() { this.channel.send("m.key.verification.done", {}); this._verifierHasFinished = true; @@ -867,4 +900,8 @@ export class VerificationRequest extends EventEmitter { this._setPhase(newTransitions[newTransitions.length - 1].phase); } } + + getEventFromOtherParty(type) { + return this._eventsByThem.get(type); + } } diff --git a/src/filter-component.js b/src/filter-component.js index 7e8fd524..8ff76067 100644 --- a/src/filter-component.js +++ b/src/filter-component.js @@ -108,8 +108,9 @@ FilterComponent.prototype._checkFields = } const allowed_values = self[name]; - if (allowed_values) { - if (!allowed_values.map(match_func)) { + if (allowed_values && allowed_values.length > 0) { + const anyMatch = allowed_values.some(match_func); + if (!anyMatch) { return false; } } diff --git a/src/filter.js b/src/filter.js index f8884b27..6ab581c6 100644 --- a/src/filter.js +++ b/src/filter.js @@ -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 diff --git a/src/http-api.js b/src/http-api.js index d297c308..e7134bb5 100644 --- a/src/http-api.js +++ b/src/http-api.js @@ -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; diff --git a/src/interactive-auth.js b/src/interactive-auth.js index eb0e25a5..ad47701e 100644 --- a/src/interactive-auth.js +++ b/src/interactive-auth.js @@ -143,11 +143,13 @@ InteractiveAuth.prototype = { this._resolveFunc = resolve; this._rejectFunc = reject; - // if we have no flows, try a request (we'll have - // just a session ID in _data if resuming) - if (!this._data.flows) { + const hasFlows = this._data && this._data.flows; + + // if we have no flows, try a request to acquire the flows + if (!hasFlows) { if (this._busyChangedCallback) this._busyChangedCallback(true); - this._doRequest(this._data).finally(() => { + // Do a fresh request as we're just acquiring flows. + this._doRequest(null).finally(() => { if (this._busyChangedCallback) this._busyChangedCallback(false); }); } else { @@ -186,7 +188,11 @@ InteractiveAuth.prototype = { } authDict = { type: EMAIL_STAGE_TYPE, + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/matrix-org/synapse/issues/5665 + // See https://github.com/matrix-org/matrix-doc/issues/2220 threepid_creds: creds, + threepidCreds: creds, }; } } @@ -264,11 +270,16 @@ InteractiveAuth.prototype = { } } - // use the sessionid from the last request. - const auth = { - session: this._data.session, - }; - utils.extend(auth, authData); + // use the sessionid from the last request, if one is present. + let auth; + if (this._data.session) { + auth = { + session: this._data.session, + }; + utils.extend(auth, authData); + } else { + auth = authData; + } try { // NB. the 'background' flag is deprecated by the busyChanged @@ -325,7 +336,7 @@ InteractiveAuth.prototype = { } catch (error) { // sometimes UI auth errors don't come with flows const errorFlows = error.data ? error.data.flows : null; - const haveFlows = Boolean(this._data.flows) || Boolean(errorFlows); + const haveFlows = this._data.flows || Boolean(errorFlows); if (error.httpStatus !== 401 || !error.data || !haveFlows) { // doesn't look like an interactive-auth failure. if (!background) { @@ -351,7 +362,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 && @@ -414,7 +431,7 @@ InteractiveAuth.prototype = { return; } - if (this._data.errcode || this._data.error) { + if (this._data && this._data.errcode || this._data.error) { this._stateUpdatedCallback(nextStage, { errcode: this._data.errcode || "", error: this._data.error || "", diff --git a/src/matrix.js b/src/matrix.ts index f9f71c54..7a60404d 100644 --- a/src/matrix.js +++ b/src/matrix.ts @@ -16,8 +16,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type Request from "request"; + import {MemoryCryptoStore} from "./crypto/store/memory-crypto-store"; +import {LocalStorageCryptoStore} from "./crypto/store/localStorage-crypto-store"; +import {IndexedDBCryptoStore} from "./crypto/store/indexeddb-crypto-store"; import {MemoryStore} from "./store/memory"; +import {StubStore} from "./store/stub"; +import {LocalIndexedDBStoreBackend} from "./store/indexeddb-local-backend"; +import {RemoteIndexedDBStoreBackend} from "./store/indexeddb-remote-backend"; import {MatrixScheduler} from "./scheduler"; import {MatrixClient} from "./client"; @@ -89,6 +96,10 @@ export function wrapRequest(wrapper) { }; } +type Store = + StubStore | MemoryStore | LocalIndexedDBStoreBackend | RemoteIndexedDBStoreBackend; + +type CryptoStore = MemoryCryptoStore | LocalStorageCryptoStore | IndexedDBCryptoStore; let cryptoStoreFactory = () => new MemoryCryptoStore; @@ -102,6 +113,15 @@ export function setCryptoStoreFactory(fac) { cryptoStoreFactory = fac; } + +interface ICreateClientOpts { + baseUrl: string; + store?: Store; + cryptoStore?: CryptoStore; + scheduler?: MatrixScheduler; + request?: Request; +} + /** * Construct a Matrix Client. Similar to {@link module:client.MatrixClient} * except that the 'request', 'store' and 'scheduler' dependencies are satisfied. @@ -125,10 +145,10 @@ export function setCryptoStoreFactory(fac) { * @see {@link module:client.MatrixClient} for the full list of options for * <code>opts</code>. */ -export function createClient(opts) { +export function createClient(opts: ICreateClientOpts | string) { if (typeof opts === "string") { opts = { - "baseUrl": opts, + "baseUrl": opts as string, }; } opts.request = opts.request || requestInstance; diff --git a/src/models/event.js b/src/models/event.js index 5b110cbc..90365432 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -390,11 +390,12 @@ utils.extend(MatrixEvent.prototype, { * @internal * * @param {module:crypto} crypto crypto module + * @param {bool} isRetry True if this is a retry (enables more logging) * * @returns {Promise} promise which resolves (to undefined) when the decryption * attempt is completed. */ - attemptDecryption: async function(crypto) { + attemptDecryption: async function(crypto, isRetry) { // start with a couple of sanity checks. if (!this.isEncrypted()) { throw new Error("Attempt to decrypt event which isn't encrypted"); @@ -406,7 +407,7 @@ utils.extend(MatrixEvent.prototype, { ) { // we may want to just ignore this? let's start with rejecting it. throw new Error( - "Attempt to decrypt event which has already been encrypted", + "Attempt to decrypt event which has already been decrypted", ); } @@ -424,7 +425,7 @@ utils.extend(MatrixEvent.prototype, { return this._decryptionPromise; } - this._decryptionPromise = this._decryptionLoop(crypto); + this._decryptionPromise = this._decryptionLoop(crypto, isRetry); return this._decryptionPromise; }, @@ -469,7 +470,7 @@ utils.extend(MatrixEvent.prototype, { return recipients; }, - _decryptionLoop: async function(crypto) { + _decryptionLoop: async function(crypto, isRetry) { // make sure that this method never runs completely synchronously. // (doing so would mean that we would clear _decryptionPromise *before* // it is set in attemptDecryption - and hence end up with a stuck @@ -486,13 +487,18 @@ utils.extend(MatrixEvent.prototype, { res = this._badEncryptedMessage("Encryption not enabled"); } else { res = await crypto.decryptEvent(this); + if (isRetry) { + logger.info(`Decrypted event on retry (id=${this.getId()})`); + } } } catch (e) { if (e.name !== "DecryptionError") { // not a decryption error: log the whole exception as an error // (and don't bother with a retry) + const re = isRetry ? 're' : ''; logger.error( - `Error decrypting event (id=${this.getId()}): ${e.stack || e}`, + `Error ${re}decrypting event ` + + `(id=${this.getId()}): ${e.stack || e}`, ); this._decryptionPromise = null; this._retryDecryption = false; diff --git a/src/models/room.js b/src/models/room.js index 2381ce15..abed7252 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -209,7 +209,7 @@ utils.inherits(Room, EventEmitter); Room.prototype.getVersion = function() { const createEvent = this.currentState.getStateEvents("m.room.create", ""); if (!createEvent) { - logger.warn("Room " + this.room_id + " does not have an m.room.create event"); + logger.warn("Room " + this.roomId + " does not have an m.room.create event"); return '1'; } const ver = createEvent.getContent()['room_version']; @@ -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; } diff --git a/src/pushprocessor.js b/src/pushprocessor.js index 4c733841..da7fb04a 100644 --- a/src/pushprocessor.js +++ b/src/pushprocessor.js @@ -23,9 +23,8 @@ import {escapeRegExp, globToRegexp, isNullOrUndefined} from "./utils"; const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride']; -// The default override rules to apply when calculating actions for an event. These -// defaults apply under no other circumstances to avoid confusing the client with server -// state. We do this for two reasons: +// The default override rules to apply to the push rules that arrive from the server. +// We do this for two reasons: // 1. Synapse is unlikely to send us the push rule in an incremental sync - see // https://github.com/matrix-org/synapse/pull/4867#issuecomment-481446072 for // more details. @@ -364,33 +363,6 @@ export function PushProcessor(client) { return actionObj; }; - const applyRuleDefaults = function(clientRuleset) { - // Deep clone the object before we mutate it - const ruleset = JSON.parse(JSON.stringify(clientRuleset)); - - if (!clientRuleset['global']) { - clientRuleset['global'] = {}; - } - if (!clientRuleset['global']['override']) { - clientRuleset['global']['override'] = []; - } - - // Apply default overrides - const globalOverrides = clientRuleset['global']['override']; - for (const override of DEFAULT_OVERRIDE_RULES) { - const existingRule = globalOverrides - .find((r) => r.rule_id === override.rule_id); - - if (!existingRule) { - const ruleId = override.rule_id; - console.warn(`Adding default global override for ${ruleId}`); - globalOverrides.push(override); - } - } - - return ruleset; - }; - this.ruleMatchesEvent = function(rule, ev) { let ret = true; for (let i = 0; i < rule.conditions.length; ++i) { @@ -410,8 +382,7 @@ export function PushProcessor(client) { * @return {PushAction} */ this.actionsForEvent = function(ev) { - const rules = applyRuleDefaults(client.pushRules); - return pushActionsForEventAndRulesets(ev, rules); + return pushActionsForEventAndRulesets(ev, client.pushRules); }; /** @@ -476,18 +447,25 @@ PushProcessor.rewriteDefaultRules = function(incomingRules) { if (!newRules.global) newRules.global = {}; if (!newRules.global.override) newRules.global.override = []; - // Fix default override rules - newRules.global.override = newRules.global.override.map(r => { - const defaultRule = DEFAULT_OVERRIDE_RULES.find(d => d.rule_id === r.rule_id); - if (!defaultRule) return r; - - // Copy over the actions, default, and conditions. Don't touch the user's - // preference. - r.default = defaultRule.default; - r.conditions = defaultRule.conditions; - r.actions = defaultRule.actions; - return r; - }); + // Merge the client-level defaults with the ones from the server + const globalOverrides = newRules.global.override; + for (const override of DEFAULT_OVERRIDE_RULES) { + const existingRule = globalOverrides + .find((r) => r.rule_id === override.rule_id); + + if (existingRule) { + // Copy over the actions, default, and conditions. Don't touch the user's + // preference. + existingRule.default = override.default; + existingRule.conditions = override.conditions; + existingRule.actions = override.actions; + } else { + // Add the rule + const ruleId = override.rule_id; + console.warn(`Adding default global override for ${ruleId}`); + globalOverrides.push(override); + } + } return newRules; }; diff --git a/src/store/memory.js b/src/store/memory.js index e420e05a..bf2e9cfd 100644 --- a/src/store/memory.js +++ b/src/store/memory.js @@ -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) {} }, diff --git a/src/sync.js b/src/sync.js index 99a7f8d2..142e85fb 100644 --- a/src/sync.js +++ b/src/sync.js @@ -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; diff --git a/src/utils.ts b/src/utils.ts index 1e31dff8..e5fd7d61 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -21,6 +21,7 @@ limitations under the License. */ import unhomoglyph from 'unhomoglyph'; +import {ConnectionError} from "./http-api"; /** * Encode a dictionary of query parameters. @@ -265,7 +266,7 @@ export function checkObjectHasNoAdditionalKeys(obj: object, allowedKeys: string[ * @param {Object} obj The object to deep copy. * @return {Object} A copy of the object without any references to the original. */ -export function deepCopy(obj: object): object { +export function deepCopy<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)); } @@ -372,6 +373,7 @@ export function extend() { const target = arguments[0] || {}; for (let i = 1; i < arguments.length; i++) { const source = arguments[i]; + if (!source) continue; for (const propName in source) { // eslint-disable-line guard-for-in target[propName] = source[propName]; } diff --git a/tsconfig.json b/tsconfig.json index e01a31f6..548bbe7f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, + "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", "target": "es2016", @@ -25,42 +25,43 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/[email protected]^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.6.tgz#7eeaa0dfa17e50c7d9c0832515eee09b56f04e35" - integrity sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q== +"@babel/[email protected]^7.8.6", "@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" + integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== dependencies: - browserslist "^4.8.5" + browserslist "^4.9.1" invariant "^2.2.4" semver "^5.5.0" "@babel/[email protected]^7.0.0", "@babel/[email protected]^7.1.0", "@babel/[email protected]^7.7.5": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.7.tgz#b69017d221ccdeb203145ae9da269d72cf102f3b" - integrity sha512-rBlqF3Yko9cynC5CCFy6+K/w2N+Sq/ff2BPy+Krp7rHlABIr5epbA7OxVeKoMHB39LZOp1UY5SuLjy6uWi35yA== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.7" - "@babel/helpers" "^7.8.4" - "@babel/parser" "^7.8.7" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" "@babel/template" "^7.8.6" - "@babel/traverse" "^7.8.6" - "@babel/types" "^7.8.7" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.1" - json5 "^2.1.0" + json5 "^2.1.2" lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/[email protected]^7.4.0", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.7.tgz#870b3cf7984f5297998152af625c4f3e341400f7" - integrity sha512-DQwjiKJqH4C3qGiyQCAExJHoZssn49JTMJgZ8SANGgVFdkupcUhLOdkAeoC6kmHZCPfoDG5M0b6cFlSN5wW7Ew== +"@babel/[email protected]^7.4.0", "@babel/[email protected]^7.9.0", "@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9" + integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ== dependencies: - "@babel/types" "^7.8.7" + "@babel/types" "^7.9.5" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -80,15 +81,6 @@ "@babel/helper-explode-assignable-expression" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/[email protected]^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz#28a279c2e6c622a6233da548127f980751324cab" - integrity sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ== - dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.7" - "@babel/[email protected]^7.8.7": version "7.8.7" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" @@ -101,25 +93,25 @@ semver "^5.5.0" "@babel/[email protected]^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" - integrity sha512-klTBDdsr+VFFqaDHm5rR69OpEQtO2Qv8ECxHS1mNhJJvaHArR6a1xTf5K/eZW7eZpJbhCx3NW1Yt/sKsLXLblg== + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.9.5.tgz#79753d44017806b481017f24b02fd4113c7106ea" + integrity sha512-IipaxGaQmW4TfWoXdqjY0TzoXQ1HRS0kPpEgvjosb3u7Uedcq297xFqDQiCcQtRRwzIMif+N1MLVI8C5a4/PAA== dependencies: - "@babel/helper-function-name" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" "@babel/helper-member-expression-to-functions" "^7.8.3" "@babel/helper-optimise-call-expression" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.6" "@babel/helper-split-export-declaration" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz#7fa040c97fb8aebe1247a5c645330c32d083066b" - integrity sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A== +"@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.8": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" + integrity sha512-LYVPdwkrQEiX9+1R29Ld/wTrmQu1SSKYnuOk3g0CkcZMA1p0gsNxJFj/3gBdaJ7Cg0Fnek5z0DsMULePP7Lrqg== dependencies: "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-regex" "^7.8.3" - regexpu-core "^4.6.0" + regexpu-core "^4.7.0" "@babel/[email protected]^7.8.3": version "7.8.3" @@ -138,14 +130,14 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" - integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== +"@babel/[email protected]^7.8.3", "@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c" + integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw== dependencies: "@babel/helper-get-function-arity" "^7.8.3" "@babel/template" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/types" "^7.9.5" "@babel/[email protected]^7.8.3": version "7.8.3" @@ -175,17 +167,17 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz#6a13b5eecadc35692047073a64e42977b97654a4" - integrity sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" + integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA== dependencies: "@babel/helper-module-imports" "^7.8.3" "@babel/helper-replace-supers" "^7.8.6" "@babel/helper-simple-access" "^7.8.3" "@babel/helper-split-export-declaration" "^7.8.3" "@babel/template" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/types" "^7.9.0" lodash "^4.17.13" "@babel/[email protected]^7.8.3": @@ -243,6 +235,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/[email protected]^7.9.0", "@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" + integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== + "@babel/[email protected]^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" @@ -253,28 +250,28 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" -"@babel/[email protected]^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" - integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== +"@babel/[email protected]^7.9.0": + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f" + integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA== dependencies: "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.4" - "@babel/types" "^7.8.3" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" "@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" - integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" + integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== dependencies: + "@babel/helper-validator-identifier" "^7.9.0" chalk "^2.0.0" - esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/[email protected]^7.1.0", "@babel/[email protected]^7.2.3", "@babel/[email protected]^7.4.3", "@babel/[email protected]^7.4.4", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.7.tgz#7b8facf95d25fef9534aad51c4ffecde1a61e26a" - integrity sha512-9JWls8WilDXFGxs0phaXAZgpxTZhSk/yOYH2hTHC0X1yC7Z78IJfvR1vJ+rmJKq3I35td2XzXzN6ZLYlna+r/A== +"@babel/[email protected]^7.1.0", "@babel/[email protected]^7.2.3", "@babel/[email protected]^7.4.3", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.9.0", "@babel/[email protected]^7.9.4": + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" + integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== "@babel/[email protected]^7.8.3": version "7.8.3" @@ -317,7 +314,7 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/[email protected]^7.7.4": +"@babel/[email protected]^7.7.4", "@babel/[email protected]^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== @@ -325,13 +322,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-numeric-separator" "^7.8.3" -"@babel/[email protected]^7.7.4", "@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" - integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== +"@babel/[email protected]^7.7.4", "@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116" + integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.9.5" "@babel/[email protected]^7.8.3": version "7.8.3" @@ -341,20 +339,20 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" - integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== dependencies: "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz#b646c3adea5f98800c9ab45105ac34d06cd4a47f" - integrity sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ== +"@babel/[email protected]^7.4.4", "@babel/[email protected]^7.8.3": + version "7.8.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" + integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.8.8" "@babel/helper-plugin-utils" "^7.8.3" "@babel/[email protected]^7.8.0": @@ -385,7 +383,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/[email protected]^7.8.3": +"@babel/[email protected]^7.8.0", "@babel/[email protected]^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== @@ -458,14 +456,14 @@ "@babel/helper-plugin-utils" "^7.8.3" lodash "^4.17.13" -"@babel/[email protected]^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz#77534447a477cbe5995ae4aee3e39fbc8090c46d" - integrity sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg== +"@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c" + integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg== dependencies: "@babel/helper-annotate-as-pure" "^7.8.3" "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" + "@babel/helper-function-name" "^7.9.5" "@babel/helper-optimise-call-expression" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.6" @@ -479,14 +477,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b" - integrity sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ== +"@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50" + integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q== dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/[email protected]^7.8.3": +"@babel/[email protected]^7.4.4", "@babel/[email protected]^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== @@ -509,10 +507,10 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/[email protected]^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz#a051bd1b402c61af97a27ff51b468321c7c2a085" - integrity sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" + integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== dependencies: "@babel/helper-plugin-utils" "^7.8.3" @@ -538,41 +536,41 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" - integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" + integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== dependencies: - "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" - integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz#e3e72f4cbc9b4a260e30be0ea59bdf5a39748940" + integrity sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g== dependencies: - "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" - integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" + integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== dependencies: "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" - integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== +"@babel/[email protected]^7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" + integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== dependencies: - "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-module-transforms" "^7.9.0" "@babel/helper-plugin-utils" "^7.8.3" "@babel/[email protected]^7.8.3": @@ -597,12 +595,11 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/helper-replace-supers" "^7.8.3" -"@babel/[email protected]^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.7.tgz#66fa2f1de4129b4e0447509223ac71bda4955395" - integrity sha512-brYWaEPTRimOctz2NDA3jnBbDi7SVN2T4wYuu0aqSzxC3nozFZngGaw29CJ9ZPweB7k+iFmZuoG3IVPIcXmD2g== +"@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795" + integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA== dependencies: - "@babel/helper-call-delegate" "^7.8.7" "@babel/helper-get-function-arity" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -628,9 +625,9 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/[email protected]^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.8.3.tgz#c0153bc0a5375ebc1f1591cb7eea223adea9f169" - integrity sha512-/vqUt5Yh+cgPZXXjmaG9NT8aVfThKk7G4OqkVhrXqwsC5soMn/qTCxs36rZ2QFhpfTJcjw4SNDIZ4RUb8OL4jQ== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" + integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== dependencies: "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -674,10 +671,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" -"@babel/[email protected]^7.8.3": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.8.7.tgz#48bccff331108a7b3a28c3a4adc89e036dc3efda" - integrity sha512-7O0UsPQVNKqpHeHLpfvOG4uXmlw+MOxYvUv6Otc9uH5SYMIxvF6eBdjkWvC3f9G+VXe0RsNExyAQBeTRug/wqQ== +"@babel/[email protected]^7.9.0": + version "7.9.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.9.4.tgz#4bb4dde4f10bbf2d787fce9707fb09b483e33359" + integrity sha512-yeWeUkKx2auDbSxRe8MusAG+n4m9BFY/v+lPjmQDgOFX5qnySkUY5oXzkp6FwPdsYqnKay6lorXYdC0n3bZO7w== dependencies: "@babel/helper-create-class-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -700,11 +697,11 @@ regenerator-runtime "^0.13.4" "@babel/[email protected]^7.7.6": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.7.tgz#1fc7d89c7f75d2d70c2b6768de6c2e049b3cb9db" - integrity sha512-BYftCVOdAYJk5ASsznKAUl53EMhfBbr8CJ1X+AJLfGPscQkwJFiaV/Wn9DPH/7fzm2v6iRYJKYHSqyynTGw0nw== + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.5.tgz#8ddc76039bc45b774b19e2fc548f6807d8a8919f" + integrity sha512-eWGYeADTlPJH+wq1F0wNfPbVS1w1wtmMJiYk55Td5Yu28AsdR9AsC97sZ0Qq8fHqQuslVSIYSGJMcblr345GfQ== dependencies: - "@babel/compat-data" "^7.8.6" + "@babel/compat-data" "^7.9.0" "@babel/helper-compilation-targets" "^7.8.7" "@babel/helper-module-imports" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" @@ -712,14 +709,16 @@ "@babel/plugin-proposal-dynamic-import" "^7.8.3" "@babel/plugin-proposal-json-strings" "^7.8.3" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.5" "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" "@babel/plugin-syntax-async-generators" "^7.8.0" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-json-strings" "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" @@ -728,24 +727,24 @@ "@babel/plugin-transform-async-to-generator" "^7.8.3" "@babel/plugin-transform-block-scoped-functions" "^7.8.3" "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.8.6" + "@babel/plugin-transform-classes" "^7.9.5" "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.9.5" "@babel/plugin-transform-dotall-regex" "^7.8.3" "@babel/plugin-transform-duplicate-keys" "^7.8.3" "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.8.6" + "@babel/plugin-transform-for-of" "^7.9.0" "@babel/plugin-transform-function-name" "^7.8.3" "@babel/plugin-transform-literals" "^7.8.3" "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.8.3" - "@babel/plugin-transform-modules-commonjs" "^7.8.3" - "@babel/plugin-transform-modules-systemjs" "^7.8.3" - "@babel/plugin-transform-modules-umd" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" "@babel/plugin-transform-new-target" "^7.8.3" "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-parameters" "^7.9.5" "@babel/plugin-transform-property-literals" "^7.8.3" "@babel/plugin-transform-regenerator" "^7.8.7" "@babel/plugin-transform-reserved-words" "^7.8.3" @@ -755,25 +754,37 @@ "@babel/plugin-transform-template-literals" "^7.8.3" "@babel/plugin-transform-typeof-symbol" "^7.8.4" "@babel/plugin-transform-unicode-regex" "^7.8.3" - "@babel/types" "^7.8.7" - browserslist "^4.8.5" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.5" + browserslist "^4.9.1" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" semver "^5.5.0" +"@babel/[email protected]^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + "@babel/[email protected]^7.7.4": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.8.3.tgz#90af8690121beecd9a75d0cc26c6be39d1595d13" - integrity sha512-qee5LgPGui9zQ0jR1TeU5/fP9L+ovoArklEqY12ek8P/wV5ZeM/VYSQYwICeoT6FfpJTekG9Ilay5PhwsOpMHA== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz#87705a72b1f0d59df21c179f7c3d2ef4b16ce192" + integrity sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg== dependencies: "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-typescript" "^7.8.3" + "@babel/plugin-transform-typescript" "^7.9.0" "@babel/[email protected]^7.7.4": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.8.6.tgz#a1066aa6168a73a70c35ef28cc5865ccc087ea69" - integrity sha512-7IDO93fuRsbyml7bAafBQb3RcBGlCpU4hh5wADA2LJEEcYk92WkwFZ0pHyIi2fb5Auoz1714abETdZKCOxN0CQ== + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.9.0.tgz#02464ede57548bddbb5e9f705d263b7c3f43d48b" + integrity sha512-Tv8Zyi2J2VRR8g7pC5gTeIN8Ihultbmk0ocyNz8H2nEZbmhp1N6q0A1UGsQbDvGP/sNinQKUHf3SqXwqjtFv4Q== dependencies: find-cache-dir "^2.0.0" lodash "^4.17.13" @@ -782,9 +793,9 @@ source-map-support "^0.5.16" "@babel/[email protected]^7.0.0", "@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.4": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.7.tgz#8fefce9802db54881ba59f90bb28719b4996324d" - integrity sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg== + version "7.9.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" + integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== dependencies: regenerator-runtime "^0.13.4" @@ -797,27 +808,27 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/[email protected]^7.1.0", "@babel/[email protected]^7.4.3", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.4", "@babel/[email protected]^7.8.6": - version "7.8.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" - integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== +"@babel/[email protected]^7.1.0", "@babel/[email protected]^7.4.3", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.9.0": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" + integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== dependencies: "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.8.6" - "@babel/helper-function-name" "^7.8.3" + "@babel/generator" "^7.9.5" + "@babel/helper-function-name" "^7.9.5" "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" + "@babel/parser" "^7.9.0" + "@babel/types" "^7.9.5" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/[email protected]^7.0.0", "@babel/[email protected]^7.3.0", "@babel/[email protected]^7.4.0", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.8.7": - version "7.8.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.7.tgz#1fc9729e1acbb2337d5b6977a63979b4819f5d1d" - integrity sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw== +"@babel/[email protected]^7.0.0", "@babel/[email protected]^7.3.0", "@babel/[email protected]^7.4.0", "@babel/[email protected]^7.4.4", "@babel/[email protected]^7.7.0", "@babel/[email protected]^7.8.3", "@babel/[email protected]^7.8.6", "@babel/[email protected]^7.9.0", "@babel/[email protected]^7.9.5": + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" + integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== dependencies: - esutils "^2.0.2" + "@babel/helper-validator-identifier" "^7.9.5" lodash "^4.17.13" to-fast-properties "^2.0.0" @@ -983,9 +994,9 @@ integrity sha512-dBtBbrc+qTHy1WdfHYjBwRln4+LWqASWakLHsWHR2NWHIFkv4W3O070IGoGLEBrJBvct3r0L1BUPuvURi7kYUQ== "@types/[email protected]^7.1.0": - version "7.1.6" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" - integrity sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg== + version "7.1.7" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" + integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" @@ -1009,9 +1020,9 @@ "@babel/types" "^7.0.0" "@types/[email protected]*", "@types/[email protected]^7.0.6": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.9.tgz#be82fab304b141c3eee81a4ce3b034d0eba1590a" - integrity sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw== + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf" + integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw== dependencies: "@babel/types" "^7.3.0" @@ -1022,6 +1033,11 @@ dependencies: "@types/babel-types" "*" +"@types/[email protected]*": + version "0.12.2" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" + integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w== + "@types/[email protected]*", "@types/[email protected]^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1047,16 +1063,36 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== +"@types/[email protected]*": + version "13.13.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.1.tgz#1ba94c5a177a1692518bfc7b41aec0aa1a14354e" + integrity sha512-uysqysLJ+As9jqI5yqjwP3QJrhOcUwBjHUlUxPxjbplwKoILvXVsmYWEhfmAQlrPfbRZmhJB007o4L9sKqtHqQ== + "@types/[email protected]": - version "12.12.29" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.29.tgz#46275f028b4e463b9ac5fefc1d08bc66cc193f25" - integrity sha512-yo8Qz0ygADGFptISDj3pOC9wXfln/5pQaN/ysDIzOaAWXt73cNHmtEC8zSO2Y+kse/txmwIAJzkYZ5fooaS5DQ== + version "12.12.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.34.tgz#0a5d6ae5d22612f0cf5f10320e1fc5d2a745dcb8" + integrity sha512-BneGN0J9ke24lBRn44hVHNeDlrXRYF+VRp0HbSUNnEZahXGAysHZIqnf/hER6aabdBgzM4YOV4jrR8gj4Zfi0g== + +"@types/[email protected]^2.48.4": + version "2.48.4" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e" + integrity sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw== + dependencies: + "@types/caseless" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + form-data "^2.5.0" "@types/[email protected]^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/[email protected]*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" + integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A== + "@types/[email protected]*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -1070,18 +1106,19 @@ "@types/yargs-parser" "*" "@typescript-eslint/[email protected]^2.5.0": - version "2.23.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.23.0.tgz#5d2261c8038ec1698ca4435a8da479c661dc9242" - integrity sha512-OswxY59RcXH3NNPmq+4Kis2CYZPurRU6mG5xPcn24CjFyfdVli5mySwZz/g/xDbJXgDsYqNGq7enV0IziWGXVQ== + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.27.0.tgz#801a952c10b58e486c9a0b36cf21e2aab1e9e01a" + integrity sha512-vOsYzjwJlY6E0NJRXPTeCGqjv5OHgRU1kzxHKWJVPjDYGbPgLudBXjIlc+OD1hDBZ4l1DLbOc5VjofKahsu9Jw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.23.0" + "@typescript-eslint/typescript-estree" "2.27.0" eslint-scope "^5.0.0" + eslint-utils "^2.0.0" -"@typescript-eslint/[email protected]": - version "2.23.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.23.0.tgz#d355960fab96bd550855488dcc34b9a4acac8d36" - integrity sha512-pmf7IlmvXdlEXvE/JWNNJpEvwBV59wtJqA8MLAxMKLXNKVRC3HZBXR/SlZLPWTCcwOSg9IM7GeRSV3SIerGVqw== +"@typescript-eslint/[email protected]": + version "2.27.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.27.0.tgz#a288e54605412da8b81f1660b56c8b2e42966ce8" + integrity sha512-t2miCCJIb/FU8yArjAvxllxbTiyNqaXJag7UOpB5DVoM3+xnjeOngtqlJkLRnMtzaRcJhe3CIR9RmL40omubhg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -1497,7 +1534,7 @@ [email protected]^1.5.0: dependencies: file-uri-to-path "1.0.0" [email protected]^3.5.0, [email protected]^3.5.4: [email protected]^3.5.0, [email protected]^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -1630,16 +1667,16 @@ [email protected]~0.2.0: pako "~1.0.5" [email protected]^16.5.0: - version "16.5.0" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881" - integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw== + version "16.5.1" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.1.tgz#3c13c97436802930d5c3ae28658ddc33bfd37dc2" + integrity sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A== dependencies: JSONStream "^1.0.3" assert "^1.4.0" browser-pack "^6.0.1" browser-resolve "^1.11.0" browserify-zlib "~0.2.0" - buffer "^5.0.2" + buffer "~5.2.1" cached-path-relative "^1.0.0" concat-stream "^1.6.0" console-browserify "^1.1.0" @@ -1657,7 +1694,7 @@ [email protected]^16.5.0: inherits "~2.0.1" insert-module-globals "^7.0.0" labeled-stream-splicer "^2.0.0" - mkdirp "^0.5.0" + mkdirp-classic "^0.5.2" module-deps "^6.0.0" os-browserify "~0.3.0" parents "^1.0.1" @@ -1683,14 +1720,15 @@ [email protected]^16.5.0: vm-browserify "^1.0.0" xtend "^4.0.0" - version "4.9.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.9.1.tgz#01ffb9ca31a1aef7678128fc6a2253316aa7287c" - integrity sha512-Q0DnKq20End3raFulq6Vfp1ecB9fh8yUNV55s8sekaDDeqBaCtWlRHCUdaWyUeSSBJM7IbM6HcsyaeYqgeDhnw== [email protected]^4.8.3, [email protected]^4.9.1: + version "4.11.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b" + integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g== dependencies: - caniuse-lite "^1.0.30001030" - electron-to-chromium "^1.3.363" - node-releases "^1.1.50" + caniuse-lite "^1.0.30001038" + electron-to-chromium "^1.3.390" + node-releases "^1.1.53" + pkg-up "^2.0.0" [email protected]^4.0.1: version "4.0.1" @@ -1716,10 +1754,10 @@ [email protected]^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= [email protected]^5.0.2: - version "5.5.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.5.0.tgz#9c3caa3d623c33dd1c7ef584b89b88bf9c9bc1ce" - integrity sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww== [email protected]~5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" + integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1769,10 +1807,10 @@ [email protected]^5.0.0, [email protected]^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== [email protected]^1.0.30001030: - version "1.0.30001033" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001033.tgz#60c328fb56860de60f9a2cb419c31fb80587cba0" - integrity sha512-8Ibzxee6ibc5q88cM1usPsMpJOG5CTq0s/dKOmlekPbDGKt+UrnOOTPSjQz3kVo6yL7N4SB5xd+FGLHQmbzh6A== [email protected]^1.0.30001038: + version "1.0.30001039" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe" + integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q== [email protected]^2.0.0: version "2.0.0" @@ -2298,10 +2336,10 @@ [email protected]~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" [email protected]^1.3.363: - version "1.3.375" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.375.tgz#e290d59d316024e5499057944c10d05c518b7a24" - integrity sha512-zmaFnYVBtfpF8bGRYxgPeVAlXB7N3On8rjBE2ROc6wOpTPpzRWaiHo6KkbJMvlH07CH33uks/TEb6kuMMn8q6A== [email protected]^1.3.390: + version "1.3.398" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.398.tgz#4c01e29091bf39e578ac3f66c1f157d92fa5725d" + integrity sha512-BJjxuWLKFbM5axH3vES7HKMQgAknq9PZHBkMK/rEXUQG9i1Iw5R+6hGkm6GtsQSANjSUrh/a6m32nzCNDNo/+w== [email protected]^6.0.0: version "6.5.2" @@ -2328,10 +2366,10 @@ [email protected]^1.1.0: dependencies: once "^1.4.0" [email protected]~1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== [email protected]~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" + integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== [email protected]^1.2.0, [email protected]^1.3.1: version "1.3.2" @@ -2340,10 +2378,10 @@ [email protected]^1.2.0, [email protected]^1.3.1: dependencies: is-arrayish "^0.2.1" - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== [email protected]^1.17.0-next.1, [email protected]^1.17.2, [email protected]^1.17.4, [email protected]^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" @@ -2448,6 +2486,13 @@ [email protected]^1.3.1: dependencies: eslint-visitor-keys "^1.1.0" [email protected]^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + [email protected]^1.0.0, [email protected]^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== [email protected]^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.1.0.tgz#c5c0b66f383e7656404f86b31334d72524eddb48" - integrity sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q== + version "1.2.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.2.0.tgz#a010a519c0288f2530b3404124bfb5f02e9797fe" + integrity sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q== dependencies: - estraverse "^4.0.0" + estraverse "^5.0.0" [email protected]^4.1.0: version "4.2.1" @@ -2523,11 +2568,16 @@ [email protected]^4.1.0: dependencies: estraverse "^4.1.0" version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== [email protected]^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.0.0.tgz#ac81750b482c11cca26e4b07e83ed8f75fbcdc22" + integrity sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A== + [email protected]^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -2743,6 +2793,13 @@ [email protected]^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" [email protected]^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + [email protected]^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -2760,9 +2817,9 @@ [email protected]^2.0.1: write "1.0.3" [email protected]^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" - integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== [email protected]^1.0.2: version "1.0.2" @@ -2774,6 +2831,15 @@ [email protected]~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= [email protected]^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" + integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + [email protected]~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -2801,9 +2867,9 @@ [email protected]^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= [email protected]^1.2.7: - version "1.2.11" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" - integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== + version "1.2.12" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.12.tgz#db7e0d8ec3b0b45724fd4d83d43554a8f1f0de5c" + integrity sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q== dependencies: bindings "^1.5.0" nan "^2.12.1" @@ -3001,9 +3067,9 @@ [email protected]^1.0.2: whatwg-encoding "^1.0.1" [email protected]^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" - integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== [email protected]^1.1.0: version "1.1.1" @@ -3856,7 +3922,7 @@ [email protected]^3.13.0, [email protected]^3.13.1: argparse "^1.0.7" esprima "^4.0.0" [email protected]^4.0.0: [email protected]^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.1.tgz#670ef71bc5661f089cc90481b99a05a1227ae3bd" integrity sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw== @@ -3869,24 +3935,24 @@ [email protected]~0.1.0: integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= [email protected]^3.5.5: - version "3.6.3" - resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.3.tgz#dccea97d0e62d63d306b8b3ed1527173b5e2190d" - integrity sha512-Yf1ZKA3r9nvtMWHO1kEuMZTlHOF8uoQ0vyo5eH7SQy5YeIiHM+B0DgKnn+X6y6KDYZcF7G2SPkKF+JORCXWE/A== + version "3.6.4" + resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.4.tgz#246b2832a0ea8b37a441b61745509cfe29e174b6" + integrity sha512-3G9d37VHv7MFdheviDCjUfQoIjdv4TC5zTTf5G9VODLtOnVS6La1eoYBDlbWfsRT3/Xo+j2MIqki2EV12BZfwA== dependencies: - "@babel/parser" "^7.4.4" - bluebird "^3.5.4" + "@babel/parser" "^7.9.4" + bluebird "^3.7.2" catharsis "^0.8.11" escape-string-regexp "^2.0.0" - js2xmlparser "^4.0.0" + js2xmlparser "^4.0.1" klaw "^3.0.0" - markdown-it "^8.4.2" - markdown-it-anchor "^5.0.2" - marked "^0.7.0" - mkdirp "^0.5.1" + markdown-it "^10.0.0" + markdown-it-anchor "^5.2.7" + marked "^0.8.2" + mkdirp "^1.0.4" requizzle "^0.2.3" - strip-json-comments "^3.0.1" + strip-json-comments "^3.1.0" taffydb "2.6.2" - underscore "~1.9.1" + underscore "~1.10.2" [email protected]^11.5.1: version "11.12.0" @@ -3962,12 +4028,12 @@ [email protected]~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= [email protected]^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" - integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== [email protected]^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== dependencies: - minimist "^1.2.0" + minimist "^1.2.5" [email protected]~0.0.0: version "0.0.0" @@ -4088,6 +4154,14 @@ [email protected]^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" [email protected]^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + [email protected]^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -4173,26 +4247,26 @@ [email protected]^1.0.0: dependencies: object-visit "^1.0.0" [email protected]^5.0.2: - version "5.2.5" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.2.5.tgz#dbf13cfcdbffd16a510984f1263e1d479a47d27a" - integrity sha512-xLIjLQmtym3QpoY9llBgApknl7pxAcN3WDRc2d3rwpl+/YvDZHPmKscGs+L6E05xf2KrCXPBvosWt7MZukwSpQ== [email protected]^5.2.7: + version "5.2.7" + resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-5.2.7.tgz#ec740f6bd03258a582cd0c65b9644b9f9852e5a3" + integrity sha512-REFmIaSS6szaD1bye80DMbp7ePwsPNvLTR5HunsUcZ0SG0rWJQ+Pz24R4UlTKtjKBPhxo0v0tOBDYjZQQknW8Q== [email protected]^8.4.2: - version "8.4.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" - integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== [email protected]^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" + integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== dependencies: argparse "^1.0.7" - entities "~1.1.1" + entities "~2.0.0" linkify-it "^2.0.0" mdurl "^1.0.1" uc.micro "^1.0.5" [email protected]^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" - integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== [email protected]^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.8.2.tgz#4faad28d26ede351a7a1aaa5fec67915c869e355" + integrity sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw== [email protected]^1.2.3: version "1.2.3" @@ -4287,15 +4361,10 @@ [email protected]: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.5.tgz#d7aa327bcecf518f9106ac6b8f003fa3bcea8566" integrity sha1-16oye87PUY+RBqxrjwA/o7zqhWY= - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - - version "1.2.3" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.3.tgz#3db5c0765545ab8637be71f333a104a965a9ca3f" - integrity sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw== + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== [email protected]^1.2.0: version "1.3.2" @@ -4305,12 +4374,22 @@ [email protected]^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= [email protected]^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" + integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== + [email protected]^0.5.1, [email protected]~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: - minimist "0.0.8" + minimist "^1.2.5" + [email protected]^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== [email protected]^6.0.0: version "6.2.2" @@ -4416,12 +4495,10 @@ [email protected]^5.4.2: shellwords "^0.1.1" which "^1.3.0" [email protected]^1.1.50: - version "1.1.51" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.51.tgz#70d0e054221343d2966006bfbd4d98622cc00bd0" - integrity sha512-1eQEs6HFYY1kMXQPOLzCf7HdjReErmvn85tZESMczdCNVWP3Y7URYLBAyYynuI7yef1zj4HN5q+oB2x67QU0lw== - dependencies: - semver "^6.3.0" [email protected]^1.1.53: + version "1.1.53" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" + integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== [email protected]^2.3.2: version "2.5.0" @@ -4580,13 +4657,27 @@ [email protected]^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= [email protected]^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + [email protected]^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" [email protected]^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + [email protected]^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -4599,6 +4690,11 @@ [email protected]^1.0.0: resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= [email protected]^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + [email protected]^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -4747,6 +4843,13 @@ [email protected]^3.0.0: dependencies: find-up "^3.0.0" [email protected]^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= + dependencies: + find-up "^2.1.0" + [email protected]^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -4800,9 +4903,9 @@ [email protected]^7.0.1: asap "~2.0.3" [email protected]^2.0.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.1.tgz#b63a9ce2809f106fa9ae1277c275b167af46ea05" - integrity sha512-qIP2lQyCwYbdzcqHIUi2HAxiWixhoM9OdLCWf8txXsapC/X9YdsCoeyRIXE/GP+Q0J37Q7+XN/MFqbUa7IzXNA== + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== dependencies: kleur "^3.0.3" sisteransi "^1.0.4" @@ -4822,9 +4925,9 @@ [email protected]^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= [email protected]^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== [email protected]^4.0.0: version "4.0.3" @@ -4967,9 +5070,9 @@ [email protected]^2.1.0, [email protected]^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== [email protected]^6.5.2: - version "6.9.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" - integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA== + version "6.9.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== [email protected]~6.5.2: version "6.5.2" @@ -5032,9 +5135,9 @@ [email protected]^4.1.1: integrity sha512-NfJp90AvYA1R6+uSYafQ+n+UM2HjHqi4WGHeprVXa6quU9d8o6ZFRzQ36uemY82dlkZFzf2jigFx6E4UzNFajA== [email protected]^16.8.1, [email protected]^16.8.4: - version "16.13.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" - integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== [email protected]^2.0.0: version "2.0.0" @@ -5118,10 +5221,10 @@ [email protected]^0.17.3: private "^0.1.8" source-map "~0.6.1" [email protected]^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz#ef51e0f0ea4ad424b77bf7cb41f3e015c70a3f0e" - integrity sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA== [email protected]^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== dependencies: regenerate "^1.4.0" @@ -5136,14 +5239,14 @@ [email protected]^0.11.0: integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== [email protected]^0.13.4: - version "0.13.4" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.4.tgz#e96bf612a3362d12bb69f7e8f74ffeab25c7ac91" - integrity sha512-plpwicqEzfEyTQohIKktWigcLzmNStMGwbOUbykx51/29Z3JOGYldaaNGK7ngNXV+UcoqvIMmloZ48Sr74sd+g== + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== [email protected]^0.14.2: - version "0.14.2" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.2.tgz#949d9d87468ff88d5a7e4734ebb994a892de1ff2" - integrity sha512-V4+lGplCM/ikqi5/mkkpJ06e9Bujq1NFmNLvsCs56zg3ZbzrnUzAtizZ24TXxtRX/W2jcdScwQCnbL0CICTFkQ== + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== dependencies: "@babel/runtime" "^7.8.4" private "^0.1.8" @@ -5161,24 +5264,24 @@ [email protected]^2.0.1: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== [email protected]^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" - integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== [email protected]^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== dependencies: regenerate "^1.4.0" - regenerate-unicode-properties "^8.1.0" - regjsgen "^0.5.0" - regjsparser "^0.6.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" unicode-match-property-ecmascript "^1.0.4" - unicode-match-property-value-ecmascript "^1.1.0" + unicode-match-property-value-ecmascript "^1.2.0" [email protected]^0.5.0: [email protected]^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== [email protected]^0.6.0: [email protected]^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== @@ -5355,9 +5458,9 @@ [email protected]^2.2.0: is-promise "^2.1.0" [email protected]^6.4.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" - integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== dependencies: tslib "^1.9.0" @@ -5484,9 +5587,9 @@ [email protected]^0.1.1: integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== [email protected]^3.0.0, [email protected]^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== [email protected]^1.0.0: version "1.0.0" @@ -5494,9 +5597,9 @@ [email protected]^1.0.0: integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= [email protected]^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.4.tgz#386713f1ef688c7c0304dc4c0632898941cad2e3" - integrity sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig== + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== [email protected]^2.0.0: version "2.0.0" @@ -5706,21 +5809,39 @@ [email protected]^3.0.0, [email protected]^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" [email protected]^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" + integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + [email protected]^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" [email protected]^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + [email protected]^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2" + integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" [email protected]^1.1.1: version "1.3.0" @@ -5772,10 +5893,10 @@ [email protected]^2.0.0, [email protected]^2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= [email protected]^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== [email protected]^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== [email protected]^1.0.0: version "1.0.0" @@ -5826,9 +5947,9 @@ [email protected]: integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg= [email protected]^4.4.3: - version "4.6.6" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.6.tgz#da2382e6cafbdf86205e82fb9a115bd664d54863" - integrity sha512-4lYPyeNmstjIIESr/ysHg2vUPRGf2tzF9z2yYwnowXVuVzLEamPN1Gfrz7f8I9uEPuHcbFlW4PLIAsJoxXyJ1g== + version "4.6.10" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2" + integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA== dependencies: commander "^2.20.0" source-map "~0.6.1" @@ -6100,10 +6221,10 @@ [email protected]^1.1.2: simple-concat "^1.0.0" xtend "^4.0.1" [email protected]^1.9.1, [email protected]~1.9.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" - integrity sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ== [email protected]^1.9.1, [email protected]~1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.10.2.tgz#73d6aa3668f3188e4adb0f1943bd12cfd7efaaaf" + integrity sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg== [email protected]^1.0.2: version "1.0.5" @@ -6123,15 +6244,15 @@ [email protected]^1.0.4: unicode-canonical-property-names-ecmascript "^1.0.4" unicode-property-aliases-ecmascript "^1.0.4" [email protected]^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" - integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== [email protected]^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== [email protected]^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" - integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== [email protected]^1.0.0: version "1.0.1" @@ -6440,18 +6561,18 @@ [email protected]^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= [email protected]^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== [email protected]^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" [email protected]^13.3.0: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" find-up "^3.0.0" @@ -6462,7 +6583,7 @@ [email protected]^13.3.0: string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.1" + yargs-parser "^13.1.2" [email protected]~3.10.0: version "3.10.0" |