From e323d917a418ab0f9e117b691c673cb3984d8a26 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Mar 2019 14:06:28 -0600 Subject: [PATCH 01/67] Support default push rules for when servers are outdated --- src/pushprocessor.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/pushprocessor.js b/src/pushprocessor.js index f7ee4721f..b5a50121d 100644 --- a/src/pushprocessor.js +++ b/src/pushprocessor.js @@ -23,6 +23,12 @@ import {escapeRegExp, globToRegexp} from "./utils"; const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride']; +// The default override rules to apply when calculating actions for an event. These +// defaults apply under no other circumstances to avoid confusing the client with server +// state. +const DEFAULT_OVERRIDE_RULES = [ +]; + /** * Construct a Push Processor. * @constructor @@ -312,6 +318,26 @@ function PushProcessor(client) { return actionObj; }; + const applyRuleDefaults = function(clientRuleset) { + // Deep clone the object before we mutate it + const ruleset = JSON.parse(JSON.stringify(clientRuleset)); + + if (!clientRuleset['global']) clientRuleset['global'] = {}; + if (!clientRuleset['global']['override']) clientRuleset['global']['override'] = []; + + // Apply default overrides + const globalOverrides = clientRuleset['global']['override']; + for (const override of DEFAULT_OVERRIDE_RULES) { + const existingRule = globalOverrides.find((r) => r.rule_id === override.rule_id); + if (!existingRule) { + console.warn(`Adding global override for ${override.rule_id} because is is missing`); + globalOverrides.push(override); + } + } + + return ruleset; + }; + this.ruleMatchesEvent = function(rule, ev) { let ret = true; for (let i = 0; i < rule.conditions.length; ++i) { @@ -331,7 +357,8 @@ function PushProcessor(client) { * @return {PushAction} */ this.actionsForEvent = function(ev) { - return pushActionsForEventAndRulesets(ev, client.pushRules); + const rules = applyRuleDefaults(client.pushRules); + return pushActionsForEventAndRulesets(ev, rules); }; /** From b3d2d39b60e8e1b3820fc06cc0a20d6fb4470705 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Mar 2019 14:07:15 -0600 Subject: [PATCH 02/67] Add tombstone rule as a default rule in support of MSC1930 Part of https://github.com/vector-im/riot-web/issues/8447 See also https://github.com/matrix-org/matrix-doc/pull/1930 --- src/pushprocessor.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/pushprocessor.js b/src/pushprocessor.js index b5a50121d..675db1895 100644 --- a/src/pushprocessor.js +++ b/src/pushprocessor.js @@ -27,6 +27,26 @@ const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride' // defaults apply under no other circumstances to avoid confusing the client with server // state. const DEFAULT_OVERRIDE_RULES = [ + { + // For homeservers which don't support MSC1930 yet + rule_id: ".m.rule.tombstone", + default: true, + enabled: true, + conditions: [ + { + kind: "event_match", + key: "type", + pattern: "m.room.tombstone", + }, + ], + actions: [ + "notify", + { + set_tweak: "highlight", + value: true, + }, + ], + }, ]; /** From 42f181cc7bad956a19f2d7d23054543d57351b7d Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 15 Mar 2019 14:11:29 -0600 Subject: [PATCH 03/67] Appease the linter --- src/pushprocessor.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pushprocessor.js b/src/pushprocessor.js index 675db1895..5bb6699d9 100644 --- a/src/pushprocessor.js +++ b/src/pushprocessor.js @@ -342,15 +342,22 @@ function PushProcessor(client) { // Deep clone the object before we mutate it const ruleset = JSON.parse(JSON.stringify(clientRuleset)); - if (!clientRuleset['global']) clientRuleset['global'] = {}; - if (!clientRuleset['global']['override']) clientRuleset['global']['override'] = []; + if (!clientRuleset['global']) { + clientRuleset['global'] = {}; + } + if (!clientRuleset['global']['override']) { + clientRuleset['global']['override'] = []; + } // Apply default overrides const globalOverrides = clientRuleset['global']['override']; for (const override of DEFAULT_OVERRIDE_RULES) { - const existingRule = globalOverrides.find((r) => r.rule_id === override.rule_id); + const existingRule = globalOverrides + .find((r) => r.rule_id === override.rule_id); + if (!existingRule) { - console.warn(`Adding global override for ${override.rule_id} because is is missing`); + const ruleId = override.rule_id; + console.warn(`Adding default global override for ${ruleId}`); globalOverrides.push(override); } } From f5cbdeac8f429a88ba36b358579c8996c578c546 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Apr 2019 18:06:04 +0100 Subject: [PATCH 04/67] Trigger react-sdk build in buildkite pipeline --- .buildkite/pipeline.yaml | 10 ++++++++++ release.sh | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index df61d7678..1bbeae7bb 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -22,3 +22,13 @@ steps: plugins: - docker#v3.0.1: image: "node:10" + + - wait + + - label: "🐴 Trigger matrix-react-sdk" + trigger: "matrix-react" + branches: "develop" + build: + branch: "develop" + message: "[js-sdk] ${BUILDKITE_MESSAGE}" + async: true diff --git a/release.sh b/release.sh index 3ff018c67..27c59949f 100755 --- a/release.sh +++ b/release.sh @@ -172,10 +172,10 @@ git commit package.json $pkglock -m "$tag" # figure out if we should be signing this release -signing_id= -if [ -f release_config.yaml ]; then - signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"` -fi +signing_id=releases@riot.im +#if [ -f release_config.yaml ]; then + #signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"` +#fi # If there is a 'dist' script in the package.json, From bb4f5a3fa1b7bfdc36948da48a742d7fb37a4453 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 26 Apr 2019 18:07:04 +0100 Subject: [PATCH 05/67] Get the name of the pipeline right --- .buildkite/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 1bbeae7bb..4909aabde 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -26,7 +26,7 @@ steps: - wait - label: "🐴 Trigger matrix-react-sdk" - trigger: "matrix-react" + trigger: "matrix-react-sdk" branches: "develop" build: branch: "develop" From 103d8114412ae33153b27d9402fe3b751867fc47 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 11:43:34 +0100 Subject: [PATCH 06/67] Prepare changelog for v1.1.0-rc.1 --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e09a9281c..4032ee941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,59 @@ +Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.4...v1.1.0-rc.1) + + * use the release version of olm 3.1.0 + [\#903](https://github.com/matrix-org/matrix-js-sdk/pull/903) + * Use new Olm repo link in README + [\#901](https://github.com/matrix-org/matrix-js-sdk/pull/901) + * Support being fed a .well-known config object for validation + [\#897](https://github.com/matrix-org/matrix-js-sdk/pull/897) + * emit self-membership event at end of handling sync update + [\#900](https://github.com/matrix-org/matrix-js-sdk/pull/900) + * Use packages.matrix.org for Olm + [\#898](https://github.com/matrix-org/matrix-js-sdk/pull/898) + * Fix tests on develop + [\#899](https://github.com/matrix-org/matrix-js-sdk/pull/899) + * Stop syncing when the token is invalid + [\#895](https://github.com/matrix-org/matrix-js-sdk/pull/895) + * change event redact, POST request to PUT request + [\#887](https://github.com/matrix-org/matrix-js-sdk/pull/887) + * Expose better autodiscovery error messages + [\#894](https://github.com/matrix-org/matrix-js-sdk/pull/894) + * Explicitly guard store usage during sync startup + [\#892](https://github.com/matrix-org/matrix-js-sdk/pull/892) + * Flag v3 rooms as safe + [\#893](https://github.com/matrix-org/matrix-js-sdk/pull/893) + * Cache failed capabilities lookups for shorter amounts of time + [\#890](https://github.com/matrix-org/matrix-js-sdk/pull/890) + * Fix highlight notifications for unencrypted rooms + [\#891](https://github.com/matrix-org/matrix-js-sdk/pull/891) + * Document checking crypto state before using `hasUnverifiedDevices` + [\#889](https://github.com/matrix-org/matrix-js-sdk/pull/889) + * Add logging to sync startup path + [\#888](https://github.com/matrix-org/matrix-js-sdk/pull/888) + * Track e2e highlights better, particularly in 'Mentions Only' rooms + [\#886](https://github.com/matrix-org/matrix-js-sdk/pull/886) + * support both the incorrect and correct MAC methods + [\#882](https://github.com/matrix-org/matrix-js-sdk/pull/882) + * Refuse to set forwards pagination token on live timeline + [\#885](https://github.com/matrix-org/matrix-js-sdk/pull/885) + * Degrade `IndexedDBStore` back to memory only on failure + [\#884](https://github.com/matrix-org/matrix-js-sdk/pull/884) + * Refuse to link live timelines into the forwards/backwards position when + either is invalid + [\#877](https://github.com/matrix-org/matrix-js-sdk/pull/877) + * Key backup logging improvements + [\#883](https://github.com/matrix-org/matrix-js-sdk/pull/883) + * Don't assume aborts are always from txn.abort() + [\#880](https://github.com/matrix-org/matrix-js-sdk/pull/880) + * Add a bunch of logging + [\#878](https://github.com/matrix-org/matrix-js-sdk/pull/878) + * Refuse splicing the live timeline into a broken position + [\#873](https://github.com/matrix-org/matrix-js-sdk/pull/873) + * Add existence check to local storage based crypto store + [\#872](https://github.com/matrix-org/matrix-js-sdk/pull/872) + Changes in [1.0.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.4) (2019-04-08) ================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.3...v1.0.4) From e9b95f85676f9a8a2d8901210f479f83643ca999 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 11:43:35 +0100 Subject: [PATCH 07/67] v1.1.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d5f47bb7..36c11f78e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "1.0.4", + "version": "1.1.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From 821f1c876b8cf4569559c802cd274fe09411fe30 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 30 Apr 2019 11:48:38 +0100 Subject: [PATCH 08/67] Undo unintentional commenting --- release.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/release.sh b/release.sh index 27c59949f..3ff018c67 100755 --- a/release.sh +++ b/release.sh @@ -172,10 +172,10 @@ git commit package.json $pkglock -m "$tag" # figure out if we should be signing this release -signing_id=releases@riot.im -#if [ -f release_config.yaml ]; then - #signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"` -#fi +signing_id= +if [ -f release_config.yaml ]; then + signing_id=`cat release_config.yaml | python -c "import yaml; import sys; print yaml.load(sys.stdin)['signing_id']"` +fi # If there is a 'dist' script in the package.json, From cdb78e4c7514ccb6eb053b4782edb29a6f6ebedd Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 30 Apr 2019 15:43:52 +0100 Subject: [PATCH 09/67] Remove noisy debug logs The debug logs in the sync loop haven't been helpful so far, and they are quite noisy pushing other logs out of the way, so this change removes them. --- src/sync.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sync.js b/src/sync.js index 46f9e2a5d..c863af955 100644 --- a/src/sync.js +++ b/src/sync.js @@ -711,7 +711,6 @@ SyncApi.prototype._syncFromCache = async function(savedSync) { * @param {boolean} syncOptions.hasSyncedBefore */ SyncApi.prototype._sync = async function(syncOptions) { - debuglog("Starting sync request processing..."); const client = this.client; if (!this._running) { @@ -750,9 +749,7 @@ SyncApi.prototype._sync = async function(syncOptions) { // Reset after a successful sync this._failedSyncCount = 0; - debuglog("Storing sync data..."); await client.store.setSyncData(data); - debuglog("Sync data stored"); const syncEventData = { oldSyncToken: syncToken, @@ -767,7 +764,6 @@ SyncApi.prototype._sync = async function(syncOptions) { } try { - debuglog("Processing sync response..."); await this._processSyncResponse(syncEventData, data); } catch(e) { // log the exception with stack if we have it, else fall back From 116cf31199c03cf24705088a2e3dd8886d52a398 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 10:50:23 +0100 Subject: [PATCH 10/67] yarn upgrade --- yarn.lock | 728 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 373 insertions(+), 355 deletions(-) diff --git a/yarn.lock b/yarn.lock index ee96227cc..09bbec87d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9,14 +9,14 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/generator@^7.2.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.2.tgz#fff31a7b2f2f3dad23ef8e01be45b0d5c2fc0132" - integrity sha512-f3QCuPppXxtZOEm5GWPra/uYUjmNQlu9pbAD8D/9jze4pTY83rTtB1igTBSwvkeNlC5gR24zFFkz+2WHLFQhqQ== +"@babel/generator@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041" + integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ== dependencies: - "@babel/types" "^7.3.2" + "@babel/types" "^7.4.4" jsesc "^2.5.1" - lodash "^4.17.10" + lodash "^4.17.11" source-map "^0.5.0" trim-right "^1.0.1" @@ -36,12 +36,12 @@ dependencies: "@babel/types" "^7.0.0" -"@babel/helper-split-export-declaration@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" - integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== +"@babel/helper-split-export-declaration@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" + integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.4.4" "@babel/highlight@^7.0.0": version "7.0.0" @@ -52,42 +52,42 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.2.tgz#95cdeddfc3992a6ca2a1315191c1679ca32c55cd" - integrity sha512-QzNUC2RO1gadg+fs21fi0Uu0OuGNzRKEmgCxoLNzbCdoprLwjfmZwzUrpUNfJPaVRwBpDY47A17yYEGWyRelnQ== +"@babel/parser@^7.0.0", "@babel/parser@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.4.tgz#5977129431b8fe33471730d255ce8654ae1250b6" + integrity sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w== "@babel/template@^7.1.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" - integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" + integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.2.2" - "@babel/types" "^7.2.2" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" "@babel/traverse@^7.0.0": - version "7.2.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8" - integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw== + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.4.tgz#0776f038f6d78361860b6823887d4f3937133fe8" + integrity sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A== dependencies: "@babel/code-frame" "^7.0.0" - "@babel/generator" "^7.2.2" + "@babel/generator" "^7.4.4" "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.0.0" - "@babel/parser" "^7.2.3" - "@babel/types" "^7.2.2" + "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/parser" "^7.4.4" + "@babel/types" "^7.4.4" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.10" + lodash "^4.17.11" -"@babel/types@^7.0.0", "@babel/types@^7.2.2", "@babel/types@^7.3.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.2.tgz#424f5be4be633fff33fb83ab8d67e4a8290f5a2f" - integrity sha512-3Y6H8xlUlpbGR+XvawiH0UXehqydTmNmEpozWcXymqwcrwYAl5KMvKtQ+TF6f6E08V6Jur7v/ykdDSF+WDEIXQ== +"@babel/types@^7.0.0", "@babel/types@^7.4.4": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" + integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== dependencies: esutils "^2.0.2" - lodash "^4.17.10" + lodash "^4.17.11" to-fast-properties "^2.0.0" JSONStream@^1.0.3: @@ -98,7 +98,12 @@ JSONStream@^1.0.3: jsonparse "^1.2.0" through ">=2.2.7 <3" -abbrev@1, abbrev@1.0.x: +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abbrev@1.0.x: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= @@ -122,7 +127,7 @@ acorn-jsx@^5.0.0: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== -acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2: +acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: version "1.6.2" resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.6.2.tgz#b7d7ceca6f22e6417af933a62cad4de01048d5d2" integrity sha512-rIhNEZuNI8ibQcL7ANm/mGyPukIaZsRNX9psFNQURyJW0nu6k8wjSDld20z6v2mDBWqX13pIEnk9gGZJHIlEXg== @@ -142,25 +147,15 @@ acorn@^5.2.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== -acorn@^6.0.2: - version "6.0.4" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754" - integrity sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg== +acorn@^6.0.2, acorn@^6.0.7: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== -ajv@^6.5.3, ajv@^6.5.5: - version "6.5.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1" - integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^6.6.1: - version "6.6.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.2.tgz#caceccf474bf3fc3ce3b147443711a24063cc30d" - integrity sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g== +ajv@^6.5.5, ajv@^6.9.1: + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -186,10 +181,10 @@ another-json@^0.2.0: resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc" integrity sha1-tfQBnJc7bdXGUGotk0acttMq7tw= -ansi-escapes@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" - integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== ansi-regex@^2.0.0: version "2.1.1" @@ -201,10 +196,10 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" - integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== ansi-styles@^2.2.1: version "2.2.1" @@ -245,9 +240,9 @@ aproba@^1.0.3: integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" - integrity sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0= + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: delegates "^1.0.0" readable-stream "^2.0.6" @@ -345,22 +340,15 @@ astral-regex@^1.0.0: integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== async-each@^1.0.0, async-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== async@1.x: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.5.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" - integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== - dependencies: - lodash "^4.17.11" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -904,13 +892,20 @@ balanced-match@~0.2.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" integrity sha1-e8ZYtL7WHu5CStdPdfXD4sTfPMc= -base-x@3.0.4, base-x@^3.0.2: +base-x@3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77" integrity sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA== dependencies: safe-buffer "^5.0.1" +base-x@^3.0.2: + version "3.0.5" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.5.tgz#d3ada59afed05b921ab581ec3112e6444ba0795a" + integrity sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA== + dependencies: + safe-buffer "^5.0.1" + base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -937,14 +932,14 @@ bcrypt-pbkdf@^1.0.0: tweetnacl "^0.14.3" binary-extensions@^1.0.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" - integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== bluebird@^3.5.0, bluebird@~3.5.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" - integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + version "3.5.4" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714" + integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -1193,9 +1188,9 @@ cached-path-relative@^1.0.0: integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== callsites@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" - integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^1.0.2: version "1.2.1" @@ -1233,7 +1228,7 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0: +chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1264,9 +1259,9 @@ chokidar@^1.6.1: fsevents "^1.0.0" chokidar@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" - integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== + version "2.1.5" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" + integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== dependencies: anymatch "^2.0.0" async-each "^1.0.1" @@ -1278,7 +1273,7 @@ chokidar@^2.1.1: normalize-path "^3.0.0" path-is-absolute "^1.0.0" readdirp "^2.2.1" - upath "^1.1.0" + upath "^1.1.1" optionalDependencies: fsevents "^1.2.7" @@ -1295,11 +1290,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -1378,20 +1368,15 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@^2.11.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== - -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== +commander@^2.11.0, commander@~2.20.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== concat-map@0.0.1: version "0.0.1" @@ -1448,9 +1433,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js@^2.4.0, core-js@^2.5.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" - integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== + version "2.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" + integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1516,6 +1501,11 @@ crypto-browserify@^3.0.0: randombytes "^2.0.0" randomfill "^1.0.3" +dash-ast@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" + integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1535,7 +1525,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1569,7 +1559,7 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -define-properties@^1.1.2, define-properties@~1.1.2: +define-properties@^1.1.3, define-properties@~1.1.2: 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== @@ -1652,11 +1642,11 @@ detective@^4.5.0: defined "^1.0.0" detective@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.1.0.tgz#7a20d89236d7b331ccea65832e7123b5551bb7cb" - integrity sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ== + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== dependencies: - acorn-node "^1.3.0" + acorn-node "^1.6.1" defined "^1.0.0" minimist "^1.1.1" @@ -1687,10 +1677,10 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" @@ -1732,18 +1722,24 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -es-abstract@^1.6.1: - version "1.12.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" - integrity sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA== - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -es-to-primitive@^1.1.1: +es-abstract@^1.12.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== @@ -1793,10 +1789,10 @@ eslint-scope@3.7.1: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -1812,54 +1808,53 @@ eslint-visitor-keys@^1.0.0: integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== eslint@^5.12.0: - version "5.12.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.12.0.tgz#fab3b908f60c52671fb14e996a450b96c743c859" - integrity sha512-LntwyPxtOHrsJdcSwyQKVtHofPHdv+4+mFwEe91r2V13vqpM8yLr7b1sW+Oo/yheOPkWYsYlYJCkzlFAt8KV7g== + version "5.16.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" + integrity sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg== dependencies: "@babel/code-frame" "^7.0.0" - ajv "^6.5.3" + ajv "^6.9.1" chalk "^2.1.0" cross-spawn "^6.0.5" debug "^4.0.1" - doctrine "^2.1.0" - eslint-scope "^4.0.0" + doctrine "^3.0.0" + eslint-scope "^4.0.3" eslint-utils "^1.3.1" eslint-visitor-keys "^1.0.0" - espree "^5.0.0" + espree "^5.0.1" esquery "^1.0.1" esutils "^2.0.2" - file-entry-cache "^2.0.0" + file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob "^7.1.2" globals "^11.7.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.1.0" - js-yaml "^3.12.0" + inquirer "^6.2.2" + js-yaml "^3.13.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.3.0" - lodash "^4.17.5" + lodash "^4.17.11" minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" optionator "^0.8.2" path-is-inside "^1.0.2" - pluralize "^7.0.0" progress "^2.0.0" regexpp "^2.0.1" semver "^5.5.1" strip-ansi "^4.0.0" strip-json-comments "^2.0.1" - table "^5.0.2" + table "^5.2.3" text-table "^0.2.0" -espree@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c" - integrity sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA== +espree@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" + integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A== dependencies: - acorn "^6.0.2" + acorn "^6.0.7" acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" @@ -2015,7 +2010,7 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.0: +external-editor@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== @@ -2045,11 +2040,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extsprintf@1.3.0, extsprintf@^1.2.0: +extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -2072,13 +2072,12 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - integrity sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" + flat-cache "^2.0.1" filename-regex@^2.0.0: version "2.0.1" @@ -2111,15 +2110,19 @@ find-parent-dir@~0.3.0: resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" + integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" @@ -2171,23 +2174,15 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" - integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== +fsevents@^1.0.0, fsevents@^1.2.7: + version "1.2.9" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" + integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" + nan "^2.12.1" + node-pre-gyp "^0.12.0" -fsevents@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" - integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== - dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" - -function-bind@^1.1.0, function-bind@^1.1.1: +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== @@ -2274,7 +2269,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5, glob@^7.1.0, glob@^7.1.2: +glob@^7.1.0, glob@^7.1.2, glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -2287,9 +2282,9 @@ glob@^7.0.5, glob@^7.1.0, glob@^7.1.2: path-is-absolute "^1.0.0" globals@^11.1.0, globals@^11.7.0: - version "11.10.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.10.0.tgz#1e09776dffda5e01816b3bb4077c8b59c24eaa50" - integrity sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ== + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^9.18.0: version "9.18.0" @@ -2305,7 +2300,7 @@ globo@~1.1.0: is-defined "~1.0.0" ternary "~1.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.4, graceful-fs@^4.1.9: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -2316,11 +2311,11 @@ growl@1.10.5: integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== handlebars@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.0.tgz#0d6a6f34ff1f63cecec8423aa4169827bf787c3a" - integrity sha512-l2jRuU1NAWK6AW5qqcTATWQJvNPEwkM7NEKSiv/gqOsoSQbVoWyqVEY5GS+XPQ88zLNmqASRpzfdm8d79hJS+w== + version "4.1.2" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" + integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== dependencies: - async "^2.5.0" + neo-async "^2.6.0" optimist "^0.6.1" source-map "^0.6.1" optionalDependencies: @@ -2404,7 +2399,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.1: +has@^1.0.0, has@^1.0.1, 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== @@ -2420,9 +2415,9 @@ hash-base@^3.0.0: safe-buffer "^5.0.1" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" - integrity sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA== + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.1" @@ -2476,9 +2471,9 @@ iconv-lite@^0.4.24, iconv-lite@^0.4.4: safer-buffer ">= 2.1.2 < 3" ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" + integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== ignore-walk@^3.0.1: version "3.0.1" @@ -2535,23 +2530,23 @@ inline-source-map@~0.6.0: dependencies: source-map "~0.5.3" -inquirer@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.1.tgz#9943fc4882161bdb0b0c9276769c75b32dbfcd52" - integrity sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg== +inquirer@^6.2.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7" + integrity sha512-MmL624rfkFt4TG9y/Jvmt8vdmOo836U7Y0Hxr2aFk3RelZEGX4Igk0KabWrcaaZaTv9uzglOqWh1Vly+FAWAXA== dependencies: - ansi-escapes "^3.0.0" - chalk "^2.0.0" + ansi-escapes "^3.2.0" + chalk "^2.4.2" cli-cursor "^2.1.0" cli-width "^2.0.0" - external-editor "^3.0.0" + external-editor "^3.0.3" figures "^2.0.0" - lodash "^4.17.10" + lodash "^4.17.11" mute-stream "0.0.7" run-async "^2.2.0" - rxjs "^6.1.0" + rxjs "^6.4.0" string-width "^2.1.0" - strip-ansi "^5.0.0" + strip-ansi "^5.1.0" through "^2.3.6" insert-module-globals@^7.0.0: @@ -2752,9 +2747,9 @@ is-glob@^3.1.0: is-extglob "^2.1.0" is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" @@ -2890,20 +2885,20 @@ istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" -"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-tokens@^4.0.0: +"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== -js-yaml@3.x, js-yaml@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +js-yaml@3.x, js-yaml@^3.13.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -3063,7 +3058,7 @@ lodash.memoize@~3.0.3: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: +lodash@^4.17.11, lodash@^4.17.4: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -3113,9 +3108,9 @@ marked@~0.3.6: integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg== math-random@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" - integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= + version "1.0.4" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" + integrity sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A== matrix-mock-request@^1.2.3: version "1.2.3" @@ -3180,17 +3175,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.37.0: - version "1.37.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" - integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.21" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" - integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== dependencies: - mime-db "~1.37.0" + mime-db "1.40.0" mimic-fn@^1.0.0: version "1.2.0" @@ -3219,7 +3214,7 @@ minimist@0.0.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.5.tgz#d7aa327bcecf518f9106ac6b8f003fa3bcea8566" integrity sha1-16oye87PUY+RBqxrjwA/o7zqhWY= -minimist@0.0.8, minimist@~0.0.1: +minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= @@ -3229,6 +3224,11 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= + minipass@^2.2.1, minipass@^2.3.4: version "2.3.5" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" @@ -3337,10 +3337,10 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.9.2: - version "2.11.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" - integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== +nan@^2.12.1: + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== nanomatch@^1.2.9: version "1.2.13" @@ -3370,23 +3370,28 @@ nave@~0.5.1: integrity sha1-Ws7HI3WFblx2yDvSGmjXE+tfG6Q= needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.1.tgz#d272f2f4034afb9c4c9ab1379aabc17fc85c9388" + integrity sha512-CaLXV3W8Vnbps8ZANqDGz7j4x7Yj1LW4TWF/TQuDfj7Cfx4nAPTvw98qgTevtto1oHDrh3pQkaODbqupXlsWTg== dependencies: - debug "^2.1.2" + debug "^4.1.0" iconv-lite "^0.4.4" sax "^1.2.4" +neo-async@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== +node-pre-gyp@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" + integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -3427,14 +3432,14 @@ normalize-path@^3.0.0: integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-bundled@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" - integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== npm-packlist@^1.1.6: - version "1.2.0" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.2.0.tgz#55a60e793e272f00862c7089274439a4cc31fc7f" - integrity sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ== + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -3459,7 +3464,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -3479,9 +3484,9 @@ object-inspect@^1.1.0: integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== object-keys@^1.0.12, object-keys@^1.0.9: - version "1.0.12" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" - integrity sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag== + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object-keys@~0.4.0: version "0.4.0" @@ -3496,14 +3501,14 @@ object-visit@^1.0.0: isobject "^3.0.0" object.entries@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" - integrity sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8= + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" + integrity sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.12.0" + function-bind "^1.1.1" + has "^1.0.3" object.omit@^2.0.0: version "2.0.1" @@ -3598,14 +3603,14 @@ output-file-sync@^1.1.2: object-assign "^4.1.0" pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg== + version "1.0.10" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" + integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== parent-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" - integrity sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" @@ -3617,15 +3622,16 @@ parents@^1.0.0, parents@^1.0.1: path-platform "~0.11.15" parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - integrity sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw== + version "5.1.4" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" + integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" parse-glob@^3.0.4: version "3.0.4" @@ -3672,7 +3678,7 @@ path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-parse@^1.0.5: +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== @@ -3698,11 +3704,6 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -3739,9 +3740,9 @@ progress@^2.0.0: integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" - integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ== + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== public-encrypt@^4.0.0: version "4.0.3" @@ -3770,7 +3771,12 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@^6.5.2, qs@~6.5.2: +qs@^6.5.2: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== @@ -3795,9 +3801,9 @@ randomatic@^3.0.0: math-random "^1.0.1" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - integrity sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" @@ -4021,11 +4027,11 @@ resolve@1.1.7, resolve@1.1.x: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@^1.1.4, resolve@^1.4.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== dependencies: - path-parse "^1.0.5" + path-parse "^1.0.6" resolve@~0.6.1: version "0.6.3" @@ -4052,12 +4058,12 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@~2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== +rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: - glob "^7.0.5" + glob "^7.1.3" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" @@ -4074,23 +4080,18 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" -rxjs@^6.1.0: - version "6.3.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" - integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== +rxjs@^6.4.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.1.tgz#f7a005a9386361921b8524f38f54cbf80e5d08f4" + integrity sha512-y0j31WJc83wPu31vS1VlAFW5JGrnGC+j+TtGAa1fRQphy48+fDYiDmX8tjGloToEsMkxnouOg/1IzXGKkJnZMg== dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, 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== -safe-buffer@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -4109,9 +4110,9 @@ sax@^1.2.4: integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== semver@^5.3.0, semver@^5.5.0, semver@^5.5.1: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== set-blocking@~2.0.0: version "2.0.0" @@ -4191,10 +4192,10 @@ slash@^1.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= -slice-ansi@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.0.0.tgz#5373bdb8559b45676e8541c66916cdd6251612e7" - integrity sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ== +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== dependencies: ansi-styles "^3.2.0" astral-regex "^1.0.0" @@ -4298,9 +4299,9 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.15.2.tgz#c946d6bd9b1a39d0e8635763f5242d6ed6dcb629" - integrity sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA== + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -4321,9 +4322,9 @@ static-extend@^0.1.1: object-copy "^0.1.0" stream-browserify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - integrity sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds= + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== dependencies: inherits "~2.0.1" readable-stream "^2.0.2" @@ -4355,7 +4356,7 @@ stream-splicer@^2.0.0: inherits "^2.0.1" readable-stream "^2.0.2" -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -4364,7 +4365,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -4372,10 +4373,19 @@ string-width@^2.1.0, string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.1.1, string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== dependencies: safe-buffer "~5.1.0" @@ -4384,6 +4394,13 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -4398,12 +4415,12 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" - integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: - ansi-regex "^4.0.0" + ansi-regex "^4.1.0" strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" @@ -4450,15 +4467,15 @@ syntax-error@^1.1.1: dependencies: acorn-node "^1.2.0" -table@^5.0.2: - version "5.1.1" - resolved "https://registry.yarnpkg.com/table/-/table-5.1.1.tgz#92030192f1b7b51b6eeab23ed416862e47b70837" - integrity sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw== +table@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2" + integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ== dependencies: - ajv "^6.6.1" + ajv "^6.9.1" lodash "^4.17.11" - slice-ansi "2.0.0" - string-width "^2.1.1" + slice-ansi "^2.1.0" + string-width "^3.0.0" taffydb@2.6.2: version "2.6.2" @@ -4638,11 +4655,11 @@ uglify-js@^2.8.26: uglify-to-browserify "~1.0.0" uglify-js@^3.1.4: - version "3.4.9" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" - integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== + version "3.5.10" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.10.tgz#652bef39f86d9dbfd6674407ee05a5e2d372cf2d" + integrity sha512-/GTF0nosyPLbdJBd+AwYiZ+Hu5z8KXWnO0WCGt1BQ/u9Iamhejykqmz5o1OHJ53+VAk6xVxychonnApDjuqGsw== dependencies: - commander "~2.17.1" + commander "~2.20.0" source-map "~0.6.1" uglify-to-browserify@~1.0.0: @@ -4656,11 +4673,12 @@ umd@^3.0.0: integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== undeclared-identifiers@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.2.tgz#7d850a98887cff4bd0bf64999c014d08ed6d1acc" - integrity sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ== + version "1.1.3" + resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" + integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== dependencies: acorn-node "^1.3.0" + dash-ast "^1.0.0" get-assigned-identifiers "^1.2.0" simple-concat "^1.0.0" xtend "^4.0.1" @@ -4705,10 +4723,10 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" - integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== +upath@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" + integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== uri-js@^4.2.2: version "4.2.2" @@ -4806,11 +4824,11 @@ which@^1.1.1, which@^1.2.9: isexe "^2.0.0" wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - integrity sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w== + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: - string-width "^1.0.2" + string-width "^1.0.2 || 2" window-size@0.1.0: version "0.1.0" @@ -4837,10 +4855,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== dependencies: mkdirp "^0.5.1" From a3fba730441390c95b085442303732be0b15eab9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 11:03:29 +0100 Subject: [PATCH 11/67] Set base-x back to 3.0.4 3.0.5 exports ES6 which breaks the build. Also specifically depending on version 3.0.4 in the package.json doesn't look like it has the desired effect now (yarn just installs two separate versions) so remove that. --- package.json | 1 - yarn.lock | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/package.json b/package.json index 6d5f47bb7..25a6872f3 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "dependencies": { "another-json": "^0.2.0", "babel-runtime": "^6.26.0", - "base-x": "3.0.4", "bluebird": "^3.5.0", "browser-request": "^0.3.3", "bs58": "^4.0.1", diff --git a/yarn.lock b/yarn.lock index 09bbec87d..1db1a44d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -892,20 +892,13 @@ balanced-match@~0.2.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" integrity sha1-e8ZYtL7WHu5CStdPdfXD4sTfPMc= -base-x@3.0.4: +base-x@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77" integrity sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA== dependencies: safe-buffer "^5.0.1" -base-x@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.5.tgz#d3ada59afed05b921ab581ec3112e6444ba0795a" - integrity sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA== - dependencies: - safe-buffer "^5.0.1" - base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" From 8d0c03b4f04e8ccf4e991c6d6260e0f4cdaef3b2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 3 May 2019 11:46:11 +0100 Subject: [PATCH 12/67] Add customer resolution for base-x to fix dependency version to 3.0.4 (ie. the version that exports ES5 rather than ES6) --- package.json | 3 +++ yarn.lock | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 25a6872f3..fb08041ca 100644 --- a/package.json +++ b/package.json @@ -92,5 +92,8 @@ "transform": [ "sourceify" ] + }, + "resolutions": { + "bs58/base-x": "3.0.4" } } diff --git a/yarn.lock b/yarn.lock index 1db1a44d6..3897d3d99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -892,7 +892,7 @@ balanced-match@~0.2.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.2.1.tgz#7bc658b4bed61eee424ad74f75f5c3e2c4df3cc7" integrity sha1-e8ZYtL7WHu5CStdPdfXD4sTfPMc= -base-x@^3.0.2: +base-x@3.0.4, base-x@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77" integrity sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA== From dc946dffbc4290a1a60e52353e5e55a3bb6c4c4c Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 3 May 2019 11:19:46 -0600 Subject: [PATCH 13/67] Add some words to explain why we do things the way we do --- src/pushprocessor.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pushprocessor.js b/src/pushprocessor.js index 5bb6699d9..544fdabd1 100644 --- a/src/pushprocessor.js +++ b/src/pushprocessor.js @@ -25,7 +25,12 @@ const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride' // The default override rules to apply when calculating actions for an event. These // defaults apply under no other circumstances to avoid confusing the client with server -// state. +// state. We do this for two reasons: +// 1. Synapse is unlikely to send us the push rule in an incremental sync - see +// https://github.com/matrix-org/synapse/pull/4867#issuecomment-481446072 for +// more details. +// 2. We often want to start using push rules ahead of the server supporting them, +// and so we can put them here. const DEFAULT_OVERRIDE_RULES = [ { // For homeservers which don't support MSC1930 yet From 56316dc5d9d4c6a032f9756a6935f3ba4cdca2e8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 May 2019 15:23:32 +0100 Subject: [PATCH 14/67] Prepare changelog for v1.1.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4032ee941..5058f4d40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [1.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.1.0) (2019-05-07) +================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.1.0-rc.1...v1.1.0) + + * No Changes since rc.1 + Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30) ========================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.4...v1.1.0-rc.1) From c1956d3f052c10347cd234cc296be62d9674f014 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 May 2019 15:23:32 +0100 Subject: [PATCH 15/67] v1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36c11f78e..a727bc42c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "1.1.0-rc.1", + "version": "1.1.0", "description": "Matrix Client-Server SDK for Javascript", "main": "index.js", "scripts": { From c1c81df4dee56c11ebcca8eabf262735d222338c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 8 May 2019 17:41:06 +0100 Subject: [PATCH 16/67] Intern `rel_type` for relations In anticipation of relations being quite frequently used, we should intern strings of common fields, such as `rel_type`. --- src/models/event.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 40fc03529..3ef2cdb87 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -50,6 +50,12 @@ module.exports.EventStatus = { }; const interns = {}; +function intern(str) { + if (!interns[str]) { + interns[str] = str; + } + return interns[str]; +} /** * Construct a Matrix Event object @@ -87,20 +93,25 @@ module.exports.MatrixEvent = function MatrixEvent( if (!event[prop]) { return; } - if (!interns[event[prop]]) { - interns[event[prop]] = event[prop]; - } - event[prop] = interns[event[prop]]; + event[prop] = intern(event[prop]); }); ["membership", "avatar_url", "displayname"].forEach((prop) => { if (!event.content || !event.content[prop]) { return; } - if (!interns[event.content[prop]]) { - interns[event.content[prop]] = event.content[prop]; + event.content[prop] = intern(event.content[prop]); + }); + + ["rel_type"].forEach((prop) => { + if ( + !event.content || + !event.content["m.relates_to"] || + !event.content["m.relates_to"][prop] + ) { + return; } - event.content[prop] = interns[event.content[prop]]; + event.content["m.relates_to"][prop] = intern(event.content["m.relates_to"][prop]); }); this.event = event || {}; From 6078bbbe2475a1b95d211b64caa588b375c371d9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 8 May 2019 17:50:25 +0100 Subject: [PATCH 17/67] Add basic read path for relations This adds a read path for relations (gated behind an unstable option). A few basic client-side grouping and sorting operations are supported. Consumers are expected to ask the `EventTimelineSet` for a relation container when desired. --- src/client.js | 6 ++ src/models/event-timeline-set.js | 101 ++++++++++++++++++++- src/models/relations.js | 149 +++++++++++++++++++++++++++++++ src/models/room.js | 5 +- src/sync.js | 7 +- 5 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 src/models/relations.js diff --git a/src/client.js b/src/client.js index 83912c45a..8e9b7d175 100644 --- a/src/client.js +++ b/src/client.js @@ -148,6 +148,11 @@ function keyFromRecoverySession(session, decryptionKey) { * maintain support for back-paginating the live timeline after a '/sync' * result with a gap. * + * @param {boolean} [opts.unstableClientRelationAggregation = false] + * Optional. Set to true to enable client-side aggregation of event relations + * via `EventTimelineSet#getRelationsForEvent`. + * This feature is currently unstable and the API may change without notice. + * * @param {Array} [opts.verificationMethods] Optional. The verification method * that the application can handle. Each element should be an item from {@link * module:crypto~verificationMethods verificationMethods}, or a class that @@ -213,6 +218,7 @@ function MatrixClient(opts) { this.timelineSupport = Boolean(opts.timelineSupport); this.urlPreviewCache = {}; this._notifTimelineSet = null; + this.unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation; this._crypto = null; this._cryptoStore = opts.cryptoStore; diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 6025b7d77..a64201da8 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -20,6 +20,7 @@ limitations under the License. const EventEmitter = require("events").EventEmitter; const utils = require("../utils"); const EventTimeline = require("./event-timeline"); +import Relations from './relations'; // var DEBUG = false; const DEBUG = true; @@ -54,22 +55,38 @@ if (DEBUG) { * map from event_id to timeline and index. * * @constructor - * @param {?Room} room the optional room for this timelineSet - * @param {Object} opts hash of options inherited from Room. - * opts.timelineSupport gives whether timeline support is enabled - * opts.filter is the filter object, if any, for this timelineSet. + * @param {?Room} room + * Room for this timelineSet. May be null for non-room cases, such as the + * notification timeline. + * @param {Object} opts Options inherited from Room. + * + * @param {boolean} [opts.timelineSupport = false] + * Set to true to enable improved timeline support. + * @param {Object} [opts.filter = null] + * The filter object, if any, for this timelineSet. + * @param {boolean} [opts.unstableClientRelationAggregation = false] + * Optional. Set to true to enable client-side aggregation of event relations + * via `getRelationsForEvent`. + * This feature is currently unstable and the API may change without notice. */ function EventTimelineSet(room, opts) { this.room = room; this._timelineSupport = Boolean(opts.timelineSupport); this._liveTimeline = new EventTimeline(this); + this._unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation; // just a list - *not* ordered. this._timelines = [this._liveTimeline]; this._eventIdToTimeline = {}; this._filter = opts.filter || null; + + if (this._unstableClientRelationAggregation) { + // A tree of objects to access a set of relations for an event, as in: + // this._relations[relatesToEventId][relationType][relationEventType] + this._relations = {}; + } } utils.inherits(EventTimelineSet, EventEmitter); @@ -523,6 +540,8 @@ EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, timeline.addEvent(event, toStartOfTimeline); this._eventIdToTimeline[eventId] = timeline; + this._aggregateRelations(event); + const data = { timeline: timeline, liveEvent: !toStartOfTimeline && timeline == this._liveTimeline, @@ -657,6 +676,80 @@ EventTimelineSet.prototype.compareEventOrdering = function(eventId1, eventId2) { return null; }; +/** + * Get a collection of relations to a given event in this timeline set. + * + * @param {String} eventId + * The ID of the event that you'd like to access relation events for. + * For example, with annotations, this would be the ID of the event being annotated. + * @param {String} relationType + * The type of relation involved, such as "m.annotation", "m.reference", "m.replace", etc. + * @param {String} eventType + * The relation event's type, such as "m.reaction", etc. + * + * @returns {Relations} + * A container for relation events. + */ +EventTimelineSet.prototype.getRelationsForEvent = function( + eventId, relationType, eventType, +) { + if (!this._unstableClientRelationAggregation) { + throw new Error("Client-side relation aggregation is disabled"); + } + + if (!eventId || !relationType || !eventType) { + throw new Error("Invalid arguments for `getRelationsForEvent`"); + } + + // debuglog("Getting relations for: ", eventId, relationType, eventType); + + const relationsForEvent = this._relations[eventId] || {}; + const relationsWithRelType = relationsForEvent[relationType] || {}; + return relationsWithRelType[eventType]; +}; + +/** + * Add relation events to the relevant relation collection. + * + * @param {MatrixEvent} event + * The new relation event to be aggregated. + */ +EventTimelineSet.prototype._aggregateRelations = function(event) { + if (!this._unstableClientRelationAggregation) { + return; + } + + const content = event.getContent(); + const relation = content && content["m.relates_to"]; + if (!relation || !relation.rel_type || !relation.event_id) { + return; + } + + const relatesToEventId = relation.event_id; + const relationType = relation.rel_type; + const eventType = event.getType(); + + // debuglog("Aggregating relation: ", event.getId(), eventType, relation); + + let relationsForEvent = this._relations[relatesToEventId]; + if (!relationsForEvent) { + relationsForEvent = this._relations[relatesToEventId] = {}; + } + let relationsWithRelType = relationsForEvent[relationType]; + if (!relationsWithRelType) { + relationsWithRelType = relationsForEvent[relationType] = {}; + } + let relationsWithEventType = relationsWithRelType[eventType]; + if (!relationsWithEventType) { + relationsWithEventType = relationsWithRelType[eventType] = new Relations( + relationType, + eventType, + ); + } + + relationsWithEventType.addEvent(event); +}; + /** * The EventTimelineSet class. */ diff --git a/src/models/relations.js b/src/models/relations.js new file mode 100644 index 000000000..16f02cb26 --- /dev/null +++ b/src/models/relations.js @@ -0,0 +1,149 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * A container for relation events that supports easy access to common ways of + * aggregating such events. Each instance holds events that of a single relation + * type and event type. All of the events also relate to the same original event. + * + * The typical way to get one of these containers is via + * EventTimelineSet#getRelationsForEvent. + */ +export default class Relations { + /** + * @param {String} relationType + * The type of relation involved, such as "m.annotation", "m.reference", + * "m.replace", etc. + * @param {String} eventType + * The relation event's type, such as "m.reaction", etc. + */ + constructor(relationType, eventType) { + this.relationType = relationType; + this.eventType = eventType; + this._events = []; + this._annotationsByKey = {}; + this._annotationsBySender = {}; + this._sortedAnnotationsByKey = []; + } + + /** + * Add relation events to this collection. + * + * @param {MatrixEvent} event + * The new relation event to be aggregated. + */ + addEvent(event) { + const content = event.getContent(); + const relation = content && content["m.relates_to"]; + if (!relation || !relation.rel_type || !relation.event_id) { + console.error("Event must have relation info"); + return; + } + + const relationType = relation.rel_type; + const eventType = event.getType(); + + if (this.relationType !== relationType || this.eventType !== eventType) { + console.error("Event relation info doesn't match this container"); + return; + } + + if (this.relationType === "m.annotation") { + const key = relation.key; + this._aggregateAnnotation(key, event); + } + + this._events.push(event); + } + + /** + * Get all events in this collection. + * + * These are currently in the order of insertion to this collection, which + * won't match timeline order in the case of scrollback. + * TODO: Tweak `addEvent` to insert correctly for scrollback. + * + * @return {Array} + * Relation events in insertion order. + */ + getEvents() { + return this._events; + } + + _aggregateAnnotation(key, event) { + if (!key) { + return; + } + + let eventsForKey = this._annotationsByKey[key]; + if (!eventsForKey) { + eventsForKey = this._annotationsByKey[key] = []; + this._sortedAnnotationsByKey.push([key, eventsForKey]); + } + // Add the new event to the list for this key + eventsForKey.push(event); + // Re-sort the [key, events] pairs in descending order of event count + this._sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.length - aEvents.length; + }); + + const sender = event.getSender(); + let eventsFromSender = this._annotationsBySender[sender]; + if (!eventsFromSender) { + eventsFromSender = this._annotationsBySender[sender] = []; + } + // Add the new event to the list for this sender + eventsFromSender.push(event); + } + + /** + * Get all events in this collection grouped by key and sorted by descending + * event count in each group. + * + * This is currently only supported for the annotation relation type. + * + * @return {Array} + * An array of [key, events] pairs sorted by descending event count. + */ + getSortedAnnotationsByKey() { + if (this.relationType !== "m.annotation") { + // Other relation types are not grouped currently. + return null; + } + + return this._sortedAnnotationsByKey; + } + + /** + * Get all events in this collection grouped by sender. + * + * This is currently only supported for the annotation relation type. + * + * @return {Object} + * An object with each relation sender as a key and the matching list of + * events for that sender as a value. + */ + getAnnotationsBySender() { + if (this.relationType !== "m.annotation") { + // Other relation types are not grouped currently. + return null; + } + + return this._annotationsBySender; + } +} diff --git a/src/models/room.js b/src/models/room.js index 84bcfd0ff..d026c673d 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -92,9 +92,12 @@ function synthesizeReceipt(userId, event, receiptType) { * "detached", pending messages will appear in a separate list, * accessbile via {@link module:models/room#getPendingEvents}. Default: * "chronological". - * * @param {boolean} [opts.timelineSupport = false] Set to true to enable improved * timeline support. + * @param {boolean} [opts.unstableClientRelationAggregation = false] + * Optional. Set to true to enable client-side aggregation of event relations + * via `EventTimelineSet#getRelationsForEvent`. + * This feature is currently unstable and the API may change without notice. * * @prop {string} roomId The ID of this room. * @prop {string} name The human-readable display name for this room. diff --git a/src/sync.js b/src/sync.js index c863af955..ea909214b 100644 --- a/src/sync.js +++ b/src/sync.js @@ -116,10 +116,15 @@ function SyncApi(client, opts) { */ SyncApi.prototype.createRoom = function(roomId) { const client = this.client; + const { + timelineSupport, + unstableClientRelationAggregation, + } = client; const room = new Room(roomId, client, client.getUserId(), { lazyLoadMembers: this.opts.lazyLoadMembers, pendingEventOrdering: this.opts.pendingEventOrdering, - timelineSupport: client.timelineSupport, + timelineSupport, + unstableClientRelationAggregation, }); client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction", "Room.receipt", "Room.tags", From 761806c678bf8308731833a79268802657ee2f96 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 9 May 2019 13:25:49 +0100 Subject: [PATCH 18/67] Add support for class properties This enables compiler and linting features to allow class properties like we do in the React SDK. --- .babelrc | 2 ++ .eslintrc.js | 8 ++++++ package.json | 4 ++- spec/integ/devicelist-integ-spec.js | 2 +- spec/integ/matrix-client-crypto.spec.js | 2 +- .../integ/matrix-client-event-emitter.spec.js | 2 +- .../matrix-client-event-timeline.spec.js | 4 +-- spec/integ/matrix-client-methods.spec.js | 2 +- spec/integ/matrix-client-opts.spec.js | 2 +- spec/integ/matrix-client-retrying.spec.js | 2 +- .../integ/matrix-client-room-timeline.spec.js | 2 +- spec/integ/matrix-client-syncing.spec.js | 2 +- spec/integ/megolm-integ.spec.js | 2 +- spec/unit/autodiscovery.spec.js | 2 +- spec/unit/content-repo.spec.js | 2 +- spec/unit/crypto/DeviceList.spec.js | 2 +- spec/unit/crypto/algorithms/megolm.spec.js | 2 +- spec/unit/crypto/algorithms/olm.spec.js | 2 +- spec/unit/crypto/backup.spec.js | 2 +- spec/unit/crypto/verification/util.js | 2 +- spec/unit/event-timeline.spec.js | 2 +- spec/unit/event.spec.js | 2 +- spec/unit/filter.spec.js | 2 +- spec/unit/interactive-auth.spec.js | 2 +- spec/unit/matrix-client.spec.js | 2 +- spec/unit/realtime-callbacks.spec.js | 6 ++--- spec/unit/room-member.spec.js | 2 +- spec/unit/room-state.spec.js | 2 +- spec/unit/room.spec.js | 2 +- spec/unit/scheduler.spec.js | 2 +- spec/unit/sync-accumulator.spec.js | 2 +- spec/unit/timeline-window.spec.js | 4 +-- spec/unit/user.spec.js | 2 +- spec/unit/utils.spec.js | 2 +- src/store/indexeddb.js | 2 +- yarn.lock | 27 +++++++++++++++++++ 36 files changed, 76 insertions(+), 37 deletions(-) diff --git a/.babelrc b/.babelrc index 22a8c2bbf..572b4baff 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,8 @@ { "presets": ["es2015"], "plugins": [ + "transform-class-properties", + // this transforms async functions into generator functions, which // are then made to use the regenerator module by babel's // transform-regnerator plugin (which is enabled by es2015). diff --git a/.eslintrc.js b/.eslintrc.js index 4606ca6ae..b464abcbb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,6 +14,9 @@ module.exports = { es6: true, }, extends: ["eslint:recommended", "google"], + plugins: [ + "babel", + ], rules: { // rules we've always adhered to or now do "max-len": ["error", { @@ -73,5 +76,10 @@ module.exports = { "asyncArrow": "always", }], "arrow-parens": "off", + + // eslint's built in no-invalid-this rule breaks with class properties + "no-invalid-this": "off", + // so we replace it with a version that is class property aware + "babel/no-invalid-this": "error", } } diff --git a/package.json b/package.json index eb10dd16e..bad18771f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors", "test": "yarn test:build && yarn test:run", "check": "yarn test:build && _mocha --recursive specbuild --colors", - "gendoc": "babel --no-babelrc -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc", + "gendoc": "babel --no-babelrc --plugins transform-class-properties -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc", "start": "yarn start:init && yarn start:watch", "start:watch": "babel -s -w --skip-initial-build -d lib src", "start:init": "babel -s -d lib src", @@ -67,12 +67,14 @@ "babel-cli": "^6.18.0", "babel-eslint": "^10.0.1", "babel-plugin-transform-async-to-bluebird": "^1.1.1", + "babel-plugin-transform-class-properties": "^6.24.1", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.18.0", "browserify": "^16.2.3", "browserify-shim": "^3.8.13", "eslint": "^5.12.0", "eslint-config-google": "^0.7.1", + "eslint-plugin-babel": "^5.3.0", "exorcist": "^0.4.0", "expect": "^1.20.2", "istanbul": "^0.4.5", diff --git a/spec/integ/devicelist-integ-spec.js b/spec/integ/devicelist-integ-spec.js index 249db5593..fccdbb667 100644 --- a/spec/integ/devicelist-integ-spec.js +++ b/spec/integ/devicelist-integ-spec.js @@ -87,7 +87,7 @@ describe("DeviceList management:", function() { } beforeEach(async function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this // we create our own sessionStoreBackend so that we can use it for // another TestClient. diff --git a/spec/integ/matrix-client-crypto.spec.js b/spec/integ/matrix-client-crypto.spec.js index f0626cf61..b8f0524d9 100644 --- a/spec/integ/matrix-client-crypto.spec.js +++ b/spec/integ/matrix-client-crypto.spec.js @@ -405,7 +405,7 @@ describe("MatrixClient crypto", function() { } beforeEach(async function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken); await aliTestClient.client.initCrypto(); diff --git a/spec/integ/matrix-client-event-emitter.spec.js b/spec/integ/matrix-client-event-emitter.spec.js index bc3a07214..93f4fbce4 100644 --- a/spec/integ/matrix-client-event-emitter.spec.js +++ b/spec/integ/matrix-client-event-emitter.spec.js @@ -15,7 +15,7 @@ describe("MatrixClient events", function() { const selfAccessToken = "aseukfgwef"; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); client = sdk.createClient({ diff --git a/spec/integ/matrix-client-event-timeline.spec.js b/spec/integ/matrix-client-event-timeline.spec.js index 2f1c9fa87..cf031aeaa 100644 --- a/spec/integ/matrix-client-event-timeline.spec.js +++ b/spec/integ/matrix-client-event-timeline.spec.js @@ -102,7 +102,7 @@ describe("getEventTimeline support", function() { let client; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); }); @@ -227,7 +227,7 @@ describe("MatrixClient event timelines", function() { let httpBackend = null; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); diff --git a/spec/integ/matrix-client-methods.spec.js b/spec/integ/matrix-client-methods.spec.js index 5fe423de6..b37d26d5a 100644 --- a/spec/integ/matrix-client-methods.spec.js +++ b/spec/integ/matrix-client-methods.spec.js @@ -21,7 +21,7 @@ describe("MatrixClient", function() { const accessToken = "aseukfgwef"; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); store = new MemoryStore(); diff --git a/spec/integ/matrix-client-opts.spec.js b/spec/integ/matrix-client-opts.spec.js index 8c892ec10..11bcf6b6d 100644 --- a/spec/integ/matrix-client-opts.spec.js +++ b/spec/integ/matrix-client-opts.spec.js @@ -58,7 +58,7 @@ describe("MatrixClient opts", function() { }; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); }); diff --git a/spec/integ/matrix-client-retrying.spec.js b/spec/integ/matrix-client-retrying.spec.js index b82294f2c..0dabc0abe 100644 --- a/spec/integ/matrix-client-retrying.spec.js +++ b/spec/integ/matrix-client-retrying.spec.js @@ -20,7 +20,7 @@ describe("MatrixClient retrying", function() { let room; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); scheduler = new sdk.MatrixScheduler(); diff --git a/spec/integ/matrix-client-room-timeline.spec.js b/spec/integ/matrix-client-room-timeline.spec.js index 5970ceb66..f951c0cf4 100644 --- a/spec/integ/matrix-client-room-timeline.spec.js +++ b/spec/integ/matrix-client-room-timeline.spec.js @@ -104,7 +104,7 @@ describe("MatrixClient room timelines", function() { } beforeEach(function(done) { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); client = sdk.createClient({ diff --git a/spec/integ/matrix-client-syncing.spec.js b/spec/integ/matrix-client-syncing.spec.js index 6002436c4..2d702c8a7 100644 --- a/spec/integ/matrix-client-syncing.spec.js +++ b/spec/integ/matrix-client-syncing.spec.js @@ -23,7 +23,7 @@ describe("MatrixClient syncing", function() { const roomTwo = "!bar:localhost"; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new HttpBackend(); sdk.request(httpBackend.requestFn); client = sdk.createClient({ diff --git a/spec/integ/megolm-integ.spec.js b/spec/integ/megolm-integ.spec.js index 0963ef43b..ce6f595c9 100644 --- a/spec/integ/megolm-integ.spec.js +++ b/spec/integ/megolm-integ.spec.js @@ -282,7 +282,7 @@ describe("megolm", function() { } beforeEach(async function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this aliceTestClient = new TestClient( "@alice:localhost", "xzcvb", "akjgkrgjs", diff --git a/spec/unit/autodiscovery.spec.js b/spec/unit/autodiscovery.spec.js index 3326e781d..f6cb486a1 100644 --- a/spec/unit/autodiscovery.spec.js +++ b/spec/unit/autodiscovery.spec.js @@ -30,7 +30,7 @@ describe("AutoDiscovery", function() { let httpBackend = null; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this httpBackend = new MockHttpBackend(); sdk.request(httpBackend.requestFn); }); diff --git a/spec/unit/content-repo.spec.js b/spec/unit/content-repo.spec.js index d66560677..b1a97f681 100644 --- a/spec/unit/content-repo.spec.js +++ b/spec/unit/content-repo.spec.js @@ -9,7 +9,7 @@ describe("ContentRepo", function() { const baseUrl = "https://my.home.server"; beforeEach(function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); describe("getHttpUriForMxc", function() { diff --git a/spec/unit/crypto/DeviceList.spec.js b/spec/unit/crypto/DeviceList.spec.js index fcca8c963..c0073c394 100644 --- a/spec/unit/crypto/DeviceList.spec.js +++ b/spec/unit/crypto/DeviceList.spec.js @@ -59,7 +59,7 @@ describe('DeviceList', function() { let deviceLists = []; beforeEach(function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this deviceLists = []; diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js index 5d98f033e..9196d90de 100644 --- a/spec/unit/crypto/algorithms/megolm.spec.js +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -31,7 +31,7 @@ describe("MegolmDecryption", function() { let mockBaseApis; beforeEach(async function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this await Olm.init(); diff --git a/spec/unit/crypto/algorithms/olm.spec.js b/spec/unit/crypto/algorithms/olm.spec.js index d8c211918..79c05b762 100644 --- a/spec/unit/crypto/algorithms/olm.spec.js +++ b/spec/unit/crypto/algorithms/olm.spec.js @@ -53,7 +53,7 @@ describe("OlmDecryption", function() { let bobOlmDevice; beforeEach(async function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this await global.Olm.init(); diff --git a/spec/unit/crypto/backup.spec.js b/spec/unit/crypto/backup.spec.js index 1b613c945..8a7385fb4 100644 --- a/spec/unit/crypto/backup.spec.js +++ b/spec/unit/crypto/backup.spec.js @@ -125,7 +125,7 @@ describe("MegolmBackup", function() { let megolmDecryption; beforeEach(async function() { await Olm.init(); - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this mockCrypto = testUtils.mock(Crypto, 'Crypto'); mockCrypto.backupKey = new Olm.PkEncryption(); diff --git a/spec/unit/crypto/verification/util.js b/spec/unit/crypto/verification/util.js index faa9c58f4..6e5622f3e 100644 --- a/spec/unit/crypto/verification/util.js +++ b/spec/unit/crypto/verification/util.js @@ -29,7 +29,7 @@ export async function makeTestClients(userInfos, options) { for (const [deviceId, msg] of Object.entries(devMap)) { if (deviceId in clientMap[userId]) { const event = new MatrixEvent({ - sender: this.getUserId(), // eslint-disable-line no-invalid-this + sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this type: type, content: msg, }); diff --git a/spec/unit/event-timeline.spec.js b/spec/unit/event-timeline.spec.js index 170687681..85f8a8215 100644 --- a/spec/unit/event-timeline.spec.js +++ b/spec/unit/event-timeline.spec.js @@ -18,7 +18,7 @@ describe("EventTimeline", function() { let timeline; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this // XXX: this is a horrid hack; should use sinon or something instead to mock const timelineSet = { room: { roomId: roomId }}; diff --git a/spec/unit/event.spec.js b/spec/unit/event.spec.js index 47de6615f..b628af9f8 100644 --- a/spec/unit/event.spec.js +++ b/spec/unit/event.spec.js @@ -24,7 +24,7 @@ import Promise from 'bluebird'; describe("MatrixEvent", () => { beforeEach(function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); describe(".attemptDecryption", () => { diff --git a/spec/unit/filter.spec.js b/spec/unit/filter.spec.js index f8fbcc540..249eaa171 100644 --- a/spec/unit/filter.spec.js +++ b/spec/unit/filter.spec.js @@ -12,7 +12,7 @@ describe("Filter", function() { let filter; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this filter = new Filter(userId); }); diff --git a/spec/unit/interactive-auth.spec.js b/spec/unit/interactive-auth.spec.js index 2f810d845..182cc5d6f 100644 --- a/spec/unit/interactive-auth.spec.js +++ b/spec/unit/interactive-auth.spec.js @@ -35,7 +35,7 @@ class FakeClient { describe("InteractiveAuth", function() { beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); it("should start an auth stage and complete it", function(done) { diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index a59e0af43..2b929b36d 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -124,7 +124,7 @@ describe("MatrixClient", function() { } beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this clock = lolex.install(); scheduler = [ "getQueueForEvent", "queueEvent", "removeEventFromQueue", diff --git a/spec/unit/realtime-callbacks.spec.js b/spec/unit/realtime-callbacks.spec.js index 1a05b7a40..c78cf82db 100644 --- a/spec/unit/realtime-callbacks.spec.js +++ b/spec/unit/realtime-callbacks.spec.js @@ -15,7 +15,7 @@ describe("realtime-callbacks", function() { } beforeEach(function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this clock = lolex.install(); const fakeDate = clock.Date; callbacks.setNow(fakeDate.now.bind(fakeDate)); @@ -56,8 +56,8 @@ describe("realtime-callbacks", function() { it("should set 'this' to the global object", function() { let passed = false; const callback = function() { - expect(this).toBe(global); // eslint-disable-line no-invalid-this - expect(this.console).toBeTruthy(); // eslint-disable-line no-invalid-this + expect(this).toBe(global); // eslint-disable-line babel/no-invalid-this + expect(this.console).toBeTruthy(); // eslint-disable-line babel/no-invalid-this passed = true; }; callbacks.setTimeout(callback); diff --git a/spec/unit/room-member.spec.js b/spec/unit/room-member.spec.js index 77c2f7058..e07de6c96 100644 --- a/spec/unit/room-member.spec.js +++ b/spec/unit/room-member.spec.js @@ -14,7 +14,7 @@ describe("RoomMember", function() { let member; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this member = new RoomMember(roomId, userA); }); diff --git a/spec/unit/room-state.spec.js b/spec/unit/room-state.spec.js index baa602781..553f424ff 100644 --- a/spec/unit/room-state.spec.js +++ b/spec/unit/room-state.spec.js @@ -17,7 +17,7 @@ describe("RoomState", function() { let state; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this state = new RoomState(roomId); state.setStateEvents([ utils.mkMembership({ // userA joined diff --git a/spec/unit/room.spec.js b/spec/unit/room.spec.js index 1b1d6fb29..a809dd6ee 100644 --- a/spec/unit/room.spec.js +++ b/spec/unit/room.spec.js @@ -19,7 +19,7 @@ describe("Room", function() { let room; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this room = new Room(roomId); // mock RoomStates room.oldState = room.getLiveTimeline()._startState = diff --git a/spec/unit/scheduler.spec.js b/spec/unit/scheduler.spec.js index 4ece84d00..502edb3f3 100644 --- a/spec/unit/scheduler.spec.js +++ b/spec/unit/scheduler.spec.js @@ -26,7 +26,7 @@ describe("MatrixScheduler", function() { }); beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this clock = lolex.install(); scheduler = new MatrixScheduler(function(ev, attempts, err) { if (retryFn) { diff --git a/spec/unit/sync-accumulator.spec.js b/spec/unit/sync-accumulator.spec.js index a29be7efd..5f067b0b7 100644 --- a/spec/unit/sync-accumulator.spec.js +++ b/spec/unit/sync-accumulator.spec.js @@ -26,7 +26,7 @@ describe("SyncAccumulator", function() { let sa; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this sa = new SyncAccumulator({ maxTimelineEntries: 10, }); diff --git a/spec/unit/timeline-window.spec.js b/spec/unit/timeline-window.spec.js index 9fe5e96a0..3637fe968 100644 --- a/spec/unit/timeline-window.spec.js +++ b/spec/unit/timeline-window.spec.js @@ -68,7 +68,7 @@ function createLinkedTimelines() { describe("TimelineIndex", function() { beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); describe("minIndex", function() { @@ -164,7 +164,7 @@ describe("TimelineWindow", function() { } beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); describe("load", function() { diff --git a/spec/unit/user.spec.js b/spec/unit/user.spec.js index 3bbc4920a..7448cdec9 100644 --- a/spec/unit/user.spec.js +++ b/spec/unit/user.spec.js @@ -11,7 +11,7 @@ describe("User", function() { let user; beforeEach(function() { - utils.beforeEach(this); // eslint-disable-line no-invalid-this + utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this user = new User(userId); }); diff --git a/spec/unit/utils.spec.js b/spec/unit/utils.spec.js index bbbba072f..ca451eb81 100644 --- a/spec/unit/utils.spec.js +++ b/spec/unit/utils.spec.js @@ -7,7 +7,7 @@ import expect from 'expect'; describe("utils", function() { beforeEach(function() { - testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this }); describe("encodeParams", function() { diff --git a/src/store/indexeddb.js b/src/store/indexeddb.js index 35a862955..8c9938606 100644 --- a/src/store/indexeddb.js +++ b/src/store/indexeddb.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -/* eslint-disable no-invalid-this */ +/* eslint-disable babel/no-invalid-this */ import Promise from 'bluebird'; import {MemoryStore} from "./memory"; diff --git a/yarn.lock b/yarn.lock index 3897d3d99..8361644da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -554,6 +554,11 @@ babel-plugin-syntax-async-functions@^6.8.0: resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= +babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= + babel-plugin-transform-async-to-bluebird@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-bluebird/-/babel-plugin-transform-async-to-bluebird-1.1.1.tgz#46ea3e7c5af629782ac9f1ed1b7cd38f8425afd4" @@ -564,6 +569,16 @@ babel-plugin-transform-async-to-bluebird@^1.1.1: babel-template "^6.9.0" babel-traverse "^6.10.4" +babel-plugin-transform-class-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" @@ -1774,6 +1789,18 @@ eslint-config-google@^0.7.1: resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.7.1.tgz#5598f8498e9e078420f34b80495b8d959f651fb2" integrity sha1-VZj4SY6eB4Qg80uASVuNlZ9lH7I= +eslint-plugin-babel@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.0.tgz#2e7f251ccc249326da760c1a4c948a91c32d0023" + integrity sha512-HPuNzSPE75O+SnxHIafbW5QB45r2w78fxqwK3HmjqIUoPfPzVrq6rD+CINU3yzoDSzEhUkX07VUphbF73Lth/w== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" From 53d8cf0852e079ff9129ca7c62badeb3b80d5cb7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 9 May 2019 15:08:16 +0100 Subject: [PATCH 19/67] Update relation collections after redaction This watches for redactions of relations and updates the relations collection to match, including various aggregations. In addition, a redaction event is emitted on the redaction collection to notify consumers of the change. Part of https://github.com/vector-im/riot-web/issues/9574 Part of https://github.com/vector-im/riot-web/issues/9485 --- src/models/event-timeline-set.js | 1 + src/models/relations.js | 76 +++++++++++++++++++++++++++----- src/models/room.js | 1 + 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index a64201da8..150adfed4 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -744,6 +744,7 @@ EventTimelineSet.prototype._aggregateRelations = function(event) { relationsWithEventType = relationsWithRelType[eventType] = new Relations( relationType, eventType, + this.room, ); } diff --git a/src/models/relations.js b/src/models/relations.js index 16f02cb26..c19a245d4 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import EventEmitter from 'events'; + /** * A container for relation events that supports easy access to common ways of * aggregating such events. Each instance holds events that of a single relation @@ -22,21 +24,29 @@ limitations under the License. * The typical way to get one of these containers is via * EventTimelineSet#getRelationsForEvent. */ -export default class Relations { +export default class Relations extends EventEmitter { /** * @param {String} relationType * The type of relation involved, such as "m.annotation", "m.reference", * "m.replace", etc. * @param {String} eventType * The relation event's type, such as "m.reaction", etc. + * @param {?Room} room + * Room for this container. May be null for non-room cases, such as the + * notification timeline. */ - constructor(relationType, eventType) { + constructor(relationType, eventType, room) { + super(); this.relationType = relationType; this.eventType = eventType; - this._events = []; + this._relations = new Set(); this._annotationsByKey = {}; this._annotationsBySender = {}; this._sortedAnnotationsByKey = []; + + if (room) { + room.on("Room.beforeRedaction", this._onBeforeRedaction); + } } /** @@ -66,11 +76,11 @@ export default class Relations { this._aggregateAnnotation(key, event); } - this._events.push(event); + this._relations.add(event); } /** - * Get all events in this collection. + * Get all relation events in this collection. * * These are currently in the order of insertion to this collection, which * won't match timeline order in the case of scrollback. @@ -79,8 +89,8 @@ export default class Relations { * @return {Array} * Relation events in insertion order. */ - getEvents() { - return this._events; + getRelations() { + return [...this._relations]; } _aggregateAnnotation(key, event) { @@ -90,16 +100,16 @@ export default class Relations { let eventsForKey = this._annotationsByKey[key]; if (!eventsForKey) { - eventsForKey = this._annotationsByKey[key] = []; + eventsForKey = this._annotationsByKey[key] = new Set(); this._sortedAnnotationsByKey.push([key, eventsForKey]); } - // Add the new event to the list for this key - eventsForKey.push(event); + // Add the new event to the set for this key + eventsForKey.add(event); // Re-sort the [key, events] pairs in descending order of event count this._sortedAnnotationsByKey.sort((a, b) => { const aEvents = a[1]; const bEvents = b[1]; - return bEvents.length - aEvents.length; + return bEvents.size - aEvents.size; }); const sender = event.getSender(); @@ -111,6 +121,49 @@ export default class Relations { eventsFromSender.push(event); } + /** + * For relations that are about to be redacted, remove them from aggregation + * data sets and emit an update event. + * + * @param {MatrixEvent} redactedEvent + * The original relation event that is about to be redacted. + */ + _onBeforeRedaction = (redactedEvent) => { + if (!this._relations.has(redactedEvent)) { + return; + } + + if (this.relationType === "m.annotation") { + // Remove the redacted annotation from aggregation by key + const content = redactedEvent.getContent(); + const relation = content && content["m.relates_to"]; + if (!relation) { + return; + } + + const key = relation.key; + const eventsForKey = this._annotationsByKey[key]; + if (!eventsForKey) { + return; + } + eventsForKey.delete(redactedEvent); + + // Re-sort the [key, events] pairs in descending order of event count + this._sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.size - aEvents.size; + }); + } + + // Dispatch a redaction event on this collection. `setTimeout` is used + // to wait until the next event loop iteration by which time the event + // has actually been marked as redacted. + setTimeout(() => { + this.emit("Relations.redaction"); + }, 0); + } + /** * Get all events in this collection grouped by key and sorted by descending * event count in each group. @@ -119,6 +172,7 @@ export default class Relations { * * @return {Array} * An array of [key, events] pairs sorted by descending event count. + * The events are stored in a Set (which preserves insertion order). */ getSortedAnnotationsByKey() { if (this.relationType !== "m.annotation") { diff --git a/src/models/room.js b/src/models/room.js index d026c673d..e50edf07b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1011,6 +1011,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // if we know about this event, redact its contents now. const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId); if (redactedEvent) { + this.emit("Room.beforeRedaction", redactedEvent, event, this); redactedEvent.makeRedacted(event); this.emit("Room.redaction", event, this); From 7ea820f6e1805dc5099e33963d70607648af4da8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 9 May 2019 17:57:00 +0100 Subject: [PATCH 20/67] Add `Event.relationsCreated` event to listen for future relations collections If you ask for relations but none currently exist, we return `null` to avoid the overhead of many empty relations objects in memory. However, we still want some way to alert consumers when one _is_ later made, so this event level event provides that. Part of https://github.com/vector-im/riot-web/issues/9572 Part of https://github.com/vector-im/riot-web/issues/9485 --- src/models/event-timeline-set.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 150adfed4..d9cc15ce4 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -746,6 +746,10 @@ EventTimelineSet.prototype._aggregateRelations = function(event) { eventType, this.room, ); + const relatesToEvent = this.findEventById(relatesToEventId); + if (relatesToEvent) { + relatesToEvent.emit("Event.relationsCreated", relationType, eventType); + } } relationsWithEventType.addEvent(event); From 4b4ba861679a044d0681af1352a902528e81ae5b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 9 May 2019 18:29:50 +0100 Subject: [PATCH 21/67] Add `Relations.add` event for additional relations in collection This adds a `Relations.add` event that consumers can listen for to be notified each time an additional relation event is added to a relations collection. Part of https://github.com/vector-im/riot-web/issues/9485 Part of https://github.com/vector-im/riot-web/issues/9572 --- src/models/relations.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/models/relations.js b/src/models/relations.js index c19a245d4..78bb74952 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -77,6 +77,8 @@ export default class Relations extends EventEmitter { } this._relations.add(event); + + this.emit("Relations.add", event); } /** From 8822d255b35168f493caf86afffae3acaebb833b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 10 May 2019 16:10:12 +0100 Subject: [PATCH 22/67] Fix indentation in src/models/event.js --- src/models/event.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 3ef2cdb87..76544b6da 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -363,7 +363,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { if ( this._clearEvent && this._clearEvent.content && - this._clearEvent.content.msgtype !== "m.bad.encrypted" + this._clearEvent.content.msgtype !== "m.bad.encrypted" ) { // we may want to just ignore this? let's start with rejecting it. throw new Error( @@ -708,28 +708,28 @@ utils.extend(module.exports.MatrixEvent.prototype, { * * @return {?Object} push actions */ - getPushActions: function() { + getPushActions: function() { return this._pushActions; - }, + }, /** * Set the push actions for this event. * * @param {Object} pushActions push actions */ - setPushActions: function(pushActions) { + setPushActions: function(pushActions) { this._pushActions = pushActions; - }, + }, - /** - * Replace the `event` property and recalculate any properties based on it. - * @param {Object} event the object to assign to the `event` property - */ - handleRemoteEcho: function(event) { + /** + * Replace the `event` property and recalculate any properties based on it. + * @param {Object} event the object to assign to the `event` property + */ + handleRemoteEcho: function(event) { this.event = event; // successfully sent. this.status = null; - }, + }, }); From 00851df25cd47c4bd90b6a04ec9871a4865178f8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 10 May 2019 16:33:49 +0100 Subject: [PATCH 23/67] Always add pending relation events to the timeline sets directly This special cases pending relation events to go directly to the timeline sets and ignores the `pendingEventOrdering` option. This feels a bit strange in the code, so we should revisit this choice when we stabilized relation support. --- src/models/event.js | 10 ++++++++++ src/models/room.js | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/models/event.js b/src/models/event.js index 76544b6da..ba5abf366 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -730,6 +730,16 @@ utils.extend(module.exports.MatrixEvent.prototype, { // successfully sent. this.status = null; }, + + /** + * Get whether the event is a relation event. + * @return {boolean} + */ + isRelation() { + const content = this.getContent(); + const relation = content && content["m.relates_to"]; + return relation && relation.rel_type && relation.event_id; + }, }); diff --git a/src/models/room.js b/src/models/room.js index e50edf07b..b0930f38b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1101,7 +1101,11 @@ Room.prototype.addPendingEvent = function(event, txnId) { this._txnToEvent[txnId] = event; - if (this._opts.pendingEventOrdering == "detached") { + // TODO: We currently ignore `pendingEventOrdering` for relation events. + // They are aggregated by the timeline set, and we want that to happen right + // away for easy local echo, but it complicates what should be a general + // code path by branching on the event type. + if (!event.isRelation() && this._opts.pendingEventOrdering == "detached") { if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) { console.warn("Setting event as NOT_SENT due to messages in the same state"); event.status = EventStatus.NOT_SENT; From f411d50253ff344080641c2de0a750f389e22190 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 13 May 2019 14:17:23 +0100 Subject: [PATCH 24/67] Clarify before redaction event timing --- src/models/relations.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/models/relations.js b/src/models/relations.js index 78bb74952..240e36340 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -124,8 +124,12 @@ export default class Relations extends EventEmitter { } /** - * For relations that are about to be redacted, remove them from aggregation - * data sets and emit an update event. + * For relations that have been redacted, we want to remove them from + * aggregation data sets and emit an update event. + * + * To do so, we listen for `Room.beforeRedaction`, which happens: + * - after the server accepted the redaction and remote echoed back to us + * - before the original event has been marked redacted in the client * * @param {MatrixEvent} redactedEvent * The original relation event that is about to be redacted. From 3a20114c39f7df8a5011f85f44c7f8a387a744c9 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 13 May 2019 14:35:39 +0100 Subject: [PATCH 25/67] Change to event-level `beforeRedaction` event for efficiency To avoid an O(n^2) situation with every relations container trying to process every redaction that may occur in a room, this switches to an event-level notification, so that the specific relations container who cares can listen to just the events it wants to know about. --- src/models/event.js | 2 ++ src/models/relations.js | 10 +++++----- src/models/room.js | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index ba5abf366..cfc736183 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -661,6 +661,8 @@ utils.extend(module.exports.MatrixEvent.prototype, { throw new Error("invalid redaction_event in makeRedacted"); } + this.emit("Event.beforeRedaction", this, redaction_event); + // we attempt to replicate what we would see from the server if // the event had been redacted before we saw it. // diff --git a/src/models/relations.js b/src/models/relations.js index 240e36340..52193b028 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -43,10 +43,6 @@ export default class Relations extends EventEmitter { this._annotationsByKey = {}; this._annotationsBySender = {}; this._sortedAnnotationsByKey = []; - - if (room) { - room.on("Room.beforeRedaction", this._onBeforeRedaction); - } } /** @@ -78,6 +74,8 @@ export default class Relations extends EventEmitter { this._relations.add(event); + event.on("Event.beforeRedaction", this._onBeforeRedaction); + this.emit("Relations.add", event); } @@ -127,7 +125,7 @@ export default class Relations extends EventEmitter { * For relations that have been redacted, we want to remove them from * aggregation data sets and emit an update event. * - * To do so, we listen for `Room.beforeRedaction`, which happens: + * To do so, we listen for `Event.beforeRedaction`, which happens: * - after the server accepted the redaction and remote echoed back to us * - before the original event has been marked redacted in the client * @@ -162,6 +160,8 @@ export default class Relations extends EventEmitter { }); } + redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction); + // Dispatch a redaction event on this collection. `setTimeout` is used // to wait until the next event loop iteration by which time the event // has actually been marked as redacted. diff --git a/src/models/room.js b/src/models/room.js index b0930f38b..1019a4819 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1011,7 +1011,6 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // if we know about this event, redact its contents now. const redactedEvent = this.getUnfilteredTimelineSet().findEventById(redactId); if (redactedEvent) { - this.emit("Room.beforeRedaction", redactedEvent, event, this); redactedEvent.makeRedacted(event); this.emit("Room.redaction", event, this); From 09438b440eac870abc2997a74fd39ea51bc2f9e8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 May 2019 13:43:44 -0600 Subject: [PATCH 26/67] Fix spelling error in txnId for redactions Fixes https://github.com/vector-im/riot-web/issues/9700 --- src/base-apis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base-apis.js b/src/base-apis.js index 6750e7823..a799ffa85 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -904,7 +904,7 @@ MatrixBaseApis.prototype.redactEvent = function( callback = txnId; } - const path = utils.encodeUri("/rooms/$roomId/redact/$eventId/$tnxId", { + const path = utils.encodeUri("/rooms/$roomId/redact/$eventId/$txnId", { $roomId: roomId, $eventId: eventId, $txnId: txnId ? txnId : this.makeTxnId(), From 25df31bf96f867fdedbbe1b3dc2bc9087832c4f8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 13 May 2019 18:39:59 -0600 Subject: [PATCH 27/67] Use a short timeout for .well-known requests Applies to verification of the homeserver, identity server, and fetching of the .well-known objects. Does not affect other HTTP requests. See https://github.com/vector-im/riot-web/issues/9290 --- src/autodiscovery.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autodiscovery.js b/src/autodiscovery.js index 4079833c9..24195c671 100644 --- a/src/autodiscovery.js +++ b/src/autodiscovery.js @@ -480,7 +480,7 @@ export class AutoDiscovery { const request = require("./matrix").getRequest(); if (!request) throw new Error("No request library available"); request( - { method: "GET", uri: url }, + { method: "GET", uri: url, timeout: 5000 }, (err, response, body) => { if (err || response.statusCode < 200 || response.statusCode >= 300) { let action = "FAIL_PROMPT"; From 0161664b6c813a019eb88986b3b416267fb5b1e3 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:22:46 +0100 Subject: [PATCH 28/67] add support for replacing an existing event in a timeline(set) --- src/models/event-timeline-set.js | 29 +++++++++++++++++++++++++++++ src/models/event-timeline.js | 21 +++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index d9cc15ce4..25344ff21 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -243,6 +243,35 @@ EventTimelineSet.prototype.findEventById = function(eventId) { }); }; +/** + * Find an event by id which is stored in our timelines and replace it with a new event + * + * @param {string} eventId event ID to look for + * @param {string} newEvent the event to replace with + * @return {?module:models/event~MatrixEvent} the replaced event, or undefined if unknown + */ +EventTimelineSet.prototype.tryReplaceEvent = function(eventId, newEvent) { + // we can't use this._eventIdToTimeline here to find the matching timeline + // because we need to match the original event id, + // which might have been replaced by a previous m.replace already. + // for now, loop through all timelines. Maybe a separate index would be justifyable. + let oldEvent; + for(const tl of this._timelines) { + oldEvent = tl.tryReplaceEvent(eventId, newEvent); + if (oldEvent) { + break; + } + } + if (oldEvent) { + let originalEvent; + if (oldEvent.isReplacement()) { + originalEvent = oldEvent.getReplacedEvent(); + } + this.replaceEventId(oldEvent.getId(), newEvent.getId()); + return oldEvent; + } +}; + /** * Add a new timeline to this timeline list * diff --git a/src/models/event-timeline.js b/src/models/event-timeline.js index 63846cebd..5ab4eaadc 100644 --- a/src/models/event-timeline.js +++ b/src/models/event-timeline.js @@ -339,6 +339,27 @@ EventTimeline.prototype.addEvent = function(event, atStart) { } }; + +/** + * Replaces an event in the timeline, if it exists. + * + * @param {string} eventId the id of the existing event + * @param {boolean} newEvent the new event + * @return {MatrixEvent?} the old event, if found and replaced. + */ +EventTimeline.prototype.tryReplaceEvent = function(eventId, newEvent) { + const eventIndex = this._events.findIndex(event => event.getOriginalId() === eventId); + if (eventIndex === -1) { + return; + } + const oldEvent = this._events[eventIndex]; + this._events[eventIndex] = newEvent; + // copy over metadata, as we don't hold state for random locations in the timeline, only for the end or start + // and assuming for now that replacing an event doesn't change the sender that should be shown. + newEvent.sender = oldEvent.sender; + return oldEvent; +}; + /** * Static helper method to set sender and target properties * From 18cd017f58076ac4ded5f9b3a1ab809f95668937 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:23:22 +0100 Subject: [PATCH 29/67] support marking an as replacing another and take if the timestamp of the original event if so also helper methods --- src/models/event.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index cfc736183..cac716400 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -122,6 +122,7 @@ module.exports.MatrixEvent = function MatrixEvent( this.error = null; this.forwardLooking = true; this._pushActions = null; + this._replacedEvent = null; this._clearEvent = {}; @@ -208,6 +209,9 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Number} The event timestamp, e.g. 1433502692297 */ getTs: function() { + if (this._replacedEvent) { + return this._replacedEvent.getTs(); + } return this.event.origin_server_ts; }, @@ -216,6 +220,9 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Date} The event date, e.g. new Date(1433502692297) */ getDate: function() { + if (this._replacedEvent) { + return this._replacedEvent.getDate(); + } return this.event.origin_server_ts ? new Date(this.event.origin_server_ts) : null; }, @@ -742,6 +749,30 @@ utils.extend(module.exports.MatrixEvent.prototype, { const relation = content && content["m.relates_to"]; return relation && relation.rel_type && relation.event_id; }, + + isReplacement() { + const content = this.getContent(); + const relation = content && content["m.relates_to"]; + return relation && relation.rel_type === "m.replace" && relation.event_id; + }, + + setReplacedEvent(replacedEvent) { + this._replacedEvent = replacedEvent; + }, + + getReplacedEvent() { + return this._replacedEvent; + }, + + getOriginalId() { + const content = this.getContent(); + const relation = content && content["m.relates_to"]; + if (relation && relation.rel_type === "m.replace") { + return relation.event_id; + } else { + return this.getId(); + } + }, }); From 25e6b1cac873c372bf4ce154633d7d47495da87d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:24:36 +0100 Subject: [PATCH 30/67] handle m.replace relations in room, emit Room.replaceEvent --- src/models/room.js | 21 +++++++++++++++++++++ src/sync.js | 1 + 2 files changed, 22 insertions(+) diff --git a/src/models/room.js b/src/models/room.js index 1019a4819..6606a92e0 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1028,6 +1028,27 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // this may be needed to trigger an update. } + if (event.isReplacement()) { + const replacedEventId = event.getOriginalId(); + const replacedEvent = replacedEventId && + this.getUnfilteredTimelineSet().tryReplaceEvent(replacedEventId, event); + if (replacedEvent) { + // if this was already a replacement, get the original + let originalEvent = replacedEvent; + if (originalEvent.isReplacement()) { + originalEvent = originalEvent.getReplacedEvent(); + } + event.setReplacedEvent(originalEvent); + // report replacedEvent and not originalEvent because replaceEvent was in the timeline so far + this.emit("Room.replaceEvent", replacedEvent, event, this); + } + else { + console.warn(`could not find replaced event for target id ${replacedEventId} and if ${event.getId()}, body: ${event.getContent().body}`); + } + // we don't add the event because the event type would get rendered + return; + } + if (event.getUnsigned().transaction_id) { const existingEvent = this._txnToEvent[event.getUnsigned().transaction_id]; if (existingEvent) { diff --git a/src/sync.js b/src/sync.js index ea909214b..7e3cbf817 100644 --- a/src/sync.js +++ b/src/sync.js @@ -132,6 +132,7 @@ SyncApi.prototype.createRoom = function(roomId) { "Room.localEchoUpdated", "Room.accountData", "Room.myMembership", + "Room.replaceEvent", ]); this._registerStateListeners(room); return room; From f68a3dde46b41a89df2706507a1d66cc636f0906 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:24:57 +0100 Subject: [PATCH 31/67] cleanup --- src/models/room.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 6606a92e0..d72f801a8 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1004,7 +1004,6 @@ Room.prototype.removeFilteredTimelineSet = function(filter) { * @private */ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { - let i; if (event.getType() === "m.room.redaction") { const redactId = event.event.redacts; @@ -1059,7 +1058,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { } // add to our timeline sets - for (i = 0; i < this._timelineSets.length; i++) { + for (let i = 0; i < this._timelineSets.length; i++) { this._timelineSets[i].addLiveEvent(event, duplicateStrategy); } From df6012c58daac0d41bbe4f6ca870965936739916 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:25:07 +0100 Subject: [PATCH 32/67] completely avoid local echo for edits for now --- src/models/room.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/room.js b/src/models/room.js index d72f801a8..91868af6b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1099,6 +1099,10 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { * unique transaction id. */ Room.prototype.addPendingEvent = function(event, txnId) { + if (event.isReplacement()) { + return; + } + if (event.status !== EventStatus.SENDING) { throw new Error("addPendingEvent called on an event with status " + event.status); From 455f52f1f5c371dccc9c7d117458e24d20f0282b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:25:47 +0100 Subject: [PATCH 33/67] remove logging --- src/models/room.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 91868af6b..0a2fc7f29 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1041,9 +1041,6 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // report replacedEvent and not originalEvent because replaceEvent was in the timeline so far this.emit("Room.replaceEvent", replacedEvent, event, this); } - else { - console.warn(`could not find replaced event for target id ${replacedEventId} and if ${event.getId()}, body: ${event.getContent().body}`); - } // we don't add the event because the event type would get rendered return; } From 852c88c34108ed1583696a61eecde08a9374d573 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:33:49 +0100 Subject: [PATCH 34/67] add unstableClientRelationReplacements in js-sdk --- src/client.js | 6 ++++++ src/models/room.js | 7 ++++++- src/sync.js | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/client.js b/src/client.js index 8e9b7d175..860debc4b 100644 --- a/src/client.js +++ b/src/client.js @@ -153,6 +153,11 @@ function keyFromRecoverySession(session, decryptionKey) { * via `EventTimelineSet#getRelationsForEvent`. * This feature is currently unstable and the API may change without notice. * + * @param {boolean} [opts.unstableClientRelationReplacements = false] + * Optional. Set to true to enable client-side handling of m.replace event relations, + * exposed through the `Room.replaceEvent` event. + * This feature is currently unstable and the API may change without notice. + * * @param {Array} [opts.verificationMethods] Optional. The verification method * that the application can handle. Each element should be an item from {@link * module:crypto~verificationMethods verificationMethods}, or a class that @@ -219,6 +224,7 @@ function MatrixClient(opts) { this.urlPreviewCache = {}; this._notifTimelineSet = null; this.unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation; + this.unstableClientRelationReplacements = !!opts.unstableClientRelationReplacements; this._crypto = null; this._cryptoStore = opts.cryptoStore; diff --git a/src/models/room.js b/src/models/room.js index 0a2fc7f29..bb15f2921 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -99,6 +99,11 @@ function synthesizeReceipt(userId, event, receiptType) { * via `EventTimelineSet#getRelationsForEvent`. * This feature is currently unstable and the API may change without notice. * + * @param {boolean} [opts.unstableClientRelationReplacements = false] + * Optional. Set to true to enable client-side handling of m.replace event relations, + * exposed through the `Room.replaceEvent` event. + * This feature is currently unstable and the API may change without notice. + * * @prop {string} roomId The ID of this room. * @prop {string} name The human-readable display name for this room. * @prop {Array} timeline The live event timeline for this room, @@ -1027,7 +1032,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // this may be needed to trigger an update. } - if (event.isReplacement()) { + if (this._opts.unstableClientRelationReplacements && event.isReplacement()) { const replacedEventId = event.getOriginalId(); const replacedEvent = replacedEventId && this.getUnfilteredTimelineSet().tryReplaceEvent(replacedEventId, event); diff --git a/src/sync.js b/src/sync.js index 7e3cbf817..b180c1dd0 100644 --- a/src/sync.js +++ b/src/sync.js @@ -119,12 +119,14 @@ SyncApi.prototype.createRoom = function(roomId) { const { timelineSupport, unstableClientRelationAggregation, + unstableClientRelationReplacements, } = client; const room = new Room(roomId, client, client.getUserId(), { lazyLoadMembers: this.opts.lazyLoadMembers, pendingEventOrdering: this.opts.pendingEventOrdering, timelineSupport, unstableClientRelationAggregation, + unstableClientRelationReplacements, }); client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction", "Room.receipt", "Room.tags", From 62e69cacb7acf72c9aa0112c8d8f0e52cf7cfbf8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 14 May 2019 15:52:02 +0100 Subject: [PATCH 35/67] remove leftover code, fix lint --- src/models/event-timeline-set.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 25344ff21..b23d7c842 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -263,10 +263,6 @@ EventTimelineSet.prototype.tryReplaceEvent = function(eventId, newEvent) { } } if (oldEvent) { - let originalEvent; - if (oldEvent.isReplacement()) { - originalEvent = oldEvent.getReplacedEvent(); - } this.replaceEventId(oldEvent.getId(), newEvent.getId()); return oldEvent; } From db7a402e9b089f881a1ea8662cb280d022411d6d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 11:47:00 +0100 Subject: [PATCH 36/67] mark original event as replaced instead of replacing the event object this is more in line with what happens on the server-side, and also doesn't break existing reply relations. --- src/models/event-timeline-set.js | 25 -------------- src/models/event-timeline.js | 21 ------------ src/models/event.js | 59 +++++++++++++++----------------- src/models/room.js | 23 +++++-------- 4 files changed, 36 insertions(+), 92 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index b23d7c842..d9cc15ce4 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -243,31 +243,6 @@ EventTimelineSet.prototype.findEventById = function(eventId) { }); }; -/** - * Find an event by id which is stored in our timelines and replace it with a new event - * - * @param {string} eventId event ID to look for - * @param {string} newEvent the event to replace with - * @return {?module:models/event~MatrixEvent} the replaced event, or undefined if unknown - */ -EventTimelineSet.prototype.tryReplaceEvent = function(eventId, newEvent) { - // we can't use this._eventIdToTimeline here to find the matching timeline - // because we need to match the original event id, - // which might have been replaced by a previous m.replace already. - // for now, loop through all timelines. Maybe a separate index would be justifyable. - let oldEvent; - for(const tl of this._timelines) { - oldEvent = tl.tryReplaceEvent(eventId, newEvent); - if (oldEvent) { - break; - } - } - if (oldEvent) { - this.replaceEventId(oldEvent.getId(), newEvent.getId()); - return oldEvent; - } -}; - /** * Add a new timeline to this timeline list * diff --git a/src/models/event-timeline.js b/src/models/event-timeline.js index 5ab4eaadc..63846cebd 100644 --- a/src/models/event-timeline.js +++ b/src/models/event-timeline.js @@ -339,27 +339,6 @@ EventTimeline.prototype.addEvent = function(event, atStart) { } }; - -/** - * Replaces an event in the timeline, if it exists. - * - * @param {string} eventId the id of the existing event - * @param {boolean} newEvent the new event - * @return {MatrixEvent?} the old event, if found and replaced. - */ -EventTimeline.prototype.tryReplaceEvent = function(eventId, newEvent) { - const eventIndex = this._events.findIndex(event => event.getOriginalId() === eventId); - if (eventIndex === -1) { - return; - } - const oldEvent = this._events[eventIndex]; - this._events[eventIndex] = newEvent; - // copy over metadata, as we don't hold state for random locations in the timeline, only for the end or start - // and assuming for now that replacing an event doesn't change the sender that should be shown. - newEvent.sender = oldEvent.sender; - return oldEvent; -}; - /** * Static helper method to set sender and target properties * diff --git a/src/models/event.js b/src/models/event.js index cac716400..e705d8cb4 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -122,7 +122,7 @@ module.exports.MatrixEvent = function MatrixEvent( this.error = null; this.forwardLooking = true; this._pushActions = null; - this._replacedEvent = null; + this._replacingEvent = null; this._clearEvent = {}; @@ -209,9 +209,6 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Number} The event timestamp, e.g. 1433502692297 */ getTs: function() { - if (this._replacedEvent) { - return this._replacedEvent.getTs(); - } return this.event.origin_server_ts; }, @@ -220,9 +217,6 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Date} The event date, e.g. new Date(1433502692297) */ getDate: function() { - if (this._replacedEvent) { - return this._replacedEvent.getDate(); - } return this.event.origin_server_ts ? new Date(this.event.origin_server_ts) : null; }, @@ -232,7 +226,13 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Object} The event content JSON, or an empty object. */ getContent: function() { - return this._clearEvent.content || this.event.content || {}; + const content = this._clearEvent.content || this.event.content || {}; + if (this._replacingEvent) { + const newContent = this._replacingEvent.getContent()["m.new_content"]; + return Object.assign({}, newContent, content["m.relates_to"]); + } else { + return content; + } }, /** @@ -741,37 +741,34 @@ utils.extend(module.exports.MatrixEvent.prototype, { }, /** - * Get whether the event is a relation event. + * Get whether the event is a relation event, and of a given type if `relType` is passed in. + * + * @param {string?} relType if given, checks that the relation is of the given type * @return {boolean} */ - isRelation() { + isRelation(relType = undefined) { const content = this.getContent(); const relation = content && content["m.relates_to"]; - return relation && relation.rel_type && relation.event_id; + return relation && relation.rel_type && relation.event_id && + ((relType && relation.rel_type === relType) || !relType); }, - isReplacement() { - const content = this.getContent(); - const relation = content && content["m.relates_to"]; - return relation && relation.rel_type === "m.replace" && relation.event_id; + /** + * Set an event that replaces the content of this event, through an m.replace relation. + * + * @param {MatrixEvent} newEvent the event with the replacing content. + */ + makeReplaced(newEvent) { + this._replacingEvent = newEvent; }, - setReplacedEvent(replacedEvent) { - this._replacedEvent = replacedEvent; - }, - - getReplacedEvent() { - return this._replacedEvent; - }, - - getOriginalId() { - const content = this.getContent(); - const relation = content && content["m.relates_to"]; - if (relation && relation.rel_type === "m.replace") { - return relation.event_id; - } else { - return this.getId(); - } + /** + * Returns the event ID of the event replacing the content of this event, if any. + * + * @return {string?} + */ + replacingEventId() { + return this._replacingEvent && this._replacingEvent.getId(); }, }); diff --git a/src/models/room.js b/src/models/room.js index bb15f2921..3ea9b2a2f 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1032,22 +1032,15 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // this may be needed to trigger an update. } - if (this._opts.unstableClientRelationReplacements && event.isReplacement()) { - const replacedEventId = event.getOriginalId(); - const replacedEvent = replacedEventId && - this.getUnfilteredTimelineSet().tryReplaceEvent(replacedEventId, event); + + if (this._opts.unstableClientRelationReplacements && event.isRelation("m.replace")) { + const relatesTo = event.getContent()["m.relates_to"]; + const replacedId = relatesTo && relatesTo.event_id; + const replacedEvent = this.getUnfilteredTimelineSet().findEventById(replacedId); if (replacedEvent) { - // if this was already a replacement, get the original - let originalEvent = replacedEvent; - if (originalEvent.isReplacement()) { - originalEvent = originalEvent.getReplacedEvent(); - } - event.setReplacedEvent(originalEvent); - // report replacedEvent and not originalEvent because replaceEvent was in the timeline so far - this.emit("Room.replaceEvent", replacedEvent, event, this); + replacedEvent.makeReplaced(event); + this.emit("Room.replaceEvent", replacedEvent, this); } - // we don't add the event because the event type would get rendered - return; } if (event.getUnsigned().transaction_id) { @@ -1101,7 +1094,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { * unique transaction id. */ Room.prototype.addPendingEvent = function(event, txnId) { - if (event.isReplacement()) { + if (event.isRelation("m.replace")) { return; } From 600dff62e83cb683f220ec50cb513f2abaeac64d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 13:56:00 +0100 Subject: [PATCH 37/67] detect relations on encrypted events properly --- src/models/event.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/models/event.js b/src/models/event.js index e705d8cb4..84c209fcf 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -747,7 +747,9 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {boolean} */ isRelation(relType = undefined) { - const content = this.getContent(); + // must use event.content as m.relates_to is not encrypted + // and _clearEvent doesn't have it. + const content = this.event.content; const relation = content && content["m.relates_to"]; return relation && relation.rel_type && relation.event_id && ((relType && relation.rel_type === relType) || !relType); From a18bdad44f85491b22fb94984bb9fecb83a08aa2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 13:56:13 +0100 Subject: [PATCH 38/67] dont replace a redacted event --- src/models/event.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index 84c209fcf..130443310 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -761,6 +761,9 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @param {MatrixEvent} newEvent the event with the replacing content. */ makeReplaced(newEvent) { + if (this.isRedacted()) { + return; + } this._replacingEvent = newEvent; }, From 5fd001354ac117879a96feea0ab1a029f1df4a50 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 14:53:44 +0100 Subject: [PATCH 39/67] replace content when replacing instead of evaluating in getContent --- src/models/event.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 130443310..4c07db75e 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -226,13 +226,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Object} The event content JSON, or an empty object. */ getContent: function() { - const content = this._clearEvent.content || this.event.content || {}; - if (this._replacingEvent) { - const newContent = this._replacingEvent.getContent()["m.new_content"]; - return Object.assign({}, newContent, content["m.relates_to"]); - } else { - return content; - } + return this._clearEvent.content || this.event.content || {}; }, /** @@ -764,6 +758,13 @@ utils.extend(module.exports.MatrixEvent.prototype, { if (this.isRedacted()) { return; } + const oldContent = this.getContent(); + const newContent = newEvent.getContent()["m.new_content"]; + // need to always replace m.relates_to with the old one, + // even if there is none, as the m.replace relation should + // not be exposed on the target event m.relates_to (that's what the server does). + Object.assign(oldContent, newContent, + {"m.relates_to": oldContent["m.relates_to"]}); this._replacingEvent = newEvent; }, From 1c1781ce763adbce12b480fdf1c75ac06ce331c7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 14:54:21 +0100 Subject: [PATCH 40/67] make replacements work in e2e rooms --- src/models/event.js | 4 ++++ src/models/room.js | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 4c07db75e..6949674cb 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -758,6 +758,10 @@ utils.extend(module.exports.MatrixEvent.prototype, { if (this.isRedacted()) { return; } + if (newEvent.isBeingDecrypted()) { + throw new Error("Trying to replace event when " + + "new content hasn't been decrypted yet"); + } const oldContent = this.getContent(); const newContent = newEvent.getContent()["m.new_content"]; // need to always replace m.relates_to with the old one, diff --git a/src/models/room.js b/src/models/room.js index 3ea9b2a2f..471967549 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1038,8 +1038,16 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { const replacedId = relatesTo && relatesTo.event_id; const replacedEvent = this.getUnfilteredTimelineSet().findEventById(replacedId); if (replacedEvent) { - replacedEvent.makeReplaced(event); - this.emit("Room.replaceEvent", replacedEvent, this); + const doAndEmitReplacement = () => { + replacedEvent.makeReplaced(event); + this.emit("Room.replaceEvent", replacedEvent, this); + }; + + if (event.isBeingDecrypted()) { + event.once("Event.decrypted", doAndEmitReplacement); + } else { + doAndEmitReplacement(); + } } } From ad48d2997e3d7740c6c61e4fcce7b64c96349568 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 14:54:52 +0100 Subject: [PATCH 41/67] prevent earlier replacements from messing things up --- src/models/event.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index 6949674cb..41ddbc12c 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -758,6 +758,10 @@ utils.extend(module.exports.MatrixEvent.prototype, { if (this.isRedacted()) { return; } + // ignore previous replacements + if (this._replacingEvent && this._replacingEvent.getTs() > newEvent.getTs()) { + return; + } if (newEvent.isBeingDecrypted()) { throw new Error("Trying to replace event when " + "new content hasn't been decrypted yet"); From f17ecba5191027147e9199717ebf61c144f1f65a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 May 2019 13:42:06 +0100 Subject: [PATCH 42/67] Add `getRelation` helper This adds a `getRelation` helper to ensure we always read relation info from the wire content as required in E2E rooms. --- src/models/event-timeline-set.js | 5 ++--- src/models/event.js | 30 ++++++++++++++++++++---------- src/models/relations.js | 8 +++----- src/models/room.js | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index d9cc15ce4..3295359f7 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -719,9 +719,8 @@ EventTimelineSet.prototype._aggregateRelations = function(event) { return; } - const content = event.getContent(); - const relation = content && content["m.relates_to"]; - if (!relation || !relation.rel_type || !relation.event_id) { + const relation = event.getRelation(); + if (!relation) { return; } diff --git a/src/models/event.js b/src/models/event.js index 41ddbc12c..c6829cfac 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -735,20 +735,34 @@ utils.extend(module.exports.MatrixEvent.prototype, { }, /** - * Get whether the event is a relation event, and of a given type if `relType` is passed in. + * Get whether the event is a relation event, and of a given type if + * `relType` is passed in. * - * @param {string?} relType if given, checks that the relation is of the given type + * @param {string?} relType if given, checks that the relation is of the + * given type * @return {boolean} */ isRelation(relType = undefined) { - // must use event.content as m.relates_to is not encrypted - // and _clearEvent doesn't have it. - const content = this.event.content; + // Relation info is lifted out of the encrypted content when sent to + // encrypted rooms, so we have to check `getWireContent` for this. + const content = this.getWireContent(); const relation = content && content["m.relates_to"]; return relation && relation.rel_type && relation.event_id && ((relType && relation.rel_type === relType) || !relType); }, + /** + * Get relation info for the event, if any. + * + * @return {Object} + */ + getRelation() { + if (!this.isRelation()) { + return null; + } + return this.getWireContent()["m.relates_to"]; + }, + /** * Set an event that replaces the content of this event, through an m.replace relation. * @@ -768,11 +782,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { } const oldContent = this.getContent(); const newContent = newEvent.getContent()["m.new_content"]; - // need to always replace m.relates_to with the old one, - // even if there is none, as the m.replace relation should - // not be exposed on the target event m.relates_to (that's what the server does). - Object.assign(oldContent, newContent, - {"m.relates_to": oldContent["m.relates_to"]}); + Object.assign(oldContent, newContent); this._replacingEvent = newEvent; }, diff --git a/src/models/relations.js b/src/models/relations.js index 52193b028..a74e65bf8 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -52,9 +52,8 @@ export default class Relations extends EventEmitter { * The new relation event to be aggregated. */ addEvent(event) { - const content = event.getContent(); - const relation = content && content["m.relates_to"]; - if (!relation || !relation.rel_type || !relation.event_id) { + const relation = event.getRelation(); + if (!relation) { console.error("Event must have relation info"); return; } @@ -139,8 +138,7 @@ export default class Relations extends EventEmitter { if (this.relationType === "m.annotation") { // Remove the redacted annotation from aggregation by key - const content = redactedEvent.getContent(); - const relation = content && content["m.relates_to"]; + const relation = redactedEvent.getRelation(); if (!relation) { return; } diff --git a/src/models/room.js b/src/models/room.js index 471967549..b09e34944 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1034,7 +1034,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { if (this._opts.unstableClientRelationReplacements && event.isRelation("m.replace")) { - const relatesTo = event.getContent()["m.relates_to"]; + const relatesTo = event.getRelation(); const replacedId = relatesTo && relatesTo.event_id; const replacedEvent = this.getUnfilteredTimelineSet().findEventById(replacedId); if (replacedEvent) { From 6b9a11b6977bed490f18a7db1747b4353e6f4b6f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 May 2019 14:20:34 +0100 Subject: [PATCH 43/67] Wait until decrypt before aggregating For encrypted annotations, we need to wait until the event has been decrypted before adding it to the relations collection. --- src/models/event-timeline-set.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 3295359f7..f435d1e8c 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -719,6 +719,14 @@ EventTimelineSet.prototype._aggregateRelations = function(event) { return; } + // If the event is currently encrypted, wait until it has been decrypted. + if (event.isBeingDecrypted()) { + event.once("Event.decrypted", () => { + this._aggregateRelations(event); + }); + return; + } + const relation = event.getRelation(); if (!relation) { return; From 3f2bac71c6c657c161c853fe370d9efc948aafcb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 15 May 2019 15:52:37 +0100 Subject: [PATCH 44/67] filter out replacements for senders that are not the original sender --- src/models/room.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/room.js b/src/models/room.js index 471967549..480717be4 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1037,7 +1037,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { const relatesTo = event.getContent()["m.relates_to"]; const replacedId = relatesTo && relatesTo.event_id; const replacedEvent = this.getUnfilteredTimelineSet().findEventById(replacedId); - if (replacedEvent) { + if (replacedEvent && event.getSender() === replacedEvent.getSender()) { const doAndEmitReplacement = () => { replacedEvent.makeReplaced(event); this.emit("Room.replaceEvent", replacedEvent, this); From 80ba5d29f2b3588df796d29ab8788c70bbf4a658 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 15 May 2019 12:58:07 +0100 Subject: [PATCH 45/67] Add stringify helper to summarise events when debugging --- src/models/event.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index 41ddbc12c..4ded83646 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -784,6 +784,35 @@ utils.extend(module.exports.MatrixEvent.prototype, { replacingEventId() { return this._replacingEvent && this._replacingEvent.getId(); }, + + /** + * Summarise the event as JSON for debugging. If encrypted, include both the + * decrypted and encrypted view of the event. This is named `toJSON` for use + * with `JSON.stringify` which checks objects for functions named `toJSON` + * and will call them to customise the output if they are defined. + * + * @return {Object} + */ + toJSON() { + const event = { + type: this.getType(), + sender: this.getSender(), + content: this.getContent(), + event_id: this.getId(), + origin_server_ts: this.getTs(), + unsigned: this.getUnsigned(), + room_id: this.getRoomId(), + }; + + if (!this.isEncrypted()) { + return event; + } + + return { + decrypted: event, + encrypted: this.event, + }; + }, }); From 21e1312dd7516b91589ae0650a10b985c2a241a6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 May 2019 10:35:36 +0100 Subject: [PATCH 46/67] Allow relations into the pending event list Relations actually should go into the pending event list, just like messages. This is the easiest way to keep them in a holding area in case of unverified devices, etc. We still want the relations to local echo immediately, so we directly trigger the aggregation on the timeline sets. --- src/models/event-timeline-set.js | 6 +++--- src/models/room.js | 22 +++++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index f435d1e8c..4947748a1 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -540,7 +540,7 @@ EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, timeline.addEvent(event, toStartOfTimeline); this._eventIdToTimeline[eventId] = timeline; - this._aggregateRelations(event); + this.aggregateRelations(event); const data = { timeline: timeline, @@ -714,7 +714,7 @@ EventTimelineSet.prototype.getRelationsForEvent = function( * @param {MatrixEvent} event * The new relation event to be aggregated. */ -EventTimelineSet.prototype._aggregateRelations = function(event) { +EventTimelineSet.prototype.aggregateRelations = function(event) { if (!this._unstableClientRelationAggregation) { return; } @@ -722,7 +722,7 @@ EventTimelineSet.prototype._aggregateRelations = function(event) { // If the event is currently encrypted, wait until it has been decrypted. if (event.isBeingDecrypted()) { event.once("Event.decrypted", () => { - this._aggregateRelations(event); + this.aggregateRelations(event); }); return; } diff --git a/src/models/room.js b/src/models/room.js index fd55a7f51..b0a67053e 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1127,16 +1127,28 @@ Room.prototype.addPendingEvent = function(event, txnId) { this._txnToEvent[txnId] = event; - // TODO: We currently ignore `pendingEventOrdering` for relation events. - // They are aggregated by the timeline set, and we want that to happen right - // away for easy local echo, but it complicates what should be a general - // code path by branching on the event type. - if (!event.isRelation() && this._opts.pendingEventOrdering == "detached") { + if (this._opts.pendingEventOrdering == "detached") { if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) { console.warn("Setting event as NOT_SENT due to messages in the same state"); event.status = EventStatus.NOT_SENT; } this._pendingEventList.push(event); + + // For pending events, add them to the relations collection immediately. + // (The alternate case below already covers this as part of adding to + // the timeline set.) + // TODO: We should consider whether this means it would be a better + // design to lift the relations handling up to the room instead. + for (let i = 0; i < this._timelineSets.length; i++) { + const timelineSet = this._timelineSets[i]; + if (timelineSet.getFilter()) { + if (this._filter.filterRoomTimeline([event]).length) { + timelineSet.aggregateRelations(event); + } + } else { + timelineSet.aggregateRelations(event); + } + } } else { for (let i = 0; i < this._timelineSets.length; i++) { const timelineSet = this._timelineSets[i]; From 875c6b973bcfe86da19a3aea870e228093ecf642 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 May 2019 12:28:13 +0100 Subject: [PATCH 47/67] Remove cancelled relations from the relations collection This listens for event status changes on sending events in case they might be cancelled and removes them from aggregation if so. Part of https://github.com/vector-im/riot-web/issues/9731 --- src/client.js | 4 +- src/models/event.js | 22 +++++++- src/models/relations.js | 120 +++++++++++++++++++++++++++++++--------- src/models/room.js | 4 +- 4 files changed, 120 insertions(+), 30 deletions(-) diff --git a/src/client.js b/src/client.js index 860debc4b..c95583600 100644 --- a/src/client.js +++ b/src/client.js @@ -1723,7 +1723,7 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId, content: content, }); localEvent._txnId = txnId; - localEvent.status = EventStatus.SENDING; + localEvent.setStatus(EventStatus.SENDING); // add this event immediately to the local store as 'sending'. if (room) { @@ -1853,7 +1853,7 @@ function _updatePendingEventStatus(room, event, newStatus) { if (room) { room.updatePendingEvent(event, newStatus); } else { - event.status = newStatus; + event.setStatus(newStatus); } } diff --git a/src/models/event.js b/src/models/event.js index 753dc2b63..f01614061 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -731,7 +731,27 @@ utils.extend(module.exports.MatrixEvent.prototype, { handleRemoteEcho: function(event) { this.event = event; // successfully sent. - this.status = null; + this.setStatus(null); + }, + + /** + * Whether the event is in any phase of sending, send failure, waiting for + * remote echo, etc. + * + * @return {boolean} + */ + isSending() { + return !!this.status; + }, + + /** + * Update the event's sending status and emit an event as well. + * + * @param {String} status The new status + */ + setStatus(status) { + this.status = status; + this.emit("Event.status", this, status); }, /** diff --git a/src/models/relations.js b/src/models/relations.js index a74e65bf8..5d04d287f 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -15,6 +15,7 @@ limitations under the License. */ import EventEmitter from 'events'; +import { EventStatus } from '../../lib/models/event'; /** * A container for relation events that supports easy access to common ways of @@ -49,9 +50,55 @@ export default class Relations extends EventEmitter { * Add relation events to this collection. * * @param {MatrixEvent} event - * The new relation event to be aggregated. + * The new relation event to be added. */ addEvent(event) { + if (this._relations.has(event)) { + return; + } + + const relation = event.getRelation(); + if (!relation) { + console.error("Event must have relation info"); + return; + } + + const relationType = relation.rel_type; + const eventType = event.getType(); + + if (this.relationType !== relationType || this.eventType !== eventType) { + console.error("Event relation info doesn't match this container"); + return; + } + + // If the event is in the process of being sent, listen for cancellation + // so we can remove the event from the collection. + if (event.isSending()) { + event.on("Event.status", this._onEventStatus); + } + + if (this.relationType === "m.annotation") { + this._addAnnotationToAggregation(event); + } + + this._relations.add(event); + + event.on("Event.beforeRedaction", this._onBeforeRedaction); + + this.emit("Relations.add", event); + } + + /** + * Remove relation event from this collection. + * + * @param {MatrixEvent} event + * The relation event to remove. + */ + _removeEvent(event) { + if (!this._relations.has(event)) { + return; + } + const relation = event.getRelation(); if (!relation) { console.error("Event must have relation info"); @@ -67,15 +114,32 @@ export default class Relations extends EventEmitter { } if (this.relationType === "m.annotation") { - const key = relation.key; - this._aggregateAnnotation(key, event); + this._removeAnnotationFromAggregation(event); } - this._relations.add(event); + this._relations.delete(event); - event.on("Event.beforeRedaction", this._onBeforeRedaction); + this.emit("Relations.remove", event); + } - this.emit("Relations.add", event); + /** + * Listens for event status changes to remove cancelled events. + * + * @param {MatrixEvent} event The event whose status has changed + * @param {EventStatus} status The new status + */ + _onEventStatus = (event, status) => { + if (!event.isSending()) { + // Sending is done, so we don't need to listen anymore + event.removeListener("Event.status", this._onEventStatus); + return; + } + if (status !== EventStatus.CANCELLED) { + return; + } + // Event was cancelled, remove from the collection + event.removeListener("Event.status", this._onEventStatus); + this._removeEvent(event); } /** @@ -92,7 +156,8 @@ export default class Relations extends EventEmitter { return [...this._relations]; } - _aggregateAnnotation(key, event) { + _addAnnotationToAggregation(event) { + const { key } = event.getRelation(); if (!key) { return; } @@ -120,6 +185,28 @@ export default class Relations extends EventEmitter { eventsFromSender.push(event); } + _removeAnnotationFromAggregation(event) { + const { key } = event.getRelation(); + if (!key) { + return; + } + + const eventsForKey = this._annotationsByKey[key]; + if (!eventsForKey) { + return; + } + eventsForKey.delete(event); + + // Re-sort the [key, events] pairs in descending order of event count + this._sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.size - aEvents.size; + }); + + // TODO: Remove from events by sender if needed + } + /** * For relations that have been redacted, we want to remove them from * aggregation data sets and emit an update event. @@ -138,24 +225,7 @@ export default class Relations extends EventEmitter { if (this.relationType === "m.annotation") { // Remove the redacted annotation from aggregation by key - const relation = redactedEvent.getRelation(); - if (!relation) { - return; - } - - const key = relation.key; - const eventsForKey = this._annotationsByKey[key]; - if (!eventsForKey) { - return; - } - eventsForKey.delete(redactedEvent); - - // Re-sort the [key, events] pairs in descending order of event count - this._sortedAnnotationsByKey.sort((a, b) => { - const aEvents = a[1]; - const bEvents = b[1]; - return bEvents.size - aEvents.size; - }); + this._removeAnnotationFromAggregation(redactedEvent); } redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction); diff --git a/src/models/room.js b/src/models/room.js index b0a67053e..f8d7bdc69 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1130,7 +1130,7 @@ Room.prototype.addPendingEvent = function(event, txnId) { if (this._opts.pendingEventOrdering == "detached") { if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) { console.warn("Setting event as NOT_SENT due to messages in the same state"); - event.status = EventStatus.NOT_SENT; + event.setStatus(EventStatus.NOT_SENT); } this._pendingEventList.push(event); @@ -1287,7 +1287,7 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) { newStatus); } - event.status = newStatus; + event.setStatus(newStatus); if (newStatus == EventStatus.SENT) { // update the event id From 51fb5c4a15dddfb5f5041daf341b51d83bd6d2bc Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 16 May 2019 12:38:09 +0100 Subject: [PATCH 48/67] Update aggregation by sender on remove This updates the aggregation by sender in the relations collection on removal. It also changes this aggregation to use a Set, so consumers will need to update. --- src/models/relations.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/models/relations.js b/src/models/relations.js index 5d04d287f..79ce5d10a 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -179,10 +179,10 @@ export default class Relations extends EventEmitter { const sender = event.getSender(); let eventsFromSender = this._annotationsBySender[sender]; if (!eventsFromSender) { - eventsFromSender = this._annotationsBySender[sender] = []; + eventsFromSender = this._annotationsBySender[sender] = new Set(); } - // Add the new event to the list for this sender - eventsFromSender.push(event); + // Add the new event to the set for this sender + eventsFromSender.add(event); } _removeAnnotationFromAggregation(event) { @@ -192,19 +192,22 @@ export default class Relations extends EventEmitter { } const eventsForKey = this._annotationsByKey[key]; - if (!eventsForKey) { - return; + if (eventsForKey) { + eventsForKey.delete(event); + + // Re-sort the [key, events] pairs in descending order of event count + this._sortedAnnotationsByKey.sort((a, b) => { + const aEvents = a[1]; + const bEvents = b[1]; + return bEvents.size - aEvents.size; + }); } - eventsForKey.delete(event); - // Re-sort the [key, events] pairs in descending order of event count - this._sortedAnnotationsByKey.sort((a, b) => { - const aEvents = a[1]; - const bEvents = b[1]; - return bEvents.size - aEvents.size; - }); - - // TODO: Remove from events by sender if needed + const sender = event.getSender(); + const eventsFromSender = this._annotationsBySender[sender]; + if (eventsFromSender) { + eventsFromSender.delete(event); + } } /** @@ -263,7 +266,7 @@ export default class Relations extends EventEmitter { * This is currently only supported for the annotation relation type. * * @return {Object} - * An object with each relation sender as a key and the matching list of + * An object with each relation sender as a key and the matching Set of * events for that sender as a value. */ getAnnotationsBySender() { From dde4f558f317592c811def1c4ed1082d8d058621 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:33:04 +0100 Subject: [PATCH 49/67] set targetEvent on Relations, once known --- src/models/event-timeline-set.js | 2 +- src/models/relations.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 4947748a1..e2fad2afc 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -759,7 +759,7 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { } } - relationsWithEventType.addEvent(event); + relationsWithEventType.addEvent(event, relatesToEvent); }; /** diff --git a/src/models/relations.js b/src/models/relations.js index 79ce5d10a..02dfaf602 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -44,15 +44,17 @@ export default class Relations extends EventEmitter { this._annotationsByKey = {}; this._annotationsBySender = {}; this._sortedAnnotationsByKey = []; + this._targetEvent = null; } /** * Add relation events to this collection. * * @param {MatrixEvent} event + * @param {MatrixEvent?} targetEvent the event related to, if available. * The new relation event to be added. */ - addEvent(event) { + addEvent(event, targetEvent) { if (this._relations.has(event)) { return; } @@ -63,6 +65,12 @@ export default class Relations extends EventEmitter { return; } + // set the event these relations point to + // so m.replace can update the event + if (!this._targetEvent && targetEvent) { + this._targetEvent = targetEvent; + } + const relationType = relation.rel_type; const eventType = event.getType(); From 07572d1e8d53258ffbdf922a492abc356b247a12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:34:08 +0100 Subject: [PATCH 50/67] helper method to get last valid replacement --- src/models/relations.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/models/relations.js b/src/models/relations.js index 02dfaf602..82bd3b572 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -285,4 +285,34 @@ export default class Relations extends EventEmitter { return this._annotationsBySender; } + + /** + * Returns the most recent (and allowed) m.replace relation, if any. + * + * This is currently only supported for the m.replace relation type, + * once the target event is known, see `addEvent`. + * + * @return {MatrixEvent?} + */ + getLastReplacement() { + if (this.relationType !== "m.replace") { + // Aggregating on last only makes sense for this relation type + return null; + } + if (!this._targetEvent) { + // Don't know which replacements to accept yet. + // This method shouldn't be called before the original + // event is known anyway. + return null; + } + return this.getRelations().reduce((last, event) => { + if (event.getSender() !== this._targetEvent.getSender()) { + return last; + } + if (last && last.getTs() > event.getTs()) { + return last; + } + return event; + }, null); + } } From 33e9eb371eaa026ec34c09d8e48cc265efaa6fe9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:35:50 +0100 Subject: [PATCH 51/67] use relation handling in timelineset for replacements --- src/models/event-timeline-set.js | 7 ++++++- src/models/room.js | 19 ------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index e2fad2afc..055ed5c4b 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -747,19 +747,24 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { relationsWithRelType = relationsForEvent[relationType] = {}; } let relationsWithEventType = relationsWithRelType[eventType]; + const relatesToEvent = this.findEventById(relatesToEventId); if (!relationsWithEventType) { relationsWithEventType = relationsWithRelType[eventType] = new Relations( relationType, eventType, this.room, ); - const relatesToEvent = this.findEventById(relatesToEventId); if (relatesToEvent) { relatesToEvent.emit("Event.relationsCreated", relationType, eventType); } } relationsWithEventType.addEvent(event, relatesToEvent); + + if (relationType === "m.replace" && relatesToEvent) { + const replacement = relationsWithEventType.getLastReplacement(); + relatesToEvent.makeReplaced(replacement); + } }; /** diff --git a/src/models/room.js b/src/models/room.js index f8d7bdc69..4f488dab1 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1032,25 +1032,6 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { // this may be needed to trigger an update. } - - if (this._opts.unstableClientRelationReplacements && event.isRelation("m.replace")) { - const relatesTo = event.getRelation(); - const replacedId = relatesTo && relatesTo.event_id; - const replacedEvent = this.getUnfilteredTimelineSet().findEventById(replacedId); - if (replacedEvent && event.getSender() === replacedEvent.getSender()) { - const doAndEmitReplacement = () => { - replacedEvent.makeReplaced(event); - this.emit("Room.replaceEvent", replacedEvent, this); - }; - - if (event.isBeingDecrypted()) { - event.once("Event.decrypted", doAndEmitReplacement); - } else { - doAndEmitReplacement(); - } - } - } - if (event.getUnsigned().transaction_id) { const existingEvent = this._txnToEvent[event.getUnsigned().transaction_id]; if (existingEvent) { From 3cd2b3925ab97ff736b329da7b3f5eac42557ca0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:36:38 +0100 Subject: [PATCH 52/67] re-evaluate last replacement on redaction and local echo cancellation --- src/models/relations.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/models/relations.js b/src/models/relations.js index 82bd3b572..2e2639e2d 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -121,12 +121,14 @@ export default class Relations extends EventEmitter { return; } + this._relations.delete(event); + if (this.relationType === "m.annotation") { this._removeAnnotationFromAggregation(event); + } else if (this.relationType === "m.replace" && this._targetEvent) { + this._targetEvent.makeReplaced(this.getLastReplacement()); } - this._relations.delete(event); - this.emit("Relations.remove", event); } @@ -234,9 +236,13 @@ export default class Relations extends EventEmitter { return; } + this._relations.delete(redactedEvent); + if (this.relationType === "m.annotation") { // Remove the redacted annotation from aggregation by key this._removeAnnotationFromAggregation(redactedEvent); + } else if (this.relationType === "m.replace" && this._targetEvent) { + this._targetEvent.makeReplaced(this.getLastReplacement()); } redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction); From 69d25c1498afc08f6555b135b86680568b4ffa58 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:37:49 +0100 Subject: [PATCH 53/67] emit from MatrixEvent.makeReplaced instead of Room now that event can be replaced from Relations instead of Room Also make `makeReplaced` non-destructive by not touching the original event.content, so it can be undone by later calls. --- src/client.js | 4 ++++ src/models/event.js | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/client.js b/src/client.js index c95583600..f0e6fe074 100644 --- a/src/client.js +++ b/src/client.js @@ -4168,6 +4168,10 @@ function _PojoToMatrixEventMapper(client) { ]); event.attemptDecryption(client._crypto); } + const room = client.getRoom(event.getRoomId()); + if (room) { + room.reEmitter.reEmit(event, ["Event.replaced"]); + } return event; } return mapper; diff --git a/src/models/event.js b/src/models/event.js index f01614061..54c39c194 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -226,7 +226,12 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Object} The event content JSON, or an empty object. */ getContent: function() { - return this._clearEvent.content || this.event.content || {}; + if (this._replacingEvent && !this.isRedacted()) { + return this._replacingEvent.getContent()["m.new_content"] || {}; + // content = Object.assign({}, content, newContent); + } else { + return this._clearEvent.content || this.event.content || {}; + } }, /** @@ -786,24 +791,17 @@ utils.extend(module.exports.MatrixEvent.prototype, { /** * Set an event that replaces the content of this event, through an m.replace relation. * - * @param {MatrixEvent} newEvent the event with the replacing content. + * @param {MatrixEvent?} newEvent the event with the replacing content, if any. */ makeReplaced(newEvent) { if (this.isRedacted()) { return; } - // ignore previous replacements - if (this._replacingEvent && this._replacingEvent.getTs() > newEvent.getTs()) { - return; + if (this._replacingEvent !== newEvent) { + this._replacingEvent = newEvent; + this.emit("Event.replaced", this); } - if (newEvent.isBeingDecrypted()) { - throw new Error("Trying to replace event when " + - "new content hasn't been decrypted yet"); } - const oldContent = this.getContent(); - const newContent = newEvent.getContent()["m.new_content"]; - Object.assign(oldContent, newContent); - this._replacingEvent = newEvent; }, /** From 266d0f9d057d16a10409e278450367f246e29e40 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:40:17 +0100 Subject: [PATCH 54/67] remove flag for edits as it's not handled separately anymore --- src/client.js | 6 ------ src/models/room.js | 5 ----- src/sync.js | 2 -- 3 files changed, 13 deletions(-) diff --git a/src/client.js b/src/client.js index f0e6fe074..aabc7f288 100644 --- a/src/client.js +++ b/src/client.js @@ -153,11 +153,6 @@ function keyFromRecoverySession(session, decryptionKey) { * via `EventTimelineSet#getRelationsForEvent`. * This feature is currently unstable and the API may change without notice. * - * @param {boolean} [opts.unstableClientRelationReplacements = false] - * Optional. Set to true to enable client-side handling of m.replace event relations, - * exposed through the `Room.replaceEvent` event. - * This feature is currently unstable and the API may change without notice. - * * @param {Array} [opts.verificationMethods] Optional. The verification method * that the application can handle. Each element should be an item from {@link * module:crypto~verificationMethods verificationMethods}, or a class that @@ -224,7 +219,6 @@ function MatrixClient(opts) { this.urlPreviewCache = {}; this._notifTimelineSet = null; this.unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation; - this.unstableClientRelationReplacements = !!opts.unstableClientRelationReplacements; this._crypto = null; this._cryptoStore = opts.cryptoStore; diff --git a/src/models/room.js b/src/models/room.js index 4f488dab1..daafd3b3b 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -99,11 +99,6 @@ function synthesizeReceipt(userId, event, receiptType) { * via `EventTimelineSet#getRelationsForEvent`. * This feature is currently unstable and the API may change without notice. * - * @param {boolean} [opts.unstableClientRelationReplacements = false] - * Optional. Set to true to enable client-side handling of m.replace event relations, - * exposed through the `Room.replaceEvent` event. - * This feature is currently unstable and the API may change without notice. - * * @prop {string} roomId The ID of this room. * @prop {string} name The human-readable display name for this room. * @prop {Array} timeline The live event timeline for this room, diff --git a/src/sync.js b/src/sync.js index b180c1dd0..7e3cbf817 100644 --- a/src/sync.js +++ b/src/sync.js @@ -119,14 +119,12 @@ SyncApi.prototype.createRoom = function(roomId) { const { timelineSupport, unstableClientRelationAggregation, - unstableClientRelationReplacements, } = client; const room = new Room(roomId, client, client.getUserId(), { lazyLoadMembers: this.opts.lazyLoadMembers, pendingEventOrdering: this.opts.pendingEventOrdering, timelineSupport, unstableClientRelationAggregation, - unstableClientRelationReplacements, }); client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction", "Room.receipt", "Room.tags", From 21c8c76dc336831ecdb034813d3e37a97e231029 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:40:39 +0100 Subject: [PATCH 55/67] helper method to get sending status of event or replacement --- src/models/event.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index 54c39c194..e2fdb30af 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -801,6 +801,18 @@ utils.extend(module.exports.MatrixEvent.prototype, { this._replacingEvent = newEvent; this.emit("Event.replaced", this); } + }, + + /** + * Returns the status of the event, or the replacing event in case `makeReplace` has been called. + * + * @return {EventStatus} + */ + replacementOrOwnStatus() { + if (this._replacingEvent) { + return this._replacingEvent.status; + } else { + return this.status; } }, From 261ab7ae68f54f3628203a3c55f489cbfdde4463 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:40:55 +0100 Subject: [PATCH 56/67] don't block local echo for m.replace anymore! --- src/models/room.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index daafd3b3b..3b31cba36 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1078,10 +1078,6 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) { * unique transaction id. */ Room.prototype.addPendingEvent = function(event, txnId) { - if (event.isRelation("m.replace")) { - return; - } - if (event.status !== EventStatus.SENDING) { throw new Error("addPendingEvent called on an event with status " + event.status); From e23ba50dd8a9d46f67bf0167ed7901bcdadffa40 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 14:42:09 +0100 Subject: [PATCH 57/67] only call aggregation code for local echo for relations --- src/models/room.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/models/room.js b/src/models/room.js index 3b31cba36..209b392b4 100644 --- a/src/models/room.js +++ b/src/models/room.js @@ -1106,19 +1106,21 @@ Room.prototype.addPendingEvent = function(event, txnId) { } this._pendingEventList.push(event); - // For pending events, add them to the relations collection immediately. - // (The alternate case below already covers this as part of adding to - // the timeline set.) - // TODO: We should consider whether this means it would be a better - // design to lift the relations handling up to the room instead. - for (let i = 0; i < this._timelineSets.length; i++) { - const timelineSet = this._timelineSets[i]; - if (timelineSet.getFilter()) { - if (this._filter.filterRoomTimeline([event]).length) { + if (event.isRelation()) { + // For pending events, add them to the relations collection immediately. + // (The alternate case below already covers this as part of adding to + // the timeline set.) + // TODO: We should consider whether this means it would be a better + // design to lift the relations handling up to the room instead. + for (let i = 0; i < this._timelineSets.length; i++) { + const timelineSet = this._timelineSets[i]; + if (timelineSet.getFilter()) { + if (this._filter.filterRoomTimeline([event]).length) { + timelineSet.aggregateRelations(event); + } + } else { timelineSet.aggregateRelations(event); } - } else { - timelineSet.aggregateRelations(event); } } } else { From bddd03c2fdc6c5cb0863ac9b6c701ffc515489ed Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 15:27:14 +0100 Subject: [PATCH 58/67] separate setTargetEvent method call makeReplaced from addEvent instead so it's all done from Relations --- src/models/event-timeline-set.js | 11 ++++------- src/models/relations.js | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 055ed5c4b..ffa75952a 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -747,24 +747,21 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { relationsWithRelType = relationsForEvent[relationType] = {}; } let relationsWithEventType = relationsWithRelType[eventType]; - const relatesToEvent = this.findEventById(relatesToEventId); + if (!relationsWithEventType) { relationsWithEventType = relationsWithRelType[eventType] = new Relations( relationType, eventType, this.room, ); + const relatesToEvent = this.findEventById(relatesToEventId); if (relatesToEvent) { + relationsWithEventType.setTargetEvent(relatesToEvent); relatesToEvent.emit("Event.relationsCreated", relationType, eventType); } } - relationsWithEventType.addEvent(event, relatesToEvent); - - if (relationType === "m.replace" && relatesToEvent) { - const replacement = relationsWithEventType.getLastReplacement(); - relatesToEvent.makeReplaced(replacement); - } + relationsWithEventType.addEvent(event); }; /** diff --git a/src/models/relations.js b/src/models/relations.js index 2e2639e2d..6c7d38664 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -51,10 +51,9 @@ export default class Relations extends EventEmitter { * Add relation events to this collection. * * @param {MatrixEvent} event - * @param {MatrixEvent?} targetEvent the event related to, if available. * The new relation event to be added. */ - addEvent(event, targetEvent) { + addEvent(event) { if (this._relations.has(event)) { return; } @@ -65,12 +64,6 @@ export default class Relations extends EventEmitter { return; } - // set the event these relations point to - // so m.replace can update the event - if (!this._targetEvent && targetEvent) { - this._targetEvent = targetEvent; - } - const relationType = relation.rel_type; const eventType = event.getType(); @@ -85,12 +78,14 @@ export default class Relations extends EventEmitter { event.on("Event.status", this._onEventStatus); } + this._relations.add(event); + if (this.relationType === "m.annotation") { this._addAnnotationToAggregation(event); + } else if (this.relationType === "m.replace" && this._targetEvent) { + this._targetEvent.makeReplaced(this.getLastReplacement()); } - this._relations.add(event); - event.on("Event.beforeRedaction", this._onBeforeRedaction); this.emit("Relations.add", event); @@ -321,4 +316,13 @@ export default class Relations extends EventEmitter { return event; }, null); } + /* + * @param {MatrixEvent} targetEvent the event related to. + */ + setTargetEvent(event) { + this._targetEvent = event; + if (this.relationType === "m.replace") { + this._targetEvent.makeReplaced(this.getLastReplacement()); + } + } } From 68e9be47d9c401e0b1400250d34f708ec7e0a612 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 15:49:55 +0100 Subject: [PATCH 59/67] check if an incoming event is the target of a Relations --- src/models/event-timeline-set.js | 30 ++++++++++++++++++++++++++++++ src/models/relations.js | 14 ++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index ffa75952a..3b7608644 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -540,6 +540,7 @@ EventTimelineSet.prototype.addEventToTimeline = function(event, timeline, timeline.addEvent(event, toStartOfTimeline); this._eventIdToTimeline[eventId] = timeline; + this.setRelationsTarget(event); this.aggregateRelations(event); const data = { @@ -708,6 +709,35 @@ EventTimelineSet.prototype.getRelationsForEvent = function( return relationsWithRelType[eventType]; }; + +/** + * Set an event as the target event if any Relations exist for it already + * + * @param {MatrixEvent} event + * The event to check as relation target. + */ +EventTimelineSet.prototype.setRelationsTarget = function(event) { + if (!this._unstableClientRelationAggregation) { + return; + } + + const relationsForEvent = this._relations[event.getId()]; + if (!relationsForEvent) { + return; + } + // don't need it for non m.replace relations for now + const relationsWithRelType = relationsForEvent["m.replace"]; + if (!relationsWithRelType) { + return; + } + // only doing replacements for messages for now (e.g. edits) + const relationsWithEventType = relationsWithRelType["m.room.message"]; + + if (relationsWithEventType) { + relationsWithEventType.setTargetEvent(event); + } +}; + /** * Add relation events to the relevant relation collection. * diff --git a/src/models/relations.js b/src/models/relations.js index 6c7d38664..29053bfb2 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -316,13 +316,19 @@ export default class Relations extends EventEmitter { return event; }, null); } + /* - * @param {MatrixEvent} targetEvent the event related to. + * @param {MatrixEvent} targetEvent the event the relations are related to. */ setTargetEvent(event) { - this._targetEvent = event; - if (this.relationType === "m.replace") { - this._targetEvent.makeReplaced(this.getLastReplacement()); + if (!this._targetEvent) { + this._targetEvent = event; + if (this.relationType === "m.replace") { + const replacement = this.getLastReplacement(); + if (replacement) { + this._targetEvent.makeReplaced(); + } + } } } } From d10b348e74b485bac762d32102a145bbff2061d8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 15:53:46 +0100 Subject: [PATCH 60/67] clear event replacement on redaction as redaction supersedes a replacement --- src/models/event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/event.js b/src/models/event.js index e2fdb30af..605c67176 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -226,7 +226,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @return {Object} The event content JSON, or an empty object. */ getContent: function() { - if (this._replacingEvent && !this.isRedacted()) { + if (this._replacingEvent) { return this._replacingEvent.getContent()["m.new_content"] || {}; // content = Object.assign({}, content, newContent); } else { @@ -669,6 +669,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { this.emit("Event.beforeRedaction", this, redaction_event); + this._replacingEvent = null; // we attempt to replicate what we would see from the server if // the event had been redacted before we saw it. // From 74438716afb64ea3724fb83108f0112627c8647d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 15:54:15 +0100 Subject: [PATCH 61/67] PR feedback/cleanup --- src/models/event.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index 605c67176..d4ea1bac4 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -228,7 +228,6 @@ utils.extend(module.exports.MatrixEvent.prototype, { getContent: function() { if (this._replacingEvent) { return this._replacingEvent.getContent()["m.new_content"] || {}; - // content = Object.assign({}, content, newContent); } else { return this._clearEvent.content || this.event.content || {}; } @@ -805,7 +804,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { }, /** - * Returns the status of the event, or the replacing event in case `makeReplace` has been called. + * Returns the status of the event, or the replacing event in case `makeReplace` has been called. * * @return {EventStatus} */ From 0b87a573b3cbc797e58049cd671725bc36ebe175 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 16:14:19 +0100 Subject: [PATCH 62/67] reduce indenting by returning early, also actually pass replacement! also add comment --- src/models/relations.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/models/relations.js b/src/models/relations.js index 29053bfb2..aa5c17d3f 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -321,13 +321,16 @@ export default class Relations extends EventEmitter { * @param {MatrixEvent} targetEvent the event the relations are related to. */ setTargetEvent(event) { - if (!this._targetEvent) { - this._targetEvent = event; - if (this.relationType === "m.replace") { - const replacement = this.getLastReplacement(); - if (replacement) { - this._targetEvent.makeReplaced(); - } + if (this._targetEvent) { + return; + } + this._targetEvent = event; + if (this.relationType === "m.replace") { + const replacement = this.getLastReplacement(); + // this is the initial update, so only call it if we already have something + // to not emit Event.replaced needlessly + if (replacement) { + this._targetEvent.makeReplaced(replacement); } } } From 1f2a701acedc57d738b2286786956fa43922b0bc Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 16:14:53 +0100 Subject: [PATCH 63/67] remove double newline --- src/models/event-timeline-set.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 3b7608644..c966cb2a1 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -709,7 +709,6 @@ EventTimelineSet.prototype.getRelationsForEvent = function( return relationsWithRelType[eventType]; }; - /** * Set an event as the target event if any Relations exist for it already * From fd01ba1fcf5bf43727e8c974187ea709beab7cc3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 17 May 2019 10:08:27 +0100 Subject: [PATCH 64/67] More logging when signature verification fails Still no luck diagnosing https://github.com/vector-im/riot-web/issues/9357 so adding more logging. --- src/crypto/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/crypto/index.js b/src/crypto/index.js index 22f3c99d6..0e6af4f09 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -378,7 +378,11 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) { ); sigInfo.valid = true; } catch (e) { - logger.info("Bad signature from key ID " + keyId, e); + logger.info( + "Bad signature from key ID " + keyId + " userID " + this._userId + + " device ID " + device.deviceId + " fingerprint: " + + device.getFingerprint(), backupInfo.auth_data, e, + ); sigInfo.valid = false; } } else { From ff9505073f44cac28fbb09ab5f90ba9d37f5ebef Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 11:56:28 +0100 Subject: [PATCH 65/67] add method to retrieve replacing event --- src/models/event.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/models/event.js b/src/models/event.js index d4ea1bac4..285583d1e 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -825,6 +825,15 @@ utils.extend(module.exports.MatrixEvent.prototype, { return this._replacingEvent && this._replacingEvent.getId(); }, + /** + * Returns the event replacing the content of this event, if any. + * + * @return {string?} + */ + replacingEvent() { + return this._replacingEvent; + }, + /** * Summarise the event as JSON for debugging. If encrypted, include both the * decrypted and encrypted view of the event. This is named `toJSON` for use From 7720c72b73f6ab422fce9c0a157dcd912a09f7df Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 12:03:36 +0100 Subject: [PATCH 66/67] correct return type --- src/models/event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/event.js b/src/models/event.js index 285583d1e..ad25fd2a0 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -828,7 +828,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { /** * Returns the event replacing the content of this event, if any. * - * @return {string?} + * @return {MatrixEvent?} */ replacingEvent() { return this._replacingEvent; From aa70687d9e7422b7afd722eaf1f00c96cbc34c5d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 13:25:21 +0100 Subject: [PATCH 67/67] allow access to unreplaced message content --- src/models/event.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/models/event.js b/src/models/event.js index ad25fd2a0..4a545f6ac 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -221,7 +221,19 @@ utils.extend(module.exports.MatrixEvent.prototype, { }, /** - * Get the (decrypted, if necessary) event content JSON. + * Get the (decrypted, if necessary) event content JSON, even if the event + * was replaced by another event. + * + * @return {Object} The event content JSON, or an empty object. + */ + getOriginalContent: function() { + return this._clearEvent.content || this.event.content || {}; + }, + + /** + * Get the (decrypted, if necessary) event content JSON, + * or the content from the replacing event, if any. + * See `makeReplaced`. * * @return {Object} The event content JSON, or an empty object. */ @@ -229,7 +241,7 @@ utils.extend(module.exports.MatrixEvent.prototype, { if (this._replacingEvent) { return this._replacingEvent.getContent()["m.new_content"] || {}; } else { - return this._clearEvent.content || this.event.content || {}; + return this.getOriginalContent(); } },