feat: announce poll lifecycle events to IRC and Kosmi
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>
This commit is contained in:
137
docs/POLL.md
Normal file
137
docs/POLL.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# 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:
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
|
||||
```json
|
||||
{
|
||||
"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.start` always replaces any existing active poll.
|
||||
- `poll.ending` may be followed by either `voting.ended` (normal close) or `poll.ending.cancelled` (countdown aborted).
|
||||
- After `voting.ended`, no further vote-related messages are sent until the next `poll.start`.
|
||||
- `game.started` implicitly deactivates any active poll — no explicit poll message is sent.
|
||||
Reference in New Issue
Block a user