Commit 9c1b9fca authored by Mahmoud Aglan's avatar Mahmoud Aglan

Add connection logging for debugging disconnects

Logs all connect/disconnect/close events with player IDs, room codes,
and close codes. Logs relay packets (first 20 + every 100th). Logs
ping/pong keepalive terminations.
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 1ebad2bc
......@@ -5,6 +5,11 @@ const { RelayHandler } = require('./relay');
const PORT = process.env.PORT || 3000;
function log(tag, msg) {
const ts = new Date().toISOString().slice(11, 23);
console.log(`[${ts}] [${tag}] ${msg}`);
}
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
......@@ -38,6 +43,8 @@ wss.on('connection', (ws) => {
ws.playerName = null;
ws.isRelaying = false;
log('CONN', `New WebSocket connection. Total clients: ${wss.clients.size}`);
ws.on('pong', () => { ws.isAlive = true; });
ws.on('message', (data, isBinary) => {
......@@ -48,34 +55,50 @@ wss.on('connection', (ws) => {
}
const msg = JSON.parse(data.toString());
log('MSG', `player=${ws.playerId} room=${ws.roomCode} type=${msg.type}`);
handleMessage(ws, msg);
} catch (e) {
log('ERR', `Bad message from player=${ws.playerId}: ${e.message}`);
sendError(ws, 'Invalid message format');
}
});
ws.on('close', () => {
ws.on('close', (code, reason) => {
log('CLOSE', `player=${ws.playerId} room=${ws.roomCode} name=${ws.playerName} code=${code} reason=${reason || 'none'}`);
if (ws.roomCode) {
const room = roomManager.getRoom(ws.roomCode);
if (room) {
room.removePlayer(ws.playerId);
if (room.isEmpty()) {
log('ROOM', `Room ${ws.roomCode} deleted (empty)`);
roomManager.deleteRoom(ws.roomCode);
} else {
log('ROOM', `Room ${ws.roomCode} now has ${room.getPlayerCount()} players`);
broadcastLobbyState(room);
}
}
}
});
ws.on('error', (err) => {
log('WS_ERR', `player=${ws.playerId} room=${ws.roomCode}: ${err.message}`);
});
});
// Ping/pong keepalive
setInterval(() => {
let terminated = 0;
wss.clients.forEach((ws) => {
if (!ws.isAlive) { ws.terminate(); return; }
if (!ws.isAlive) {
log('PING', `Terminating dead connection: player=${ws.playerId} room=${ws.roomCode}`);
ws.terminate();
terminated++;
return;
}
ws.isAlive = false;
ws.ping();
});
if (terminated > 0) log('PING', `Terminated ${terminated} dead connections`);
}, 30000);
// Room cleanup (stale rooms older than 30 min)
......
const { WebSocket } = require('ws');
function log(tag, msg) {
const ts = new Date().toISOString().slice(11, 23);
console.log(`[${ts}] [${tag}] ${msg}`);
}
class RelayHandler {
constructor(roomManager) {
this.roomManager = roomManager;
this._packetCount = 0;
}
handlePacket(ws, data) {
if (!ws.roomCode) return;
if (!ws.roomCode) {
log('RELAY', `Binary from player=${ws.playerId} but no room!`);
return;
}
const room = this.roomManager.getRoom(ws.roomCode);
if (!room || room.state !== 'playing') return;
if (!room || room.state !== 'playing') {
log('RELAY', `Binary from player=${ws.playerId} but room state=${room?.state || 'null'}`);
return;
}
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
// Packet format:
// First byte = target:
// 0x00 = broadcast to all except sender
// 0x01 = to host only
// 0xFF = to all clients (from host)
// 0x02-0xFE = to specific player ID
// Remaining bytes = FishNet payload
if (buffer.length < 2) return;
const target = buffer[0];
......@@ -31,6 +35,11 @@ class RelayHandler {
relayedPacket.writeUInt16LE(ws.playerId, 0);
payload.copy(relayedPacket, 2);
this._packetCount++;
if (this._packetCount <= 20 || this._packetCount % 100 === 0) {
log('RELAY', `#${this._packetCount} from=${ws.playerId} target=0x${target.toString(16)} len=${buffer.length} room=${ws.roomCode}`);
}
switch (target) {
case 0x00: // Broadcast to all except sender
this.broadcastExcept(room, ws.playerId, relayedPacket);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment