Add E2EE password prompt
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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<UserChoices | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [e2eeConfig, setE2EEConfig] = useState<E2EEConfig | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const livekitServiceURL =
|
||||
groupCall.foci[0]?.livekitServiceUrl ??
|
||||
Config.get().livekit?.livekit_service_url;
|
||||
groupCall.livekitServiceURL ?? Config.get().livekit?.livekit_service_url;
|
||||
if (!livekitServiceURL) {
|
||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||
}
|
||||
@@ -243,6 +245,7 @@ export function GroupCallView({
|
||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||
hideHeader={hideHeader}
|
||||
userChoices={userChoices}
|
||||
e2eeConfig={e2eeConfig}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
/>
|
||||
</OpenIDLoader>
|
||||
@@ -283,8 +286,9 @@ export function GroupCallView({
|
||||
return (
|
||||
<LobbyView
|
||||
matrixInfo={matrixInfo}
|
||||
onEnter={(choices: UserChoices) => {
|
||||
onEnter={(choices: UserChoices, e2eeConfig?: E2EEConfig) => {
|
||||
setUserChoices(choices);
|
||||
setE2EEConfig(e2eeConfig);
|
||||
enter();
|
||||
}}
|
||||
isEmbedded={isEmbedded}
|
||||
|
||||
@@ -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<InCallViewProps, "livekitRoom"> {
|
||||
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;
|
||||
|
||||
@@ -66,3 +66,9 @@ limitations under the License.
|
||||
.copyButton:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.passwordField {
|
||||
width: 320px !important;
|
||||
margin-bottom: 20px;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
@@ -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<UserChoices | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [e2eeSharedKey, setE2EESharedKey] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const onE2EESharedKeyChanged = useCallback(
|
||||
(event: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value;
|
||||
setE2EESharedKey(value === "" ? undefined : value);
|
||||
},
|
||||
[setE2EESharedKey]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.room}>
|
||||
@@ -68,12 +80,24 @@ export function LobbyView(props: Props) {
|
||||
matrixInfo={props.matrixInfo}
|
||||
onUserChoicesChanged={setUserChoices}
|
||||
/>
|
||||
<InputField
|
||||
className={styles.passwordField}
|
||||
label={t("Password (if none E2EE, is disabled)")}
|
||||
type="text"
|
||||
onChange={onE2EESharedKeyChanged}
|
||||
value={e2eeSharedKey}
|
||||
/>
|
||||
<Trans>
|
||||
<Button
|
||||
ref={joinCallButtonRef}
|
||||
className={styles.copyButton}
|
||||
size="lg"
|
||||
onPress={() => props.onEnter(userChoices!)}
|
||||
onPress={() =>
|
||||
props.onEnter(
|
||||
userChoices!,
|
||||
e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined
|
||||
)
|
||||
}
|
||||
data-testid="lobby_joinCall"
|
||||
>
|
||||
Join call now
|
||||
|
||||
Reference in New Issue
Block a user