From aab90650f1dda8e86df2fd01a9ee8d20e4221b5a Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Fri, 15 May 2026 18:38:22 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20synchroniser=20for?= =?UTF-8?q?=20room=20metadata=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Listen to room metadata change events and synchronize the React Query cache with the latest room data fetched from the API. This ensures clients react to live configuration updates, such as showing or hiding mute controls when `everyone_can_mute` changes. --- .../livekit/hooks/useSyncLiveKitMetadata.ts | 76 +++++++++++++++++++ .../rooms/livekit/prefabs/VideoConference.tsx | 2 + 2 files changed, 78 insertions(+) create mode 100644 src/frontend/src/features/rooms/livekit/hooks/useSyncLiveKitMetadata.ts diff --git a/src/frontend/src/features/rooms/livekit/hooks/useSyncLiveKitMetadata.ts b/src/frontend/src/features/rooms/livekit/hooks/useSyncLiveKitMetadata.ts new file mode 100644 index 00000000..154af313 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/hooks/useSyncLiveKitMetadata.ts @@ -0,0 +1,76 @@ +// features/rooms/hooks/useSyncLiveKitMetadata.ts + +import { useEffect } from 'react' +import { RoomEvent } from 'livekit-client' +import { queryClient } from '@/api/queryClient' +import { keys } from '@/api/queryKeys' +import type { ApiRoom, RoomConfiguration } from '@/features/rooms/api/ApiRoom' +import { useRoomContext } from '@livekit/components-react' +import { useRoomData } from './useRoomData' + +/** + * Shape of the LiveKit room metadata blob pushed by the backend. + * Matches RoomManagement.update_metadata → {"configuration": room.configuration} + */ +type RoomLiveKitMetadata = { + configuration?: RoomConfiguration +} + +const parseMetadata = (raw: string | undefined): RoomLiveKitMetadata | null => { + if (!raw) return null + try { + return JSON.parse(raw) as RoomLiveKitMetadata + } catch { + console.warn('useSyncLiveKitMetadata: failed to parse room metadata') + return null + } +} + +/** + * Sync LiveKit room metadata into the React Query cache. + * + * The backend pushes room configuration into LiveKit's room metadata + * whenever it changes. This hook listens for those changes and patches + * the ApiRoom cache so every `useRoomData()` + * consumer sees the fresh value automatically. + * + * Mount once, at the level where the LiveKit Room instance lives. + */ +export const useSyncLiveKitMetadata = () => { + const room = useRoomContext() + const roomData = useRoomData() + const roomSlug = roomData?.slug + + useEffect(() => { + if (!room || !roomSlug) return + + const applyMetadata = (raw: string | undefined) => { + const parsed = parseMetadata(raw) + // No configuration key → leave the cache alone. Don't clobber the + // value we already loaded from the API with `undefined`. + if (!parsed?.configuration) return + + queryClient.setQueryData([keys.room, roomSlug], (prev) => { + if (!prev) return prev + if ( + JSON.stringify(prev.configuration) === + JSON.stringify(parsed.configuration) + ) { + return prev + } + return { ...prev, configuration: parsed.configuration } + }) + } + + // Apply whatever metadata is currently set (covers the case where we + // joined the room AFTER the last metadata change, so no event will fire). + applyMetadata(room.metadata) + + const handler = (raw: string) => applyMetadata(raw) + room.on(RoomEvent.RoomMetadataChanged, handler) + + return () => { + room.off(RoomEvent.RoomMetadataChanged, handler) + } + }, [room, roomSlug]) +} diff --git a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx index c81dfc68..fb5b763a 100644 --- a/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx +++ b/src/frontend/src/features/rooms/livekit/prefabs/VideoConference.tsx @@ -32,6 +32,7 @@ import { useRegisterKeyboardShortcut } from '@/features/shortcuts/useRegisterKey import { useSettingsDialog } from '@/features/settings' import { SettingsDialogExtendedKey } from '@/features/settings/type' import { useVideoResolutionSubscription } from '../hooks/useVideoResolutionSubscription' +import { useSyncLiveKitMetadata } from '../hooks/useSyncLiveKitMetadata' import { SettingsDialogProvider } from '@/features/settings/components/SettingsDialogProvider' import { IsIdleDisconnectModal } from '../components/IsIdleDisconnectModal' import { getParticipantName } from '@/features/rooms/utils/getParticipantName' @@ -90,6 +91,7 @@ export function VideoConference({ ...props }: VideoConferenceProps) { useConnectionObserver() useRoomPageTitle() useVideoResolutionSubscription() + useSyncLiveKitMetadata() useRegisterKeyboardShortcut({ id: 'open-shortcuts',