Handle poll.start, poll.ending, voting.ended, and poll.ending.cancelled WebSocket messages from the upstream GamePicker API. Broadcasts opening, closing-countdown, and closed announcements with per-bridge bold formatting (IRC control codes vs asterisks for Kosmi). Co-authored-by: Cursor <cursoragent@cursor.com>
3.9 KiB
Poll WebSocket Messages
Overview
Poll messages manage the lifecycle of viewer polls over the upstream WebSocket connection. They are session-scoped — you must be subscribed to the relevant session to send or receive them.
All messages use the standard envelope:
{
"type": "<message-type>",
"data": { ... }
}
Message Flow
poll.start ──► (poll is open, viewers are voting)
│
poll.ending ──► (countdown active)
│
┌───────────┴───────────┐
│ │
voting.ended poll.ending.cancelled
(poll is closed) (countdown aborted,
poll stays open)
Client → Server
poll.start
Triggers poll generation for the active session.
{
"type": "poll.start",
"sessionId": 3
}
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "poll.start" |
sessionId |
number | no | Included for protocol consistency. The server uses its internally tracked session ID. |
Any existing active poll is deactivated and replaced.
poll.leading
Sent by the client to report the current leading option. Typically sent whenever the lead changes.
{
"type": "poll.leading",
"sessionId": 3,
"gameId": 17,
"label": "Quiplash 3",
"votes": 12
}
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | "poll.leading" |
sessionId |
number | yes | Active session ID |
gameId |
number | yes | Game ID of the leading option (0 for "Other") |
label |
string | yes | Display label of the leading option |
votes |
number | yes | Current vote count for the leader |
Server → Client
poll.ending
Signals that voting will close after a countdown. Sent when the server initiates poll closure.
{
"type": "poll.ending",
"data": {
"sessionId": 3,
"endsAt": "2026-05-13T02:20:30.123456789Z",
"delaySeconds": 30
}
}
| Field | Type | Description |
|---|---|---|
sessionId |
number | Active session ID |
endsAt |
string | RFC 3339 timestamp when voting closes |
delaySeconds |
number | Seconds remaining until voting closes |
poll.ending.cancelled
The previously announced countdown has been cancelled. Voting remains open.
{
"type": "poll.ending.cancelled",
"data": {
"sessionId": 3
}
}
| Field | Type | Description |
|---|---|---|
sessionId |
number | Active session ID |
voting.ended
Voting has closed. The active poll is finalized.
{
"type": "voting.ended"
}
No data payload. The server does not include results — consumers should query their own tallies or rely on the preceding poll.leading updates for the final state.
Lifecycle Notes
- A
poll.startalways replaces any existing active poll. poll.endingmay be followed by eithervoting.ended(normal close) orpoll.ending.cancelled(countdown aborted).- After
voting.ended, no further vote-related messages are sent until the nextpoll.start. game.startedimplicitly deactivates any active poll — no explicit poll message is sent.