@firefly0621/dsh-remote-relay

Standalone WebSocket relay for dsh remote control: device registry, pairing, session routing


License
MIT
Install
npm install @firefly0621/dsh-remote-relay@0.1.0-rc.11

Documentation

@firefly0621/dsh-remote-relay

English | 中文

Standalone WebSocket relay for the dsh remote-control capability. Devices (dsh hosts running @firefly0621/dsh-remote-control) connect outbound with a long-lived secret; mobile apps pair with a short-lived code; the relay routes request/response messages between them. The PC behind NAT never needs an inbound port — it dials out, exactly like the OpenClaw/Claw mobile-control pattern.

The relay is a single-process Node service with no harness dependency. Device registrations and pairing codes live in memory and clear on restart; app sessions persist when DSH_RELAY_DATA_DIR is set, so a paired phone resumes with its stored token instead of re-pairing.

Configuration (environment variables)

Variable Default Meaning
PORT 8787 Listening port
NODE_ENV production requires TLS (refuses plaintext WS)
DSH_RELAY_DEVICE_SECRETS Comma-separated deviceId:secret pairs; the device registry
DSH_RELAY_ALLOW_AUTO_REGISTER 1 accepts the first hello for an unknown random deviceId and binds it (the plugin's zero-config mode)
DSH_RELAY_DATA_DIR Directory for durable session storage; absent keeps sessions in memory
TLS_CERT / TLS_KEY PEM cert/key paths; required when NODE_ENV=production

Deployment (systemd on a VPS)

[Unit]
Description=dsh remote relay
After=network.target

[Service]
WorkingDirectory=/opt/dsh-relay
ExecStart=/usr/bin/node /opt/dsh-relay/lib/bin.js
Environment=NODE_ENV=production
Environment=PORT=8787
Environment=DSH_RELAY_DEVICE_SECRETS=my-pc:CHANGE_ME_LONG_RANDOM
Environment=TLS_CERT=/etc/letsencrypt/live/relay.example.com/fullchain.pem
Environment=TLS_KEY=/etc/letsencrypt/live/relay.example.com/privkey.pem
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Generate the device secret with openssl rand -hex 32. Front with a real certificate (Let's Encrypt) — the relay never serves plaintext WS in production.

Security notes

  • Auth is two-layered: the device secret proves "this is the registered host"; the pairing code proves "the phone user is at the keyboard of that host". The relay forwards requests only between a paired app and its bound device.
  • DSH_RELAY_ALLOW_AUTO_REGISTER is first-seen-wins: an unknown deviceId binds to whatever secret its first hello presents. Plugin-generated deviceIds are random 128-bit values, so claiming a vacant id gains nothing; keep the flag off on shared relays.
  • Session control is device-side: the bound device can list (sessions.list) and revoke (sessions.revoke) its app sessions; a revoked token stops resuming.
  • The relay is a dumb pipe: it never inspects command payloads and never persists message content. Compromising the relay exposes routing metadata, not settings values — though settings reads still transit it, so TLS is mandatory.
  • Pairing codes: 6 digits, 10-minute TTL, one-time use, max 5 wrong attempts. Rotated by the device on every relay (re)registration.
  • Heartbeats: peers ping every 30s; a silent connection is dropped after 60s.

Protocol

Defined in @firefly0621/dsh-remote-protocol — this package only implements the relay side.