mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
fix(mobile): revert 5s connect-timeout — was force-closing healthy in-progress pairs
The 5s CONNECT_TIMEOUT_MS added in #1481 was a speculative band-aid for 'WS sometimes never fires onopen' — but in practice it was force-closing perfectly fine connections that hadn't completed by 5s (notably first-time QR pairing on iOS, where Local Network permission prompt blocks the WS open for several seconds). Worse, the force-close transitioned state to 'reconnecting' (not 'disconnected'), so waitForConnected() never rejected and sendRequest() hung. Combined with reconnect re-firing the timer, the result was an infinite 'Connecting…' loop with no surfaced error. Pair flows hit this hardest because they call connect() directly without the shared RpcClientProvider, so there's no UI state listener to even show 'reconnecting' to the user — just the spinner. Removing the timer entirely. The handshake-timeout (5s, post-onopen) still catches genuinely stuck handshakes. If we hit a real 'never fires onopen' case later, we can revisit with proper instrumentation rather than a guess at the symptom. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -35,13 +35,6 @@ export type RpcClient = {
|
||||
const RECONNECT_DELAYS = [1000, 2000, 4000, 8000, 16000]
|
||||
const REQUEST_TIMEOUT_MS = 30_000
|
||||
const HANDSHAKE_TIMEOUT_MS = 5_000
|
||||
// Why: belt-and-suspenders against React Native WebSocket implementations
|
||||
// that occasionally never fire onerror/onclose for an unreachable host
|
||||
// (observed when waking the device with stale DNS). Without this safety
|
||||
// net the UI sat on 'Connecting…' forever and only a Metro reload
|
||||
// recovered. Five seconds is a generous upper bound — a healthy LAN WS
|
||||
// typically connects in <100ms.
|
||||
const CONNECT_TIMEOUT_MS = 5_000
|
||||
|
||||
export function connect(
|
||||
endpoint: string,
|
||||
@@ -55,7 +48,6 @@ export function connect(
|
||||
let reconnectAttempt = 0
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let handshakeTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let connectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let intentionallyClosed = false
|
||||
|
||||
// Why: fresh ephemeral keypair per connection provides forward secrecy.
|
||||
@@ -107,24 +99,7 @@ export function connect(
|
||||
|
||||
ws = new WebSocket(endpoint)
|
||||
|
||||
connectTimer = setTimeout(() => {
|
||||
connectTimer = null
|
||||
// Why: WS still stuck before 'open' — force-close so onclose fires
|
||||
// and reconnect logic kicks in (rather than sitting on 'connecting'
|
||||
// until the user reloads Metro). Safe even if the WS is mid-open;
|
||||
// close() will trigger the 'closing' → 'closed' transitions.
|
||||
try {
|
||||
ws?.close()
|
||||
} catch {
|
||||
// ignore — onclose will still wire up reconnect
|
||||
}
|
||||
}, CONNECT_TIMEOUT_MS)
|
||||
|
||||
ws.onopen = () => {
|
||||
if (connectTimer) {
|
||||
clearTimeout(connectTimer)
|
||||
connectTimer = null
|
||||
}
|
||||
reconnectAttempt = 0
|
||||
setState('handshaking')
|
||||
|
||||
@@ -267,10 +242,6 @@ export function connect(
|
||||
clearTimeout(handshakeTimer)
|
||||
handshakeTimer = null
|
||||
}
|
||||
if (connectTimer) {
|
||||
clearTimeout(connectTimer)
|
||||
connectTimer = null
|
||||
}
|
||||
if (intentionallyClosed) {
|
||||
setState('disconnected')
|
||||
rejectAllPending('Connection closed')
|
||||
@@ -411,10 +382,6 @@ export function connect(
|
||||
clearTimeout(handshakeTimer)
|
||||
handshakeTimer = null
|
||||
}
|
||||
if (connectTimer) {
|
||||
clearTimeout(connectTimer)
|
||||
connectTimer = null
|
||||
}
|
||||
if (ws) {
|
||||
ws.close()
|
||||
ws = null
|
||||
|
||||
Reference in New Issue
Block a user