Reorganize project: move docs to docs/ and test scripts to tests/
Moved 9 documentation .md files from root into docs/. Moved 4 test scripts from root into tests/. Updated cross-references in README.md and docs to reflect new paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
306
docs/SESSION_END_WEBSOCKET.md
Normal file
306
docs/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)
|
||||
|
||||
Reference in New Issue
Block a user