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:
commit
a1d25efceb
16 changed files with 352 additions and 176 deletions
98
CHANGELOG.md
98
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)
|
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)
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
this.removeCallForRoom(call.roomId);
|
||||||
this.pause("ringbackAudio");
|
if (oldState === CallState.InviteSent && (
|
||||||
this.play("callendAudio");
|
call.hangupParty === CallParty.Remote ||
|
||||||
} else if (newState === "ended" && oldState === "invite_sent" &&
|
(call.hangupParty === CallParty.Local && call.hangupReason === CallErrorCode.InviteTimeout)
|
||||||
(call.hangupParty === "remote" ||
|
|
||||||
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
|
|
||||||
)) {
|
)) {
|
||||||
this.setCallState(call, call.roomId, "busy");
|
this.play(AudioID.Busy);
|
||||||
this.pause("ringbackAudio");
|
|
||||||
this.play("busyAudio");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
|
||||||
title: _t('Call Timeout'),
|
title: _t('Call Timeout'),
|
||||||
description: _t('The remote side failed to pick up') + '.',
|
description: _t('The remote side failed to pick up') + '.',
|
||||||
});
|
});
|
||||||
} else if (oldState === "invite_sent") {
|
} else {
|
||||||
this.setCallState(call, call.roomId, "stop_ringback");
|
this.play(AudioID.CallEnd);
|
||||||
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,20 +280,25 @@ export default class CallHandler {
|
||||||
}, null, true);
|
}, null, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAction = (payload: ActionPayload) => {
|
|
||||||
const placeCall = (newCall) => {
|
private placeCall(
|
||||||
this.setCallListeners(newCall);
|
roomId: string, type: PlaceCallType,
|
||||||
if (payload.type === 'voice') {
|
localElement: HTMLVideoElement, remoteElement: HTMLVideoElement,
|
||||||
newCall.placeVoiceCall();
|
) {
|
||||||
} else if (payload.type === 'video') {
|
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), roomId);
|
||||||
newCall.placeVideoCall(
|
this.calls.set(roomId, call);
|
||||||
payload.remote_element,
|
this.setCallListeners(call);
|
||||||
payload.local_element,
|
if (type === PlaceCallType.Voice) {
|
||||||
|
call.placeVoiceCall();
|
||||||
|
} else if (type === 'video') {
|
||||||
|
call.placeVideoCall(
|
||||||
|
remoteElement,
|
||||||
|
localElement,
|
||||||
);
|
);
|
||||||
} else if (payload.type === 'screensharing') {
|
} else if (type === PlaceCallType.ScreenSharing) {
|
||||||
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
|
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
|
||||||
if (screenCapErrorString) {
|
if (screenCapErrorString) {
|
||||||
this.removeCallForRoom(newCall.roomId);
|
this.removeCallForRoom(roomId);
|
||||||
console.log("Can't capture screen: " + screenCapErrorString);
|
console.log("Can't capture screen: " + screenCapErrorString);
|
||||||
Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, {
|
||||||
title: _t('Unable to capture screen'),
|
title: _t('Unable to capture screen'),
|
||||||
|
@ -300,15 +306,13 @@ export default class CallHandler {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newCall.placeScreenSharingCall(
|
call.placeScreenSharingCall(remoteElement, localElement);
|
||||||
payload.remote_element,
|
|
||||||
payload.local_element,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.error("Unknown conf call type: %s", payload.type);
|
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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
let activeCall = null;
|
||||||
|
{
|
||||||
|
// New block because this variable doesn't need to hang around for the rest of the function
|
||||||
const call = this.getCallForRoom();
|
const call = this.getCallForRoom();
|
||||||
let inCall = false;
|
|
||||||
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
|
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
|
||||||
inCall = true;
|
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 (
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>?",
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue