Revert "Revert "Support for getting SFU config using OIDC""

This commit is contained in:
David Baker 2023-07-05 13:12:37 +01:00 committed by GitHub
parent 0bcec1efbd
commit 130b70756a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 189 additions and 51 deletions

View File

@ -6,8 +6,7 @@
} }
}, },
"livekit": { "livekit": {
"server_url": "wss://sfu.call.element.dev", "livekit_service_url": "https://lk-jwt-service.lab.element.dev"
"jwt_service_url": "https://voip-sip-poc.element.io/lk/jwt_service"
}, },
"posthog": { "posthog": {
"api_key": "phc_rXGHx9vDmyEvyRxPziYtdVIv0ahEv8A9uLWFcCi1WcU", "api_key": "phc_rXGHx9vDmyEvyRxPziYtdVIv0ahEv8A9uLWFcCi1WcU",

View File

@ -55,10 +55,8 @@ export interface ConfigOptions {
// Describes the LiveKit configuration to be used. // Describes the LiveKit configuration to be used.
livekit?: { livekit?: {
// The LiveKit server URL to connect to. // The link to the service that returns a livekit url and token to use it
server_url: string; livekit_service_url: string;
// The link to the service that generates JWT tokens to join LiveKit rooms.
jwt_service_url: string;
}; };
/** /**

View File

@ -0,0 +1,96 @@
/*
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 {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from "react";
import { logger } from "matrix-js-sdk/src/logger";
import {
OpenIDClientParts,
SFUConfig,
getSFUConfigWithOpenID,
} from "./openIDSFU";
import { ErrorView, LoadingView } from "../FullScreenView";
interface Props {
client: OpenIDClientParts;
livekitServiceURL: string;
roomName: string;
children: ReactNode;
}
const SFUConfigContext = createContext<SFUConfig>(undefined);
export const useSFUConfig = () => useContext(SFUConfigContext);
export function OpenIDLoader({
client,
livekitServiceURL,
roomName,
children,
}: Props) {
const [state, setState] = useState<
SFUConfigLoading | SFUConfigLoaded | SFUConfigFailed
>({ kind: "loading" });
useEffect(() => {
(async () => {
try {
const result = await getSFUConfigWithOpenID(
client,
livekitServiceURL,
roomName
);
setState({ kind: "loaded", sfuConfig: result });
} catch (e) {
logger.error("Failed to fetch SFU config: ", e);
setState({ kind: "failed", error: e });
}
})();
}, [client, livekitServiceURL, roomName]);
switch (state.kind) {
case "loading":
return <LoadingView />;
case "failed":
return <ErrorView error={state.error} />;
case "loaded":
return (
<SFUConfigContext.Provider value={state.sfuConfig}>
{children}
</SFUConfigContext.Provider>
);
}
}
type SFUConfigLoading = {
kind: "loading";
};
type SFUConfigLoaded = {
kind: "loaded";
sfuConfig: SFUConfig;
};
type SFUConfigFailed = {
kind: "failed";
error: Error;
};

54
src/livekit/openIDSFU.ts Normal file
View File

@ -0,0 +1,54 @@
/*
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 { MatrixClient } from "matrix-js-sdk";
import { logger } from "matrix-js-sdk/src/logger";
export interface SFUConfig {
url: string;
jwt: string;
}
// The bits we need from MatrixClient
export type OpenIDClientParts = Pick<
MatrixClient,
"getOpenIdToken" | "getDeviceId"
>;
export async function getSFUConfigWithOpenID(
client: OpenIDClientParts,
livekitServiceURL: string,
roomName: string
): Promise<SFUConfig> {
const openIdToken = await client.getOpenIdToken();
logger.debug("Got openID token", openIdToken);
const res = await fetch(livekitServiceURL + "/sfu/get", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
room: roomName,
openid_token: openIdToken,
device_id: client.getDeviceId(),
}),
});
if (!res.ok) {
throw new Error("SFO Config fetch failed with status code " + res.status);
}
return await res.json();
}

View File

@ -1,8 +1,9 @@
import { Room, RoomOptions } from "livekit-client"; import { Room, RoomOptions } from "livekit-client";
import { useLiveKitRoom, useToken } from "@livekit/components-react"; import { useLiveKitRoom } from "@livekit/components-react";
import { useMemo } from "react"; import { useMemo } from "react";
import { defaultLiveKitOptions } from "./options"; import { defaultLiveKitOptions } from "./options";
import { SFUConfig } from "./openIDSFU";
export type UserChoices = { export type UserChoices = {
audio?: DeviceChoices; audio?: DeviceChoices;
@ -14,29 +15,10 @@ export type DeviceChoices = {
enabled: boolean; enabled: boolean;
}; };
export type LiveKitConfig = {
sfuUrl: string;
jwtUrl: string;
roomName: string;
userDisplayName: string;
userIdentity: string;
};
export function useLiveKit( export function useLiveKit(
userChoices: UserChoices, userChoices: UserChoices,
config: LiveKitConfig sfuConfig: SFUConfig
): Room | undefined { ): Room | undefined {
const tokenOptions = useMemo(
() => ({
userInfo: {
name: config.userDisplayName,
identity: config.userIdentity,
},
}),
[config.userDisplayName, config.userIdentity]
);
const token = useToken(config.jwtUrl, config.roomName, tokenOptions);
const roomOptions = useMemo((): RoomOptions => { const roomOptions = useMemo((): RoomOptions => {
const options = defaultLiveKitOptions; const options = defaultLiveKitOptions;
options.videoCaptureDefaults = { options.videoCaptureDefaults = {
@ -51,8 +33,8 @@ export function useLiveKit(
}, [userChoices.video, userChoices.audio]); }, [userChoices.video, userChoices.audio]);
const { room } = useLiveKitRoom({ const { room } = useLiveKitRoom({
token, token: sfuConfig.jwt,
serverUrl: config.sfuUrl, serverUrl: sfuConfig.url,
audio: userChoices.audio?.enabled ?? false, audio: userChoices.audio?.enabled ?? false,
video: userChoices.video?.enabled ?? false, video: userChoices.video?.enabled ?? false,
options: roomOptions, options: roomOptions,

View File

@ -28,13 +28,15 @@ import { useGroupCall } from "./useGroupCall";
import { ErrorView, FullScreenView } from "../FullScreenView"; import { ErrorView, FullScreenView } from "../FullScreenView";
import { LobbyView } from "./LobbyView"; import { LobbyView } from "./LobbyView";
import { MatrixInfo } from "./VideoPreview"; import { MatrixInfo } from "./VideoPreview";
import { ActiveCall } from "./InCallView";
import { CallEndedView } from "./CallEndedView"; import { CallEndedView } from "./CallEndedView";
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler"; import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
import { useProfile } from "../profile/useProfile"; import { useProfile } from "../profile/useProfile";
import { UserChoices } from "../livekit/useLiveKit"; import { UserChoices } from "../livekit/useLiveKit";
import { findDeviceByName } from "../media-utils"; import { findDeviceByName } from "../media-utils";
import { OpenIDLoader } from "../livekit/OpenIDLoader";
import { ActiveCall } from "./InCallView";
import { Config } from "../config/Config";
declare global { declare global {
interface Window { interface Window {
@ -218,21 +220,33 @@ export function GroupCallView({
undefined undefined
); );
const lkServiceURL = Config.get().livekit?.livekit_service_url;
if (!lkServiceURL) {
return <ErrorView error={new Error("No livekit_service_url defined")} />;
}
if (error) { if (error) {
return <ErrorView error={error} />; return <ErrorView error={error} />;
} else if (state === GroupCallState.Entered && userChoices) { } else if (state === GroupCallState.Entered && userChoices) {
return ( return (
<ActiveCall <OpenIDLoader
groupCall={groupCall}
client={client} client={client}
participants={participants} livekitServiceURL={lkServiceURL}
onLeave={onLeave} roomName={matrixInfo.roomName}
unencryptedEventsFromUsers={unencryptedEventsFromUsers} >
hideHeader={hideHeader} <ActiveCall
matrixInfo={matrixInfo} client={client}
userChoices={userChoices} groupCall={groupCall}
otelGroupCallMembership={otelGroupCallMembership} participants={participants}
/> onLeave={onLeave}
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
hideHeader={hideHeader}
matrixInfo={matrixInfo}
userChoices={userChoices}
otelGroupCallMembership={otelGroupCallMembership}
/>
</OpenIDLoader>
); );
} else if (left) { } else if (left) {
// The call ended view is shown for two reasons: prompting guests to create // The call ended view is shown for two reasons: prompting guests to create

View File

@ -72,7 +72,6 @@ import { MatrixInfo } from "./VideoPreview";
import { useJoinRule } from "./useJoinRule"; import { useJoinRule } from "./useJoinRule";
import { ParticipantInfo } from "./useGroupCall"; import { ParticipantInfo } from "./useGroupCall";
import { ItemData, TileContent } from "../video-grid/VideoTile"; import { ItemData, TileContent } from "../video-grid/VideoTile";
import { Config } from "../config/Config";
import { NewVideoGrid } from "../video-grid/NewVideoGrid"; import { NewVideoGrid } from "../video-grid/NewVideoGrid";
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership"; import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
import { SettingsModal } from "../settings/SettingsModal"; import { SettingsModal } from "../settings/SettingsModal";
@ -84,6 +83,7 @@ import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
import { useMediaDevices } from "../livekit/useMediaDevices"; import { useMediaDevices } from "../livekit/useMediaDevices";
import { useFullscreen } from "./useFullscreen"; import { useFullscreen } from "./useFullscreen";
import { useLayoutStates } from "../video-grid/Layout"; import { useLayoutStates } from "../video-grid/Layout";
import { useSFUConfig } from "../livekit/OpenIDLoader";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// There is currently a bug in Safari our our code with cloning and sending MediaStreams // There is currently a bug in Safari our our code with cloning and sending MediaStreams
@ -91,18 +91,13 @@ const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
// For now we can disable screensharing in Safari. // For now we can disable screensharing in Safari.
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
interface ActiveCallProps extends Omit<Props, "livekitRoom"> { export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
userChoices: UserChoices; userChoices: UserChoices;
} }
export function ActiveCall(props: ActiveCallProps) { export function ActiveCall(props: ActiveCallProps) {
const livekitRoom = useLiveKit(props.userChoices, { const sfuConfig = useSFUConfig();
sfuUrl: Config.get().livekit!.server_url, const livekitRoom = useLiveKit(props.userChoices, sfuConfig);
jwtUrl: `${Config.get().livekit!.jwt_service_url}/token`,
roomName: props.matrixInfo.roomName,
userDisplayName: props.matrixInfo.displayName,
userIdentity: `${props.client.getUserId()}:${props.client.getDeviceId()}`,
});
return ( return (
livekitRoom && ( livekitRoom && (
@ -113,7 +108,7 @@ export function ActiveCall(props: ActiveCallProps) {
); );
} }
interface Props { export interface InCallViewProps {
client: MatrixClient; client: MatrixClient;
groupCall: GroupCall; groupCall: GroupCall;
livekitRoom: Room; livekitRoom: Room;
@ -135,7 +130,7 @@ export function InCallView({
hideHeader, hideHeader,
matrixInfo, matrixInfo,
otelGroupCallMembership, otelGroupCallMembership,
}: Props) { }: InCallViewProps) {
const { t } = useTranslation(); const { t } = useTranslation();
usePreventScroll(); usePreventScroll();