uipcat-cli
v0.0.5
Published
CLI for uIPCat — use phone cameras as IP cameras via WebRTC
Readme
Non-Browser WebRTC Receiver
Node.js receiver that connects to a uIPCat Node camera via MQTT + WebRTC (using node-datachannel). Supports two modes:
- record (default) — continuous H.264 + Opus recording to 10-second MP4 segments.
- snapshot — one-shot JPEG capture via DataChannel RPC, then exit.
Requirements
- Node.js 18.20+
- ffmpeg installed and on
PATH(record mode only) - A running Node camera (e.g.
/node/{id}in the main app) and itsnodeId+securityKey(from the Node URL hash)
Install
npm installUsage — record (default)
npx tsx src/index.ts \
--node-id <nodeId> \
--security-key <securityKey> \
--server <baseUrl> \
--output-dir <path>Example:
npx tsx src/index.ts \
--node-id qBqgepTeuoBU \
--security-key HK1TKprTwSVBKgKz \
--server http://localhost:3000 \
--output-dir ./recordingsIf the MQTT broker uses a self-signed or unverified certificate (e.g. dev), add --insecure:
npx tsx src/index.ts \
--node-id YnvQ9UwmRWMB \
--security-key 9GXxQENbv3fXyD8h \
--server http://localhost:3000 \
--output-dir ./recordings \
--insecureRecord flags
- node-id: 12-character Node peer ID (from
/node/{id}). - security-key: 16-character key from the URL hash (
#...); must match the Node's key for E2EE signaling. - server: Base URL of the uIPCat app (e.g.
http://localhost:3000). - output-dir: Directory for MP4 segments; created if missing. Segments are named with strftime pattern
%Y%m%d_%H%M%S.mp4(e.g.20250309_143022.mp4). - --insecure (optional): Skip TLS certificate verification for the MQTT WSS connection (e.g. self-signed or dev certs). Use only in development.
Record behavior
- Fetches ICE/MQTT config from
GET {server}/api/v1/peer/config/{peerId}(receiver uses a random peer ID). - Connects to MQTT (WSS), optionally with AES-GCM encryption using
securityKey. - Subscribes to
{nodeId}:notifyand sends a WebRTC offer (H.264 video + Opus audio, recv-only, anddc:uipcDataChannel) to the Node via MQTT. - After SDP/ICE exchange, when ICE is connected, RTP is forwarded to local UDP ports and ffmpeg reads them from an SDP file and writes 10-second MP4 segments (video copy, audio transcoded to AAC).
Usage — snapshot
npx tsx src/index.ts snapshot \
--node-id <nodeId> \
--security-key <securityKey> \
--server <baseUrl> \
--output <file>Example:
npx tsx src/index.ts snapshot \
--node-id qBqgepTeuoBU \
--security-key HK1TKprTwSVBKgKz \
--server http://localhost:3000 \
--output ./snapshot.jpgSnapshot flags
- node-id / security-key / server / --insecure: Same as record mode.
- output: Destination file path for the JPEG image. Parent directories are created if missing. If no extension is provided,
.jpgis appended automatically. - --quality (optional, default
0.8): JPEG quality (0–1). - --timeout (optional, default
30000): Timeout in milliseconds for the entire snapshot RPC (includes WebRTC negotiation + capture).
Snapshot behavior
- Fetches ICE/MQTT config (same as record mode).
- Connects to MQTT, then opens a DataChannel-only WebRTC connection to the Node (no media tracks).
- Once the
dc:uipcDataChannel is open, sendssnapshot.captureRPC with the specified quality. - Receives the JPEG image bytes from the Node, saves to
--output, and exits.
Protocol
Signaling and codec choices follow docs/design-docs/signaling-protocol.md: same MQTT topics, message shape, and H.264/Opus usage.
Updating the embedded CA certificate
The MQTT TLS CA is embedded in src/cert-embedded.ts so the published npm package works without shipping a cert/ folder. To refresh it after updating cert/mqtt-fullchain10.pem:
# From uipcat-cli directory
pnpm run embed-cert
# Or from repo root, with explicit path
node uipcat-cli/scripts/embed-cert.js uipcat-cli/cert/mqtt-fullchain10.pemThen run pnpm run build and commit the updated src/cert-embedded.ts.
Troubleshooting
Unexpected local description in signaling state have-local-offer, ignoring
The receiver now guards against starting the call twice (e.g. when MQTT Open and Node "connected" both fire). If you still see this, ensure you are not starting multiple receiver processes for the same node.juice: Send failed, errno=101
errno 101 =ENETUNREACH(network unreachable). ICE is trying to send (e.g. STUN) but the path to the Node is not reachable. Common causes: Node and receiver on different networks without TURN, firewall blocking UDP, or Node not yet connected. Ensure the Node page is open and on the same reachable network (or use a TURN server in config).
