diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a7ddc407..4a22954c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +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) + + * 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) diff --git a/package.json b/package.json index bd332def37..3f073ce59c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.5.0", + "version": "3.6.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { 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..6b66a614d2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -77,13 +77,28 @@ 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, CallEvent, CallParty } 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', +} + +// 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>(); + private calls = new Map(); + private audioPromises = new Map>(); static sharedInstance() { if (!window.mxCallHandler) { @@ -108,20 +123,20 @@ export default class CallHandler { } } - getCallForRoom(roomId: string): Call { + getCallForRoom(roomId: string): MatrixCall { return this.calls.get(roomId) || null; } getAnyActiveCall() { for (const call of this.calls.values()) { - if (call.state !== "ended") { + if (call.state !== CallState.Ended) { return call; } } 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 +165,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,8 +179,8 @@ export default class CallHandler { } } - private setCallListeners(call: Call) { - call.on("error", (err) => { + private setCallListeners(call: MatrixCall) { + call.on(CallEvent.Error, (err) => { console.error("Call error:", err); if ( MatrixClientPeg.get().getTurnServers().length === 0 && @@ -180,74 +195,60 @@ export default class CallHandler { description: err.message, }); }); - call.on("hangup", () => { + call.on(CallEvent.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, 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(CallEvent.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 === CallParty.Remote || + (call.hangupParty === CallParty.Local && call.hangupReason === CallErrorCode.InviteTimeout) )) { - 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 +280,39 @@ 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: PlaceCallType, + localElement: HTMLVideoElement, remoteElement: HTMLVideoElement, + ) { + const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), roomId); + this.calls.set(roomId, call); + this.setCallListeners(call); + if (type === PlaceCallType.Voice) { + call.placeVoiceCall(); + } else if (type === 'video') { + call.placeVideoCall( + remoteElement, + localElement, + ); + } else if (type === PlaceCallType.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 +347,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 +387,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/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..4d277692df 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, +): (keyParams: { 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 diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index cdaa0bb7f9..e390be6979 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. @@ -26,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; @@ -46,10 +45,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 +122,12 @@ export default class RoomStatusBar extends React.Component { }); }; + _showCallBar() { + return (this.props.callState && + (this.props.callState !== CallState.Ended && this.props.callState !== CallState.Ringing) + ); + } + _onResendAllClick = () => { Resend.resendUnsentEvents(this.props.room); dis.fire(Action.FocusComposer); @@ -153,7 +160,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 +172,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 +276,25 @@ export default class RoomStatusBar extends React.Component { ; } + _getCallStatusText() { + switch (this.props.callState) { + case CallState.CreateOffer: + case CallState.InviteSent: + return _t('Calling...'); + case CallState.Connecting: + case CallState.CreateAnswer: + return _t('Call connecting...'); + case CallState.Connected: + return _t('Active call'); + case CallState.WaitLocalMedia: + if (this.props.callType === 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 +317,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 0f917f383b..afa11f9db9 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, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call"; import WidgetStore from "../../stores/WidgetStore"; import {UPDATE_EVENT} from "../../stores/AsyncStore"; @@ -143,7 +144,7 @@ export interface IState { }>; searchHighlights?: string[]; searchInProgress?: boolean; - callState?: string; + callState?: CallState; guestsCanJoin: boolean; canPeek: boolean; showApps: boolean; @@ -496,7 +497,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, }); @@ -730,14 +731,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; } @@ -1631,7 +1627,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; } @@ -1768,10 +1764,13 @@ 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; + { + // 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; + } } const scrollheaderClasses = classNames({ @@ -1790,7 +1789,8 @@ export default class RoomView extends React.Component { statusBar = { }; } - if (inCall) { + if (activeCall) { let zoomButton; let videoMuteButton; - if (call.type === "video") { + if (activeCall.type === CallType.Video) { zoomButton = (
{ videoMuteButton =
@@ -1945,10 +1946,10 @@ export default class RoomView extends React.Component { const voiceMuteButton =
@@ -2066,7 +2067,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/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index a145770e3f..cac746f203 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, }); }; 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 aa2e14a3b4..bdf695d28f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2088,6 +2088,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?", 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 { diff --git a/yarn.lock b/yarn.lock index d095f23532..1f06fd6130 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6501,8 +6501,8 @@ mathml-tag-names@^2.0.1: 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/a727da9193e0ccb2fa8d7c3e8e321916f6717190" + version "8.5.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d8c4101fdd521e189f4755c6f02a8971b991ef5f" dependencies: "@babel/runtime" "^7.11.2" another-json "^0.2.0"