Skip to content

[Experimental] Wireless ADB: USB enable step + WebSocket bridge - #7

Open
andronedev wants to merge 5 commits into
developfrom
feature/wireless-adb
Open

[Experimental] Wireless ADB: USB enable step + WebSocket bridge#7
andronedev wants to merge 5 commits into
developfrom
feature/wireless-adb

Conversation

@andronedev

Copy link
Copy Markdown
Owner

⚠️ Experimental. This is a first working cut of wireless ADB. The core is
verified on real hardware, but it needs a production certificate and code
signing before it ships to users. Do not merge to a release without the
follow-ups below.

What this adds

Manage a Portal over Wi-Fi, not only over the USB cable, while keeping the
browser-first, no-backend identity. Full spec in docs/prd-wireless-adb.md.

A browser tab cannot open a raw TCP socket, so this ships in two honest halves:

  1. A USB-side "Enable wireless" step that needs nothing installed.
  2. An optional local bridge (a Rust companion) that relays the browser's
    WebSocket to the device's ADB TCP port. It is a dumb byte pipe: ADB's RSA
    handshake runs end to end in the browser, so the bridge holds no keys.

Browser (this repo)

  • Transport seam in src/lib/adb/connection.ts (ActiveConnection, usb/wireless
    kinds) feeding the same AdbDaemonTransport.authenticate.
  • Device-side enable via adb.tcpip.setPort(5555) reusing the existing
    getIpAddress (src/lib/adb/wireless.ts); endpoint persisted in
    wireless-store.
  • WebSocket transport that frames the raw ADB packet stream and probes the
    bridge /health (src/lib/adb/ws-connection.ts).
  • connectViaWireless in device-store; WirelessPanel.tsx UI (enable on the
    dashboard, reconnect on the connect screen). i18n en + fr.

Bridge (new Rust crate at bridge/, polyglot monorepo)

  • axum relay on 127.0.0.1: GET /health (JSON + CORS) and
    GET /adb?ip=&port=5555 upgraded to a byte pipe to the device.
  • Security: loopback-only, Origin allowlist, RFC1918 + port 5555 target
    restriction, optional pinned device IP, no ADB keys held.
  • Cargo features: default networking core; tls (axum-server) and desktop
    (tray-icon/tao + auto-launch) gated.

Monorepo wiring

  • deploy.yml ignores bridge/**; new bridge.yml (clippy/test on PRs,
    release matrix on bridge-v* tags); biome ignores bridge; gitignore adds
    bridge/target.
  • Docs: corrected the --wifi-adb note in docs/programs.md; PRD updated with
    status, pinned bridge contract, and stack.

Verified

  • pnpm lint + pnpm build green; cargo check/clippy -D warnings green
    (core and production).
  • Bridge runtime smoke test: health/CORS/origin/port/SSRF guards behave.
  • On a real Portal+ (aloha, Android 9): adb tcpip 5555 + wireless connect
    work; the bridge relayed a real ADB CNXN and the device answered AUTH
    through the WebSocket; scrcpy 2.3 server runs and exposes an H.264 encoder, so
    screen mirroring works over Wi-Fi (no audio, as before).
  • End-to-end in the browser UI: enable over USB, then reconnect over Wi-Fi via
    the bridge, then screen mirror.

Not ready yet (follow-ups)

  • Production Let's Encrypt certificate for local.openportal.cc (prod uses WSS;
    dev uses plain ws to loopback, already working).
  • macOS notarization and Windows Authenticode signing for the bridge installers.
  • Doc nit: scrcpy.ts / CLAUDE.md say "Android 10" but Gen 1 is Android 9
    (conclusion of "no audio" still holds; audio needs Android 11+).

How to test

pnpm dev, then adb kill-server, connect over USB, click Enable wireless
ADB
, run cargo run --manifest-path bridge/Cargo.toml, then Reconnect over
Wi-Fi
and open the screen mirror.

Browser (this repo):
- Abstract the connection layer into an ActiveConnection with usb/wireless
  transport kinds so AdbDaemonTransport.authenticate is fed from either a
  WebUSB duplex or a WebSocket duplex (connection.ts).
- Device-side enable over USB via adb.tcpip.setPort(5555), reusing the
  existing getIpAddress; persist the endpoint (wireless.ts, wireless-store).
- WebSocket transport that frames the raw ADB packet stream to/from the
  bridge, plus a /health bridge probe (ws-connection.ts).
- device-store gains connectViaWireless and a transport-agnostic finalize;
  WirelessPanel drives enable / reconnect UI, wired into ConnectPanel and
  the dashboard. i18n keys in en and fr.

Bridge (new Rust crate at bridge/, polyglot monorepo):
- axum WS relay on 127.0.0.1: GET /health (JSON + CORS) and
  GET /adb?ip=&port=5555 upgraded to a byte pipe to the device.
- Security: loopback-only, Origin allowlist, RFC1918 + port 5555 target
  restriction, optional pinned device IP, no ADB keys held.
- Features: default networking core; tls (axum-server) and desktop
  (tray-icon/tao + auto-launch) gated. Verified: cargo check/clippy -D
  warnings (core and production), and runtime smoke test of health/CORS/
  origin/port/SSRF guards.

Monorepo wiring: deploy.yml ignores bridge/**, new bridge.yml (clippy/test
on PRs, release matrix on bridge-v* tags), biome ignores bridge, gitignore
adds bridge/target. Docs: correct the --wifi-adb note in programs.md and
update the PRD status, pinned bridge contract, and stack.
adb tcpip restarts adbd and tears down the live (USB) transport, so
getIpAddress must run before setPort, not after. Also disconnect after a
successful enable so the UI returns to the connect screen where the
Wi-Fi reconnect action surfaces.
Rework the wireless UI for non-technical users. The connect screen now
always offers a collapsible Wi-Fi guide (open by default once configured)
with three staged steps: enable on the Portal, install and run the bridge,
then connect. Each step shows a live done/active/todo state.

Step 2 detects the bridge and, when absent, surfaces a download button plus
an "already installed? make sure it is running, then check again" hint with
a recheck action, covering both not-installed and not-running. A "why is the
cable needed first?" disclosure explains the one-time USB requirement.

Clarify the dashboard enable card (what happens after enabling) and add
BRIDGE_DOWNLOAD_URL. New copy is in i18n en + fr.
Replace the single-device store with a per-serial fleet registry so
several Portals can stay connected at once, with one "active" device
driving the heavy tools (screen/terminal/logcat/files) while the rest
stay connected in the background. Adds fleet UI (sidebar switcher,
add-device flow, reconnect wall for known USB/Wi-Fi devices, and a
bulk action bar for install/reboot/uninstall/set-flag across selected
devices), turns wireless-adb's last-known-endpoint into a persisted
set, and seeds three distinct mock devices in demo mode so the fleet
UI is testable without hardware.

Also fixes Modal/ConfirmDialog rendering trapped inside the sidebar's
bounding box (a transformed ancestor becomes the containing block for
fixed-position descendants) by portaling them to document.body.

Claude-Session: https://claude.ai/code/session_01PDwMvqk55fZNE9hvqGe42Q
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant