-
Notifications
You must be signed in to change notification settings - Fork 0
fix: WebRTC auto-reconnect on tab resume + Go Available reconnects first #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,8 @@ export function Header() { | |
| const autoOfflineAttemptedRef = useRef(false) | ||
| const { wrapUpSecondsRemaining } = useWrapUpCountdown() | ||
| const { connectionState, reconnectPresence } = usePresence() | ||
| const { isRegistered, hasInitialized, currentCall } = useTelnyxContext() | ||
| const { isRegistered, hasInitialized, currentCall, reconnect, connectionStatus } = useTelnyxContext() | ||
| const isRegisteredRef = useRef(isRegistered) | ||
|
|
||
| // Sync status when user.agent_status changes mid-session (e.g. a call is routed to this agent). | ||
| // Without this, the dropdown stays editable even while the agent is on_call. | ||
|
|
@@ -76,10 +77,29 @@ export function Header() { | |
| return () => window.removeEventListener('agent-status-optimistic', handleOptimistic) | ||
| }, []) | ||
|
|
||
| useEffect(() => { | ||
| isRegisteredRef.current = isRegistered | ||
| }, [isRegistered]) | ||
|
|
||
| const waitForRegistration = useCallback(async (timeoutMs = 12000) => { | ||
| const startedAt = Date.now() | ||
| while (Date.now() - startedAt < timeoutMs) { | ||
| if (isRegisteredRef.current) return true | ||
| await new Promise((resolve) => setTimeout(resolve, 300)) | ||
| } | ||
| return false | ||
| }, []) | ||
|
|
||
| const handleStatusChange = useCallback(async (newStatus: AgentStatus): Promise<boolean> => { | ||
| if (newStatus === 'available' && !isRegistered) { | ||
| showPresenceToast('Cannot go available — phone not connected. Please refresh.', 'error') | ||
| return false | ||
| showPresenceToast('Phone reconnecting…', 'warning') | ||
| reconnect() | ||
|
|
||
| const recovered = await waitForRegistration() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This handler now awaits registration before applying the status change, but there is no cancellation/version guard for in-flight requests. If an agent clicks “Go Available” while disconnected and then chooses another status before registration completes, the delayed first call continues and can overwrite the later selection by posting Useful? React with 👍 / 👎. |
||
| if (!recovered) { | ||
| showPresenceToast('Cannot go available — phone not connected. Click Agent Diagnostic → Reconnect.', 'error') | ||
| return false | ||
| } | ||
| } | ||
|
Comment on lines
93
to
103
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Minor: The If distinguishing "Go Available" reconnects from manual button clicks in logs is valuable, consider exposing the reason parameter: // In UseTelnyxReturn interface
reconnect: (reason?: string) => void
// In the hook's reconnect callback
const reconnect = useCallback((reason = 'manual-button') => {
manualReconnectFnRef.current?.(reason)
}, [])🤖 Prompt for AI Agents |
||
|
|
||
| const previousStatus = status | ||
|
|
@@ -129,14 +149,23 @@ export function Header() { | |
| ) | ||
| return false | ||
| } | ||
| }, [isRegistered, status]) | ||
| }, [isRegistered, status, reconnect, waitForRegistration]) | ||
|
|
||
| useEffect(() => { | ||
| if (isRegistered) { | ||
| autoOfflineAttemptedRef.current = false | ||
| } | ||
| }, [isRegistered]) | ||
|
|
||
| useEffect(() => { | ||
| if (document.visibilityState !== 'visible') return | ||
| if (connectionStatus !== 'disconnected' && connectionStatus !== 'error') return | ||
|
|
||
| reconnect() | ||
| const retryTimer = setTimeout(() => reconnect(), 2500) | ||
| return () => clearTimeout(retryTimer) | ||
| }, [connectionStatus, reconnect]) | ||
|
Comment on lines
+160
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential duplicate reconnect triggers with hook's visibility handler. This effect runs when When the tab becomes visible after backgrounding:
Both reconnect paths will fire nearly simultaneously. While internal guards prevent duplicate Additionally, the 2500ms retry at line 165 may interfere with the hook's own recovery timing (500ms Consider either:
🤖 Prompt for AI Agents |
||
|
|
||
| useEffect(() => { | ||
| const activeCall = currentCall?.state && ['ringing_inbound', 'ringing_outbound', 'answering', 'active', 'held'].includes(currentCall.state) | ||
| if (status === 'available' && hasInitialized && !isRegistered && !activeCall) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -667,7 +667,8 @@ export function useTelnyx(): UseTelnyxReturn { | |
| } | ||
|
|
||
| const now = Date.now() | ||
| if (now - lastManualReconnectAtRef.current < MANUAL_RECONNECT_COOLDOWN_MS) { | ||
| const bypassCooldown = reason === 'manual-button' || reason === 'visibility-resume' || reason === 'go-available' | ||
| if (!bypassCooldown && now - lastManualReconnectAtRef.current < MANUAL_RECONNECT_COOLDOWN_MS) { | ||
| console.log(`[useTelnyx] Manual reconnect skipped (${reason}) - in cooldown`) | ||
| return | ||
| } | ||
|
|
@@ -881,6 +882,13 @@ export function useTelnyx(): UseTelnyxReturn { | |
| function handleVisibilityChange() { | ||
| if (document.visibilityState === 'visible') { | ||
| console.log('[useTelnyx] Tab visible, checking registration...') | ||
|
|
||
| if (connectionStatusRef.current === 'disconnected' || connectionStatusRef.current === 'error') { | ||
| void triggerManualReconnect('visibility-resume') | ||
| runLifecycleRecovery('visibilitychange', 500) | ||
| return | ||
| } | ||
|
|
||
| runLifecycleRecovery('visibilitychange', 1500) | ||
|
Comment on lines
+885
to
892
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider potential double-reconnect on visibility resume. When the tab becomes visible with This is likely benign given the existing guards (
If the intent is to have 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitForRegistrationcaps the Go Available recovery wait at 12 seconds, but reconnect attempts intriggerManualReconnectuse exponential delays (3s/6s/12s… up to 30s) beforeinit()even runs, andinit()itself can take up to the registration timeout. In those common failure-recovery paths, this function returnsfalsebefore the reconnect attempt can realistically complete, so agents get a hard “cannot go available” error even though the phone may recover shortly after.Useful? React with 👍 / 👎.