307 lines
8.2 KiB
Markdown
307 lines
8.2 KiB
Markdown
|
|
# 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 test-session-end-websocket.js <session_id> <jwt_token>
|
||
|
|
```
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```bash
|
||
|
|
node 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 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)
|
||
|
|
|