@@ -345,6 +345,116 @@ export const useAgentTimeoutIdStore = (): {
345345
346346type SessionStub = Pick < UseSessionReturn , 'connectionState' | 'room' | 'internal' > ;
347347
348+ /** Internal hook used by useAgent which generates a function that when called, will return a
349+ * promise which resolves when agent.isAvailable is enabled. */
350+ function useAgentWaitUntilDerivedStates ( emitter : TypedEventEmitter < AgentCallbacks > , state : AgentState ) {
351+ const stateRef = React . useRef ( state ) ;
352+ React . useEffect ( ( ) => {
353+ stateRef . current = state ;
354+ } , [ state ] ) ;
355+
356+ const waitUntilConnected = React . useCallback (
357+ async ( signal ?: AbortSignal ) => {
358+ const { isConnected } = generateDerivedStateValues ( stateRef . current ) ;
359+ if ( isConnected ) {
360+ return ;
361+ }
362+
363+ return new Promise < void > ( ( resolve , reject ) => {
364+ const stateChangedHandler = ( state : AgentState ) => {
365+ const { isConnected } = generateDerivedStateValues ( state ) ;
366+ if ( ! isConnected ) {
367+ return ;
368+ }
369+ cleanup ( ) ;
370+ resolve ( ) ;
371+ } ;
372+ const abortHandler = ( ) => {
373+ cleanup ( ) ;
374+ reject ( new Error ( 'useAgent.waitUntilConnected - signal aborted' ) ) ;
375+ } ;
376+
377+ const cleanup = ( ) => {
378+ emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
379+ signal ?. removeEventListener ( 'abort' , abortHandler ) ;
380+ } ;
381+
382+ emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
383+ signal ?. addEventListener ( 'abort' , abortHandler ) ;
384+ } ) ;
385+ } ,
386+ [ emitter ] ,
387+ ) ;
388+
389+ const waitUntilCouldBeListening = React . useCallback (
390+ async ( signal ?: AbortSignal ) => {
391+ const { canListen } = generateDerivedStateValues ( stateRef . current ) ;
392+ if ( canListen ) {
393+ return ;
394+ }
395+
396+ return new Promise < void > ( ( resolve , reject ) => {
397+ const stateChangedHandler = ( state : AgentState ) => {
398+ const { canListen } = generateDerivedStateValues ( state ) ;
399+ if ( ! canListen ) {
400+ return ;
401+ }
402+ cleanup ( ) ;
403+ resolve ( ) ;
404+ } ;
405+ const abortHandler = ( ) => {
406+ cleanup ( ) ;
407+ reject ( new Error ( 'useAgent.waitUntilCouldBeListening - signal aborted' ) ) ;
408+ } ;
409+
410+ const cleanup = ( ) => {
411+ emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
412+ signal ?. removeEventListener ( 'abort' , abortHandler ) ;
413+ } ;
414+
415+ emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
416+ signal ?. addEventListener ( 'abort' , abortHandler ) ;
417+ } ) ;
418+ } ,
419+ [ emitter ] ,
420+ ) ;
421+
422+ const waitUntilFinished = React . useCallback (
423+ async ( signal ?: AbortSignal ) => {
424+ const { isFinished } = generateDerivedStateValues ( stateRef . current ) ;
425+ if ( isFinished ) {
426+ return ;
427+ }
428+
429+ return new Promise < void > ( ( resolve , reject ) => {
430+ const stateChangedHandler = ( state : AgentState ) => {
431+ const { isFinished } = generateDerivedStateValues ( state ) ;
432+ if ( ! isFinished ) {
433+ return ;
434+ }
435+ cleanup ( ) ;
436+ resolve ( ) ;
437+ } ;
438+ const abortHandler = ( ) => {
439+ cleanup ( ) ;
440+ reject ( new Error ( 'useAgent.waitUntilFinished - signal aborted' ) ) ;
441+ } ;
442+
443+ const cleanup = ( ) => {
444+ emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
445+ signal ?. removeEventListener ( 'abort' , abortHandler ) ;
446+ } ;
447+
448+ emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
449+ signal ?. addEventListener ( 'abort' , abortHandler ) ;
450+ } ) ;
451+ } ,
452+ [ emitter ] ,
453+ ) ;
454+
455+ return { waitUntilConnected, waitUntilCouldBeListening, waitUntilFinished } ;
456+ }
457+
348458/**
349459 * useAgent encapculates all agent state, normalizing some quirks around how LiveKit Agents work.
350460 * @public
@@ -675,104 +785,7 @@ export function useAgent(session?: SessionStub): UseAgentReturn {
675785 }
676786 } , [ agentParticipantAttributes , emitter , agentParticipant , state , videoTrack , audioTrack ] ) ;
677787
678- const waitUntilConnected = React . useCallback (
679- async ( signal ?: AbortSignal ) => {
680- const { isConnected } = generateDerivedStateValues ( state ) ;
681- if ( isConnected ) {
682- return ;
683- }
684-
685- return new Promise < void > ( ( resolve , reject ) => {
686- const stateChangedHandler = ( state : AgentState ) => {
687- const { isConnected } = generateDerivedStateValues ( state ) ;
688- if ( ! isConnected ) {
689- return ;
690- }
691- cleanup ( ) ;
692- resolve ( ) ;
693- } ;
694- const abortHandler = ( ) => {
695- cleanup ( ) ;
696- reject ( new Error ( 'useAgent.waitUntilConnected - signal aborted' ) ) ;
697- } ;
698-
699- const cleanup = ( ) => {
700- emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
701- signal ?. removeEventListener ( 'abort' , abortHandler ) ;
702- } ;
703-
704- emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
705- signal ?. addEventListener ( 'abort' , abortHandler ) ;
706- } ) ;
707- } ,
708- [ state , emitter ] ,
709- ) ;
710-
711- const waitUntilCouldBeListening = React . useCallback (
712- async ( signal ?: AbortSignal ) => {
713- const { canListen } = generateDerivedStateValues ( state ) ;
714- if ( canListen ) {
715- return ;
716- }
717-
718- return new Promise < void > ( ( resolve , reject ) => {
719- const stateChangedHandler = ( state : AgentState ) => {
720- const { canListen } = generateDerivedStateValues ( state ) ;
721- if ( ! canListen ) {
722- return ;
723- }
724- cleanup ( ) ;
725- resolve ( ) ;
726- } ;
727- const abortHandler = ( ) => {
728- cleanup ( ) ;
729- reject ( new Error ( 'useAgent.waitUntilCouldBeListening - signal aborted' ) ) ;
730- } ;
731-
732- const cleanup = ( ) => {
733- emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
734- signal ?. removeEventListener ( 'abort' , abortHandler ) ;
735- } ;
736-
737- emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
738- signal ?. addEventListener ( 'abort' , abortHandler ) ;
739- } ) ;
740- } ,
741- [ state , emitter ] ,
742- ) ;
743-
744- const waitUntilFinished = React . useCallback (
745- async ( signal ?: AbortSignal ) => {
746- const { isFinished } = generateDerivedStateValues ( state ) ;
747- if ( isFinished ) {
748- return ;
749- }
750-
751- return new Promise < void > ( ( resolve , reject ) => {
752- const stateChangedHandler = ( state : AgentState ) => {
753- const { isFinished } = generateDerivedStateValues ( state ) ;
754- if ( ! isFinished ) {
755- return ;
756- }
757- cleanup ( ) ;
758- resolve ( ) ;
759- } ;
760- const abortHandler = ( ) => {
761- cleanup ( ) ;
762- reject ( new Error ( 'useAgent.waitUntilFinished - signal aborted' ) ) ;
763- } ;
764-
765- const cleanup = ( ) => {
766- emitter . off ( AgentEvent . StateChanged , stateChangedHandler ) ;
767- signal ?. removeEventListener ( 'abort' , abortHandler ) ;
768- } ;
769-
770- emitter . on ( AgentEvent . StateChanged , stateChangedHandler ) ;
771- signal ?. addEventListener ( 'abort' , abortHandler ) ;
772- } ) ;
773- } ,
774- [ state , emitter ] ,
775- ) ;
788+ const { waitUntilConnected, waitUntilCouldBeListening, waitUntilFinished } = useAgentWaitUntilDerivedStates ( emitter , state ) ;
776789
777790 const waitUntilCamera = React . useCallback (
778791 ( signal ?: AbortSignal ) => {
0 commit comments