diff --git a/html/js/network/socketHandlers.js b/html/js/network/socketHandlers.js index f37e473..db945f1 100644 --- a/html/js/network/socketHandlers.js +++ b/html/js/network/socketHandlers.js @@ -870,18 +870,34 @@ const SocketHandlers = { // Hide the QR code now that payment is confirmed SocketHandlers.hidePaymentQR(); - if (typeof Game !== 'undefined' && Game.drawWaitingScreen) { - Game.drawWaitingScreen(); + // Credits purchase: return to home screen (no game queue) + if (data.creditsAdded || data.newBalance !== undefined) { + if (typeof Game !== 'undefined') { + Game._pendingPaymentConfirmed(); + } + // Return to welcome/home screen so user can start a game + if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWelcomeScreen) { + ScreenManager.drawWelcomeScreen(); + } + $('#messages').append($('
  • ').html( + '✅ Credits purchased! Type \'enter\' or click START GAME to play.' + )); + UI.scrollChat(); + SocketHandlers._setBannerStatus('Credits Added', '#0f0'); + } else { + // Single game payment: show waiting screen (player is in queue) + if (typeof Game !== 'undefined' && Game.drawWaitingScreen) { + Game.drawWaitingScreen(); + } + if (typeof Game !== 'undefined') { + Game._pendingPaymentConfirmed(); + } + $('#messages').append($('
  • ').html( + '✅ Payment confirmed in block. You are in the game queue.' + )); + UI.scrollChat(); + SocketHandlers._setBannerStatus('Confirmed', '#0f0'); } - if (typeof Game !== 'undefined') { - Game._pendingPaymentConfirmed(); - } - - $('#messages').append($('
  • ').html( - '✅ Payment confirmed in block. You are in the game queue.' - )); - UI.scrollChat(); - SocketHandlers._setBannerStatus('Confirmed', '#0f0'); // Sound handled by AudioAlerts._patchSocketHandlers() - don't duplicate }, diff --git a/src/index.js b/src/index.js index 27bd75c..3715d18 100644 --- a/src/index.js +++ b/src/index.js @@ -638,15 +638,24 @@ const adminAuth = (req, res, next) => { } // Origin check — reject cross-origin requests to admin endpoints + // Same-origin requests (Origin matches Host) are always allowed const origin = req.headers.origin || req.headers.referer; if (origin) { try { - const allowedOrigins = process.env.ALLOWED_ORIGINS - ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()) - : [`http://localhost:${process.env.PORT || 3000}`, `http://127.0.0.1:${process.env.PORT || 3000}`]; const requestOrigin = new URL(origin).origin; - if (!allowedOrigins.includes(requestOrigin)) { - return res.status(403).json({ error: 'Forbidden', message: 'Origin not allowed.' }); + const host = req.headers.host; + const protocol = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http'; + const selfOrigin = `${protocol}://${host}`; + + // Allow same-origin requests (admin.html served from same server) + if (requestOrigin !== selfOrigin) { + // Check explicit allowlist for cross-origin requests + const allowedOrigins = process.env.ALLOWED_ORIGINS + ? process.env.ALLOWED_ORIGINS.split(',').map(o => o.trim()) + : [`http://localhost:${process.env.PORT || 3000}`, `http://127.0.0.1:${process.env.PORT || 3000}`]; + if (!allowedOrigins.includes(requestOrigin)) { + return res.status(403).json({ error: 'Forbidden', message: 'Origin not allowed.' }); + } } } catch (e) { return res.status(403).json({ error: 'Forbidden', message: 'Invalid origin.' }); @@ -1804,7 +1813,7 @@ async function startServer() { const payoutIntervalMs = payoutIntervalSeconds * 1000; batchPayoutInterval = setInterval(async () => { try { - await walletRPCService.processBatchPayouts(); + await gameModeManager._processPendingPayouts(); } catch (error) { console.error('Error in batch payout processing:', error); } diff --git a/src/network/paymentHandlers.js b/src/network/paymentHandlers.js index 43fd4c4..37d82e0 100644 --- a/src/network/paymentHandlers.js +++ b/src/network/paymentHandlers.js @@ -279,25 +279,37 @@ class PaymentHandlers { if (status.in_mempool && !status.confirmed) { if (this.mempoolNotified.has(paymentRequest.address)) return; this.mempoolNotified.add(paymentRequest.address); - 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) { - // 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 - }); + // Check payment type — credits_package should NOT queue for a game + const mapping = this.socketPaymentMap.get(socket.id); + const isCredits = mapping && mapping.paymentType === 'credits_package'; + + const detectMessage = isCredits + ? 'Payment detected in mempool! Awaiting block confirmation to add credits...' + : 'Payment detected in mempool! Adding you to the game queue...'; + socket.emit('payment_detected', { paymentId: paymentRequest.id, message: detectMessage, amount: status.amount, confirmations: 0 }); + + // Only queue for single_game payments (credits just add balance, no game queue) + if (!isCredits) { + const existingIdx = this.queueManager.getPlayerIndex(socket.id); + if (existingIdx === -1) { + // 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 }); } - 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) { // SECURITY: Use in-memory Set as fast-path, but DB is source of truth // This prevents double-processing on server restart @@ -344,6 +356,8 @@ class PaymentHandlers { console.error('Error processing credits package confirmation:', e.message); socket.emit('payment_confirmed', { paymentId: paymentRequest.id, message: 'Payment confirmed in block!', confirmations: status.confirmations }); } + // Defensively remove from queue — credits purchases should never be queued + this.queueManager.removePlayer(socket.id); } else { // single_game payment - standard flow // CRITICAL: Atomically update payment status and check if we actually updated it diff --git a/src/network/socketHandlers.js b/src/network/socketHandlers.js index e8eaa0f..0e5722b 100644 --- a/src/network/socketHandlers.js +++ b/src/network/socketHandlers.js @@ -537,7 +537,7 @@ class SocketHandlers { // Create game immediately const blockHeight = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null; memUser.blockRec = blockHeight; // keep legacy timeout logic consistent - const game = this.gameManager.createGameForUser(memUser, 'standard'); + const game = await this.gameManager.createGameForUser(memUser, 'standard'); const state = game.getState(); state.blockHeight = blockHeight;