PhoneConnect lets you trigger a cellular call from your laptop by relaying commands through a small WebSocket gateway and a companion Android app running on a nearby phone. The typical deployment looks like this:
Laptop CLI ⇄ WebSocket Gateway (node.js) ⇄ Wi‑Fi ⇄ Android App → Phone Call
- CLI (
client/in Rust): simpledialcommand‑line tool used on a laptop or any machine that can reach the gateway. It discovers the gateway via mDNS and sends HTTP requests. - Gateway (
server/in Node.js): single‑process Express server exposing a REST API and a WebSocket endpoint. Android devices connect over WebSocket and register themselves. CLI callers POST to/callwhich are forwarded as JSON commands to the appropriate device. - Android App (
PhoneConnect/in Kotlin/Gradle): foreground service keeps a persistent WebSocket to the gateway. IncomingCALLmessages launch the dialer; call state is reported back over the socket. The app also performs mDNS discovery so the CLI/gateway can be located automatically.
- Discovery – the gateway advertises
_phoneconnect._tcp.local.via mDNS; both the CLI and the Android app browse for the same service type. When a gateway is found the CLI saves the HTTP/WS URL in~/.config/phoneconnect/config.tomlso subsequent invocations are instant. - Authentication – every request uses a bearer token. The server reads
allowed tokens from the
GATEWAY_TOKENSenvironment variable (comma‑separated). The Android app supplies its token as part of theAUTHWebSocket message; the CLI uses it in theAuthorizationHTTP header. - Call flow – the CLI issues
POST /call { deviceId, number }. If the target device is connected the gateway generates aCALLpayload with a UUID, marks it logged, and sends it over the device’s WebSocket. The device acknowledges withACKand then dials the given number. Call status messages (CALL_STARTED,CALL_ENDED,CALL_FAILED) are reported back and may be surfaced to future REST endpoints (currently logged).
Path: client/ — produces binary dial.
- Uses
clapfor command parsing (call,devices,status,discover,config). - mDNS discovery via
mdns-sdcrate, asynchronous via Tokio. - Configuration stored at
~/.config/phoneconnect/config.tomlwithserver_urlandtoken. - Implements simple E.164 phone validation and helpful coloured output.
Path: server/.
- Express +
wsfor WebSocket handling. - REST routes in
src/routes/call.jsprovide/call,/devices,/health. - Connection management (heartbeat, single‑connection per device) in
connectionManager.js. - Messages parsed/handled in
messageHandler.js; contains helpers to build outbound commands. - mDNS advertisement with
bonjour-serviceso Android apps auto‑discover the gateway. - Rate limiting, validation (
express-validator), structured logging withpino. - Authentication middleware in
auth.js. - Logs & config courtesy of
.envandpino-prettyin dev.
Path: PhoneConnect/ (Gradle project).
- Jetpack Compose UI with three tabs: Home, Logs, Settings.
- Foreground
WsServicemaintains WebSocket usingOkHttpwith automatic reconnection, heartbeat, and JSON message handling. GatewayDiscoveryuses AndroidNsdManagerto discover the gateway.CallManagertriggers telephone calls viaACTION_CALLand listens for telephony state changes to report lifecycle events back to the gateway.- Preferences stored with Jetpack DataStore; includes server URL,
deviceId(UUID prefixed withandroid_…), and auth token. - Service bus (
ServiceBus) propagates state to the Compose UI without binding. - Optional
BootReceiverrestarts the service on device boot.
- Rust toolchain (
cargo) for CLI. - Node 18+ and pnpm/npm for the server.
- Android Studio / SDK for the mobile app (min SDK 31).
git clone …/PhoneConnect server
cd server
pnpm install # or npm install
dotenv config (create .env)
# example .env:
# PORT=3000
# GATEWAY_TOKENS=secret-android,secret-cli
npm run dev # or pnpm dev
# production build: npm run build && node dist/server.jscd client
cargo build --release
# copy target/release/dial to your PATH
# initial config
dial config init
# edit ~/.config/phoneconnect/config.toml and set token & (optionally)
# server_url or run `dial discover` after the gateway is running
# make a call:
dial call android_abc123 +1234567890
# other commands:
dial devices
dial status
dial discoverOpen the PhoneConnect directory in Android Studio. Build & run on a device
with CALL_PHONE and READ_PHONE_STATE permissions. On first launch the app
will request runtime permissions and start the foreground service.
- The home screen shows connection status, device ID, and server URL.
- Settings allow manual server URL/token entry or scanning via mDNS.
- Logs tab displays WebSocket events for debugging.
You can also configure the app to start automatically on boot (requires
RECEIVE_BOOT_COMPLETED permission).
| Component | Config file / env | Default placeholder |
|---|---|---|
| CLI | ~/.config/phoneconnect/config.toml |
PLACEHOLDER_URL (http://10.61.214.187:3000) |
| Server | .env / environment |
PORT=3000, GATEWAY_TOKENS |
| Android | DataStore prefs | ws://192.168.1.100:3000/ws |
The CLI and Android use mDNS to avoid manual IP entry; if discovery fails you can set the URL directly.
- All traffic is protected by a bearer token; rotate by changing
GATEWAY_TOKENSand updating clients. - WebSocket connections are authenticated before a device may register.
- Tokens are redacted from logs by the server logger configuration.
- Server logs are emitted as pretty‑printed text in development, JSON in production.
- The Rust client’s discovery code runs in a blocking thread and respects a configurable timeout.
- The Android app avoids duplicate commands using a bounded LRU cache.
- Expose a server‑side status SSE endpoint for rich CLI output or web UI.
- Persist call history on the Android device.
- Add TLS support to the gateway and clients.
© 2025–2026 PhoneConnect — A lightweight bridge between your laptop and your phone.{