Skip to content

Commit 4055f2c

Browse files
committed
fix: make agent.waitUntilAvailable more stable
It had a dependency on `state` so that it could bail if the state was the expected state when being initially called, but this had the side effect of causing it to change reference often. So, cache this in a ref instead.
1 parent 328a37d commit 4055f2c

File tree

2 files changed

+115
-99
lines changed

2 files changed

+115
-99
lines changed

packages/react/src/hooks/useAgent.ts

Lines changed: 111 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,116 @@ export const useAgentTimeoutIdStore = (): {
345345

346346
type 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) => {

packages/react/src/hooks/useSession.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,10 @@ export function useSession(
478478
emitter.emit(SessionEvent.ConnectionStateChanged, conversationState.connectionState);
479479
}, [emitter, conversationState.connectionState]);
480480

481-
const waitUntilConnectionState = useSessionWaitUntilConnectionState(emitter, conversationState.connectionState);
481+
const waitUntilConnectionState = useSessionWaitUntilConnectionState(
482+
emitter,
483+
conversationState.connectionState,
484+
);
482485

483486
const waitUntilConnected = React.useCallback(
484487
async (signal?: AbortSignal) => {

0 commit comments

Comments
 (0)