diff --git a/html/js/input/inputHandler.js b/html/js/input/inputHandler.js
index 38de57b..972d0e8 100644
--- a/html/js/input/inputHandler.js
+++ b/html/js/input/inputHandler.js
@@ -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($('
').text("⚠️ Please set a payout address before starting."));
+ $('#messages').append($('').text("⚠️ Please set a payout address using the button below before starting."));
UI.scrollChat();
}
}
diff --git a/src/db/databaseManager.js b/src/db/databaseManager.js
index 4ca46f2..73ce2e4 100644
--- a/src/db/databaseManager.js
+++ b/src/db/databaseManager.js
@@ -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')) {
diff --git a/src/network/paymentHandlers.js b/src/network/paymentHandlers.js
index 4408fd5..fbc8385 100644
--- a/src/network/paymentHandlers.js
+++ b/src/network/paymentHandlers.js
@@ -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) {
diff --git a/src/network/queueHandler.js b/src/network/queueHandler.js
index 4134f60..c106668 100644
--- a/src/network/queueHandler.js
+++ b/src/network/queueHandler.js
@@ -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
});
diff --git a/src/network/queueManager.js b/src/network/queueManager.js
index 67b2181..5ae4c16 100644
--- a/src/network/queueManager.js
+++ b/src/network/queueManager.js
@@ -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) {
diff --git a/src/network/socketHandlers.js b/src/network/socketHandlers.js
index eb2a46f..a415d4a 100644
--- a/src/network/socketHandlers.js
+++ b/src/network/socketHandlers.js
@@ -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) {