Skip to content

File Bridge 1.14.0 — move files over the USB cable

Choose a tag to compare

@sayanthns sayanthns released this 08 Sep 12:41
· 10 commits to main since this release

Move files between the Mac and the phone over the USB cable, not just wifi.

The phone dials its own 127.0.0.1:8001; adb reverse carries that to a second
listener bound to loopback on the Mac. No wifi is involved at any point, which
removes the two failure classes that have cost this project the most time: a
full-tunnel VPN cannot swallow loopback, and there is no radio to fall asleep.

It is not faster. The cable tested here negotiates USB 2.0 High Speed
(480 Mbit/s) against an 802.11ax link already running at 600 Mbit/s. Wired is
for reliability, not throughput — and the docs say so rather than letting anyone
hope otherwise.

Pairing needs no QR and no typing

Press Pair over cable in the Mac panel. It arms the reverse mapping and then
fires the same filebridge:// deep link the QR encodes straight at the phone, so
the app opens already connected.

Needs USB debugging on the phone, and adb on the Mac.

The security part

_local() decided "is this the Mac?" by comparing the client address to
127.0.0.1. That is right with one listener and wrong with two, because
adb reverse delivers the phone's requests from 127.0.0.1.

Trust now follows the socket. The wired listener is a separate, flagged socket
that is never treated as local, so it answers 403 to /connect, /qr.png,
/api/status, /api/stop, /api/start, /api/quit, /api/open and
/api/usb. Measured with the guard removed, the same socket served /connect
with the access key rendered into the page, handed the key over again in
/api/status, and stopped the server with one POST /api/quit.

Pause covers the cable too, since it runs through the same check.

Also in this release

  • The phone keeps one saved link per transport and falls back between them.
    Pairing over the cable used to overwrite the only saved link, leaving the app
    stranded on a 127.0.0.1 that nothing answers once unplugged.
  • A VPN is no longer blamed for the cable. No VPN can reach loopback, so
    naming one there would send you off to debug the wrong thing.
  • The wired bind is probed, not attempted. SO_REUSEADDR lets a
    127.0.0.1:8001 bind succeed underneath a live *:8001, and the narrower
    socket then quietly takes every loopback connection — including the panel's.
  • build-android.sh is pinned to Gradle 8. A cached 9.7.1 appeared and
    newest-wins picked it; AGP 8.2.1 dies on Gradle 9 reaching for
    org/gradle/api/internal/HasConvention.
  • The released APK now lives in dist/ and in git, because a loose
    gitignored copy at the repo root went missing and could not be recovered.

Not verified

The two adb calls have never run against real hardware. USB debugging was off on
the test phone, so adb devices was empty and everything measured was the
loopback socket driven by curl. HANDOFF.md says so, and makes turning it on
the top open item.

Versions

Piece Version
Server 1.13.0
Mac app 1.13.0
Android app 1.12.0 (versionCode 15)

The APK is debug-signed. It installs and upgrades in place, but is not Play
Store distributable.