From 2fd9b9b3b9d5237737d85952363c7882b6d16f56 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 7 Oct 2020 14:23:52 +0100 Subject: [PATCH 01/43] 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/43] 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/43] 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/43] 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/43] 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 3e7f1176111f997029611bf4965b22800d6b9157 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 9 Oct 2020 09:26:52 -0600 Subject: [PATCH 06/43] 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 07/43] 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 08/43] 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 09/43] 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 10/43] 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 11/43] 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 12/43] 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 13/43] 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 14/43] 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 15/43] 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 16/43] 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 17/43] 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 18/43] 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 19/43] 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 = () => { From 54babddb38bee140ea1d7290fc6865746d4c619c Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 13 Oct 2020 15:08:23 +0100 Subject: [PATCH 20/43] Support glare for VoIP calls The js-sdk supports glare but we didn't support it, which means the js-sdk will still do glare but we didn't know about it, leaving the UI in horribly broken states where the js-sdk would be on a call but the app didn't think it was. Fixes https://github.com/vector-im/element-web/issues/5770 --- src/CallHandler.tsx | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 6b66a614d2..fc24feb90f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -179,8 +179,18 @@ export default class CallHandler { } } + private matchesCallForThisRoom(call: MatrixCall) { + // We don't allow placing more than one call per room, but that doesn't mean there + // can't be more than one, eg. in a glare situation. This checks that the given call + // is the call we consider 'the' call for its room. + const callForThisRoom = this.getCallForRoom(call.roomId); + return callForThisRoom && call.callId === callForThisRoom.callId; + } + private setCallListeners(call: MatrixCall) { call.on(CallEvent.Error, (err) => { + if (!this.matchesCallForThisRoom(call)) return; + console.error("Call error:", err); if ( MatrixClientPeg.get().getTurnServers().length === 0 && @@ -196,9 +206,13 @@ export default class CallHandler { }); }); call.on(CallEvent.Hangup, () => { + if (!this.matchesCallForThisRoom(call)) return; + this.removeCallForRoom(call.roomId); }); call.on(CallEvent.State, (newState: CallState, oldState: CallState) => { + if (!this.matchesCallForThisRoom(call)) return; + this.setCallState(call, newState); switch (oldState) { @@ -233,6 +247,21 @@ export default class CallHandler { } } }); + call.on(CallEvent.Replaced, (newCall: MatrixCall) => { + if (!this.matchesCallForThisRoom(call)) return; + + console.log(`Call ID ${call.callId} is being replaced by call ID ${newCall.callId}`); + + if (call.state === CallState.Ringing) { + this.pause(AudioID.Ring); + } else if (call.state === CallState.InviteSent) { + this.pause(AudioID.Ringback); + } + + this.calls.set(newCall.roomId, newCall); + this.setCallListeners(newCall); + this.setCallState(newCall, newCall.state); + }); } private setCallState(call: MatrixCall, status: CallState) { From 5989a21dfb97e0804b94bae503b2637e6d2dd7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 13 Oct 2020 16:02:05 +0200 Subject: [PATCH 21/43] event-index: Pass the user/device id pair when initializing the event index. --- src/indexing/BaseEventIndexManager.ts | 5 ++++- src/indexing/EventIndexPeg.js | 9 +++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts index 64cf01bd6e..df14f84622 100644 --- a/src/indexing/BaseEventIndexManager.ts +++ b/src/indexing/BaseEventIndexManager.ts @@ -105,10 +105,13 @@ export default abstract class BaseEventIndexManager { /** * Initialize the event index for the given user. * + * @param {string} user_id The event that should be added to the index. + * @param {string} device_id The profile of the event sender at the + * * @return {Promise} A promise that will resolve when the event index is * initialized. */ - async initEventIndex(): Promise { + async initEventIndex(user_id: string, device_id: string): Promise { throw new Error("Unimplemented"); } diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index 58e8430825..6acc6caa10 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -21,6 +21,7 @@ limitations under the License. import PlatformPeg from "../PlatformPeg"; import EventIndex from "../indexing/EventIndex"; +import {MatrixClientPeg} from "../MatrixClientPeg"; import SettingsStore from '../settings/SettingsStore'; import {SettingLevel} from "../settings/SettingLevel"; @@ -70,9 +71,13 @@ class EventIndexPeg { async initEventIndex() { const index = new EventIndex(); const indexManager = PlatformPeg.get().getEventIndexingManager(); + const client = MatrixClientPeg.get(); + + const user_id = client.getUserId(); + const device_id = client.getDeviceId(); try { - await indexManager.initEventIndex(); + await indexManager.initEventIndex(user_id, device_id); const userVersion = await indexManager.getUserVersion(); const eventIndexIsEmpty = await indexManager.isEventIndexEmpty(); @@ -83,7 +88,7 @@ class EventIndexPeg { await indexManager.closeEventIndex(); await this.deleteEventIndex(); - await indexManager.initEventIndex(); + await indexManager.initEventIndex(user_id, device_id); await indexManager.setUserVersion(INDEX_VERSION); } From 860b1b46e0440293af0460434cf16b1760e3288c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 13 Oct 2020 17:03:58 +0200 Subject: [PATCH 22/43] event-index: Use camel case for the user/device id. --- src/indexing/BaseEventIndexManager.ts | 6 +++--- src/indexing/EventIndexPeg.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts index df14f84622..2474406618 100644 --- a/src/indexing/BaseEventIndexManager.ts +++ b/src/indexing/BaseEventIndexManager.ts @@ -105,13 +105,13 @@ export default abstract class BaseEventIndexManager { /** * Initialize the event index for the given user. * - * @param {string} user_id The event that should be added to the index. - * @param {string} device_id The profile of the event sender at the + * @param {string} userId The event that should be added to the index. + * @param {string} deviceId The profile of the event sender at the * * @return {Promise} A promise that will resolve when the event index is * initialized. */ - async initEventIndex(user_id: string, device_id: string): Promise { + async initEventIndex(userId: string, deviceId: string): Promise { throw new Error("Unimplemented"); } diff --git a/src/indexing/EventIndexPeg.js b/src/indexing/EventIndexPeg.js index 6acc6caa10..443daa8f43 100644 --- a/src/indexing/EventIndexPeg.js +++ b/src/indexing/EventIndexPeg.js @@ -73,11 +73,11 @@ class EventIndexPeg { const indexManager = PlatformPeg.get().getEventIndexingManager(); const client = MatrixClientPeg.get(); - const user_id = client.getUserId(); - const device_id = client.getDeviceId(); + const userId = client.getUserId(); + const deviceId = client.getDeviceId(); try { - await indexManager.initEventIndex(user_id, device_id); + await indexManager.initEventIndex(userId, deviceId); const userVersion = await indexManager.getUserVersion(); const eventIndexIsEmpty = await indexManager.isEventIndexEmpty(); @@ -88,7 +88,7 @@ class EventIndexPeg { await indexManager.closeEventIndex(); await this.deleteEventIndex(); - await indexManager.initEventIndex(user_id, device_id); + await indexManager.initEventIndex(userId, deviceId); await indexManager.setUserVersion(INDEX_VERSION); } From a2b61796afb5692fb433de04b4ced72200b720eb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 10:30:52 +0100 Subject: [PATCH 23/43] Improve LHS resize performance Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 79f2916200..23c91d7ee0 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -218,6 +218,7 @@ class LoggedInView extends React.Component { vertical: "mx_ResizeHandle_vertical", reverse: "mx_ResizeHandle_reverse", }; + let size; const collapseConfig = { toggleSize: 260 - 50, onCollapsed: (collapsed) => { @@ -228,21 +229,19 @@ class LoggedInView extends React.Component { dis.dispatch({action: "show_left_panel"}, true); } }, - onResized: (size) => { - window.localStorage.setItem("mx_lhs_size", '' + size); + onResized: (_size) => { + size = _size; this.props.resizeNotifier.notifyLeftHandleResized(); }, onResizeStart: () => { this.props.resizeNotifier.startResizing(); }, onResizeStop: () => { + window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.stopResizing(); }, }; - const resizer = new Resizer( - this._resizeContainer.current, - CollapseDistributor, - collapseConfig); + const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig); resizer.setClassNames(classNames); return resizer; } From bb52bad0995380b9e52ce785a640e925f74de115 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 16:23:49 +0100 Subject: [PATCH 24/43] Remove stale props and CSS classes Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_AppsDrawer.scss | 79 ------------------------ src/components/structures/RoomView.tsx | 1 - src/components/views/rooms/AppsDrawer.js | 3 - src/components/views/rooms/AuxPanel.js | 3 - 4 files changed, 86 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 451704bd88..6e3ffbe5f0 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -50,10 +50,6 @@ $MiniAppTileHeight: 200px; } } -.mx_AppsDrawer_hidden { - display: none; -} - .mx_AppsContainer { display: flex; flex-direction: row; @@ -78,15 +74,6 @@ $MiniAppTileHeight: 200px; font-size: $font-12px; } -.mx_SetAppURLDialog_input { - border-radius: 3px; - border: 1px solid $input-border-color; - padding: 9px; - color: $primary-hairline-color; - background-color: $primary-bg-color; - font-size: $font-15px; -} - .mx_AppTile { width: 50%; border: 5px solid $widget-menu-bar-bg-color; @@ -242,72 +229,6 @@ $MiniAppTileHeight: 200px; display: block; } -.mx_AppTileMenuBarWidgetPadding { - margin-right: 5px; -} - -.mx_AppIconTile { - background-color: $lightbox-bg-color; - border: 1px solid rgba(0, 0, 0, 0); - width: 200px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); - transition: 0.3s; - border-radius: 3px; - margin: 5px; - display: inline-block; -} - -.mx_AppIconTile.mx_AppIconTile_active { - color: $accent-color; - border-color: $accent-color; -} - -.mx_AppIconTile:hover { - border: 1px solid $accent-color; - box-shadow: 0 0 10px 5px rgba(200, 200, 200, 0.5); -} - -.mx_AppIconTile_content { - padding: 2px 16px; - height: 60px; - overflow: hidden; -} - -.mx_AppIconTile_content h4 { - margin-top: 5px; - margin-bottom: 2px; -} - -.mx_AppIconTile_content p { - margin-top: 0; - margin-bottom: 5px; - font-size: smaller; -} - -.mx_AppIconTile_image { - padding: 10px; - max-width: 100px; - max-height: 100px; - width: auto; - height: auto; -} - -.mx_AppIconTile_imageContainer { - text-align: center; - width: 100%; - background-color: white; - border-radius: 3px 3px 0 0; - height: 155px; - display: flex; - justify-content: center; - align-items: center; -} - -form.mx_Custom_Widget_Form div { - margin-top: 10px; - margin-bottom: 10px; -} - .mx_AppPermissionWarning { text-align: center; background-color: $widget-menu-bar-bg-color; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index fcb2d274c1..bfe823b2f9 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1853,7 +1853,6 @@ export default class RoomView extends React.Component { draggingFile={this.state.draggingFile} maxHeight={this.state.auxPanelMaxHeight} showApps={this.state.showApps} - hideAppsDrawer={false} onResize={this.onResize} resizeNotifier={this.props.resizeNotifier} > diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index a67338b9d5..a360016ba6 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -40,12 +40,10 @@ export default class AppsDrawer extends React.Component { room: PropTypes.object.isRequired, resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired, showApps: PropTypes.bool, // Should apps be rendered - hide: PropTypes.bool, // If rendered, should apps drawer be visible }; static defaultProps = { showApps: true, - hide: false, }; constructor(props) { @@ -173,7 +171,6 @@ export default class AppsDrawer extends React.Component { const classes = classNames({ "mx_AppsDrawer": true, - "mx_AppsDrawer_hidden": this.props.hide, "mx_AppsDrawer_fullWidth": apps.length < 2, "mx_AppsDrawer_minimised": !this.props.showApps, }); diff --git a/src/components/views/rooms/AuxPanel.js b/src/components/views/rooms/AuxPanel.js index b7ed457a74..a088418d5e 100644 --- a/src/components/views/rooms/AuxPanel.js +++ b/src/components/views/rooms/AuxPanel.js @@ -37,7 +37,6 @@ export default class AuxPanel extends React.Component { room: PropTypes.object.isRequired, userId: PropTypes.string.isRequired, showApps: PropTypes.bool, // Render apps - hideAppsDrawer: PropTypes.bool, // Do not display apps drawer and content (may still be rendered) // set to true to show the file drop target draggingFile: PropTypes.bool, @@ -54,7 +53,6 @@ export default class AuxPanel extends React.Component { static defaultProps = { showApps: true, - hideAppsDrawer: false, }; constructor(props) { @@ -170,7 +168,6 @@ export default class AuxPanel extends React.Component { userId={this.props.userId} maxHeight={this.props.maxHeight} showApps={this.props.showApps} - hide={this.props.hideAppsDrawer} resizeNotifier={this.props.resizeNotifier} />; } From 17ed333beb7221debdedff8e0fa5a15cf130b43a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:36:40 +0100 Subject: [PATCH 25/43] Replace extend with Object.assign Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/ContentMessages.tsx | 5 ++--- src/extend.js | 26 -------------------------- 2 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 src/extend.js diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index eb8fff0eb1..cba8671143 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -17,7 +17,6 @@ limitations under the License. */ import React from "react"; -import extend from './extend'; import dis from './dispatcher/dispatcher'; import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClient} from "matrix-js-sdk/src/client"; @@ -497,7 +496,7 @@ export default class ContentMessages { if (file.type.indexOf('image/') === 0) { content.msgtype = 'm.image'; infoForImageFile(matrixClient, roomId, file).then((imageInfo) => { - extend(content.info, imageInfo); + Object.assign(content.info, imageInfo); resolve(); }, (e) => { console.error(e); @@ -510,7 +509,7 @@ export default class ContentMessages { } else if (file.type.indexOf('video/') === 0) { content.msgtype = 'm.video'; infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => { - extend(content.info, videoInfo); + Object.assign(content.info, videoInfo); resolve(); }, (e) => { content.msgtype = 'm.file'; diff --git a/src/extend.js b/src/extend.js deleted file mode 100644 index 263d802ab6..0000000000 --- a/src/extend.js +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -'use strict'; - -export default function(dest, src) { - for (const i in src) { - if (src.hasOwnProperty(i)) { - dest[i] = src[i]; - } - } - return dest; -} From 8e401cff05c2915b6bff050fc10b3ff222d20611 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:36:50 +0100 Subject: [PATCH 26/43] Convert Analytics to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{Analytics.js => Analytics.tsx} | 124 ++++++++++++++++------------ 1 file changed, 73 insertions(+), 51 deletions(-) rename src/{Analytics.js => Analytics.tsx} (76%) diff --git a/src/Analytics.js b/src/Analytics.tsx similarity index 76% rename from src/Analytics.js rename to src/Analytics.tsx index 135cc2eb7a..79d45cfd1d 100644 --- a/src/Analytics.js +++ b/src/Analytics.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; -import { getCurrentLanguage, _t, _td } from './languageHandler'; +import {getCurrentLanguage, _t, _td, IVariables} from './languageHandler'; import PlatformPeg from './PlatformPeg'; import SdkConfig from './SdkConfig'; import Modal from './Modal'; @@ -27,7 +27,7 @@ const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password const hashVarRegex = /#\/(group|room|user)\/.*$/; // Remove all but the first item in the hash path. Redact unexpected hashes. -function getRedactedHash(hash) { +function getRedactedHash(hash: string): string { // Don't leak URLs we aren't expecting - they could contain tokens/PII const match = hashRegex.exec(hash); if (!match) { @@ -44,7 +44,7 @@ function getRedactedHash(hash) { // Return the current origin, path and hash separated with a `/`. This does // not include query parameters. -function getRedactedUrl() { +function getRedactedUrl(): string { const { origin, hash } = window.location; let { pathname } = window.location; @@ -56,7 +56,25 @@ function getRedactedUrl() { return origin + pathname + getRedactedHash(hash); } -const customVariables = { +interface IData { + /* eslint-disable camelcase */ + gt_ms?: string; + e_c?: string; + e_a?: string; + e_n?: string; + e_v?: string; + ping?: string; + /* eslint-enable camelcase */ +} + +interface IVariable { + id: number; + expl: string; // explanation + example: string; // example value + getTextVariables?(): IVariables; // object to pass as 2nd argument to `_t` +} + +const customVariables: Record = { // The Matomo installation at https://matomo.riot.im is currently configured // with a limit of 10 custom variables. 'App Platform': { @@ -120,7 +138,7 @@ const customVariables = { }, }; -function whitelistRedact(whitelist, str) { +function whitelistRedact(whitelist: string[], str: string): string { if (whitelist.includes(str)) return str; return ''; } @@ -130,7 +148,7 @@ const CREATION_TS_KEY = "mx_Riot_Analytics_cts"; const VISIT_COUNT_KEY = "mx_Riot_Analytics_vc"; const LAST_VISIT_TS_KEY = "mx_Riot_Analytics_lvts"; -function getUid() { +function getUid(): string { try { let data = localStorage && localStorage.getItem(UID_KEY); if (!data && localStorage) { @@ -145,32 +163,36 @@ function getUid() { const HEARTBEAT_INTERVAL = 30 * 1000; // seconds -class Analytics { +export class Analytics { + private baseUrl: URL = null; + private siteId: string = null; + private visitVariables: Record = {}; // {[id: number]: [name: string, value: string]} + private firstPage = true; + private heartbeatIntervalID: number = null; + + private readonly creationTs: string; + private readonly lastVisitTs: string; + private readonly visitCount: string; + constructor() { - this.baseUrl = null; - this.siteId = null; - this.visitVariables = {}; - - this.firstPage = true; - this._heartbeatIntervalID = null; - this.creationTs = localStorage && localStorage.getItem(CREATION_TS_KEY); if (!this.creationTs && localStorage) { - localStorage.setItem(CREATION_TS_KEY, this.creationTs = new Date().getTime()); + localStorage.setItem(CREATION_TS_KEY, this.creationTs = String(new Date().getTime())); } this.lastVisitTs = localStorage && localStorage.getItem(LAST_VISIT_TS_KEY); - this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || 0; + this.visitCount = localStorage && localStorage.getItem(VISIT_COUNT_KEY) || "0"; + this.visitCount = String(parseInt(this.visitCount, 10) + 1); // increment if (localStorage) { - localStorage.setItem(VISIT_COUNT_KEY, parseInt(this.visitCount, 10) + 1); + localStorage.setItem(VISIT_COUNT_KEY, this.visitCount); } } - get disabled() { + public get disabled() { return !this.baseUrl; } - canEnable() { + public canEnable() { const config = SdkConfig.get(); return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId; } @@ -179,67 +201,67 @@ class Analytics { * Enable Analytics if initialized but disabled * otherwise try and initalize, no-op if piwik config missing */ - async enable() { + public async enable() { if (!this.disabled) return; if (!this.canEnable()) return; const config = SdkConfig.get(); this.baseUrl = new URL("piwik.php", config.piwik.url); // set constants - this.baseUrl.searchParams.set("rec", 1); // rec is required for tracking + this.baseUrl.searchParams.set("rec", "1"); // rec is required for tracking this.baseUrl.searchParams.set("idsite", config.piwik.siteId); // rec is required for tracking - this.baseUrl.searchParams.set("apiv", 1); // API version to use - this.baseUrl.searchParams.set("send_image", 0); // we want a 204, not a tiny GIF + this.baseUrl.searchParams.set("apiv", "1"); // API version to use + this.baseUrl.searchParams.set("send_image", "0"); // we want a 204, not a tiny GIF // set user parameters this.baseUrl.searchParams.set("_id", getUid()); // uuid this.baseUrl.searchParams.set("_idts", this.creationTs); // first ts - this.baseUrl.searchParams.set("_idvc", parseInt(this.visitCount, 10)+ 1); // visit count + this.baseUrl.searchParams.set("_idvc", this.visitCount); // visit count if (this.lastVisitTs) { this.baseUrl.searchParams.set("_viewts", this.lastVisitTs); // last visit ts } const platform = PlatformPeg.get(); - this._setVisitVariable('App Platform', platform.getHumanReadableName()); + this.setVisitVariable('App Platform', platform.getHumanReadableName()); try { - this._setVisitVariable('App Version', await platform.getAppVersion()); + this.setVisitVariable('App Version', await platform.getAppVersion()); } catch (e) { - this._setVisitVariable('App Version', 'unknown'); + this.setVisitVariable('App Version', 'unknown'); } - this._setVisitVariable('Chosen Language', getCurrentLanguage()); + this.setVisitVariable('Chosen Language', getCurrentLanguage()); const hostname = window.location.hostname; if (hostname === 'riot.im') { - this._setVisitVariable('Instance', window.location.pathname); + this.setVisitVariable('Instance', window.location.pathname); } else if (hostname.endsWith('.element.io')) { - this._setVisitVariable('Instance', hostname.replace('.element.io', '')); + this.setVisitVariable('Instance', hostname.replace('.element.io', '')); } let installedPWA = "unknown"; try { // Known to work at least for desktop Chrome - installedPWA = window.matchMedia('(display-mode: standalone)').matches; + installedPWA = String(window.matchMedia('(display-mode: standalone)').matches); } catch (e) { } - this._setVisitVariable('Installed PWA', installedPWA); + this.setVisitVariable('Installed PWA', installedPWA); let touchInput = "unknown"; try { // MDN claims broad support across browsers - touchInput = window.matchMedia('(pointer: coarse)').matches; + touchInput = String(window.matchMedia('(pointer: coarse)').matches); } catch (e) { } - this._setVisitVariable('Touch Input', touchInput); + this.setVisitVariable('Touch Input', touchInput); // start heartbeat - this._heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL); + this.heartbeatIntervalID = window.setInterval(this.ping.bind(this), HEARTBEAT_INTERVAL); } /** * Disable Analytics, stop the heartbeat and clear identifiers from localStorage */ - disable() { + public disable() { if (this.disabled) return; this.trackEvent('Analytics', 'opt-out'); - window.clearInterval(this._heartbeatIntervalID); + window.clearInterval(this.heartbeatIntervalID); this.baseUrl = null; this.visitVariables = {}; localStorage.removeItem(UID_KEY); @@ -248,7 +270,7 @@ class Analytics { localStorage.removeItem(LAST_VISIT_TS_KEY); } - async _track(data) { + private async _track(data: IData) { if (this.disabled) return; const now = new Date(); @@ -264,13 +286,13 @@ class Analytics { s: now.getSeconds(), }; - const url = new URL(this.baseUrl); + const url = new URL(this.baseUrl.toString()); // copy for (const key in params) { url.searchParams.set(key, params[key]); } try { - await window.fetch(url, { + await window.fetch(url.toString(), { method: "GET", mode: "no-cors", cache: "no-cache", @@ -281,14 +303,14 @@ class Analytics { } } - ping() { + public ping() { this._track({ - ping: 1, + ping: "1", }); - localStorage.setItem(LAST_VISIT_TS_KEY, new Date().getTime()); // update last visit ts + localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts } - trackPageChange(generationTimeMs) { + public trackPageChange(generationTimeMs?: number) { if (this.disabled) return; if (this.firstPage) { // De-duplicate first page @@ -303,11 +325,11 @@ class Analytics { } this._track({ - gt_ms: generationTimeMs, + gt_ms: String(generationTimeMs), }); } - trackEvent(category, action, name, value) { + public trackEvent(category: string, action: string, name?: string, value?: string) { if (this.disabled) return; this._track({ e_c: category, @@ -317,7 +339,7 @@ class Analytics { }); } - _setVisitVariable(key, value) { + private setVisitVariable(key: keyof typeof customVariables, value: string) { if (this.disabled) return; this.visitVariables[customVariables[key].id] = [key, value]; } @@ -330,13 +352,13 @@ class Analytics { const whitelistedHSUrls = config.piwik.whitelistedHSUrls || []; - this._setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In'); - this._setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); + this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In'); + this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); } setBreadcrumbs(state) { if (this.disabled) return; - this._setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled'); + this.setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled'); } showDetailsModal = () => { @@ -360,7 +382,7 @@ class Analytics { 'e.g. ', {}, { - CurrentPageURL: getRedactedUrl(), + CurrentPageURL: getRedactedUrl, }, ), }, From f26a842ff3afac01f9208c2543a06ece23dae1d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:37:06 +0100 Subject: [PATCH 27/43] Convert WhoIsTyping to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{WhoIsTyping.js => WhoIsTyping.ts} | 34 ++++++++++++-------------- 1 file changed, 15 insertions(+), 19 deletions(-) rename src/{WhoIsTyping.js => WhoIsTyping.ts} (72%) diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.ts similarity index 72% rename from src/WhoIsTyping.js rename to src/WhoIsTyping.ts index d11cddf487..a8ca425ea8 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.ts @@ -14,19 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {Room} from "matrix-js-sdk/src/models/room"; +import {RoomMember} from "matrix-js-sdk/src/models/room-member"; + import {MatrixClientPeg} from "./MatrixClientPeg"; import { _t } from './languageHandler'; -export function usersTypingApartFromMeAndIgnored(room) { - return usersTyping( - room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers()), - ); +export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] { + return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers())); } -export function usersTypingApartFromMe(room) { - return usersTyping( - room, [MatrixClientPeg.get().credentials.userId], - ); +export function usersTypingApartFromMe(room: Room): RoomMember[] { + return usersTyping(room, [MatrixClientPeg.get().getUserId()]); } /** @@ -34,15 +33,11 @@ export function usersTypingApartFromMe(room) { * to exclude, return a list of user objects who are typing. * @param {Room} room: room object to get users from. * @param {string[]} exclude: list of user mxids to exclude. - * @returns {string[]} list of user objects who are typing. + * @returns {RoomMember[]} list of user objects who are typing. */ -export function usersTyping(room, exclude) { +export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] { const whoIsTyping = []; - if (exclude === undefined) { - exclude = []; - } - const memberKeys = Object.keys(room.currentState.members); for (let i = 0; i < memberKeys.length; ++i) { const userId = memberKeys[i]; @@ -57,20 +52,21 @@ export function usersTyping(room, exclude) { return whoIsTyping; } -export function whoIsTypingString(whoIsTyping, limit) { +export function whoIsTypingString(whoIsTyping: RoomMember[], limit: number): string { let othersCount = 0; if (whoIsTyping.length > limit) { othersCount = whoIsTyping.length - limit + 1; } + if (whoIsTyping.length === 0) { return ''; } else if (whoIsTyping.length === 1) { return _t('%(displayName)s is typing …', {displayName: whoIsTyping[0].name}); } - const names = whoIsTyping.map(function(m) { - return m.name; - }); - if (othersCount>=1) { + + const names = whoIsTyping.map(m => m.name); + + if (othersCount >= 1) { return _t('%(names)s and %(count)s others are typing …', { names: names.slice(0, limit - 1).join(', '), count: othersCount, From 1a4a86682024d0bba9529ff75fd08af689b693a0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:38:33 +0100 Subject: [PATCH 28/43] Convert Avatar to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{Avatar.js => Avatar.ts} | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) rename src/{Avatar.js => Avatar.ts} (85%) diff --git a/src/Avatar.js b/src/Avatar.ts similarity index 85% rename from src/Avatar.js rename to src/Avatar.ts index 1c1182b98d..b742d9002c 100644 --- a/src/Avatar.js +++ b/src/Avatar.ts @@ -14,14 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; +import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; +import {RoomMember} from "matrix-js-sdk/src/models/room-member"; +import {User} from "matrix-js-sdk/src/models/user"; +import {Room} from "matrix-js-sdk/src/models/room"; + import {MatrixClientPeg} from './MatrixClientPeg'; import DMRoomMap from './utils/DMRoomMap'; -import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; + +type ResizeMethod = "crop" | "scale"; // Not to be used for BaseAvatar urls as that has similar default avatar fallback already -export function avatarUrlForMember(member, width, height, resizeMethod) { - let url; +export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) { + let url: string; if (member && member.getAvatarUrl) { url = member.getAvatarUrl( MatrixClientPeg.get().getHomeserverUrl(), @@ -41,7 +46,7 @@ export function avatarUrlForMember(member, width, height, resizeMethod) { return url; } -export function avatarUrlForUser(user, width, height, resizeMethod) { +export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod: ResizeMethod) { const url = getHttpUriForMxc( MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl, Math.floor(width * window.devicePixelRatio), @@ -54,14 +59,14 @@ export function avatarUrlForUser(user, width, height, resizeMethod) { return url; } -function isValidHexColor(color) { +function isValidHexColor(color: string): boolean { return typeof color === "string" && - (color.length === 7 || color.lengh === 9) && + (color.length === 7 || color.length === 9) && color.charAt(0) === "#" && !color.substr(1).split("").some(c => isNaN(parseInt(c, 16))); } -function urlForColor(color) { +function urlForColor(color: string): string { const size = 40; const canvas = document.createElement("canvas"); canvas.width = size; @@ -79,9 +84,9 @@ function urlForColor(color) { // XXX: Ideally we'd clear this cache when the theme changes // but since this function is at global scope, it's a bit // hard to install a listener here, even if there were a clear event to listen to -const colorToDataURLCache = new Map(); +const colorToDataURLCache = new Map(); -export function defaultAvatarUrlForString(s) { +export function defaultAvatarUrlForString(s: string): string { if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake const defaultColors = ['#0DBD8B', '#368bd6', '#ac3ba8']; let total = 0; @@ -113,7 +118,7 @@ export function defaultAvatarUrlForString(s) { * @param {string} name * @return {string} the first letter */ -export function getInitialLetter(name) { +export function getInitialLetter(name: string): string { if (!name) { // XXX: We should find out what causes the name to sometimes be falsy. console.trace("`name` argument to `getInitialLetter` not supplied"); @@ -146,7 +151,7 @@ export function getInitialLetter(name) { return firstChar.toUpperCase(); } -export function avatarUrlForRoom(room, width, height, resizeMethod) { +export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod: ResizeMethod) { if (!room) return null; // null-guard const explicitRoomAvatar = room.getAvatarUrl( From ee4f75cb6ec3bb1976e73e62c0bbdb880b52cdf7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:38:49 +0100 Subject: [PATCH 29/43] Convert UserActivity to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{UserActivity.js => UserActivity.ts} | 121 +++++++++++------------ 1 file changed, 59 insertions(+), 62 deletions(-) rename src/{UserActivity.js => UserActivity.ts} (63%) diff --git a/src/UserActivity.js b/src/UserActivity.ts similarity index 63% rename from src/UserActivity.js rename to src/UserActivity.ts index 0174aebaf5..bf2a546979 100644 --- a/src/UserActivity.js +++ b/src/UserActivity.ts @@ -38,19 +38,16 @@ const RECENTLY_ACTIVE_THRESHOLD_MS = 2 * 60 * 1000; * see doc on the userActive* functions for what these mean. */ export default class UserActivity { - constructor(windowObj, documentObj) { - this._window = windowObj; - this._document = documentObj; + private readonly activeNowTimeout: Timer; + private readonly activeRecentlyTimeout: Timer; + private attachedActiveNowTimers: Timer[] = []; + private attachedActiveRecentlyTimers: Timer[] = []; + private lastScreenX = 0; + private lastScreenY = 0; - this._attachedActiveNowTimers = []; - this._attachedActiveRecentlyTimers = []; - this._activeNowTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS); - this._activeRecentlyTimeout = new Timer(RECENTLY_ACTIVE_THRESHOLD_MS); - this._onUserActivity = this._onUserActivity.bind(this); - this._onWindowBlurred = this._onWindowBlurred.bind(this); - this._onPageVisibilityChanged = this._onPageVisibilityChanged.bind(this); - this.lastScreenX = 0; - this.lastScreenY = 0; + constructor(private readonly window: Window, private readonly document: Document) { + this.activeNowTimeout = new Timer(CURRENTLY_ACTIVE_THRESHOLD_MS); + this.activeRecentlyTimeout = new Timer(RECENTLY_ACTIVE_THRESHOLD_MS); } static sharedInstance() { @@ -69,8 +66,8 @@ export default class UserActivity { * later on when the user does become active. * @param {Timer} timer the timer to use */ - timeWhileActiveNow(timer) { - this._timeWhile(timer, this._attachedActiveNowTimers); + public timeWhileActiveNow(timer: Timer) { + this.timeWhile(timer, this.attachedActiveNowTimers); if (this.userActiveNow()) { timer.start(); } @@ -85,14 +82,14 @@ export default class UserActivity { * later on when the user does become active. * @param {Timer} timer the timer to use */ - timeWhileActiveRecently(timer) { - this._timeWhile(timer, this._attachedActiveRecentlyTimers); + public timeWhileActiveRecently(timer: Timer) { + this.timeWhile(timer, this.attachedActiveRecentlyTimers); if (this.userActiveRecently()) { timer.start(); } } - _timeWhile(timer, attachedTimers) { + private timeWhile(timer: Timer, attachedTimers: Timer[]) { // important this happens first const index = attachedTimers.indexOf(timer); if (index === -1) { @@ -112,36 +109,36 @@ export default class UserActivity { /** * Start listening to user activity */ - start() { - this._document.addEventListener('mousedown', this._onUserActivity); - this._document.addEventListener('mousemove', this._onUserActivity); - this._document.addEventListener('keydown', this._onUserActivity); - this._document.addEventListener("visibilitychange", this._onPageVisibilityChanged); - this._window.addEventListener("blur", this._onWindowBlurred); - this._window.addEventListener("focus", this._onUserActivity); + public start() { + this.document.addEventListener('mousedown', this.onUserActivity); + this.document.addEventListener('mousemove', this.onUserActivity); + this.document.addEventListener('keydown', this.onUserActivity); + this.document.addEventListener("visibilitychange", this.onPageVisibilityChanged); + this.window.addEventListener("blur", this.onWindowBlurred); + this.window.addEventListener("focus", this.onUserActivity); // can't use document.scroll here because that's only the document // itself being scrolled. Need to use addEventListener's useCapture. // also this needs to be the wheel event, not scroll, as scroll is // fired when the view scrolls down for a new message. - this._window.addEventListener('wheel', this._onUserActivity, { - passive: true, capture: true, + this.window.addEventListener('wheel', this.onUserActivity, { + passive: true, + capture: true, }); } /** * Stop tracking user activity */ - stop() { - this._document.removeEventListener('mousedown', this._onUserActivity); - this._document.removeEventListener('mousemove', this._onUserActivity); - this._document.removeEventListener('keydown', this._onUserActivity); - this._window.removeEventListener('wheel', this._onUserActivity, { - passive: true, capture: true, + public stop() { + this.document.removeEventListener('mousedown', this.onUserActivity); + this.document.removeEventListener('mousemove', this.onUserActivity); + this.document.removeEventListener('keydown', this.onUserActivity); + this.window.removeEventListener('wheel', this.onUserActivity, { + capture: true, }); - - this._document.removeEventListener("visibilitychange", this._onPageVisibilityChanged); - this._window.removeEventListener("blur", this._onWindowBlurred); - this._window.removeEventListener("focus", this._onUserActivity); + this.document.removeEventListener("visibilitychange", this.onPageVisibilityChanged); + this.window.removeEventListener("blur", this.onWindowBlurred); + this.window.removeEventListener("focus", this.onUserActivity); } /** @@ -151,8 +148,8 @@ export default class UserActivity { * user's attention at any given moment. * @returns {boolean} true if user is currently 'active' */ - userActiveNow() { - return this._activeNowTimeout.isRunning(); + public userActiveNow() { + return this.activeNowTimeout.isRunning(); } /** @@ -163,27 +160,27 @@ export default class UserActivity { * (or they may have gone to make tea and left the window focused). * @returns {boolean} true if user has been active recently */ - userActiveRecently() { - return this._activeRecentlyTimeout.isRunning(); + public userActiveRecently() { + return this.activeRecentlyTimeout.isRunning(); } - _onPageVisibilityChanged(e) { - if (this._document.visibilityState === "hidden") { - this._activeNowTimeout.abort(); - this._activeRecentlyTimeout.abort(); + private onPageVisibilityChanged = e => { + if (this.document.visibilityState === "hidden") { + this.activeNowTimeout.abort(); + this.activeRecentlyTimeout.abort(); } else { - this._onUserActivity(e); + this.onUserActivity(e); } - } + }; - _onWindowBlurred() { - this._activeNowTimeout.abort(); - this._activeRecentlyTimeout.abort(); - } + private onWindowBlurred = () => { + this.activeNowTimeout.abort(); + this.activeRecentlyTimeout.abort(); + }; - _onUserActivity(event) { + private onUserActivity = (event: MouseEvent) => { // ignore anything if the window isn't focused - if (!this._document.hasFocus()) return; + if (!this.document.hasFocus()) return; if (event.screenX && event.type === "mousemove") { if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) { @@ -195,25 +192,25 @@ export default class UserActivity { } dis.dispatch({action: 'user_activity'}); - if (!this._activeNowTimeout.isRunning()) { - this._activeNowTimeout.start(); + if (!this.activeNowTimeout.isRunning()) { + this.activeNowTimeout.start(); dis.dispatch({action: 'user_activity_start'}); - this._runTimersUntilTimeout(this._attachedActiveNowTimers, this._activeNowTimeout); + UserActivity.runTimersUntilTimeout(this.attachedActiveNowTimers, this.activeNowTimeout); } else { - this._activeNowTimeout.restart(); + this.activeNowTimeout.restart(); } - if (!this._activeRecentlyTimeout.isRunning()) { - this._activeRecentlyTimeout.start(); + if (!this.activeRecentlyTimeout.isRunning()) { + this.activeRecentlyTimeout.start(); - this._runTimersUntilTimeout(this._attachedActiveRecentlyTimers, this._activeRecentlyTimeout); + UserActivity.runTimersUntilTimeout(this.attachedActiveRecentlyTimers, this.activeRecentlyTimeout); } else { - this._activeRecentlyTimeout.restart(); + this.activeRecentlyTimeout.restart(); } - } + }; - async _runTimersUntilTimeout(attachedTimers, timeout) { + private static async runTimersUntilTimeout(attachedTimers: Timer[], timeout: Timer) { attachedTimers.forEach((t) => t.start()); try { await timeout.finished(); From dd8cac307cdc19625cb94f0393ed2ccdd77761a1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:38:59 +0100 Subject: [PATCH 30/43] Convert shouldHideEvent to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{shouldHideEvent.js => shouldHideEvent.ts} | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) rename src/{shouldHideEvent.js => shouldHideEvent.ts} (86%) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.ts similarity index 86% rename from src/shouldHideEvent.js rename to src/shouldHideEvent.ts index b1533ae835..2a47b9c417 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.ts @@ -14,10 +14,20 @@ limitations under the License. */ +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; + import SettingsStore from "./settings/SettingsStore"; -function memberEventDiff(ev) { - const diff = { +interface IDiff { + isMemberEvent: boolean; + isJoin?: boolean; + isPart?: boolean; + isDisplaynameChange?: boolean; + isAvatarChange?: boolean; +} + +function memberEventDiff(ev: MatrixEvent): IDiff { + const diff: IDiff = { isMemberEvent: ev.getType() === 'm.room.member', }; @@ -37,7 +47,7 @@ function memberEventDiff(ev) { return diff; } -export default function shouldHideEvent(ev) { +export default function shouldHideEvent(ev: MatrixEvent): boolean { // Wrap getValue() for readability. Calling the SettingsStore can be // fairly resource heavy, so the checks below should avoid hitting it // where possible. From a94c899c0af0adb8181972d84f4e1ad838208b49 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:39:13 +0100 Subject: [PATCH 31/43] Convert Roles to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{Roles.js => Roles.ts} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename src/{Roles.js => Roles.ts} (87%) diff --git a/src/Roles.js b/src/Roles.ts similarity index 87% rename from src/Roles.js rename to src/Roles.ts index 7cc3c880d7..b4be97fdce 100644 --- a/src/Roles.js +++ b/src/Roles.ts @@ -13,9 +13,10 @@ 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 { _t } from './languageHandler'; -export function levelRoleMap(usersDefault) { +export function levelRoleMap(usersDefault: number) { return { undefined: _t('Default'), 0: _t('Restricted'), @@ -25,7 +26,7 @@ export function levelRoleMap(usersDefault) { }; } -export function textualPowerLevel(level, usersDefault) { +export function textualPowerLevel(level: number, usersDefault: number): string { const LEVEL_ROLE_MAP = levelRoleMap(usersDefault); if (LEVEL_ROLE_MAP[level]) { return LEVEL_ROLE_MAP[level]; From 9cfc075d684e3db77829d41fe7c0f87e94a9d022 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:39:29 +0100 Subject: [PATCH 32/43] Convert Presence to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{Presence.js => Presence.ts} | 60 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 29 deletions(-) rename src/{Presence.js => Presence.ts} (65%) diff --git a/src/Presence.js b/src/Presence.ts similarity index 65% rename from src/Presence.js rename to src/Presence.ts index 42bca35f96..660bb0ac94 100644 --- a/src/Presence.js +++ b/src/Presence.ts @@ -19,30 +19,34 @@ limitations under the License. import {MatrixClientPeg} from "./MatrixClientPeg"; import dis from "./dispatcher/dispatcher"; import Timer from './utils/Timer'; +import {ActionPayload} from "./dispatcher/payloads"; - // Time in ms after that a user is considered as unavailable/away +// Time in ms after that a user is considered as unavailable/away const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins -const PRESENCE_STATES = ["online", "offline", "unavailable"]; + +enum State { + Online = "online", + Offline = "offline", + Unavailable = "unavailable", +} class Presence { - constructor() { - this._activitySignal = null; - this._unavailableTimer = null; - this._onAction = this._onAction.bind(this); - this._dispatcherRef = null; - } + private unavailableTimer: Timer = null; + private dispatcherRef: string = null; + private state: State = null; + /** * Start listening the user activity to evaluate his presence state. * Any state change will be sent to the homeserver. */ - async start() { - this._unavailableTimer = new Timer(UNAVAILABLE_TIME_MS); + public async start() { + this.unavailableTimer = new Timer(UNAVAILABLE_TIME_MS); // the user_activity_start action starts the timer - this._dispatcherRef = dis.register(this._onAction); - while (this._unavailableTimer) { + this.dispatcherRef = dis.register(this.onAction); + while (this.unavailableTimer) { try { - await this._unavailableTimer.finished(); - this.setState("unavailable"); + await this.unavailableTimer.finished(); + this.setState(State.Unavailable); } catch (e) { /* aborted, stop got called */ } } } @@ -50,14 +54,14 @@ class Presence { /** * Stop tracking user activity */ - stop() { - if (this._dispatcherRef) { - dis.unregister(this._dispatcherRef); - this._dispatcherRef = null; + public stop() { + if (this.dispatcherRef) { + dis.unregister(this.dispatcherRef); + this.dispatcherRef = null; } - if (this._unavailableTimer) { - this._unavailableTimer.abort(); - this._unavailableTimer = null; + if (this.unavailableTimer) { + this.unavailableTimer.abort(); + this.unavailableTimer = null; } } @@ -65,14 +69,14 @@ class Presence { * Get the current presence state. * @returns {string} the presence state (see PRESENCE enum) */ - getState() { + public getState() { return this.state; } - _onAction(payload) { + private onAction = (payload: ActionPayload) => { if (payload.action === 'user_activity') { - this.setState("online"); - this._unavailableTimer.restart(); + this.setState(State.Online); + this.unavailableTimer.restart(); } } @@ -81,13 +85,11 @@ class Presence { * If the state has changed, the homeserver will be notified. * @param {string} newState the new presence state (see PRESENCE enum) */ - async setState(newState) { + private async setState(newState: State) { if (newState === this.state) { return; } - if (PRESENCE_STATES.indexOf(newState) === -1) { - throw new Error("Bad presence state: " + newState); - } + const oldState = this.state; this.state = newState; From 9396f98f2b41e35e262ac1f0ae05c0f9361cf266 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:39:38 +0100 Subject: [PATCH 33/43] Convert email to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{email.js => email.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/{email.js => email.ts} (92%) diff --git a/src/email.js b/src/email.ts similarity index 92% rename from src/email.js rename to src/email.ts index 6e2ed69bb7..6642a51541 100644 --- a/src/email.js +++ b/src/email.ts @@ -16,6 +16,6 @@ limitations under the License. const EMAIL_ADDRESS_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; -export function looksValid(email) { +export function looksValid(email: string): boolean { return EMAIL_ADDRESS_REGEX.test(email); } From 5a2eda260da298ca8f7fb6edde7e7f5fe909eec2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:39:58 +0100 Subject: [PATCH 34/43] Convert DateUtils to TS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/{DateUtils.js => DateUtils.ts} | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename src/{DateUtils.js => DateUtils.ts} (85%) diff --git a/src/DateUtils.js b/src/DateUtils.ts similarity index 85% rename from src/DateUtils.js rename to src/DateUtils.ts index 108697238c..9b1edf0775 100644 --- a/src/DateUtils.js +++ b/src/DateUtils.ts @@ -17,7 +17,7 @@ limitations under the License. import { _t } from './languageHandler'; -function getDaysArray() { +function getDaysArray(): string[] { return [ _t('Sun'), _t('Mon'), @@ -29,7 +29,7 @@ function getDaysArray() { ]; } -function getMonthsArray() { +function getMonthsArray(): string[] { return [ _t('Jan'), _t('Feb'), @@ -46,11 +46,11 @@ function getMonthsArray() { ]; } -function pad(n) { +function pad(n: number): string { return (n < 10 ? '0' : '') + n; } -function twelveHourTime(date, showSeconds=false) { +function twelveHourTime(date: Date, showSeconds = false): string { let hours = date.getHours() % 12; const minutes = pad(date.getMinutes()); const ampm = date.getHours() >= 12 ? _t('PM') : _t('AM'); @@ -62,7 +62,7 @@ function twelveHourTime(date, showSeconds=false) { return `${hours}:${minutes}${ampm}`; } -export function formatDate(date, showTwelveHour=false) { +export function formatDate(date: Date, showTwelveHour = false): string { const now = new Date(); const days = getDaysArray(); const months = getMonthsArray(); @@ -86,7 +86,7 @@ export function formatDate(date, showTwelveHour=false) { return formatFullDate(date, showTwelveHour); } -export function formatFullDateNoTime(date) { +export function formatFullDateNoTime(date: Date): string { const days = getDaysArray(); const months = getMonthsArray(); return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s', { @@ -97,7 +97,7 @@ export function formatFullDateNoTime(date) { }); } -export function formatFullDate(date, showTwelveHour=false) { +export function formatFullDate(date: Date, showTwelveHour = false): string { const days = getDaysArray(); const months = getMonthsArray(); return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', { @@ -109,14 +109,14 @@ export function formatFullDate(date, showTwelveHour=false) { }); } -export function formatFullTime(date, showTwelveHour=false) { +export function formatFullTime(date: Date, showTwelveHour = false): string { if (showTwelveHour) { return twelveHourTime(date, true); } return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()); } -export function formatTime(date, showTwelveHour=false) { +export function formatTime(date: Date, showTwelveHour = false): string { if (showTwelveHour) { return twelveHourTime(date); } @@ -124,7 +124,7 @@ export function formatTime(date, showTwelveHour=false) { } const MILLIS_IN_DAY = 86400000; -export function wantsDateSeparator(prevEventDate, nextEventDate) { +export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean { if (!nextEventDate || !prevEventDate) { return false; } From cace3fd8aafff34d5a82b5d1e2b352bc1589dd2d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 17:40:34 +0100 Subject: [PATCH 35/43] TS conversion exports Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/@types/global.d.ts | 4 ++++ src/languageHandler.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 93be0fafc0..ed28a5c479 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -32,6 +32,8 @@ import type {Renderer} from "react-dom"; import RightPanelStore from "../stores/RightPanelStore"; import WidgetStore from "../stores/WidgetStore"; import CallHandler from "../CallHandler"; +import {Analytics} from "../Analytics"; +import UserActivity from "../UserActivity"; declare global { interface Window { @@ -56,6 +58,8 @@ declare global { mxRightPanelStore: RightPanelStore; mxWidgetStore: WidgetStore; mxCallHandler: CallHandler; + mxAnalytics: Analytics; + mxUserActivity: UserActivity; } interface Document { diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index e699f8e301..0921b65137 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -96,7 +96,7 @@ function safeCounterpartTranslate(text: string, options?: object) { return translated; } -interface IVariables { +export interface IVariables { count?: number; [key: string]: number | string; } From 20cc3911ed1b776c691fe12e9906aadd3e77fae4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 18:48:02 +0100 Subject: [PATCH 36/43] switch form global to window Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Analytics.tsx | 6 +++--- src/Avatar.ts | 6 +++--- src/UserActivity.ts | 6 +++--- src/components/views/avatars/RoomAvatar.tsx | 3 ++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Analytics.tsx b/src/Analytics.tsx index 79d45cfd1d..280033fcc4 100644 --- a/src/Analytics.tsx +++ b/src/Analytics.tsx @@ -423,7 +423,7 @@ export class Analytics { }; } -if (!global.mxAnalytics) { - global.mxAnalytics = new Analytics(); +if (!window.mxAnalytics) { + window.mxAnalytics = new Analytics(); } -export default global.mxAnalytics; +export default window.mxAnalytics; diff --git a/src/Avatar.ts b/src/Avatar.ts index b742d9002c..60bdfdcf75 100644 --- a/src/Avatar.ts +++ b/src/Avatar.ts @@ -22,7 +22,7 @@ import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClientPeg} from './MatrixClientPeg'; import DMRoomMap from './utils/DMRoomMap'; -type ResizeMethod = "crop" | "scale"; +export type ResizeMethod = "crop" | "scale"; // Not to be used for BaseAvatar urls as that has similar default avatar fallback already export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) { @@ -46,7 +46,7 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu return url; } -export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod: ResizeMethod) { +export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) { const url = getHttpUriForMxc( MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl, Math.floor(width * window.devicePixelRatio), @@ -151,7 +151,7 @@ export function getInitialLetter(name: string): string { return firstChar.toUpperCase(); } -export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod: ResizeMethod) { +export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) { if (!room) return null; // null-guard const explicitRoomAvatar = room.getAvatarUrl( diff --git a/src/UserActivity.ts b/src/UserActivity.ts index bf2a546979..606075ec7c 100644 --- a/src/UserActivity.ts +++ b/src/UserActivity.ts @@ -51,10 +51,10 @@ export default class UserActivity { } static sharedInstance() { - if (global.mxUserActivity === undefined) { - global.mxUserActivity = new UserActivity(window, document); + if (window.mxUserActivity === undefined) { + window.mxUserActivity = new UserActivity(window, document); } - return global.mxUserActivity; + return window.mxUserActivity; } /** diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx index e37dff4bfe..cbdae765f7 100644 --- a/src/components/views/avatars/RoomAvatar.tsx +++ b/src/components/views/avatars/RoomAvatar.tsx @@ -22,6 +22,7 @@ import ImageView from '../elements/ImageView'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import * as Avatar from '../../../Avatar'; +import {ResizeMethod} from "../../../Avatar"; interface IProps { // Room may be left unset here, but if it is, @@ -32,7 +33,7 @@ interface IProps { oobData?: any; width?: number; height?: number; - resizeMethod?: string; + resizeMethod?: ResizeMethod; viewAvatarOnClick?: boolean; } From 40038a6100de92e3629c48577cb1e50386e457bc Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 13 Oct 2020 14:39:27 -0600 Subject: [PATCH 37/43] Run templating on the popout URL too Fixes https://github.com/vector-im/element-web/issues/15443 --- src/stores/widgets/StopGapWidget.ts | 42 +++++++++++++++++------------ 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 1eb4f9cd27..f7d6d1853e 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -74,6 +74,16 @@ class ElementWidget extends Widget { return super.templateUrl; } + public get popoutTemplateUrl(): string { + if (WidgetType.JITSI.matches(this.type)) { + return WidgetUtils.getLocalJitsiWrapperUrl({ + forLocalRender: false, // The only important difference between this and templateUrl() + auth: super.rawData?.auth, + }); + } + return this.templateUrl; // use this instead of super to ensure we get appropriate templating + } + public get rawData(): IWidgetData { let conferenceId = super.rawData['conferenceId']; if (conferenceId === undefined) { @@ -94,8 +104,8 @@ class ElementWidget extends Widget { }; } - public getCompleteUrl(params: ITemplateParams): string { - return runTemplate(this.templateUrl, { + public getCompleteUrl(params: ITemplateParams, asPopout=false): string { + return runTemplate(asPopout ? this.popoutTemplateUrl : 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. @@ -109,7 +119,7 @@ class ElementWidget extends Widget { export class StopGapWidget extends EventEmitter { private messaging: ClientWidgetApi; - private mockWidget: Widget; + private mockWidget: ElementWidget; private scalarToken: string; constructor(private appTileProps: IAppTileProps) { @@ -133,12 +143,23 @@ export class StopGapWidget extends EventEmitter { * The URL to use in the iframe */ public get embedUrl(): string { + return this.runUrlTemplate({asPopout: false}); + } + + /** + * The URL to use in the popout + */ + public get popoutUrl(): string { + return this.runUrlTemplate({asPopout: true}); + } + + private runUrlTemplate(opts: {asPopout: boolean} = {asPopout: false}): string { const templated = this.mockWidget.getCompleteUrl({ currentRoomId: RoomViewStore.getRoomId(), currentUserId: MatrixClientPeg.get().getUserId(), userDisplayName: OwnProfileStore.instance.displayName, userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), - }); + }, opts?.asPopout); // Add in some legacy support sprinkles // TODO: Replace these with proper widget params @@ -158,19 +179,6 @@ export class StopGapWidget extends EventEmitter { return parsed.toString().replace(/%24/g, '$'); } - /** - * The URL to use in the popout - */ - public get popoutUrl(): string { - if (WidgetType.JITSI.matches(this.mockWidget.type)) { - return WidgetUtils.getLocalJitsiWrapperUrl({ - forLocalRender: false, - auth: this.mockWidget.rawData?.auth, - }); - } - return this.embedUrl; - } - public get isManagedByManager(): boolean { return !!this.scalarToken; } From 294c35347c89fe2e938258334c1de483b53b0832 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 13 Oct 2020 14:55:44 -0600 Subject: [PATCH 38/43] Ensure widgets are destroyed cleanly when minimized Fixes https://github.com/vector-im/element-web/issues/15444 (an artifact of joining a call then minimizing the widget) Also fixes other issues relating to widgets not loading when being minimized/maximized. --- src/components/views/elements/AppTile.js | 9 ++++++++- src/components/views/elements/PersistentApp.js | 5 +++++ src/stores/widgets/StopGapWidget.ts | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index fda2652d12..c732d8ec95 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -240,10 +240,14 @@ export default class AppTile extends React.Component { this.iframe.src = 'about:blank'; } + if (WidgetType.JITSI.matches(this.props.app.type)) { + dis.dispatch({action: 'hangup_conference'}); + } + // Delete the widget from the persisted store for good measure. PersistedElement.destroyElement(this._persistKey); - this._sgWidget.stop(); + this._sgWidget.stop({forceDestroy: true}); } /* If user has permission to modify widgets, delete the widget, @@ -387,6 +391,9 @@ export default class AppTile extends React.Component { if (this.props.show) { // if we were being shown, end the widget as we're about to be minimized. this._endWidgetActions(); + } else { + // restart the widget actions + this._resetWidget(this.props); } dis.dispatch({ action: 'appsDrawer', diff --git a/src/components/views/elements/PersistentApp.js b/src/components/views/elements/PersistentApp.js index a3e413151a..8b2aa5c87c 100644 --- a/src/components/views/elements/PersistentApp.js +++ b/src/components/views/elements/PersistentApp.js @@ -58,6 +58,11 @@ export default class PersistentApp extends React.Component { const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); if (this.state.roomId !== persistentWidgetInRoomId) { const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId); + + // Sanity check the room - the widget may have been destroyed between render cycles, and + // thus no room is associated anymore. + if (!persistentWidgetInRoom) return null; + // get the widget data const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => { return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId(); diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index f7d6d1853e..998dd2afb3 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -283,8 +283,8 @@ export class StopGapWidget extends EventEmitter { } } - public stop() { - if (ActiveWidgetStore.getPersistentWidgetId() === this.mockWidget.id) { + public stop(opts = {forceDestroy: false}) { + if (!opts?.forceDestroy && ActiveWidgetStore.getPersistentWidgetId() === this.mockWidget.id) { console.log("Skipping destroy - persistent widget"); return; } From f9ac390842a90f7a0062a623b8c32858d8e294e1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 13 Oct 2020 14:56:27 -0600 Subject: [PATCH 39/43] Appease the linter --- src/stores/widgets/StopGapWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 998dd2afb3..41b040b8c6 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -153,7 +153,7 @@ export class StopGapWidget extends EventEmitter { return this.runUrlTemplate({asPopout: true}); } - private runUrlTemplate(opts: {asPopout: boolean} = {asPopout: false}): string { + private runUrlTemplate(opts = {asPopout: false}): string { const templated = this.mockWidget.getCompleteUrl({ currentRoomId: RoomViewStore.getRoomId(), currentUserId: MatrixClientPeg.get().getUserId(), From e885d9cb0d1137abcebb52fcd3b518ee36c96489 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 13 Oct 2020 22:58:21 +0100 Subject: [PATCH 40/43] TS improvements Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/Analytics.tsx | 6 +++--- src/Notifier.ts | 4 ++-- src/components/views/right_panel/HeaderButton.tsx | 2 +- src/components/views/rooms/RoomBreadcrumbs.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Analytics.tsx b/src/Analytics.tsx index 280033fcc4..212bfd3757 100644 --- a/src/Analytics.tsx +++ b/src/Analytics.tsx @@ -344,7 +344,7 @@ export class Analytics { this.visitVariables[customVariables[key].id] = [key, value]; } - setLoggedIn(isGuest, homeserverUrl, identityServerUrl) { + public setLoggedIn(isGuest: boolean, homeserverUrl: string) { if (this.disabled) return; const config = SdkConfig.get(); @@ -356,12 +356,12 @@ export class Analytics { this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl)); } - setBreadcrumbs(state) { + public setBreadcrumbs(state: boolean) { if (this.disabled) return; this.setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled'); } - showDetailsModal = () => { + public showDetailsModal = () => { let rows = []; if (!this.disabled) { rows = Object.values(this.visitVariables); diff --git a/src/Notifier.ts b/src/Notifier.ts index 2643de1abc..1899896f9b 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -218,7 +218,7 @@ export const Notifier = { // calculated value. It is determined based upon whether or not the master rule is enabled // and other flags. Setting it here would cause a circular reference. - Analytics.trackEvent('Notifier', 'Set Enabled', enable); + Analytics.trackEvent('Notifier', 'Set Enabled', String(enable)); // make sure that we persist the current setting audio_enabled setting // before changing anything @@ -287,7 +287,7 @@ export const Notifier = { setPromptHidden: function(hidden: boolean, persistent = true) { this.toolbarHidden = hidden; - Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden); + Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', String(hidden)); hideNotificationsToast(); diff --git a/src/components/views/right_panel/HeaderButton.tsx b/src/components/views/right_panel/HeaderButton.tsx index ff092ca060..7f682e2d89 100644 --- a/src/components/views/right_panel/HeaderButton.tsx +++ b/src/components/views/right_panel/HeaderButton.tsx @@ -31,7 +31,7 @@ interface IProps { // The badge to display above the icon badge?: React.ReactNode; // The parameters to track the click event - analytics: string[]; + analytics: Parameters; // Button name name: string; diff --git a/src/components/views/rooms/RoomBreadcrumbs.tsx b/src/components/views/rooms/RoomBreadcrumbs.tsx index 92e911067c..7725ce456e 100644 --- a/src/components/views/rooms/RoomBreadcrumbs.tsx +++ b/src/components/views/rooms/RoomBreadcrumbs.tsx @@ -76,7 +76,7 @@ export default class RoomBreadcrumbs extends React.PureComponent }; private viewRoom = (room: Room, index: number) => { - Analytics.trackEvent("Breadcrumbs", "click_node", index); + Analytics.trackEvent("Breadcrumbs", "click_node", String(index)); defaultDispatcher.dispatch({action: "view_room", room_id: room.roomId}); }; From 910bdf0802190fd37a255fea818389175adef3e6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 14 Oct 2020 09:38:45 +0100 Subject: [PATCH 41/43] Fix broken rendering of Room Create when showHiddenEvents enabled Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/messages/RoomCreate.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.js index 22d5ebba1e..268acc935c 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.js @@ -22,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import ViewSourceEvent from "./ViewSourceEvent"; export default class RoomCreate extends React.Component { static propTypes = { @@ -45,7 +46,8 @@ export default class RoomCreate extends React.Component { render() { const predecessor = this.props.mxEvent.getContent()['predecessor']; if (predecessor === undefined) { - return
; // We should never have been instaniated in this case + // We would never have been instantiated in this case except if user has showHiddenEventsInTimeline enabled + return ; } const prevRoom = MatrixClientPeg.get().getRoom(predecessor['room_id']); const permalinkCreator = new RoomPermalinkCreator(prevRoom, predecessor['room_id']); From 38bf7b3055ace8e4a7ddc01e61c2b587125c22d7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 14 Oct 2020 09:46:54 +0100 Subject: [PATCH 42/43] Apply the fix more generically Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/messages/RoomCreate.js | 4 +--- src/components/views/rooms/EventTile.js | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.js index 268acc935c..6098b1217e 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.js @@ -22,7 +22,6 @@ import dis from '../../../dispatcher/dispatcher'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import ViewSourceEvent from "./ViewSourceEvent"; export default class RoomCreate extends React.Component { static propTypes = { @@ -46,8 +45,7 @@ export default class RoomCreate extends React.Component { render() { const predecessor = this.props.mxEvent.getContent()['predecessor']; if (predecessor === undefined) { - // We would never have been instantiated in this case except if user has showHiddenEventsInTimeline enabled - return ; + return
; // We should never have been instantiated in this case } const prevRoom = MatrixClientPeg.get().getRoom(predecessor['room_id']); const permalinkCreator = new RoomPermalinkCreator(prevRoom, predecessor['room_id']); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index c2b1af2ddc..01646ad567 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -657,8 +657,7 @@ export default class EventTile extends React.Component { // source tile when there's no regular tile for an event and also for // replace relations (which otherwise would display as a confusing // duplicate of the thing they are replacing). - const useSource = !tileHandler || this.props.mxEvent.isRelation("m.replace"); - if (useSource && SettingsStore.getValue("showHiddenEventsInTimeline")) { + if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) { tileHandler = "messages.ViewSourceEvent"; // Reuse info message avatar and sender profile styling isInfoMessage = true; From b7a1e698df2b0f9d53ff27def5d99637a34f7805 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 14 Oct 2020 10:47:46 +0100 Subject: [PATCH 43/43] fix tests which use a private method Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- test/UserActivity-test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/UserActivity-test.js b/test/UserActivity-test.js index 1b0fbafb48..51fb720bce 100644 --- a/test/UserActivity-test.js +++ b/test/UserActivity-test.js @@ -64,7 +64,7 @@ describe('UserActivity', function() { it('should not consider user active after activity if no window focus', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(false); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); expect(userActivity.userActiveNow()).toBe(false); expect(userActivity.userActiveRecently()).toBe(false); }); @@ -72,7 +72,7 @@ describe('UserActivity', function() { it('should consider user active shortly after activity', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); expect(userActivity.userActiveNow()).toBe(true); expect(userActivity.userActiveRecently()).toBe(true); clock.tick(200); @@ -83,7 +83,7 @@ describe('UserActivity', function() { it('should consider user not active after 10s of no activity', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(10000); expect(userActivity.userActiveNow()).toBe(false); }); @@ -91,7 +91,7 @@ describe('UserActivity', function() { it('should consider user passive after 10s of no activity', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(10000); expect(userActivity.userActiveRecently()).toBe(true); }); @@ -99,7 +99,7 @@ describe('UserActivity', function() { it('should not consider user passive after 10s if window un-focused', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(10000); fakeDocument.hasFocus = jest.fn().mockReturnValue(false); @@ -111,7 +111,7 @@ describe('UserActivity', function() { it('should not consider user passive after 3 mins', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(3 * 60 * 1000); expect(userActivity.userActiveRecently()).toBe(false); @@ -120,11 +120,11 @@ describe('UserActivity', function() { it('should extend timer on activity', function() { fakeDocument.hasFocus = jest.fn().mockReturnValue(true); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(1 * 60 * 1000); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(1 * 60 * 1000); - userActivity._onUserActivity({}); + userActivity.onUserActivity({}); clock.tick(1 * 60 * 1000); expect(userActivity.userActiveRecently()).toBe(true);