mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-18 14:51:26 +02:00
The client trusted the server to pre-escape chat content and rendered user-supplied fields via .html()/innerHTML — one server regression away from stored XSS, and the admin panel injected user-controlled payout addresses into its authenticated DOM. - Add a single shared escapeHtml helper (escapes all five HTML-significant chars including both quotes); leaderboard now delegates to it. - Escape chat username + message in onChatBroadcast/onChatHistory. - Escape user-controlled fields in the admin panel (payout address, socket id, player name, status) and make its local escapeHtml quote-safe so values in title="..." can't break out. - Add CSP plus X-Content-Type-Options, X-Frame-Options and Referrer-Policy headers via middleware. connect-src 'self' keeps Socket.IO working; removing 'unsafe-inline' (nonces) is tracked as a later cleanup. Full suite: 13 suites / 119 tests.
372 lines
23 KiB
HTML
372 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Wownerogue - A Wownero Roguelike</title>
|
||
<link rel="stylesheet" type="text/css" href="styles/stylesheet.css">
|
||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||
|
||
<!-- Socket.io -->
|
||
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
|
||
|
||
<!-- Initialize socket with resume token (for persistent anonymous session) -->
|
||
<script>
|
||
(function(){
|
||
let opts = {};
|
||
window._localStorageAvailable = false;
|
||
try {
|
||
// Test localStorage availability (fails in private browsing on some browsers)
|
||
localStorage.setItem('_test', '1');
|
||
localStorage.removeItem('_test');
|
||
window._localStorageAvailable = true;
|
||
const existing = localStorage.getItem('wownerogue_token');
|
||
if (existing) {
|
||
// Send the session token in the handshake `auth` payload, NOT the URL
|
||
// query string. Query strings leak into proxy/access/referer logs;
|
||
// auth is carried in the connection body. (Server keeps a query-string
|
||
// fallback for older clients during rollout.)
|
||
opts.auth = { resumeToken: existing };
|
||
}
|
||
} catch(_) {
|
||
window._localStorageAvailable = false;
|
||
console.warn('localStorage unavailable - session will not persist');
|
||
}
|
||
window.socket = io(opts);
|
||
// Show warning if localStorage unavailable (after DOM ready)
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
if (!window._localStorageAvailable) {
|
||
const warn = document.getElementById('sessionWarning');
|
||
if (warn) warn.style.display = 'inline';
|
||
}
|
||
// Fetch server config and populate hosted-by footer
|
||
fetch('/api/game-modes')
|
||
.then(function(res) { return res.json(); })
|
||
.then(function(data) {
|
||
if (data.hostedBy && data.hostedBy.url) {
|
||
var container = document.getElementById('hosted-by-container');
|
||
if (container) {
|
||
container.innerHTML = ' | Hosted by: <a href="' +
|
||
data.hostedBy.url + '" target="_blank" rel="noopener" style="color:#888;">' +
|
||
(data.hostedBy.name || data.hostedBy.url) + '</a>';
|
||
}
|
||
}
|
||
})
|
||
.catch(function() { /* ignore errors */ });
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<!-- Core libraries -->
|
||
<script src="js/lib/jquery-3.4.1.min.js"></script>
|
||
<script src="js/lib/rot.min.js"></script>
|
||
<script src="js/core/escapeHtml.js"></script>
|
||
<script src="js/core/options.js"></script>
|
||
<script src="js/ui/teletype.js"></script>
|
||
|
||
<!-- Modular game components (order matters for dependencies) -->
|
||
<script src="js/display/displayManager.js"></script>
|
||
<script src="js/display/waitingScreenAnimator.js"></script>
|
||
<script src="js/display/screenManager.js"></script>
|
||
<script src="js/display/renderEngine.js"></script>
|
||
<script src="js/core/gameState.js"></script>
|
||
<script src="js/core/game.js"></script>
|
||
|
||
<!-- Other modules -->
|
||
<script src="js/ui/ui.js"></script>
|
||
<script src="js/ui/paymentUI.js"></script>
|
||
<script src="js/ui/audioAlerts.js"></script>
|
||
<script src="js/ui/addressModal.js"></script>
|
||
<script src="js/ui/transactionHistory.js"></script>
|
||
<script src="js/ui/leaderboard.js"></script>
|
||
<script src="js/ui/helpModal.js"></script>
|
||
<script src="js/network/socketHandlers.js"></script>
|
||
<script src="js/network/smirkAuth.js"></script>
|
||
<script src="js/input/inputHandler.js"></script>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- Header area -->
|
||
<header class="banner">
|
||
<div id="staticbanner">
|
||
<span id="banner"></span><span id="bannercursor">|</span>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Status area for block height -->
|
||
<div class="status">
|
||
<div id="blockHeight">Block Height: <span id="heightValue">Loading...</span></div>
|
||
<div id="connectionStatus">Status: <span id="statusValue">Connecting...</span></div>
|
||
<div id="creditsDisplay" style="display:none; margin-top:4px; font-size:13px; font-family:monospace; color:#0f0; background:#001a00; padding:4px 8px; border:1px solid #0a0; border-radius:4px; font-weight:bold;"></div>
|
||
<button id="startButton" style="background: #0a5c0a; color: #ff0; border: 2px solid #0f0; padding: 8px 16px; font-weight: bold; cursor: pointer; margin-top: 5px; display: none;">
|
||
🎮 START GAME
|
||
</button>
|
||
<button id="watchGamesButton" style="background: #053655; color: #0af; border: 2px solid #0af; padding: 8px 16px; font-weight: bold; cursor: pointer; margin-top: 5px; margin-left: 10px;">
|
||
👁️ WATCH GAMES
|
||
</button>
|
||
<button id="shopButton" style="background: #004400; color: #0f0; border: 2px solid #0f0; padding: 8px 16px; font-weight: bold; cursor: pointer; margin-top: 5px; margin-left: 10px; display: none;">
|
||
🛒 SHOP
|
||
</button>
|
||
<button id="animationToggleButton" style="background: #5c5c0a; color: #ff0; border: 2px solid #aa0; padding: 6px 12px; font-weight: bold; cursor: pointer; margin-top: 5px; margin-left: 10px; display: none;">
|
||
🎬 DISABLE ANIMATION
|
||
</button>
|
||
<div style="margin-top:6px; font-size:11px; color:#ccc; display:flex; align-items:center; gap:4px;">
|
||
<label style="cursor:pointer; display:flex; align-items:center; gap:4px;">
|
||
<input type="checkbox" id="audioAlertsToggle" style="cursor:pointer;"> Audio Alerts
|
||
</label>
|
||
<button id="helpButton" style="margin-left:auto; background:#2d2d00; color:#ffd700; border:1px solid #ffd700; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="How to play and payment information">❓ Help</button>
|
||
<button id="historyButton" style="background:#1a1a4d; color:#c4b5fd; border:1px solid #8b5cf6; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="View your payment and payout history">📜 History</button>
|
||
<button id="leaderboardButton" style="background:#4a2800; color:#fbbf24; border:1px solid #f59e0b; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="View high scores">🏆 Scores</button>
|
||
<button id="manageAddressButton" style="background:#053655; color:#0ff; border:1px solid #0ff; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="Your payout address and credits are tied to a browser session token. Clearing cookies/localStorage or using incognito mode will lose your session.">⚙️ Manage Payout Address</button>
|
||
<span id="sessionWarning" style="display:none; color:#f59e0b; font-size:10px; margin-left:6px;" title="Private browsing or localStorage blocked - your session won't persist">⚠️ Session not saved</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Payment System UI -->
|
||
<div id="payment-ui" class="payment-section" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2000; background: rgba(0,20,0,0.95); border: 2px solid #0f0; padding: 20px; color: #0f0; min-width: 400px; max-width: 500px; box-shadow: 0 0 20px rgba(0,255,0,0.2);">
|
||
<div class="payment-header">
|
||
<strong>🎮 Choose How to Play</strong>
|
||
<button id="close-payment" style="float: right; background: #500; color: #f00; border: 1px solid #f00; padding: 2px 6px; cursor: pointer;">✕</button>
|
||
</div>
|
||
|
||
<!-- Dynamic game mode options populated by PaymentUI.js -->
|
||
<div class="game-modes" style="margin: 15px 0;">
|
||
<div style="color: #888; text-align: center; padding: 20px;">Loading payment options...</div>
|
||
</div>
|
||
|
||
<div id="credits-display" style="margin: 10px 0; display: none;">
|
||
<strong>Credits remaining: <span id="credits-count">0</span></strong>
|
||
</div>
|
||
|
||
<div id="payment-details" style="display: none; margin: 15px 0;">
|
||
<div style="margin-bottom: 5px;"><strong>Payment Required:</strong></div>
|
||
<div style="font-size: 1.2em; margin-bottom: 10px;">Amount: <span id="payment-amount" style="color: #ff0;">0</span></div>
|
||
<div style="margin-bottom: 5px;">Address:</div>
|
||
<input id="payment-address" type="text" readonly style="width: 100%; background: #000; color: #0f0; border: 1px solid #0f0; padding: 8px; font-family: monospace; margin-bottom: 10px;">
|
||
<div style="margin: 10px 0; display: flex; gap: 10px;">
|
||
<button id="copy-address" style="flex: 1; background: #050; color: #0f0; border: 1px solid #0f0; padding: 8px; cursor: pointer;">📋 Copy Address</button>
|
||
<button id="check-payment" style="flex: 1; background: #005; color: #0f0; border: 1px solid #0f0; padding: 8px; cursor: pointer;">🔄 Check Payment</button>
|
||
</div>
|
||
<div id="payment-status" style="margin: 10px 0; font-weight: bold; text-align: center;"></div>
|
||
<div style="font-size: 0.8em; color: #aaa; text-align: center; margin-top: 15px; border-top: 1px solid #333; padding-top: 10px;">
|
||
ℹ️ You can close this window. The payment information is also available in the sidebar/chat area.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Transaction History Panel -->
|
||
<div id="history-panel" style="display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2000; background: rgba(10,10,30,0.98); border: 2px solid #8b5cf6; padding: 20px; color: #e0e0e0; min-width: 500px; max-width: 600px; max-height: 80vh; overflow-y: auto; box-shadow: 0 0 20px rgba(139,92,246,0.3); border-radius: 8px;">
|
||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px; border-bottom:1px solid #444; padding-bottom:10px;">
|
||
<strong style="font-size:1.2em; color:#c4b5fd;">📜 Transaction History</strong>
|
||
<button id="close-history" style="background: #500; color: #f00; border: 1px solid #f00; padding: 4px 8px; cursor: pointer; border-radius:3px;">✕</button>
|
||
</div>
|
||
|
||
<!-- Summary Section -->
|
||
<div id="history-summary" style="display:grid; grid-template-columns:1fr 1fr; gap:15px; margin-bottom:20px;">
|
||
<div style="background:#1a1a3d; padding:12px; border-radius:6px; border:1px solid #4c1d95;">
|
||
<div style="color:#888; font-size:0.85em;">Total Rewards</div>
|
||
<div id="total-received" style="color:#4ade80; font-size:1.3em; font-weight:bold;">0 WOW</div>
|
||
</div>
|
||
<div style="background:#1a1a3d; padding:12px; border-radius:6px; border:1px solid #4c1d95;">
|
||
<div style="color:#888; font-size:0.85em;">Total Paid</div>
|
||
<div id="total-payments" style="color:#60a5fa; font-size:1.3em; font-weight:bold;">0 WOW</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div style="display:flex; gap:10px; margin-bottom:15px;">
|
||
<button class="history-tab active" data-tab="payouts" style="flex:1; padding:8px; background:#4c1d95; color:#fff; border:none; cursor:pointer; border-radius:4px;">💰 Payouts</button>
|
||
<button class="history-tab" data-tab="payments" style="flex:1; padding:8px; background:#333; color:#aaa; border:none; cursor:pointer; border-radius:4px;">💳 Payments</button>
|
||
</div>
|
||
|
||
<!-- Payouts Tab Content -->
|
||
<div id="payouts-content" class="history-content">
|
||
<div id="payouts-list" style="max-height:300px; overflow-y:auto;"></div>
|
||
<div id="payouts-empty" style="text-align:center; color:#666; padding:20px;">No payouts yet</div>
|
||
</div>
|
||
|
||
<!-- Payments Tab Content -->
|
||
<div id="payments-content" class="history-content" style="display:none;">
|
||
<div id="payments-list" style="max-height:300px; overflow-y:auto;"></div>
|
||
<div id="payments-empty" style="text-align:center; color:#666; padding:20px;">No payments yet</div>
|
||
</div>
|
||
|
||
<div style="margin-top:15px; font-size:0.8em; color:#666; text-align:center;">
|
||
History is tied to your browser session
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Game display -->
|
||
<div id="game-display" class="rotdis"></div>
|
||
|
||
<!-- Debug display area (hidden in production) -->
|
||
<div id="debug-display" style="display: none; background: rgba(0,0,0,0.8); color: #0f0; font-family: monospace; font-size: 12px; padding: 10px; margin: 5px; border: 1px solid #333; max-height: 200px; overflow-y: auto;">
|
||
<div><strong>Debug Information:</strong></div>
|
||
<div id="debug-content">Ready...</div>
|
||
</div>
|
||
|
||
<!-- Chat messages area -->
|
||
<div class="chat-container">
|
||
<div id="messageBox">
|
||
<ul id="messages"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat input in its own row -->
|
||
<div class="input-container">
|
||
<form id="chatForm" action="">
|
||
<input id="chatInput" autocomplete="off" placeholder="Type a command..." maxlength="200" />
|
||
<span id="charCounter" style="color:#666; font-size:10px; margin-left:5px; min-width:45px;">0/200</span>
|
||
<button type="submit">Send</button>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
<div class="footer">
|
||
<span>Wownero Roguelike v0.1.0 | Use WASD or arrow keys to move</span>
|
||
<span id="hosted-by-container"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Address management modal -->
|
||
<div id="addressModal" class="modal-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="addressModalTitle">
|
||
<div class="modal-content">
|
||
<button type="button" class="modal-close" id="addressModalClose" aria-label="Close">✕</button>
|
||
<h2 id="addressModalTitle">Manage Payout Address</h2>
|
||
<p class="modal-description">Enter your Wownero (WOW) or Monero (XMR) address below. This will be used for payouts when you escape.</p>
|
||
<p class="modal-description" style="font-size:11px; color:#888; margin-top:8px; padding:6px; background:rgba(255,200,0,0.1); border-radius:4px;">💡 <strong>Session Info:</strong> Your address and credits are tied to a browser session token stored locally. They will persist across page refreshes but will be lost if you clear cookies, use incognito mode, or switch browsers.</p>
|
||
|
||
<!-- Smirk Wallet Integration -->
|
||
<div id="smirkSection" style="margin: 15px 0; padding: 12px; background: rgba(26,26,77,0.5); border: 1px solid #3a3a7d; border-radius: 6px;">
|
||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||
<span style="font-size: 1.2em;">🔐</span>
|
||
<strong style="color: #aaf;">Connect with Smirk Wallet</strong>
|
||
</div>
|
||
<p style="font-size: 11px; color: #999; margin-bottom: 10px;">Automatically set your payout address by connecting your Smirk browser wallet.</p>
|
||
<div id="smirkButtonContainer">
|
||
<!-- Will be populated by smirkAuth.js -->
|
||
</div>
|
||
</div>
|
||
|
||
<div style="text-align: center; color: #666; margin: 10px 0; font-size: 12px;">— or enter manually —</div>
|
||
|
||
<form id="addressModalForm">
|
||
<label for="addressModalInput" class="modal-label">Payout Address</label>
|
||
<textarea id="addressModalInput" autocomplete="off" spellcheck="false" rows="3" placeholder="Paste your payout address here"></textarea>
|
||
<div class="modal-actions">
|
||
<button type="submit" id="addressModalSave">Save Address</button>
|
||
<button type="button" id="addressModalCancel">Cancel</button>
|
||
</div>
|
||
<div id="addressModalFeedback" class="modal-feedback" aria-live="polite"></div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Help/Instructions Modal -->
|
||
<div id="helpModal" class="modal-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="helpModalTitle">
|
||
<div class="modal-content" style="max-width: 600px; max-height: 80vh; overflow-y: auto;">
|
||
<button type="button" class="modal-close" id="helpModalClose" aria-label="Close">✕</button>
|
||
<h2 id="helpModalTitle" style="color:#ffd700; margin-bottom:15px;">❓ How to Play</h2>
|
||
|
||
<div style="margin-bottom:20px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🎮 Game Objective</h3>
|
||
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Navigate through the dungeon, collect the treasure bag, and escape through the exit before the monster catches you or time runs out!</p>
|
||
</div>
|
||
|
||
<div style="margin-bottom:20px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">⌨️ Controls</h3>
|
||
<ul style="color:#ccc; font-size:0.9em; line-height:1.6; padding-left:20px;">
|
||
<li><strong>WASD</strong> or <strong>Arrow Keys</strong> - Move your character</li>
|
||
<li><strong>Enter</strong> - Join the queue to play</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div style="margin-bottom:20px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🕐 Block Timing</h3>
|
||
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Games are synchronized to blockchain blocks. <strong>You may need to wait a few minutes</strong> for the next block before your game starts. The block height is shown at the top of the screen.</p>
|
||
</div>
|
||
|
||
<div id="helpPaymentModes" style="margin-bottom:20px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">💰 Payment Modes</h3>
|
||
<div id="helpModesList" style="color:#ccc; font-size:0.9em; line-height:1.4;">
|
||
<!-- Dynamically populated based on active modes -->
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-bottom:20px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🎁 Payouts</h3>
|
||
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Set your payout address using the "Manage Payout Address" button. When you escape the dungeon, your winnings will be sent automatically. Escaping with the treasure bag earns a higher multiplier!</p>
|
||
</div>
|
||
|
||
<div style="margin-bottom:15px;">
|
||
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">📜 Legend</h3>
|
||
<div style="display:grid; grid-template-columns: 30px 1fr; gap:8px; color:#ccc; font-size:0.9em;">
|
||
<span style="color:#60a5fa;">@</span><span>You (the player)</span>
|
||
<span style="color:#4ade80;">></span><span>Exit - escape here to win</span>
|
||
<span style="color:#ef4444;">~</span><span>Monster - avoid at all costs</span>
|
||
<span style="color:#fbbf24;">$</span><span>Treasure - collect for bonus payout</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button id="helpModalCloseBtn" style="width:100%; padding:10px; background:#4c1d95; color:#fff; border:none; cursor:pointer; border-radius:4px; font-weight:bold;">Got it!</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Browser info panel -->
|
||
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
|
||
<div>Browser: <span id="browser-name">checking...</span></div>
|
||
<div>Display: <span id="display-status">checking...</span></div>
|
||
<button onclick="document.getElementById('browser-info').style.display='none'" style="margin-top: 5px; background: #333; border: 1px solid #666; color: white; font-size: 10px; padding: 2px 4px;">
|
||
Close
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Initialize game when ready -->
|
||
<script>
|
||
$(document).ready(function() {
|
||
console.log("Document ready - initializing modules...");
|
||
console.log("Socket available:", !!window.socket);
|
||
console.log("Game object available:", typeof Game !== 'undefined');
|
||
console.log("SocketHandlers available:", typeof SocketHandlers !== 'undefined');
|
||
console.log("UI available:", typeof UI !== 'undefined');
|
||
console.log("InputHandler available:", typeof InputHandler !== 'undefined');
|
||
|
||
// Small delay to ensure all modules are fully loaded
|
||
setTimeout(function() {
|
||
// Initialize Game first
|
||
if (typeof Game !== 'undefined') {
|
||
console.log("Initializing Game...");
|
||
const gameInitResult = Game.init();
|
||
console.log("Game initialization result:", gameInitResult);
|
||
|
||
// Ensure welcome screen is shown after a brief delay
|
||
setTimeout(function() {
|
||
if (gameInitResult && typeof ScreenManager !== 'undefined' && ScreenManager.drawWelcomeScreen) {
|
||
console.log("Ensuring welcome screen is drawn...");
|
||
ScreenManager.drawWelcomeScreen();
|
||
}
|
||
}, 50);
|
||
} else {
|
||
console.error("Game object not found!");
|
||
}
|
||
|
||
// Initialize SocketHandlers (CRITICAL for receiving game_start events!)
|
||
if (typeof SocketHandlers !== 'undefined') {
|
||
console.log("Initializing SocketHandlers...");
|
||
// IMPORTANT: AudioAlerts patches SocketHandlers methods, so must init BEFORE socket bindings
|
||
if (typeof AudioAlerts !== 'undefined') { AudioAlerts.init(); }
|
||
SocketHandlers.init();
|
||
} else {
|
||
console.error("SocketHandlers object not found!");
|
||
}
|
||
|
||
// Initialize InputHandler
|
||
if (typeof InputHandler !== 'undefined') {
|
||
console.log("Initializing InputHandler...");
|
||
InputHandler.init();
|
||
} else {
|
||
console.error("InputHandler object not found!");
|
||
}
|
||
}, 100); // 100ms delay to ensure everything is loaded
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |