mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 06:41:27 +02:00
Fix: make reconnect more robust and make spectate window re-openable
This commit is contained in:
@@ -215,7 +215,7 @@ const InputHandler = {
|
||||
} else {
|
||||
// If not showing waiting screen, add helpful message to chat if missing address
|
||||
if (addressRequired && !hasAddress) {
|
||||
$('#messages').append($('<li style="color: #ffa500;">').text("⚠️ Please set a payout address before starting."));
|
||||
$('#messages').append($('<li style="color: #ffa500;">').text("⚠️ Please set a payout address using the button below before starting."));
|
||||
UI.scrollChat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,19 @@ class QueryValidator {
|
||||
}
|
||||
|
||||
// Check for dangerous patterns in the query text itself
|
||||
const upperText = text.toUpperCase();
|
||||
for (const keyword of this.dangerousKeywords) {
|
||||
if (upperText.includes(keyword)) {
|
||||
// Use regex to avoid false positives (e.g., "DESCRIPTION" containing "SCRIPT")
|
||||
// Alphanumeric keywords use word boundaries; others use literal match
|
||||
let regex;
|
||||
if (/^[A-Z0-9_]+$/i.test(keyword)) {
|
||||
regex = new RegExp(`\\b${keyword}\\b`, 'i');
|
||||
} else {
|
||||
// Escape special characters for literal match
|
||||
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
regex = new RegExp(escaped, 'i');
|
||||
}
|
||||
|
||||
if (regex.test(text)) {
|
||||
// Allow legitimate DDL in migrations only
|
||||
const stack = new Error().stack;
|
||||
if (!stack.includes('migrations')) {
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
const { normalizeError } = require('../utils/errors');
|
||||
|
||||
class PaymentHandlers {
|
||||
constructor({ io, gameModeManager, walletService, debugManager, queueManager, broadcastManager }) {
|
||||
constructor({ io, gameModeManager, walletService, debugManager, queueManager, broadcastManager, sessionManager }) {
|
||||
this.io = io;
|
||||
this.gameModeManager = gameModeManager;
|
||||
this.walletService = walletService;
|
||||
this.debugManager = debugManager;
|
||||
this.queueManager = queueManager;
|
||||
this.broadcastManager = broadcastManager;
|
||||
this.sessionManager = sessionManager;
|
||||
this.mempoolNotified = new Set();
|
||||
this.paymentMonitors = new Map();
|
||||
// Track pending payment metadata per socket (may reuse existing)
|
||||
@@ -239,7 +240,20 @@ class PaymentHandlers {
|
||||
socket.emit('payment_detected', { paymentId: paymentRequest.id, message: 'Payment detected in mempool! Adding you to the game queue...', amount: status.amount, confirmations: 0 });
|
||||
const existingIdx = this.queueManager.getPlayerIndex(socket.id);
|
||||
if (existingIdx === -1) {
|
||||
this.queueManager.addPlayer({ serverId: socket.id, clientId: currentUser ? currentUser.clientId : null, paymentId: paymentRequest.id, requiresConfirmation: true, confirmed: false });
|
||||
// Try to get DB userId from session
|
||||
let userId = null;
|
||||
if (this.sessionManager?.sessions?.has(socket.id)) {
|
||||
userId = this.sessionManager.sessions.get(socket.id).id;
|
||||
}
|
||||
|
||||
this.queueManager.addPlayer({
|
||||
serverId: socket.id,
|
||||
clientId: currentUser ? currentUser.clientId : null,
|
||||
userId: userId,
|
||||
paymentId: paymentRequest.id,
|
||||
requiresConfirmation: true,
|
||||
confirmed: false
|
||||
});
|
||||
}
|
||||
socket.emit('queue_joined', { position: (existingIdx === -1 ? this.queueManager.getQueueLength() : existingIdx + 1), message: 'Payment received! Waiting for next block to start game...', currentBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null, nextBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() + 1 : null });
|
||||
} else if (status.confirmed) {
|
||||
|
||||
@@ -73,10 +73,20 @@ class QueueHandler {
|
||||
// Record the queue attempt
|
||||
await this.rateLimiter.recordAttempt(socket.id, 'game:queue');
|
||||
|
||||
// Get session info to pass userId
|
||||
let userId = null;
|
||||
if (this.paymentHandlers?.gameModeManager?.getOrCreateUser) {
|
||||
try {
|
||||
const dbUser = await this.paymentHandlers.gameModeManager.getOrCreateUser(socket.id);
|
||||
userId = dbUser.id;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Add to waiting queue (in free mode or already authorized paid mode)
|
||||
this.queueManager.addPlayer({
|
||||
serverId: socket.id,
|
||||
clientId: currentUser.clientId,
|
||||
userId: userId,
|
||||
requiresConfirmation: false,
|
||||
confirmed: true
|
||||
});
|
||||
|
||||
@@ -22,14 +22,39 @@ class QueueManager {
|
||||
this._waitingPlayers = []; // internal queue
|
||||
}
|
||||
|
||||
addPlayer({ serverId, clientId, entryTime = Date.now(), paymentId = null, requiresConfirmation = false, confirmed = true }) {
|
||||
addPlayer({ serverId, clientId, userId = null, entryTime = Date.now(), paymentId = null, requiresConfirmation = false, confirmed = true }) {
|
||||
if (this.getPlayerIndex(serverId) !== -1) return; // already queued
|
||||
this._waitingPlayers.push({ serverId, clientId, entryTime, paymentId, requiresConfirmation, confirmed });
|
||||
this._waitingPlayers.push({ serverId, clientId, userId, entryTime, paymentId, requiresConfirmation, confirmed });
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.log(`[QueueManager] Added player ${serverId} (confirmed=${confirmed}, requiresConfirmation=${requiresConfirmation}). Queue length: ${this._waitingPlayers.length}`);
|
||||
console.log(`[QueueManager] Added player ${serverId} (userId=${userId}, confirmed=${confirmed}, requiresConfirmation=${requiresConfirmation}). Queue length: ${this._waitingPlayers.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the serverId (socket.id) for a queued player when their session resumes
|
||||
*/
|
||||
updateSocketId(userId, newSocketId) {
|
||||
const entry = this._waitingPlayers.find(p => p.userId === userId);
|
||||
if (entry) {
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.log(`[QueueManager] Updating socket ID for user ${userId}: ${entry.serverId} -> ${newSocketId}`);
|
||||
}
|
||||
entry.serverId = newSocketId;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player entry has persistent data that should be preserved on disconnect
|
||||
*/
|
||||
isValuableEntry(serverId) {
|
||||
const idx = this.getPlayerIndex(serverId);
|
||||
if (idx === -1) return false;
|
||||
const entry = this._waitingPlayers[idx];
|
||||
return !!(entry.paymentId || entry.userId);
|
||||
}
|
||||
|
||||
removePlayer(serverId) {
|
||||
const idx = this.getPlayerIndex(serverId);
|
||||
if (idx !== -1) {
|
||||
|
||||
@@ -98,7 +98,8 @@ class SocketHandlers {
|
||||
walletService: this.walletService,
|
||||
debugManager: this.debugManager,
|
||||
queueManager: this.queueManager,
|
||||
broadcastManager: this.broadcastManager
|
||||
broadcastManager: this.broadcastManager,
|
||||
sessionManager: this.sessionManager
|
||||
});
|
||||
|
||||
// Address / payout handling encapsulated in AddressManager
|
||||
@@ -341,6 +342,26 @@ class SocketHandlers {
|
||||
const connectionResult = await this.connectionHandler.handleConnection(socket);
|
||||
if (!connectionResult) return; // Connection was rejected or failed
|
||||
|
||||
const { sessionInfo } = connectionResult;
|
||||
|
||||
// Restore queue status if session resumed
|
||||
if (sessionInfo && sessionInfo.resumed && sessionInfo.user && this.queueManager) {
|
||||
const wasUpdated = this.queueManager.updateSocketId(sessionInfo.user.id, socket.id);
|
||||
if (wasUpdated) {
|
||||
const position = this.queueManager.getQueuePosition(socket.id);
|
||||
if (position !== -1) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'queue',
|
||||
`Welcome back! You are still in the queue at position ${position}.`);
|
||||
socket.emit('queue_joined', {
|
||||
position: position,
|
||||
message: `Welcome back! You are still in queue.`,
|
||||
currentBlock: this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null,
|
||||
nextBlock: (this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : 0) + 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send game mode info to client
|
||||
if (this.gameModeManager) {
|
||||
socket.emit('game_mode_info', this.gameModeManager.getGameModeInfo());
|
||||
@@ -428,7 +449,7 @@ class SocketHandlers {
|
||||
try {
|
||||
const dbUser = await this.gameModeManager.getOrCreateUser(socket.id);
|
||||
if (!dbUser.payout_address) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'payment', '⚠️ Paste your payout address first, then type confirm. Payment request will appear automatically.');
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'payment', '⚠️ Use the "Manage Payout Address" button or paste your payout address here and type confirm. Payment request will appear automatically.');
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -649,8 +670,18 @@ class SocketHandlers {
|
||||
// Clean up active games
|
||||
this.activeGames.delete(socket.id);
|
||||
|
||||
// Remove from queue manager (idempotent)
|
||||
this.queueManager.removePlayer(socket.id);
|
||||
// Remove from queue manager if not a "valuable" entry (persistent session or payment)
|
||||
// This allows users who paid but disconnected to stay in the queue for session resumption
|
||||
if (this.queueManager && typeof this.queueManager.removePlayer === 'function') {
|
||||
if (this.queueManager.isValuableEntry && !this.queueManager.isValuableEntry(socket.id)) {
|
||||
this.queueManager.removePlayer(socket.id);
|
||||
} else if (this.debugManager.CONSOLE_LOGGING && this.queueManager.isValuableEntry && this.queueManager.isValuableEntry(socket.id)) {
|
||||
console.log(`[SocketHandlers] Preserving queue entry for ${socket.id} (user has session/payment)`);
|
||||
} else if (!this.queueManager.isValuableEntry) {
|
||||
// Fallback for safety if method not found
|
||||
this.queueManager.removePlayer(socket.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear any pending address confirmations
|
||||
if (this.chatHandler) {
|
||||
|
||||
Reference in New Issue
Block a user