Protocol

The wire format between your code, the PC server and the phones. One JSON object per line, so any language can drive a phone.

This page summarizes the protocol. The normative text, including the key schedule and the relay frames, is PROTOCOL.md in the repository, and the commands themselves are defined in commands.json.

Ports

PortProtocolListens onFor
8778UDPall interfacesPhones finding the PC on Wi-Fi
8779TCPall interfacesPhones: plain, TLS or WebSocket on the same port
8780TCP127.0.0.1Your code: NDJSON, HTTP or WebSocket on the same port

Your code to the server

UTF-8 JSON, one object per line. Each request has an id you choose and a cmd:

{"id":1,"cmd":"touch","device":"shelf-01","by":"id","value":"login"}
{"id":1,"ok":true,"via":"node","ms":412}
  • Leave out device when exactly one phone is online.
  • Parameters are named as in the reference. Positional values also work: {"id":2,"cmd":"tap","args":[540,1200]}.
  • Shorthands such as touchById are accepted as cmd.
  • A single result value comes back under value: {"id":3,"ok":true,"value":false}.
  • Errors: {"id":4,"ok":false,"error":"NOT_FOUND","msg":"…","retryable":true,"screen":"com.example/.Main"}.
  • Requests can be pipelined. Replies to different phones may arrive out of order; replies for one phone keep their order.

Events

After {"id":9,"cmd":"subscribe","events":["notification","device"]} the connection also receives lines without an id:

{"event":"device","device":"k7d2q9xa","name":"shelf-01","state":"online","route":"lan"}
{"event":"notification","device":"k7d2q9xa","key":"0|com.kakao.talk|1|null|10123","package":"com.kakao.talk","title":"Kim","text":"See you at 3"}

Kinds: device, notification, screen (foreground app changed), toast, and result (the late result of a command that answered accepted).

Network-cutting commands

data(false), wifi(false), airplane(true) and batches containing them reply {"ok":true,"accepted":true} first and deliver {"event":"result","id":…} once the phone is back. Add "wait":true to get the final result as the reply instead. "offline_wait": seconds overrides how long a command waits for an offline phone.

The server to a phone

The phone connects to the PC and sends two plaintext handshake lines: its ephemeral P-256 key and a nonce, answered by the server's. Both sides then derive keys with HKDF-SHA256 from two ECDH results: ephemeral with ephemeral (forward secrecy) and static with static (both sides prove who they are). In QR pairing a one-time token is mixed in as well; in code pairing both sides show a 6-digit code derived from the same keys.

Every line after that is an envelope:

{"seq":0,"blob":"<base64url AES-256-GCM ciphertext>"}

seq counts up by one in each direction and doubles as the nonce, so a replayed or reordered line is rejected. Blobs over 512 KiB are split into parts with "more":true.

Inside, the server sends commands with its own id, and the phone numbers everything it sends with n:

{"id":17,"cmd":"touch","by":"id","value":"com.kakao.talk:id/login","nth":0,"timeout":10}
{"id":17,"n":42,"ok":true,"via":"node","ms":412}

The phone keeps messages until the server acknowledges n, and remembers the last 2000 command IDs, so a reconnect loses nothing and repeats nothing. Keepalive pings run every 25 seconds.

Discovery

{"droidline":"discover","v":1,"server":"k3j9d0a2mq"}
{"droidline":"here","v":1,"server":"k3j9d0a2mq","name":"OFFICE-PC","port":8779,"pub":"BD3x…","pairing":true}

The reply is not trusted on its own; the handshake checks the PC's key.

Test vectors

test-vectors.json holds fixed keys, handshake lines, derived keys, 6-digit codes and encrypted envelopes. The Go server, the Android app and the relay all test against it; an implementation in another language can too.