diff --git a/package.json b/package.json
index 9e22569a..956e027b 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,11 @@
"xxhashjs": "^0.2.2"
},
"dependencies": {
+ "aes-js": "^3.1.2",
"another-json": "^0.2.0",
- "olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz"
+ "base64-arraybuffer": "^0.2.0",
+ "bs58": "^4.0.1",
+ "olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
+ "text-encoding": "^0.7.0"
}
}
diff --git a/prototypes/derive-key-ie11.html b/prototypes/derive-key-ie11.html
new file mode 100644
index 00000000..6e48e788
--- /dev/null
+++ b/prototypes/derive-key-ie11.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prototypes/derive-keys.js b/prototypes/derive-keys.js
new file mode 100644
index 00000000..ce2b9cb0
--- /dev/null
+++ b/prototypes/derive-keys.js
@@ -0,0 +1,458 @@
+import {base58} from "../src/utils/base-encoding.js";
+
+function subtleCryptoResult(promiseOrOp, method) {
+ if (promiseOrOp instanceof Promise) {
+ return promiseOrOp;
+ } else {
+ return new Promise((resolve, reject) => {
+ promiseOrOp.oncomplete = e => resolve(e.target.result);
+ promiseOrOp.onerror = e => reject(new Error("Crypto error on " + method));
+ });
+ }
+}
+
+class CryptoHMACDriver {
+ constructor(subtleCrypto) {
+ this._subtleCrypto = subtleCrypto;
+ }
+ /**
+ * [hmac description]
+ * @param {BufferSource} key
+ * @param {BufferSource} mac
+ * @param {BufferSource} data
+ * @param {HashName} hash
+ * @return {boolean}
+ */
+ async verify(key, mac, data, hash) {
+ const opts = {
+ name: 'HMAC',
+ hash: {name: hashName(hash)},
+ };
+ const hmacKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['verify'],
+ ), "importKey");
+ const isVerified = await subtleCryptoResult(this._subtleCrypto.verify(
+ opts,
+ hmacKey,
+ mac,
+ data,
+ ), "verify");
+ return isVerified;
+ }
+
+ async compute(key, data, hash) {
+ const opts = {
+ name: 'HMAC',
+ hash: {name: hashName(hash)},
+ };
+ const hmacKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['sign'],
+ ), "importKey");
+ const buffer = await subtleCryptoResult(this._subtleCrypto.sign(
+ opts,
+ hmacKey,
+ data,
+ ), "sign");
+ return new Uint8Array(buffer);
+ }
+}
+
+const nwbo = (num, len) => {
+ const arr = new Uint8Array(len);
+ for(let i=0; i> ((len - i - 1)*8));
+ return arr;
+};
+
+class CryptoLegacyHMACDriver {
+ constructor(hmacDriver) {
+ this._hmacDriver = hmacDriver;
+ }
+
+ async verify(key, mac, data, hash) {
+ if (hash === "SHA-512") {
+ throw new Error("SHA-512 HMAC verification is not implemented yet");
+ } else {
+ return this._hmacDriver.verify(key, mac, data, hash)
+ }
+ }
+
+ async compute(key, data, hash) {
+ if (hash === "SHA-256") {
+ return await this._hmacDriver.compute(key, data, hash);
+ } else {
+ const shaObj = new window.jsSHA(hash, "UINT8ARRAY", {
+ "hmacKey": {
+ "value": key,
+ "format": "UINT8ARRAY"
+ }
+ });
+ shaObj.update(data);
+ return shaObj.getHash("UINT8ARRAY");
+ }
+ }
+}
+
+class CryptoLegacyDeriveDriver {
+ constructor(cryptoDriver) {
+ this._cryptoDriver = cryptoDriver;
+ }
+
+ // adapted from https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-pbkdf/src/pbkdf.ts#L21
+ // could also consider https://github.com/brix/crypto-js/blob/develop/src/pbkdf2.js although not async
+ async pbkdf2(password, iterations, salt, hash, length) {
+ const dkLen = length / 8;
+ if (iterations <= 0) {
+ throw new Error('InvalidIterationCount');
+ }
+ if (dkLen <= 0) {
+ throw new Error('InvalidDerivedKeyLength');
+ }
+ const hLen = this._cryptoDriver.digestSize(hash);
+ if(dkLen > (Math.pow(2, 32) - 1) * hLen) throw new Error('DerivedKeyTooLong');
+
+ const l = Math.ceil(dkLen/hLen);
+ const r = dkLen - (l-1)*hLen;
+
+ const funcF = async (i) => {
+ const seed = new Uint8Array(salt.length + 4);
+ seed.set(salt);
+ seed.set(nwbo(i+1, 4), salt.length);
+ let u = await this._cryptoDriver.hmac.compute(password, seed, hash);
+ let outputF = new Uint8Array(u);
+ for(let j = 1; j < iterations; j++){
+ if ((j % 1000) === 0) {
+ console.log(j, j/iterations);
+ }
+ u = await this._cryptoDriver.hmac.compute(password, u, hash);
+ outputF = u.map( (elem, idx) => elem ^ outputF[idx]);
+ }
+ return {index: i, value: outputF};
+ };
+
+ const Tis = [];
+ const DK = new Uint8Array(dkLen);
+ for(let i = 0; i < l; i++) {
+ Tis.push(funcF(i));
+ }
+ const TisResolved = await Promise.all(Tis);
+ TisResolved.forEach(elem => {
+ if (elem.index !== l - 1) {
+ DK.set(elem.value, elem.index*hLen);
+ }
+ else {
+ DK.set(elem.value.slice(0, r), elem.index*hLen);
+ }
+ });
+
+ return DK;
+ }
+
+ // based on https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-hkdf/src/hkdf.ts
+ async hkdf(key, salt, info, hash, length) {
+ length = length / 8;
+ const len = this._cryptoDriver.digestSize(hash);
+
+ // RFC5869 Step 1 (Extract)
+ const prk = await this._cryptoDriver.hmac.compute(salt, key, hash);
+
+ // RFC5869 Step 2 (Expand)
+ let t = new Uint8Array([]);
+ const okm = new Uint8Array(Math.ceil(length / len) * len);
+ for(let i = 0; i < Math.ceil(length / len); i++){
+ const concat = new Uint8Array(t.length + info.length + 1);
+ concat.set(t);
+ concat.set(info, t.length);
+ concat.set(new Uint8Array([i+1]), t.length + info.length);
+ t = await this._cryptoDriver.hmac.compute(prk, concat, hash);
+ okm.set(t, len * i);
+ }
+ return okm.slice(0, length);
+ }
+}
+
+class CryptoDeriveDriver {
+ constructor(subtleCrypto) {
+ this._subtleCrypto = subtleCrypto;
+ }
+ /**
+ * [pbkdf2 description]
+ * @param {BufferSource} password
+ * @param {Number} iterations
+ * @param {BufferSource} salt
+ * @param {HashName} hash
+ * @param {Number} length the desired length of the generated key, in bits (not bytes!)
+ * @return {BufferSource}
+ */
+ async pbkdf2(password, iterations, salt, hash, length) {
+ // check for existance of deriveBits, which IE11 does not have
+ const key = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ password,
+ {name: 'PBKDF2'},
+ false,
+ ['deriveBits'],
+ ), "importKey");
+ const keybits = await subtleCryptoResult(this._subtleCrypto.deriveBits(
+ {
+ name: 'PBKDF2',
+ salt,
+ iterations,
+ hash: hashName(hash),
+ },
+ key,
+ length,
+ ), "deriveBits");
+ return new Uint8Array(keybits);
+ }
+
+ /**
+ * [hkdf description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} salt [description]
+ * @param {BufferSource} info [description]
+ * @param {HashName} hash the hash to use
+ * @param {Number} length desired length of the generated key in bits (not bytes!)
+ * @return {[type]} [description]
+ */
+ async hkdf(key, salt, info, hash, length) {
+ const hkdfkey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ {name: "HKDF"},
+ false,
+ ["deriveBits"],
+ ), "importKey");
+ const keybits = await subtleCryptoResult(this._subtleCrypto.deriveBits({
+ name: "HKDF",
+ salt,
+ info,
+ hash: hashName(hash),
+ },
+ hkdfkey,
+ length,
+ ), "deriveBits");
+ return new Uint8Array(keybits);
+ }
+}
+
+class CryptoAESDriver {
+ constructor(subtleCrypto) {
+ this._subtleCrypto = subtleCrypto;
+ }
+ /**
+ * [decrypt description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} iv [description]
+ * @param {BufferSource} ciphertext [description]
+ * @return {BufferSource} [description]
+ */
+ async decrypt(key, iv, ciphertext) {
+ const opts = {
+ name: "AES-CTR",
+ counter: iv,
+ length: 64,
+ };
+ let aesKey;
+ try {
+ aesKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['decrypt'],
+ ), "importKey");
+ } catch (err) {
+ throw new Error(`Could not import key for AES-CTR decryption: ${err.message}`);
+ }
+ try {
+ const plaintext = await subtleCryptoResult(this._subtleCrypto.decrypt(
+ // see https://developer.mozilla.org/en-US/docs/Web/API/AesCtrParams
+ opts,
+ aesKey,
+ ciphertext,
+ ), "decrypt");
+ return new Uint8Array(plaintext);
+ } catch (err) {
+ throw new Error(`Could not decrypt with AES-CTR: ${err.message}`);
+ }
+ }
+}
+
+
+class CryptoLegacyAESDriver {
+ /**
+ * [decrypt description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} iv [description]
+ * @param {BufferSource} ciphertext [description]
+ * @return {BufferSource} [description]
+ */
+ async decrypt(key, iv, ciphertext) {
+ const aesjs = window.aesjs;
+ var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(iv));
+ return aesCtr.decrypt(ciphertext);
+ }
+}
+
+function hashName(name) {
+ if (name !== "SHA-256" && name !== "SHA-512") {
+ throw new Error(`Invalid hash name: ${name}`);
+ }
+ return name;
+}
+
+export class CryptoDriver {
+ constructor(subtleCrypto) {
+ this.aes = new CryptoLegacyAESDriver();
+ // this.aes = new CryptoAESDriver(subtleCrypto);
+ //this.derive = new CryptoDeriveDriver(subtleCrypto);
+ this.derive = new CryptoLegacyDeriveDriver(this);
+ // subtleCrypto.deriveBits ?
+ // new CryptoDeriveDriver(subtleCrypto) :
+ // new CryptoLegacyDeriveDriver(this);
+ this.hmac = new CryptoLegacyHMACDriver(new CryptoHMACDriver(subtleCrypto));
+ this._subtleCrypto = subtleCrypto;
+ }
+
+ /**
+ * [digest description]
+ * @param {HashName} hash
+ * @param {BufferSource} data
+ * @return {BufferSource}
+ */
+ async digest(hash, data) {
+ return await subtleCryptoResult(this._subtleCrypto.digest(hashName(hash), data));
+ }
+
+ digestSize(hash) {
+ switch (hashName(hash)) {
+ case "SHA-512": return 64;
+ case "SHA-256": return 32;
+ default: throw new Error(`Not implemented for ${hashName(hash)}`);
+ }
+ }
+}
+
+export function decodeBase64(base64) {
+ const binStr = window.atob(base64);
+ const len = binStr.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binStr.charCodeAt(i);
+ }
+ return bytes;
+}
+
+const DEFAULT_ITERATIONS = 500000;
+
+const DEFAULT_BITSIZE = 256;
+
+export async function deriveSSSSKey(cryptoDriver, passphrase, ssssKey) {
+ const textEncoder = new TextEncoder();
+ return await cryptoDriver.derive.pbkdf2(
+ textEncoder.encode(passphrase),
+ ssssKey.content.passphrase.iterations || DEFAULT_ITERATIONS,
+ textEncoder.encode(ssssKey.content.passphrase.salt),
+ "SHA-512",
+ ssssKey.content.passphrase.bits || DEFAULT_BITSIZE);
+}
+
+export async function decryptSecret(cryptoDriver, keyId, ssssKey, event) {
+ const textEncoder = new TextEncoder();
+ const textDecoder = new TextDecoder();
+ // now derive the aes and mac key from the 4s key
+ const hkdfKey = await cryptoDriver.derive.hkdf(
+ ssssKey,
+ new Uint8Array(8).buffer, //salt
+ textEncoder.encode(event.type), // info
+ "SHA-256",
+ 512 // 512 bits or 64 bytes
+ );
+ const aesKey = hkdfKey.slice(0, 32);
+ const hmacKey = hkdfKey.slice(32);
+
+ const data = event.content.encrypted[keyId];
+
+ const ciphertextBytes = decodeBase64(data.ciphertext);
+ const isVerified = await cryptoDriver.hmac.verify(
+ hmacKey, decodeBase64(data.mac),
+ ciphertextBytes, "SHA-256");
+
+ if (!isVerified) {
+ throw new Error("Bad MAC");
+ }
+
+ const plaintext = await cryptoDriver.aes.decrypt(aesKey, decodeBase64(data.iv), ciphertextBytes);
+ return textDecoder.decode(new Uint8Array(plaintext));
+}
+
+
+export async function decryptSession(backupKeyBase64, backupInfo, sessionResponse) {
+ const privKey = decodeBase64(backupKeyBase64);
+ console.log("privKey", privKey);
+
+ const decryption = new window.Olm.PkDecryption();
+ let backupPubKey;
+ try {
+ backupPubKey = decryption.init_with_private_key(privKey);
+ } catch (e) {
+ decryption.free();
+ throw e;
+ }
+
+ // If the pubkey computed from the private data we've been given
+ // doesn't match the one in the auth_data, the user has enetered
+ // a different recovery key / the wrong passphrase.
+ if (backupPubKey !== backupInfo.auth_data.public_key) {
+ console.log("backupPubKey", backupPubKey.length, backupPubKey);
+ throw new Error("bad backup key");
+ }
+
+ const sessionInfo = decryption.decrypt(
+ sessionResponse.session_data.ephemeral,
+ sessionResponse.session_data.mac,
+ sessionResponse.session_data.ciphertext,
+ );
+ return JSON.parse(sessionInfo);
+}
+
+const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
+
+
+export async function deserializeSSSSKey(recoverykey) {
+ const result = base58.decode(recoverykey.replace(/ /g, ''));
+
+ let parity = 0;
+ for (const b of result) {
+ parity ^= b;
+ }
+ if (parity !== 0) {
+ throw new Error("Incorrect parity");
+ }
+
+ for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
+ if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
+ throw new Error("Incorrect prefix");
+ }
+ }
+
+ if (
+ result.length !==
+ OLM_RECOVERY_KEY_PREFIX.length + window.Olm.PRIVATE_KEY_LENGTH + 1
+ ) {
+ throw new Error("Incorrect length");
+ }
+
+ return Uint8Array.from(result.slice(
+ OLM_RECOVERY_KEY_PREFIX.length,
+ OLM_RECOVERY_KEY_PREFIX.length + window.Olm.PRIVATE_KEY_LENGTH,
+ ));
+}
diff --git a/prototypes/ie11-hmac.html b/prototypes/ie11-hmac.html
new file mode 100644
index 00000000..893e8328
--- /dev/null
+++ b/prototypes/ie11-hmac.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prototypes/pkcrypto-ie11.html b/prototypes/pkcrypto-ie11.html
new file mode 100644
index 00000000..8f2853c4
--- /dev/null
+++ b/prototypes/pkcrypto-ie11.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/prototypes/tools/package.json b/prototypes/tools/package.json
new file mode 100644
index 00000000..d7487cac
--- /dev/null
+++ b/prototypes/tools/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "foo",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "@babel/core": "^7.11.1",
+ "@babel/preset-env": "^7.11.0",
+ "@rollup/plugin-babel": "^5.1.0",
+ "@rollup/plugin-commonjs": "^15.0.0",
+ "@rollup/plugin-multi-entry": "^4.0.0",
+ "@rollup/plugin-node-resolve": "^9.0.0",
+ "mdn-polyfills": "^5.20.0",
+ "regenerator-runtime": "^0.13.7",
+ "rollup": "^2.26.4",
+ "core-js": "^3.6.5"
+ }
+}
diff --git a/prototypes/tools/transpile-ie11.mjs b/prototypes/tools/transpile-ie11.mjs
new file mode 100644
index 00000000..1b0670b2
--- /dev/null
+++ b/prototypes/tools/transpile-ie11.mjs
@@ -0,0 +1,46 @@
+import fsRoot from "fs";
+const fs = fsRoot.promises;
+import { rollup } from 'rollup';
+// needed for legacy bundle
+import babel from '@rollup/plugin-babel';
+// needed to find the polyfill modules in the main-legacy.js bundle
+import { nodeResolve } from '@rollup/plugin-node-resolve';
+// needed because some of the polyfills are written as commonjs modules
+import commonjs from '@rollup/plugin-commonjs';
+// multi-entry plugin so we can add polyfill file to main
+import multi from '@rollup/plugin-multi-entry';
+import removeJsComments from 'rollup-plugin-cleanup';
+// replace urls of asset names with content hashed version
+
+async function build(inputFile, outputFile) {
+ // compile down to whatever IE 11 needs
+ const babelPlugin = babel.babel({
+ babelHelpers: 'bundled',
+ exclude: '../../node_modules/**',
+ presets: [
+ [
+ "@babel/preset-env",
+ {
+ useBuiltIns: "entry",
+ corejs: "3",
+ targets: "IE 11"
+ }
+ ]
+ ]
+ });
+ const polyfillFile = '../../src/worker-polyfill.js';
+ // create js bundle
+ const rollupConfig = {
+ input: [polyfillFile, inputFile],
+ plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
+ };
+ const bundle = await rollup(rollupConfig);
+ const {output} = await bundle.generate({
+ format: 'iife',
+ name: `bundle`
+ });
+ const code = output[0].code;
+ await fs.writeFile(outputFile, code, "utf8");
+}
+
+build(process.argv[2], process.argv[3]);
diff --git a/prototypes/tools/yarn.lock b/prototypes/tools/yarn.lock
new file mode 100644
index 00000000..666bce0a
--- /dev/null
+++ b/prototypes/tools/yarn.lock
@@ -0,0 +1,1485 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@babel/code-frame@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
+ integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==
+ dependencies:
+ "@babel/highlight" "^7.10.4"
+
+"@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c"
+ integrity sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==
+ dependencies:
+ browserslist "^4.12.0"
+ invariant "^2.2.4"
+ semver "^5.5.0"
+
+"@babel/core@^7.11.1":
+ version "7.11.6"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.11.6.tgz#3a9455dc7387ff1bac45770650bc13ba04a15651"
+ integrity sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.11.6"
+ "@babel/helper-module-transforms" "^7.11.0"
+ "@babel/helpers" "^7.10.4"
+ "@babel/parser" "^7.11.5"
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.11.5"
+ "@babel/types" "^7.11.5"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.19"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
+"@babel/generator@^7.11.5", "@babel/generator@^7.11.6":
+ version "7.11.6"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620"
+ integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==
+ dependencies:
+ "@babel/types" "^7.11.5"
+ jsesc "^2.5.1"
+ source-map "^0.5.0"
+
+"@babel/helper-annotate-as-pure@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
+ integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3"
+ integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==
+ dependencies:
+ "@babel/helper-explode-assignable-expression" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-compilation-targets@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2"
+ integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==
+ dependencies:
+ "@babel/compat-data" "^7.10.4"
+ browserslist "^4.12.0"
+ invariant "^2.2.4"
+ levenary "^1.1.1"
+ semver "^5.5.0"
+
+"@babel/helper-create-class-features-plugin@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d"
+ integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-member-expression-to-functions" "^7.10.5"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+
+"@babel/helper-create-regexp-features-plugin@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8"
+ integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-regex" "^7.10.4"
+ regexpu-core "^4.7.0"
+
+"@babel/helper-define-map@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30"
+ integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/types" "^7.10.5"
+ lodash "^4.17.19"
+
+"@babel/helper-explode-assignable-expression@^7.10.4":
+ version "7.11.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz#2d8e3470252cc17aba917ede7803d4a7a276a41b"
+ integrity sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-function-name@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a"
+ integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-get-function-arity@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2"
+ integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-hoist-variables@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e"
+ integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df"
+ integrity sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==
+ dependencies:
+ "@babel/types" "^7.11.0"
+
+"@babel/helper-module-imports@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620"
+ integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359"
+ integrity sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.10.4"
+ "@babel/helper-simple-access" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.11.0"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.11.0"
+ lodash "^4.17.19"
+
+"@babel/helper-optimise-call-expression@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673"
+ integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==
+ dependencies:
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375"
+ integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==
+
+"@babel/helper-regex@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0"
+ integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==
+ dependencies:
+ lodash "^4.17.19"
+
+"@babel/helper-remap-async-to-generator@^7.10.4":
+ version "7.11.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz#4474ea9f7438f18575e30b0cac784045b402a12d"
+ integrity sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-wrap-function" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-replace-supers@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf"
+ integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.10.4"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-simple-access@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461"
+ integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==
+ dependencies:
+ "@babel/template" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729"
+ integrity sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==
+ dependencies:
+ "@babel/types" "^7.11.0"
+
+"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f"
+ integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==
+ dependencies:
+ "@babel/types" "^7.11.0"
+
+"@babel/helper-validator-identifier@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
+ integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==
+
+"@babel/helper-wrap-function@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87"
+ integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/helpers@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.4.tgz#2abeb0d721aff7c0a97376b9e1f6f65d7a475044"
+ integrity sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==
+ dependencies:
+ "@babel/template" "^7.10.4"
+ "@babel/traverse" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/highlight@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143"
+ integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.10.4", "@babel/parser@^7.11.5":
+ version "7.11.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
+ integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
+
+"@babel/plugin-proposal-async-generator-functions@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558"
+ integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-remap-async-to-generator" "^7.10.4"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+
+"@babel/plugin-proposal-class-properties@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807"
+ integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-dynamic-import@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e"
+ integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+
+"@babel/plugin-proposal-export-namespace-from@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz#570d883b91031637b3e2958eea3c438e62c05f54"
+ integrity sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-proposal-json-strings@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db"
+ integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+
+"@babel/plugin-proposal-logical-assignment-operators@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz#9f80e482c03083c87125dee10026b58527ea20c8"
+ integrity sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a"
+ integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+
+"@babel/plugin-proposal-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06"
+ integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-proposal-object-rest-spread@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz#bd81f95a1f746760ea43b6c2d3d62b11790ad0af"
+ integrity sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+ "@babel/plugin-transform-parameters" "^7.10.4"
+
+"@babel/plugin-proposal-optional-catch-binding@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd"
+ integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
+
+"@babel/plugin-proposal-optional-chaining@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz#de5866d0646f6afdaab8a566382fe3a221755076"
+ integrity sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+
+"@babel/plugin-proposal-private-methods@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909"
+ integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.10.4", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d"
+ integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-async-generators@^7.8.0":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c"
+ integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-dynamic-import@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
+ integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-export-namespace-from@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a"
+ integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-json-strings@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.0":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-top-level-await@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d"
+ integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-arrow-functions@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd"
+ integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-async-to-generator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37"
+ integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-remap-async-to-generator" "^7.10.4"
+
+"@babel/plugin-transform-block-scoped-functions@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8"
+ integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-block-scoping@^7.10.4":
+ version "7.11.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215"
+ integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-classes@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7"
+ integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-define-map" "^7.10.4"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-optimise-call-expression" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.10.4"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb"
+ integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-destructuring@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5"
+ integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-dotall-regex@^7.10.4", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee"
+ integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-duplicate-keys@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47"
+ integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-exponentiation-operator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e"
+ integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-for-of@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9"
+ integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-function-name@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7"
+ integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==
+ dependencies:
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-literals@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c"
+ integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7"
+ integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-modules-amd@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1"
+ integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.5"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-commonjs@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0"
+ integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-simple-access" "^7.10.4"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-systemjs@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85"
+ integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.10.4"
+ "@babel/helper-module-transforms" "^7.10.5"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ babel-plugin-dynamic-import-node "^2.3.3"
+
+"@babel/plugin-transform-modules-umd@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e"
+ integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6"
+ integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+
+"@babel/plugin-transform-new-target@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888"
+ integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-object-super@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894"
+ integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-replace-supers" "^7.10.4"
+
+"@babel/plugin-transform-parameters@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a"
+ integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-property-literals@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0"
+ integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-regenerator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63"
+ integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==
+ dependencies:
+ regenerator-transform "^0.14.2"
+
+"@babel/plugin-transform-reserved-words@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd"
+ integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-shorthand-properties@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6"
+ integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-spread@^7.11.0":
+ version "7.11.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz#fa84d300f5e4f57752fe41a6d1b3c554f13f17cc"
+ integrity sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.11.0"
+
+"@babel/plugin-transform-sticky-regex@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d"
+ integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/helper-regex" "^7.10.4"
+
+"@babel/plugin-transform-template-literals@^7.10.4":
+ version "7.10.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c"
+ integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-typeof-symbol@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc"
+ integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-escapes@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007"
+ integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-transform-unicode-regex@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8"
+ integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/preset-env@^7.11.0":
+ version "7.11.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272"
+ integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==
+ dependencies:
+ "@babel/compat-data" "^7.11.0"
+ "@babel/helper-compilation-targets" "^7.10.4"
+ "@babel/helper-module-imports" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ "@babel/plugin-proposal-async-generator-functions" "^7.10.4"
+ "@babel/plugin-proposal-class-properties" "^7.10.4"
+ "@babel/plugin-proposal-dynamic-import" "^7.10.4"
+ "@babel/plugin-proposal-export-namespace-from" "^7.10.4"
+ "@babel/plugin-proposal-json-strings" "^7.10.4"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4"
+ "@babel/plugin-proposal-numeric-separator" "^7.10.4"
+ "@babel/plugin-proposal-object-rest-spread" "^7.11.0"
+ "@babel/plugin-proposal-optional-catch-binding" "^7.10.4"
+ "@babel/plugin-proposal-optional-chaining" "^7.11.0"
+ "@babel/plugin-proposal-private-methods" "^7.10.4"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.10.4"
+ "@babel/plugin-syntax-async-generators" "^7.8.0"
+ "@babel/plugin-syntax-class-properties" "^7.10.4"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.0"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+ "@babel/plugin-syntax-json-strings" "^7.8.0"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@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"
+ "@babel/plugin-syntax-top-level-await" "^7.10.4"
+ "@babel/plugin-transform-arrow-functions" "^7.10.4"
+ "@babel/plugin-transform-async-to-generator" "^7.10.4"
+ "@babel/plugin-transform-block-scoped-functions" "^7.10.4"
+ "@babel/plugin-transform-block-scoping" "^7.10.4"
+ "@babel/plugin-transform-classes" "^7.10.4"
+ "@babel/plugin-transform-computed-properties" "^7.10.4"
+ "@babel/plugin-transform-destructuring" "^7.10.4"
+ "@babel/plugin-transform-dotall-regex" "^7.10.4"
+ "@babel/plugin-transform-duplicate-keys" "^7.10.4"
+ "@babel/plugin-transform-exponentiation-operator" "^7.10.4"
+ "@babel/plugin-transform-for-of" "^7.10.4"
+ "@babel/plugin-transform-function-name" "^7.10.4"
+ "@babel/plugin-transform-literals" "^7.10.4"
+ "@babel/plugin-transform-member-expression-literals" "^7.10.4"
+ "@babel/plugin-transform-modules-amd" "^7.10.4"
+ "@babel/plugin-transform-modules-commonjs" "^7.10.4"
+ "@babel/plugin-transform-modules-systemjs" "^7.10.4"
+ "@babel/plugin-transform-modules-umd" "^7.10.4"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4"
+ "@babel/plugin-transform-new-target" "^7.10.4"
+ "@babel/plugin-transform-object-super" "^7.10.4"
+ "@babel/plugin-transform-parameters" "^7.10.4"
+ "@babel/plugin-transform-property-literals" "^7.10.4"
+ "@babel/plugin-transform-regenerator" "^7.10.4"
+ "@babel/plugin-transform-reserved-words" "^7.10.4"
+ "@babel/plugin-transform-shorthand-properties" "^7.10.4"
+ "@babel/plugin-transform-spread" "^7.11.0"
+ "@babel/plugin-transform-sticky-regex" "^7.10.4"
+ "@babel/plugin-transform-template-literals" "^7.10.4"
+ "@babel/plugin-transform-typeof-symbol" "^7.10.4"
+ "@babel/plugin-transform-unicode-escapes" "^7.10.4"
+ "@babel/plugin-transform-unicode-regex" "^7.10.4"
+ "@babel/preset-modules" "^0.1.3"
+ "@babel/types" "^7.11.5"
+ browserslist "^4.12.0"
+ core-js-compat "^3.6.2"
+ invariant "^2.2.2"
+ levenary "^1.1.1"
+ semver "^5.5.0"
+
+"@babel/preset-modules@^0.1.3":
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e"
+ integrity sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==
+ 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/runtime@^7.8.4":
+ version "7.11.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736"
+ integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
+"@babel/template@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
+ integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/parser" "^7.10.4"
+ "@babel/types" "^7.10.4"
+
+"@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5":
+ version "7.11.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3"
+ integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/generator" "^7.11.5"
+ "@babel/helper-function-name" "^7.10.4"
+ "@babel/helper-split-export-declaration" "^7.11.0"
+ "@babel/parser" "^7.11.5"
+ "@babel/types" "^7.11.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.19"
+
+"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.4.4":
+ version "7.11.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
+ integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ lodash "^4.17.19"
+ to-fast-properties "^2.0.0"
+
+"@rollup/plugin-babel@^5.1.0":
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.2.1.tgz#20fc8f8864dc0eaa1c5578408459606808f72924"
+ integrity sha512-Jd7oqFR2dzZJ3NWANDyBjwTtX/lYbZpVcmkHrfQcpvawHs9E4c0nYk5U2mfZ6I/DZcIvy506KZJi54XK/jxH7A==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@rollup/pluginutils" "^3.1.0"
+
+"@rollup/plugin-commonjs@^15.0.0":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-15.0.0.tgz#690d15a9d54ba829db93555bff9b98ff34e08574"
+ integrity sha512-8uAdikHqVyrT32w1zB9VhW6uGwGjhKgnDNP4pQJsjdnyF4FgCj6/bmv24c7v2CuKhq32CcyCwRzMPEElaKkn0w==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ commondir "^1.0.1"
+ estree-walker "^2.0.1"
+ glob "^7.1.6"
+ is-reference "^1.2.1"
+ magic-string "^0.25.7"
+ resolve "^1.17.0"
+
+"@rollup/plugin-multi-entry@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-multi-entry/-/plugin-multi-entry-4.0.0.tgz#8e105f16ec1bb26639eb3302c8db5665f44b9939"
+ integrity sha512-1Sw86rwFxrNS7ECY3iSZ7T940xKnruNGpmQDgSDVTp+VTa1g5cPXNzBgp+IoOer41CiVeGFLwYwvicVoJLHEDQ==
+ dependencies:
+ "@rollup/plugin-virtual" "^2.0.3"
+ matched "^5.0.0"
+
+"@rollup/plugin-node-resolve@^9.0.0":
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6"
+ integrity sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg==
+ dependencies:
+ "@rollup/pluginutils" "^3.1.0"
+ "@types/resolve" "1.17.1"
+ builtin-modules "^3.1.0"
+ deepmerge "^4.2.2"
+ is-module "^1.0.0"
+ resolve "^1.17.0"
+
+"@rollup/plugin-virtual@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-2.0.3.tgz#0afc88d75c1e1378ab290b8e9898d4edb5be0d74"
+ integrity sha512-pw6ziJcyjZtntQ//bkad9qXaBx665SgEL8C8KI5wO8G5iU5MPxvdWrQyVaAvjojGm9tJoS8M9Z/EEepbqieYmw==
+
+"@rollup/pluginutils@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
+ integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
+ dependencies:
+ "@types/estree" "0.0.39"
+ estree-walker "^1.0.1"
+ picomatch "^2.2.2"
+
+"@types/estree@*":
+ version "0.0.45"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
+ integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
+
+"@types/estree@0.0.39":
+ version "0.0.39"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+ integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+
+"@types/node@*":
+ version "14.10.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.1.tgz#cc323bad8e8a533d4822f45ce4e5326f36e42177"
+ integrity sha512-aYNbO+FZ/3KGeQCEkNhHFRIzBOUgc7QvcVNKXbfnhDkSfwUv91JsQQa10rDgKSTSLkXZ1UIyPe4FJJNVgw1xWQ==
+
+"@types/resolve@1.17.1":
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
+ integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==
+ dependencies:
+ "@types/node" "*"
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+babel-plugin-dynamic-import-node@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
+ integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
+ dependencies:
+ object.assign "^4.1.0"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+browserslist@^4.12.0, browserslist@^4.8.5:
+ version "4.14.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.2.tgz#1b3cec458a1ba87588cc5e9be62f19b6d48813ce"
+ integrity sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==
+ dependencies:
+ caniuse-lite "^1.0.30001125"
+ electron-to-chromium "^1.3.564"
+ escalade "^3.0.2"
+ node-releases "^1.1.61"
+
+builtin-modules@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
+ integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==
+
+caniuse-lite@^1.0.30001125:
+ version "1.0.30001131"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz#afad8a28fc2b7a0d3ae9407e71085a0ead905d54"
+ integrity sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw==
+
+chalk@^2.0.0:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+commondir@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+ integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+convert-source-map@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
+ integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
+ dependencies:
+ safe-buffer "~5.1.1"
+
+core-js-compat@^3.6.2:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c"
+ integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==
+ dependencies:
+ browserslist "^4.8.5"
+ semver "7.0.0"
+
+core-js@^3.6.5:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
+ integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
+
+debug@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+ integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+ dependencies:
+ ms "^2.1.1"
+
+deepmerge@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+ integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
+define-properties@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
+ integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
+ dependencies:
+ object-keys "^1.0.12"
+
+electron-to-chromium@^1.3.564:
+ version "1.3.568"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.568.tgz#0fa28cd3e5cbd9e8c66f72309eef0646f65a5b66"
+ integrity sha512-j9MlEwgTHVW/lq93Hw8yhzA886oLjDm3Hz7eDkWP2v4fzLVuqOWhpNluziSnmR/tBqgoYldagbLknrdg+B7Tlw==
+
+es-abstract@^1.17.5:
+ version "1.17.6"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
+ integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
+ dependencies:
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+ is-callable "^1.2.0"
+ is-regex "^1.1.0"
+ object-inspect "^1.7.0"
+ object-keys "^1.1.1"
+ object.assign "^4.1.0"
+ string.prototype.trimend "^1.0.1"
+ string.prototype.trimstart "^1.0.1"
+
+es-abstract@^1.18.0-next.0:
+ version "1.18.0-next.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.0.tgz#b302834927e624d8e5837ed48224291f2c66e6fc"
+ integrity sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==
+ dependencies:
+ es-to-primitive "^1.2.1"
+ function-bind "^1.1.1"
+ has "^1.0.3"
+ has-symbols "^1.0.1"
+ is-callable "^1.2.0"
+ is-negative-zero "^2.0.0"
+ is-regex "^1.1.1"
+ object-inspect "^1.8.0"
+ object-keys "^1.1.1"
+ object.assign "^4.1.0"
+ string.prototype.trimend "^1.0.1"
+ string.prototype.trimstart "^1.0.1"
+
+es-to-primitive@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+ integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+ dependencies:
+ is-callable "^1.1.4"
+ is-date-object "^1.0.1"
+ is-symbol "^1.0.2"
+
+escalade@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.0.tgz#e8e2d7c7a8b76f6ee64c2181d6b8151441602d4e"
+ integrity sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+estree-walker@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
+ integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
+
+estree-walker@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0"
+ integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+fsevents@~2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+ integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+gensync@^1.0.0-beta.1:
+ version "1.0.0-beta.1"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
+ integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+
+glob@^7.1.6:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+has-symbols@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
+ integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+invariant@^2.2.2, invariant@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
+ integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
+ dependencies:
+ loose-envify "^1.0.0"
+
+is-callable@^1.1.4, is-callable@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.1.tgz#4d1e21a4f437509d25ce55f8184350771421c96d"
+ integrity sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==
+
+is-date-object@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
+ integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
+
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+ integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=
+
+is-negative-zero@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461"
+ integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=
+
+is-reference@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
+ integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
+ dependencies:
+ "@types/estree" "*"
+
+is-regex@^1.1.0, is-regex@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9"
+ integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==
+ dependencies:
+ has-symbols "^1.0.1"
+
+is-symbol@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+ integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
+ dependencies:
+ has-symbols "^1.0.1"
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+jsesc@~0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
+ integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
+
+json5@^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.5"
+
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
+levenary@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77"
+ integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==
+ dependencies:
+ leven "^3.1.0"
+
+lodash@^4.17.19:
+ version "4.17.20"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
+ integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+
+loose-envify@^1.0.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+magic-string@^0.25.7:
+ version "0.25.7"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
+ integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
+ dependencies:
+ sourcemap-codec "^1.4.4"
+
+matched@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/matched/-/matched-5.0.0.tgz#4b10735a89f87b6f9bf457136472631e19df05d7"
+ integrity sha512-O0LCuxYYBNBjP2dmAg0i6PME0Mb0dvjulpMC0tTIeMRh6kXYsugOT5GOWpFkSzqjQjgOUs/eiyvpVhXdN2La4g==
+ dependencies:
+ glob "^7.1.6"
+ picomatch "^2.2.1"
+
+mdn-polyfills@^5.20.0:
+ version "5.20.0"
+ resolved "https://registry.yarnpkg.com/mdn-polyfills/-/mdn-polyfills-5.20.0.tgz#ca8247edf20a4f60dec6804372229812b348260b"
+ integrity sha512-AbTv1ytcoOUAkxw6u5oo2QPf27kEZgxBAQr49jFb4i2VnTnFGfJbcIQ9UDBOdfNECeXsgkYFwB2BkdeTfOzztw==
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+ms@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+node-releases@^1.1.61:
+ version "1.1.61"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.61.tgz#707b0fca9ce4e11783612ba4a2fcba09047af16e"
+ integrity sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==
+
+object-inspect@^1.7.0, object-inspect@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
+ integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
+
+object-keys@^1.0.12, object-keys@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+ integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd"
+ integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.18.0-next.0"
+ has-symbols "^1.0.1"
+ object-keys "^1.1.1"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-parse@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
+ integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+
+picomatch@^2.2.1, picomatch@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+ integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
+regenerate-unicode-properties@^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"
+
+regenerate@^1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f"
+ integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==
+
+regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
+ integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
+
+regenerator-transform@^0.14.2:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4"
+ integrity sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==
+ dependencies:
+ "@babel/runtime" "^7.8.4"
+
+regexpu-core@^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.2.0"
+ regjsgen "^0.5.1"
+ regjsparser "^0.6.4"
+ unicode-match-property-ecmascript "^1.0.4"
+ unicode-match-property-value-ecmascript "^1.2.0"
+
+regjsgen@^0.5.1:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
+ integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
+
+regjsparser@^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==
+ dependencies:
+ jsesc "~0.5.0"
+
+resolve@^1.17.0, resolve@^1.3.2:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+ integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+ dependencies:
+ path-parse "^1.0.6"
+
+rollup@^2.26.4:
+ version "2.26.11"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.11.tgz#4fc31de9c7b83d50916fc8395f8c3d24730cdaae"
+ integrity sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==
+ optionalDependencies:
+ fsevents "~2.1.2"
+
+safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+semver@7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
+ integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
+
+semver@^5.4.1, semver@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+source-map@^0.5.0:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
+
+sourcemap-codec@^1.4.4:
+ version "1.4.8"
+ resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+ integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
+string.prototype.trimend@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+ integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.5"
+
+string.prototype.trimstart@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+ integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.5"
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
+
+unicode-canonical-property-names-ecmascript@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
+ integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==
+
+unicode-match-property-ecmascript@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c"
+ integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==
+ dependencies:
+ unicode-canonical-property-names-ecmascript "^1.0.4"
+ unicode-property-aliases-ecmascript "^1.0.4"
+
+unicode-match-property-value-ecmascript@^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==
+
+unicode-property-aliases-ecmascript@^1.0.4:
+ 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==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/scripts/build.mjs b/scripts/build.mjs
index 7084ebc0..c2909283 100644
--- a/scripts/build.mjs
+++ b/scripts/build.mjs
@@ -88,7 +88,7 @@ async function build() {
// so do it first
const themeAssets = await copyThemeAssets(themes, legacy);
const jsBundlePath = await buildJs("src/main.js", `${PROJECT_ID}.js`);
- const jsLegacyBundlePath = await buildJsLegacy("src/main.js", `${PROJECT_ID}-legacy.js`);
+ const jsLegacyBundlePath = await buildJsLegacy("src/main.js", `${PROJECT_ID}-legacy.js`, 'src/legacy-extras.js');
const jsWorkerPath = await buildWorkerJsLegacy("src/worker.js", `worker.js`);
const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes, themeAssets);
@@ -185,7 +185,7 @@ async function buildHtml(doc, version, assetPaths, manifestPath) {
doc("script#main").replaceWith(
`` +
`` +
- ``);
+ ``);
removeOrEnableScript(doc("script#service-worker"), offline);
const versionScript = doc("script#version");
@@ -218,7 +218,7 @@ async function buildJs(inputFile, outputName) {
return bundlePath;
}
-async function buildJsLegacy(inputFile, outputName, polyfillFile = null) {
+async function buildJsLegacy(inputFile, outputName, extraFile, polyfillFile) {
// compile down to whatever IE 11 needs
const babelPlugin = babel.babel({
babelHelpers: 'bundled',
@@ -237,10 +237,14 @@ async function buildJsLegacy(inputFile, outputName, polyfillFile = null) {
if (!polyfillFile) {
polyfillFile = 'src/legacy-polyfill.js';
}
+ const inputFiles = [polyfillFile, inputFile];
+ if (extraFile) {
+ inputFiles.push(extraFile);
+ }
// create js bundle
const rollupConfig = {
- input: [polyfillFile, inputFile],
- plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
+ input: inputFiles,
+ plugins: [multi(), commonjs(), nodeResolve(), babelPlugin]
};
const bundle = await rollup(rollupConfig);
const {output} = await bundle.generate({
@@ -255,7 +259,7 @@ async function buildJsLegacy(inputFile, outputName, polyfillFile = null) {
function buildWorkerJsLegacy(inputFile, outputName) {
const polyfillFile = 'src/worker-polyfill.js';
- return buildJsLegacy(inputFile, outputName, polyfillFile);
+ return buildJsLegacy(inputFile, outputName, null, polyfillFile);
}
async function buildOffline(version, assetPaths) {
diff --git a/scripts/package-overrides/buffer/index.js b/scripts/package-overrides/buffer/index.js
new file mode 100644
index 00000000..0ed6d01c
--- /dev/null
+++ b/scripts/package-overrides/buffer/index.js
@@ -0,0 +1,6 @@
+module.exports = class Buffer {
+ static isBuffer(array) {return array instanceof Uint8Array;}
+ static from(arrayBuffer) {return arrayBuffer;}
+ static allocUnsafe(size) {return Buffer.alloc(size);}
+ static alloc(size) {return new Uint8Array(size);}
+};
diff --git a/scripts/package-overrides/safe-buffer/index.js b/scripts/package-overrides/safe-buffer/index.js
new file mode 100644
index 00000000..93a10446
--- /dev/null
+++ b/scripts/package-overrides/safe-buffer/index.js
@@ -0,0 +1 @@
+module.exports.Buffer = require("buffer");
diff --git a/scripts/post-install.mjs b/scripts/post-install.mjs
index 328f8c7c..41d233c7 100644
--- a/scripts/post-install.mjs
+++ b/scripts/post-install.mjs
@@ -22,18 +22,34 @@ import { fileURLToPath } from 'url';
import { dirname } from 'path';
// needed to translate commonjs modules to esm
import commonjs from '@rollup/plugin-commonjs';
-// multi-entry plugin so we can add polyfill file to main
+import { nodeResolve } from '@rollup/plugin-node-resolve';
import {removeDirIfExists} from "./common.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectDir = path.join(__dirname, "../");
+/** function used to resolve common-js require calls below. */
+function packageIterator(request, start, defaultIterator) {
+ // this is just working for bs58, would need to tune it further for other dependencies
+ if (request === "safe-buffer") {
+ return [path.join(projectDir, "/scripts/package-overrides/safe-buffer")];
+ } else if (request === "buffer/") {
+ return [path.join(projectDir, "/scripts/package-overrides/buffer")];
+ } else {
+ return defaultIterator();
+ }
+}
+
async function commonjsToESM(src, dst) {
// create js bundle
const bundle = await rollup({
input: src,
- plugins: [commonjs()]
+ plugins: [commonjs(), nodeResolve({
+ browser: true,
+ preferBuiltins: false,
+ customResolveOptions: {packageIterator}
+ })]
});
const {output} = await bundle.generate({
format: 'es'
@@ -59,6 +75,27 @@ async function populateLib() {
path.join(modulesDir, 'another-json/another-json.js'),
path.join(libDir, "another-json/index.js")
);
+ // transpile bs58 to esm
+ await fs.mkdir(path.join(libDir, "bs58/"));
+ await commonjsToESM(
+ path.join(modulesDir, 'bs58/index.js'),
+ path.join(libDir, "bs58/index.js")
+ );
+ // transpile base64-arraybuffer to esm
+ await fs.mkdir(path.join(libDir, "base64-arraybuffer/"));
+ await commonjsToESM(
+ path.join(modulesDir, 'base64-arraybuffer/lib/base64-arraybuffer.js'),
+ path.join(libDir, "base64-arraybuffer/index.js")
+ );
+ // this probably should no go in here, we can just import "aes-js" from legacy-extras.js
+ // as that file is never loaded from a browser
+
+ // transpile aesjs to esm
+ await fs.mkdir(path.join(libDir, "aes-js/"));
+ await commonjsToESM(
+ path.join(modulesDir, 'aes-js/index.js'),
+ path.join(libDir, "aes-js/index.js")
+ );
}
populateLib();
diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js
index 0511d8a7..e9454a24 100644
--- a/src/domain/SessionLoadViewModel.js
+++ b/src/domain/SessionLoadViewModel.js
@@ -80,7 +80,7 @@ export class SessionLoadViewModel extends ViewModel {
async cancel() {
try {
if (this._sessionContainer) {
- this._sessionContainer.stop();
+ this._sessionContainer.dispose();
if (this._deleteSessionOnCancel) {
await this._sessionContainer.deleteSession();
}
diff --git a/src/domain/session/SessionStatusViewModel.js b/src/domain/session/SessionStatusViewModel.js
index 54fad4a1..d6ce464c 100644
--- a/src/domain/session/SessionStatusViewModel.js
+++ b/src/domain/session/SessionStatusViewModel.js
@@ -31,21 +31,24 @@ const SessionStatus = createEnum(
export class SessionStatusViewModel extends ViewModel {
constructor(options) {
super(options);
- const {sync, reconnector} = options;
+ const {sync, reconnector, session} = options;
this._sync = sync;
this._reconnector = reconnector;
this._status = this._calculateState(reconnector.connectionStatus.get(), sync.status.get());
-
+ this._session = session;
}
start() {
const update = () => this._updateStatus();
this.track(this._sync.status.subscribe(update));
this.track(this._reconnector.connectionStatus.subscribe(update));
+ this.track(this._session.needsSessionBackup.subscribe(() => {
+ this.emitChange();
+ }));
}
get isShown() {
- return this._status !== SessionStatus.Syncing;
+ return this._session.needsSessionBackup.get() || this._status !== SessionStatus.Syncing;
}
get statusLabel() {
@@ -61,6 +64,9 @@ export class SessionStatusViewModel extends ViewModel {
case SessionStatus.SyncError:
return this.i18n`Sync failed because of ${this._sync.error}`;
}
+ if (this._session.needsSessionBackup.get()) {
+ return this.i18n`Set up secret storage to decrypt older messages.`;
+ }
return "";
}
@@ -122,9 +128,25 @@ export class SessionStatusViewModel extends ViewModel {
return this._status === SessionStatus.Disconnected;
}
+ get isSecretStorageShown() {
+ // TODO: we need a model here where we can have multiple messages queued up and their buttons don't bleed into each other.
+ return this._status === SessionStatus.Syncing && this._session.needsSessionBackup.get();
+ }
+
connectNow() {
if (this.isConnectNowShown) {
this._reconnector.tryNow();
}
}
+
+ async enterPassphrase(passphrase) {
+ if (passphrase) {
+ try {
+ await this._session.enableSecretStorage("recoverykey", passphrase);
+ } catch (err) {
+ console.error(err);
+ alert(`Could not set up secret storage: ${err.message}`);
+ }
+ }
+ }
}
diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js
index 152b77ac..0a77d2a0 100644
--- a/src/domain/session/SessionViewModel.js
+++ b/src/domain/session/SessionViewModel.js
@@ -26,7 +26,8 @@ export class SessionViewModel extends ViewModel {
this._session = sessionContainer.session;
this._sessionStatusViewModel = this.track(new SessionStatusViewModel(this.childOptions({
sync: sessionContainer.sync,
- reconnector: sessionContainer.reconnector
+ reconnector: sessionContainer.reconnector,
+ session: sessionContainer.session,
})));
this._currentRoomTileViewModel = null;
this._currentRoomViewModel = null;
diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js
index c2d9df5d..cf46899a 100644
--- a/src/domain/session/room/timeline/TilesCollection.js
+++ b/src/domain/session/room/timeline/TilesCollection.js
@@ -145,7 +145,12 @@ export class TilesCollection extends BaseObservableList {
if (tile) {
const action = tile.updateEntry(entry, params);
if (action.shouldReplace) {
- this._replaceTile(tileIdx, tile, this._tileCreator(entry));
+ const newTile = this._tileCreator(entry);
+ if (newTile) {
+ this._replaceTile(tileIdx, tile, newTile);
+ } else {
+ this._removeTile(tileIdx, tile);
+ }
}
if (action.shouldRemove) {
this._removeTile(tileIdx, tile);
diff --git a/src/legacy-extras.js b/src/legacy-extras.js
new file mode 100644
index 00000000..e6b1f08e
--- /dev/null
+++ b/src/legacy-extras.js
@@ -0,0 +1,6 @@
+import aesjs from "../lib/aes-js/index.js";
+import {hkdf} from "./utils/crypto/hkdf.js";
+
+// these are run-time dependencies that are only needed for the legacy bundle.
+// they are exported here and passed into main to make them available to the app.
+export const legacyExtras = {crypto:{aesjs, hkdf}};
diff --git a/src/legacy-polyfill.js b/src/legacy-polyfill.js
index a48416c7..80be7a61 100644
--- a/src/legacy-polyfill.js
+++ b/src/legacy-polyfill.js
@@ -18,6 +18,13 @@ limitations under the License.
import "core-js/stable";
import "regenerator-runtime/runtime";
import "mdn-polyfills/Element.prototype.closest";
+// olm.init needs utf-16le, and this polyfill was
+// the only one I could find supporting it.
+// TODO: because the library sees a commonjs environment,
+// it will also include the file supporting *all* the encodings,
+// weighing a good extra 500kb :-(
+import "text-encoding";
+
// TODO: contribute this to mdn-polyfills
if (!Element.prototype.remove) {
Element.prototype.remove = function remove() {
diff --git a/src/main.js b/src/main.js
index 97c0b812..5ad97cda 100644
--- a/src/main.js
+++ b/src/main.js
@@ -25,6 +25,7 @@ import {BrawlViewModel} from "./domain/BrawlViewModel.js";
import {BrawlView} from "./ui/web/BrawlView.js";
import {Clock} from "./ui/web/dom/Clock.js";
import {OnlineStatus} from "./ui/web/dom/OnlineStatus.js";
+import {CryptoDriver} from "./ui/web/dom/CryptoDriver.js";
import {WorkerPool} from "./utils/WorkerPool.js";
import {OlmWorker} from "./matrix/e2ee/OlmWorker.js";
@@ -78,8 +79,9 @@ async function loadOlmWorker(paths) {
// Don't use a default export here, as we use multiple entries during legacy build,
// which does not support default exports,
// see https://github.com/rollup/plugins/tree/master/packages/multi-entry
-export async function main(container, paths) {
+export async function main(container, paths, legacyExtras) {
try {
+ // TODO: add .legacy to body in (legacy)platform.createAndMountRootView; and use body:not(.legacy) if needed for modern stuff
const isIE11 = !!window.MSInputMethodContext && !!document.documentMode;
if (isIE11) {
document.body.className += " ie11";
@@ -122,6 +124,7 @@ export async function main(container, paths) {
sessionInfoStorage,
request,
clock,
+ cryptoDriver: new CryptoDriver(legacyExtras?.crypto),
olmPromise,
workerPromise,
});
@@ -132,6 +135,7 @@ export async function main(container, paths) {
});
window.__brawlViewModel = vm;
await vm.load();
+ // TODO: replace with platform.createAndMountRootView(vm, container);
const view = new BrawlView(vm);
container.appendChild(view.mount());
} catch(err) {
diff --git a/src/matrix/Session.js b/src/matrix/Session.js
index f8ac2f40..36a9b866 100644
--- a/src/matrix/Session.js
+++ b/src/matrix/Session.js
@@ -23,18 +23,26 @@ import {Account as E2EEAccount} from "./e2ee/Account.js";
import {Decryption as OlmDecryption} from "./e2ee/olm/Decryption.js";
import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js";
import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption.js";
+import {SessionBackup} from "./e2ee/megolm/SessionBackup.js";
import {Encryption as MegOlmEncryption} from "./e2ee/megolm/Encryption.js";
import {MEGOLM_ALGORITHM} from "./e2ee/common.js";
import {RoomEncryption} from "./e2ee/RoomEncryption.js";
import {DeviceTracker} from "./e2ee/DeviceTracker.js";
import {LockMap} from "../utils/LockMap.js";
import {groupBy} from "../utils/groupBy.js";
+import {
+ keyFromCredential as ssssKeyFromCredential,
+ readKey as ssssReadKey,
+ writeKey as ssssWriteKey,
+} from "./ssss/index.js";
+import {SecretStorage} from "./ssss/SecretStorage.js";
+import {ObservableValue} from "../observable/ObservableValue.js";
const PICKLE_KEY = "DEFAULT_KEY";
export class Session {
// sessionInfo contains deviceId, userId and homeServer
- constructor({clock, storage, hsApi, sessionInfo, olm, olmWorker}) {
+ constructor({clock, storage, hsApi, sessionInfo, olm, olmWorker, cryptoDriver}) {
this._clock = clock;
this._storage = storage;
this._hsApi = hsApi;
@@ -54,6 +62,7 @@ export class Session {
this._megolmDecryption = null;
this._getSyncToken = () => this.syncToken;
this._olmWorker = olmWorker;
+ this._cryptoDriver = cryptoDriver;
if (olm) {
this._olmUtil = new olm.Utility();
@@ -66,6 +75,7 @@ export class Session {
});
}
this._createRoomEncryption = this._createRoomEncryption.bind(this);
+ this.needsSessionBackup = new ObservableValue(false);
}
// called once this._e2eeAccount is assigned
@@ -130,10 +140,62 @@ export class Session {
megolmEncryption: this._megolmEncryption,
megolmDecryption: this._megolmDecryption,
storage: this._storage,
- encryptionParams
+ sessionBackup: this._sessionBackup,
+ encryptionParams,
+ notifyMissingMegolmSession: () => {
+ if (!this._sessionBackup) {
+ this.needsSessionBackup.set(true)
+ }
+ },
+ clock: this._clock
});
}
+ /**
+ * Enable secret storage by providing the secret storage credential.
+ * This will also see if there is a megolm session backup and try to enable that if so.
+ *
+ * @param {string} type either "passphrase" or "recoverykey"
+ * @param {string} credential either the passphrase or the recovery key, depending on the type
+ * @return {Promise} resolves or rejects after having tried to enable secret storage
+ */
+ async enableSecretStorage(type, credential) {
+ if (!this._olm) {
+ throw new Error("olm required");
+ }
+ const key = await ssssKeyFromCredential(type, credential, this._storage, this._cryptoDriver, this._olm);
+ // and create session backup, which needs to read from accountData
+ const readTxn = await this._storage.readTxn([
+ this._storage.storeNames.accountData,
+ ]);
+ await this._createSessionBackup(key, readTxn);
+ // only after having read a secret, write the key
+ // as we only find out if it was good if the MAC verification succeeds
+ const writeTxn = await this._storage.readWriteTxn([
+ this._storage.storeNames.session,
+ ]);
+ try {
+ ssssWriteKey(key, writeTxn);
+ } catch (err) {
+ writeTxn.abort();
+ throw err;
+ }
+ await writeTxn.complete();
+ }
+
+ async _createSessionBackup(ssssKey, txn) {
+ const secretStorage = new SecretStorage({key: ssssKey, cryptoDriver: this._cryptoDriver});
+ this._sessionBackup = await SessionBackup.fromSecretStorage({olm: this._olm, secretStorage, hsApi: this._hsApi, txn});
+ if (this._sessionBackup) {
+ for (const room of this._rooms.values()) {
+ if (room.isEncrypted) {
+ room.enableSessionBackup(this._sessionBackup);
+ }
+ }
+ }
+ this.needsSessionBackup.set(false);
+ }
+
// called after load
async beforeFirstSync(isNewLogin) {
if (this._olm) {
@@ -155,6 +217,17 @@ export class Session {
await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
await this._e2eeAccount.uploadKeys(this._storage);
await this._deviceMessageHandler.decryptPending(this.rooms);
+
+ const txn = await this._storage.readTxn([
+ this._storage.storeNames.session,
+ this._storage.storeNames.accountData,
+ ]);
+ // try set up session backup if we stored the ssss key
+ const ssssKey = await ssssReadKey(txn);
+ if (ssssKey) {
+ // txn will end here as this does a network request
+ await this._createSessionBackup(ssssKey, txn);
+ }
}
}
@@ -197,9 +270,13 @@ export class Session {
return this._sendScheduler.isStarted;
}
- stop() {
+ dispose() {
this._olmWorker?.dispose();
this._sendScheduler.stop();
+ this._sessionBackup?.dispose();
+ for (const room of this._rooms.values()) {
+ room.dispose();
+ }
}
async start(lastVersionResponse) {
@@ -289,6 +366,16 @@ export class Session {
if (Array.isArray(toDeviceEvents)) {
this._deviceMessageHandler.writeSync(toDeviceEvents, txn);
}
+
+ // store account data
+ const accountData = syncResponse["account_data"];
+ if (Array.isArray(accountData?.events)) {
+ for (const event of accountData.events) {
+ if (typeof event.type === "string") {
+ txn.accountData.set(event);
+ }
+ }
+ }
return changes;
}
@@ -306,8 +393,6 @@ export class Session {
const needsToUploadOTKs = await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
const promises = [this._deviceMessageHandler.decryptPending(this.rooms)];
if (needsToUploadOTKs) {
- // TODO: we could do this in parallel with sync if it proves to be too slow
- // but I'm not sure how to not swallow errors in that case
promises.push(this._e2eeAccount.uploadKeys(this._storage));
}
// run key upload and decryption in parallel
diff --git a/src/matrix/SessionContainer.js b/src/matrix/SessionContainer.js
index 98ffd2b1..0de70400 100644
--- a/src/matrix/SessionContainer.js
+++ b/src/matrix/SessionContainer.js
@@ -42,7 +42,7 @@ export const LoginFailure = createEnum(
);
export class SessionContainer {
- constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage, olmPromise, workerPromise}) {
+ constructor({clock, random, onlineStatus, request, storageFactory, sessionInfoStorage, olmPromise, workerPromise, cryptoDriver}) {
this._random = random;
this._clock = clock;
this._onlineStatus = onlineStatus;
@@ -60,6 +60,7 @@ export class SessionContainer {
this._storage = null;
this._olmPromise = olmPromise;
this._workerPromise = workerPromise;
+ this._cryptoDriver = cryptoDriver;
}
createNewSessionId() {
@@ -159,7 +160,7 @@ export class SessionContainer {
}
this._session = new Session({storage: this._storage,
sessionInfo: filteredSessionInfo, hsApi, olm,
- clock: this._clock, olmWorker});
+ clock: this._clock, olmWorker, cryptoDriver: this._cryptoDriver});
await this._session.load();
this._status.set(LoadStatus.SessionSetup);
await this._session.beforeFirstSync(isNewLogin);
@@ -245,7 +246,7 @@ export class SessionContainer {
return this._reconnector;
}
- stop() {
+ dispose() {
if (this._reconnectSubscription) {
this._reconnectSubscription();
this._reconnectSubscription = null;
@@ -254,7 +255,7 @@ export class SessionContainer {
this._sync.stop();
}
if (this._session) {
- this._session.stop();
+ this._session.dispose();
}
if (this._waitForFirstSyncHandle) {
this._waitForFirstSyncHandle.dispose();
diff --git a/src/matrix/Sync.js b/src/matrix/Sync.js
index ca76e57a..fe2a2bed 100644
--- a/src/matrix/Sync.js
+++ b/src/matrix/Sync.js
@@ -105,6 +105,8 @@ export class Sync {
this._status.set(SyncStatus.Syncing);
} catch (err) {
if (!(err instanceof AbortError)) {
+ console.warn("stopping sync because of error");
+ console.error(err);
this._error = err;
this._status.set(SyncStatus.Stopped);
}
@@ -168,8 +170,6 @@ export class Sync {
}));
sessionChanges = await this._session.writeSync(response, syncFilterId, syncTxn);
} catch(err) {
- console.warn("aborting syncTxn because of error");
- console.error(err);
// avoid corrupting state by only
// storing the sync up till the point
// the exception occurred
@@ -228,7 +228,8 @@ export class Sync {
// to discard outbound session when somebody leaves a room
// and to create room key messages when somebody leaves
storeNames.outboundGroupSessions,
- storeNames.operations
+ storeNames.operations,
+ storeNames.accountData,
]);
}
diff --git a/src/matrix/e2ee/RoomEncryption.js b/src/matrix/e2ee/RoomEncryption.js
index b5b56ec2..87c64cbe 100644
--- a/src/matrix/e2ee/RoomEncryption.js
+++ b/src/matrix/e2ee/RoomEncryption.js
@@ -21,8 +21,17 @@ import {makeTxnId} from "../common.js";
const ENCRYPTED_TYPE = "m.room.encrypted";
+function encodeMissingSessionKey(senderKey, sessionId) {
+ return `${senderKey}|${sessionId}`;
+}
+
+function decodeMissingSessionKey(key) {
+ const [senderKey, sessionId] = key.split("|");
+ return {senderKey, sessionId};
+}
+
export class RoomEncryption {
- constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage}) {
+ constructor({room, deviceTracker, olmEncryption, megolmEncryption, megolmDecryption, encryptionParams, storage, sessionBackup, notifyMissingMegolmSession, clock}) {
this._room = room;
this._deviceTracker = deviceTracker;
this._olmEncryption = olmEncryption;
@@ -37,6 +46,21 @@ export class RoomEncryption {
this._eventIdsByMissingSession = new Map();
this._senderDeviceCache = new Map();
this._storage = storage;
+ this._sessionBackup = sessionBackup;
+ this._notifyMissingMegolmSession = notifyMissingMegolmSession;
+ this._clock = clock;
+ this._disposed = false;
+ }
+
+ async enableSessionBackup(sessionBackup) {
+ if (this._sessionBackup) {
+ return;
+ }
+ this._sessionBackup = sessionBackup;
+ for(const key of this._eventIdsByMissingSession.keys()) {
+ const {senderKey, sessionId} = decodeMissingSessionKey(key);
+ await this._requestMissingSessionFromBackup(senderKey, sessionId, null);
+ }
}
notifyTimelineClosed() {
@@ -81,7 +105,7 @@ export class RoomEncryption {
} else if (source === DecryptionSource.Retry) {
// when retrying, we could have mixed events from at the bottom of the timeline (sync)
// and somewhere else, so create a custom cache we use just for this operation.
- customCache = this._megolmEncryption.createSessionCache();
+ customCache = this._megolmDecryption.createSessionCache();
sessionCache = customCache;
} else {
throw new Error("Unknown source: " + source);
@@ -91,13 +115,13 @@ export class RoomEncryption {
if (customCache) {
customCache.dispose();
}
- return new DecryptionPreparation(preparation, errors, {isTimelineOpen}, this);
+ return new DecryptionPreparation(preparation, errors, {isTimelineOpen, source}, this);
}
async _processDecryptionResults(results, errors, flags, txn) {
for (const error of errors.values()) {
if (error.code === "MEGOLM_NO_SESSION") {
- this._addMissingSessionEvent(error.event);
+ this._addMissingSessionEvent(error.event, flags.source);
}
}
if (flags.isTimelineOpen) {
@@ -120,23 +144,78 @@ export class RoomEncryption {
}
}
- _addMissingSessionEvent(event) {
+ _addMissingSessionEvent(event, source) {
const senderKey = event.content?.["sender_key"];
const sessionId = event.content?.["session_id"];
- const key = `${senderKey}|${sessionId}`;
+ const key = encodeMissingSessionKey(senderKey, sessionId);
let eventIds = this._eventIdsByMissingSession.get(key);
+ // new missing session
if (!eventIds) {
+ this._requestMissingSessionFromBackup(senderKey, sessionId, source);
eventIds = new Set();
this._eventIdsByMissingSession.set(key, eventIds);
}
eventIds.add(event.event_id);
}
+ async _requestMissingSessionFromBackup(senderKey, sessionId, source) {
+ if (!this._sessionBackup) {
+ this._notifyMissingMegolmSession();
+ return;
+ }
+ // if the message came from sync, wait 10s to see if the room key arrives,
+ // and only after that proceed to request from backup
+ if (source === DecryptionSource.Sync) {
+ await this._clock.createTimeout(10000).elapsed();
+ if (this._disposed || !this._eventIdsByMissingSession.has(encodeMissingSessionKey(senderKey, sessionId))) {
+ return;
+ }
+ }
+
+ try {
+ const session = await this._sessionBackup.getSession(this._room.id, sessionId);
+ if (session?.algorithm === MEGOLM_ALGORITHM) {
+ if (session["sender_key"] !== senderKey) {
+ console.warn("Got session key back from backup with different sender key, ignoring", {session, senderKey});
+ return;
+ }
+ const txn = await this._storage.readWriteTxn([this._storage.storeNames.inboundGroupSessions]);
+ let roomKey;
+ try {
+ roomKey = await this._megolmDecryption.addRoomKeyFromBackup(
+ this._room.id, sessionId, session, txn);
+ } catch (err) {
+ txn.abort();
+ throw err;
+ }
+ await txn.complete();
+
+ if (roomKey) {
+ // this will call into applyRoomKeys below
+ await this._room.notifyRoomKeys([roomKey]);
+ }
+ } else if (session?.algorithm) {
+ console.info(`Backed-up session of unknown algorithm: ${session.algorithm}`);
+ }
+ } catch (err) {
+ console.error(`Could not get session ${sessionId} from backup`, err);
+ }
+ }
+
+ /**
+ * @type {RoomKeyDescription}
+ * @property {RoomKeyDescription} senderKey the curve25519 key of the sender
+ * @property {RoomKeyDescription} sessionId
+ *
+ *
+ * @param {Array} roomKeys
+ * @return {Array} the event ids that should be retried to decrypt
+ */
applyRoomKeys(roomKeys) {
// retry decryption with the new sessions
const retryEventIds = [];
for (const roomKey of roomKeys) {
- const key = `${roomKey.senderKey}|${roomKey.sessionId}`;
+ const key = encodeMissingSessionKey(roomKey.senderKey, roomKey.sessionId);
const entriesForSession = this._eventIdsByMissingSession.get(key);
if (entriesForSession) {
this._eventIdsByMissingSession.delete(key);
@@ -263,6 +342,10 @@ export class RoomEncryption {
const txnId = makeTxnId();
await hsApi.sendToDevice(type, payload, txnId).response();
}
+
+ dispose() {
+ this._disposed = true;
+ }
}
/**
diff --git a/src/matrix/e2ee/common.js b/src/matrix/e2ee/common.js
index 190f2fa2..52995765 100644
--- a/src/matrix/e2ee/common.js
+++ b/src/matrix/e2ee/common.js
@@ -17,7 +17,7 @@ limitations under the License.
import anotherjson from "../../../lib/another-json/index.js";
import {createEnum} from "../../utils/enum.js";
-export const DecryptionSource = createEnum(["Sync", "Timeline", "Retry"]);
+export const DecryptionSource = createEnum("Sync", "Timeline", "Retry");
// use common prefix so it's easy to clear properties that are not e2ee related during session clear
export const SESSION_KEY_PREFIX = "e2ee:";
diff --git a/src/matrix/e2ee/megolm/Decryption.js b/src/matrix/e2ee/megolm/Decryption.js
index 4d756dcb..09662e96 100644
--- a/src/matrix/e2ee/megolm/Decryption.js
+++ b/src/matrix/e2ee/megolm/Decryption.js
@@ -141,37 +141,76 @@ export class Decryption {
const session = new this._olm.InboundGroupSession();
try {
session.create(sessionKey);
-
- let incomingSessionIsBetter = true;
- const existingSessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
- if (existingSessionEntry) {
- const existingSession = new this._olm.InboundGroupSession();
- try {
- existingSession.unpickle(this._pickleKey, existingSessionEntry.session);
- incomingSessionIsBetter = session.first_known_index() < existingSession.first_known_index();
- } finally {
- existingSession.free();
- }
- }
-
- if (incomingSessionIsBetter) {
- const sessionEntry = {
- roomId,
- senderKey,
- sessionId,
- session: session.pickle(this._pickleKey),
- claimedKeys: {ed25519: claimedEd25519Key},
- };
- txn.inboundGroupSessions.set(sessionEntry);
+ const sessionEntry = await this._writeInboundSession(
+ session, roomId, senderKey, claimedEd25519Key, sessionId, txn);
+ if (sessionEntry) {
newSessions.push(sessionEntry);
}
} finally {
session.free();
}
-
}
// this will be passed to the Room in notifyRoomKeys
return newSessions;
}
+
+ /*
+ sessionInfo is a response from key backup and has the following keys:
+ algorithm
+ forwarding_curve25519_key_chain
+ sender_claimed_keys
+ sender_key
+ session_key
+ */
+ async addRoomKeyFromBackup(roomId, sessionId, sessionInfo, txn) {
+ const sessionKey = sessionInfo["session_key"];
+ const senderKey = sessionInfo["sender_key"];
+ // TODO: can we just trust this?
+ const claimedEd25519Key = sessionInfo["sender_claimed_keys"]?.["ed25519"];
+
+ if (
+ typeof roomId !== "string" ||
+ typeof sessionId !== "string" ||
+ typeof senderKey !== "string" ||
+ typeof sessionKey !== "string" ||
+ typeof claimedEd25519Key !== "string"
+ ) {
+ return;
+ }
+ const session = new this._olm.InboundGroupSession();
+ try {
+ session.import_session(sessionKey);
+ return await this._writeInboundSession(
+ session, roomId, senderKey, claimedEd25519Key, sessionId, txn);
+ } finally {
+ session.free();
+ }
+ }
+
+ async _writeInboundSession(session, roomId, senderKey, claimedEd25519Key, sessionId, txn) {
+ let incomingSessionIsBetter = true;
+ const existingSessionEntry = await txn.inboundGroupSessions.get(roomId, senderKey, sessionId);
+ if (existingSessionEntry) {
+ const existingSession = new this._olm.InboundGroupSession();
+ try {
+ existingSession.unpickle(this._pickleKey, existingSessionEntry.session);
+ incomingSessionIsBetter = session.first_known_index() < existingSession.first_known_index();
+ } finally {
+ existingSession.free();
+ }
+ }
+
+ if (incomingSessionIsBetter) {
+ const sessionEntry = {
+ roomId,
+ senderKey,
+ sessionId,
+ session: session.pickle(this._pickleKey),
+ claimedKeys: {ed25519: claimedEd25519Key},
+ };
+ txn.inboundGroupSessions.set(sessionEntry);
+ return sessionEntry;
+ }
+ }
}
diff --git a/src/matrix/e2ee/megolm/SessionBackup.js b/src/matrix/e2ee/megolm/SessionBackup.js
new file mode 100644
index 00000000..e6614601
--- /dev/null
+++ b/src/matrix/e2ee/megolm/SessionBackup.js
@@ -0,0 +1,60 @@
+/*
+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.
+*/
+
+import base64 from "../../../../lib/base64-arraybuffer/index.js";
+
+export class SessionBackup {
+ constructor({backupInfo, decryption, hsApi}) {
+ this._backupInfo = backupInfo;
+ this._decryption = decryption;
+ this._hsApi = hsApi;
+ }
+
+ async getSession(roomId, sessionId) {
+ const sessionResponse = await this._hsApi.roomKeyForRoomAndSession(this._backupInfo.version, roomId, sessionId).response();
+ const sessionInfo = this._decryption.decrypt(
+ sessionResponse.session_data.ephemeral,
+ sessionResponse.session_data.mac,
+ sessionResponse.session_data.ciphertext,
+ );
+ return JSON.parse(sessionInfo);
+ }
+
+ dispose() {
+ this._decryption.free();
+ }
+
+ static async fromSecretStorage({olm, secretStorage, hsApi, txn}) {
+ const base64PrivateKey = await secretStorage.readSecret("m.megolm_backup.v1", txn);
+ if (base64PrivateKey) {
+ const privateKey = new Uint8Array(base64.decode(base64PrivateKey));
+ const backupInfo = await hsApi.roomKeysVersion().response();
+ const expectedPubKey = backupInfo.auth_data.public_key;
+ const decryption = new olm.PkDecryption();
+ try {
+ const pubKey = decryption.init_with_private_key(privateKey);
+ if (pubKey !== expectedPubKey) {
+ throw new Error(`Bad backup key, public key does not match. Calculated ${pubKey} but expected ${expectedPubKey}`);
+ }
+ } catch(err) {
+ decryption.free();
+ throw err;
+ }
+ return new SessionBackup({backupInfo, decryption, hsApi});
+ }
+ }
+}
+
diff --git a/src/matrix/net/HomeServerApi.js b/src/matrix/net/HomeServerApi.js
index 649a7462..955e9548 100644
--- a/src/matrix/net/HomeServerApi.js
+++ b/src/matrix/net/HomeServerApi.js
@@ -73,13 +73,13 @@ export class HomeServerApi {
return `${this._homeserver}/_matrix/client/r0${csPath}`;
}
- _request(method, url, queryParams, body, options) {
+ _baseRequest(method, url, queryParams, body, options, accessToken) {
const queryString = encodeQueryParams(queryParams);
url = `${url}?${queryString}`;
let bodyString;
const headers = new Map();
- if (this._accessToken) {
- headers.set("Authorization", `Bearer ${this._accessToken}`);
+ if (accessToken) {
+ headers.set("Authorization", `Bearer ${accessToken}`);
}
headers.set("Accept", "application/json");
if (body) {
@@ -106,16 +106,24 @@ export class HomeServerApi {
return wrapper;
}
+ _unauthedRequest(method, url, queryParams, body, options) {
+ return this._baseRequest(method, url, queryParams, body, options, null);
+ }
+
+ _authedRequest(method, url, queryParams, body, options) {
+ return this._baseRequest(method, url, queryParams, body, options, this._accessToken);
+ }
+
_post(csPath, queryParams, body, options) {
- return this._request("POST", this._url(csPath), queryParams, body, options);
+ return this._authedRequest("POST", this._url(csPath), queryParams, body, options);
}
_put(csPath, queryParams, body, options) {
- return this._request("PUT", this._url(csPath), queryParams, body, options);
+ return this._authedRequest("PUT", this._url(csPath), queryParams, body, options);
}
_get(csPath, queryParams, body, options) {
- return this._request("GET", this._url(csPath), queryParams, body, options);
+ return this._authedRequest("GET", this._url(csPath), queryParams, body, options);
}
sync(since, filter, timeout, options = null) {
@@ -142,7 +150,7 @@ export class HomeServerApi {
}
passwordLogin(username, password, initialDeviceDisplayName, options = null) {
- return this._post("/login", null, {
+ return this._unauthedRequest("POST", this._url("/login"), null, {
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
@@ -158,7 +166,7 @@ export class HomeServerApi {
}
versions(options = null) {
- return this._request("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
+ return this._unauthedRequest("GET", `${this._homeserver}/_matrix/client/versions`, null, null, options);
}
uploadKeys(payload, options = null) {
@@ -176,6 +184,18 @@ export class HomeServerApi {
sendToDevice(type, payload, txnId, options = null) {
return this._put(`/sendToDevice/${encodeURIComponent(type)}/${encodeURIComponent(txnId)}`, null, payload, options);
}
+
+ roomKeysVersion(version = null, options = null) {
+ let versionPart = "";
+ if (version) {
+ versionPart = `/${encodeURIComponent(version)}`;
+ }
+ return this._get(`/room_keys/version${versionPart}`, null, null, options);
+ }
+
+ roomKeyForRoomAndSession(version, roomId, sessionId, options = null) {
+ return this._get(`/room_keys/keys/${encodeURIComponent(roomId)}/${encodeURIComponent(sessionId)}`, {version}, null, options);
+ }
get mediaRepository() {
return this._mediaRepository;
diff --git a/src/matrix/room/Room.js b/src/matrix/room/Room.js
index 5976242d..db61aa14 100644
--- a/src/matrix/room/Room.js
+++ b/src/matrix/room/Room.js
@@ -429,6 +429,10 @@ export class Room extends EventEmitter {
return !!this._summary.encryption;
}
+ enableSessionBackup(sessionBackup) {
+ this._roomEncryption?.enableSessionBackup(sessionBackup);
+ }
+
get isTrackingMembers() {
return this._summary.isTrackingMembers;
}
@@ -525,6 +529,11 @@ export class Room extends EventEmitter {
applyIsTrackingMembersChanges(changes) {
this._summary.applyChanges(changes);
}
+
+ dispose() {
+ this._roomEncryption?.dispose();
+ this._timeline?.dispose();
+ }
}
class DecryptionRequest {
diff --git a/src/matrix/room/RoomSummary.js b/src/matrix/room/RoomSummary.js
index 741b8764..21739a1e 100644
--- a/src/matrix/room/RoomSummary.js
+++ b/src/matrix/room/RoomSummary.js
@@ -326,7 +326,7 @@ export class RoomSummary {
* writeIsTrackingMembers, ... take a txn directly.
*/
async writeAndApplyChanges(data, storage) {
- const txn = await storage.readTxn([
+ const txn = await storage.readWriteTxn([
storage.storeNames.roomSummary,
]);
try {
diff --git a/src/matrix/ssss/SecretStorage.js b/src/matrix/ssss/SecretStorage.js
new file mode 100644
index 00000000..a94c2e19
--- /dev/null
+++ b/src/matrix/ssss/SecretStorage.js
@@ -0,0 +1,72 @@
+/*
+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.
+*/
+
+import base64 from "../../../lib/base64-arraybuffer/index.js";
+
+export class SecretStorage {
+ constructor({key, cryptoDriver}) {
+ this._key = key;
+ this._cryptoDriver = cryptoDriver;
+ }
+
+ async readSecret(name, txn) {
+ const accountData = await txn.accountData.get(name);
+ if (!accountData) {
+ return;
+ }
+ const encryptedData = accountData?.content?.encrypted?.[this._key.id];
+ if (!encryptedData) {
+ throw new Error(`Secret ${accountData.type} is not encrypted for key ${this._key.id}`);
+ }
+
+ if (this._key.algorithm === "m.secret_storage.v1.aes-hmac-sha2") {
+ return await this._decryptAESSecret(accountData.type, encryptedData);
+ } else {
+ throw new Error(`Unsupported algorithm for key ${this._key.id}: ${this._key.algorithm}`);
+ }
+ }
+
+ async _decryptAESSecret(type, encryptedData) {
+ // TODO: we should we move this to platform specific code
+ const textEncoder = new TextEncoder();
+ const textDecoder = new TextDecoder();
+ // now derive the aes and mac key from the 4s key
+ const hkdfKey = await this._cryptoDriver.derive.hkdf(
+ this._key.binaryKey,
+ new Uint8Array(8).buffer, //zero salt
+ textEncoder.encode(type), // info
+ "SHA-256",
+ 512 // 512 bits or 64 bytes
+ );
+ const aesKey = hkdfKey.slice(0, 32);
+ const hmacKey = hkdfKey.slice(32);
+
+ const ciphertextBytes = base64.decode(encryptedData.ciphertext);
+
+ const isVerified = await this._cryptoDriver.hmac.verify(
+ hmacKey, base64.decode(encryptedData.mac),
+ ciphertextBytes, "SHA-256");
+
+ if (!isVerified) {
+ throw new Error("Bad MAC");
+ }
+
+ const plaintextBytes = await this._cryptoDriver.aes.decrypt(
+ aesKey, base64.decode(encryptedData.iv), ciphertextBytes);
+
+ return textDecoder.decode(plaintextBytes);
+ }
+}
diff --git a/src/matrix/ssss/common.js b/src/matrix/ssss/common.js
new file mode 100644
index 00000000..38a14790
--- /dev/null
+++ b/src/matrix/ssss/common.js
@@ -0,0 +1,53 @@
+/*
+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 class KeyDescription {
+ constructor(id, keyAccountData) {
+ this._id = id;
+ this._keyAccountData = keyAccountData;
+ }
+
+ get id() {
+ return this._id;
+ }
+
+ get passphraseParams() {
+ return this._keyAccountData?.content?.passphrase;
+ }
+
+ get algorithm() {
+ return this._keyAccountData?.content?.algorithm;
+ }
+}
+
+export class Key {
+ constructor(keyDescription, binaryKey) {
+ this._keyDescription = keyDescription;
+ this._binaryKey = binaryKey;
+ }
+
+ get id() {
+ return this._keyDescription.id;
+ }
+
+ get binaryKey() {
+ return this._binaryKey;
+ }
+
+ get algorithm() {
+ return this._keyDescription.algorithm;
+ }
+}
diff --git a/src/matrix/ssss/index.js b/src/matrix/ssss/index.js
new file mode 100644
index 00000000..286744fc
--- /dev/null
+++ b/src/matrix/ssss/index.js
@@ -0,0 +1,64 @@
+/*
+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.
+*/
+
+import {KeyDescription, Key} from "./common.js";
+import {keyFromPassphrase} from "./passphrase.js";
+import {keyFromRecoveryKey} from "./recoveryKey.js";
+
+async function readDefaultKeyDescription(storage) {
+ const txn = await storage.readTxn([
+ storage.storeNames.accountData
+ ]);
+ const defaultKeyEvent = await txn.accountData.get("m.secret_storage.default_key");
+ const id = defaultKeyEvent?.content?.key;
+ if (!id) {
+ return;
+ }
+ const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${id}`);
+ if (!keyAccountData) {
+ return;
+ }
+ return new KeyDescription(id, keyAccountData);
+}
+
+export async function writeKey(key, txn) {
+ txn.session.set("ssssKey", {id: key.id, binaryKey: key.binaryKey});
+}
+
+export async function readKey(txn) {
+ const keyData = await txn.session.get("ssssKey");
+ if (!keyData) {
+ return;
+ }
+ const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`);
+ return new Key(new KeyDescription(keyData.id, keyAccountData), keyData.binaryKey);
+}
+
+export async function keyFromCredential(type, credential, storage, cryptoDriver, olm) {
+ const keyDescription = await readDefaultKeyDescription(storage);
+ if (!keyDescription) {
+ throw new Error("Could not find a default secret storage key in account data");
+ }
+ let key;
+ if (type === "passphrase") {
+ key = await keyFromPassphrase(keyDescription, credential, cryptoDriver);
+ } else if (type === "recoverykey") {
+ key = keyFromRecoveryKey(olm, keyDescription, credential);
+ } else {
+ throw new Error(`Invalid type: ${type}`);
+ }
+ return key;
+}
diff --git a/src/matrix/ssss/passphrase.js b/src/matrix/ssss/passphrase.js
new file mode 100644
index 00000000..1e3935a4
--- /dev/null
+++ b/src/matrix/ssss/passphrase.js
@@ -0,0 +1,46 @@
+/*
+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.
+*/
+
+import {Key} from "./common.js";
+
+const DEFAULT_ITERATIONS = 500000;
+const DEFAULT_BITSIZE = 256;
+
+/**
+ * @param {KeyDescription} keyDescription
+ * @param {string} passphrase
+ * @param {CryptoDriver} cryptoDriver
+ * @return {Key}
+ */
+export async function keyFromPassphrase(keyDescription, passphrase, cryptoDriver) {
+ const {passphraseParams} = keyDescription;
+ if (!passphraseParams) {
+ throw new Error("not a passphrase key");
+ }
+ if (passphraseParams.algorithm !== "m.pbkdf2") {
+ throw new Error(`Unsupported passphrase algorithm: ${passphraseParams.algorithm}`);
+ }
+ // TODO: we should we move this to platform specific code
+ const textEncoder = new TextEncoder();
+ const keyBits = await cryptoDriver.derive.pbkdf2(
+ textEncoder.encode(passphrase),
+ passphraseParams.iterations || DEFAULT_ITERATIONS,
+ // salt is just a random string, not encoded in any way
+ textEncoder.encode(passphraseParams.salt),
+ "SHA-512",
+ passphraseParams.bits || DEFAULT_BITSIZE);
+ return new Key(keyDescription, keyBits);
+}
diff --git a/src/matrix/ssss/recoveryKey.js b/src/matrix/ssss/recoveryKey.js
new file mode 100644
index 00000000..5d2b1d7a
--- /dev/null
+++ b/src/matrix/ssss/recoveryKey.js
@@ -0,0 +1,57 @@
+/*
+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.
+*/
+import bs58 from "../../../lib/bs58/index.js";
+import {Key} from "./common.js";
+
+const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
+
+/**
+ * @param {Olm} olm
+ * @param {KeyDescription} keyDescription
+ * @param {string} recoveryKey
+ * @return {Key}
+ */
+export function keyFromRecoveryKey(olm, keyDescription, recoveryKey) {
+ const result = bs58.decode(recoveryKey.replace(/ /g, ''));
+
+ let parity = 0;
+ for (const b of result) {
+ parity ^= b;
+ }
+ if (parity !== 0) {
+ throw new Error("Incorrect parity");
+ }
+
+ for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
+ if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
+ throw new Error("Incorrect prefix");
+ }
+ }
+
+ if (
+ result.length !==
+ OLM_RECOVERY_KEY_PREFIX.length + olm.PRIVATE_KEY_LENGTH + 1
+ ) {
+ throw new Error("Incorrect length");
+ }
+
+ const keyBits = Uint8Array.from(result.slice(
+ OLM_RECOVERY_KEY_PREFIX.length,
+ OLM_RECOVERY_KEY_PREFIX.length + olm.PRIVATE_KEY_LENGTH,
+ ));
+
+ return new Key(keyDescription, keyBits);
+}
diff --git a/src/matrix/storage/common.js b/src/matrix/storage/common.js
index f74dafdc..88238a11 100644
--- a/src/matrix/storage/common.js
+++ b/src/matrix/storage/common.js
@@ -28,7 +28,8 @@ export const STORE_NAMES = Object.freeze([
"inboundGroupSessions",
"outboundGroupSessions",
"groupSessionDecryptions",
- "operations"
+ "operations",
+ "accountData",
]);
export const STORE_MAP = Object.freeze(STORE_NAMES.reduce((nameMap, name) => {
diff --git a/src/matrix/storage/idb/StorageFactory.js b/src/matrix/storage/idb/StorageFactory.js
index 0b58d5dc..8f6e5d69 100644
--- a/src/matrix/storage/idb/StorageFactory.js
+++ b/src/matrix/storage/idb/StorageFactory.js
@@ -19,7 +19,7 @@ import { openDatabase, reqAsPromise } from "./utils.js";
import { exportSession, importSession } from "./export.js";
import { schema } from "./schema.js";
-const sessionName = sessionId => `brawl_session_${sessionId}`;
+const sessionName = sessionId => `hydrogen_session_${sessionId}`;
const openDatabaseWithSessionId = sessionId => openDatabase(sessionName(sessionId), createStores, schema.length);
export class StorageFactory {
diff --git a/src/matrix/storage/idb/Transaction.js b/src/matrix/storage/idb/Transaction.js
index d28d802f..08eacb34 100644
--- a/src/matrix/storage/idb/Transaction.js
+++ b/src/matrix/storage/idb/Transaction.js
@@ -31,6 +31,7 @@ import {InboundGroupSessionStore} from "./stores/InboundGroupSessionStore.js";
import {OutboundGroupSessionStore} from "./stores/OutboundGroupSessionStore.js";
import {GroupSessionDecryptionStore} from "./stores/GroupSessionDecryptionStore.js";
import {OperationStore} from "./stores/OperationStore.js";
+import {AccountDataStore} from "./stores/AccountDataStore.js";
export class Transaction {
constructor(txn, allowedStoreNames) {
@@ -111,6 +112,10 @@ export class Transaction {
return this._store("operations", idbStore => new OperationStore(idbStore));
}
+ get accountData() {
+ return this._store("accountData", idbStore => new AccountDataStore(idbStore));
+ }
+
complete() {
return txnAsPromise(this._txn);
}
diff --git a/src/matrix/storage/idb/schema.js b/src/matrix/storage/idb/schema.js
index b78c117b..4d0d45ac 100644
--- a/src/matrix/storage/idb/schema.js
+++ b/src/matrix/storage/idb/schema.js
@@ -10,7 +10,8 @@ export const schema = [
createMemberStore,
migrateSession,
createE2EEStores,
- migrateEncryptionFlag
+ migrateEncryptionFlag,
+ createAccountDataStore
];
// TODO: how to deal with git merge conflicts of this array?
@@ -97,3 +98,8 @@ async function migrateEncryptionFlag(db, txn) {
}
}
}
+
+// v6
+function createAccountDataStore(db) {
+ db.createObjectStore("accountData", {keyPath: "type"});
+}
diff --git a/src/matrix/storage/idb/stores/AccountDataStore.js b/src/matrix/storage/idb/stores/AccountDataStore.js
new file mode 100644
index 00000000..7f22a518
--- /dev/null
+++ b/src/matrix/storage/idb/stores/AccountDataStore.js
@@ -0,0 +1,29 @@
+/*
+Copyright 2020 Bruno Windels
+
+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 class AccountDataStore {
+ constructor(store) {
+ this._store = store;
+ }
+
+ async get(type) {
+ return await this._store.get(type);
+ }
+
+ set(event) {
+ return this._store.put(event);
+ }
+}
diff --git a/src/ui/web/dom/CryptoDriver.js b/src/ui/web/dom/CryptoDriver.js
new file mode 100644
index 00000000..66bb35c0
--- /dev/null
+++ b/src/ui/web/dom/CryptoDriver.js
@@ -0,0 +1,257 @@
+/*
+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.
+*/
+
+// turn IE11 result into promise
+function subtleCryptoResult(promiseOrOp, method) {
+ if (promiseOrOp instanceof Promise) {
+ return promiseOrOp;
+ } else {
+ return new Promise((resolve, reject) => {
+ promiseOrOp.oncomplete = e => resolve(e.target.result);
+ promiseOrOp.onerror = () => reject(new Error("Crypto error on " + method));
+ });
+ }
+}
+
+class CryptoHMACDriver {
+ constructor(subtleCrypto) {
+ this._subtleCrypto = subtleCrypto;
+ }
+ /**
+ * [hmac description]
+ * @param {BufferSource} key
+ * @param {BufferSource} mac
+ * @param {BufferSource} data
+ * @param {HashName} hash
+ * @return {boolean}
+ */
+ async verify(key, mac, data, hash) {
+ const opts = {
+ name: 'HMAC',
+ hash: {name: hashName(hash)},
+ };
+ const hmacKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['verify'],
+ ), "importKey");
+ const isVerified = await subtleCryptoResult(this._subtleCrypto.verify(
+ opts,
+ hmacKey,
+ mac,
+ data,
+ ), "verify");
+ return isVerified;
+ }
+
+ async compute(key, data, hash) {
+ const opts = {
+ name: 'HMAC',
+ hash: {name: hashName(hash)},
+ };
+ const hmacKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['sign'],
+ ), "importKey");
+ const buffer = await subtleCryptoResult(this._subtleCrypto.sign(
+ opts,
+ hmacKey,
+ data,
+ ), "sign");
+ return new Uint8Array(buffer);
+ }
+}
+
+class CryptoDeriveDriver {
+ constructor(subtleCrypto, cryptoDriver, cryptoExtras) {
+ this._subtleCrypto = subtleCrypto;
+ this._cryptoDriver = cryptoDriver;
+ this._cryptoExtras = cryptoExtras;
+ }
+ /**
+ * [pbkdf2 description]
+ * @param {BufferSource} password
+ * @param {Number} iterations
+ * @param {BufferSource} salt
+ * @param {HashName} hash
+ * @param {Number} length the desired length of the generated key, in bits (not bytes!)
+ * @return {BufferSource}
+ */
+ async pbkdf2(password, iterations, salt, hash, length) {
+ if (!this._subtleCrypto.deriveBits) {
+ throw new Error("PBKDF2 is not supported");
+ }
+ const key = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ password,
+ {name: 'PBKDF2'},
+ false,
+ ['deriveBits'],
+ ), "importKey");
+ const keybits = await subtleCryptoResult(this._subtleCrypto.deriveBits(
+ {
+ name: 'PBKDF2',
+ salt,
+ iterations,
+ hash: hashName(hash),
+ },
+ key,
+ length,
+ ), "deriveBits");
+ return new Uint8Array(keybits);
+ }
+
+ /**
+ * [hkdf description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} salt [description]
+ * @param {BufferSource} info [description]
+ * @param {HashName} hash the hash to use
+ * @param {Number} length desired length of the generated key in bits (not bytes!)
+ * @return {[type]} [description]
+ */
+ async hkdf(key, salt, info, hash, length) {
+ if (!this._subtleCrypto.deriveBits) {
+ return this._cryptoExtras.hkdf(this._cryptoDriver, key, salt, info, hash, length);
+ }
+ const hkdfkey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ {name: "HKDF"},
+ false,
+ ["deriveBits"],
+ ), "importKey");
+ const keybits = await subtleCryptoResult(this._subtleCrypto.deriveBits({
+ name: "HKDF",
+ salt,
+ info,
+ hash: hashName(hash),
+ },
+ hkdfkey,
+ length,
+ ), "deriveBits");
+ return new Uint8Array(keybits);
+ }
+}
+
+class CryptoAESDriver {
+ constructor(subtleCrypto) {
+ this._subtleCrypto = subtleCrypto;
+ }
+ /**
+ * [decrypt description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} iv [description]
+ * @param {BufferSource} ciphertext [description]
+ * @return {BufferSource} [description]
+ */
+ async decrypt(key, iv, ciphertext) {
+ const opts = {
+ name: "AES-CTR",
+ counter: iv,
+ length: 64,
+ };
+ let aesKey;
+ try {
+ aesKey = await subtleCryptoResult(this._subtleCrypto.importKey(
+ 'raw',
+ key,
+ opts,
+ false,
+ ['decrypt'],
+ ), "importKey");
+ } catch (err) {
+ throw new Error(`Could not import key for AES-CTR decryption: ${err.message}`);
+ }
+ try {
+ const plaintext = await subtleCryptoResult(this._subtleCrypto.decrypt(
+ // see https://developer.mozilla.org/en-US/docs/Web/API/AesCtrParams
+ opts,
+ aesKey,
+ ciphertext,
+ ), "decrypt");
+ return new Uint8Array(plaintext);
+ } catch (err) {
+ throw new Error(`Could not decrypt with AES-CTR: ${err.message}`);
+ }
+ }
+}
+
+
+class CryptoLegacyAESDriver {
+ constructor(aesjs) {
+ this._aesjs = aesjs;
+ }
+ /**
+ * [decrypt description]
+ * @param {BufferSource} key [description]
+ * @param {BufferSource} iv [description]
+ * @param {BufferSource} ciphertext [description]
+ * @return {BufferSource} [description]
+ */
+ async decrypt(key, iv, ciphertext) {
+ const aesjs = this._aesjs;
+ var aesCtr = new aesjs.ModeOfOperation.ctr(new Uint8Array(key), new aesjs.Counter(new Uint8Array(iv)));
+ return aesCtr.decrypt(new Uint8Array(ciphertext));
+ }
+}
+
+function hashName(name) {
+ if (name !== "SHA-256" && name !== "SHA-512") {
+ throw new Error(`Invalid hash name: ${name}`);
+ }
+ return name;
+}
+
+export class CryptoDriver {
+ constructor(cryptoExtras) {
+ const crypto = window.crypto || window.msCrypto;
+ const subtleCrypto = crypto.subtle || crypto.webkitSubtle;
+ this._subtleCrypto = subtleCrypto;
+ // not exactly guaranteeing AES-CTR support
+ // but in practice IE11 doesn't have this
+ if (!subtleCrypto.deriveBits && cryptoExtras.aesjs) {
+ this.aes = new CryptoLegacyAESDriver(cryptoExtras.aesjs);
+ } else {
+ this.aes = new CryptoAESDriver(subtleCrypto);
+ }
+ this.hmac = new CryptoHMACDriver(subtleCrypto);
+ this.derive = new CryptoDeriveDriver(subtleCrypto, this, cryptoExtras);
+ }
+
+ /**
+ * [digest description]
+ * @param {HashName} hash
+ * @param {BufferSource} data
+ * @return {BufferSource}
+ */
+ async digest(hash, data) {
+ return await subtleCryptoResult(this._subtleCrypto.digest(hashName(hash), data));
+ }
+
+ digestSize(hash) {
+ switch (hashName(hash)) {
+ case "SHA-512": return 64;
+ case "SHA-256": return 32;
+ default: throw new Error(`Not implemented for ${hashName(hash)}`);
+ }
+ }
+}
diff --git a/src/ui/web/session/SessionStatusView.js b/src/ui/web/session/SessionStatusView.js
index 648ef232..0a4fea0e 100644
--- a/src/ui/web/session/SessionStatusView.js
+++ b/src/ui/web/session/SessionStatusView.js
@@ -26,6 +26,7 @@ export class SessionStatusView extends TemplateView {
spinner(t, {hidden: vm => !vm.isWaiting}),
t.p(vm => vm.statusLabel),
t.if(vm => vm.isConnectNowShown, t.createTemplate(t => t.button({onClick: () => vm.connectNow()}, "Retry now"))),
+ t.if(vm => vm.isSecretStorageShown, t.createTemplate(t => t.button({onClick: () => vm.enterPassphrase(prompt("Security key"))}, "Enter security key"))),
window.DEBUG ? t.button({id: "showlogs"}, "Show logs") : ""
]);
}
diff --git a/src/utils/crypto/hkdf.js b/src/utils/crypto/hkdf.js
new file mode 100644
index 00000000..d46dc496
--- /dev/null
+++ b/src/utils/crypto/hkdf.js
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2018 Jun Kurihara
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * MIT LICENSE, See https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-hkdf/LICENSE
+ * Based on https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-hkdf/src/hkdf.ts
+ */
+
+// forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible
+export async function hkdf(cryptoDriver, key, salt, info, hash, length) {
+ length = length / 8;
+ const len = cryptoDriver.digestSize(hash);
+
+ // RFC5869 Step 1 (Extract)
+ const prk = await cryptoDriver.hmac.compute(salt, key, hash);
+
+ // RFC5869 Step 2 (Expand)
+ let t = new Uint8Array([]);
+ const okm = new Uint8Array(Math.ceil(length / len) * len);
+ for(let i = 0; i < Math.ceil(length / len); i++){
+ const concat = new Uint8Array(t.length + info.length + 1);
+ concat.set(t);
+ concat.set(info, t.length);
+ concat.set(new Uint8Array([i+1]), t.length + info.length);
+ t = await cryptoDriver.hmac.compute(prk, concat, hash);
+ okm.set(t, len * i);
+ }
+ return okm.slice(0, length);
+}
diff --git a/src/utils/crypto/pbkdf2.js b/src/utils/crypto/pbkdf2.js
new file mode 100644
index 00000000..5a239791
--- /dev/null
+++ b/src/utils/crypto/pbkdf2.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2018 Jun Kurihara
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * MIT LICENSE, See https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-pbkdf/LICENSE
+ * Based on https://github.com/junkurihara/jscu/blob/develop/packages/js-crypto-pbkdf/src/pbkdf.ts
+ */
+
+// not used atm, but might in the future
+// forked this code to make it use the cryptoDriver for HMAC that is more backwards-compatible
+
+
+const nwbo = (num, len) => {
+ const arr = new Uint8Array(len);
+ for(let i=0; i> ((len - i - 1)*8));
+ return arr;
+};
+
+export async function pbkdf2(cryptoDriver, password, iterations, salt, hash, length) {
+ const dkLen = length / 8;
+ if (iterations <= 0) {
+ throw new Error('InvalidIterationCount');
+ }
+ if (dkLen <= 0) {
+ throw new Error('InvalidDerivedKeyLength');
+ }
+ const hLen = cryptoDriver.digestSize(hash);
+ if(dkLen > (Math.pow(2, 32) - 1) * hLen) throw new Error('DerivedKeyTooLong');
+
+ const l = Math.ceil(dkLen/hLen);
+ const r = dkLen - (l-1)*hLen;
+
+ const funcF = async (i) => {
+ const seed = new Uint8Array(salt.length + 4);
+ seed.set(salt);
+ seed.set(nwbo(i+1, 4), salt.length);
+ let u = await cryptoDriver.hmac.compute(password, seed, hash);
+ let outputF = new Uint8Array(u);
+ for(let j = 1; j < iterations; j++){
+ if ((j % 1000) === 0) {
+ console.log(j, j/iterations);
+ }
+ u = await cryptoDriver.hmac.compute(password, u, hash);
+ outputF = u.map( (elem, idx) => elem ^ outputF[idx]);
+ }
+ return {index: i, value: outputF};
+ };
+
+ const Tis = [];
+ const DK = new Uint8Array(dkLen);
+ for(let i = 0; i < l; i++) {
+ Tis.push(funcF(i));
+ }
+ const TisResolved = await Promise.all(Tis);
+ TisResolved.forEach(elem => {
+ if (elem.index !== l - 1) {
+ DK.set(elem.value, elem.index*hLen);
+ }
+ else {
+ DK.set(elem.value.slice(0, r), elem.index*hLen);
+ }
+ });
+
+ return DK;
+}
diff --git a/src/utils/enum.js b/src/utils/enum.js
index 733a548b..4defcfd7 100644
--- a/src/utils/enum.js
+++ b/src/utils/enum.js
@@ -17,6 +17,9 @@ limitations under the License.
export function createEnum(...values) {
const obj = {};
for (const value of values) {
+ if (typeof value !== "string") {
+ throw new Error("Invalid enum value name" + value?.toString());
+ }
obj[value] = value;
}
return Object.freeze(obj);
diff --git a/src/worker-polyfill.js b/src/worker-polyfill.js
index 15b955d5..7d9f9521 100644
--- a/src/worker-polyfill.js
+++ b/src/worker-polyfill.js
@@ -21,3 +21,30 @@ import "regenerator-runtime/runtime";
import "core-js/modules/es.promise";
import "core-js/modules/es.math.imul";
import "core-js/modules/es.math.clz32";
+
+import "core-js/modules/es.typed-array.from";
+import "core-js/modules/es.typed-array.of";
+import "core-js/modules/es.typed-array.copy-within";
+import "core-js/modules/es.typed-array.every";
+import "core-js/modules/es.typed-array.fill";
+import "core-js/modules/es.typed-array.filter";
+import "core-js/modules/es.typed-array.find";
+import "core-js/modules/es.typed-array.find-index";
+import "core-js/modules/es.typed-array.for-each";
+import "core-js/modules/es.typed-array.includes";
+import "core-js/modules/es.typed-array.index-of";
+import "core-js/modules/es.typed-array.join";
+import "core-js/modules/es.typed-array.last-index-of";
+import "core-js/modules/es.typed-array.map";
+import "core-js/modules/es.typed-array.reduce";
+import "core-js/modules/es.typed-array.reduce-right";
+import "core-js/modules/es.typed-array.reverse";
+import "core-js/modules/es.typed-array.set";
+import "core-js/modules/es.typed-array.slice";
+import "core-js/modules/es.typed-array.some";
+import "core-js/modules/es.typed-array.sort";
+import "core-js/modules/es.typed-array.subarray";
+import "core-js/modules/es.typed-array.to-locale-string";
+import "core-js/modules/es.typed-array.to-string";
+import "core-js/modules/es.typed-array.iterator";
+import "core-js/modules/es.object.to-string";
diff --git a/yarn.lock b/yarn.lock
index d1bf27a2..f1b9a4be 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -821,7 +821,16 @@
globals "^11.1.0"
lodash "^4.17.19"
-"@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.4.4":
+"@babel/types@^7.10.4":
+ version "7.11.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
+ integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.10.4"
+ lodash "^4.17.19"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.4.4":
version "7.11.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d"
integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==
@@ -831,9 +840,9 @@
to-fast-properties "^2.0.0"
"@rollup/plugin-babel@^5.1.0":
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.2.0.tgz#b87556d61ed108b4eaf9d18b5323965adf8d9bee"
- integrity sha512-CPABsajaKjINgBQ3it+yMnfVO3ibsrMBxRzbUOUw2cL1hsZJ7aogU8mgglQm3S2hHJgjnAmxPz0Rq7DVdmHsTw==
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.2.1.tgz#20fc8f8864dc0eaa1c5578408459606808f72924"
+ integrity sha512-Jd7oqFR2dzZJ3NWANDyBjwTtX/lYbZpVcmkHrfQcpvawHs9E4c0nYk5U2mfZ6I/DZcIvy506KZJi54XK/jxH7A==
dependencies:
"@babel/helper-module-imports" "^7.10.4"
"@rollup/pluginutils" "^3.1.0"
@@ -886,9 +895,9 @@
picomatch "^2.2.2"
"@types/estree@*":
- version "0.0.42"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.42.tgz#8d0c1f480339efedb3e46070e22dd63e0430dd11"
- integrity sha512-K1DPVvnBCPxzD+G51/cxVIoc2X8uUVl1zpJeE6iKcgHMj4+tbat5Xu4TjV7v2QSDbIeAfLi2hIk+u2+s0MlpUQ==
+ version "0.0.45"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884"
+ integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==
"@types/estree@0.0.39":
version "0.0.39"
@@ -896,9 +905,9 @@
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
"@types/node@*":
- version "13.9.1"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.1.tgz#96f606f8cd67fb018847d9b61e93997dabdefc72"
- integrity sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==
+ version "14.10.3"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.10.3.tgz#5ae1f119c96643fc9b19b2d1a83bfa2ec3dbb7ea"
+ integrity sha512-zdN0hor7TLkjAdKTnYW+Y22oIhUUpil5ZD1V1OFq0CR0CLKw+NdR6dkziTfkWRLo6sKzisayoj/GNpNbe4LY9Q==
"@types/resolve@1.17.1":
version "1.17.1"
@@ -907,6 +916,11 @@
dependencies:
"@types/node" "*"
+aes-js@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.1.2.tgz#db9aabde85d5caabbfc0d4f2a4446960f627146a"
+ integrity sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==
+
another-json@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc"
@@ -931,6 +945,18 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base-x@^3.0.2:
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
+ integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
+ dependencies:
+ safe-buffer "^5.0.1"
+
+base64-arraybuffer@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45"
+ integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==
+
boolbase@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@@ -954,6 +980,13 @@ browserslist@^4.12.0, browserslist@^4.8.5:
escalade "^3.0.2"
node-releases "^1.1.60"
+bs58@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
+ integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
+ dependencies:
+ base-x "^3.0.2"
+
builtin-modules@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
@@ -1720,12 +1753,17 @@ rollup-pluginutils@^2.3.3:
estree-walker "^0.6.1"
rollup@^2.26.4:
- version "2.26.4"
- resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.4.tgz#a8350fd6bd56fce9873a7db2bd9547d40de3992b"
- integrity sha512-6+qsGuP0MXGd7vlYmk72utm1MrgZj5GfXibGL+cRkKQ9+ZL/BnFThDl0D5bcl7AqlzMjAQXRAwZX1HVm22M/4Q==
+ version "2.27.1"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.27.1.tgz#372744e1d36eba0fd942d997600c2fc2ca266305"
+ integrity sha512-GiWHQvnmMgBktSpY/1+nrGpwPsTw4b9P28og2uedfeq4JZ16rzAmnQ5Pm/E0/BEmDNia1ZbY7+qu3nBgNa19Hg==
optionalDependencies:
fsevents "~2.1.2"
+safe-buffer@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
@@ -1826,6 +1864,11 @@ supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
+text-encoding@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643"
+ integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"