{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://git.octoturge.com/Continuum/continuum-schemas/raw/branch/main/schemas/continuum/v1/edge/server_message.schema.json", "title": "EdgeServerMessage", "description": "What continuum-backend sends back down the same WebSocket. Discriminated on 'type'; snake_case for the same reason as EdgeClientMessage.", "oneOf": [ { "type": "object", "title": "HelloAck", "properties": { "type": { "const": "hello_ack" }, "session_id": { "type": "string" } }, "required": [ "type", "session_id" ] }, { "type": "object", "title": "HelloError", "description": "The handshake was rejected. The connection closes immediately after this.", "properties": { "type": { "const": "hello_error" }, "message": { "type": "string" } }, "required": [ "type", "message" ] }, { "type": "object", "title": "HeartbeatAck", "properties": { "type": { "const": "heartbeat_ack" }, "seq": { "type": "integer", "minimum": 0, "description": "Echoes the seq from the heartbeat being acknowledged." } }, "required": [ "type", "seq" ] }, { "type": "object", "title": "Command", "description": "Do something to one printer on this gateway. Answered with a client command_result carrying the same command_id.", "properties": { "type": { "const": "command" }, "command_id": { "type": "string" }, "printer_id": { "type": "string", "description": "The gateway's own printer id, not the backend UUID - the backend translates on the way out, so a gateway never has to know its printers' cloud identities." }, "command": { "$ref": "./printer_command.schema.json" } }, "required": [ "type", "command_id", "printer_id", "command" ] }, { "type": "object", "title": "WebrtcOffer", "description": "Relay a browser's WebRTC offer to this gateway's go2rtc and send the answer back. SIGNALLING ONLY - no video crosses this socket. The gateway POSTs the offer to its own go2rtc over loopback (which go2rtc exempts from its API password, so no credential is needed or sent) and returns the answer as a WebrtcAnswer, after which the browser and go2rtc negotiate media directly over ICE. This exists because a browser cannot signal to a gateway itself: measured against go2rtc 1.9.14, it sends no CORS headers on anything and checks its password before CORS, so the unauthenticated preflight a browser always sends gets a 401. Relaying only the signalling keeps the media peer-to-peer - on a workshop LAN it never leaves the building, and go2rtc also offers a server-reflexive candidate, so a remote viewer can often hole-punch straight to the gateway. A TURN server in go2rtc's ice_servers is the fallback for the NATs where that fails; it is configuration, not a change to this protocol.", "properties": { "type": { "const": "webrtc_offer" }, "session_id": { "type": "string", "description": "Correlates the answer with this offer. One negotiation, not a lasting subscription." }, "camera_stream": { "type": "string", "description": "The go2rtc stream name, which is what PrinterTelemetry.camera_stream reported. Deliberately not called a printer id: the two are equal today, but one is the gateway's own naming and the other is the cloud's." }, "sdp": { "type": "string", "description": "The browser's offer SDP, passed through verbatim." } }, "required": [ "type", "session_id", "camera_stream", "sdp" ], "additionalProperties": false } ] }