diff --git a/.changeset/pink-paths-pick.md b/.changeset/pink-paths-pick.md new file mode 100644 index 000000000..50deda0bf --- /dev/null +++ b/.changeset/pink-paths-pick.md @@ -0,0 +1,5 @@ +--- +'@livekit/components-react': patch +--- + +Update LiveKitRoom connection logic to be more robust diff --git a/packages/react/etc/components-react.api.md b/packages/react/etc/components-react.api.md index acdf30abf..7df303821 100644 --- a/packages/react/etc/components-react.api.md +++ b/packages/react/etc/components-react.api.md @@ -375,6 +375,7 @@ export const LiveKitRoom: (props: React_2.PropsWithChildren & export interface LiveKitRoomProps extends Omit, 'onError'> { audio?: AudioCaptureOptions | boolean; connect?: boolean; + connectionSideEffect?: (room: Room) => Promise; connectOptions?: RoomConnectOptions; // @internal (undocumented) featureFlags?: FeatureFlags; diff --git a/packages/react/src/components/LiveKitRoom.tsx b/packages/react/src/components/LiveKitRoom.tsx index 23234dc12..2f0bf7539 100644 --- a/packages/react/src/components/LiveKitRoom.tsx +++ b/packages/react/src/components/LiveKitRoom.tsx @@ -78,6 +78,12 @@ export interface LiveKitRoomProps extends Omit Promise; + /** * @internal */ diff --git a/packages/react/src/hooks/useLiveKitRoom.ts b/packages/react/src/hooks/useLiveKitRoom.ts index 0e01d7be7..bf41ba25b 100644 --- a/packages/react/src/hooks/useLiveKitRoom.ts +++ b/packages/react/src/hooks/useLiveKitRoom.ts @@ -1,6 +1,6 @@ import { log, setupLiveKitRoom } from '@livekit/components-core'; import type { DisconnectReason } from 'livekit-client'; -import { Room, MediaDeviceFailure, RoomEvent } from 'livekit-client'; +import { Room, MediaDeviceFailure, RoomEvent, Mutex } from 'livekit-client'; import * as React from 'react'; import type { HTMLAttributes } from 'react'; @@ -47,6 +47,7 @@ export function useLiveKitRoom( onMediaDeviceFailure, onEncryptionError, simulateParticipants, + connectionSideEffect, ...rest } = { ...defaultRoomProps, ...props }; if (options && passedRoom) { @@ -84,7 +85,7 @@ export function useLiveKitRoom( }); }; - const handleMediaDeviceError = (e: Error, kind: MediaDeviceKind) => { + const handleMediaDeviceError = (e: Error, kind?: MediaDeviceKind) => { const mediaDeviceFailure = MediaDeviceFailure.getFailure(e); onMediaDeviceFailure?.(mediaDeviceFailure, kind); }; @@ -125,6 +126,8 @@ export function useLiveKitRoom( onDisconnected, ]); + const connectDisconnectLock = React.useMemo(() => new Mutex(), []); + React.useEffect(() => { if (!room) return; @@ -153,16 +156,31 @@ export function useLiveKitRoom( onError?.(Error('no livekit url provided')); return; } - room.connect(serverUrl, token, connectOptions).catch((e) => { - log.warn(e); - if (shouldConnect.current === true) { - onError?.(e as Error); + + connectDisconnectLock.lock().then(async (unlock) => { + try { + const connectionPromise = room.connect(serverUrl, token, connectOptions); + if (connectionSideEffect) { + await Promise.all([connectionPromise, connectionSideEffect]); + } else { + await connectionPromise; + } + } catch (e) { + log.warn(e); + if (shouldConnect.current === true) { + onError?.(e as Error); + } + } finally { + unlock(); } }); } else { log.debug('disconnecting because connect is false'); shouldConnect.current = false; - room.disconnect(); + connectDisconnectLock.lock().then(async (unlock) => { + await room.disconnect(); + unlock(); + }); } }, [ connect, @@ -172,13 +190,17 @@ export function useLiveKitRoom( onError, serverUrl, simulateParticipants, + connectionSideEffect, ]); React.useEffect(() => { if (!room) return; return () => { log.info('disconnecting on onmount'); - room.disconnect(); + connectDisconnectLock.lock().then(async (unlock) => { + await room.disconnect(); + unlock(); + }); }; }, [room]);