ControllerConnection
Properties
socket(roStreamSocket)mode(string)wsBuffer(roByteArray)httpBuffer(roByteArray) — Accumulates raw bytes across possibly-multiple recv()/poll() calls ' until a full HTTP request (terminated by a blank line) has arrived - ' see onHttpReadable(). A single recv() is not guaranteed to return an ' entire request in one call, especially a real browser's larger ' WebSocket-upgrade request (many headers) split across TCP segments.staleReadPolls(integer) — Consecutive poll() cycles where isReadable() reported true but ' receive() returned a negative "not ready yet" result with no bytes ' actually delivered - see onHttpReadable()/onWebSocketReadable(). Reset ' to 0 on every poll that actually receives bytes. Guards against a dead ' connection (peer vanished without a clean FIN) spinning forever now ' that a negative receive() no longer closes the connection immediately. ' ' IMPORTANT, confirmed on a real 2024 Roku Ultra with an on-device debug ' capture: isReadable() is NOT a reliable "there might be data" signal ' for a websocket socket on this hardware - once a connection has ever ' been read from, isReadable() reports true on essentially every single ' subsequent poll() even while the peer is sitting perfectly idle and ' sends nothing, and receive() returns -1 every one of those times, with ' no gaps and no distinguishing pattern versus an actually-dead peer. ' Concretely: a real browser client left untouched (no stick/button ' movement) hit exactly this and got closed after ~300 consecutive ' negative-receive polls (~5s) - not a hypothetical, this is what a ' silently-idle player looks like on this hardware. There is no signal ' available here to tell "healthy but idle" apart from "actually dead" - ' both look identical. The real fix is the browser client ' (controller-web/index.html) resending its current state once a ' second even when nothing changed, so a healthy connection always has ' real bytes arriving well inside this threshold; staleReadPolls stays ' as a backstop that still reaps a connection whose heartbeats have ' genuinely stopped (tab closed, network gone), just no longer trusted ' to distinguish idle-vs-dead on its own.pendingSend(roByteArray) — Bytes still waiting to be flushed to the socket - see ' ControllerServer.queueSend()/flushPendingSend().invalidwhen ' nothing is queued. roStreamSocket.send() is non-blocking and can ' return 0 ("would block, try again") under ordinary backpressure - ' looping on that in place would busy-spin the whole render loop ' (Roku's socket has no blocking wait), so instead any unsent ' remainder is parked here and retried once per poll() cycle until it ' fully flushes (or a real send error occurs).pendingSendOffset(integer) — How far into pendingSend the previous partial send(s) already got.pendingSendAction(string) — What to do once pendingSend fully flushes - "" (nothing further), ' "close" (close the connection - e.g. after an HTTP response), or ' "upgrade" (flip into websocket mode and emit onOpen - deferred until ' the handshake response has actually reached the client, so a flush ' failure never leaves a "ghost" upgraded connection).
Constructor
new ControllerConnection(): ControllerConnection