docs: comprehensive API documentation from source code
Replace existing docs with fresh documentation built entirely from source code analysis. OpenAPI 3.1 spec as source of truth, plus human-readable Markdown with curl examples, response samples, and workflow guides. - OpenAPI 3.1 spec covering all 42 endpoints (validated against source) - 7 endpoint reference docs (auth, games, sessions, picker, stats, votes, webhooks) - WebSocket protocol documentation (auth, subscriptions, 4 event types) - 4 guide documents (getting started, session lifecycle, voting, webhooks) - API README with overview, auth docs, and quick reference table - Old docs archived to docs/archive/ Made-with: Cursor
This commit is contained in:
474
docs/archive/API_QUICK_REFERENCE.md
Normal file
474
docs/archive/API_QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# API Quick Reference
|
||||
|
||||
Quick reference for Live Voting, WebSocket, and Webhook endpoints.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
http://localhost:5000/api
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require JWT authentication:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
Get token via:
|
||||
```bash
|
||||
POST /api/auth/login
|
||||
Body: { "key": "YOUR_ADMIN_KEY" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket Events
|
||||
|
||||
### Connect to WebSocket
|
||||
|
||||
```
|
||||
ws://localhost:5000/api/sessions/live
|
||||
```
|
||||
|
||||
**Message Protocol**:
|
||||
|
||||
```json
|
||||
// Authenticate
|
||||
{ "type": "auth", "token": "YOUR_JWT_TOKEN" }
|
||||
|
||||
// Subscribe to session
|
||||
{ "type": "subscribe", "sessionId": 123 }
|
||||
|
||||
// Unsubscribe
|
||||
{ "type": "unsubscribe", "sessionId": 123 }
|
||||
|
||||
// Heartbeat
|
||||
{ "type": "ping" }
|
||||
```
|
||||
|
||||
**Server Events**:
|
||||
|
||||
```json
|
||||
// Auth success
|
||||
{ "type": "auth_success", "message": "..." }
|
||||
|
||||
// Subscribed
|
||||
{ "type": "subscribed", "sessionId": 123 }
|
||||
|
||||
// Session started
|
||||
{
|
||||
"type": "session.started",
|
||||
"timestamp": "2025-11-01T...",
|
||||
"data": {
|
||||
"session": { "id": 123, "is_active": 1, "created_at": "...", "notes": "..." }
|
||||
}
|
||||
}
|
||||
|
||||
// Game added
|
||||
{
|
||||
"type": "game.added",
|
||||
"timestamp": "2025-11-01T...",
|
||||
"data": {
|
||||
"session": { "id": 123, "is_active": true, "games_played": 5 },
|
||||
"game": { "id": 45, "title": "Fibbage 4", ... }
|
||||
}
|
||||
}
|
||||
|
||||
// Session ended
|
||||
{
|
||||
"type": "session.ended",
|
||||
"timestamp": "2025-11-01T...",
|
||||
"data": {
|
||||
"session": { "id": 123, "is_active": 0, "games_played": 5 }
|
||||
}
|
||||
}
|
||||
|
||||
// Pong
|
||||
{ "type": "pong" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Live Voting
|
||||
|
||||
### Submit Live Vote
|
||||
|
||||
```http
|
||||
POST /api/votes/live
|
||||
```
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"vote": "up" | "down",
|
||||
"timestamp": "2025-11-01T20:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200)**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Vote recorded successfully",
|
||||
"session": { "id": 123, "games_played": 5 },
|
||||
"game": {
|
||||
"id": 45,
|
||||
"title": "Fibbage 4",
|
||||
"upvotes": 46,
|
||||
"downvotes": 3,
|
||||
"popularity_score": 43
|
||||
},
|
||||
"vote": {
|
||||
"username": "TestUser",
|
||||
"type": "up",
|
||||
"timestamp": "2025-11-01T20:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- `400` - Invalid payload
|
||||
- `404` - No active session or timestamp doesn't match any game
|
||||
- `409` - Duplicate vote (within 1 second)
|
||||
|
||||
---
|
||||
|
||||
## Webhooks
|
||||
|
||||
### List Webhooks
|
||||
|
||||
```http
|
||||
GET /api/webhooks
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Kosmi Bot",
|
||||
"url": "http://bot-url/webhook",
|
||||
"events": ["game.added"],
|
||||
"enabled": true,
|
||||
"created_at": "2025-11-01T20:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Get Single Webhook
|
||||
|
||||
```http
|
||||
GET /api/webhooks/:id
|
||||
```
|
||||
|
||||
### Create Webhook
|
||||
|
||||
```http
|
||||
POST /api/webhooks
|
||||
```
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "Kosmi Bot",
|
||||
"url": "http://bot-url/webhook",
|
||||
"secret": "your_shared_secret",
|
||||
"events": ["game.added"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (201)**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Kosmi Bot",
|
||||
"url": "http://bot-url/webhook",
|
||||
"events": ["game.added"],
|
||||
"enabled": true,
|
||||
"created_at": "2025-11-01T20:00:00Z",
|
||||
"message": "Webhook created successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Update Webhook
|
||||
|
||||
```http
|
||||
PATCH /api/webhooks/:id
|
||||
```
|
||||
|
||||
**Request Body** (all fields optional):
|
||||
```json
|
||||
{
|
||||
"name": "Updated Name",
|
||||
"url": "http://new-url/webhook",
|
||||
"secret": "new_secret",
|
||||
"events": ["game.added"],
|
||||
"enabled": false
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Webhook
|
||||
|
||||
```http
|
||||
DELETE /api/webhooks/:id
|
||||
```
|
||||
|
||||
**Response (200)**:
|
||||
```json
|
||||
{
|
||||
"message": "Webhook deleted successfully",
|
||||
"webhookId": 1
|
||||
}
|
||||
```
|
||||
|
||||
### Test Webhook
|
||||
|
||||
```http
|
||||
POST /api/webhooks/test/:id
|
||||
```
|
||||
|
||||
Sends a test `game.added` event to verify webhook is working.
|
||||
|
||||
**Response (200)**:
|
||||
```json
|
||||
{
|
||||
"message": "Test webhook sent",
|
||||
"note": "Check webhook_logs table for delivery status"
|
||||
}
|
||||
```
|
||||
|
||||
### Get Webhook Logs
|
||||
|
||||
```http
|
||||
GET /api/webhooks/:id/logs?limit=50
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"webhook_id": 1,
|
||||
"event_type": "game.added",
|
||||
"payload": { /* full payload */ },
|
||||
"response_status": 200,
|
||||
"error_message": null,
|
||||
"created_at": "2025-11-01T20:30:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Payloads
|
||||
|
||||
### Event: `session.started`
|
||||
|
||||
Sent when a new session is created.
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
||||
- `X-Webhook-Event: session.started`
|
||||
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
||||
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"event": "session.started",
|
||||
"timestamp": "2025-11-01T20:00:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": 1,
|
||||
"created_at": "2025-11-01T20:00:00Z",
|
||||
"notes": "Friday Game Night"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event: `game.added`
|
||||
|
||||
Sent when a game is added to an active session.
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
||||
- `X-Webhook-Event: game.added`
|
||||
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
||||
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"event": "game.added",
|
||||
"timestamp": "2025-11-01T20:30:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": true,
|
||||
"games_played": 5
|
||||
},
|
||||
"game": {
|
||||
"id": 45,
|
||||
"title": "Fibbage 4",
|
||||
"pack_name": "The Jackbox Party Pack 9",
|
||||
"min_players": 2,
|
||||
"max_players": 8,
|
||||
"manually_added": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event: `session.ended`
|
||||
|
||||
Sent when a session is closed/ended.
|
||||
|
||||
**Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
||||
- `X-Webhook-Event: session.ended`
|
||||
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
||||
|
||||
**Payload**:
|
||||
```json
|
||||
{
|
||||
"event": "session.ended",
|
||||
"timestamp": "2025-11-01T20:30:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": 0,
|
||||
"games_played": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## cURL Examples
|
||||
|
||||
### Submit Vote
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/votes/live" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "TestUser",
|
||||
"vote": "up",
|
||||
"timestamp": "2025-11-01T20:30:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
### Create Webhook
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/webhooks" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Kosmi Bot",
|
||||
"url": "http://localhost:3001/webhook/jackbox",
|
||||
"secret": "test_secret_123",
|
||||
"events": ["game.added"]
|
||||
}'
|
||||
```
|
||||
|
||||
### Test Webhook
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
### View Webhook Logs
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:5000/api/webhooks/1/logs?limit=10" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Signature Verification
|
||||
|
||||
**Node.js Example**:
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function verifyWebhookSignature(signature, payload, secret) {
|
||||
if (!signature || !signature.startsWith('sha256=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expectedSignature = 'sha256=' + crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(JSON.stringify(payload))
|
||||
.digest('hex');
|
||||
|
||||
return crypto.timingSafeEqual(
|
||||
Buffer.from(signature),
|
||||
Buffer.from(expectedSignature)
|
||||
);
|
||||
}
|
||||
|
||||
// In your webhook endpoint:
|
||||
app.post('/webhook/jackbox', (req, res) => {
|
||||
const signature = req.headers['x-webhook-signature'];
|
||||
const secret = process.env.WEBHOOK_SECRET;
|
||||
|
||||
if (!verifyWebhookSignature(signature, req.body, secret)) {
|
||||
return res.status(401).send('Invalid signature');
|
||||
}
|
||||
|
||||
// Process webhook...
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 200 | Success |
|
||||
| 201 | Created |
|
||||
| 400 | Bad Request - Invalid payload |
|
||||
| 401 | Unauthorized - Invalid JWT or signature |
|
||||
| 404 | Not Found - Resource doesn't exist |
|
||||
| 409 | Conflict - Duplicate vote |
|
||||
| 500 | Internal Server Error |
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Currently no rate limiting is implemented. Consider implementing rate limiting in production:
|
||||
- Per IP address
|
||||
- Per JWT token
|
||||
- Per webhook endpoint
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always verify webhook signatures** before processing
|
||||
2. **Use HTTPS** for webhook URLs in production
|
||||
3. **Store secrets securely** in environment variables
|
||||
4. **Respond quickly** to webhooks (< 5 seconds)
|
||||
5. **Log webhook activity** for debugging
|
||||
6. **Handle retries gracefully** if implementing retry logic
|
||||
7. **Validate timestamps** to prevent replay attacks
|
||||
|
||||
---
|
||||
|
||||
For detailed documentation, see [BOT_INTEGRATION.md](BOT_INTEGRATION.md)
|
||||
|
||||
758
docs/archive/BOT_INTEGRATION.md
Normal file
758
docs/archive/BOT_INTEGRATION.md
Normal file
@@ -0,0 +1,758 @@
|
||||
# Bot Integration Guide
|
||||
|
||||
This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Live Voting (Bot → API)](#live-voting-bot--api)
|
||||
2. [Game Notifications (API → Bot)](#game-notifications-api--bot)
|
||||
- [WebSocket Integration (Recommended)](#websocket-integration-recommended)
|
||||
- [Webhook Integration](#webhook-integration)
|
||||
3. [Webhook Management](#webhook-management)
|
||||
4. [Testing](#testing)
|
||||
5. [Available Events](#available-events)
|
||||
|
||||
---
|
||||
|
||||
## Live Voting (Bot → API)
|
||||
|
||||
Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat.
|
||||
|
||||
### Endpoint
|
||||
|
||||
```
|
||||
POST /api/votes/live
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Requires JWT token in Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "string", // Username of the voter
|
||||
"vote": "up" | "down", // "up" for thisgame++, "down" for thisgame--
|
||||
"timestamp": "string" // ISO 8601 timestamp (e.g., "2025-11-01T20:30:00Z")
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Success)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Vote recorded successfully",
|
||||
"session": {
|
||||
"id": 123,
|
||||
"games_played": 5
|
||||
},
|
||||
"game": {
|
||||
"id": 45,
|
||||
"title": "Fibbage 4",
|
||||
"upvotes": 46,
|
||||
"downvotes": 3,
|
||||
"popularity_score": 43
|
||||
},
|
||||
"vote": {
|
||||
"username": "TestUser",
|
||||
"type": "up",
|
||||
"timestamp": "2025-11-01T20:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
|
||||
- **400 Bad Request**: Invalid payload or timestamp format
|
||||
- **404 Not Found**: No active session or timestamp doesn't match any game
|
||||
- **409 Conflict**: Duplicate vote (within 1 second of previous vote from same user)
|
||||
- **500 Internal Server Error**: Server error
|
||||
|
||||
### Example Implementation (Node.js)
|
||||
|
||||
```javascript
|
||||
// When bot detects "thisgame++" or "thisgame--" in Kosmi chat
|
||||
async function handleVote(username, message) {
|
||||
const isUpvote = message.includes('thisgame++');
|
||||
const isDownvote = message.includes('thisgame--');
|
||||
|
||||
if (!isUpvote && !isDownvote) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('http://your-api-url/api/votes/live', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.JWT_TOKEN}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
vote: isUpvote ? 'up' : 'down',
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
console.log(`Vote recorded for ${data.game.title}: ${data.game.upvotes}👍 ${data.game.downvotes}👎`);
|
||||
} else {
|
||||
console.error('Vote failed:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending vote:', error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **Deduplication**: Votes from the same user within 1 second are automatically rejected to prevent spam
|
||||
- **Timestamp Matching**: The API matches the vote timestamp to the correct game based on when games were played
|
||||
- **Active Session Required**: Votes can only be recorded when there's an active session with games played
|
||||
|
||||
---
|
||||
|
||||
## Game Notifications (API → Bot)
|
||||
|
||||
The API can notify your bot when games are added to a session, allowing you to announce "Coming up next: Game Title!" in Kosmi chat.
|
||||
|
||||
There are two integration methods available:
|
||||
|
||||
1. **WebSocket (Recommended)**: Real-time bidirectional communication, simpler setup, works through firewalls
|
||||
2. **Webhooks**: Traditional HTTP callbacks, good for serverless/stateless integrations
|
||||
|
||||
### WebSocket Integration (Recommended)
|
||||
|
||||
WebSocket provides real-time event streaming from the API to your bot. This is the recommended approach as it:
|
||||
|
||||
- Works through firewalls and NAT (bot initiates connection)
|
||||
- No need to expose inbound ports
|
||||
- Automatic reconnection on disconnect
|
||||
- Lower latency than webhooks
|
||||
- Bidirectional communication
|
||||
|
||||
#### Connection Flow
|
||||
|
||||
1. Bot connects to WebSocket endpoint
|
||||
2. Bot authenticates with JWT token
|
||||
3. Bot subscribes to active session
|
||||
4. Bot receives `game.added` events in real-time
|
||||
|
||||
#### WebSocket Endpoint
|
||||
|
||||
```
|
||||
wss://your-api-url/api/sessions/live
|
||||
```
|
||||
|
||||
#### Message Protocol
|
||||
|
||||
All messages are JSON-formatted.
|
||||
|
||||
**Client → Server Messages:**
|
||||
|
||||
```json
|
||||
// 1. Authenticate (first message after connecting)
|
||||
{
|
||||
"type": "auth",
|
||||
"token": "YOUR_JWT_TOKEN"
|
||||
}
|
||||
|
||||
// 2. Subscribe to a session
|
||||
{
|
||||
"type": "subscribe",
|
||||
"sessionId": 123
|
||||
}
|
||||
|
||||
// 3. Unsubscribe from a session
|
||||
{
|
||||
"type": "unsubscribe",
|
||||
"sessionId": 123
|
||||
}
|
||||
|
||||
// 4. Heartbeat (keep connection alive)
|
||||
{
|
||||
"type": "ping"
|
||||
}
|
||||
```
|
||||
|
||||
**Server → Client Messages:**
|
||||
|
||||
```json
|
||||
// Authentication success
|
||||
{
|
||||
"type": "auth_success",
|
||||
"message": "Authenticated successfully"
|
||||
}
|
||||
|
||||
// Authentication failure
|
||||
{
|
||||
"type": "auth_error",
|
||||
"message": "Invalid or expired token"
|
||||
}
|
||||
|
||||
// Subscription confirmed
|
||||
{
|
||||
"type": "subscribed",
|
||||
"sessionId": 123,
|
||||
"message": "Subscribed to session 123"
|
||||
}
|
||||
|
||||
// Game added event
|
||||
{
|
||||
"type": "game.added",
|
||||
"timestamp": "2025-11-01T20:30:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": true,
|
||||
"games_played": 5
|
||||
},
|
||||
"game": {
|
||||
"id": 45,
|
||||
"title": "Fibbage 4",
|
||||
"pack_name": "The Jackbox Party Pack 9",
|
||||
"min_players": 2,
|
||||
"max_players": 8,
|
||||
"manually_added": false,
|
||||
"room_code": "JYET"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Session started event (broadcast to all authenticated clients)
|
||||
{
|
||||
"type": "session.started",
|
||||
"timestamp": "2025-11-01T20:00:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": true,
|
||||
"created_at": "2025-11-01T20:00:00Z",
|
||||
"notes": "Friday game night"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Session ended event (broadcast to session subscribers)
|
||||
{
|
||||
"type": "session.ended",
|
||||
"timestamp": "2025-11-01T23:00:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": false,
|
||||
"games_played": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Game started event (broadcast to session subscribers)
|
||||
// Fired when the Jackbox room becomes locked, meaning gameplay has begun
|
||||
{
|
||||
"type": "game.started",
|
||||
"timestamp": "2025-11-01T20:31:00Z",
|
||||
"data": {
|
||||
"sessionId": 123,
|
||||
"gameId": 456,
|
||||
"roomCode": "JYET",
|
||||
"maxPlayers": 8
|
||||
}
|
||||
}
|
||||
|
||||
// Audience joined event (broadcast to session subscribers)
|
||||
// Confirms the app successfully joined a Jackbox room as an audience member
|
||||
{
|
||||
"type": "audience.joined",
|
||||
"timestamp": "2025-11-01T20:31:05Z",
|
||||
"data": {
|
||||
"sessionId": 123,
|
||||
"gameId": 456,
|
||||
"roomCode": "JYET"
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat response
|
||||
{
|
||||
"type": "pong"
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
"type": "error",
|
||||
"message": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
#### Example Implementation (Node.js)
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
class JackboxWebSocketClient {
|
||||
constructor(apiURL, jwtToken) {
|
||||
this.apiURL = apiURL.replace(/^http/, 'ws') + '/api/sessions/live';
|
||||
this.jwtToken = jwtToken;
|
||||
this.ws = null;
|
||||
this.reconnectDelay = 1000;
|
||||
this.maxReconnectDelay = 30000;
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.ws = new WebSocket(this.apiURL);
|
||||
|
||||
this.ws.on('open', () => {
|
||||
console.log('WebSocket connected');
|
||||
this.authenticate();
|
||||
this.startHeartbeat();
|
||||
});
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
this.handleMessage(JSON.parse(data));
|
||||
});
|
||||
|
||||
this.ws.on('close', () => {
|
||||
console.log('WebSocket disconnected, reconnecting...');
|
||||
this.reconnect();
|
||||
});
|
||||
|
||||
this.ws.on('error', (err) => {
|
||||
console.error('WebSocket error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
authenticate() {
|
||||
this.send({ type: 'auth', token: this.jwtToken });
|
||||
}
|
||||
|
||||
subscribe(sessionId) {
|
||||
this.send({ type: 'subscribe', sessionId });
|
||||
}
|
||||
|
||||
send(message) {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
switch (message.type) {
|
||||
case 'auth_success':
|
||||
console.log('Authenticated successfully');
|
||||
// Get active session and subscribe
|
||||
this.getActiveSessionAndSubscribe();
|
||||
break;
|
||||
|
||||
case 'auth_error':
|
||||
console.error('Authentication failed:', message.message);
|
||||
break;
|
||||
|
||||
case 'subscribed':
|
||||
console.log('Subscribed to session:', message.sessionId);
|
||||
break;
|
||||
|
||||
case 'game.added':
|
||||
this.handleGameAdded(message.data);
|
||||
break;
|
||||
|
||||
case 'pong':
|
||||
// Heartbeat response
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error('Server error:', message.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleGameAdded(data) {
|
||||
const { game } = data;
|
||||
|
||||
// Build announcement with room code if available
|
||||
let announcement = `🎮 Coming up next: ${game.title}!`;
|
||||
if (game.room_code) {
|
||||
announcement += ` Join at jackbox.tv with code: ${game.room_code}`;
|
||||
}
|
||||
|
||||
// Send to your chat platform (e.g., Kosmi chat)
|
||||
this.broadcastToChat(announcement);
|
||||
}
|
||||
|
||||
startHeartbeat() {
|
||||
setInterval(() => {
|
||||
this.send({ type: 'ping' });
|
||||
}, 30000); // Every 30 seconds
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
|
||||
}, this.reconnectDelay);
|
||||
}
|
||||
|
||||
async getActiveSessionAndSubscribe() {
|
||||
// Fetch active session from REST API
|
||||
const response = await fetch(`${this.apiURL.replace('/api/sessions/live', '')}/api/sessions/active`, {
|
||||
headers: { 'Authorization': `Bearer ${this.jwtToken}` }
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const session = await response.json();
|
||||
if (session && session.id) {
|
||||
this.subscribe(session.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
broadcastToChat(message) {
|
||||
// Implement your chat platform integration here
|
||||
console.log('Broadcasting:', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const client = new JackboxWebSocketClient('https://your-api-url', 'YOUR_JWT_TOKEN');
|
||||
client.connect();
|
||||
```
|
||||
|
||||
#### Example Implementation (Go)
|
||||
|
||||
See the reference implementation in `irc-kosmi-relay/bridge/jackbox/websocket_client.go`.
|
||||
|
||||
---
|
||||
|
||||
### Webhook Integration
|
||||
|
||||
Webhooks are HTTP callbacks sent from the API to your bot when events occur. This is an alternative to WebSocket for bots that prefer stateless integrations.
|
||||
|
||||
#### Webhook Event: `game.added`
|
||||
|
||||
Triggered whenever a game is added to an active session (either via picker or manual selection).
|
||||
|
||||
### Webhook Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "game.added",
|
||||
"timestamp": "2025-11-01T20:30:00Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 123,
|
||||
"is_active": true,
|
||||
"games_played": 5
|
||||
},
|
||||
"game": {
|
||||
"id": 45,
|
||||
"title": "Fibbage 4",
|
||||
"pack_name": "The Jackbox Party Pack 9",
|
||||
"min_players": 2,
|
||||
"max_players": 8,
|
||||
"manually_added": false,
|
||||
"room_code": "JYET"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** `room_code` is the 4-character Jackbox room code (e.g. `"JYET"`). It will be `null` if no room code was provided when the game was added.
|
||||
|
||||
### Webhook Headers
|
||||
|
||||
The API sends the following headers with each webhook:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `X-Webhook-Signature: sha256=<hmac_signature>` - HMAC-SHA256 signature for verification
|
||||
- `X-Webhook-Event: game.added` - Event type
|
||||
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
||||
|
||||
### Signature Verification
|
||||
|
||||
**IMPORTANT**: Always verify the webhook signature to ensure the request is authentic.
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
function verifyWebhookSignature(signature, payload, secret) {
|
||||
if (!signature || !signature.startsWith('sha256=')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expectedSignature = 'sha256=' + crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(JSON.stringify(payload))
|
||||
.digest('hex');
|
||||
|
||||
// Use timing-safe comparison
|
||||
try {
|
||||
return crypto.timingSafeEqual(
|
||||
Buffer.from(signature),
|
||||
Buffer.from(expectedSignature)
|
||||
);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example Implementation (Express.js)
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const app = express();
|
||||
|
||||
// IMPORTANT: Use express.json() with verify option to get raw body
|
||||
app.use(express.json({
|
||||
verify: (req, res, buf) => {
|
||||
req.rawBody = buf.toString('utf8');
|
||||
}
|
||||
}));
|
||||
|
||||
app.post('/webhook/jackbox', (req, res) => {
|
||||
const signature = req.headers['x-webhook-signature'];
|
||||
const secret = process.env.WEBHOOK_SECRET; // Your webhook secret
|
||||
|
||||
// Verify signature
|
||||
if (!signature || !signature.startsWith('sha256=')) {
|
||||
return res.status(401).send('Missing or invalid signature');
|
||||
}
|
||||
|
||||
const expectedSignature = 'sha256=' + crypto
|
||||
.createHmac('sha256', secret)
|
||||
.update(req.rawBody)
|
||||
.digest('hex');
|
||||
|
||||
// Timing-safe comparison
|
||||
try {
|
||||
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
|
||||
return res.status(401).send('Invalid signature');
|
||||
}
|
||||
} catch (err) {
|
||||
return res.status(401).send('Invalid signature');
|
||||
}
|
||||
|
||||
// Handle the event
|
||||
if (req.body.event === 'game.added') {
|
||||
const game = req.body.data.game;
|
||||
|
||||
// Build announcement with room code if available
|
||||
let message = `🎮 Coming up next: ${game.title}!`;
|
||||
if (game.room_code) {
|
||||
message += ` Join at jackbox.tv with code: ${game.room_code}`;
|
||||
}
|
||||
|
||||
// Send message to Kosmi chat
|
||||
sendKosmiMessage(message);
|
||||
|
||||
console.log(`Announced game: ${game.title} from ${game.pack_name} (code: ${game.room_code || 'N/A'})`);
|
||||
}
|
||||
|
||||
// Always respond with 200 OK
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
|
||||
function sendKosmiMessage(message) {
|
||||
// Your Kosmi chat integration here
|
||||
console.log('Sending to Kosmi:', message);
|
||||
}
|
||||
|
||||
app.listen(3001, () => {
|
||||
console.log('Webhook receiver listening on port 3001');
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Management
|
||||
|
||||
You can manage webhooks through the API using the following endpoints (all require JWT authentication).
|
||||
|
||||
### List All Webhooks
|
||||
|
||||
```bash
|
||||
GET /api/webhooks
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
### Create Webhook
|
||||
|
||||
```bash
|
||||
POST /api/webhooks
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Kosmi Bot",
|
||||
"url": "http://your-bot-url/webhook/jackbox",
|
||||
"secret": "your_shared_secret_key",
|
||||
"events": ["game.added"]
|
||||
}
|
||||
```
|
||||
|
||||
### Update Webhook
|
||||
|
||||
```bash
|
||||
PATCH /api/webhooks/:id
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"enabled": false // Disable webhook
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Webhook
|
||||
|
||||
```bash
|
||||
DELETE /api/webhooks/:id
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
### Test Webhook
|
||||
|
||||
```bash
|
||||
POST /api/webhooks/test/:id
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
Sends a test `game.added` event to verify your webhook is working.
|
||||
|
||||
### View Webhook Logs
|
||||
|
||||
```bash
|
||||
GET /api/webhooks/:id/logs?limit=50
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
```
|
||||
|
||||
Returns recent webhook delivery attempts with status codes and errors.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Live Voting
|
||||
|
||||
```bash
|
||||
# Get your JWT token first
|
||||
curl -X POST "http://localhost:5000/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"apiKey": "YOUR_API_KEY"}'
|
||||
|
||||
# Send a test vote
|
||||
curl -X POST "http://localhost:5000/api/votes/live" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "TestUser",
|
||||
"vote": "up",
|
||||
"timestamp": "2025-11-01T20:30:00Z"
|
||||
}'
|
||||
```
|
||||
|
||||
### Test Webhooks
|
||||
|
||||
```bash
|
||||
# Create a webhook
|
||||
curl -X POST "http://localhost:5000/api/webhooks" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test Webhook",
|
||||
"url": "http://localhost:3001/webhook/jackbox",
|
||||
"secret": "test_secret_123",
|
||||
"events": ["game.added"]
|
||||
}'
|
||||
|
||||
# Test the webhook
|
||||
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
|
||||
# Check webhook logs
|
||||
curl -X GET "http://localhost:5000/api/webhooks/1/logs" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Events
|
||||
|
||||
### Webhook Events
|
||||
|
||||
- `game.added` - Triggered when a game is added to an active session. Includes `room_code` (the 4-character Jackbox join code) if one was provided.
|
||||
|
||||
### WebSocket Events
|
||||
|
||||
- `game.added` - Triggered when a game is added to an active session. Sent to clients subscribed to that session. Includes `room_code`.
|
||||
- `session.started` - Triggered when a new session is created. Broadcast to **all** authenticated clients (no subscription required).
|
||||
- `session.ended` - Triggered when a session is closed. Sent to clients subscribed to that session.
|
||||
- `game.started` - Triggered when the Jackbox room becomes locked (gameplay has begun). Detected by polling the Jackbox REST API every 10 seconds via the room monitor. Sent to clients subscribed to that session. Includes `roomCode` and `maxPlayers`.
|
||||
- `audience.joined` - Triggered when the app successfully joins a Jackbox room as an audience member for player count tracking. Sent to clients subscribed to that session.
|
||||
- `player-count.updated` - Triggered when the player count for a game is updated. Sent to clients subscribed to that session.
|
||||
|
||||
> **Tip:** To receive `session.started` events, your bot only needs to authenticate — no subscription is needed. Once you receive a `session.started` event, subscribe to the new session ID to receive `game.added` and `session.ended` events for it.
|
||||
|
||||
### Event Lifecycle (for a game with room code)
|
||||
|
||||
When a game is added with a room code, events fire in this order:
|
||||
|
||||
1. **`game.added`** — Game added to the session (immediate).
|
||||
2. **`game.started`** — Jackbox room becomes locked, gameplay has begun. Detected by a room monitor that polls the Jackbox REST API every 10 seconds. This is independent of the player count system.
|
||||
3. **`audience.joined`** — The player count bot successfully joined the Jackbox room as an audience member (seconds after `game.started`).
|
||||
4. **`player-count.updated`** (status: `checking`) — Player count data received from the game's WebSocket traffic (ongoing).
|
||||
5. **`player-count.updated`** (status: `completed`) — Game ended, final player count confirmed.
|
||||
|
||||
Room monitoring and player counting are separate systems. The room monitor (`room-monitor.js`) handles steps 1-2 and then hands off to the player count checker (`player-count-checker.js`) for steps 3-5.
|
||||
|
||||
More events may be added in the future (e.g., `vote.recorded`).
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Always verify webhook signatures** - Never trust webhook payloads without verification
|
||||
2. **Use HTTPS in production** - Webhook URLs should use HTTPS to prevent man-in-the-middle attacks
|
||||
3. **Keep secrets secure** - Store webhook secrets in environment variables, never in code
|
||||
4. **Implement rate limiting** - Protect your webhook endpoints from abuse
|
||||
5. **Log webhook activity** - Keep logs of webhook deliveries for debugging
|
||||
6. **Use strong secrets** - Generate cryptographically secure random strings for webhook secrets
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Votes Not Being Recorded
|
||||
|
||||
- Check that there's an active session with games played
|
||||
- Verify the timestamp is within the timeframe of a played game
|
||||
- Ensure you're not sending duplicate votes within 1 second
|
||||
- Check API logs for error messages
|
||||
|
||||
### Webhooks Not Being Received
|
||||
|
||||
- Verify your webhook URL is publicly accessible
|
||||
- Check webhook logs via `/api/webhooks/:id/logs`
|
||||
- Test with `ngrok` or similar tool if developing locally
|
||||
- Ensure your webhook endpoint responds with 200 OK
|
||||
- Check that webhook is enabled in the database
|
||||
|
||||
### Signature Verification Failing
|
||||
|
||||
- Ensure you're using the raw request body for signature verification
|
||||
- Check that the secret matches what's stored in the database
|
||||
- Verify you're using HMAC-SHA256 algorithm
|
||||
- Make sure to prefix with "sha256=" when comparing
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions, contact: cottongin@cottongin.xyz
|
||||
|
||||
105
docs/archive/SESSION_END_QUICK_START.md
Normal file
105
docs/archive/SESSION_END_QUICK_START.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Session End Event - Quick Start Guide
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Listen for Session End Events
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
|
||||
ws.on('open', () => {
|
||||
// 1. Authenticate
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: 'YOUR_JWT_TOKEN'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
if (msg.type === 'auth_success') {
|
||||
// 2. Subscribe to session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: 17
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'session.ended') {
|
||||
// 3. Handle session end
|
||||
console.log('Session ended!');
|
||||
console.log(`Games played: ${msg.data.session.games_played}`);
|
||||
// Announce to your users here
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 📦 Event Format
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.ended",
|
||||
"timestamp": "2025-11-01T02:30:45.123Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 17,
|
||||
"is_active": 0,
|
||||
"games_played": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Test It
|
||||
|
||||
```bash
|
||||
# Get your JWT token first
|
||||
curl -X POST http://localhost:5000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"YOUR_ADMIN_KEY"}'
|
||||
|
||||
# Run the test script
|
||||
node ../tests/test-session-end-websocket.js 17 YOUR_JWT_TOKEN
|
||||
|
||||
# In another terminal, close the session
|
||||
curl -X POST http://localhost:5000/api/sessions/17/close \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
## 🤖 Bot Integration
|
||||
|
||||
When your bot receives `session.ended`:
|
||||
|
||||
```javascript
|
||||
if (msg.type === 'session.ended') {
|
||||
const { id, games_played } = msg.data.session;
|
||||
|
||||
// Announce to IRC/Discord/etc
|
||||
bot.announce(`🌙 Game Night has ended! We played ${games_played} games.`);
|
||||
bot.announce('Thanks for playing!');
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Full Documentation
|
||||
|
||||
See [SESSION_END_WEBSOCKET.md](SESSION_END_WEBSOCKET.md) for complete documentation.
|
||||
|
||||
## ⚡ Key Points
|
||||
|
||||
- ✅ **Instant** - No polling needed
|
||||
- ✅ **Reliable** - Broadcast to all subscribers
|
||||
- ✅ **Simple** - Same format as `game.added`
|
||||
- ✅ **Tested** - Test script included
|
||||
|
||||
## 🔗 Related Events
|
||||
|
||||
| Event | When |
|
||||
|-------|------|
|
||||
| `session.started` | Session created |
|
||||
| `game.added` | Game starts |
|
||||
| `session.ended` | Session closes |
|
||||
| `vote.received` | Vote cast |
|
||||
|
||||
306
docs/archive/SESSION_END_WEBSOCKET.md
Normal file
306
docs/archive/SESSION_END_WEBSOCKET.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Session End WebSocket Event
|
||||
|
||||
This document describes the `session.ended` WebSocket event that is broadcast when a game session is closed.
|
||||
|
||||
## 📋 Event Overview
|
||||
|
||||
When a session is closed (either manually or through timeout), the backend broadcasts a `session.ended` event to all subscribed WebSocket clients. This allows bots and other integrations to react immediately to session closures.
|
||||
|
||||
## 🔌 WebSocket Connection
|
||||
|
||||
**Endpoint:** `ws://localhost:5000/api/sessions/live`
|
||||
|
||||
**Authentication:** Required (JWT token)
|
||||
|
||||
## 📨 Event Format
|
||||
|
||||
### Event Type
|
||||
```
|
||||
session.ended
|
||||
```
|
||||
|
||||
### Full Message Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.ended",
|
||||
"timestamp": "2025-11-01T02:30:45.123Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 17,
|
||||
"is_active": 0,
|
||||
"games_played": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | string | Always `"session.ended"` |
|
||||
| `timestamp` | string | ISO 8601 timestamp when the event was generated |
|
||||
| `data.session.id` | number | The ID of the session that ended |
|
||||
| `data.session.is_active` | number | Always `0` (inactive) for ended sessions |
|
||||
| `data.session.games_played` | number | Total number of games played in the session |
|
||||
|
||||
## 🚀 Implementation
|
||||
|
||||
### Backend Implementation
|
||||
|
||||
The `session.ended` event is automatically broadcast when:
|
||||
|
||||
1. **Manual Session Close**: Admin closes a session via `POST /api/sessions/:id/close`
|
||||
2. **Session Timeout**: (If implemented) When a session times out
|
||||
|
||||
**Code Location:** `backend/routes/sessions.js` - `POST /:id/close` endpoint
|
||||
|
||||
```javascript
|
||||
// Broadcast session.ended event via WebSocket
|
||||
const wsManager = getWebSocketManager();
|
||||
if (wsManager) {
|
||||
const eventData = {
|
||||
session: {
|
||||
id: closedSession.id,
|
||||
is_active: 0,
|
||||
games_played: closedSession.games_played
|
||||
}
|
||||
};
|
||||
|
||||
wsManager.broadcastEvent('session.ended', eventData, parseInt(req.params.id));
|
||||
}
|
||||
```
|
||||
|
||||
### Client Implementation Example
|
||||
|
||||
#### Node.js with `ws` library
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
|
||||
ws.on('open', () => {
|
||||
// Authenticate
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: 'your-jwt-token'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
switch (message.type) {
|
||||
case 'auth_success':
|
||||
// Subscribe to session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: 17
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'session.ended':
|
||||
console.log('Session ended!');
|
||||
console.log(`Session ID: ${message.data.session.id}`);
|
||||
console.log(`Games played: ${message.data.session.games_played}`);
|
||||
// Handle session end (e.g., announce in IRC, Discord, etc.)
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Python with `websockets` library
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
import websockets
|
||||
|
||||
async def listen_for_session_end():
|
||||
uri = "ws://localhost:5000/api/sessions/live"
|
||||
|
||||
async with websockets.connect(uri) as websocket:
|
||||
# Authenticate
|
||||
await websocket.send(json.dumps({
|
||||
"type": "auth",
|
||||
"token": "your-jwt-token"
|
||||
}))
|
||||
|
||||
async for message in websocket:
|
||||
data = json.loads(message)
|
||||
|
||||
if data["type"] == "auth_success":
|
||||
# Subscribe to session
|
||||
await websocket.send(json.dumps({
|
||||
"type": "subscribe",
|
||||
"sessionId": 17
|
||||
}))
|
||||
|
||||
elif data["type"] == "session.ended":
|
||||
session = data["data"]["session"]
|
||||
print(f"Session {session['id']} ended!")
|
||||
print(f"Games played: {session['games_played']}")
|
||||
# Handle session end
|
||||
|
||||
asyncio.run(listen_for_session_end())
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Using the Test Script
|
||||
|
||||
A test script is provided to verify the `session.ended` event:
|
||||
|
||||
```bash
|
||||
node ../tests/test-session-end-websocket.js <session_id> <jwt_token>
|
||||
```
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
node ../tests/test-session-end-websocket.js 17 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
### Manual Testing Steps
|
||||
|
||||
1. **Start the backend server:**
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
2. **Run the test script in another terminal:**
|
||||
```bash
|
||||
node ../tests/test-session-end-websocket.js 17 <your-jwt-token>
|
||||
```
|
||||
|
||||
3. **Close the session in the Picker UI** or via API:
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/sessions/17/close \
|
||||
-H "Authorization: Bearer <your-jwt-token>" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
4. **Verify the event is received** in the test script output
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
🚀 Testing session.ended WebSocket event
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📡 Connecting to: ws://localhost:5000/api/sessions/live
|
||||
🎮 Session ID: 17
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
✅ Connected to WebSocket server
|
||||
|
||||
🔐 Authenticating...
|
||||
✅ Authentication successful
|
||||
|
||||
📻 Subscribing to session 17...
|
||||
✅ Subscribed to session 17
|
||||
|
||||
👂 Listening for session.ended events...
|
||||
(Close the session in the Picker to trigger the event)
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🎉 SESSION.ENDED EVENT RECEIVED!
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📦 Event Data:
|
||||
{
|
||||
"type": "session.ended",
|
||||
"timestamp": "2025-11-01T02:30:45.123Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 17,
|
||||
"is_active": 0,
|
||||
"games_played": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
✨ Event Details:
|
||||
Session ID: 17
|
||||
Active: No
|
||||
Games Played: 5
|
||||
Timestamp: 2025-11-01T02:30:45.123Z
|
||||
|
||||
✅ Test successful! The bot should now announce the session end.
|
||||
```
|
||||
|
||||
## 🤖 Bot Integration
|
||||
|
||||
### IRC/Kosmi Bot Example
|
||||
|
||||
When the bot receives a `session.ended` event, it should:
|
||||
|
||||
1. **Announce the final vote counts** for the last game played
|
||||
2. **Announce that the game night has ended**
|
||||
3. **Optionally display session statistics**
|
||||
|
||||
Example bot response:
|
||||
```
|
||||
🗳️ Final votes for Quiplash 3: 5👍 1👎 (Score: +4)
|
||||
🌙 Game Night has ended! Thanks for playing!
|
||||
📊 Session Stats: 5 games played
|
||||
```
|
||||
|
||||
### Fallback Behavior
|
||||
|
||||
The bot should also implement **polling detection** as a fallback in case the WebSocket connection fails or the event is not received:
|
||||
|
||||
- Poll `GET /api/sessions/active` every 30 seconds
|
||||
- If a previously active session becomes inactive, treat it as a session end
|
||||
- This ensures the bot will always detect session endings, even without WebSocket
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Check WebSocket Logs
|
||||
|
||||
The backend logs WebSocket events:
|
||||
|
||||
```
|
||||
[WebSocket] Client subscribed to session 17
|
||||
[Sessions] Broadcasted session.ended event for session 17
|
||||
[WebSocket] Broadcasted session.ended to 1 client(s) for session 17
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Event not received:**
|
||||
- Verify the client is authenticated (`auth_success` received)
|
||||
- Verify the client is subscribed to the correct session
|
||||
- Check backend logs for broadcast confirmation
|
||||
|
||||
2. **Connection drops:**
|
||||
- Implement ping/pong heartbeat (send `{"type": "ping"}` every 30s)
|
||||
- Handle reconnection logic in your client
|
||||
|
||||
3. **Multiple events received:**
|
||||
- This is normal if multiple clients are subscribed
|
||||
- Each client receives its own copy of the event
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [WebSocket Testing Guide](WEBSOCKET_TESTING.md)
|
||||
- [Bot Integration Guide](BOT_INTEGRATION.md)
|
||||
- [API Quick Reference](API_QUICK_REFERENCE.md)
|
||||
|
||||
## 🔗 Related Events
|
||||
|
||||
| Event Type | Description | When Triggered |
|
||||
|------------|-------------|----------------|
|
||||
| `session.started` | A new session was created | When session is created |
|
||||
| `game.added` | A new game was added to the session | When a game starts |
|
||||
| `session.ended` | The session has ended | When session is closed |
|
||||
| `vote.received` | A vote was cast for a game | When a user votes |
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- The `session.ended` event is broadcast to **all clients subscribed to that session**
|
||||
- The event includes the final `games_played` count for the session
|
||||
- The `is_active` field will always be `0` for ended sessions
|
||||
- The timestamp is in ISO 8601 format with timezone (UTC)
|
||||
|
||||
361
docs/archive/SESSION_START_WEBSOCKET.md
Normal file
361
docs/archive/SESSION_START_WEBSOCKET.md
Normal file
@@ -0,0 +1,361 @@
|
||||
# Session Start WebSocket Event
|
||||
|
||||
This document describes the `session.started` WebSocket event that is broadcast when a new game session is created.
|
||||
|
||||
## 📋 Event Overview
|
||||
|
||||
When a new session is created, the backend broadcasts a `session.started` event to all subscribed WebSocket clients. This allows bots and other integrations to react immediately to new game sessions.
|
||||
|
||||
## 🔌 WebSocket Connection
|
||||
|
||||
**Endpoint:** `ws://localhost:5000/api/sessions/live`
|
||||
|
||||
**Authentication:** Required (JWT token)
|
||||
|
||||
## 📨 Event Format
|
||||
|
||||
### Event Type
|
||||
```
|
||||
session.started
|
||||
```
|
||||
|
||||
### Full Message Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.started",
|
||||
"timestamp": "2025-11-01T20:00:00.123Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 17,
|
||||
"is_active": 1,
|
||||
"created_at": "2025-11-01T20:00:00.123Z",
|
||||
"notes": "Friday Game Night"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | string | Always `"session.started"` |
|
||||
| `timestamp` | string | ISO 8601 timestamp when the event was generated |
|
||||
| `data.session.id` | number | The ID of the newly created session |
|
||||
| `data.session.is_active` | number | Always `1` (active) for new sessions |
|
||||
| `data.session.created_at` | string | ISO 8601 timestamp when the session was created |
|
||||
| `data.session.notes` | string/null | Optional notes for the session |
|
||||
|
||||
## 🚀 Implementation
|
||||
|
||||
### Backend Implementation
|
||||
|
||||
The `session.started` event is automatically broadcast when:
|
||||
|
||||
1. **New Session Created**: Admin creates a session via `POST /api/sessions`
|
||||
|
||||
**Code Location:** `backend/routes/sessions.js` - `POST /` endpoint
|
||||
|
||||
```javascript
|
||||
// Broadcast session.started event via WebSocket
|
||||
const wsManager = getWebSocketManager();
|
||||
if (wsManager) {
|
||||
const eventData = {
|
||||
session: {
|
||||
id: newSession.id,
|
||||
is_active: 1,
|
||||
created_at: newSession.created_at,
|
||||
notes: newSession.notes
|
||||
}
|
||||
};
|
||||
|
||||
wsManager.broadcastEvent('session.started', eventData, parseInt(newSession.id));
|
||||
}
|
||||
```
|
||||
|
||||
### Client Implementation Example
|
||||
|
||||
#### Node.js with `ws` library
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
|
||||
ws.on('open', () => {
|
||||
// Authenticate
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: 'your-jwt-token'
|
||||
}));
|
||||
});
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
switch (message.type) {
|
||||
case 'auth_success':
|
||||
// Subscribe to the new session (or subscribe when you receive session.started)
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: 17
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'session.started':
|
||||
console.log('New session started!');
|
||||
console.log(`Session ID: ${message.data.session.id}`);
|
||||
console.log(`Created at: ${message.data.session.created_at}`);
|
||||
if (message.data.session.notes) {
|
||||
console.log(`Notes: ${message.data.session.notes}`);
|
||||
}
|
||||
|
||||
// Auto-subscribe to the new session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: message.data.session.id
|
||||
}));
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Python with `websockets` library
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
import websockets
|
||||
|
||||
async def listen_for_session_start():
|
||||
uri = "ws://localhost:5000/api/sessions/live"
|
||||
|
||||
async with websockets.connect(uri) as websocket:
|
||||
# Authenticate
|
||||
await websocket.send(json.dumps({
|
||||
"type": "auth",
|
||||
"token": "your-jwt-token"
|
||||
}))
|
||||
|
||||
async for message in websocket:
|
||||
data = json.loads(message)
|
||||
|
||||
if data["type"] == "auth_success":
|
||||
print("Authenticated, waiting for sessions...")
|
||||
|
||||
elif data["type"] == "session.started":
|
||||
session = data["data"]["session"]
|
||||
print(f"🎮 New session started! ID: {session['id']}")
|
||||
print(f"📅 Created: {session['created_at']}")
|
||||
if session.get('notes'):
|
||||
print(f"📝 Notes: {session['notes']}")
|
||||
|
||||
# Auto-subscribe to the new session
|
||||
await websocket.send(json.dumps({
|
||||
"type": "subscribe",
|
||||
"sessionId": session["id"]
|
||||
}))
|
||||
|
||||
asyncio.run(listen_for_session_start())
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Manual Testing Steps
|
||||
|
||||
1. **Start the backend server:**
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
2. **Connect a WebSocket client** (use the test script or your own):
|
||||
```bash
|
||||
# You can modify ../tests/test-session-end-websocket.js to listen for session.started
|
||||
```
|
||||
|
||||
3. **Create a new session** in the Picker UI or via API:
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/sessions \
|
||||
-H "Authorization: Bearer <your-jwt-token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notes": "Friday Game Night"}'
|
||||
```
|
||||
|
||||
4. **Verify the event is received** by your WebSocket client
|
||||
|
||||
### Expected Event
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.started",
|
||||
"timestamp": "2025-11-01T20:00:00.123Z",
|
||||
"data": {
|
||||
"session": {
|
||||
"id": 18,
|
||||
"is_active": 1,
|
||||
"created_at": "2025-11-01T20:00:00.123Z",
|
||||
"notes": "Friday Game Night"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🤖 Bot Integration
|
||||
|
||||
### IRC/Kosmi Bot Example
|
||||
|
||||
When the bot receives a `session.started` event, it should:
|
||||
|
||||
1. **Announce the new game session** to users
|
||||
2. **Auto-subscribe to the session** to receive game.added and session.ended events
|
||||
3. **Optionally display session info** (notes, ID, etc.)
|
||||
|
||||
Example bot response:
|
||||
```
|
||||
🎮 Game Night has started! Session #18
|
||||
📝 Friday Game Night
|
||||
🗳️ Vote with thisgame++ or thisgame-- during games!
|
||||
```
|
||||
|
||||
### Implementation Example
|
||||
|
||||
```javascript
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
if (msg.type === 'session.started') {
|
||||
const { id, notes, created_at } = msg.data.session;
|
||||
|
||||
// Announce to IRC/Discord/etc
|
||||
bot.announce(`🎮 Game Night has started! Session #${id}`);
|
||||
if (notes) {
|
||||
bot.announce(`📝 ${notes}`);
|
||||
}
|
||||
bot.announce('🗳️ Vote with thisgame++ or thisgame-- during games!');
|
||||
|
||||
// Auto-subscribe to this session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: id
|
||||
}));
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Check WebSocket Logs
|
||||
|
||||
The backend logs WebSocket events:
|
||||
|
||||
```
|
||||
[Sessions] Broadcasted session.started event for session 18
|
||||
[WebSocket] Broadcasted session.started to 1 client(s) for session 18
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Event not received:**
|
||||
- Verify the client is authenticated (`auth_success` received)
|
||||
- Check backend logs for broadcast confirmation
|
||||
- **No subscription required** - All authenticated clients automatically receive `session.started` events
|
||||
- Make sure your WebSocket connection is open and authenticated
|
||||
|
||||
2. **Missing session data:**
|
||||
- Check if the session was created successfully
|
||||
- Verify the API response includes all fields
|
||||
|
||||
3. **Duplicate events:**
|
||||
- Normal if multiple clients are connected
|
||||
- Each client receives its own copy of the event
|
||||
|
||||
## 📚 Related Documentation
|
||||
|
||||
- [Session End WebSocket Event](SESSION_END_WEBSOCKET.md)
|
||||
- [WebSocket Testing Guide](WEBSOCKET_TESTING.md)
|
||||
- [Bot Integration Guide](BOT_INTEGRATION.md)
|
||||
- [API Quick Reference](API_QUICK_REFERENCE.md)
|
||||
|
||||
## 🔗 Session Lifecycle Events
|
||||
|
||||
```
|
||||
session.started
|
||||
↓
|
||||
game.added (multiple times)
|
||||
↓
|
||||
vote.received (during each game)
|
||||
↓
|
||||
session.ended
|
||||
```
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- The `session.started` event is broadcast to **all authenticated clients** (not just subscribed ones)
|
||||
- **No subscription required** - All authenticated clients automatically receive this event
|
||||
- Clients should auto-subscribe to the new session to receive subsequent `game.added` and `vote.received` events
|
||||
- The `is_active` field will always be `1` for new sessions
|
||||
- The `notes` field may be `null` if no notes were provided
|
||||
- The timestamp is in ISO 8601 format with timezone (UTC)
|
||||
|
||||
## 💡 Use Cases
|
||||
|
||||
1. **Bot Announcements** - Notify users when game night starts
|
||||
2. **Auto-Subscription** - Automatically subscribe to new sessions
|
||||
3. **Session Tracking** - Track all sessions in external systems
|
||||
4. **Analytics** - Log session creation times and frequency
|
||||
5. **Notifications** - Send push notifications to users
|
||||
|
||||
## 🎯 Best Practices
|
||||
|
||||
1. **Auto-subscribe** to new sessions when you receive `session.started`
|
||||
2. **Store the session ID** for later reference
|
||||
3. **Handle reconnections** gracefully (you might miss the event)
|
||||
4. **Use polling as fallback** to detect sessions created while disconnected
|
||||
5. **Validate session data** before processing
|
||||
|
||||
## 🔄 Complete Event Flow Example
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
const ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
|
||||
let currentSessionId = null;
|
||||
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
switch (msg.type) {
|
||||
case 'session.started':
|
||||
currentSessionId = msg.data.session.id;
|
||||
console.log(`🎮 Session ${currentSessionId} started!`);
|
||||
|
||||
// Auto-subscribe
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: currentSessionId
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'game.added':
|
||||
console.log(`🎲 New game: ${msg.data.game.title}`);
|
||||
break;
|
||||
|
||||
case 'vote.received':
|
||||
console.log(`🗳️ Vote: ${msg.data.vote.type}`);
|
||||
break;
|
||||
|
||||
case 'session.ended':
|
||||
console.log(`🌙 Session ${msg.data.session.id} ended!`);
|
||||
console.log(`📊 Games played: ${msg.data.session.games_played}`);
|
||||
currentSessionId = null;
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## ✨ Conclusion
|
||||
|
||||
The `session.started` WebSocket event provides instant notification when new game sessions are created, allowing bots and integrations to react immediately and provide a seamless user experience.
|
||||
|
||||
256
docs/archive/WEBSOCKET_FLOW_DIAGRAM.md
Normal file
256
docs/archive/WEBSOCKET_FLOW_DIAGRAM.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# WebSocket Event Flow Diagram
|
||||
|
||||
## 🔄 Complete Session Lifecycle
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ BOT CONNECTS │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Bot → Server: { type: "auth", token: "..." } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → Bot: { type: "auth_success" } │
|
||||
│ ✅ Bot is now AUTHENTICATED │
|
||||
│ ⏳ Bot waits... (no subscription yet) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ADMIN CREATES SESSION │
|
||||
│ POST /api/sessions { notes: "Friday Game Night" } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → ALL AUTHENTICATED CLIENTS: │
|
||||
│ { │
|
||||
│ type: "session.started", │
|
||||
│ data: { │
|
||||
│ session: { id: 22, is_active: 1, ... } │
|
||||
│ } │
|
||||
│ } │
|
||||
│ 📢 Broadcast to ALL (no subscription needed) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Bot receives session.started │
|
||||
│ 🎮 Bot announces: "Game Night #22 has started!" │
|
||||
│ │
|
||||
│ Bot → Server: { type: "subscribe", sessionId: 22 } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → Bot: { type: "subscribed", sessionId: 22 } │
|
||||
│ ✅ Bot is now SUBSCRIBED to session 22 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ADMIN ADDS GAME │
|
||||
│ POST /api/sessions/22/games { game_id: 45 } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → SUBSCRIBED CLIENTS (session 22): │
|
||||
│ { │
|
||||
│ type: "game.added", │
|
||||
│ data: { │
|
||||
│ game: { title: "Quiplash 3", ... } │
|
||||
│ } │
|
||||
│ } │
|
||||
│ 📢 Only to subscribers of session 22 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Bot receives game.added │
|
||||
│ 🎲 Bot announces: "Now playing: Quiplash 3" │
|
||||
│ 🗳️ Bot announces: "Vote with thisgame++ or thisgame--" │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ USER VOTES │
|
||||
│ POST /api/votes/live { username: "Alice", vote: "up" } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → SUBSCRIBED CLIENTS (session 22): │
|
||||
│ { │
|
||||
│ type: "vote.received", │
|
||||
│ data: { │
|
||||
│ vote: { username: "Alice", type: "up" } │
|
||||
│ } │
|
||||
│ } │
|
||||
│ 📢 Only to subscribers of session 22 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Bot receives vote.received │
|
||||
│ 🗳️ Bot tracks vote (may announce later) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
(more games and votes...)
|
||||
↓
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ADMIN CLOSES SESSION │
|
||||
│ POST /api/sessions/22/close │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Server → SUBSCRIBED CLIENTS (session 22): │
|
||||
│ { │
|
||||
│ type: "session.ended", │
|
||||
│ data: { │
|
||||
│ session: { id: 22, is_active: 0, games_played: 5 } │
|
||||
│ } │
|
||||
│ } │
|
||||
│ 📢 Only to subscribers of session 22 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Bot receives session.ended │
|
||||
│ 🗳️ Bot announces: "Final votes for Quiplash 3: 5👍 1👎" │
|
||||
│ 🌙 Bot announces: "Game Night ended! 5 games played" │
|
||||
│ ⏳ Bot waits for next session.started... │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 📊 Broadcast Scope Comparison
|
||||
|
||||
### session.started (Global Broadcast)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SERVER │
|
||||
│ │
|
||||
│ broadcastToAll('session.started', data) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓ ↓ ↓
|
||||
┌──────────┴───────────┴───────────┴──────────┐
|
||||
↓ ↓ ↓
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│ Bot A │ │ Bot B │ │ Bot C │
|
||||
│ ✅ │ │ ✅ │ │ ✅ │
|
||||
└────────┘ └────────┘ └────────┘
|
||||
|
||||
ALL authenticated clients receive it
|
||||
(no subscription required)
|
||||
```
|
||||
|
||||
### game.added, vote.received, session.ended (Session-Specific)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ SERVER │
|
||||
│ │
|
||||
│ broadcastEvent('game.added', data, sessionId: 22) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────┴─────────┐
|
||||
↓ ↓
|
||||
┌────────┐ ┌────────┐ ┌────────┐
|
||||
│ Bot A │ │ Bot B │ │ Bot C │
|
||||
│ ✅ │ │ ❌ │ │ ✅ │
|
||||
│subscr. │ │ not │ │subscr. │
|
||||
│sess 22 │ │subscr. │ │sess 22 │
|
||||
└────────┘ └────────┘ └────────┘
|
||||
|
||||
ONLY subscribers to session 22 receive it
|
||||
```
|
||||
|
||||
## 🎯 Bot State Machine
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ DISCONNECTED│
|
||||
└──────┬──────┘
|
||||
│ connect()
|
||||
↓
|
||||
┌─────────────┐
|
||||
│ CONNECTED │
|
||||
└──────┬──────┘
|
||||
│ send auth
|
||||
↓
|
||||
┌─────────────┐
|
||||
│AUTHENTICATED│ ← Wait here for session.started
|
||||
└──────┬──────┘ (no subscription yet)
|
||||
│
|
||||
│ receive session.started
|
||||
↓
|
||||
┌─────────────┐
|
||||
│ WAITING │
|
||||
│ TO │
|
||||
│ SUBSCRIBE │
|
||||
└──────┬──────┘
|
||||
│ send subscribe
|
||||
↓
|
||||
┌─────────────┐
|
||||
│ SUBSCRIBED │ ← Now receive game.added, vote.received, session.ended
|
||||
└──────┬──────┘
|
||||
│
|
||||
│ receive session.ended
|
||||
↓
|
||||
┌─────────────┐
|
||||
│AUTHENTICATED│ ← Back to waiting for next session.started
|
||||
└─────────────┘ (still authenticated, but not subscribed)
|
||||
```
|
||||
|
||||
## 🔍 Event Flow by Subscription Status
|
||||
|
||||
### Before Subscription (Just Authenticated)
|
||||
|
||||
```
|
||||
Server Events: Bot Receives:
|
||||
───────────── ─────────────
|
||||
session.started ✅ YES (broadcast to all)
|
||||
game.added ❌ NO (not subscribed yet)
|
||||
vote.received ❌ NO (not subscribed yet)
|
||||
session.ended ❌ NO (not subscribed yet)
|
||||
```
|
||||
|
||||
### After Subscription (Subscribed to Session 22)
|
||||
|
||||
```
|
||||
Server Events: Bot Receives:
|
||||
───────────── ─────────────
|
||||
session.started ✅ YES (still broadcast to all)
|
||||
game.added ✅ YES (subscribed to session 22)
|
||||
vote.received ✅ YES (subscribed to session 22)
|
||||
session.ended ✅ YES (subscribed to session 22)
|
||||
```
|
||||
|
||||
## 🎮 Multiple Sessions Example
|
||||
|
||||
```
|
||||
Time Event Bot A (sess 22) Bot B (sess 23)
|
||||
──── ───── ─────────────── ───────────────
|
||||
10:00 session.started (sess 22) ✅ Receives ✅ Receives
|
||||
10:01 Bot A subscribes to sess 22 ✅ Subscribed ❌ Not subscribed
|
||||
10:02 game.added (sess 22) ✅ Receives ❌ Doesn't receive
|
||||
10:05 session.started (sess 23) ✅ Receives ✅ Receives
|
||||
10:06 Bot B subscribes to sess 23 ✅ Still sess 22 ✅ Subscribed
|
||||
10:07 game.added (sess 22) ✅ Receives ❌ Doesn't receive
|
||||
10:08 game.added (sess 23) ❌ Doesn't receive ✅ Receives
|
||||
10:10 session.ended (sess 22) ✅ Receives ❌ Doesn't receive
|
||||
10:15 session.ended (sess 23) ❌ Doesn't receive ✅ Receives
|
||||
```
|
||||
|
||||
## 📝 Quick Reference
|
||||
|
||||
| Event | Broadcast Method | Scope | Subscription Required? |
|
||||
|-------|------------------|-------|------------------------|
|
||||
| `session.started` | `broadcastToAll()` | All authenticated clients | ❌ NO |
|
||||
| `game.added` | `broadcastEvent()` | Session subscribers only | ✅ YES |
|
||||
| `vote.received` | `broadcastEvent()` | Session subscribers only | ✅ YES |
|
||||
| `session.ended` | `broadcastEvent()` | Session subscribers only | ✅ YES |
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [WEBSOCKET_SUBSCRIPTION_GUIDE.md](WEBSOCKET_SUBSCRIPTION_GUIDE.md) - Detailed subscription guide
|
||||
- [SESSION_START_WEBSOCKET.md](SESSION_START_WEBSOCKET.md) - session.started event
|
||||
- [SESSION_END_WEBSOCKET.md](SESSION_END_WEBSOCKET.md) - session.ended event
|
||||
- [BOT_INTEGRATION.md](BOT_INTEGRATION.md) - Bot integration guide
|
||||
|
||||
310
docs/archive/WEBSOCKET_SUBSCRIPTION_GUIDE.md
Normal file
310
docs/archive/WEBSOCKET_SUBSCRIPTION_GUIDE.md
Normal file
@@ -0,0 +1,310 @@
|
||||
# WebSocket Subscription Guide
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
This guide explains how WebSocket subscriptions work in the Jackbox Game Picker and which events require subscriptions.
|
||||
|
||||
## 🔌 Connection & Authentication
|
||||
|
||||
### 1. Connect to WebSocket
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
```
|
||||
|
||||
### 2. Authenticate
|
||||
|
||||
```javascript
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: 'YOUR_JWT_TOKEN'
|
||||
}));
|
||||
```
|
||||
|
||||
### 3. Wait for Auth Success
|
||||
|
||||
```javascript
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
if (msg.type === 'auth_success') {
|
||||
console.log('Authenticated!');
|
||||
// Now you can subscribe to sessions
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 📨 Event Types & Subscription Requirements
|
||||
|
||||
| Event Type | Requires Subscription? | Broadcast To | When to Subscribe |
|
||||
|------------|------------------------|--------------|-------------------|
|
||||
| `session.started` | ❌ **NO** | All authenticated clients | N/A - Automatic |
|
||||
| `game.added` | ✅ **YES** | Subscribed clients only | After session.started |
|
||||
| `vote.received` | ✅ **YES** | Subscribed clients only | After session.started |
|
||||
| `session.ended` | ✅ **YES** | Subscribed clients only | After session.started |
|
||||
|
||||
## 🎯 Subscription Strategy
|
||||
|
||||
### Strategy 1: Auto-Subscribe to New Sessions (Recommended for Bots)
|
||||
|
||||
```javascript
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
// After authentication
|
||||
if (msg.type === 'auth_success') {
|
||||
console.log('✅ Authenticated');
|
||||
}
|
||||
|
||||
// Auto-subscribe to new sessions
|
||||
if (msg.type === 'session.started') {
|
||||
const sessionId = msg.data.session.id;
|
||||
console.log(`🎮 New session ${sessionId} started!`);
|
||||
|
||||
// Subscribe to this session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: sessionId
|
||||
}));
|
||||
}
|
||||
|
||||
// Now you'll receive game.added, vote.received, and session.ended
|
||||
if (msg.type === 'game.added') {
|
||||
console.log(`🎲 Game: ${msg.data.game.title}`);
|
||||
}
|
||||
|
||||
if (msg.type === 'session.ended') {
|
||||
console.log(`🌙 Session ended! ${msg.data.session.games_played} games played`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Strategy 2: Subscribe to Active Session on Connect
|
||||
|
||||
```javascript
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
if (msg.type === 'auth_success') {
|
||||
console.log('✅ Authenticated');
|
||||
|
||||
// Fetch active session from API
|
||||
fetch('http://localhost:5000/api/sessions/active')
|
||||
.then(res => res.json())
|
||||
.then(session => {
|
||||
if (session && session.id) {
|
||||
// Subscribe to active session
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: session.id
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Strategy 3: Subscribe to Specific Session
|
||||
|
||||
```javascript
|
||||
ws.on('message', (data) => {
|
||||
const msg = JSON.parse(data.toString());
|
||||
|
||||
if (msg.type === 'auth_success') {
|
||||
console.log('✅ Authenticated');
|
||||
|
||||
// Subscribe to specific session
|
||||
const sessionId = 17;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: sessionId
|
||||
}));
|
||||
}
|
||||
|
||||
if (msg.type === 'subscribed') {
|
||||
console.log(`✅ Subscribed to session ${msg.sessionId}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## 🔄 Complete Bot Flow
|
||||
|
||||
```javascript
|
||||
const WebSocket = require('ws');
|
||||
|
||||
class JackboxBot {
|
||||
constructor(token) {
|
||||
this.token = token;
|
||||
this.ws = null;
|
||||
this.currentSessionId = null;
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.ws = new WebSocket('ws://localhost:5000/api/sessions/live');
|
||||
|
||||
this.ws.on('open', () => {
|
||||
console.log('🔌 Connected to WebSocket');
|
||||
this.authenticate();
|
||||
});
|
||||
|
||||
this.ws.on('message', (data) => {
|
||||
this.handleMessage(JSON.parse(data.toString()));
|
||||
});
|
||||
}
|
||||
|
||||
authenticate() {
|
||||
this.ws.send(JSON.stringify({
|
||||
type: 'auth',
|
||||
token: this.token
|
||||
}));
|
||||
}
|
||||
|
||||
handleMessage(msg) {
|
||||
switch (msg.type) {
|
||||
case 'auth_success':
|
||||
console.log('✅ Authenticated');
|
||||
// Don't subscribe yet - wait for session.started
|
||||
break;
|
||||
|
||||
case 'session.started':
|
||||
this.currentSessionId = msg.data.session.id;
|
||||
console.log(`🎮 Session ${this.currentSessionId} started!`);
|
||||
|
||||
// Auto-subscribe to this session
|
||||
this.ws.send(JSON.stringify({
|
||||
type: 'subscribe',
|
||||
sessionId: this.currentSessionId
|
||||
}));
|
||||
|
||||
// Announce to users
|
||||
this.announce(`Game Night has started! Session #${this.currentSessionId}`);
|
||||
break;
|
||||
|
||||
case 'subscribed':
|
||||
console.log(`✅ Subscribed to session ${msg.sessionId}`);
|
||||
break;
|
||||
|
||||
case 'game.added':
|
||||
console.log(`🎲 Game: ${msg.data.game.title}`);
|
||||
this.announce(`Now playing: ${msg.data.game.title}`);
|
||||
break;
|
||||
|
||||
case 'vote.received':
|
||||
console.log(`🗳️ Vote: ${msg.data.vote.type}`);
|
||||
break;
|
||||
|
||||
case 'session.ended':
|
||||
console.log(`🌙 Session ${msg.data.session.id} ended`);
|
||||
this.announce(`Game Night ended! ${msg.data.session.games_played} games played`);
|
||||
this.currentSessionId = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
announce(message) {
|
||||
// Send to IRC/Discord/Kosmi/etc
|
||||
console.log(`📢 ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const bot = new JackboxBot('YOUR_JWT_TOKEN');
|
||||
bot.connect();
|
||||
```
|
||||
|
||||
## 📊 Subscription Lifecycle
|
||||
|
||||
```
|
||||
1. Connect to WebSocket
|
||||
↓
|
||||
2. Send auth message
|
||||
↓
|
||||
3. Receive auth_success
|
||||
↓
|
||||
4. Wait for session.started (no subscription needed)
|
||||
↓
|
||||
5. Receive session.started
|
||||
↓
|
||||
6. Send subscribe message with sessionId
|
||||
↓
|
||||
7. Receive subscribed confirmation
|
||||
↓
|
||||
8. Now receive: game.added, vote.received, session.ended
|
||||
↓
|
||||
9. Receive session.ended
|
||||
↓
|
||||
10. Wait for next session.started (repeat from step 4)
|
||||
```
|
||||
|
||||
## 🔍 Debugging
|
||||
|
||||
### Check What You're Subscribed To
|
||||
|
||||
The WebSocket manager tracks subscriptions. Check backend logs:
|
||||
|
||||
```
|
||||
[WebSocket] Client subscribed to session 17
|
||||
[WebSocket] Client unsubscribed from session 17
|
||||
```
|
||||
|
||||
### Verify Event Reception
|
||||
|
||||
**session.started** - Should receive immediately after authentication (no subscription needed):
|
||||
```
|
||||
[WebSocket] Broadcasted session.started to 2 authenticated client(s)
|
||||
```
|
||||
|
||||
**game.added, vote.received, session.ended** - Only after subscribing:
|
||||
```
|
||||
[WebSocket] Broadcasted game.added to 1 client(s) for session 17
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Not receiving session.started:**
|
||||
- ✅ Are you authenticated?
|
||||
- ✅ Is your WebSocket connection open?
|
||||
- ✅ Check backend logs for broadcast confirmation
|
||||
|
||||
2. **Not receiving game.added:**
|
||||
- ✅ Did you subscribe to the session?
|
||||
- ✅ Did you receive `subscribed` confirmation?
|
||||
- ✅ Is the session ID correct?
|
||||
|
||||
3. **Not receiving session.ended:**
|
||||
- ✅ Are you still subscribed to the session?
|
||||
- ✅ Did the session actually close?
|
||||
- ✅ Check backend logs
|
||||
|
||||
## 🎯 Best Practices
|
||||
|
||||
1. **Auto-subscribe to new sessions** when you receive `session.started`
|
||||
2. **Don't subscribe before session.started** - there's nothing to subscribe to yet
|
||||
3. **Handle reconnections** - re-authenticate and re-subscribe on reconnect
|
||||
4. **Use polling as fallback** - poll `/api/sessions/active` every 30s as backup
|
||||
5. **Unsubscribe when done** - clean up subscriptions when you're done with a session
|
||||
6. **Validate session IDs** - make sure the session exists before subscribing
|
||||
|
||||
## 📝 Summary
|
||||
|
||||
### ❌ No Subscription Required
|
||||
- `session.started` - Broadcast to **all authenticated clients**
|
||||
|
||||
### ✅ Subscription Required
|
||||
- `game.added` - Only to **subscribed clients**
|
||||
- `vote.received` - Only to **subscribed clients**
|
||||
- `session.ended` - Only to **subscribed clients**
|
||||
|
||||
### 🎯 Recommended Flow
|
||||
1. Authenticate
|
||||
2. Wait for `session.started` (automatic)
|
||||
3. Subscribe to the session
|
||||
4. Receive `game.added`, `vote.received`, `session.ended`
|
||||
5. Repeat from step 2 for next session
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [SESSION_START_WEBSOCKET.md](SESSION_START_WEBSOCKET.md) - session.started event details
|
||||
- [SESSION_END_WEBSOCKET.md](SESSION_END_WEBSOCKET.md) - session.ended event details
|
||||
- [API_QUICK_REFERENCE.md](API_QUICK_REFERENCE.md) - API reference
|
||||
- [BOT_INTEGRATION.md](BOT_INTEGRATION.md) - Bot integration guide
|
||||
|
||||
239
docs/archive/WEBSOCKET_TESTING.md
Normal file
239
docs/archive/WEBSOCKET_TESTING.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# WebSocket Integration Testing Guide
|
||||
|
||||
This guide walks you through testing the WebSocket event system for game notifications.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Backend API running with WebSocket support
|
||||
2. Valid JWT token for authentication
|
||||
3. Active session with games (or ability to create one)
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### Step 1: Install Backend Dependencies
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
```
|
||||
|
||||
This will install the `ws` package that was added to `package.json`.
|
||||
|
||||
### Step 2: Start the Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm start
|
||||
```
|
||||
|
||||
You should see:
|
||||
```
|
||||
Server is running on port 5000
|
||||
WebSocket server available at ws://localhost:5000/api/sessions/live
|
||||
[WebSocket] WebSocket server initialized on /api/sessions/live
|
||||
```
|
||||
|
||||
### Step 3: Get JWT Token
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key":"YOUR_ADMIN_KEY"}'
|
||||
```
|
||||
|
||||
Save the token from the response.
|
||||
|
||||
### Step 4: Test WebSocket Connection
|
||||
|
||||
Run the test script:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
JWT_TOKEN="your_token_here" node test-websocket.js
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
🚀 WebSocket Test Client
|
||||
═══════════════════════════════════════════════════════
|
||||
Connecting to: ws://localhost:5000/api/sessions/live
|
||||
|
||||
✅ Connected to WebSocket server
|
||||
|
||||
📝 Step 1: Authenticating...
|
||||
✅ Authentication successful
|
||||
|
||||
📝 Step 2: Subscribing to session 1...
|
||||
✅ Subscribed to session 1
|
||||
|
||||
🎧 Listening for events...
|
||||
Add a game in the Picker page to see events here
|
||||
Press Ctrl+C to exit
|
||||
```
|
||||
|
||||
### Step 5: Test Game Added Event
|
||||
|
||||
1. Keep the WebSocket test client running
|
||||
2. Open the web app in your browser
|
||||
3. Go to the Picker page
|
||||
4. Add a game to the session
|
||||
|
||||
You should see in the test client:
|
||||
```
|
||||
🎮 GAME ADDED EVENT RECEIVED!
|
||||
═══════════════════════════════════════════════════════
|
||||
Game: Fibbage 4
|
||||
Pack: The Jackbox Party Pack 9
|
||||
Players: 2-8
|
||||
Session ID: 1
|
||||
Games Played: 1
|
||||
Timestamp: 2025-11-01T...
|
||||
═══════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
### Step 6: Test Bot Integration
|
||||
|
||||
If you're using the `irc-kosmi-relay` bot:
|
||||
|
||||
1. Make sure `UseWebSocket=true` in `matterbridge.toml`
|
||||
2. Build and run the bot:
|
||||
```bash
|
||||
cd irc-kosmi-relay
|
||||
go build
|
||||
./matterbridge -conf matterbridge.toml
|
||||
```
|
||||
|
||||
3. Look for these log messages:
|
||||
```
|
||||
INFO Jackbox integration initialized successfully
|
||||
INFO Connecting to WebSocket: wss://your-api-url/api/sessions/live
|
||||
INFO WebSocket connected
|
||||
INFO Authentication successful
|
||||
INFO Subscribed to session X
|
||||
```
|
||||
|
||||
4. Add a game in the Picker page
|
||||
|
||||
5. The bot should announce in Kosmi/IRC:
|
||||
```
|
||||
🎮 Coming up next: Fibbage 4!
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Refused
|
||||
|
||||
**Problem**: `Error: connect ECONNREFUSED`
|
||||
|
||||
**Solution**: Make sure the backend is running on the correct port (default 5000).
|
||||
|
||||
### Authentication Failed
|
||||
|
||||
**Problem**: `Authentication failed: Invalid or expired token`
|
||||
|
||||
**Solution**:
|
||||
- Get a fresh JWT token
|
||||
- Make sure you're using the correct admin key
|
||||
- Check token hasn't expired (tokens expire after 24 hours)
|
||||
|
||||
### No Events Received
|
||||
|
||||
**Problem**: WebSocket connects but no `game.added` events are received
|
||||
|
||||
**Solution**:
|
||||
- Make sure you're subscribed to the correct session ID
|
||||
- Verify the session is active
|
||||
- Check backend logs for errors
|
||||
- Try adding a game manually via the Picker page
|
||||
|
||||
### Bot Not Connecting
|
||||
|
||||
**Problem**: Bot fails to connect to WebSocket
|
||||
|
||||
**Solution**:
|
||||
- Check `APIURL` in `matterbridge.toml` is correct
|
||||
- Verify `UseWebSocket=true` is set
|
||||
- Check bot has valid JWT token (authentication succeeded)
|
||||
- Look for error messages in bot logs
|
||||
|
||||
### Reconnection Issues
|
||||
|
||||
**Problem**: WebSocket disconnects and doesn't reconnect
|
||||
|
||||
**Solution**:
|
||||
- Check network connectivity
|
||||
- Backend automatically handles reconnection with exponential backoff
|
||||
- Bot automatically reconnects on disconnect
|
||||
- Check logs for reconnection attempts
|
||||
|
||||
## Advanced Testing
|
||||
|
||||
### Test Multiple Clients
|
||||
|
||||
You can run multiple test clients simultaneously:
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
JWT_TOKEN="token1" node test-websocket.js
|
||||
|
||||
# Terminal 2
|
||||
JWT_TOKEN="token2" node test-websocket.js
|
||||
```
|
||||
|
||||
Both should receive the same `game.added` events.
|
||||
|
||||
### Test Heartbeat
|
||||
|
||||
The WebSocket connection sends ping/pong messages every 30 seconds. You should see:
|
||||
|
||||
```
|
||||
💓 Heartbeat
|
||||
```
|
||||
|
||||
If you don't see heartbeats, the connection may be stale.
|
||||
|
||||
### Test Reconnection
|
||||
|
||||
1. Start the test client
|
||||
2. Stop the backend (Ctrl+C)
|
||||
3. The client should log: `WebSocket disconnected, reconnecting...`
|
||||
4. Restart the backend
|
||||
5. The client should reconnect automatically
|
||||
|
||||
## Integration Checklist
|
||||
|
||||
- [ ] Backend WebSocket server starts successfully
|
||||
- [ ] Test client can connect and authenticate
|
||||
- [ ] Test client receives `game.added` events
|
||||
- [ ] Heartbeat keeps connection alive (30s interval)
|
||||
- [ ] Auto-reconnect works after disconnect
|
||||
- [ ] Multiple clients can connect simultaneously
|
||||
- [ ] Invalid JWT is rejected properly
|
||||
- [ ] Bot connects and authenticates
|
||||
- [ ] Bot receives events and broadcasts to chat
|
||||
- [ ] Bot reconnects after network issues
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once testing is complete:
|
||||
|
||||
1. Update your bot configuration to use `UseWebSocket=true`
|
||||
2. Deploy the updated backend with WebSocket support
|
||||
3. Restart your bot to connect via WebSocket
|
||||
4. Monitor logs for any connection issues
|
||||
5. Webhooks remain available as a fallback option
|
||||
|
||||
## Comparison: WebSocket vs Webhooks
|
||||
|
||||
| Feature | WebSocket | Webhooks |
|
||||
|---------|-----------|----------|
|
||||
| Setup Complexity | Simple | Moderate |
|
||||
| Inbound Ports | Not needed | Required |
|
||||
| Docker Networking | Simple | Complex |
|
||||
| Latency | Lower | Higher |
|
||||
| Connection Type | Persistent | Per-event |
|
||||
| Reconnection | Automatic | N/A |
|
||||
| Best For | Real-time bots | Serverless integrations |
|
||||
|
||||
**Recommendation**: Use WebSocket for bot integrations. Use webhooks for serverless/stateless integrations or when WebSocket is not feasible.
|
||||
|
||||
34
docs/archive/todos.md
Normal file
34
docs/archive/todos.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# TODO:
|
||||
|
||||
## Chrome Extension
|
||||
- [x] /.old-chrome-extension/ contains OLD code that needs adjusting for new game picker format. (COMPLETED: New simplified extension in /chrome-extension/)
|
||||
- [x] remove clunky gamealias system, we are only tracking "thisgame++" and "thisgame--" now.
|
||||
- [x] ensure the extension is watching for "thisgame++" or "thisgame--" anywhere in each chat line.
|
||||
- [x] if a chat line matches capture the whole message/line, the author, and the timestamp (UTC).
|
||||
- [x] ensure our JSON output matches the new format:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"username": "Alice",
|
||||
"message": "thisgame++",
|
||||
"timestamp": "2024-10-30T20:15:00Z"
|
||||
},
|
||||
{
|
||||
"username": "Bob",
|
||||
"message": "This is fun! thisgame++",
|
||||
"timestamp": "2024-10-30T20:16:30Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Game Manager
|
||||
- [x] implement favoring system (for packs and games). (COMPLETED: Full weighted selection system with visual indicators)
|
||||
- [x] if a game or pack is marked favored (👍), then we bias the picking algorithm to pick those games.
|
||||
- [x] if a game or pack is marked as disfavored (👎), then we bias the picking algorithm to not pick those games.
|
||||
- [x] biased games/packs should be highlighted subtley somehow in *any* lists they're in elsewhere in the UI, like the Pool Viewer.
|
||||
|
||||
## Bug Fixes
|
||||
- [x] Entire App: local timezone display still isn't working. I see UTC times. (FIXED: Created dateUtils.js to properly parse SQLite UTC timestamps)
|
||||
|
||||
## Other Features
|
||||
- [x] Session History: export sessions to plaintext and JSON. (COMPLETED: Export buttons in History page)
|
||||
Reference in New Issue
Block a user