feat: add call management features with useCall and useRoomCallMembers hooks
- Implemented useCall hook for managing call state and actions. - Created useRoomCallMembers hook to track active call members in a room. - Added useRtcConfig for fetching RTC configurations and LiveKit JWT. - Developed Audio settings component for managing audio devices and settings. - Introduced device selection and screen sharing options in the Audio settings. - Persisted audio settings in localStorage for user preferences.
This commit is contained in:
170
src/app/features/call/useRtcConfig.ts
Normal file
170
src/app/features/call/useRtcConfig.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { LiveKitFocus, LiveKitJWTResponse, OpenIdToken, WellKnownWithRTC } from './types';
|
||||
|
||||
/**
|
||||
* Fetches the LiveKit focus configuration from well-known data
|
||||
* @param wellKnown - The well-known response that may contain RTC foci
|
||||
* @returns The LiveKit focus configuration if available
|
||||
*/
|
||||
export function getLiveKitFocus(wellKnown: WellKnownWithRTC): LiveKitFocus | undefined {
|
||||
const rtcFoci = wellKnown['org.matrix.msc4143.rtc_foci'];
|
||||
if (!rtcFoci || !Array.isArray(rtcFoci)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return rtcFoci.find((focus): focus is LiveKitFocus => focus.type === 'livekit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the well-known client configuration from a homeserver
|
||||
* @param baseUrl - The homeserver base URL
|
||||
* @returns The well-known response with RTC configuration
|
||||
*/
|
||||
export async function fetchWellKnownWithRTC(baseUrl: string): Promise<WellKnownWithRTC> {
|
||||
const wellKnownUrl = `${baseUrl}/.well-known/matrix/client`;
|
||||
|
||||
const response = await fetch(wellKnownUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch well-known: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an OpenID token from the Matrix homeserver
|
||||
* This token can be used to authenticate with third-party services
|
||||
* @param homeserverBaseUrl - The Matrix homeserver base URL
|
||||
* @param userId - The user's Matrix ID
|
||||
* @param accessToken - The Matrix access token
|
||||
* @returns The OpenID token response
|
||||
*/
|
||||
export async function requestOpenIdToken(
|
||||
homeserverBaseUrl: string,
|
||||
userId: string,
|
||||
accessToken: string
|
||||
): Promise<OpenIdToken> {
|
||||
const url = `${homeserverBaseUrl}/_matrix/client/v3/user/${encodeURIComponent(userId)}/openid/request_token`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to request OpenID token: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a LiveKit JWT for joining a call using OpenID token authentication
|
||||
* @param livekitServiceUrl - The LiveKit service URL from well-known or config
|
||||
* @param roomId - The Matrix room ID to join
|
||||
* @param userId - The Matrix user ID
|
||||
* @param deviceId - The Matrix device ID
|
||||
* @param openIdToken - The OpenID token from the Matrix homeserver
|
||||
* @returns The LiveKit JWT and server URL
|
||||
*/
|
||||
export async function fetchLiveKitJWT(
|
||||
livekitServiceUrl: string,
|
||||
roomId: string,
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
openIdToken: OpenIdToken
|
||||
): Promise<LiveKitJWTResponse> {
|
||||
// Generate a unique member ID for this participant (used for identity, not room assignment)
|
||||
const memberId = `${deviceId}_${Date.now()}`;
|
||||
|
||||
// slot_id must be constant so all participants in the same Matrix room
|
||||
// get assigned to the same LiveKit room. The JWT service hashes room_id + slot_id
|
||||
// to create the LiveKit room name.
|
||||
const slotId = 'm.call';
|
||||
|
||||
const response = await fetch(livekitServiceUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
openid_token: openIdToken,
|
||||
room_id: roomId,
|
||||
slot_id: slotId,
|
||||
member: {
|
||||
id: memberId,
|
||||
claimed_user_id: userId,
|
||||
claimed_device_id: deviceId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Failed to fetch LiveKit JWT: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to get RTC configuration functions
|
||||
* Provides utilities for fetching well-known data and LiveKit JWTs
|
||||
*/
|
||||
export function useRtcConfig() {
|
||||
const mx = useMatrixClient();
|
||||
|
||||
const getWellKnown = useCallback(async (): Promise<WellKnownWithRTC> => {
|
||||
const baseUrl = mx.getHomeserverUrl();
|
||||
return fetchWellKnownWithRTC(baseUrl);
|
||||
}, [mx]);
|
||||
|
||||
const getLiveKitJWT = useCallback(
|
||||
async (roomId: string): Promise<LiveKitJWTResponse | null> => {
|
||||
const baseUrl = mx.getHomeserverUrl();
|
||||
const accessToken = mx.getAccessToken();
|
||||
const deviceId = mx.getDeviceId();
|
||||
const userId = mx.getSafeUserId();
|
||||
|
||||
if (!accessToken || !deviceId) {
|
||||
throw new Error('Missing access token or device ID');
|
||||
}
|
||||
|
||||
const wellKnown = await fetchWellKnownWithRTC(baseUrl);
|
||||
const livekitFocus = getLiveKitFocus(wellKnown);
|
||||
|
||||
if (!livekitFocus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get OpenID token from homeserver
|
||||
const openIdToken = await requestOpenIdToken(baseUrl, userId, accessToken);
|
||||
|
||||
// Exchange for LiveKit JWT
|
||||
return fetchLiveKitJWT(livekitFocus.livekit_service_url, roomId, userId, deviceId, openIdToken);
|
||||
},
|
||||
[mx]
|
||||
);
|
||||
|
||||
const isCallSupported = useCallback(async (): Promise<boolean> => {
|
||||
try {
|
||||
const baseUrl = mx.getHomeserverUrl();
|
||||
const wellKnown = await fetchWellKnownWithRTC(baseUrl);
|
||||
return getLiveKitFocus(wellKnown) !== undefined;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, [mx]);
|
||||
|
||||
return {
|
||||
getWellKnown,
|
||||
getLiveKitJWT,
|
||||
isCallSupported,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user