mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-21 00:28:08 +08:00
Merge pull request #1166 from vector-im/dbkr/openid
Support for getting SFU config using OIDC
This commit is contained in:
commit
c9f57cb2a3
@ -6,8 +6,7 @@
|
||||
}
|
||||
},
|
||||
"livekit": {
|
||||
"server_url": "wss://sfu.call.element.dev",
|
||||
"jwt_service_url": "https://voip-sip-poc.element.io/lk/jwt_service"
|
||||
"livekit_service_url": "https://lk-jwt-service.lab.element.dev"
|
||||
},
|
||||
"posthog": {
|
||||
"api_key": "phc_rXGHx9vDmyEvyRxPziYtdVIv0ahEv8A9uLWFcCi1WcU",
|
||||
|
@ -55,10 +55,8 @@ export interface ConfigOptions {
|
||||
|
||||
// Describes the LiveKit configuration to be used.
|
||||
livekit?: {
|
||||
// The LiveKit server URL to connect to.
|
||||
server_url: string;
|
||||
// The link to the service that generates JWT tokens to join LiveKit rooms.
|
||||
jwt_service_url: string;
|
||||
// The link to the service that returns a livekit url and token to use it
|
||||
livekit_service_url: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
96
src/livekit/OpenIDLoader.tsx
Normal file
96
src/livekit/OpenIDLoader.tsx
Normal 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
54
src/livekit/openIDSFU.ts
Normal 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();
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import { useLiveKitRoom, useToken } from "@livekit/components-react";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { defaultLiveKitOptions } from "./options";
|
||||
import { SFUConfig } from "./openIDSFU";
|
||||
|
||||
export type UserChoices = {
|
||||
audio?: DeviceChoices;
|
||||
@ -14,29 +15,10 @@ export type DeviceChoices = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
export type LiveKitConfig = {
|
||||
sfuUrl: string;
|
||||
jwtUrl: string;
|
||||
roomName: string;
|
||||
userDisplayName: string;
|
||||
userIdentity: string;
|
||||
};
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
config: LiveKitConfig
|
||||
sfuConfig: SFUConfig
|
||||
): 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 options = defaultLiveKitOptions;
|
||||
options.videoCaptureDefaults = {
|
||||
@ -51,8 +33,8 @@ export function useLiveKit(
|
||||
}, [userChoices.video, userChoices.audio]);
|
||||
|
||||
const { room } = useLiveKitRoom({
|
||||
token,
|
||||
serverUrl: config.sfuUrl,
|
||||
token: sfuConfig.jwt,
|
||||
serverUrl: sfuConfig.url,
|
||||
audio: userChoices.audio?.enabled ?? false,
|
||||
video: userChoices.video?.enabled ?? false,
|
||||
options: roomOptions,
|
||||
|
@ -28,13 +28,15 @@ import { useGroupCall } from "./useGroupCall";
|
||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||
import { LobbyView } from "./LobbyView";
|
||||
import { MatrixInfo } from "./VideoPreview";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
import { CallEndedView } from "./CallEndedView";
|
||||
import { useSentryGroupCallHandler } from "./useSentryGroupCallHandler";
|
||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { UserChoices } from "../livekit/useLiveKit";
|
||||
import { findDeviceByName } from "../media-utils";
|
||||
import { OpenIDLoader } from "../livekit/OpenIDLoader";
|
||||
import { ActiveCall } from "./InCallView";
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -218,21 +220,33 @@ export function GroupCallView({
|
||||
undefined
|
||||
);
|
||||
|
||||
const lkServiceURL = Config.get().livekit?.livekit_service_url;
|
||||
|
||||
if (!lkServiceURL) {
|
||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
} else if (state === GroupCallState.Entered && userChoices) {
|
||||
return (
|
||||
<ActiveCall
|
||||
groupCall={groupCall}
|
||||
<OpenIDLoader
|
||||
client={client}
|
||||
participants={participants}
|
||||
onLeave={onLeave}
|
||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||
hideHeader={hideHeader}
|
||||
matrixInfo={matrixInfo}
|
||||
userChoices={userChoices}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
/>
|
||||
livekitServiceURL={lkServiceURL}
|
||||
roomName={matrixInfo.roomName}
|
||||
>
|
||||
<ActiveCall
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
participants={participants}
|
||||
onLeave={onLeave}
|
||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||
hideHeader={hideHeader}
|
||||
matrixInfo={matrixInfo}
|
||||
userChoices={userChoices}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
/>
|
||||
</OpenIDLoader>
|
||||
);
|
||||
} else if (left) {
|
||||
// The call ended view is shown for two reasons: prompting guests to create
|
||||
|
@ -72,7 +72,6 @@ import { MatrixInfo } from "./VideoPreview";
|
||||
import { useJoinRule } from "./useJoinRule";
|
||||
import { ParticipantInfo } from "./useGroupCall";
|
||||
import { ItemData, TileContent } from "../video-grid/VideoTile";
|
||||
import { Config } from "../config/Config";
|
||||
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
@ -84,6 +83,7 @@ import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||
import { useMediaDevices } from "../livekit/useMediaDevices";
|
||||
import { useFullscreen } from "./useFullscreen";
|
||||
import { useLayoutStates } from "../video-grid/Layout";
|
||||
import { useSFUConfig } from "../livekit/OpenIDLoader";
|
||||
|
||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||
// 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.
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
interface ActiveCallProps extends Omit<Props, "livekitRoom"> {
|
||||
export interface ActiveCallProps extends Omit<InCallViewProps, "livekitRoom"> {
|
||||
userChoices: UserChoices;
|
||||
}
|
||||
|
||||
export function ActiveCall(props: ActiveCallProps) {
|
||||
const livekitRoom = useLiveKit(props.userChoices, {
|
||||
sfuUrl: Config.get().livekit!.server_url,
|
||||
jwtUrl: `${Config.get().livekit!.jwt_service_url}/token`,
|
||||
roomName: props.matrixInfo.roomName,
|
||||
userDisplayName: props.matrixInfo.displayName,
|
||||
userIdentity: `${props.client.getUserId()}:${props.client.getDeviceId()}`,
|
||||
});
|
||||
const sfuConfig = useSFUConfig();
|
||||
const livekitRoom = useLiveKit(props.userChoices, sfuConfig);
|
||||
|
||||
return (
|
||||
livekitRoom && (
|
||||
@ -113,7 +108,7 @@ export function ActiveCall(props: ActiveCallProps) {
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
export interface InCallViewProps {
|
||||
client: MatrixClient;
|
||||
groupCall: GroupCall;
|
||||
livekitRoom: Room;
|
||||
@ -135,7 +130,7 @@ export function InCallView({
|
||||
hideHeader,
|
||||
matrixInfo,
|
||||
otelGroupCallMembership,
|
||||
}: Props) {
|
||||
}: InCallViewProps) {
|
||||
const { t } = useTranslation();
|
||||
usePreventScroll();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user