(props: Omit<APIProviderProps, "client">)
| 182 | } |
| 183 | |
| 184 | function ManagedAPIProvider(props: Omit<APIProviderProps, "client">) { |
| 185 | const [state, setState] = useState<ConnectionState>({ status: "connecting" }); |
| 186 | const [authToken, setAuthToken] = useState<string | null>(() => { |
| 187 | const urlParams = new URLSearchParams(window.location.search); |
| 188 | const urlToken = urlParams.get("token")?.trim(); |
| 189 | if (urlToken) { |
| 190 | setStoredAuthToken(urlToken); |
| 191 | // Strip token from URL so it doesn't leak into bookmarks, browser |
| 192 | // history, PWA launch URLs, or Referer headers. |
| 193 | const cleanUrl = new URL(window.location.href); |
| 194 | cleanUrl.searchParams.delete("token"); |
| 195 | window.history.replaceState({}, "", cleanUrl.pathname + cleanUrl.search); |
| 196 | return urlToken; |
| 197 | } |
| 198 | |
| 199 | return getStoredAuthToken(); |
| 200 | }); |
| 201 | |
| 202 | const cleanupRef = useRef<(() => void) | null>(null); |
| 203 | const hasConnectedRef = useRef(false); |
| 204 | const reconnectAttemptRef = useRef(0); |
| 205 | const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 206 | const scheduleReconnectRef = useRef<(() => void) | null>(null); |
| 207 | const consecutivePingFailuresRef = useRef(0); |
| 208 | const connectionIdRef = useRef(0); |
| 209 | const forceReconnectInProgressRef = useRef(false); |
| 210 | const livenessPingsInFlightCountRef = useRef(0); |
| 211 | const lastInboundBrowserFrameAtRef = useRef(0); |
| 212 | |
| 213 | // When we decide the user needs to provide a token, stop the reconnect loop. |
| 214 | // |
| 215 | // Otherwise, the WebSocket close event (triggered by cleanup()) can schedule a reconnect |
| 216 | // which immediately flips the UI back to "reconnecting", causing the AuthTokenModal |
| 217 | // to flicker. |
| 218 | const authRequiredRef = useRef(false); |
| 219 | |
| 220 | const authProbeAttemptedRef = useRef(false); |
| 221 | const wsFactory = useMemo( |
| 222 | () => props.createWebSocket ?? ((url: string) => new WebSocket(url)), |
| 223 | [props.createWebSocket] |
| 224 | ); |
| 225 | |
| 226 | const connect = useCallback( |
| 227 | (token: string | null) => { |
| 228 | const connectionId = ++connectionIdRef.current; |
| 229 | // Reset per-connection inbound traffic tracking so stale timestamps from a prior |
| 230 | // socket cannot suppress liveness pings on the next connection. |
| 231 | lastInboundBrowserFrameAtRef.current = 0; |
| 232 | livenessPingsInFlightCountRef.current = 0; |
| 233 | |
| 234 | authRequiredRef.current = false; |
| 235 | |
| 236 | // This connect() call supersedes any prior pending reconnect or active connection. |
| 237 | if (reconnectTimeoutRef.current) { |
| 238 | clearTimeout(reconnectTimeoutRef.current); |
| 239 | reconnectTimeoutRef.current = null; |
| 240 | } |
| 241 |
nothing calls this directly
no test coverage detected