From 2fd9b9b3b9d5237737d85952363c7882b6d16f56 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 7 Oct 2020 14:23:52 +0100 Subject: [PATCH 01/20] Upgrade matrix-js-sdk to 8.5.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e66d0aabcf..e5ecacd387 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "8.5.0-rc.1", "matrix-widget-api": "^0.1.0-beta.2", "minimist": "^1.2.5", "pako": "^1.0.11", diff --git a/yarn.lock b/yarn.lock index 51ff681783..a2c5cd6645 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5926,9 +5926,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "8.4.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a9a6b2de48250440dc2a1c3eee630f4957fb9f83" +matrix-js-sdk@8.5.0-rc.1: + version "8.5.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.5.0-rc.1.tgz#7ad34d8b6a786a7d4a04173e3b17da56022cd62a" + integrity sha512-hVFYkN3/rsPiNXdFqcvRvg+Egr75qhPGMi2gp59PDr7JrZEtOACA0I3NCY1vPub9kXGTtPZqPuHGFrsF9qLXGw== dependencies: "@babel/runtime" "^7.11.2" another-json "^0.2.0" From ffd7cddd0d9cbbc91eb47ab8e2d7f1d892ae3264 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 7 Oct 2020 14:30:51 +0100 Subject: [PATCH 02/20] Prepare changelog for v3.6.0-rc.1 --- CHANGELOG.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a7ddc407..98a4a1ff87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,91 @@ +Changes in [3.6.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.6.0-rc.1) (2020-10-07) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.5.0...v3.6.0-rc.1) + + * Upgrade JS SDK to 8.5.0-rc.1 + * Update from Weblate + [\#5297](https://github.com/matrix-org/matrix-react-sdk/pull/5297) + * Fix edited replies being wrongly treated as big emoji + [\#5295](https://github.com/matrix-org/matrix-react-sdk/pull/5295) + * Fix StopGapWidget infinitely recursing + [\#5294](https://github.com/matrix-org/matrix-react-sdk/pull/5294) + * Fix editing and redactions not updating the Reply Thread + [\#5281](https://github.com/matrix-org/matrix-react-sdk/pull/5281) + * Hide Jump to Read Receipt button for users who have not yet sent an RR + [\#5282](https://github.com/matrix-org/matrix-react-sdk/pull/5282) + * fix img tags not always being rendered correctly + [\#5279](https://github.com/matrix-org/matrix-react-sdk/pull/5279) + * Hopefully fix righhtpanel crash + [\#5293](https://github.com/matrix-org/matrix-react-sdk/pull/5293) + * Fix naive pinning limit and app tile widgetMessaging NPE + [\#5283](https://github.com/matrix-org/matrix-react-sdk/pull/5283) + * Show server errors from saving profile settings + [\#5272](https://github.com/matrix-org/matrix-react-sdk/pull/5272) + * Update copy for `redact` permission + [\#5273](https://github.com/matrix-org/matrix-react-sdk/pull/5273) + * Remove width limit on widgets + [\#5265](https://github.com/matrix-org/matrix-react-sdk/pull/5265) + * Fix call container avatar initial centering + [\#5280](https://github.com/matrix-org/matrix-react-sdk/pull/5280) + * Fix right panel for peeking rooms + [\#5268](https://github.com/matrix-org/matrix-react-sdk/pull/5268) + * Add support for dehydrated devices + [\#5239](https://github.com/matrix-org/matrix-react-sdk/pull/5239) + * Use Own Profile Store for the Profile Settings + [\#5277](https://github.com/matrix-org/matrix-react-sdk/pull/5277) + * null-guard defaultAvatarUrlForString + [\#5270](https://github.com/matrix-org/matrix-react-sdk/pull/5270) + * Choose first result on enter in the emoji picker + [\#5257](https://github.com/matrix-org/matrix-react-sdk/pull/5257) + * Fix room directory clipping links in the room's topic + [\#5276](https://github.com/matrix-org/matrix-react-sdk/pull/5276) + * Decorate failed e2ee downgrade attempts better + [\#5278](https://github.com/matrix-org/matrix-react-sdk/pull/5278) + * MELS use latest avatar rather than the first avatar + [\#5262](https://github.com/matrix-org/matrix-react-sdk/pull/5262) + * Fix Encryption Panel close button clashing with Base Card + [\#5261](https://github.com/matrix-org/matrix-react-sdk/pull/5261) + * Wrap canEncryptToAllUsers in a try/catch to handle server errors + [\#5275](https://github.com/matrix-org/matrix-react-sdk/pull/5275) + * Fix conditional on communities prototype room creation dialog + [\#5274](https://github.com/matrix-org/matrix-react-sdk/pull/5274) + * Fix ensureDmExists for encryption detection + [\#5271](https://github.com/matrix-org/matrix-react-sdk/pull/5271) + * Switch to using the Widget API SDK for widget messaging + [\#5171](https://github.com/matrix-org/matrix-react-sdk/pull/5171) + * Ensure package links exist when releasing + [\#5269](https://github.com/matrix-org/matrix-react-sdk/pull/5269) + * Fix the call preview when not in same room as the call + [\#5267](https://github.com/matrix-org/matrix-react-sdk/pull/5267) + * Make the hangup button do things for conference calls + [\#5223](https://github.com/matrix-org/matrix-react-sdk/pull/5223) + * Render Jitsi widget state events in a more obvious way + [\#5222](https://github.com/matrix-org/matrix-react-sdk/pull/5222) + * Make the PIP Jitsi look and feel like the 1:1 PIP + [\#5226](https://github.com/matrix-org/matrix-react-sdk/pull/5226) + * Trim range when formatting so that it excludes leading/trailing spaces + [\#5263](https://github.com/matrix-org/matrix-react-sdk/pull/5263) + * Fix button label on the Set Password Dialog + [\#5264](https://github.com/matrix-org/matrix-react-sdk/pull/5264) + * fix link to classic yarn's `yarn link` + [\#5259](https://github.com/matrix-org/matrix-react-sdk/pull/5259) + * Fix index mismatch between username colors styles and custom theming + [\#5256](https://github.com/matrix-org/matrix-react-sdk/pull/5256) + * Disable autocompletion on security key input during login + [\#5258](https://github.com/matrix-org/matrix-react-sdk/pull/5258) + * fix uninitialised state and eventlistener leak in RoomUpgradeWarningBar + [\#5255](https://github.com/matrix-org/matrix-react-sdk/pull/5255) + * Only set title when it changes + [\#5254](https://github.com/matrix-org/matrix-react-sdk/pull/5254) + * Convert CallHandler to typescript + [\#5248](https://github.com/matrix-org/matrix-react-sdk/pull/5248) + * Retry loading i18n language if it fails + [\#5209](https://github.com/matrix-org/matrix-react-sdk/pull/5209) + * Rework profile area for user and room settings to be more clear + [\#5243](https://github.com/matrix-org/matrix-react-sdk/pull/5243) + * Validation improve pattern for derived data + [\#5241](https://github.com/matrix-org/matrix-react-sdk/pull/5241) + Changes in [3.5.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.5.0) (2020-09-28) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.5.0-rc.1...v3.5.0) From 4abaa107abcad1bea704ec08a1fcf557aac4b492 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 7 Oct 2020 14:30:51 +0100 Subject: [PATCH 03/20] v3.6.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5ecacd387..0b371df5fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.5.0", + "version": "3.6.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From fcc411f8b93736d00abeecf1403be2627fda5731 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 8 Oct 2020 15:35:22 -0600 Subject: [PATCH 04/20] Use new preparing event for widget communications Fixes https://github.com/vector-im/element-web/issues/15404 We need to make sure we don't accidentally call the widget before its ready, but we can happily show it once it is loaded/prepared. --- src/components/views/elements/AppTile.js | 7 ++++++- src/stores/widgets/StopGapWidget.ts | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 3405d4ff16..fda2652d12 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -50,6 +50,7 @@ export default class AppTile extends React.Component { // The key used for PersistedElement this._persistKey = 'widget_' + this.props.app.id; this._sgWidget = new StopGapWidget(this.props); + this._sgWidget.on("preparing", this._onWidgetPrepared); this._sgWidget.on("ready", this._onWidgetReady); this.iframe = null; // ref to the iframe (callback style) @@ -142,6 +143,7 @@ export default class AppTile extends React.Component { this._sgWidget.stop(); } this._sgWidget = new StopGapWidget(newProps); + this._sgWidget.on("preparing", this._onWidgetPrepared); this._sgWidget.on("ready", this._onWidgetReady); this._startWidget(); } @@ -295,8 +297,11 @@ export default class AppTile extends React.Component { this._revokeWidgetPermission(); } - _onWidgetReady = () => { + _onWidgetPrepared = () => { this.setState({loading: false}); + }; + + _onWidgetReady = () => { if (WidgetType.JITSI.matches(this.props.app.type)) { this._sgWidget.widgetApi.transport.send(ElementWidgetActions.ClientReady, {}); } diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 9e4d124d5b..dde756cf3b 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -163,6 +163,7 @@ export class StopGapWidget extends EventEmitter { if (this.started) return; const driver = new StopGapWidgetDriver( this.appTileProps.whitelistCapabilities || []); this.messaging = new ClientWidgetApi(this.mockWidget, iframe, driver); + this.messaging.addEventListener("preparing", () => this.emit("preparing")); this.messaging.addEventListener("ready", () => this.emit("ready")); WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging); From 2caf679024742efdd21a5d031fe706fd2763b58a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 8 Oct 2020 21:22:17 -0600 Subject: [PATCH 05/20] Update widget-api --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0b371df5fe..532bb1c3e5 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "linkifyjs": "^2.1.9", "lodash": "^4.17.19", "matrix-js-sdk": "8.5.0-rc.1", - "matrix-widget-api": "^0.1.0-beta.2", + "matrix-widget-api": "^0.1.0-beta.3", "minimist": "^1.2.5", "pako": "^1.0.11", "parse5": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index a2c5cd6645..7dc34164d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5954,10 +5954,10 @@ matrix-react-test-utils@^0.2.2: resolved "https://registry.yarnpkg.com/matrix-react-test-utils/-/matrix-react-test-utils-0.2.2.tgz#c87144d3b910c7edc544a6699d13c7c2bf02f853" integrity sha512-49+7gfV6smvBIVbeloql+37IeWMTD+fiywalwCqk8Dnz53zAFjKSltB3rmWHso1uecLtQEcPtCijfhzcLXAxTQ== -matrix-widget-api@^0.1.0-beta.2: - version "0.1.0-beta.2" - resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.2.tgz#367da1ccd26b711f73fc5b6e02edf55ac2ea2692" - integrity sha512-q5g5RZN+RRjM4HmcJ+LYoQAYrB1wzyERmoQ+LvKbTV/+9Ov36Kp0QEP8CleSXEd5WLp6bkRlt60axDaY6pWGmg== +matrix-widget-api@^0.1.0-beta.3: + version "0.1.0-beta.3" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.3.tgz#356965ca357172ee056e3fd86fd96879b059a114" + integrity sha512-j7nxdhLQfdU6snsdBA29KQR0DmT8/vl6otOvGqPCV0OCHpq1312cP79Eg4JzJKIFI3A76Qha3nYx6G9/aapwXg== mdast-util-compact@^1.0.0: version "1.0.4" From 1af8d96db938dfad38b315020248364ae3c3d36f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Oct 2020 09:26:52 -0600 Subject: [PATCH 06/20] Fix templating for v1 jitsi widgets Fixes https://github.com/vector-im/element-web/issues/15427 --- src/stores/widgets/StopGapWidget.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index dde756cf3b..1eb4f9cd27 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -19,11 +19,13 @@ import { ClientWidgetApi, IStickerActionRequest, IStickyActionRequest, + ITemplateParams, IWidget, IWidgetApiRequest, IWidgetApiRequestEmptyData, IWidgetData, MatrixCapabilities, + runTemplate, Widget, WidgetApiFromWidgetAction, } from "matrix-widget-api"; @@ -76,15 +78,33 @@ class ElementWidget extends Widget { let conferenceId = super.rawData['conferenceId']; if (conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets - const parsedUrl = new URL(this.templateUrl); + const parsedUrl = new URL(super.templateUrl); // use super to get the raw widget URL conferenceId = parsedUrl.searchParams.get("confId"); } + let domain = super.rawData['domain']; + if (domain === undefined) { + // v1 widgets default to jitsi.riot.im regardless of user settings + domain = "jitsi.riot.im"; + } return { ...super.rawData, theme: SettingsStore.getValue("theme"), conferenceId, + domain, }; } + + public getCompleteUrl(params: ITemplateParams): string { + return runTemplate(this.templateUrl, { + // we need to supply a whole widget to the template, but don't have + // easy access to the definition the superclass is using, so be sad + // and gutwrench it. + // This isn't a problem when the widget architecture is fixed and this + // subclass gets deleted. + ...super['definition'], // XXX: Private member access + data: this.rawData, + }, params); + } } export class StopGapWidget extends EventEmitter { From 3e7f1176111f997029611bf4965b22800d6b9157 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Oct 2020 09:26:52 -0600 Subject: [PATCH 07/20] Fix templating for v1 jitsi widgets Fixes https://github.com/vector-im/element-web/issues/15427 --- src/stores/widgets/StopGapWidget.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index dde756cf3b..1eb4f9cd27 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -19,11 +19,13 @@ import { ClientWidgetApi, IStickerActionRequest, IStickyActionRequest, + ITemplateParams, IWidget, IWidgetApiRequest, IWidgetApiRequestEmptyData, IWidgetData, MatrixCapabilities, + runTemplate, Widget, WidgetApiFromWidgetAction, } from "matrix-widget-api"; @@ -76,15 +78,33 @@ class ElementWidget extends Widget { let conferenceId = super.rawData['conferenceId']; if (conferenceId === undefined) { // we'll need to parse the conference ID out of the URL for v1 Jitsi widgets - const parsedUrl = new URL(this.templateUrl); + const parsedUrl = new URL(super.templateUrl); // use super to get the raw widget URL conferenceId = parsedUrl.searchParams.get("confId"); } + let domain = super.rawData['domain']; + if (domain === undefined) { + // v1 widgets default to jitsi.riot.im regardless of user settings + domain = "jitsi.riot.im"; + } return { ...super.rawData, theme: SettingsStore.getValue("theme"), conferenceId, + domain, }; } + + public getCompleteUrl(params: ITemplateParams): string { + return runTemplate(this.templateUrl, { + // we need to supply a whole widget to the template, but don't have + // easy access to the definition the superclass is using, so be sad + // and gutwrench it. + // This isn't a problem when the widget architecture is fixed and this + // subclass gets deleted. + ...super['definition'], // XXX: Private member access + data: this.rawData, + }, params); + } } export class StopGapWidget extends EventEmitter { From 2698a12a6fc61e8e9a1d992b7054f116d355651f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 9 Oct 2020 16:59:56 +0100 Subject: [PATCH 08/20] Convert `src/SecurityManager.js` to TypeScript This includes a few small API tweaks as well, only in cases where TS revealed we were doing something confusing or wrong. Part of https://github.com/vector-im/element-web/issues/15350 --- src/@types/global.d.ts | 1 + src/MatrixClientPeg.ts | 4 +- src/Modal.tsx | 2 +- ...{SecurityManager.js => SecurityManager.ts} | 83 ++++++++++++------- 4 files changed, 55 insertions(+), 35 deletions(-) rename src/{SecurityManager.js => SecurityManager.ts} (86%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 91b91de90d..93be0fafc0 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first import * as ModernizrStatic from "modernizr"; import ContentMessages from "../ContentMessages"; import { IMatrixClientPeg } from "../MatrixClientPeg"; diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index 4651a0afe3..5bb10dfa89 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -17,6 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix'; import {MatrixClient} from 'matrix-js-sdk/src/client'; import {MemoryStore} from 'matrix-js-sdk/src/store/memory'; import * as utils from 'matrix-js-sdk/src/utils'; @@ -249,8 +250,7 @@ class _MatrixClientPeg implements IMatrixClientPeg { } private createClient(creds: IMatrixClientCreds): void { - // TODO: Make these opts typesafe with the js-sdk - const opts = { + const opts: ICreateClientOpts = { baseUrl: creds.homeserverUrl, idBaseUrl: creds.identityServerUrl, accessToken: creds.accessToken, diff --git a/src/Modal.tsx b/src/Modal.tsx index 0a36813961..b0f6ef988e 100644 --- a/src/Modal.tsx +++ b/src/Modal.tsx @@ -132,7 +132,7 @@ export class ModalManager { public createTrackedDialogAsync( analyticsAction: string, analyticsInfo: string, - ...rest: Parameters + ...rest: Parameters ) { Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); return this.createDialogAsync(...rest); diff --git a/src/SecurityManager.js b/src/SecurityManager.ts similarity index 86% rename from src/SecurityManager.js rename to src/SecurityManager.ts index 3272c0f015..2d97ce690b 100644 --- a/src/SecurityManager.js +++ b/src/SecurityManager.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix'; +import { MatrixClient } from 'matrix-js-sdk/src/client'; import Modal from './Modal'; import * as sdk from './index'; import {MatrixClientPeg} from './MatrixClientPeg'; @@ -31,15 +33,18 @@ import SettingsStore from "./settings/SettingsStore"; // during the same single operation. Use `accessSecretStorage` below to scope a // single secret storage operation, as it will clear the cached keys once the // operation ends. -let secretStorageKeys = {}; -let secretStorageKeyInfo = {}; +let secretStorageKeys: Record = {}; +let secretStorageKeyInfo: Record = {}; let secretStorageBeingAccessed = false; let nonInteractive = false; -let dehydrationCache = {}; +let dehydrationCache: { + key?: Uint8Array, + keyInfo?: ISecretStorageKeyInfo, +} = {}; -function isCachingAllowed() { +function isCachingAllowed(): boolean { return secretStorageBeingAccessed; } @@ -50,7 +55,7 @@ function isCachingAllowed() { * * @returns {bool} */ -export function isSecretStorageBeingAccessed() { +export function isSecretStorageBeingAccessed(): boolean { return secretStorageBeingAccessed; } @@ -60,7 +65,7 @@ export class AccessCancelledError extends Error { } } -async function confirmToDismiss() { +async function confirmToDismiss(): Promise { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const [sure] = await Modal.createDialog(QuestionDialog, { title: _t("Cancel entering passphrase?"), @@ -72,7 +77,9 @@ async function confirmToDismiss() { return !sure; } -function makeInputToKey(keyInfo) { +function makeInputToKey( + keyInfo: ISecretStorageKeyInfo, +): ({ passphrase, recoveryKey }: { passphrase: string, recoveryKey: string }) => Promise { return async ({ passphrase, recoveryKey }) => { if (passphrase) { return deriveKey( @@ -86,7 +93,10 @@ function makeInputToKey(keyInfo) { }; } -async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { +async function getSecretStorageKey( + { keys: keyInfos }: { keys: Record }, + ssssItemName, +): Promise<[string, Uint8Array]> { const keyInfoEntries = Object.entries(keyInfos); if (keyInfoEntries.length > 1) { throw new Error("Multiple storage key requests not implemented"); @@ -100,7 +110,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { if (dehydrationCache.key) { if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) { - cacheSecretStorageKey(keyId, dehydrationCache.key, keyInfo); + cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key); return [keyId, dehydrationCache.key]; } } @@ -139,12 +149,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { const key = await inputToKey(input); // Save to cache to avoid future prompts in the current session - cacheSecretStorageKey(keyId, key, keyInfo); + cacheSecretStorageKey(keyId, keyInfo, key); return [keyId, key]; } -export async function getDehydrationKey(keyInfo, checkFunc) { +export async function getDehydrationKey( + keyInfo: ISecretStorageKeyInfo, + checkFunc: (Uint8Array) => void, +): Promise { const inputToKey = makeInputToKey(keyInfo); const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", AccessSecretStorageDialog, @@ -185,20 +198,24 @@ export async function getDehydrationKey(keyInfo, checkFunc) { return key; } -function cacheSecretStorageKey(keyId, key, keyInfo) { +function cacheSecretStorageKey( + keyId: string, + keyInfo: ISecretStorageKeyInfo, + key: Uint8Array, +): void { if (isCachingAllowed()) { secretStorageKeys[keyId] = key; secretStorageKeyInfo[keyId] = keyInfo; } } -const onSecretRequested = async function({ - user_id: userId, - device_id: deviceId, - request_id: requestId, - name, - device_trust: deviceTrust, -}) { +async function onSecretRequested( + userId: string, + deviceId: string, + requestId: string, + name: string, + deviceTrust: IDeviceTrustLevel, +): Promise { console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); const client = MatrixClientPeg.get(); if (userId !== client.getUserId()) { @@ -233,16 +250,16 @@ const onSecretRequested = async function({ return key && encodeBase64(key); } console.warn("onSecretRequested didn't recognise the secret named ", name); -}; +} -export const crossSigningCallbacks = { +export const crossSigningCallbacks: ICryptoCallbacks = { getSecretStorageKey, cacheSecretStorageKey, onSecretRequested, getDehydrationKey, }; -export async function promptForBackupPassphrase() { +export async function promptForBackupPassphrase(): Promise { let key; const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { @@ -292,7 +309,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f /* priority = */ false, /* static = */ true, /* options = */ { - onBeforeClose(reason) { + onBeforeClose: async (reason) => { // If Secure Backup is required, you cannot leave the modal. if (reason === "backgroundClick") { return !isSecureBackupRequired(); @@ -329,10 +346,10 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f const keyId = Object.keys(secretStorageKeys)[0]; if (keyId && SettingsStore.getValue("feature_dehydration")) { - const dehydrationKeyInfo = - secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase - ? {passphrase: secretStorageKeyInfo[keyId].passphrase} - : {}; + let dehydrationKeyInfo = {}; + if (secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase) { + dehydrationKeyInfo = { passphrase: secretStorageKeyInfo[keyId].passphrase }; + } console.log("Setting dehydration key"); await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device"); } else { @@ -354,7 +371,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f } // FIXME: this function name is a bit of a mouthful -export async function tryToUnlockSecretStorageWithDehydrationKey(client) { +export async function tryToUnlockSecretStorageWithDehydrationKey( + client: MatrixClient, +): Promise { const key = dehydrationCache.key; let restoringBackup = false; if (key && await client.isSecretStorageReady()) { @@ -366,10 +385,10 @@ export async function tryToUnlockSecretStorageWithDehydrationKey(client) { // we also need to set a new dehydrated device to replace the // device we rehydrated - const dehydrationKeyInfo = - dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase - ? {passphrase: dehydrationCache.keyInfo.passphrase} - : {}; + let dehydrationKeyInfo = {}; + if (dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase) { + dehydrationKeyInfo = { passphrase: dehydrationCache.keyInfo.passphrase }; + } await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device"); // and restore from backup From 55f77b04ae118f2fad499c9c8ff87be416cbc37d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 9 Oct 2020 18:56:07 +0100 Subject: [PATCH 09/20] Rewrite call state machine * Remove the two separate enumerations of call state: now everything uses the js-sdk version of call state. Stop adding a separate 'call_state' field onto the call object(!) * Better reflection of the actual state of the call in the call bar, so when it's connecting, it says connecting, and only says 'active call' when the call is actually active. * More typey goodness --- src/@types/global.d.ts | 1 + src/CallHandler.tsx | 176 +++++++++--------- src/components/structures/RoomStatusBar.js | 45 +++-- src/components/structures/RoomView.tsx | 42 ++--- src/components/views/voip/CallPreview.tsx | 7 +- src/components/views/voip/CallView.tsx | 5 +- src/components/views/voip/IncomingCallBox.tsx | 3 +- src/i18n/strings/en_EN.json | 4 + 8 files changed, 153 insertions(+), 130 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 91b91de90d..93be0fafc0 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first import * as ModernizrStatic from "modernizr"; import ContentMessages from "../ContentMessages"; import { IMatrixClientPeg } from "../MatrixClientPeg"; diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 5b368016b6..4a71934b7f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -77,13 +77,18 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; +import { MatrixCall, CallErrorCode, CallState, CallType } from "matrix-js-sdk/lib/webrtc/call"; -// until we ts-ify the js-sdk voip code -type Call = any; +enum AudioId { + Ring = 'ringAudio', + Ringback = 'ringbackAudio', + CallEnd = 'callendAudio', + Busy = 'busyAudio', +} export default class CallHandler { - private calls = new Map(); - private audioPromises = new Map>(); + private calls = new Map(); + private audioPromises = new Map>(); static sharedInstance() { if (!window.mxCallHandler) { @@ -108,7 +113,7 @@ export default class CallHandler { } } - getCallForRoom(roomId: string): Call { + getCallForRoom(roomId: string): MatrixCall { return this.calls.get(roomId) || null; } @@ -121,7 +126,7 @@ export default class CallHandler { return null; } - play(audioId: string) { + play(audioId: AudioId) { // TODO: Attach an invisible element for this instead // which listens? const audio = document.getElementById(audioId) as HTMLMediaElement; @@ -150,7 +155,7 @@ export default class CallHandler { } } - pause(audioId: string) { + pause(audioId: AudioId) { // TODO: Attach an invisible element for this instead // which listens? const audio = document.getElementById(audioId) as HTMLMediaElement; @@ -164,7 +169,7 @@ export default class CallHandler { } } - private setCallListeners(call: Call) { + private setCallListeners(call: MatrixCall) { call.on("error", (err) => { console.error("Call error:", err); if ( @@ -185,69 +190,57 @@ export default class CallHandler { }); // map web rtc states to dummy UI state // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing - call.on("state", (newState, oldState) => { - if (newState === "ringing") { - this.setCallState(call, call.roomId, "ringing"); - this.pause("ringbackAudio"); - } else if (newState === "invite_sent") { - this.setCallState(call, call.roomId, "ringback"); - this.play("ringbackAudio"); - } else if (newState === "ended" && oldState === "connected") { - this.removeCallForRoom(call.roomId); - this.pause("ringbackAudio"); - this.play("callendAudio"); - } else if (newState === "ended" && oldState === "invite_sent" && - (call.hangupParty === "remote" || - (call.hangupParty === "local" && call.hangupReason === "invite_timeout") + call.on("state", (newState: CallState, oldState: CallState) => { + this.setCallState(call, newState); + + switch (oldState) { + case CallState.Ringing: + this.pause(AudioId.Ring); + break; + case CallState.InviteSent: + this.pause(AudioId.Ringback); + break; + } + + switch (newState) { + case CallState.Ringing: + this.play(AudioId.Ring); + break; + case CallState.InviteSent: + this.play(AudioId.Ringback); + break; + case CallState.Ended: + this.removeCallForRoom(call.roomId); + if (oldState === CallState.InviteSent && ( + call.hangupParty === "remote" || + (call.hangupParty === "local" && call.hangupReason === "invite_timeout") )) { - this.setCallState(call, call.roomId, "busy"); - this.pause("ringbackAudio"); - this.play("busyAudio"); - Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { - title: _t('Call Timeout'), - description: _t('The remote side failed to pick up') + '.', - }); - } else if (oldState === "invite_sent") { - this.setCallState(call, call.roomId, "stop_ringback"); - this.pause("ringbackAudio"); - } else if (oldState === "ringing") { - this.setCallState(call, call.roomId, "stop_ringing"); - this.pause("ringbackAudio"); - } else if (newState === "connected") { - this.setCallState(call, call.roomId, "connected"); - this.pause("ringbackAudio"); + this.play(AudioId.Busy); + Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { + title: _t('Call Timeout'), + description: _t('The remote side failed to pick up') + '.', + }); + } else { + this.play(AudioId.CallEnd); + } } }); } - private setCallState(call: Call, roomId: string, status: string) { + private setCallState(call: MatrixCall, status: CallState) { console.log( - `Call state in ${roomId} changed to ${status} (${call ? call.call_state : "-"})`, + `Call state in ${call.roomId} changed to ${status}`, ); - if (call) { - this.calls.set(roomId, call); - } else { - this.calls.delete(roomId); - } - if (status === "ringing") { - this.play("ringAudio"); - } else if (call && call.call_state === "ringing") { - this.pause("ringAudio"); - } - - if (call) { - call.call_state = status; - } dis.dispatch({ action: 'call_state', - room_id: roomId, + room_id: call.roomId, state: status, }); } private removeCallForRoom(roomId: string) { - this.setCallState(null, roomId, null); + this.calls.delete(roomId); } private showICEFallbackPrompt() { @@ -279,36 +272,36 @@ export default class CallHandler { }, null, true); } - private onAction = (payload: ActionPayload) => { - const placeCall = (newCall) => { - this.setCallListeners(newCall); - if (payload.type === 'voice') { - newCall.placeVoiceCall(); - } else if (payload.type === 'video') { - newCall.placeVideoCall( - payload.remote_element, - payload.local_element, - ); - } else if (payload.type === 'screensharing') { - const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); - if (screenCapErrorString) { - this.removeCallForRoom(newCall.roomId); - console.log("Can't capture screen: " + screenCapErrorString); - Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { - title: _t('Unable to capture screen'), - description: screenCapErrorString, - }); - return; - } - newCall.placeScreenSharingCall( - payload.remote_element, - payload.local_element, - ); - } else { - console.error("Unknown conf call type: %s", payload.type); - } - } + private placeCall(roomId: string, type: CallType, localElement: HTMLVideoElement, remoteElement: HTMLVideoElement) { + const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), roomId); + this.calls.set(roomId, call); + this.setCallListeners(call); + if (type === 'voice') { + call.placeVoiceCall(); + } else if (type === 'video') { + call.placeVideoCall( + remoteElement, + localElement, + ); + } else if (type === 'screensharing') { + const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); + if (screenCapErrorString) { + this.removeCallForRoom(roomId); + console.log("Can't capture screen: " + screenCapErrorString); + Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, { + title: _t('Unable to capture screen'), + description: screenCapErrorString, + }); + return; + } + call.placeScreenSharingCall(remoteElement, localElement); + } else { + console.error("Unknown conf call type: %s", type); + } + } + + private onAction = (payload: ActionPayload) => { switch (payload.action) { case 'place_call': { @@ -343,8 +336,8 @@ export default class CallHandler { return; } else if (members.length === 2) { console.info("Place %s call in %s", payload.type, payload.room_id); - const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id); - placeCall(call); + + this.placeCall(payload.room_id, payload.type, payload.local_element, payload.remote_element); } else { // > 2 dis.dispatch({ action: "place_conference_call", @@ -383,24 +376,23 @@ export default class CallHandler { return; } - const call = payload.call; + const call = payload.call as MatrixCall; + this.calls.set(call.roomId, call) this.setCallListeners(call); - this.setCallState(call, call.roomId, "ringing"); } break; case 'hangup': if (!this.calls.get(payload.room_id)) { return; // no call to hangup } - this.calls.get(payload.room_id).hangup(); + this.calls.get(payload.room_id).hangup(CallErrorCode.UserHangup, false) this.removeCallForRoom(payload.room_id); break; case 'answer': - if (!this.calls.get(payload.room_id)) { + if (!this.calls.has(payload.room_id)) { return; // no call to answer } this.calls.get(payload.room_id).answer(); - this.setCallState(this.calls.get(payload.room_id), payload.room_id, "connected"); dis.dispatch({ action: "view_room", room_id: payload.room_id, diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index cdaa0bb7f9..453663a261 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -1,7 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2017, 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015-2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -46,10 +44,12 @@ export default class RoomStatusBar extends React.Component { // Used to suggest to the user to invite someone sentMessageAndIsAlone: PropTypes.bool, - // true if there is an active call in this room (means we show - // the 'Active Call' text in the status bar if there is nothing - // more interesting) - hasActiveCall: PropTypes.bool, + // The active call in the room, if any (means we show the call bar + // along with the status of the call) + callState: PropTypes.string, + + // The type of the call in progress, or null if no call is in progress + callType: PropTypes.string, // true if the room is being peeked at. This affects components that shouldn't // logically be shown when peeking, such as a prompt to invite people to a room. @@ -121,6 +121,10 @@ export default class RoomStatusBar extends React.Component { }); }; + _showCallBar() { + return this.props.callState !== 'ended' && this.props.callState !== 'ringing'; + } + _onResendAllClick = () => { Resend.resendUnsentEvents(this.props.room); dis.fire(Action.FocusComposer); @@ -153,7 +157,7 @@ export default class RoomStatusBar extends React.Component { // indicate other sizes. _getSize() { if (this._shouldShowConnectionError() || - this.props.hasActiveCall || + this._showCallBar() || this.props.sentMessageAndIsAlone ) { return STATUS_BAR_EXPANDED; @@ -165,7 +169,7 @@ export default class RoomStatusBar extends React.Component { // return suitable content for the image on the left of the status bar. _getIndicator() { - if (this.props.hasActiveCall) { + if (this._showCallBar()) { const TintableSvg = sdk.getComponent("elements.TintableSvg"); return ( @@ -269,6 +273,25 @@ export default class RoomStatusBar extends React.Component { ; } + _getCallStatusText() { + switch (this.props.callState) { + case 'create_offer': + case 'invite_sent': + return _t('Calling...'); + case 'connecting': + case 'create_answer': + return _t('Call connecting...'); + case 'connected': + return _t('Active call'); + case 'wait_local_media': + if (this.props.callType === 'video') { + return _t('Starting camera...'); + } else { + return _t('Starting microphone...'); + } + } + } + // return suitable content for the main (text) part of the status bar. _getContent() { if (this._shouldShowConnectionError()) { @@ -291,10 +314,10 @@ export default class RoomStatusBar extends React.Component { return this._getUnsentMessageContent(); } - if (this.props.hasActiveCall) { + if (this._showCallBar()) { return (
- { _t('Active call') } + { this._getCallStatusText() }
); } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index fcb2d274c1..2a6d0b5de8 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -71,6 +71,7 @@ import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; +import { CallState, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -141,7 +142,7 @@ export interface IState { }>; searchHighlights?: string[]; searchInProgress?: boolean; - callState?: string; + callState?: CallState; guestsCanJoin: boolean; canPeek: boolean; showApps: boolean; @@ -479,7 +480,7 @@ export default class RoomView extends React.Component { componentDidMount() { const call = this.getCallForRoom(); - const callState = call ? call.call_state : "ended"; + const callState = call ? call.state : null; this.setState({ callState: callState, }); @@ -712,14 +713,9 @@ export default class RoomView extends React.Component { } const call = this.getCallForRoom(); - let callState = "ended"; - - if (call) { - callState = call.call_state; - } this.setState({ - callState: callState, + callState: call ? call.state : null, }); break; } @@ -1605,7 +1601,7 @@ export default class RoomView extends React.Component { /** * get any current call for this room */ - private getCallForRoom() { + private getCallForRoom(): MatrixCall { if (!this.state.room) { return null; } @@ -1742,10 +1738,12 @@ export default class RoomView extends React.Component { // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. - const call = this.getCallForRoom(); - let inCall = false; - if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { - inCall = true; + let activeCall = null; + { + const call = this.getCallForRoom(); + if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { + activeCall = call; + } } const scrollheaderClasses = classNames({ @@ -1764,7 +1762,8 @@ export default class RoomView extends React.Component { statusBar = { }; } - if (inCall) { + if (activeCall) { let zoomButton; let videoMuteButton; - if (call.type === "video") { + if (activeCall.type === "video") { zoomButton = (
{ videoMuteButton =
@@ -1920,10 +1920,10 @@ export default class RoomView extends React.Component { const voiceMuteButton =
@@ -2041,7 +2041,7 @@ export default class RoomView extends React.Component { }); const mainClasses = classNames("mx_RoomView", { - mx_RoomView_inCall: inCall, + mx_RoomView_inCall: Boolean(activeCall), }); return ( diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx index 9acbece8b3..ca2b510f20 100644 --- a/src/components/views/voip/CallPreview.tsx +++ b/src/components/views/voip/CallPreview.tsx @@ -24,13 +24,14 @@ import dis from '../../../dispatcher/dispatcher'; import { ActionPayload } from '../../../dispatcher/payloads'; import PersistentApp from "../elements/PersistentApp"; import SettingsStore from "../../../settings/SettingsStore"; +import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call'; interface IProps { } interface IState { roomId: string; - activeCall: any; + activeCall: MatrixCall; } export default class CallPreview extends React.Component { @@ -84,7 +85,7 @@ export default class CallPreview extends React.Component { if (call) { dis.dispatch({ action: 'view_room', - room_id: call.groupRoomId || call.roomId, + room_id: call.roomId, }); } }; @@ -93,7 +94,7 @@ export default class CallPreview extends React.Component { const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const showCall = ( this.state.activeCall && - this.state.activeCall.call_state === 'connected' && + this.state.activeCall.state === CallState.Connected && !callForRoom ); diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 2ab291ae86..3e1833a903 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -25,6 +25,7 @@ import AccessibleButton from '../elements/AccessibleButton'; import VideoView from "./VideoView"; import RoomAvatar from "../avatars/RoomAvatar"; import PulsedAvatar from '../avatars/PulsedAvatar'; +import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call'; interface IProps { // js-sdk room object. If set, we will only show calls for the given @@ -87,7 +88,7 @@ export default class CallView extends React.Component { }; private showCall() { - let call; + let call: MatrixCall; if (this.props.room) { const roomId = this.props.room.roomId; @@ -120,7 +121,7 @@ export default class CallView extends React.Component { call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement()); } } - if (call && call.type === "video" && call.call_state !== "ended" && call.call_state !== "ringing") { + if (call && call.type === "video" && call.state !== CallState.Ended && call.state !== CallState.Ringing) { this.getVideoView().getLocalVideoElement().style.display = "block"; this.getVideoView().getRemoteVideoElement().style.display = "block"; } else { diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx index 8e5d0f9e4a..560a034f47 100644 --- a/src/components/views/voip/IncomingCallBox.tsx +++ b/src/components/views/voip/IncomingCallBox.tsx @@ -25,6 +25,7 @@ import CallHandler from '../../../CallHandler'; import PulsedAvatar from '../avatars/PulsedAvatar'; import RoomAvatar from '../avatars/RoomAvatar'; import FormButton from '../elements/FormButton'; +import { CallState } from 'matrix-js-sdk/lib/webrtc/call'; interface IProps { } @@ -53,7 +54,7 @@ export default class IncomingCallBox extends React.Component { switch (payload.action) { case 'call_state': { const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); - if (call && call.call_state === 'ringing') { + if (call && call.state === CallState.Ringing) { this.setState({ incomingCall: call, }); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d3b942e6fa..eb8f9100ec 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2095,6 +2095,10 @@ "%(count)s of your messages have not been sent.|one": "Your message was not sent.", "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|other": "Resend all or cancel all now. You can also select individual messages to resend or cancel.", "%(count)s Resend all or cancel all now. You can also select individual messages to resend or cancel.|one": "Resend message or cancel message now.", + "Calling...": "Calling...", + "Call connecting...": "Call connecting...", + "Starting camera...": "Starting camera...", + "Starting microphone...": "Starting microphone...", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", From 94802036d9df10a7aa0938ab746945a67e4c9672 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 12 Oct 2020 09:55:21 +0100 Subject: [PATCH 10/20] AudioId -> AudioID --- src/CallHandler.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 4a71934b7f..edee74cac8 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -79,7 +79,7 @@ import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; import { MatrixCall, CallErrorCode, CallState, CallType } from "matrix-js-sdk/lib/webrtc/call"; -enum AudioId { +enum AudioID { Ring = 'ringAudio', Ringback = 'ringbackAudio', CallEnd = 'callendAudio', @@ -88,7 +88,7 @@ enum AudioId { export default class CallHandler { private calls = new Map(); - private audioPromises = new Map>(); + private audioPromises = new Map>(); static sharedInstance() { if (!window.mxCallHandler) { @@ -126,7 +126,7 @@ export default class CallHandler { return null; } - play(audioId: AudioId) { + play(audioId: AudioID) { // TODO: Attach an invisible element for this instead // which listens? const audio = document.getElementById(audioId) as HTMLMediaElement; @@ -155,7 +155,7 @@ export default class CallHandler { } } - pause(audioId: AudioId) { + pause(audioId: AudioID) { // TODO: Attach an invisible element for this instead // which listens? const audio = document.getElementById(audioId) as HTMLMediaElement; @@ -195,19 +195,19 @@ export default class CallHandler { switch (oldState) { case CallState.Ringing: - this.pause(AudioId.Ring); + this.pause(AudioID.Ring); break; case CallState.InviteSent: - this.pause(AudioId.Ringback); + this.pause(AudioID.Ringback); break; } switch (newState) { case CallState.Ringing: - this.play(AudioId.Ring); + this.play(AudioID.Ring); break; case CallState.InviteSent: - this.play(AudioId.Ringback); + this.play(AudioID.Ringback); break; case CallState.Ended: this.removeCallForRoom(call.roomId); @@ -215,13 +215,13 @@ export default class CallHandler { call.hangupParty === "remote" || (call.hangupParty === "local" && call.hangupReason === "invite_timeout") )) { - this.play(AudioId.Busy); + this.play(AudioID.Busy); Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { title: _t('Call Timeout'), description: _t('The remote side failed to pick up') + '.', }); } else { - this.play(AudioId.CallEnd); + this.play(AudioID.CallEnd); } } }); From 3af7abb5fec5d03a531c394a28acccda234cd56a Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 12 Oct 2020 09:56:43 +0100 Subject: [PATCH 11/20] This comment is no longer true --- src/CallHandler.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index edee74cac8..22939b2cc8 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -188,8 +188,6 @@ export default class CallHandler { call.on("hangup", () => { this.removeCallForRoom(call.roomId); }); - // map web rtc states to dummy UI state - // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing call.on("state", (newState: CallState, oldState: CallState) => { this.setCallState(call, newState); From abd5e3b3cfb389c37742d0eb7d52d144bdb514ea Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 12 Oct 2020 10:25:23 +0100 Subject: [PATCH 12/20] More enums --- src/CallHandler.tsx | 14 +++++++------- src/components/structures/RoomStatusBar.js | 17 +++++++++-------- src/components/structures/RoomView.tsx | 4 ++-- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 22939b2cc8..8b1dc41b07 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -77,7 +77,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; -import { MatrixCall, CallErrorCode, CallState, CallType } from "matrix-js-sdk/lib/webrtc/call"; +import { MatrixCall, CallErrorCode, CallState, CallType, CallEvent, CallParty } from "matrix-js-sdk/lib/webrtc/call"; enum AudioID { Ring = 'ringAudio', @@ -119,7 +119,7 @@ export default class CallHandler { getAnyActiveCall() { for (const call of this.calls.values()) { - if (call.state !== "ended") { + if (call.state !== CallState.Ended) { return call; } } @@ -170,7 +170,7 @@ export default class CallHandler { } private setCallListeners(call: MatrixCall) { - call.on("error", (err) => { + call.on(CallEvent.Error, (err) => { console.error("Call error:", err); if ( MatrixClientPeg.get().getTurnServers().length === 0 && @@ -185,10 +185,10 @@ export default class CallHandler { description: err.message, }); }); - call.on("hangup", () => { + call.on(CallEvent.Hangup, () => { this.removeCallForRoom(call.roomId); }); - call.on("state", (newState: CallState, oldState: CallState) => { + call.on(CallEvent.State, (newState: CallState, oldState: CallState) => { this.setCallState(call, newState); switch (oldState) { @@ -210,8 +210,8 @@ export default class CallHandler { case CallState.Ended: this.removeCallForRoom(call.roomId); if (oldState === CallState.InviteSent && ( - call.hangupParty === "remote" || - (call.hangupParty === "local" && call.hangupReason === "invite_timeout") + call.hangupParty === CallParty.Remote || + (call.hangupParty === CallParty.Local && call.hangupReason === CallErrorCode.InviteTimeout) )) { this.play(AudioID.Busy); Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 453663a261..3e07d617de 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -24,6 +24,7 @@ import Resend from '../../Resend'; import dis from '../../dispatcher/dispatcher'; import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; import {Action} from "../../dispatcher/actions"; +import { CallState, CallType } from 'matrix-js-sdk/lib/webrtc/call'; const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; @@ -122,7 +123,7 @@ export default class RoomStatusBar extends React.Component { }; _showCallBar() { - return this.props.callState !== 'ended' && this.props.callState !== 'ringing'; + return this.props.callState !== CallState.Ended && this.props.callState !== CallState.Ringing; } _onResendAllClick = () => { @@ -275,16 +276,16 @@ export default class RoomStatusBar extends React.Component { _getCallStatusText() { switch (this.props.callState) { - case 'create_offer': - case 'invite_sent': + case CallState.CreateOffer: + case CallState.InviteSent: return _t('Calling...'); - case 'connecting': - case 'create_answer': + case CallState.Connecting: + case CallState.CreateAnswer: return _t('Call connecting...'); - case 'connected': + case CallState.Connected: return _t('Active call'); - case 'wait_local_media': - if (this.props.callType === 'video') { + case CallState.WaitLocalMedia: + if (this.props.callType === CallType.Video) { return _t('Starting camera...'); } else { return _t('Starting microphone...'); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 2a6d0b5de8..8b90b6b810 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -71,7 +71,7 @@ import RoomHeader from "../views/rooms/RoomHeader"; import TintableSvg from "../views/elements/TintableSvg"; import {XOR} from "../../@types/common"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; -import { CallState, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; +import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -1892,7 +1892,7 @@ export default class RoomView extends React.Component { if (activeCall) { let zoomButton; let videoMuteButton; - if (activeCall.type === "video") { + if (activeCall.type === CallType.Video) { zoomButton = (
Date: Mon, 12 Oct 2020 11:11:17 +0100 Subject: [PATCH 13/20] Simplify types --- src/SecurityManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 2d97ce690b..4d277692df 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -79,7 +79,7 @@ async function confirmToDismiss(): Promise { function makeInputToKey( keyInfo: ISecretStorageKeyInfo, -): ({ passphrase, recoveryKey }: { passphrase: string, recoveryKey: string }) => Promise { +): (keyParams: { passphrase: string, recoveryKey: string }) => Promise { return async ({ passphrase, recoveryKey }) => { if (passphrase) { return deriveKey( From 414901dfaef1772677caaf557c0083c8819baa86 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 12 Oct 2020 11:38:32 +0100 Subject: [PATCH 14/20] More enums --- src/CallHandler.tsx | 21 +++++++++++++++---- src/components/views/rooms/MessageComposer.js | 5 +++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 8b1dc41b07..6b66a614d2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -77,7 +77,7 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog"; import WidgetStore from "./stores/WidgetStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; -import { MatrixCall, CallErrorCode, CallState, CallType, CallEvent, CallParty } from "matrix-js-sdk/lib/webrtc/call"; +import { MatrixCall, CallErrorCode, CallState, CallEvent, CallParty } from "matrix-js-sdk/lib/webrtc/call"; enum AudioID { Ring = 'ringAudio', @@ -86,6 +86,16 @@ enum AudioID { Busy = 'busyAudio', } +// Unlike 'CallType' in js-sdk, this one includes screen sharing +// (because a screen sharing call is only a screen sharing call to the caller, +// to the callee it's just a video call, at least as far as the current impl +// is concerned). +export enum PlaceCallType { + Voice = 'voice', + Video = 'video', + ScreenSharing = 'screensharing', +} + export default class CallHandler { private calls = new Map(); private audioPromises = new Map>(); @@ -271,18 +281,21 @@ export default class CallHandler { } - private placeCall(roomId: string, type: CallType, localElement: HTMLVideoElement, remoteElement: HTMLVideoElement) { + private placeCall( + roomId: string, type: PlaceCallType, + localElement: HTMLVideoElement, remoteElement: HTMLVideoElement, + ) { const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), roomId); this.calls.set(roomId, call); this.setCallListeners(call); - if (type === 'voice') { + if (type === PlaceCallType.Voice) { call.placeVoiceCall(); } else if (type === 'video') { call.placeVideoCall( remoteElement, localElement, ); - } else if (type === 'screensharing') { + } else if (type === PlaceCallType.ScreenSharing) { const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString(); if (screenCapErrorString) { this.removeCallForRoom(roomId); diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 2ca1cc5aef..1708e2e7f1 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -37,6 +37,7 @@ import WidgetStore from "../../../stores/WidgetStore"; import WidgetUtils from "../../../utils/WidgetUtils"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; +import { PlaceCallType } from "../../../CallHandler"; function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); @@ -53,7 +54,7 @@ function CallButton(props) { const onVoiceCallClick = (ev) => { dis.dispatch({ action: 'place_call', - type: "voice", + type: PlaceCallType.Voice, room_id: props.roomId, }); }; @@ -73,7 +74,7 @@ function VideoCallButton(props) { const onCallClick = (ev) => { dis.dispatch({ action: 'place_call', - type: ev.shiftKey ? "screensharing" : "video", + type: ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video, room_id: props.roomId, }); }; From 0ac4f21652b7ded873c770400ae0b0333662c67d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 12 Oct 2020 13:24:53 +0100 Subject: [PATCH 15/20] Upgrade matrix-js-sdk to 8.5.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 532bb1c3e5..32017f2112 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "8.5.0-rc.1", + "matrix-js-sdk": "8.5.0", "matrix-widget-api": "^0.1.0-beta.3", "minimist": "^1.2.5", "pako": "^1.0.11", diff --git a/yarn.lock b/yarn.lock index 7dc34164d4..5630ee2b64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5926,10 +5926,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@8.5.0-rc.1: - version "8.5.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.5.0-rc.1.tgz#7ad34d8b6a786a7d4a04173e3b17da56022cd62a" - integrity sha512-hVFYkN3/rsPiNXdFqcvRvg+Egr75qhPGMi2gp59PDr7JrZEtOACA0I3NCY1vPub9kXGTtPZqPuHGFrsF9qLXGw== +matrix-js-sdk@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.5.0.tgz#02d77e0e95fe32dcc74e0a94707f9103badfdca5" + integrity sha512-RJCqK/QkesL+63rX4C4mShEFw/ZR20D1UMw1RKY8pQhv1/7Skz+v5BTv/UCqG45E3rRYMarNOwy13CZ+yq33FA== dependencies: "@babel/runtime" "^7.11.2" another-json "^0.2.0" From 71e800abd1f54ebd86b2e062e54fcbea45f70b77 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 12 Oct 2020 13:36:52 +0100 Subject: [PATCH 16/20] Prepare changelog for v3.6.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98a4a1ff87..4a22954c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +Changes in [3.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.6.0) (2020-10-12) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.6.0-rc.1...v3.6.0) + + * Upgrade JS SDK to 8.5.0 + * [Release] Fix templating for v1 jitsi widgets + [\#5306](https://github.com/matrix-org/matrix-react-sdk/pull/5306) + * [Release] Use new preparing event for widget communications + [\#5304](https://github.com/matrix-org/matrix-react-sdk/pull/5304) + Changes in [3.6.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.6.0-rc.1) (2020-10-07) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.5.0...v3.6.0-rc.1) From 2242e6b8c610e868f62516bf0483962e92d55191 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 12 Oct 2020 13:36:52 +0100 Subject: [PATCH 17/20] v3.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32017f2112..71c8074a93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.6.0-rc.1", + "version": "3.6.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From d007b06d7c245354c126e9ab41c8b4b3f8b85f54 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 12 Oct 2020 13:40:23 +0100 Subject: [PATCH 18/20] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4951b7042c..3f073ce59c 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "is-ip": "^2.0.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.19", - "matrix-js-sdk": "8.5.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.3", "minimist": "^1.2.5", "pako": "^1.0.11", diff --git a/yarn.lock b/yarn.lock index 92f27c626d..1f06fd6130 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6500,10 +6500,9 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@8.5.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "8.5.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-8.5.0.tgz#02d77e0e95fe32dcc74e0a94707f9103badfdca5" - integrity sha512-RJCqK/QkesL+63rX4C4mShEFw/ZR20D1UMw1RKY8pQhv1/7Skz+v5BTv/UCqG45E3rRYMarNOwy13CZ+yq33FA== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d8c4101fdd521e189f4755c6f02a8971b991ef5f" dependencies: "@babel/runtime" "^7.11.2" another-json "^0.2.0" From ed9584bbac5764d686421407f22c561bfca5c1e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Oct 2020 09:55:03 +0100 Subject: [PATCH 19/20] Comment scoping block --- src/components/structures/RoomView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 8b90b6b810..f03b39ff52 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1740,6 +1740,7 @@ export default class RoomView extends React.Component { let activeCall = null; { + // New block because this variable doesn't need to hang around for the rest of the function const call = this.getCallForRoom(); if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { activeCall = call; From 4c11a82fefc764eeed8fda0fb64a19b79ceacc83 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Oct 2020 12:16:15 +0100 Subject: [PATCH 20/20] Fix rogue (partial) call bar Call state is now null if there is no call, so check for that too --- src/components/structures/RoomStatusBar.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 3e07d617de..e390be6979 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -123,7 +123,9 @@ export default class RoomStatusBar extends React.Component { }; _showCallBar() { - return this.props.callState !== CallState.Ended && this.props.callState !== CallState.Ringing; + return (this.props.callState && + (this.props.callState !== CallState.Ended && this.props.callState !== CallState.Ringing) + ); } _onResendAllClick = () => {