React Native: no way to recover a suspended (zombie) WebSocket faster than the hardcoded 60s inactivity timeout

Author: ZachRoberts25Created Sep 16, 2026Updated Sep 18, 2026

Environment: convex 1.45.0 / 1.46.0, Expo SDK 54, React Native 0.81, iOS 17–26

Problem

When iOS suspends an app, the WebSocket is frozen without a close event. On resume the client still reports connectionState().isWebSocketConnected === true, but the socket is dead: queries hang and no Transition/Ping arrives. The only recovery path is serverInactivityThreshold (60s, hardcoded in WebSocketManager), after which closeAndReconnect("InactiveServer") runs with 1s→16s exponential backoff.

The client's only environment listener is window.addEventListener("online"), which React Native never fires (setupNetworkListener correctly returns early there). There is no AppState/NetInfo equivalent, and closeAndReconnect / tryReconnectImmediately are private.

For a field-service app where techs reopen the app every few minutes on cellular, this shows up as 60s+ of "loading" per reopen, and on marginal signal (backoff + full resubscribe restarting after a drop) it stretches to minutes. We measured one device doing ~160 full resubscribes in a workday, many of them 30–90s apart with no app-open event — i.e. the inactivity timer firing on a socket that had silently died.

Current workaround

Fragile, reaches into client.sync.webSocketManager: on AppStateactive and on NetInfo reachability changes, run a one-shot query with a unique nonce (so it can't be served from the local cache); if it doesn't resolve within 5s, call the private closeAndReconnect. pause()/resume() don't help here because resume() on a ready socket resends the query set over the same dead socket.

Asks (any one would let us delete the hack)

  1. A public client.reconnect() (or make tryReconnectImmediately / closeAndReconnect public), so app code that does know about lifecycle events can drive recovery.
  2. A constructor option for serverInactivityThreshold (e.g. 10s on mobile).
  3. Built-in React Native lifecycle awareness: on AppStateactive, send a ping and reconnect if no server message arrives within a short window.

Happy to test a prerelease against real devices.

Source: get-convex/convex-backend