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;