From 88b328df7ecf3438354bfd109b66b0ce05d8c83e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 11 May 2021 15:03:03 +0100 Subject: [PATCH 1/8] Prepare changelog for v11.0.0-rc.1 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5dc8a32f..18f733644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +Changes in [11.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.0.0-rc.1) (2021-05-11) +============================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0...v11.0.0-rc.1) + +BREAKING CHANGES +--- + + * `MatrixCall` and related APIs have been redesigned to support multiple streams + (see [\#1660](https://github.com/matrix-org/matrix-js-sdk/pull/1660) for more details) + +All changes +--- + + * Switch from MSC1772 unstable prefixes to stable + [\#1679](https://github.com/matrix-org/matrix-js-sdk/pull/1679) + * Update the VoIP example to work with the new changes + [\#1680](https://github.com/matrix-org/matrix-js-sdk/pull/1680) + * Bump hosted-git-info from 2.8.8 to 2.8.9 + [\#1687](https://github.com/matrix-org/matrix-js-sdk/pull/1687) + * Support for multiple streams (not MSC3077) + [\#1660](https://github.com/matrix-org/matrix-js-sdk/pull/1660) + * Tweak missing m.room.create errors to describe their source + [\#1683](https://github.com/matrix-org/matrix-js-sdk/pull/1683) + Changes in [10.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.1.0) (2021-05-10) ================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0-rc.1...v10.1.0) From 684511ff069337a70c0438edb55a9a3c1861a178 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 11 May 2021 15:03:04 +0100 Subject: [PATCH 2/8] v11.0.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d9ed10c73..2e63d93fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "10.1.0", + "version": "11.0.0-rc.1", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", @@ -28,7 +28,7 @@ "keywords": [ "matrix-org" ], - "main": "./src/index.ts", + "main": "./lib/index.js", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -108,5 +108,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From 91bbf7d1a81f6af3ac6c608b587ebd1078a463ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 12 May 2021 07:47:48 +0200 Subject: [PATCH 3/8] Fix glare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ล imon Brandner --- src/webrtc/call.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index d84be56b7..e6be8491c 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1506,7 +1506,8 @@ export class MatrixCall extends EventEmitter { } // Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds() - this.stopAllMedia(); + // We don't stop media if the call was replaced as we want to re-use streams in the successor + if (hangupReason !== CallErrorCode.Replaced) this.stopAllMedia(); this.deleteAllFeeds(); this.hangupParty = hangupParty; From 7203c5aaf0b804ae029b7c0e11d48746017bc27a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 17 May 2021 13:30:07 +0100 Subject: [PATCH 4/8] Prepare changelog for v11.0.0 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18f733644..466c840a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [11.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.0.0) (2021-05-17) +================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v11.0.0-rc.1...v11.0.0) + + * [Release] Fix regressed glare + [\#1695](https://github.com/matrix-org/matrix-js-sdk/pull/1695) + Changes in [11.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v11.0.0-rc.1) (2021-05-11) ============================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0...v11.0.0-rc.1) From 88945a6d6d887613b856926a8554ff89b36a7ebb Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 17 May 2021 13:30:08 +0100 Subject: [PATCH 5/8] v11.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e63d93fb..f966c0dfb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "11.0.0-rc.1", + "version": "11.0.0", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", From 52a893a8116d60bb76f1b015d3161a15404b3628 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 17 May 2021 13:32:49 +0100 Subject: [PATCH 6/8] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f966c0dfb..e98615ba1 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "keywords": [ "matrix-org" ], - "main": "./lib/index.js", + "main": "./src/index.ts", "browser": "./lib/browser-index.js", "matrix_src_main": "./src/index.ts", "matrix_src_browser": "./src/browser-index.js", @@ -108,6 +108,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 40cd4629dbd1fc17d655afce7738e9bf8a03a50f Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 18 May 2021 17:21:06 +0100 Subject: [PATCH 7/8] Decrypt relations before applying them to target event (#1696) --- src/client.js | 22 +++++++++++++++++++++- src/crypto/index.js | 5 ++++- src/models/event-timeline-set.js | 2 +- src/models/relations.js | 32 ++++++++++++++++++++++---------- src/webrtc/callEventHandler.ts | 10 +++++++--- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/client.js b/src/client.js index 12bfc6a33..d71aa6cb5 100644 --- a/src/client.js +++ b/src/client.js @@ -5566,7 +5566,7 @@ function _PojoToMatrixEventMapper(client, options = {}) { ]); } if (decrypt) { - event.attemptDecryption(client._crypto); + client.decryptEventIfNeeded(event); } } if (!preventReEmit) { @@ -5608,6 +5608,26 @@ MatrixClient.prototype.generateClientSecret = function() { return randomString(32); }; +/** + * Attempts to decrypt an event + * @param {MatrixEvent} event The event to decrypt + * @returns {Promise} A decryption promise + * @param {object} options + * @param {bool} options.isRetry True if this is a retry (enables more logging) + * @param {bool} options.emit Emits "event.decrypted" if set to true + */ +MatrixClient.prototype.decryptEventIfNeeded = function(event, options) { + if (event.shouldAttemptDecryption()) { + event.attemptDecryption(this._crypto, options); + } + + if (event.isBeingDecrypted()) { + return event._decryptionPromise; + } else { + return Promise.resolve(); + } +}; + // MatrixClient Event JSDocs /** diff --git a/src/crypto/index.js b/src/crypto/index.js index cdcce3cd5..e418195ae 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -3322,7 +3322,10 @@ Crypto.prototype._onToDeviceEvent = function(event) { this._onKeyVerificationMessage(event); } else if (event.getContent().msgtype === "m.bad.encrypted") { this._onToDeviceBadEncrypted(event); - } else if (event.isBeingDecrypted()) { + } else if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { + if (!event.isBeingDecrypted()) { + event.attemptDecryption(this); + } // once the event has been decrypted, try again event.once('Event.decrypted', (ev) => { this._onToDeviceEvent(ev); diff --git a/src/models/event-timeline-set.js b/src/models/event-timeline-set.js index 84853d07d..784dea359 100644 --- a/src/models/event-timeline-set.js +++ b/src/models/event-timeline-set.js @@ -769,7 +769,7 @@ EventTimelineSet.prototype.aggregateRelations = function(event) { } // If the event is currently encrypted, wait until it has been decrypted. - if (event.isBeingDecrypted()) { + if (event.isBeingDecrypted() || event.shouldAttemptDecryption()) { event.once("Event.decrypted", () => { this.aggregateRelations(event); }); diff --git a/src/models/relations.js b/src/models/relations.js index 273a823be..304c7927b 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -46,6 +46,7 @@ export class Relations extends EventEmitter { this._annotationsBySender = {}; this._sortedAnnotationsByKey = []; this._targetEvent = null; + this._room = room; } /** @@ -54,7 +55,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} event * The new relation event to be added. */ - addEvent(event) { + async addEvent(event) { if (this._relations.has(event)) { return; } @@ -84,7 +85,8 @@ export class Relations extends EventEmitter { if (this.relationType === "m.annotation") { this._addAnnotationToAggregation(event); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } event.on("Event.beforeRedaction", this._onBeforeRedaction); @@ -98,7 +100,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} event * The relation event to remove. */ - _removeEvent(event) { + async _removeEvent(event) { if (!this._relations.has(event)) { return; } @@ -122,7 +124,8 @@ export class Relations extends EventEmitter { if (this.relationType === "m.annotation") { this._removeAnnotationFromAggregation(event); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } this.emit("Relations.remove", event); @@ -227,7 +230,7 @@ export class Relations extends EventEmitter { * @param {MatrixEvent} redactedEvent * The original relation event that is about to be redacted. */ - _onBeforeRedaction = (redactedEvent) => { + _onBeforeRedaction = async (redactedEvent) => { if (!this._relations.has(redactedEvent)) { return; } @@ -238,7 +241,8 @@ export class Relations extends EventEmitter { // Remove the redacted annotation from aggregation by key this._removeAnnotationFromAggregation(redactedEvent); } else if (this.relationType === "m.replace" && this._targetEvent) { - this._targetEvent.makeReplaced(this.getLastReplacement()); + const lastReplacement = await this.getLastReplacement(); + this._targetEvent.makeReplaced(lastReplacement); } redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction); @@ -291,7 +295,7 @@ export class Relations extends EventEmitter { * * @return {MatrixEvent?} */ - getLastReplacement() { + async getLastReplacement() { if (this.relationType !== "m.replace") { // Aggregating on last only makes sense for this relation type return null; @@ -309,7 +313,7 @@ export class Relations extends EventEmitter { this._targetEvent.getServerAggregatedRelation("m.replace"); const minTs = replaceRelation && replaceRelation.origin_server_ts; - return this.getRelations().reduce((last, event) => { + const lastReplacement = this.getRelations().reduce((last, event) => { if (event.getSender() !== this._targetEvent.getSender()) { return last; } @@ -321,18 +325,26 @@ export class Relations extends EventEmitter { } return event; }, null); + + if (lastReplacement?.shouldAttemptDecryption()) { + await lastReplacement.attemptDecryption(this._room._client._crypto); + } else if (lastReplacement?.isBeingDecrypted()) { + await lastReplacement._decryptionPromise; + } + + return lastReplacement; } /* * @param {MatrixEvent} targetEvent the event the relations are related to. */ - setTargetEvent(event) { + async setTargetEvent(event) { if (this._targetEvent) { return; } this._targetEvent = event; if (this.relationType === "m.replace") { - const replacement = this.getLastReplacement(); + const replacement = await this.getLastReplacement(); // this is the initial update, so only call it if we already have something // to not emit Event.replaced needlessly if (replacement) { diff --git a/src/webrtc/callEventHandler.ts b/src/webrtc/callEventHandler.ts index 9c4c38b26..44815ee63 100644 --- a/src/webrtc/callEventHandler.ts +++ b/src/webrtc/callEventHandler.ts @@ -55,7 +55,9 @@ export class CallEventHandler { private evaluateEventBuffer = () => { if (this.client.getSyncState() === "SYNCING") { // don't process any events until they are all decrypted - if (this.callEventBuffer.some((e) => e.isBeingDecrypted())) return; + if (this.callEventBuffer.some((e) => { + return e.isBeingDecrypted() || e.shouldAttemptDecryption() + })) return; const ignoreCallIds = new Set(); // inspect the buffer and mark all calls which have been answered @@ -87,17 +89,19 @@ export class CallEventHandler { private onEvent = (event: MatrixEvent) => { // any call events or ones that might be once they're decrypted + const isBeingDecrypted = event.isBeingDecrypted(); + const shouldAttemptDecryption = event.shouldAttemptDecryption(); if ( event.getType().indexOf("m.call.") === 0 || event.getType().indexOf("org.matrix.call.") === 0 - || event.isBeingDecrypted() + || isBeingDecrypted || shouldAttemptDecryption ) { // queue up for processing once all events from this sync have been // processed (see above). this.callEventBuffer.push(event); } - if (event.isBeingDecrypted() || event.isDecryptionFailure()) { + if (event.isDecryptionFailure() || isBeingDecrypted || shouldAttemptDecryption) { // add an event listener for once the event is decrypted. event.once("Event.decrypted", () => { if (event.getType().indexOf("m.call.") === -1) return; From 4b0db6c4724327ee3f249ccdec5def835d5b2d5e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 May 2021 13:20:40 +0100 Subject: [PATCH 8/8] Guard against duplicates in `Relations` model The `Relations` model was relying on object reference equality to prevent duplicates, which breaks down if we ever have two objects that represent the same event. This fixes things to additionally track event IDs we've seen before and discard any attempts to add them twice. Fixes https://github.com/vector-im/element-web/issues/11161 --- spec/unit/relations.spec.ts | 73 +++++++++++++++++++++++++++++++++++++ src/models/relations.js | 4 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 spec/unit/relations.spec.ts diff --git a/spec/unit/relations.spec.ts b/spec/unit/relations.spec.ts new file mode 100644 index 000000000..0e561c856 --- /dev/null +++ b/spec/unit/relations.spec.ts @@ -0,0 +1,73 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { MatrixEvent } from "../../src/models/event"; +import { Relations } from "../../src/models/relations"; + +describe("Relations", function() { + it("should deduplicate annotations", function() { + const relations = new Relations("m.annotation", "m.reaction"); + + // Create an instance of an annotation + const eventData = { + "sender": "@bob:example.com", + "type": "m.reaction", + "event_id": "$cZ1biX33ENJqIm00ks0W_hgiO_6CHrsAc3ZQrnLeNTw", + "room_id": "!pzVjCQSoQPpXQeHpmK:example.com", + "content": { + "m.relates_to": { + "event_id": "$2s4yYpEkVQrPglSCSqB_m6E8vDhWsg0yFNyOJdVIb_o", + "key": "๐Ÿ‘๏ธ", + "rel_type": "m.annotation", + }, + }, + }; + const eventA = new MatrixEvent(eventData); + + // Add the event once and check results + { + relations.addEvent(eventA); + const annotationsByKey = relations.getSortedAnnotationsByKey(); + expect(annotationsByKey.length).toEqual(1); + const [key, events] = annotationsByKey[0]; + expect(key).toEqual("๐Ÿ‘๏ธ"); + expect(events.size).toEqual(1); + } + + // Add the event again and expect the same + { + relations.addEvent(eventA); + const annotationsByKey = relations.getSortedAnnotationsByKey(); + expect(annotationsByKey.length).toEqual(1); + const [key, events] = annotationsByKey[0]; + expect(key).toEqual("๐Ÿ‘๏ธ"); + expect(events.size).toEqual(1); + } + + // Create a fresh object with the same event content + const eventB = new MatrixEvent(eventData); + + // Add the event again and expect the same + { + relations.addEvent(eventB); + const annotationsByKey = relations.getSortedAnnotationsByKey(); + expect(annotationsByKey.length).toEqual(1); + const [key, events] = annotationsByKey[0]; + expect(key).toEqual("๐Ÿ‘๏ธ"); + expect(events.size).toEqual(1); + } + }); +}); diff --git a/src/models/relations.js b/src/models/relations.js index 304c7927b..6be559af8 100644 --- a/src/models/relations.js +++ b/src/models/relations.js @@ -41,6 +41,7 @@ export class Relations extends EventEmitter { super(); this.relationType = relationType; this.eventType = eventType; + this._relationEventIds = new Set(); this._relations = new Set(); this._annotationsByKey = {}; this._annotationsBySender = {}; @@ -56,7 +57,7 @@ export class Relations extends EventEmitter { * The new relation event to be added. */ async addEvent(event) { - if (this._relations.has(event)) { + if (this._relationEventIds.has(event.getId())) { return; } @@ -81,6 +82,7 @@ export class Relations extends EventEmitter { } this._relations.add(event); + this._relationEventIds.add(event.getId()); if (this.relationType === "m.annotation") { this._addAnnotationToAggregation(event);