Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/feat/widgets

 Conflicts:
	src/components/structures/RoomView.tsx
This commit is contained in:
Michael Telatynski 2020-10-13 13:45:22 +01:00
commit a1d25efceb
16 changed files with 352 additions and 176 deletions

View file

@ -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) 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) [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.5.0-rc.1...v3.5.0)

View file

@ -1,6 +1,6 @@
{ {
"name": "matrix-react-sdk", "name": "matrix-react-sdk",
"version": "3.5.0", "version": "3.6.0",
"description": "SDK for matrix.org using React", "description": "SDK for matrix.org using React",
"author": "matrix.org", "author": "matrix.org",
"repository": { "repository": {

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. 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 * as ModernizrStatic from "modernizr";
import ContentMessages from "../ContentMessages"; import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg"; import { IMatrixClientPeg } from "../MatrixClientPeg";

View file

@ -77,13 +77,28 @@ import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import WidgetStore from "./stores/WidgetStore"; import WidgetStore from "./stores/WidgetStore";
import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "./stores/widgets/WidgetMessagingStore";
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; 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 enum AudioID {
type Call = any; 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 { export default class CallHandler {
private calls = new Map<string, Call>(); private calls = new Map<string, MatrixCall>();
private audioPromises = new Map<string, Promise<void>>(); private audioPromises = new Map<AudioID, Promise<void>>();
static sharedInstance() { static sharedInstance() {
if (!window.mxCallHandler) { 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; return this.calls.get(roomId) || null;
} }
getAnyActiveCall() { getAnyActiveCall() {
for (const call of this.calls.values()) { for (const call of this.calls.values()) {
if (call.state !== "ended") { if (call.state !== CallState.Ended) {
return call; return call;
} }
} }
return null; return null;
} }
play(audioId: string) { play(audioId: AudioID) {
// TODO: Attach an invisible element for this instead // TODO: Attach an invisible element for this instead
// which listens? // which listens?
const audio = document.getElementById(audioId) as HTMLMediaElement; 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 // TODO: Attach an invisible element for this instead
// which listens? // which listens?
const audio = document.getElementById(audioId) as HTMLMediaElement; const audio = document.getElementById(audioId) as HTMLMediaElement;
@ -164,8 +179,8 @@ export default class CallHandler {
} }
} }
private setCallListeners(call: Call) { private setCallListeners(call: MatrixCall) {
call.on("error", (err) => { call.on(CallEvent.Error, (err) => {
console.error("Call error:", err); console.error("Call error:", err);
if ( if (
MatrixClientPeg.get().getTurnServers().length === 0 && MatrixClientPeg.get().getTurnServers().length === 0 &&
@ -180,74 +195,60 @@ export default class CallHandler {
description: err.message, description: err.message,
}); });
}); });
call.on("hangup", () => { call.on(CallEvent.Hangup, () => {
this.removeCallForRoom(call.roomId); this.removeCallForRoom(call.roomId);
}); });
// map web rtc states to dummy UI state call.on(CallEvent.State, (newState: CallState, oldState: CallState) => {
// ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing this.setCallState(call, newState);
call.on("state", (newState, oldState) => {
if (newState === "ringing") { switch (oldState) {
this.setCallState(call, call.roomId, "ringing"); case CallState.Ringing:
this.pause("ringbackAudio"); this.pause(AudioID.Ring);
} else if (newState === "invite_sent") { break;
this.setCallState(call, call.roomId, "ringback"); case CallState.InviteSent:
this.play("ringbackAudio"); this.pause(AudioID.Ringback);
} else if (newState === "ended" && oldState === "connected") { break;
this.removeCallForRoom(call.roomId); }
this.pause("ringbackAudio");
this.play("callendAudio"); switch (newState) {
} else if (newState === "ended" && oldState === "invite_sent" && case CallState.Ringing:
(call.hangupParty === "remote" || this.play(AudioID.Ring);
(call.hangupParty === "local" && call.hangupReason === "invite_timeout") 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.play(AudioID.Busy);
this.pause("ringbackAudio"); Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
this.play("busyAudio"); title: _t('Call Timeout'),
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, { description: _t('The remote side failed to pick up') + '.',
title: _t('Call Timeout'), });
description: _t('The remote side failed to pick up') + '.', } else {
}); this.play(AudioID.CallEnd);
} 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");
} }
}); });
} }
private setCallState(call: Call, roomId: string, status: string) { private setCallState(call: MatrixCall, status: CallState) {
console.log( 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({ dis.dispatch({
action: 'call_state', action: 'call_state',
room_id: roomId, room_id: call.roomId,
state: status, state: status,
}); });
} }
private removeCallForRoom(roomId: string) { private removeCallForRoom(roomId: string) {
this.setCallState(null, roomId, null); this.calls.delete(roomId);
} }
private showICEFallbackPrompt() { private showICEFallbackPrompt() {
@ -279,36 +280,39 @@ export default class CallHandler {
}, null, true); }, 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) { switch (payload.action) {
case 'place_call': case 'place_call':
{ {
@ -343,8 +347,8 @@ export default class CallHandler {
return; return;
} else if (members.length === 2) { } else if (members.length === 2) {
console.info("Place %s call in %s", payload.type, payload.room_id); 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 } else { // > 2
dis.dispatch({ dis.dispatch({
action: "place_conference_call", action: "place_conference_call",
@ -383,24 +387,23 @@ export default class CallHandler {
return; return;
} }
const call = payload.call; const call = payload.call as MatrixCall;
this.calls.set(call.roomId, call)
this.setCallListeners(call); this.setCallListeners(call);
this.setCallState(call, call.roomId, "ringing");
} }
break; break;
case 'hangup': case 'hangup':
if (!this.calls.get(payload.room_id)) { if (!this.calls.get(payload.room_id)) {
return; // no call to hangup 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); this.removeCallForRoom(payload.room_id);
break; break;
case 'answer': case 'answer':
if (!this.calls.get(payload.room_id)) { if (!this.calls.has(payload.room_id)) {
return; // no call to answer return; // no call to answer
} }
this.calls.get(payload.room_id).answer(); this.calls.get(payload.room_id).answer();
this.setCallState(this.calls.get(payload.room_id), payload.room_id, "connected");
dis.dispatch({ dis.dispatch({
action: "view_room", action: "view_room",
room_id: payload.room_id, room_id: payload.room_id,

View file

@ -17,6 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix';
import {MatrixClient} from 'matrix-js-sdk/src/client'; import {MatrixClient} from 'matrix-js-sdk/src/client';
import {MemoryStore} from 'matrix-js-sdk/src/store/memory'; import {MemoryStore} from 'matrix-js-sdk/src/store/memory';
import * as utils from 'matrix-js-sdk/src/utils'; import * as utils from 'matrix-js-sdk/src/utils';
@ -249,8 +250,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
} }
private createClient(creds: IMatrixClientCreds): void { private createClient(creds: IMatrixClientCreds): void {
// TODO: Make these opts typesafe with the js-sdk const opts: ICreateClientOpts = {
const opts = {
baseUrl: creds.homeserverUrl, baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl, idBaseUrl: creds.identityServerUrl,
accessToken: creds.accessToken, accessToken: creds.accessToken,

View file

@ -132,7 +132,7 @@ export class ModalManager {
public createTrackedDialogAsync<T extends any[]>( public createTrackedDialogAsync<T extends any[]>(
analyticsAction: string, analyticsAction: string,
analyticsInfo: string, analyticsInfo: string,
...rest: Parameters<ModalManager["appendDialogAsync"]> ...rest: Parameters<ModalManager["createDialogAsync"]>
) { ) {
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo); Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
return this.createDialogAsync<T>(...rest); return this.createDialogAsync<T>(...rest);

View file

@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. 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 Modal from './Modal';
import * as sdk from './index'; import * as sdk from './index';
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
@ -31,15 +33,18 @@ import SettingsStore from "./settings/SettingsStore";
// during the same single operation. Use `accessSecretStorage` below to scope a // during the same single operation. Use `accessSecretStorage` below to scope a
// single secret storage operation, as it will clear the cached keys once the // single secret storage operation, as it will clear the cached keys once the
// operation ends. // operation ends.
let secretStorageKeys = {}; let secretStorageKeys: Record<string, Uint8Array> = {};
let secretStorageKeyInfo = {}; let secretStorageKeyInfo: Record<string, ISecretStorageKeyInfo> = {};
let secretStorageBeingAccessed = false; let secretStorageBeingAccessed = false;
let nonInteractive = false; let nonInteractive = false;
let dehydrationCache = {}; let dehydrationCache: {
key?: Uint8Array,
keyInfo?: ISecretStorageKeyInfo,
} = {};
function isCachingAllowed() { function isCachingAllowed(): boolean {
return secretStorageBeingAccessed; return secretStorageBeingAccessed;
} }
@ -50,7 +55,7 @@ function isCachingAllowed() {
* *
* @returns {bool} * @returns {bool}
*/ */
export function isSecretStorageBeingAccessed() { export function isSecretStorageBeingAccessed(): boolean {
return secretStorageBeingAccessed; return secretStorageBeingAccessed;
} }
@ -60,7 +65,7 @@ export class AccessCancelledError extends Error {
} }
} }
async function confirmToDismiss() { async function confirmToDismiss(): Promise<boolean> {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const [sure] = await Modal.createDialog(QuestionDialog, { const [sure] = await Modal.createDialog(QuestionDialog, {
title: _t("Cancel entering passphrase?"), title: _t("Cancel entering passphrase?"),
@ -72,7 +77,9 @@ async function confirmToDismiss() {
return !sure; return !sure;
} }
function makeInputToKey(keyInfo) { function makeInputToKey(
keyInfo: ISecretStorageKeyInfo,
): (keyParams: { passphrase: string, recoveryKey: string }) => Promise<Uint8Array> {
return async ({ passphrase, recoveryKey }) => { return async ({ passphrase, recoveryKey }) => {
if (passphrase) { if (passphrase) {
return deriveKey( return deriveKey(
@ -86,7 +93,10 @@ function makeInputToKey(keyInfo) {
}; };
} }
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) { async function getSecretStorageKey(
{ keys: keyInfos }: { keys: Record<string, ISecretStorageKeyInfo> },
ssssItemName,
): Promise<[string, Uint8Array]> {
const keyInfoEntries = Object.entries(keyInfos); const keyInfoEntries = Object.entries(keyInfos);
if (keyInfoEntries.length > 1) { if (keyInfoEntries.length > 1) {
throw new Error("Multiple storage key requests not implemented"); throw new Error("Multiple storage key requests not implemented");
@ -100,7 +110,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
if (dehydrationCache.key) { if (dehydrationCache.key) {
if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) { if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
cacheSecretStorageKey(keyId, dehydrationCache.key, keyInfo); cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
return [keyId, dehydrationCache.key]; return [keyId, dehydrationCache.key];
} }
} }
@ -139,12 +149,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
const key = await inputToKey(input); const key = await inputToKey(input);
// Save to cache to avoid future prompts in the current session // Save to cache to avoid future prompts in the current session
cacheSecretStorageKey(keyId, key, keyInfo); cacheSecretStorageKey(keyId, keyInfo, key);
return [keyId, key]; return [keyId, key];
} }
export async function getDehydrationKey(keyInfo, checkFunc) { export async function getDehydrationKey(
keyInfo: ISecretStorageKeyInfo,
checkFunc: (Uint8Array) => void,
): Promise<Uint8Array> {
const inputToKey = makeInputToKey(keyInfo); const inputToKey = makeInputToKey(keyInfo);
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "", const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
AccessSecretStorageDialog, AccessSecretStorageDialog,
@ -185,20 +198,24 @@ export async function getDehydrationKey(keyInfo, checkFunc) {
return key; return key;
} }
function cacheSecretStorageKey(keyId, key, keyInfo) { function cacheSecretStorageKey(
keyId: string,
keyInfo: ISecretStorageKeyInfo,
key: Uint8Array,
): void {
if (isCachingAllowed()) { if (isCachingAllowed()) {
secretStorageKeys[keyId] = key; secretStorageKeys[keyId] = key;
secretStorageKeyInfo[keyId] = keyInfo; secretStorageKeyInfo[keyId] = keyInfo;
} }
} }
const onSecretRequested = async function({ async function onSecretRequested(
user_id: userId, userId: string,
device_id: deviceId, deviceId: string,
request_id: requestId, requestId: string,
name, name: string,
device_trust: deviceTrust, deviceTrust: IDeviceTrustLevel,
}) { ): Promise<string> {
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust); console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (userId !== client.getUserId()) { if (userId !== client.getUserId()) {
@ -233,16 +250,16 @@ const onSecretRequested = async function({
return key && encodeBase64(key); return key && encodeBase64(key);
} }
console.warn("onSecretRequested didn't recognise the secret named ", name); console.warn("onSecretRequested didn't recognise the secret named ", name);
}; }
export const crossSigningCallbacks = { export const crossSigningCallbacks: ICryptoCallbacks = {
getSecretStorageKey, getSecretStorageKey,
cacheSecretStorageKey, cacheSecretStorageKey,
onSecretRequested, onSecretRequested,
getDehydrationKey, getDehydrationKey,
}; };
export async function promptForBackupPassphrase() { export async function promptForBackupPassphrase(): Promise<Uint8Array> {
let key; let key;
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, { const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
@ -292,7 +309,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
/* priority = */ false, /* priority = */ false,
/* static = */ true, /* static = */ true,
/* options = */ { /* options = */ {
onBeforeClose(reason) { onBeforeClose: async (reason) => {
// If Secure Backup is required, you cannot leave the modal. // If Secure Backup is required, you cannot leave the modal.
if (reason === "backgroundClick") { if (reason === "backgroundClick") {
return !isSecureBackupRequired(); return !isSecureBackupRequired();
@ -329,10 +346,10 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
const keyId = Object.keys(secretStorageKeys)[0]; const keyId = Object.keys(secretStorageKeys)[0];
if (keyId && SettingsStore.getValue("feature_dehydration")) { if (keyId && SettingsStore.getValue("feature_dehydration")) {
const dehydrationKeyInfo = let dehydrationKeyInfo = {};
secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase if (secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase) {
? {passphrase: secretStorageKeyInfo[keyId].passphrase} dehydrationKeyInfo = { passphrase: secretStorageKeyInfo[keyId].passphrase };
: {}; }
console.log("Setting dehydration key"); console.log("Setting dehydration key");
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device"); await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
} else { } else {
@ -354,7 +371,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
} }
// FIXME: this function name is a bit of a mouthful // FIXME: this function name is a bit of a mouthful
export async function tryToUnlockSecretStorageWithDehydrationKey(client) { export async function tryToUnlockSecretStorageWithDehydrationKey(
client: MatrixClient,
): Promise<void> {
const key = dehydrationCache.key; const key = dehydrationCache.key;
let restoringBackup = false; let restoringBackup = false;
if (key && await client.isSecretStorageReady()) { 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 // we also need to set a new dehydrated device to replace the
// device we rehydrated // device we rehydrated
const dehydrationKeyInfo = let dehydrationKeyInfo = {};
dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase if (dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase) {
? {passphrase: dehydrationCache.keyInfo.passphrase} dehydrationKeyInfo = { passphrase: dehydrationCache.keyInfo.passphrase };
: {}; }
await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device"); await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device");
// and restore from backup // and restore from backup

View file

@ -1,7 +1,5 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015-2020 The Matrix.org Foundation C.I.C.
Copyright 2017, 2018 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 dis from '../../dispatcher/dispatcher';
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
import {Action} from "../../dispatcher/actions"; import {Action} from "../../dispatcher/actions";
import { CallState, CallType } from 'matrix-js-sdk/lib/webrtc/call';
const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1; 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 // Used to suggest to the user to invite someone
sentMessageAndIsAlone: PropTypes.bool, sentMessageAndIsAlone: PropTypes.bool,
// true if there is an active call in this room (means we show // The active call in the room, if any (means we show the call bar
// the 'Active Call' text in the status bar if there is nothing // along with the status of the call)
// more interesting) callState: PropTypes.string,
hasActiveCall: PropTypes.bool,
// 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 // 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. // 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 = () => { _onResendAllClick = () => {
Resend.resendUnsentEvents(this.props.room); Resend.resendUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
@ -153,7 +160,7 @@ export default class RoomStatusBar extends React.Component {
// indicate other sizes. // indicate other sizes.
_getSize() { _getSize() {
if (this._shouldShowConnectionError() || if (this._shouldShowConnectionError() ||
this.props.hasActiveCall || this._showCallBar() ||
this.props.sentMessageAndIsAlone this.props.sentMessageAndIsAlone
) { ) {
return STATUS_BAR_EXPANDED; 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. // return suitable content for the image on the left of the status bar.
_getIndicator() { _getIndicator() {
if (this.props.hasActiveCall) { if (this._showCallBar()) {
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
return ( return (
<TintableSvg src={require("../../../res/img/element-icons/room/in-call.svg")} width="23" height="20" /> <TintableSvg src={require("../../../res/img/element-icons/room/in-call.svg")} width="23" height="20" />
@ -269,6 +276,25 @@ export default class RoomStatusBar extends React.Component {
</div>; </div>;
} }
_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. // return suitable content for the main (text) part of the status bar.
_getContent() { _getContent() {
if (this._shouldShowConnectionError()) { if (this._shouldShowConnectionError()) {
@ -291,10 +317,10 @@ export default class RoomStatusBar extends React.Component {
return this._getUnsentMessageContent(); return this._getUnsentMessageContent();
} }
if (this.props.hasActiveCall) { if (this._showCallBar()) {
return ( return (
<div className="mx_RoomStatusBar_callBar"> <div className="mx_RoomStatusBar_callBar">
<b>{ _t('Active call') }</b> <b>{ this._getCallStatusText() }</b>
</div> </div>
); );
} }

View file

@ -71,6 +71,7 @@ import RoomHeader from "../views/rooms/RoomHeader";
import TintableSvg from "../views/elements/TintableSvg"; import TintableSvg from "../views/elements/TintableSvg";
import {XOR} from "../../@types/common"; import {XOR} from "../../@types/common";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/lib/webrtc/call";
import WidgetStore from "../../stores/WidgetStore"; import WidgetStore from "../../stores/WidgetStore";
import {UPDATE_EVENT} from "../../stores/AsyncStore"; import {UPDATE_EVENT} from "../../stores/AsyncStore";
@ -143,7 +144,7 @@ export interface IState {
}>; }>;
searchHighlights?: string[]; searchHighlights?: string[];
searchInProgress?: boolean; searchInProgress?: boolean;
callState?: string; callState?: CallState;
guestsCanJoin: boolean; guestsCanJoin: boolean;
canPeek: boolean; canPeek: boolean;
showApps: boolean; showApps: boolean;
@ -496,7 +497,7 @@ export default class RoomView extends React.Component<IProps, IState> {
componentDidMount() { componentDidMount() {
const call = this.getCallForRoom(); const call = this.getCallForRoom();
const callState = call ? call.call_state : "ended"; const callState = call ? call.state : null;
this.setState({ this.setState({
callState: callState, callState: callState,
}); });
@ -730,14 +731,9 @@ export default class RoomView extends React.Component<IProps, IState> {
} }
const call = this.getCallForRoom(); const call = this.getCallForRoom();
let callState = "ended";
if (call) {
callState = call.call_state;
}
this.setState({ this.setState({
callState: callState, callState: call ? call.state : null,
}); });
break; break;
} }
@ -1631,7 +1627,7 @@ export default class RoomView extends React.Component<IProps, IState> {
/** /**
* get any current call for this room * get any current call for this room
*/ */
private getCallForRoom() { private getCallForRoom(): MatrixCall {
if (!this.state.room) { if (!this.state.room) {
return null; return null;
} }
@ -1768,10 +1764,13 @@ export default class RoomView extends React.Component<IProps, IState> {
// We have successfully loaded this room, and are not previewing. // We have successfully loaded this room, and are not previewing.
// Display the "normal" room view. // Display the "normal" room view.
const call = this.getCallForRoom(); let activeCall = null;
let inCall = false; {
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) { // New block because this variable doesn't need to hang around for the rest of the function
inCall = true; const call = this.getCallForRoom();
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
activeCall = call;
}
} }
const scrollheaderClasses = classNames({ const scrollheaderClasses = classNames({
@ -1790,7 +1789,8 @@ export default class RoomView extends React.Component<IProps, IState> {
statusBar = <RoomStatusBar statusBar = <RoomStatusBar
room={this.state.room} room={this.state.room}
sentMessageAndIsAlone={this.state.isAlone} sentMessageAndIsAlone={this.state.isAlone}
hasActiveCall={inCall} callState={this.state.callState}
callType={activeCall ? activeCall.type : null}
isPeeking={myMembership !== "join"} isPeeking={myMembership !== "join"}
onInviteClick={this.onInviteButtonClick} onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick} onStopWarningClick={this.onStopAloneWarningClick}
@ -1915,10 +1915,10 @@ export default class RoomView extends React.Component<IProps, IState> {
}; };
} }
if (inCall) { if (activeCall) {
let zoomButton; let videoMuteButton; let zoomButton; let videoMuteButton;
if (call.type === "video") { if (activeCall.type === CallType.Video) {
zoomButton = ( zoomButton = (
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={_t("Fill screen")}> <div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={_t("Fill screen")}>
<TintableSvg <TintableSvg
@ -1933,10 +1933,11 @@ export default class RoomView extends React.Component<IProps, IState> {
videoMuteButton = videoMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}> <div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg <TintableSvg
src={call.isLocalVideoMuted() ? src={activeCall.isLocalVideoMuted() ?
require("../../../res/img/element-icons/call/video-muted.svg") : require("../../../res/img/element-icons/call/video-muted.svg") :
require("../../../res/img/element-icons/call/video-call.svg")} require("../../../res/img/element-icons/call/video-call.svg")}
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")} alt={activeCall.isLocalVideoMuted() ? _t("Click to unmute video") :
_t("Click to mute video")}
width="" width=""
height="27" height="27"
/> />
@ -1945,10 +1946,10 @@ export default class RoomView extends React.Component<IProps, IState> {
const voiceMuteButton = const voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}> <div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg <TintableSvg
src={call.isMicrophoneMuted() ? src={activeCall.isMicrophoneMuted() ?
require("../../../res/img/element-icons/call/voice-muted.svg") : require("../../../res/img/element-icons/call/voice-muted.svg") :
require("../../../res/img/element-icons/call/voice-unmuted.svg")} require("../../../res/img/element-icons/call/voice-unmuted.svg")}
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")} alt={activeCall.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
width="21" width="21"
height="26" height="26"
/> />
@ -2066,7 +2067,7 @@ export default class RoomView extends React.Component<IProps, IState> {
}); });
const mainClasses = classNames("mx_RoomView", { const mainClasses = classNames("mx_RoomView", {
mx_RoomView_inCall: inCall, mx_RoomView_inCall: Boolean(activeCall),
}); });
return ( return (

View file

@ -37,6 +37,7 @@ import WidgetStore from "../../../stores/WidgetStore";
import WidgetUtils from "../../../utils/WidgetUtils"; import WidgetUtils from "../../../utils/WidgetUtils";
import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore";
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore"; import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
import { PlaceCallType } from "../../../CallHandler";
function ComposerAvatar(props) { function ComposerAvatar(props) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
@ -53,7 +54,7 @@ function CallButton(props) {
const onVoiceCallClick = (ev) => { const onVoiceCallClick = (ev) => {
dis.dispatch({ dis.dispatch({
action: 'place_call', action: 'place_call',
type: "voice", type: PlaceCallType.Voice,
room_id: props.roomId, room_id: props.roomId,
}); });
}; };
@ -73,7 +74,7 @@ function VideoCallButton(props) {
const onCallClick = (ev) => { const onCallClick = (ev) => {
dis.dispatch({ dis.dispatch({
action: 'place_call', action: 'place_call',
type: ev.shiftKey ? "screensharing" : "video", type: ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video,
room_id: props.roomId, room_id: props.roomId,
}); });
}; };

View file

@ -24,13 +24,14 @@ import dis from '../../../dispatcher/dispatcher';
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import PersistentApp from "../elements/PersistentApp"; import PersistentApp from "../elements/PersistentApp";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call';
interface IProps { interface IProps {
} }
interface IState { interface IState {
roomId: string; roomId: string;
activeCall: any; activeCall: MatrixCall;
} }
export default class CallPreview extends React.Component<IProps, IState> { export default class CallPreview extends React.Component<IProps, IState> {
@ -84,7 +85,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
if (call) { if (call) {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: call.groupRoomId || call.roomId, room_id: call.roomId,
}); });
} }
}; };
@ -93,7 +94,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId); const callForRoom = CallHandler.sharedInstance().getCallForRoom(this.state.roomId);
const showCall = ( const showCall = (
this.state.activeCall && this.state.activeCall &&
this.state.activeCall.call_state === 'connected' && this.state.activeCall.state === CallState.Connected &&
!callForRoom !callForRoom
); );

View file

@ -25,6 +25,7 @@ import AccessibleButton from '../elements/AccessibleButton';
import VideoView from "./VideoView"; import VideoView from "./VideoView";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import PulsedAvatar from '../avatars/PulsedAvatar'; import PulsedAvatar from '../avatars/PulsedAvatar';
import { CallState, MatrixCall } from 'matrix-js-sdk/lib/webrtc/call';
interface IProps { interface IProps {
// js-sdk room object. If set, we will only show calls for the given // 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<IProps, IState> {
}; };
private showCall() { private showCall() {
let call; let call: MatrixCall;
if (this.props.room) { if (this.props.room) {
const roomId = this.props.room.roomId; const roomId = this.props.room.roomId;
@ -120,7 +121,7 @@ export default class CallView extends React.Component<IProps, IState> {
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement()); 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().getLocalVideoElement().style.display = "block";
this.getVideoView().getRemoteVideoElement().style.display = "block"; this.getVideoView().getRemoteVideoElement().style.display = "block";
} else { } else {

View file

@ -25,6 +25,7 @@ import CallHandler from '../../../CallHandler';
import PulsedAvatar from '../avatars/PulsedAvatar'; import PulsedAvatar from '../avatars/PulsedAvatar';
import RoomAvatar from '../avatars/RoomAvatar'; import RoomAvatar from '../avatars/RoomAvatar';
import FormButton from '../elements/FormButton'; import FormButton from '../elements/FormButton';
import { CallState } from 'matrix-js-sdk/lib/webrtc/call';
interface IProps { interface IProps {
} }
@ -53,7 +54,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
switch (payload.action) { switch (payload.action) {
case 'call_state': { case 'call_state': {
const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id); const call = CallHandler.sharedInstance().getCallForRoom(payload.room_id);
if (call && call.call_state === 'ringing') { if (call && call.state === CallState.Ringing) {
this.setState({ this.setState({
incomingCall: call, incomingCall: call,
}); });

View file

@ -2088,6 +2088,10 @@
"%(count)s of your messages have not been sent.|one": "Your message was not sent.", "%(count)s of your messages have not been sent.|one": "Your message was not sent.",
"%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|other": "<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.", "%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|other": "<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.",
"%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|one": "<resendText>Resend message</resendText> or <cancelText>cancel message</cancelText> now.", "%(count)s <resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. You can also select individual messages to resend or cancel.|one": "<resendText>Resend message</resendText> or <cancelText>cancel message</cancelText> 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.", "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.", "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 <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?", "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?": "There's no one else here! Would you like to <inviteText>invite others</inviteText> or <nowarnText>stop warning about the empty room</nowarnText>?",

View file

@ -19,11 +19,13 @@ import {
ClientWidgetApi, ClientWidgetApi,
IStickerActionRequest, IStickerActionRequest,
IStickyActionRequest, IStickyActionRequest,
ITemplateParams,
IWidget, IWidget,
IWidgetApiRequest, IWidgetApiRequest,
IWidgetApiRequestEmptyData, IWidgetApiRequestEmptyData,
IWidgetData, IWidgetData,
MatrixCapabilities, MatrixCapabilities,
runTemplate,
Widget, Widget,
WidgetApiFromWidgetAction, WidgetApiFromWidgetAction,
} from "matrix-widget-api"; } from "matrix-widget-api";
@ -76,15 +78,33 @@ class ElementWidget extends Widget {
let conferenceId = super.rawData['conferenceId']; let conferenceId = super.rawData['conferenceId'];
if (conferenceId === undefined) { if (conferenceId === undefined) {
// we'll need to parse the conference ID out of the URL for v1 Jitsi widgets // 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"); 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 { return {
...super.rawData, ...super.rawData,
theme: SettingsStore.getValue("theme"), theme: SettingsStore.getValue("theme"),
conferenceId, 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 { export class StopGapWidget extends EventEmitter {

View file

@ -6501,8 +6501,8 @@ mathml-tag-names@^2.0.1:
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "8.4.1" version "8.5.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/a727da9193e0ccb2fa8d7c3e8e321916f6717190" resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d8c4101fdd521e189f4755c6f02a8971b991ef5f"
dependencies: dependencies:
"@babel/runtime" "^7.11.2" "@babel/runtime" "^7.11.2"
another-json "^0.2.0" another-json "^0.2.0"