Inject _origin=ws:<clientId> into JSON payloads on inbound WS messages and strip it on broadcast while skipping the origin client. Documents behavior in StreamingAPI.md.
81 lines
2.3 KiB
Markdown
81 lines
2.3 KiB
Markdown
## Streaming API (WebSocket)
|
|
|
|
### Overview
|
|
|
|
The streaming API exposes an event-driven WebSocket at `/ws`. It bridges between external clients and the internal event bus:
|
|
|
|
- Incoming WebSocket JSON `{ event, payload }` → `ctx.fire(event, payload)`
|
|
- Local events → broadcasted to all connected WebSocket clients as `{ event, payload }`
|
|
|
|
This allows real-time control and observation of the system without polling.
|
|
|
|
### URL
|
|
|
|
- `ws://<device-ip>/ws`
|
|
|
|
### Message Format
|
|
|
|
- Client → Device
|
|
```json
|
|
{
|
|
"event": "<event-name>",
|
|
"payload": "<json-string>" | { /* inline JSON */ }
|
|
}
|
|
```
|
|
|
|
- Device → Client
|
|
```json
|
|
{
|
|
"event": "<event-name>",
|
|
"payload": "<json-string>"
|
|
}
|
|
```
|
|
|
|
Notes:
|
|
- The device accepts `payload` as a string or a JSON object/array. Objects are serialized into a string before dispatching to local subscribers to keep a consistent downstream contract.
|
|
- A minimal ack `{ "ok": true }` is sent after a valid inbound message.
|
|
|
|
#### Echo suppression (origin tagging)
|
|
|
|
- To prevent the sender from receiving an immediate echo of its own message, the server injects a private field into JSON payloads:
|
|
- `_origin: "ws:<clientId>"`
|
|
- When re-broadcasting local events to WebSocket clients, the server:
|
|
- Strips the `_origin` field from the outgoing payload
|
|
- Skips the originating `clientId` so only other clients receive the message
|
|
- If a payload is not valid JSON (plain string), no origin tag is injected and the message may be echoed
|
|
|
|
### Event Bus Integration
|
|
|
|
- The WebSocket registers an `onAny` subscriber to `NodeContext` so that all local events are mirrored to clients.
|
|
- Services should subscribe to specific events via `ctx.on("<name>", ...)`.
|
|
|
|
### Examples
|
|
|
|
1) Set a solid color on NeoPattern:
|
|
```json
|
|
{
|
|
"event": "api/neopattern/color",
|
|
"payload": { "color": "#FF0000", "brightness": 128 }
|
|
}
|
|
```
|
|
|
|
2) Broadcast a cluster event (delegated to core):
|
|
```json
|
|
{
|
|
"event": "cluster/broadcast",
|
|
"payload": {
|
|
"event": "api/neopattern/color",
|
|
"data": { "color": "#00FF00", "brightness": 128 }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Reference Implementation
|
|
|
|
- WebSocket setup and bridging are implemented in `ApiServer`.
|
|
- Global event subscription uses `NodeContext::onAny`.
|
|
|
|
Related docs:
|
|
- [`ClusterBroadcast.md`](./ClusterBroadcast.md) — centralized UDP broadcasting and CLUSTER_EVENT format
|
|
|