From 8946af8f4e5e86b94198f39a1fe7f4270dace997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 12 Jul 2023 17:50:07 +0200 Subject: [PATCH 01/23] Hack e2ee in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/livekit/options.ts | 11 +++++++++++ src/livekit/useLiveKit.ts | 4 +++- src/room/InCallView.tsx | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/livekit/options.ts b/src/livekit/options.ts index 5115f213..e808149b 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -6,7 +6,9 @@ import { TrackPublishDefaults, VideoPreset, VideoPresets, + ExternalE2EEKeyProvider, } from "livekit-client"; +import E2EEWorker from "livekit-client/e2ee-worker?worker"; const defaultLiveKitPublishOptions: TrackPublishDefaults = { audioPreset: AudioPresets.music, @@ -22,7 +24,16 @@ const defaultLiveKitPublishOptions: TrackPublishDefaults = { backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding }, } as const; +const e2eeWorker = new E2EEWorker(); +const e2eeKeyProvider = new ExternalE2EEKeyProvider(); +e2eeKeyProvider.setKey("not secret password"); + export const defaultLiveKitOptions: RoomOptions = { + e2ee: { + keyProvider: e2eeKeyProvider, + worker: e2eeWorker, + }, + // automatically manage subscribed video quality adaptiveStream: true, diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 7a0a5330..6b862a66 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -32,12 +32,14 @@ export function useLiveKit( return options; }, [userChoices.video, userChoices.audio]); + const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]); + const { room } = useLiveKitRoom({ token: sfuConfig?.jwt, serverUrl: sfuConfig?.url, audio: userChoices.audio?.enabled ?? false, video: userChoices.video?.enabled ?? false, - options: roomOptions, + room: roomWithoutProps, }); return room; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index b8284975..0140061d 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -102,6 +102,8 @@ export function ActiveCall(props: ActiveCallProps) { return null; } + livekitRoom.setE2EEEnabled(true); + return ( From 4193629c2ce0ed4eb84ffb09995af504db95dd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 17 Jul 2023 16:53:58 +0200 Subject: [PATCH 02/23] Add E2EE password prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/livekit/options.ts | 11 ---------- src/livekit/useLiveKit.ts | 39 +++++++++++++++++++++++++++++++---- src/room/GroupCallView.tsx | 12 +++++++---- src/room/InCallView.tsx | 9 ++++++-- src/room/LobbyView.module.css | 6 ++++++ src/room/LobbyView.tsx | 32 ++++++++++++++++++++++++---- 6 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/livekit/options.ts b/src/livekit/options.ts index e808149b..5115f213 100644 --- a/src/livekit/options.ts +++ b/src/livekit/options.ts @@ -6,9 +6,7 @@ import { TrackPublishDefaults, VideoPreset, VideoPresets, - ExternalE2EEKeyProvider, } from "livekit-client"; -import E2EEWorker from "livekit-client/e2ee-worker?worker"; const defaultLiveKitPublishOptions: TrackPublishDefaults = { audioPreset: AudioPresets.music, @@ -24,16 +22,7 @@ const defaultLiveKitPublishOptions: TrackPublishDefaults = { backupCodec: { codec: "vp8", encoding: VideoPresets.h720.encoding }, } as const; -const e2eeWorker = new E2EEWorker(); -const e2eeKeyProvider = new ExternalE2EEKeyProvider(); -e2eeKeyProvider.setKey("not secret password"); - export const defaultLiveKitOptions: RoomOptions = { - e2ee: { - keyProvider: e2eeKeyProvider, - worker: e2eeWorker, - }, - // automatically manage subscribed video quality adaptiveStream: true, diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index 6b862a66..aa8d1e42 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -1,6 +1,12 @@ -import { Room, RoomOptions } from "livekit-client"; +import { + E2EEOptions, + ExternalE2EEKeyProvider, + Room, + RoomOptions, +} from "livekit-client"; import { useLiveKitRoom } from "@livekit/components-react"; -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; +import E2EEWorker from "livekit-client/e2ee-worker?worker"; import { defaultLiveKitOptions } from "./options"; import { SFUConfig } from "./openIDSFU"; @@ -15,10 +21,32 @@ export type DeviceChoices = { enabled: boolean; }; +export type E2EEConfig = { + sharedKey: string; +}; + export function useLiveKit( userChoices: UserChoices, - sfuConfig?: SFUConfig + sfuConfig?: SFUConfig, + e2eeConfig?: E2EEConfig ): Room | undefined { + const e2eeOptions = useMemo(() => { + if (!e2eeConfig?.sharedKey) return undefined; + + return { + keyProvider: new ExternalE2EEKeyProvider(), + worker: new E2EEWorker(), + } as E2EEOptions; + }, [e2eeConfig]); + + useEffect(() => { + if (!e2eeConfig?.sharedKey || !e2eeOptions) return; + + (e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey( + e2eeConfig?.sharedKey + ); + }, [e2eeOptions, e2eeConfig?.sharedKey]); + const roomOptions = useMemo((): RoomOptions => { const options = defaultLiveKitOptions; options.videoCaptureDefaults = { @@ -29,8 +57,11 @@ export function useLiveKit( ...options.audioCaptureDefaults, deviceId: userChoices.audio?.selectedId, }; + + options.e2ee = e2eeOptions; + return options; - }, [userChoices.video, userChoices.audio]); + }, [userChoices.video, userChoices.audio, e2eeOptions]); const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 9afc2ec3..1ec959bc 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -32,7 +32,7 @@ import { CallEndedView } from "./CallEndedView"; import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useProfile } from "../profile/useProfile"; -import { UserChoices } from "../livekit/useLiveKit"; +import { E2EEConfig, UserChoices } from "../livekit/useLiveKit"; import { findDeviceByName } from "../media-utils"; import { OpenIDLoader } from "../livekit/OpenIDLoader"; import { ActiveCall } from "./InCallView"; @@ -218,10 +218,12 @@ export function GroupCallView({ const [userChoices, setUserChoices] = useState( undefined ); + const [e2eeConfig, setE2EEConfig] = useState( + undefined + ); const livekitServiceURL = - groupCall.foci[0]?.livekitServiceUrl ?? - Config.get().livekit?.livekit_service_url; + groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url; if (!livekitServiceURL) { return ; } @@ -243,6 +245,7 @@ export function GroupCallView({ unencryptedEventsFromUsers={unencryptedEventsFromUsers} hideHeader={hideHeader} userChoices={userChoices} + e2eeConfig={e2eeConfig} otelGroupCallMembership={otelGroupCallMembership} /> @@ -283,8 +286,9 @@ export function GroupCallView({ return ( { + onEnter={(choices: UserChoices, e2eeConfig?: E2EEConfig) => { setUserChoices(choices); + setE2EEConfig(e2eeConfig); enter(); }} isEmbedded={isEmbedded} diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 0140061d..0889a883 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -77,7 +77,7 @@ import { SettingsModal } from "../settings/SettingsModal"; import { InviteModal } from "./InviteModal"; import { useRageshakeRequestModal } from "../settings/submit-rageshake"; import { RageshakeRequestModal } from "./RageshakeRequestModal"; -import { UserChoices, useLiveKit } from "../livekit/useLiveKit"; +import { E2EEConfig, UserChoices, useLiveKit } from "../livekit/useLiveKit"; import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher"; import { useFullscreen } from "./useFullscreen"; import { useLayoutStates } from "../video-grid/Layout"; @@ -92,11 +92,16 @@ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); export interface ActiveCallProps extends Omit { userChoices: UserChoices; + e2eeConfig?: E2EEConfig; } export function ActiveCall(props: ActiveCallProps) { const sfuConfig = useSFUConfig(); - const livekitRoom = useLiveKit(props.userChoices, sfuConfig); + const livekitRoom = useLiveKit( + props.userChoices, + sfuConfig, + props.e2eeConfig + ); if (!livekitRoom) { return null; diff --git a/src/room/LobbyView.module.css b/src/room/LobbyView.module.css index 55b7b5b0..3bb0162e 100644 --- a/src/room/LobbyView.module.css +++ b/src/room/LobbyView.module.css @@ -66,3 +66,9 @@ limitations under the License. .copyButton:last-child { margin-bottom: 0; } + +.passwordField { + width: 320px !important; + margin-bottom: 20px; + flex: 0; +} diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index 0f07ae7d..e52835ae 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { useRef, useEffect, useState } from "react"; +import { useRef, useEffect, useState, useCallback, ChangeEvent } from "react"; import { Trans, useTranslation } from "react-i18next"; import styles from "./LobbyView.module.css"; @@ -25,12 +25,13 @@ import { UserMenuContainer } from "../UserMenuContainer"; import { Body, Link } from "../typography/Typography"; import { useLocationNavigation } from "../useLocationNavigation"; import { MatrixInfo, VideoPreview } from "./VideoPreview"; -import { UserChoices } from "../livekit/useLiveKit"; +import { E2EEConfig, UserChoices } from "../livekit/useLiveKit"; +import { InputField } from "../input/Input"; interface Props { matrixInfo: MatrixInfo; - onEnter: (userChoices: UserChoices) => void; + onEnter: (userChoices: UserChoices, e2eeConfig?: E2EEConfig) => void; isEmbedded: boolean; hideHeader: boolean; } @@ -49,6 +50,17 @@ export function LobbyView(props: Props) { const [userChoices, setUserChoices] = useState( undefined ); + const [e2eeSharedKey, setE2EESharedKey] = useState( + undefined + ); + + const onE2EESharedKeyChanged = useCallback( + (event: ChangeEvent) => { + const value = event.target.value; + setE2EESharedKey(value === "" ? undefined : value); + }, + [setE2EESharedKey] + ); return (
@@ -68,12 +80,24 @@ export function LobbyView(props: Props) { matrixInfo={props.matrixInfo} onUserChoicesChanged={setUserChoices} /> + - ); - } - return ( @@ -139,10 +112,7 @@ export function CrashView() { )} -
{logsComponent}
- {error && ( - - )} + +
+ +
+
+ + + + {t("Return to home screen")} + + + + ); + } else { + return ( + <> +
+ + {surveySubmitted + ? t("{{displayName}}, your call has ended.", { + displayName, + }) + : t("{{displayName}}, your call has ended.", { + displayName, + }) + + "\n" + + t("How did it go?")} + + {!surveySubmitted && PosthogAnalytics.instance.isEnabled() + ? qualitySurveyDialog + : createAccountDialog} +
+ + + {t("Not now, return to home screen")} + + + + ); + } + }; + return ( <>
@@ -146,29 +205,7 @@ export function CallEndedView({
-
-
- - {surveySubmitted - ? t("{{displayName}}, your call has ended.", { - displayName, - }) - : t("{{displayName}}, your call has ended.", { - displayName, - }) + - "\n" + - t("How did it go?")} - - {!surveySubmitted && PosthogAnalytics.instance.isEnabled() - ? qualitySurveyDialog - : createAccountDialog} -
- - - {t("Not now, return to home screen")} - - -
+
{renderBody()}
); } diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 29d14b82..da7cb768 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -163,42 +163,47 @@ export function GroupCallView({ useSentryGroupCallHandler(groupCall); const [left, setLeft] = useState(false); + const [leaveError, setLeaveError] = useState(undefined); const history = useHistory(); - const onLeave = useCallback(async () => { - setLeft(true); + const onLeave = useCallback( + async (leaveError?: Error) => { + setLeaveError(leaveError); + setLeft(true); - let participantCount = 0; - for (const deviceMap of groupCall.participants.values()) { - participantCount += deviceMap.size; - } + let participantCount = 0; + for (const deviceMap of groupCall.participants.values()) { + participantCount += deviceMap.size; + } - // In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent, - // therefore we want the event to be sent instantly without getting queued/batched. - const sendInstantly = !!widget; - PosthogAnalytics.instance.eventCallEnded.track( - groupCall.groupCallId, - participantCount, - sendInstantly - ); + // In embedded/widget mode the iFrame will be killed right after the call ended prohibiting the posthog event from getting sent, + // therefore we want the event to be sent instantly without getting queued/batched. + const sendInstantly = !!widget; + PosthogAnalytics.instance.eventCallEnded.track( + groupCall.groupCallId, + participantCount, + sendInstantly + ); - leave(); - if (widget) { - // we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked. - await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms - widget.api.setAlwaysOnScreen(false); - PosthogAnalytics.instance.logout(); - widget.api.transport.send(ElementWidgetActions.HangupCall, {}); - } + leave(); + if (widget) { + // we need to wait until the callEnded event is tracked. Otherwise the iFrame gets killed before the callEnded event got tracked. + await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms + widget.api.setAlwaysOnScreen(false); + PosthogAnalytics.instance.logout(); + widget.api.transport.send(ElementWidgetActions.HangupCall, {}); + } - if ( - !isPasswordlessUser && - !isEmbedded && - !PosthogAnalytics.instance.isEnabled() - ) { - history.push("/"); - } - }, [groupCall, leave, isPasswordlessUser, isEmbedded, history]); + if ( + !isPasswordlessUser && + !isEmbedded && + !PosthogAnalytics.instance.isEnabled() + ) { + history.push("/"); + } + }, + [groupCall, leave, isPasswordlessUser, isEmbedded, history] + ); useEffect(() => { if (widget && state === GroupCallState.Entered) { @@ -218,6 +223,12 @@ export function GroupCallView({ undefined ); + const onReconnect = useCallback(() => { + setLeft(false); + setLeaveError(undefined); + groupCall.enter(); + }, [groupCall]); + if (error) { return ; } else if (state === GroupCallState.Entered && userChoices) { @@ -248,13 +259,16 @@ export function GroupCallView({ // submitting anything. if ( isPasswordlessUser || - (PosthogAnalytics.instance.isEnabled() && !isEmbedded) + (PosthogAnalytics.instance.isEnabled() && !isEmbedded) || + leaveError ) { return ( ); } else { diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index b8284975..30458599 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -24,7 +24,7 @@ import { } from "@livekit/components-react"; import { usePreventScroll } from "@react-aria/overlays"; import classNames from "classnames"; -import { Room, Track } from "livekit-client"; +import { DisconnectReason, Room, RoomEvent, Track } from "livekit-client"; import { MatrixClient } from "matrix-js-sdk/src/client"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; @@ -33,6 +33,7 @@ import { useTranslation } from "react-i18next"; import useMeasure from "react-use-measure"; import { OverlayTriggerState } from "@react-stately/overlays"; import { JoinRule } from "matrix-js-sdk/src/@types/partials"; +import { logger } from "matrix-js-sdk/src/logger"; import type { IWidgetApiRequest } from "matrix-widget-api"; import { @@ -114,7 +115,7 @@ export interface InCallViewProps { groupCall: GroupCall; livekitRoom: Room; participants: Map>; - onLeave: () => void; + onLeave: (error?: Error) => void; unencryptedEventsFromUsers: Set; hideHeader: boolean; otelGroupCallMembership?: OTelGroupCallMembership; @@ -188,6 +189,28 @@ export function InCallView({ async (muted) => await localParticipant.setMicrophoneEnabled(!muted) ); + const onDisconnected = useCallback( + (reason?: DisconnectReason) => { + logger.info("Disconnected from livekit call with reason ", reason); + onLeave( + new Error("Disconnected from LiveKit call with reason " + reason) + ); + }, + [onLeave] + ); + + const onLeavePress = useCallback(() => { + onLeave(); + }, [onLeave]); + + useEffect(() => { + livekitRoom.on(RoomEvent.Disconnected, onDisconnected); + + return () => { + livekitRoom.off(RoomEvent.Disconnected, onDisconnected); + }; + }, [onDisconnected, livekitRoom]); + useEffect(() => { widget?.api.transport.send( layout === "freedom" @@ -384,7 +407,7 @@ export function InCallView({ } buttons.push( - + ); footer =
{buttons}
; } diff --git a/src/settings/RageshakeButton.module.css b/src/settings/RageshakeButton.module.css new file mode 100644 index 00000000..66d0e2d9 --- /dev/null +++ b/src/settings/RageshakeButton.module.css @@ -0,0 +1,22 @@ +/* +Copyright 2022 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.rageshakeControl { + height: 50px; + width: 176px; + text-align: center; + vertical-align: middle; +} diff --git a/src/settings/RageshakeButton.tsx b/src/settings/RageshakeButton.tsx new file mode 100644 index 00000000..b6b045d8 --- /dev/null +++ b/src/settings/RageshakeButton.tsx @@ -0,0 +1,67 @@ +/* +Copyright 2023 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { useTranslation } from "react-i18next"; +import { useCallback } from "react"; + +import { Button } from "../button"; +import { Config } from "../config/Config"; +import styles from "./RageshakeButton.module.css"; +import { useSubmitRageshake } from "./submit-rageshake"; + +interface Props { + description: string; +} + +export const RageshakeButton = ({ description }: Props) => { + const { submitRageshake, sending, sent, error } = useSubmitRageshake(); + const { t } = useTranslation(); + + const sendDebugLogs = useCallback(() => { + submitRageshake({ + description, + sendLogs: true, + }); + }, [submitRageshake, description]); + + if (!Config.get().rageshake?.submit_url) return null; + + let logsComponent: JSX.Element | null = null; + if (sent) { + logsComponent =
{t("Thanks!")}
; + } else { + let caption = t("Send debug logs"); + if (error) { + caption = t("Retry sending logs"); + } else if (sending) { + logsComponent = {t("Sending…")}; + } + + logsComponent = ( + + ); + } + + return
{logsComponent}
; +}; From 4071c9029cc9fb080a6d4cdae167be8081adbeb5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 20 Jul 2023 18:22:17 +0100 Subject: [PATCH 06/23] Track call disconnections --- src/analytics/PosthogAnalytics.ts | 2 ++ src/analytics/PosthogEvents.ts | 16 ++++++++++++++++ src/room/InCallView.tsx | 1 + 3 files changed, 19 insertions(+) diff --git a/src/analytics/PosthogAnalytics.ts b/src/analytics/PosthogAnalytics.ts index 9ef2e70c..0ca23fb3 100644 --- a/src/analytics/PosthogAnalytics.ts +++ b/src/analytics/PosthogAnalytics.ts @@ -30,6 +30,7 @@ import { MuteMicrophoneTracker, UndecryptableToDeviceEventTracker, QualitySurveyEventTracker, + CallDisconnectedEventTracker, } from "./PosthogEvents"; import { Config } from "../config/Config"; import { getUrlParams } from "../UrlParams"; @@ -437,4 +438,5 @@ export class PosthogAnalytics { public eventMuteCamera = new MuteCameraTracker(); public eventUndecryptableToDevice = new UndecryptableToDeviceEventTracker(); public eventQualitySurvey = new QualitySurveyEventTracker(); + public eventCallDisconnected = new CallDisconnectedEventTracker(); } diff --git a/src/analytics/PosthogEvents.ts b/src/analytics/PosthogEvents.ts index f2fecb4e..97e25b6f 100644 --- a/src/analytics/PosthogEvents.ts +++ b/src/analytics/PosthogEvents.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { DisconnectReason } from "livekit-client"; + import { IPosthogEvent, PosthogAnalytics, @@ -181,3 +183,17 @@ export class QualitySurveyEventTracker { }); } } + +interface CallDisconnectedEvent { + eventName: "CallDisconnected"; + reason?: DisconnectReason; +} + +export class CallDisconnectedEventTracker { + track(reason?: DisconnectReason) { + PosthogAnalytics.instance.trackEvent({ + eventName: "CallDisconnected", + reason, + }); + } +} diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 30458599..604a5d95 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -191,6 +191,7 @@ export function InCallView({ const onDisconnected = useCallback( (reason?: DisconnectReason) => { + PosthogAnalytics.instance.eventCallDisconnected.track(reason); logger.info("Disconnected from livekit call with reason ", reason); onLeave( new Error("Disconnected from LiveKit call with reason " + reason) From e37783ad7e778c254b309a44111a42d799442b6a Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 20 Jul 2023 18:25:54 +0100 Subject: [PATCH 07/23] i18n --- public/locales/en-GB/app.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 5fe1ffa8..2973ac79 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -76,9 +76,11 @@ "Profile": "Profile", "Recaptcha dismissed": "Recaptcha dismissed", "Recaptcha not loaded": "Recaptcha not loaded", + "Reconnect": "Reconnect", "Register": "Register", "Registering…": "Registering…", "Remove": "Remove", + "Retry sending logs": "Retry sending logs", "Return to home screen": "Return to home screen", "Select an option": "Select an option", "Send debug logs": "Send debug logs", @@ -98,7 +100,7 @@ "Submitting…": "Submitting…", "Take me Home": "Take me Home", "Thanks, we received your feedback!": "Thanks, we received your feedback!", - "Thanks! We'll get right on it.": "Thanks! We'll get right on it.", + "Thanks!": "Thanks!", "This call already exists, would you like to join?": "This call already exists, would you like to join?", "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)", "Turn off camera": "Turn off camera", @@ -115,6 +117,7 @@ "Walkie-talkie call name": "Walkie-talkie call name", "WebRTC is not supported or is being blocked in this browser.": "WebRTC is not supported or is being blocked in this browser.", "Yes, join call": "Yes, join call", + "You were disconnected from the call": "You were disconnected from the call", "Your feedback": "Your feedback", "Your recent calls": "Your recent calls" } From 1fc17a5b64b9121fdabde424395dbc66c795b2ea Mon Sep 17 00:00:00 2001 From: Vri Date: Mon, 24 Jul 2023 13:39:36 +0000 Subject: [PATCH 08/23] Translated using Weblate (German) Currently translated at 100.0% (119 of 119 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/de/ --- public/locales/de/app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/locales/de/app.json b/public/locales/de/app.json index 99866dc5..60d68ea1 100644 --- a/public/locales/de/app.json +++ b/public/locales/de/app.json @@ -116,5 +116,6 @@ "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)": "Mit einem Klick auf „Anruf beitreten“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)", "By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)": "Mit einem Klick auf „Los geht’s“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)", "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)": "Diese Seite wird durch reCAPTCHA geschützt und es gelten Googles <2>Datenschutzerklärung und <6>Nutzungsbedingungen. <9>Mit einem Klick auf „Registrieren“ akzeptierst du unseren <2>Endbenutzer-Lizenzvertrag (EULA)", - "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call ist temporär nicht Ende-zu-Ende-verschlüsselt, während wir die Skalierbarkeit testen." + "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call ist temporär nicht Ende-zu-Ende-verschlüsselt, während wir die Skalierbarkeit testen.", + "Connectivity to the server has been lost.": "Die Verbindung zum Server wurde getrennt." } From 82050427bab2410f80e15d82c5cd9dba776b9888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 24 Jul 2023 15:42:24 +0000 Subject: [PATCH 09/23] Translated using Weblate (Estonian) Currently translated at 100.0% (119 of 119 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/et/ --- public/locales/et/app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/locales/et/app.json b/public/locales/et/app.json index d8659dbf..8d934ccc 100644 --- a/public/locales/et/app.json +++ b/public/locales/et/app.json @@ -116,5 +116,6 @@ "By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)": "Klõpsides „Jätka“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)", "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)": "Klõpsides „Liitu kõnega kohe“, nõustud sa meie <2>Lõppkasutaja litsentsilepinguga (EULA)", "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)": "Selles saidis on kasutusel ReCAPTCHA ja kehtivad Google'i <2>Privaatsuspoliitika ning <6>Teenusetingimused.<9>Klõpsides „Registreeru“, sa nõustud meie <12>Lõppkasutaja litsentsilepingu (EULA) tingimustega", - "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Seni kuni me testime skaleeritavust, siis Element Call ajutiselt pole läbivalt krüptitud." + "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Seni kuni me testime skaleeritavust, siis Element Call ajutiselt pole läbivalt krüptitud.", + "Connectivity to the server has been lost.": "Võrguühendus serveriga on katkenud." } From 71a56c22fdcce80209c2967c504f50faf2e690e5 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Mon, 24 Jul 2023 18:02:27 +0000 Subject: [PATCH 10/23] Translated using Weblate (Slovak) Currently translated at 100.0% (119 of 119 strings) Translation: Element Call/element-call Translate-URL: https://translate.element.io/projects/element-call/element-call/sk/ --- public/locales/sk/app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/locales/sk/app.json b/public/locales/sk/app.json index 6f588a91..52668084 100644 --- a/public/locales/sk/app.json +++ b/public/locales/sk/app.json @@ -116,5 +116,6 @@ "By clicking \"Join call now\", you agree to our <2>End User Licensing Agreement (EULA)": "Kliknutím na \"Pripojiť sa k hovoru teraz\" súhlasíte s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)", "By clicking \"Go\", you agree to our <2>End User Licensing Agreement (EULA)": "Kliknutím na tlačidlo \"Prejsť\" vyjadrujete súhlas s našou <2>Licenčnou zmluvou s koncovým používateľom (EULA)", "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy and <6>Terms of Service apply.<9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)": "Táto stránka je chránená systémom ReCAPTCHA a platia na ňu <2>Pravidlá ochrany osobných údajov spoločnosti Google a <6>Podmienky poskytovania služieb.<9>Kliknutím na tlačidlo \"Registrovať sa\" súhlasíte s našou <12>Licenčnou zmluvou s koncovým používateľom (EULA)", - "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call nie je dočasne šifrovaný, kým testujeme škálovateľnosť." + "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call nie je dočasne šifrovaný, kým testujeme škálovateľnosť.", + "Connectivity to the server has been lost.": "Spojenie so serverom sa stratilo." } From 9fdafaf39522b171a2daa7ad0ba2c854beecb5f9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 24 Jul 2023 21:03:56 +0100 Subject: [PATCH 11/23] Add a useEventEmitterThree for livekit's event emitters and use it --- src/room/InCallView.tsx | 9 ++------- src/useEvents.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 604a5d95..0daa0282 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -84,6 +84,7 @@ import { useFullscreen } from "./useFullscreen"; import { useLayoutStates } from "../video-grid/Layout"; import { useSFUConfig } from "../livekit/OpenIDLoader"; import { E2EELock } from "../E2EELock"; +import { useEventEmitterThree } from "../useEvents"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -204,13 +205,7 @@ export function InCallView({ onLeave(); }, [onLeave]); - useEffect(() => { - livekitRoom.on(RoomEvent.Disconnected, onDisconnected); - - return () => { - livekitRoom.off(RoomEvent.Disconnected, onDisconnected); - }; - }, [onDisconnected, livekitRoom]); + useEventEmitterThree(livekitRoom, RoomEvent.Disconnected, onDisconnected); useEffect(() => { widget?.api.transport.send( diff --git a/src/useEvents.ts b/src/useEvents.ts index 91a03abd..669cf863 100644 --- a/src/useEvents.ts +++ b/src/useEvents.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { useEffect } from "react"; +import EventEmitter from "eventemitter3"; import type { Listener, @@ -59,3 +60,20 @@ export const useTypedEventEmitter = < }; }, [emitter, eventType, listener]); }; + +// Shortcut for registering a listener on an eventemitter3 EventEmitter (ie. what the LiveKit SDK uses) +export const useEventEmitterThree = < + EventType extends EventEmitter.ValidEventTypes, + T extends EventEmitter.EventNames +>( + emitter: EventEmitter, + eventType: T, + listener: EventEmitter.EventListener +) => { + useEffect(() => { + emitter.on(eventType, listener); + return () => { + emitter.off(eventType, listener); + }; + }, [emitter, eventType, listener]); +}; From 8e6380db0b68b93bdd8feebf6ff4f83ef6435b17 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 24 Jul 2023 21:33:13 +0100 Subject: [PATCH 12/23] Fix logs component states Also just display text when sending which makes the button jump around a bit but avoids hardcoding the width --- src/settings/RageshakeButton.module.css | 1 - src/settings/RageshakeButton.tsx | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/settings/RageshakeButton.module.css b/src/settings/RageshakeButton.module.css index 66d0e2d9..5a621b6c 100644 --- a/src/settings/RageshakeButton.module.css +++ b/src/settings/RageshakeButton.module.css @@ -16,7 +16,6 @@ limitations under the License. .rageshakeControl { height: 50px; - width: 176px; text-align: center; vertical-align: middle; } diff --git a/src/settings/RageshakeButton.tsx b/src/settings/RageshakeButton.tsx index b6b045d8..599c565d 100644 --- a/src/settings/RageshakeButton.tsx +++ b/src/settings/RageshakeButton.tsx @@ -40,14 +40,14 @@ export const RageshakeButton = ({ description }: Props) => { if (!Config.get().rageshake?.submit_url) return null; let logsComponent: JSX.Element | null = null; - if (sent) { + if (sending) { + logsComponent = {t("Sending…")}; + } else if (sent) { logsComponent =
{t("Thanks!")}
; } else { let caption = t("Send debug logs"); if (error) { caption = t("Retry sending logs"); - } else if (sending) { - logsComponent = {t("Sending…")}; } logsComponent = ( From fa2fcbcbecd993ab51b4bd2f3f4f395c85d5ef8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 11:07:20 +0200 Subject: [PATCH 13/23] Hide E2EE behind a flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/LobbyView.tsx | 19 ++++++++++++------- src/settings/SettingsModal.tsx | 14 ++++++++++++++ src/settings/useSetting.ts | 3 +++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index e52835ae..258b474f 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -27,6 +27,7 @@ import { useLocationNavigation } from "../useLocationNavigation"; import { MatrixInfo, VideoPreview } from "./VideoPreview"; import { E2EEConfig, UserChoices } from "../livekit/useLiveKit"; import { InputField } from "../input/Input"; +import { useEnableE2EE } from "../settings/useSetting"; interface Props { matrixInfo: MatrixInfo; @@ -40,6 +41,8 @@ export function LobbyView(props: Props) { const { t } = useTranslation(); useLocationNavigation(); + const [enableE2EE] = useEnableE2EE(); + const joinCallButtonRef = useRef(null); useEffect(() => { if (joinCallButtonRef.current) { @@ -80,13 +83,15 @@ export function LobbyView(props: Props) { matrixInfo={props.matrixInfo} onUserChoicesChanged={setUserChoices} /> - + {enableE2EE && ( + + )} diff --git a/src/settings/useSetting.ts b/src/settings/useSetting.ts index 63f1e23a..8d3f074f 100644 --- a/src/settings/useSetting.ts +++ b/src/settings/useSetting.ts @@ -104,6 +104,9 @@ export const useDeveloperSettingsTab = () => export const useShowConnectionStats = () => useSetting("show-connection-stats", false); +export const useEnableE2EE = () => + useSetting("enable-end-to-end-encryption", false); + export const useDefaultDevices = () => useSetting("defaultDevices", { audioinput: "", From 9abd409a9affc1ebc8983cdfa4140eeee6e7ff44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 11:07:30 +0200 Subject: [PATCH 14/23] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- public/locales/en-GB/app.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 5425fe69..4fcdc1b5 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -38,6 +38,7 @@ "Download debug logs": "Download debug logs", "Element Call Home": "Element Call Home", "Element Call is temporarily not end-to-end encrypted while we test scalability.": "Element Call is temporarily not end-to-end encrypted while we test scalability.", + "Enable end-to-end encryption (password protected calls)": "Enable end-to-end encryption (password protected calls)", "Exit full screen": "Exit full screen", "Expose developer settings in the settings window.": "Expose developer settings in the settings window.", "Feedback": "Feedback", From 7ddede4feeb9bb3f2b737bc092d8c583b5c852d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 13:03:31 +0200 Subject: [PATCH 15/23] Update string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- public/locales/en-GB/app.json | 2 +- src/room/LobbyView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/locales/en-GB/app.json b/public/locales/en-GB/app.json index 4fcdc1b5..41604e6d 100644 --- a/public/locales/en-GB/app.json +++ b/public/locales/en-GB/app.json @@ -73,7 +73,7 @@ "Not registered yet? <2>Create an account": "Not registered yet? <2>Create an account", "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}": "Other users are trying to join this call from incompatible versions. These users should ensure that they have refreshed their browsers:<1>{userLis}", "Password": "Password", - "Password (if none E2EE, is disabled)": "Password (if none E2EE, is disabled)", + "Password (if none, E2EE is disabled)": "Password (if none, E2EE is disabled)", "Passwords must match": "Passwords must match", "Profile": "Profile", "Recaptcha dismissed": "Recaptcha dismissed", diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index 258b474f..99c4f21a 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -86,7 +86,7 @@ export function LobbyView(props: Props) { {enableE2EE && ( Date: Tue, 25 Jul 2023 13:40:22 +0200 Subject: [PATCH 16/23] Don't change mute state while in the lobby MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/GroupCallView.tsx | 4 +++- src/room/LobbyView.tsx | 4 ++-- src/room/VideoPreview.tsx | 14 +++++--------- src/room/useGroupCall.ts | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 58fa0595..7de67cd8 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -229,6 +229,8 @@ export function GroupCallView({ groupCall.enter(); }, [groupCall]); + console.log("LOG participant size", participants.size); + if (error) { return ; } else if (state === GroupCallState.Entered && userChoices) { @@ -293,7 +295,7 @@ export function GroupCallView({ setUserChoices(choices); enter(); }} - muteAudio={participants.size > 8} + initWithMutedAudio={participants.size > 0} isEmbedded={isEmbedded} hideHeader={hideHeader} /> diff --git a/src/room/LobbyView.tsx b/src/room/LobbyView.tsx index 6b51542a..c97cc3ca 100644 --- a/src/room/LobbyView.tsx +++ b/src/room/LobbyView.tsx @@ -33,7 +33,7 @@ interface Props { onEnter: (userChoices: UserChoices) => void; isEmbedded: boolean; hideHeader: boolean; - muteAudio: boolean; + initWithMutedAudio: boolean; } export function LobbyView(props: Props) { @@ -67,7 +67,7 @@ export function LobbyView(props: Props) {
diff --git a/src/room/VideoPreview.tsx b/src/room/VideoPreview.tsx index d39aeb17..9ba11a89 100644 --- a/src/room/VideoPreview.tsx +++ b/src/room/VideoPreview.tsx @@ -40,13 +40,13 @@ export type MatrixInfo = { interface Props { matrixInfo: MatrixInfo; - muteAudio: boolean; + initWithMutedAudio: boolean; onUserChoicesChanged: (choices: UserChoices) => void; } export function VideoPreview({ matrixInfo, - muteAudio, + initWithMutedAudio, onUserChoicesChanged, }: Props) { const { client } = useClient(); @@ -69,13 +69,9 @@ export function VideoPreview({ // Create local media tracks. const [videoEnabled, setVideoEnabled] = useState(true); - const [audioEnabled, setAudioEnabled] = useState(!muteAudio); - - useEffect(() => { - if (muteAudio) { - setAudioEnabled(false); - } - }, [muteAudio]); + const [audioEnabled, setAudioEnabled] = useState( + !initWithMutedAudio + ); // The settings are updated as soon as the device changes. We wrap the settings value in a ref to store their initial value. // Not changing the device options prohibits the usePreviewTracks hook to recreate the tracks. diff --git a/src/room/useGroupCall.ts b/src/room/useGroupCall.ts index b0cbf76f..b02e7113 100644 --- a/src/room/useGroupCall.ts +++ b/src/room/useGroupCall.ts @@ -171,7 +171,7 @@ export function useGroupCall( isScreensharing: false, screenshareFeeds: [], requestingScreenshare: false, - participants: new Map(), + participants: getParticipants(groupCall), hasLocalParticipant: false, }); From f3615b73fcb939b6cf18c2c71bd208b2ec43b07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 15:18:45 +0200 Subject: [PATCH 17/23] /me is an idiot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/GroupCallView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 7de67cd8..2af6e314 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -295,7 +295,7 @@ export function GroupCallView({ setUserChoices(choices); enter(); }} - initWithMutedAudio={participants.size > 0} + initWithMutedAudio={participants.size > 8} isEmbedded={isEmbedded} hideHeader={hideHeader} /> From d8fcaa6eba13ad9421aa83a2ea56441227f3f810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 15:23:00 +0200 Subject: [PATCH 18/23] Make it a const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/GroupCallView.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 2af6e314..d47cbb31 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -37,6 +37,12 @@ import { findDeviceByName } from "../media-utils"; import { OpenIDLoader } from "../livekit/OpenIDLoader"; import { ActiveCall } from "./InCallView"; +/** + * If there already is this many participants in the call, we automatically mute + * the user + */ +const MUTE_PARTICIPANT_COUNT = 8; + declare global { interface Window { groupCall?: GroupCall; @@ -295,7 +301,7 @@ export function GroupCallView({ setUserChoices(choices); enter(); }} - initWithMutedAudio={participants.size > 8} + initWithMutedAudio={participants.size > MUTE_PARTICIPANT_COUNT} isEmbedded={isEmbedded} hideHeader={hideHeader} /> From 7c5f07b498bbc49963cac45bbd8b832d1aeea2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 15:49:59 +0200 Subject: [PATCH 19/23] Add a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/livekit/useLiveKit.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index aa8d1e42..37fcffbb 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -63,8 +63,10 @@ export function useLiveKit( return options; }, [userChoices.video, userChoices.audio, e2eeOptions]); + // We have to create the room manually here due to a bug inside + // @livekit/components-react. JSON.stringify() is used in deps of a + // useEffect() with an argument that references itself, if E2EE is enabled const roomWithoutProps = useMemo(() => new Room(roomOptions), [roomOptions]); - const { room } = useLiveKitRoom({ token: sfuConfig?.jwt, serverUrl: sfuConfig?.url, From a1e18322df30073d05d8dca7ac607096477d4992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 16:13:45 +0200 Subject: [PATCH 20/23] Missing import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/GroupCallView.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index a75ed451..63c73474 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -36,6 +36,7 @@ import { E2EEConfig, UserChoices } from "../livekit/useLiveKit"; import { findDeviceByName } from "../media-utils"; import { OpenIDLoader } from "../livekit/OpenIDLoader"; import { ActiveCall } from "./InCallView"; +import { Config } from "../config/Config"; /** * If there already is this many participants in the call, we automatically mute From 926eb8adbff8867957874c71d59d3d2864fd416d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 16:40:12 +0200 Subject: [PATCH 21/23] Fix e2ee bugginess MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/InCallView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 60f3e0f1..ee3e893a 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -110,7 +110,9 @@ export function ActiveCall(props: ActiveCallProps) { return null; } - livekitRoom.setE2EEEnabled(true); + if (props.e2eeConfig && !livekitRoom.isE2EEEnabled) { + livekitRoom.setE2EEEnabled(!!props.e2eeConfig); + } return ( From ec20c017d1ff680b5bd5c780494cb4e36d3c912e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 16:57:43 +0200 Subject: [PATCH 22/23] Update livekit-client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- package.json | 6 +++--- yarn.lock | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5934f89f..74f15cd8 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "i18next": "^21.10.0", "i18next-browser-languagedetector": "^6.1.8", "i18next-http-backend": "^1.4.4", - "livekit-client": "1.12.0", + "livekit-client": "1.12.1", "lodash": "^4.17.21", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#b698217445318f453e0b1086364a33113eaa85d9", "matrix-widget-api": "^1.3.1", @@ -83,12 +83,12 @@ "@storybook/react": "^6.5.0-alpha.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", - "@types/node": "^18.13.0", - "@types/request": "^2.48.8", "@types/content-type": "^1.1.5", "@types/dom-screen-wake-lock": "^1.0.1", "@types/grecaptcha": "^3.0.4", + "@types/node": "^18.13.0", "@types/react-router-dom": "^5.3.3", + "@types/request": "^2.48.8", "@types/sdp-transform": "^2.4.5", "@types/uuid": "9", "@typescript-eslint/eslint-plugin": "^6.1.0", diff --git a/yarn.lock b/yarn.lock index 5941702c..68fa7316 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2217,7 +2217,7 @@ "@react-hook/latest" "^1.0.3" clsx "^1.2.1" -"@matrix-org/matrix-sdk-crypto-js@^0.1.0": +"@matrix-org/matrix-sdk-crypto-js@^0.1.1": version "0.1.4" resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.4.tgz#c13c7c8c3a1d8da08e6ad195d25e5e61cc402df7" integrity sha512-OxG84iSeR89zYLFeb+DCaFtZT+DDiIu+kTkqY8OYfhE5vpGLFX2sDVBRrAdos1IUqEoboDloDBR9+yU7hNRyog== @@ -6450,6 +6450,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + css-blank-pseudo@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" @@ -10751,10 +10756,10 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -livekit-client@1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-1.12.0.tgz#4b4f18087331d4893adaccb148e33fe870eb9a2e" - integrity sha512-G1KHNMSaEMXtPIKxTQt+WH/uRvhkQ0tQaZ5Kkem2CvB/5I4KPrV4DSmhBKP1P+8reHcaBBiye8V9bfpD69aHyQ== +livekit-client@1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/livekit-client/-/livekit-client-1.12.1.tgz#b927b4fb07d2d64543d25a99db36ffbb7caa23e6" + integrity sha512-/mob04a/Mb0D+4sIzB7/pqakpJMCORSK+Qu5oTIcuSpgL+eBYGzHPE2sutGCGoe3Ns9sITAqUTyiui5+GN3i2w== dependencies: eventemitter3 "^5.0.1" loglevel "^1.8.0" @@ -11005,7 +11010,7 @@ matrix-events-sdk@0.0.1: resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b698217445318f453e0b1086364a33113eaa85d9" dependencies: "@babel/runtime" "^7.12.5" - "@matrix-org/matrix-sdk-crypto-js" "^0.1.0" + "@matrix-org/matrix-sdk-crypto-js" "^0.1.1" another-json "^0.2.0" bs58 "^5.0.0" content-type "^1.0.4" @@ -11700,6 +11705,14 @@ objectorarray@^1.0.5: resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5" integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg== +oidc-client-ts@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz#7d86b5efe2248f3637a6f3a0ee1af86764aea125" + integrity sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g== + dependencies: + crypto-js "^4.1.1" + jwt-decode "^3.1.2" + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" From 7c558b16ca89593dd9d0214b504262c1878693e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 25 Jul 2023 17:17:49 +0200 Subject: [PATCH 23/23] Add screen-sharing volume control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/video-grid/VideoTile.tsx | 19 +++++++++---------- src/video-grid/VideoTileSettingsModal.tsx | 20 +++++++++++++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/video-grid/VideoTile.tsx b/src/video-grid/VideoTile.tsx index 63ceddf2..f15052e0 100644 --- a/src/video-grid/VideoTile.tsx +++ b/src/video-grid/VideoTile.tsx @@ -121,6 +121,15 @@ export const VideoTile = forwardRef( const toolbarButtons: JSX.Element[] = []; if (!sfuParticipant.isLocal) { + toolbarButtons.push( + + ); + if (content === TileContent.ScreenShare) { toolbarButtons.push( ( onPress={onFullscreen} /> ); - } else { - // Due to the LK SDK this sadly only works for user-media atm - toolbarButtons.push( - - ); } } diff --git a/src/video-grid/VideoTileSettingsModal.tsx b/src/video-grid/VideoTileSettingsModal.tsx index 00d41c50..0c77075a 100644 --- a/src/video-grid/VideoTileSettingsModal.tsx +++ b/src/video-grid/VideoTileSettingsModal.tsx @@ -16,29 +16,36 @@ limitations under the License. import React, { ChangeEvent, useState } from "react"; import { useTranslation } from "react-i18next"; -import { RemoteParticipant } from "livekit-client"; +import { RemoteParticipant, Track } from "livekit-client"; import { FieldRow } from "../input/Input"; import { Modal } from "../Modal"; import styles from "./VideoTileSettingsModal.module.css"; import { VolumeIcon } from "../button/VolumeIcon"; -import { ItemData } from "./VideoTile"; +import { ItemData, TileContent } from "./VideoTile"; interface LocalVolumeProps { participant: RemoteParticipant; + content: TileContent; } const LocalVolume: React.FC = ({ participant, + content, }: LocalVolumeProps) => { + const source = + content === TileContent.UserMedia + ? Track.Source.Microphone + : Track.Source.ScreenShareAudio; + const [localVolume, setLocalVolume] = useState( - participant.getVolume() ?? 0 + participant.getVolume(source) ?? 0 ); const onLocalVolumeChanged = (event: ChangeEvent) => { const value: number = +event.target.value; setLocalVolume(value); - participant.setVolume(value); + participant.setVolume(value, source); }; return ( @@ -78,7 +85,10 @@ export const VideoTileSettingsModal = ({ data, onClose, ...rest }: Props) => { {...rest} >
- +
);