mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-31 07:00:26 +00:00
Handle embedding
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
@@ -19,10 +19,10 @@ import { Trans } from "react-i18next";
|
||||
import { Banner } from "./Banner";
|
||||
import styles from "./E2EEBanner.module.css";
|
||||
import { ReactComponent as LockOffIcon } from "./icons/LockOff.svg";
|
||||
import { useEnableE2EE } from "./settings/useSetting";
|
||||
import { useEnableE2EE } from "./e2ee/sharedKeyManagement";
|
||||
|
||||
export const E2EEBanner = () => {
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
const e2eeEnabled = useEnableE2EE();
|
||||
if (e2eeEnabled) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -92,6 +92,10 @@ interface UrlParams {
|
||||
* E2EE password
|
||||
*/
|
||||
password: string | null;
|
||||
/**
|
||||
* Whether we the app should use per participant keys for E2EE.
|
||||
*/
|
||||
perParticipantE2EE: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,6 +194,7 @@ export const getUrlParams = (
|
||||
fontScale: Number.isNaN(fontScale) ? null : fontScale,
|
||||
analyticsID: getParam("analyticsID"),
|
||||
allowIceFallback: hasParam("allowIceFallback"),
|
||||
perParticipantE2EE: hasParam("perParticipantE2EE"),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -15,11 +15,7 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger } from "@sentry/utils";
|
||||
import {
|
||||
BaseKeyProvider,
|
||||
KeyProviderOptions,
|
||||
createKeyMaterialFromString,
|
||||
} from "livekit-client";
|
||||
import { BaseKeyProvider, createKeyMaterialFromString } from "livekit-client";
|
||||
import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership";
|
||||
import {
|
||||
MatrixRTCSession,
|
||||
@@ -27,19 +23,29 @@ import {
|
||||
} from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
export class MatrixKeyProvider extends BaseKeyProvider {
|
||||
constructor(
|
||||
private rtcSession: MatrixRTCSession,
|
||||
keyProviderOptions: Partial<KeyProviderOptions> = {}
|
||||
) {
|
||||
super(keyProviderOptions);
|
||||
private rtcSession?: MatrixRTCSession;
|
||||
|
||||
const encryptionKey = this.rtcSession.activeEncryptionKey;
|
||||
public setRTCSession(rtcSession: MatrixRTCSession) {
|
||||
const encryptionKey = rtcSession.activeEncryptionKey;
|
||||
if (!encryptionKey) {
|
||||
throw new Error(
|
||||
"MatrixKeyProvider requires the given MatrixRTCSession to have an activeEncryptionKey"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.rtcSession) {
|
||||
this.rtcSession.off(
|
||||
MatrixRTCSessionEvent.MembershipsChanged,
|
||||
this.onMemberShipsChanged
|
||||
);
|
||||
this.rtcSession.off(
|
||||
MatrixRTCSessionEvent.ActiveEncryptionKeyChanged,
|
||||
this.onEncryptionKeyChanged
|
||||
);
|
||||
}
|
||||
|
||||
this.rtcSession = rtcSession;
|
||||
|
||||
this.rtcSession.on(
|
||||
MatrixRTCSessionEvent.MembershipsChanged,
|
||||
this.onMemberShipsChanged
|
||||
|
||||
@@ -15,8 +15,9 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { isE2EESupported } from "livekit-client";
|
||||
|
||||
import { useEnableE2EE } from "../settings/useSetting";
|
||||
import { useEnableSPAE2EE } from "../settings/useSetting";
|
||||
import { useLocalStorage } from "../useLocalStorage";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { PASSWORD_STRING, useUrlParams } from "../UrlParams";
|
||||
@@ -28,7 +29,7 @@ export const useInternalRoomSharedKey = (
|
||||
roomId: string
|
||||
): [string | null, (value: string) => void] => {
|
||||
const key = useMemo(() => getRoomSharedKeyLocalStorageKey(roomId), [roomId]);
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
const [e2eeEnabled] = useEnableSPAE2EE();
|
||||
const [roomSharedKey, setRoomSharedKey] = useLocalStorage(key);
|
||||
|
||||
return [e2eeEnabled ? roomSharedKey : null, setRoomSharedKey];
|
||||
@@ -67,7 +68,7 @@ export const useManageRoomSharedKey = (roomId: string): string | null => {
|
||||
};
|
||||
|
||||
export const useIsRoomE2EE = (roomId: string): boolean | null => {
|
||||
const { isEmbedded } = useUrlParams();
|
||||
const { isEmbedded, perParticipantE2EE } = useUrlParams();
|
||||
const client = useClient();
|
||||
const room = useMemo(
|
||||
() => client.client?.getRoom(roomId) ?? null,
|
||||
@@ -75,11 +76,32 @@ export const useIsRoomE2EE = (roomId: string): boolean | null => {
|
||||
);
|
||||
const isE2EE = useMemo(() => {
|
||||
if (isEmbedded) {
|
||||
return false;
|
||||
return perParticipantE2EE;
|
||||
} else {
|
||||
return room ? !room?.getCanonicalAlias() : null;
|
||||
}
|
||||
}, [isEmbedded, room]);
|
||||
}, [room, isEmbedded, perParticipantE2EE]);
|
||||
|
||||
return isE2EE;
|
||||
};
|
||||
|
||||
export const useEnableEmbeddedE2EE = (): boolean => {
|
||||
const { isEmbedded, perParticipantE2EE } = useUrlParams();
|
||||
|
||||
if (!isEmbedded) return false;
|
||||
if (!isE2EESupported()) return false;
|
||||
|
||||
return perParticipantE2EE;
|
||||
};
|
||||
|
||||
export const useEnableE2EE = (): boolean => {
|
||||
const [spaE2EEEnabled] = useEnableSPAE2EE();
|
||||
const embeddedE2EEEnabled = useEnableEmbeddedE2EE();
|
||||
|
||||
const e2eeEnabled = useMemo(
|
||||
() => spaE2EEEnabled || embeddedE2EEEnabled,
|
||||
[spaE2EEEnabled, embeddedE2EEEnabled]
|
||||
);
|
||||
|
||||
return e2eeEnabled;
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ import { JoinExistingCallModal } from "./JoinExistingCallModal";
|
||||
import { Caption, Title } from "../typography/Typography";
|
||||
import { Form } from "../form/Form";
|
||||
import { CallType, CallTypeDropdown } from "./CallTypeDropdown";
|
||||
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
|
||||
import { useEnableSPAE2EE, useOptInAnalytics } from "../settings/useSetting";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { E2EEBanner } from "../E2EEBanner";
|
||||
import { setLocalStorageItem } from "../useLocalStorage";
|
||||
@@ -57,7 +57,7 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
const [e2eeEnabled] = useEnableSPAE2EE();
|
||||
|
||||
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
|
||||
(e: FormEvent) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ import styles from "./UnauthenticatedView.module.css";
|
||||
import commonStyles from "./common.module.css";
|
||||
import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { AnalyticsNotice } from "../analytics/AnalyticsNotice";
|
||||
import { useEnableE2EE, useOptInAnalytics } from "../settings/useSetting";
|
||||
import { useEnableSPAE2EE, useOptInAnalytics } from "../settings/useSetting";
|
||||
import { Config } from "../config/Config";
|
||||
import { E2EEBanner } from "../E2EEBanner";
|
||||
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
|
||||
@@ -60,7 +60,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
const [e2eeEnabled] = useEnableSPAE2EE();
|
||||
|
||||
const onSubmit: FormEventHandler<HTMLFormElement> = useCallback(
|
||||
(e) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
import {
|
||||
ConnectionState,
|
||||
E2EEOptions,
|
||||
ExternalE2EEKeyProvider,
|
||||
Room,
|
||||
RoomOptions,
|
||||
setLogLevel,
|
||||
@@ -42,8 +42,14 @@ import {
|
||||
} from "./useECConnectionState";
|
||||
import { MatrixKeyProvider } from "../e2ee/matrixKeyProvider";
|
||||
|
||||
export enum E2EEMode {
|
||||
PerParticipantKey = "per_participant_key",
|
||||
SharedKey = "shared_key",
|
||||
}
|
||||
|
||||
export type E2EEConfig = {
|
||||
sharedKey: string;
|
||||
mode: E2EEMode;
|
||||
sharedKey?: string;
|
||||
};
|
||||
|
||||
setLogLevel("debug");
|
||||
@@ -60,21 +66,33 @@ export function useLiveKit(
|
||||
e2eeConfig?: E2EEConfig
|
||||
): UseLivekitResult {
|
||||
const e2eeOptions = useMemo(() => {
|
||||
if (!e2eeConfig?.sharedKey) return undefined;
|
||||
if (!e2eeConfig) return undefined;
|
||||
|
||||
return {
|
||||
keyProvider: new ExternalE2EEKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
} as E2EEOptions;
|
||||
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
|
||||
return {
|
||||
keyProvider: new MatrixKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
};
|
||||
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
|
||||
return {
|
||||
keyProvider: new ExternalE2EEKeyProvider(),
|
||||
worker: new E2EEWorker(),
|
||||
};
|
||||
}
|
||||
}, [e2eeConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!e2eeConfig?.sharedKey || !e2eeOptions) return;
|
||||
if (!e2eeOptions) return;
|
||||
if (!e2eeConfig) return;
|
||||
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||
e2eeConfig?.sharedKey
|
||||
);
|
||||
}, [e2eeOptions, e2eeConfig?.sharedKey]);
|
||||
if (e2eeConfig.mode === E2EEMode.PerParticipantKey) {
|
||||
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
|
||||
} else if (e2eeConfig.mode === E2EEMode.SharedKey && e2eeConfig.sharedKey) {
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||
e2eeConfig.sharedKey
|
||||
);
|
||||
}
|
||||
}, [e2eeOptions, e2eeConfig, rtcSession]);
|
||||
|
||||
const initialMuteStates = useRef<MuteStates>(muteStates);
|
||||
const devices = useMediaDevices();
|
||||
|
||||
@@ -40,8 +40,12 @@ import { useMatrixRTCSessionJoinState } from "../useMatrixRTCSessionJoinState";
|
||||
import {
|
||||
useManageRoomSharedKey,
|
||||
useIsRoomE2EE,
|
||||
useEnableEmbeddedE2EE,
|
||||
useEnableE2EE,
|
||||
} from "../e2ee/sharedKeyManagement";
|
||||
import { useEnableE2EE } from "../settings/useSetting";
|
||||
import { useEnableSPAE2EE } from "../settings/useSetting";
|
||||
import { E2EEConfig, E2EEMode } from "../livekit/useLiveKit";
|
||||
import { useUrlParams } from "../UrlParams";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -71,6 +75,10 @@ export function GroupCallView({
|
||||
|
||||
const e2eeSharedKey = useManageRoomSharedKey(rtcSession.room.roomId);
|
||||
const isRoomE2EE = useIsRoomE2EE(rtcSession.room.roomId);
|
||||
const [spaE2EEEnabled] = useEnableSPAE2EE();
|
||||
const embeddedE2EEEnabled = useEnableEmbeddedE2EE();
|
||||
const e2eeEnabled = useEnableE2EE();
|
||||
const { perParticipantE2EE } = useUrlParams();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -153,7 +161,7 @@ export function GroupCallView({
|
||||
}
|
||||
}
|
||||
|
||||
enterRTCSession(rtcSession);
|
||||
enterRTCSession(rtcSession, embeddedE2EEEnabled);
|
||||
|
||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||
// we only have room sessions right now, so call ID is the emprty string - we use the room ID
|
||||
@@ -172,18 +180,18 @@ export function GroupCallView({
|
||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
};
|
||||
}
|
||||
}, [rtcSession, preload]);
|
||||
}, [rtcSession, preload, embeddedE2EEEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmbedded && !preload) {
|
||||
// In embedded mode, bypass the lobby and just enter the call straight away
|
||||
enterRTCSession(rtcSession);
|
||||
enterRTCSession(rtcSession, embeddedE2EEEnabled);
|
||||
|
||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||
// use the room ID as above
|
||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||
}
|
||||
}, [rtcSession, isEmbedded, preload]);
|
||||
}, [rtcSession, isEmbedded, preload, embeddedE2EEEnabled]);
|
||||
|
||||
const [left, setLeft] = useState(false);
|
||||
const [leaveError, setLeaveError] = useState<Error | undefined>(undefined);
|
||||
@@ -237,20 +245,21 @@ export function GroupCallView({
|
||||
}
|
||||
}, [isJoined, rtcSession]);
|
||||
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
|
||||
const e2eeConfig = useMemo(
|
||||
() => (e2eeSharedKey ? { sharedKey: e2eeSharedKey } : undefined),
|
||||
[e2eeSharedKey]
|
||||
);
|
||||
const e2eeConfig = useMemo((): E2EEConfig | undefined => {
|
||||
if (perParticipantE2EE) {
|
||||
return { mode: E2EEMode.PerParticipantKey };
|
||||
} else if (e2eeSharedKey) {
|
||||
return { mode: E2EEMode.SharedKey, sharedKey: e2eeSharedKey };
|
||||
}
|
||||
}, [perParticipantE2EE, e2eeSharedKey]);
|
||||
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
setLeaveError(undefined);
|
||||
enterRTCSession(rtcSession);
|
||||
}, [rtcSession]);
|
||||
enterRTCSession(rtcSession, embeddedE2EEEnabled);
|
||||
}, [rtcSession, embeddedE2EEEnabled]);
|
||||
|
||||
if (e2eeEnabled && isRoomE2EE && !e2eeSharedKey) {
|
||||
if (spaE2EEEnabled && isRoomE2EE && !e2eeSharedKey) {
|
||||
return (
|
||||
<ErrorView
|
||||
error={
|
||||
@@ -319,7 +328,7 @@ export function GroupCallView({
|
||||
<LobbyView
|
||||
matrixInfo={matrixInfo}
|
||||
muteStates={muteStates}
|
||||
onEnter={() => enterRTCSession(rtcSession)}
|
||||
onEnter={() => enterRTCSession(rtcSession, embeddedE2EEEnabled)}
|
||||
isEmbedded={isEmbedded}
|
||||
hideHeader={hideHeader}
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,7 @@ import type { Room } from "matrix-js-sdk/src/models/room";
|
||||
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { setLocalStorageItem } from "../useLocalStorage";
|
||||
import { isLocalRoomId, createRoom, roomNameFromRoomId } from "../matrix-utils";
|
||||
import { useEnableE2EE } from "../settings/useSetting";
|
||||
import { useEnableSPAE2EE } from "../settings/useSetting";
|
||||
import { getRoomSharedKeyLocalStorageKey } from "../e2ee/sharedKeyManagement";
|
||||
|
||||
export type GroupCallLoaded = {
|
||||
@@ -62,7 +62,7 @@ export const useLoadGroupCall = (
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState<GroupCallStatus>({ kind: "loading" });
|
||||
|
||||
const [e2eeEnabled] = useEnableE2EE();
|
||||
const [e2eeEnabled] = useEnableSPAE2EE();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||
|
||||
@@ -33,7 +33,10 @@ function makeFocus(livekitAlias: string): LivekitFocus {
|
||||
};
|
||||
}
|
||||
|
||||
export function enterRTCSession(rtcSession: MatrixRTCSession) {
|
||||
export function enterRTCSession(
|
||||
rtcSession: MatrixRTCSession,
|
||||
encryptMedia: boolean
|
||||
) {
|
||||
PosthogAnalytics.instance.eventCallEnded.cacheStartCall(new Date());
|
||||
PosthogAnalytics.instance.eventCallStarted.track(rtcSession.room.roomId);
|
||||
|
||||
@@ -44,7 +47,7 @@ export function enterRTCSession(rtcSession: MatrixRTCSession) {
|
||||
// right now we asume everything is a room-scoped call
|
||||
const livekitAlias = rtcSession.room.roomId;
|
||||
|
||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)]);
|
||||
rtcSession.joinRoomSession([makeFocus(livekitAlias)], encryptMedia);
|
||||
}
|
||||
|
||||
export function leaveRTCSession(rtcSession: MatrixRTCSession) {
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
useOptInAnalytics,
|
||||
useDeveloperSettingsTab,
|
||||
useShowConnectionStats,
|
||||
useEnableE2EE,
|
||||
useEnableSPAE2EE,
|
||||
} from "./useSetting";
|
||||
import { FieldRow, InputField } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
@@ -69,7 +69,7 @@ export const SettingsModal = (props: Props) => {
|
||||
useDeveloperSettingsTab();
|
||||
const [showConnectionStats, setShowConnectionStats] =
|
||||
useShowConnectionStats();
|
||||
const [enableE2EE, setEnableE2EE] = useEnableE2EE();
|
||||
const [enableE2EE, setEnableE2EE] = useEnableSPAE2EE();
|
||||
|
||||
const downloadDebugLog = useDownloadDebugLog();
|
||||
|
||||
|
||||
@@ -89,14 +89,15 @@ export const useOptInAnalytics = (): DisableableSetting<boolean | null> => {
|
||||
return [false, null];
|
||||
};
|
||||
|
||||
export const useEnableE2EE = (): DisableableSetting<boolean | null> => {
|
||||
export const useEnableSPAE2EE = (): DisableableSetting<boolean | null> => {
|
||||
const settingVal = useSetting<boolean | null>(
|
||||
"enable-end-to-end-encryption",
|
||||
false
|
||||
);
|
||||
if (isE2EESupported()) return settingVal;
|
||||
|
||||
return [false, null];
|
||||
if (!isE2EESupported()) return [false, null];
|
||||
|
||||
return settingVal;
|
||||
};
|
||||
|
||||
export const useDeveloperSettingsTab = () =>
|
||||
|
||||
@@ -79,6 +79,8 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
logger.info("Widget API is available");
|
||||
const api = new WidgetApi(widgetId, parentOrigin);
|
||||
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
|
||||
api.requestCapabilityToSendEvent(EventType.CallEncryptionPrefix);
|
||||
api.requestCapabilityToReceiveEvent(EventType.CallEncryptionPrefix);
|
||||
|
||||
// Set up the lazy action emitter, but only for select actions that we
|
||||
// intend for the app to handle
|
||||
|
||||
Reference in New Issue
Block a user