fix(auth): stop infinite SSE retry on 401/403, surface auth failures (#79)
* fix(auth): stop infinite SSE retry on 401/403 and surface auth failures Root cause (Sentry OPENCODE-MOBILE-1, 309 events / 65 users): auth is static HTTP Basic and no code path treated 401 specially. The SSE reconnect loop in events.ts retried on a fixed backoff regardless of cause, so a bad password spammed Sentry and drained battery forever with zero user feedback. Advanced-mode connection save also had no pre-flight check and silently persisted bad credentials as the active connection. - src/lib/api-error.ts: new pure ApiAuthError/isAuthStatus/isAuthError module (node --test covered) so 401/403 are distinguishable from other failures. - src/lib/sdk.ts: request()/events() now throw ApiAuthError for 401/403 instead of a generic Error. - src/stores/events.ts: the SSE loop stops retrying on an auth error and sets a new `authError` flag instead of reconnecting forever; other errors keep the existing backoff. Fires connection_failed (source: sse, error_class: unauthorized) so it's visible in the existing funnel. - app/(tabs)/index.tsx: sessions screen shows an "Authentication Failed" state with a link to the connection edit screen when authError is set. - app/connection/[id].tsx: saving edited credentials for the active connection now reconnects SSE immediately instead of requiring an app restart. - app/connection/add.tsx: Advanced-mode save now runs the same testConnection pre-flight as Quick Connect and shows the same "Connection Failed" alert (with diagnostics/share-report) instead of silently saving bad credentials. Closes #76 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E * fix(auth): add Retry button on 401 error state, widen ConnectionTestSource - Authentication Failed screen now offers Retry alongside Check Credentials, calling events store's connect() directly to restart the SSE state machine on transient 401s without leaving the app. - Widen ConnectionTestSource to include 'sse' (events.ts:389's connection_failed track call) and note the activation funnel only filters on source=onboarding. Addresses PR #79 review follow-ups. --------- Co-authored-by: engineer <engineer@gray-knight-m1.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
import { router, useLocalSearchParams } from "expo-router"
|
||||
import { Ionicons } from "@expo/vector-icons"
|
||||
import { useConnections } from "../../src/stores/connections"
|
||||
import { useEvents } from "../../src/stores/events"
|
||||
import type { ConnectionType } from "../../src/lib/types"
|
||||
import { probeConnection, shareReport } from "../../src/lib/diagnostics"
|
||||
import { captureDiagnostic } from "../../src/lib/sentry"
|
||||
@@ -130,6 +131,13 @@ export default function EditConnectionScreen() {
|
||||
directory: directory.trim() || undefined,
|
||||
username: username.trim() || undefined,
|
||||
})
|
||||
// If this was the active connection, the SSE loop may have stopped
|
||||
// retrying after a prior 401 (see events.ts) — reconnect now with the
|
||||
// freshly saved credentials instead of leaving the user stuck until
|
||||
// they relaunch the app.
|
||||
if (useConnections.getState().activeConnection?.id === connection.id) {
|
||||
useEvents.getState().connect()
|
||||
}
|
||||
setIsSaving(false)
|
||||
router.back()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user