Skip to content

Android APK pairing fails with CHANNEL_ERROR: transport failure (works in browser, fails in WebView) #137

Description

@aasis21

Summary

Pairing from the native Android APK (Capacitor WebView) consistently fails with:

helm/transport-supabase: subscribe failed with status CHANNEL_ERROR: channel error: transport failure (channel private:helm:...)

The exact same pairing flow works fine in a mobile browser (Chrome) on the same phone/network, so this is specific to the Capacitor WebView runtime, not a network-wide or account-wide issue.

Environment

  • Device: Samsung SM-S906W (Galaxy S22+ variant), Android 16
  • WebView: wv tag, Chrome/149.0.7827.164 (confirmed current, not stale)
  • App: helm-debug.apk (debug-signed, package dev.aasis21.helm, minSdk 23, targetSdk 35)
  • Transport: shared/transport-supabase.mjs (Supabase Realtime channels)

Ruled out so far

  • Supabase project health: ACTIVE_HEALTHY
  • RLS policies on realtime.messages (helm_receive_broadcast / helm_send_broadcast, scoped to private:helm:%) — correct
  • AndroidManifest permissions — INTERNET present; no CSP in index.html
  • VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY — verified correctly embedded in the built APK's JS bundle
  • WebView staleness — ruled out, UA shows Chrome/149 (very current)
  • Code branching — createTransport / helmClient.ts / transport-supabase.mjs have no native-vs-web branch anywhere in the connect/subscribe path (only QR-scan UI and getSenderName() branch on Capacitor.isNativePlatform(), neither touches this flow)

Root cause of the generic message

@supabase/realtime-js's normalizeChannelError falls back to the generic "channel error: transport failure" string whenever the underlying failure reason has no numeric .code (i.e. it's a plain WebSocket error Event rather than a CloseEvent). Browsers/WebViews never enrich a raw socket error event with a reason, by design — so this message alone can't tell us why the connection failed.

Diagnostics shipped

  • mobile/src/lib/debugSettings.ts — persisted Debug mode toggle + describeError() (platform/transport/UA/online-status/error-cause chain)

  • mobile/src/components/JoinSessionScreen.tsx — debug toggle + expandable technical-details panel on pairing failure

  • mobile/src/lib/wsProbe.ts + "Run connectivity test" button (visible when Debug mode is on) — opens a raw WebSocket (bypassing supabase-js) to:

    1. a public echo server (wss://echo.websocket.org) — proves the WebView can open any external WebSocket
    2. the actual configured Supabase Realtime endpoint — proves reachability of that specific host

    Reports the real CloseEvent.code / reason, or a timeout/error, instead of supabase-js's swallowed generic message.

Live APK: https://usehelm.netlify.app/app

Next steps

  1. Install the latest debug APK, enable Debug mode, retry pairing, run "Run connectivity test", and capture the output (both probe results with any close codes).
  2. Interpret results:
    • Echo probe fails too → WebView-level or network-level block on all external WebSockets from the app (e.g. a VPN/firewall/DPI rule keyed on user-agent or app identity)
    • Echo probe succeeds but Supabase probe fails/times out → issue is specific to the Supabase Realtime host (carrier/firewall block, DNS, or an upstream Realtime/Capacitor compatibility issue)
    • Supabase probe returns a numeric close code → look up Phoenix/Realtime close-code meaning to pinpoint the failure stage (auth, upgrade, TLS, etc.)
  3. Based on findings, likely fixes: adjust network/DNS, work around carrier/firewall WebSocket blocking, or file an upstream issue against @supabase/realtime-js / Capacitor WebView interaction.

Related commits

  • 04cda31 — debug mode + technical error details + APK packaging fix
  • 2cc1f9c — raw WebSocket connectivity probe wired into debug panel

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtrackingMeta / tracking issue

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions