Architecture
Why not Bluetooth
iOS does not expose classic Bluetooth file profiles (OBEX/OPP). Third-party apps only get BLE, which is too slow and fragile for a typical photo. Eric's Drop to Phone uses Bluetooth’s product idea (nearby, no account) with HTTP on the LAN as the transport.
Roles
- Windows tray app hosts a Kestrel server on
0.0.0.0:17890, advertises_droptophone._tcp(Bonjour), and holds one drop (one file or several) on disk for a short session. - Receiver / sender is any HTTP client: desktop browser (test on the PC), phone Safari/Chrome (QR), the SwiftUI iOS app, or the Kotlin Android app (
GET /current, then/d/{token}/item/{id}or/d/{token}/file;POST /from-phoneto send files back).
The phone pulls PC drops. After the native app scans once (or taps Connect on a discovered PC), it remembers the PC and polls GET /current. Later drops do not need a new QR. If the PC’s IPv4 address changes, the phone looks up the same Bonjour name. A linked phone can also push photos, videos, or files with POST /from-phone; Windows saves them in Downloads\DropToPhone.
[drop files] -> [token + files on disk] -> [QR = http://LAN:17890/d/token?pc=Name]
| |
+-> Bonjour _droptophone._tcp ------------+
v
GET /current JSON (items, progress)
GET /d/token HTML gallery
GET /d/token/item/id one file
GET /d/token/file one file, or a zip of all
Trust model (v1)
- No accounts, no TLS. Traffic is on the local network only.
- The URL token is 24 random bytes, base64url (~32 characters). Guessing it is impractical for a 10-minute window.
- A session expires after 10 minutes or when a new drop replaces it.
RemainingGetsstill limits PC simulator Save (save-pc), not phone downloads (video Range requests would burn the counter). - Bind is all interfaces so the phone can connect; choose the correct NIC in the UI so the first QR is not a VPN address.
- Windows Firewall often blocks inbound 17890 until a private rule is added.
This is not a substitute for HTTPS on untrusted networks (public café Wi-Fi).
Routes
| Method | Path | Result |
|---|---|---|
| GET | / |
Redirect to /iphone |
| GET | /iphone |
Phone-sized preview for Cursor Simple Browser |
| GET | /current |
JSON { ready, preparing, canReceive, token, items, progress, … } |
| GET | /health |
ok |
| GET | /d/{token} |
HTML receiver (gallery when several files) |
| GET | /d/{token}/file |
The single file, or a zip of a multi-file drop |
| GET | /d/{token}/item/{id} |
One file from a multi-file drop |
| POST | /d/{token}/clear |
Clear the session |
| POST | /d/{token}/save-pc |
Loopback only — copy into Downloads on this PC |
| POST | /from-phone |
Multipart upload from a linked iPhone → Downloads\DropToPhone |
Relative file / item URLs keep the token in the path so Safari and desktop browsers both work.
Process model
WPF (net8.0-windows) + Microsoft.AspNetCore.App. WebApplication.StartAsync runs beside the UI thread. Closing the window hides it; tray Quit shuts down Kestrel and wipes the session.
Single instance: mutex Local\DropToPhone.SingleInstance. A second launch signals Local\DropToPhone.ShowWindow.
Limits
- Up to 40 files per drop, 250 MB total, in either direction.
- Photos, video (including WebM / animated WebP converted to MP4 on the way to the phone), PDFs, and other documents. Programs and scripts are blocked.
- Photos/video default to the phone Photos library / gallery; other files go to Files.
- Files sent from the iPhone land in
Downloads\DropToPhoneon Windows. - Progress is reported while converting, packing a zip, sending, and receiving.
- Bonjour service type:
_droptophone._tcpon port 17890.