From 871c48f69b8039f82da7aeaff0b8a6e87ae973a5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 10:02:21 +0100 Subject: [PATCH 1/5] stop assuming that decryption happens ahead of time --- src/components/structures/FilePanel.js | 4 ++++ src/components/structures/RoomView.tsx | 2 +- src/components/views/messages/MessageActionBar.js | 7 +++++++ src/components/views/messages/ViewSourceEvent.js | 7 +++++++ src/indexing/EventIndex.js | 4 ++++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index d5e4b092e2..8f9908f1ca 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -50,6 +50,10 @@ class FilePanel extends React.Component { if (room?.roomId !== this.props?.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; + if (ev.shouldAttemptDecryption()) { + ev.attemptDecryption(room._client._crypto); + } + if (ev.isBeingDecrypted()) { this.decryptingEvents.add(ev.getId()); } else { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index c0f3c59457..dbfba13297 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -811,7 +811,7 @@ export default class RoomView extends React.Component { }; private onEvent = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; + if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || ev.shouldAttemptDecryption()) return; this.handleEffects(ev); }; diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index b2f7f8a692..cf3a0a704f 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -31,6 +31,7 @@ import {RovingAccessibleTooltipButton, useRovingTabIndex} from "../../../accessi import {replaceableComponent} from "../../../utils/replaceableComponent"; import {canCancel} from "../context_menus/MessageContextMenu"; import Resend from "../../../Resend"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -122,6 +123,12 @@ export default class MessageActionBar extends React.PureComponent { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { this.props.mxEvent.on("Event.status", this.onSent); } + + if (this.props.mxEvent.shouldAttemptDecryption()) { + const client = MatrixClientPeg.get(); + this.props.mxEvent.attemptDecryption(client._crypto); + } + if (this.props.mxEvent.isBeingDecrypted()) { this.props.mxEvent.once("Event.decrypted", this.onDecrypted); } diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.js index adc7a248cd..9a110a8826 100644 --- a/src/components/views/messages/ViewSourceEvent.js +++ b/src/components/views/messages/ViewSourceEvent.js @@ -18,6 +18,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; @replaceableComponent("views.messages.ViewSourceEvent") export default class ViewSourceEvent extends React.PureComponent { @@ -36,6 +37,12 @@ export default class ViewSourceEvent extends React.PureComponent { componentDidMount() { const {mxEvent} = this.props; + + const client = MatrixClientPeg.get(); + if (mxEvent.shouldAttemptDecryption()) { + mxEvent.attemptDecryption(client._client._crypto); + } + if (mxEvent.isBeingDecrypted()) { mxEvent.once("Event.decrypted", () => this.forceUpdate()); } diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 857dc5b248..df46d800b1 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -187,6 +187,10 @@ export default class EventIndex extends EventEmitter { return; } + if (ev.shouldAttemptDecryption()) { + ev.attemptDecryption(room._client._crypto); + } + if (ev.isBeingDecrypted()) { // XXX: Private member access await ev._decryptionPromise; From 1cfd4b6e1aa176f10dfb572cbdb7cad92a7f467a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 11:41:20 +0100 Subject: [PATCH 2/5] Use client.decryptEvent to avoid accessing js-sdk private members --- src/components/structures/FilePanel.js | 5 ++-- src/components/structures/TimelinePanel.js | 3 +- .../views/messages/MessageActionBar.js | 2 +- .../views/messages/ViewSourceEvent.js | 2 +- src/indexing/EventIndex.js | 30 +++++-------------- 5 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 8f9908f1ca..ff30c25073 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -50,9 +50,8 @@ class FilePanel extends React.Component { if (room?.roomId !== this.props?.roomId) return; if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; - if (ev.shouldAttemptDecryption()) { - ev.attemptDecryption(room._client._crypto); - } + const client = MatrixClientPeg.get(); + client.decryptEvent(ev); if (ev.isBeingDecrypted()) { this.decryptingEvents.add(ev.getId()); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 5012d91a5f..4918a4a3b4 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1150,7 +1150,8 @@ class TimelinePanel extends React.Component { .reverse() .forEach(event => { if (event.shouldAttemptDecryption()) { - event.attemptDecryption(MatrixClientPeg.get()._crypto); + const client = MatrixClientPeg.get(); + client.decryptEvent(event); } }); diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index cf3a0a704f..21b7cd64c9 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -126,7 +126,7 @@ export default class MessageActionBar extends React.PureComponent { if (this.props.mxEvent.shouldAttemptDecryption()) { const client = MatrixClientPeg.get(); - this.props.mxEvent.attemptDecryption(client._crypto); + client.decryptEvent(this.props.mxEvent); } if (this.props.mxEvent.isBeingDecrypted()) { diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.js index 9a110a8826..9e30261fd6 100644 --- a/src/components/views/messages/ViewSourceEvent.js +++ b/src/components/views/messages/ViewSourceEvent.js @@ -40,7 +40,7 @@ export default class ViewSourceEvent extends React.PureComponent { const client = MatrixClientPeg.get(); if (mxEvent.shouldAttemptDecryption()) { - mxEvent.attemptDecryption(client._client._crypto); + client.decryptEvent(mxEvent); } if (mxEvent.isBeingDecrypted()) { diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index df46d800b1..4d207d9f49 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -177,8 +177,10 @@ export default class EventIndex extends EventEmitter { * listener. */ onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => { + const client = MatrixClientPeg.get(); + // We only index encrypted rooms locally. - if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return; + if (!client.isRoomEncrypted(room.roomId)) return; // If it isn't a live event or if it's redacted there's nothing to // do. @@ -187,14 +189,7 @@ export default class EventIndex extends EventEmitter { return; } - if (ev.shouldAttemptDecryption()) { - ev.attemptDecryption(room._client._crypto); - } - - if (ev.isBeingDecrypted()) { - // XXX: Private member access - await ev._decryptionPromise; - } + await client.decryptEvent(ev); await this.addLiveEventToIndex(ev); } @@ -522,19 +517,10 @@ export default class EventIndex extends EventEmitter { const decryptionPromises = matrixEvents .filter(event => event.isEncrypted()) .map(event => { - if (event.shouldAttemptDecryption()) { - return event.attemptDecryption(client._crypto, { - isRetry: true, - emit: false, - }); - } else { - // TODO the decryption promise is a private property, this - // should either be made public or we should convert the - // event that gets fired when decryption is done into a - // promise using the once event emitter method: - // https://nodejs.org/api/events.html#events_events_once_emitter_name - return event._decryptionPromise; - } + return client.decryptEvent(event, { + isRetry: true, + emit: false, + }); }); // Let us wait for all the events to get decrypted. From f9f10de0da767431a8bead387b4b960993537f4a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 13:01:38 +0100 Subject: [PATCH 3/5] use renamed decrypt event method --- src/components/structures/FilePanel.js | 2 +- src/components/structures/TimelinePanel.js | 6 ++---- src/components/views/messages/MessageActionBar.js | 6 ++---- src/components/views/messages/ViewSourceEvent.js | 4 +--- src/indexing/EventIndex.js | 4 ++-- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index ff30c25073..bb7c1f9642 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -51,7 +51,7 @@ class FilePanel extends React.Component { if (toStartOfTimeline || !data || !data.liveEvent || ev.isRedacted()) return; const client = MatrixClientPeg.get(); - client.decryptEvent(ev); + client.decryptEventIfNeeded(ev); if (ev.isBeingDecrypted()) { this.decryptingEvents.add(ev.getId()); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 4918a4a3b4..af20c31cb2 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -1149,10 +1149,8 @@ class TimelinePanel extends React.Component { arrayFastClone(events) .reverse() .forEach(event => { - if (event.shouldAttemptDecryption()) { - const client = MatrixClientPeg.get(); - client.decryptEvent(event); - } + const client = MatrixClientPeg.get(); + client.decryptEventIfNeeded(event); }); const firstVisibleEventIndex = this._checkForPreJoinUISI(events); diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 21b7cd64c9..37737519ce 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -124,10 +124,8 @@ export default class MessageActionBar extends React.PureComponent { this.props.mxEvent.on("Event.status", this.onSent); } - if (this.props.mxEvent.shouldAttemptDecryption()) { - const client = MatrixClientPeg.get(); - client.decryptEvent(this.props.mxEvent); - } + const client = MatrixClientPeg.get(); + client.decryptEventIfNeeded(this.props.mxEvent); if (this.props.mxEvent.isBeingDecrypted()) { this.props.mxEvent.once("Event.decrypted", this.onDecrypted); diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.js index 9e30261fd6..2ec567c5ad 100644 --- a/src/components/views/messages/ViewSourceEvent.js +++ b/src/components/views/messages/ViewSourceEvent.js @@ -39,9 +39,7 @@ export default class ViewSourceEvent extends React.PureComponent { const {mxEvent} = this.props; const client = MatrixClientPeg.get(); - if (mxEvent.shouldAttemptDecryption()) { - client.decryptEvent(mxEvent); - } + client.decryptEventIfNeeded(mxEvent); if (mxEvent.isBeingDecrypted()) { mxEvent.once("Event.decrypted", () => this.forceUpdate()); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index 4d207d9f49..ed4418140b 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -189,7 +189,7 @@ export default class EventIndex extends EventEmitter { return; } - await client.decryptEvent(ev); + await client.decryptEventIfNeeded(ev); await this.addLiveEventToIndex(ev); } @@ -517,7 +517,7 @@ export default class EventIndex extends EventEmitter { const decryptionPromises = matrixEvents .filter(event => event.isEncrypted()) .map(event => { - return client.decryptEvent(event, { + return client.decryptEventIfNeeded(event, { isRetry: true, emit: false, }); From 454df8947be27c9fe1529043061b07e007a000c6 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 13:46:47 +0100 Subject: [PATCH 4/5] Add mock for new client method --- test/test-utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test-utils.js b/test/test-utils.js index 6dc02463a5..953693a820 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -95,6 +95,7 @@ export function createTestClient() { getItem: jest.fn(), }, }, + decryptEventIfNeeded: () => Promise.resolve(), }; } From 0e221ae5488c99935f09a051f3da121ac5899e47 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 16:24:38 +0100 Subject: [PATCH 5/5] Start decryption process if needed --- src/Notifier.ts | 2 ++ src/stores/widgets/StopGapWidget.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Notifier.ts b/src/Notifier.ts index 3e927cea0c..4f55046e72 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -331,6 +331,8 @@ export const Notifier = { if (!this.isSyncing) return; // don't alert for any messages initially if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return; + MatrixClientPeg.get().decryptEventIfNeeded(ev); + // If it's an encrypted event and the type is still 'm.room.encrypted', // it hasn't yet been decrypted, so wait until it is. if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) { diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 84cf35aeb4..397d637125 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -400,6 +400,7 @@ export class StopGapWidget extends EventEmitter { } private onEvent = (ev: MatrixEvent) => { + MatrixClientPeg.get().decryptEventIfNeeded(ev); if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; if (ev.getRoomId() !== this.eventListenerRoomId) return; this.feedEvent(ev);