1/** 2 * @file firebase-counter.js 3 * @description Firebase Realtime Database view counter with daily usage limit. 4 * Tracks total all-time views and per-day views. If the daily count exceeds 5 * DAILY_LIMIT, the start screen is replaced with a "limit reached" page that 6 * shows a countdown to midnight. View counts are displayed live via Firebase 7 * .on('value') listeners. 8 * @requires config.js - firebaseConfig object and DAILY_LIMIT constant 9 */ 10 11// ========== FIREBASE VIEW COUNTER + DAILY LIMIT ========== 12 13// Initialize Firebase app with credentials from config.js 14firebase.initializeApp(firebaseConfig); 15 16var db = firebase.database(); 17 18/** @type {firebase.database.Reference} Reference to /views (total all-time count) */ 19var viewsRef = db.ref('views'); 20 21/** @type {string} Today's date as YYYY-MM-DD, used as the daily counter key */ 22var today = new Date().toISOString().slice(0, 10); 23 24/** @type {firebase.database.Reference} Reference to /daily/<today> (today's count) */ 25var dailyRef = db.ref('daily/' + today); 26 27// On page load, check if today's limit has been reached 28dailyRef.once('value', function (snapshot) { 29 var todayCount = snapshot.val() || 0; 30 31 if (todayCount >= DAILY_LIMIT) { 32 document.getElementById('start').classList.add('hidden'); 33 document.getElementById('limitPage').classList.add('show'); 34 startLimitTimer(); 35 } 36}); 37 38/** 39 * Per-browser deduplication: a browser increments the counter at most once 40 * per UTC day. Prevents inflated counts from refresh loops (e.g. students 41 * replaying the simulation within one lesson) and raises the bar for 42 * casual abuse -- a scripted attacker still bypasses this by clearing 43 * localStorage, but organic double-counting is eliminated entirely. 44 * Storage failures (private mode, disabled cookies) fall through silently 45 * so the counter keeps working. 46 */ 47var COUNT_STORAGE_KEY = 'cms_last_count'; 48 49function hasCountedToday() { 50 try { 51 return localStorage.getItem(COUNT_STORAGE_KEY) === today; 52 } catch (e) { 53 return false; 54 } 55} 56 57function markCountedToday() { 58 try { 59 localStorage.setItem(COUNT_STORAGE_KEY, today); 60 } catch (e) { 61 /* localStorage unavailable -- accept duplicate count */ 62 } 63} 64 65/** 66 * Atomically increments both the total and daily view counters. 67 * Called once when the simulation starts (from go() in main.js). 68 * Uses Firebase transactions to avoid race conditions with concurrent users. 69 * Skips the increment entirely if this browser has already counted today. 70 */ 71function incrementCounters() { 72 if (hasCountedToday()) return; 73 markCountedToday(); 74 viewsRef 75 .transaction(function (current) { 76 return (current || 0) + 1; 77 }) 78 .catch(function (err) { 79 if (typeof console !== 'undefined') console.warn('Counter increment failed:', err); 80 }); 81 dailyRef 82 .transaction(function (current) { 83 return (current || 0) + 1; 84 }) 85 .catch(function (err) { 86 if (typeof console !== 'undefined') console.warn('Daily counter increment failed:', err); 87 }); 88} 89 90// Live-update all counter display elements whenever the total changes. 91// If the Firebase connection is blocked (ad blockers, school firewalls, 92// Firefox ETP), the callback never fires; hide the counter boxes after 93// 5s so users do not see the "--" placeholder stuck on screen. 94var viewCountLoaded = false; 95 96function hideViewCounters() { 97 var boxes = document.querySelectorAll('.start-views, .fin-views'); 98 for (var i = 0; i < boxes.length; i++) boxes[i].style.display = 'none'; 99} 100 101viewsRef.on( 102 'value', 103 function (snapshot) { 104 viewCountLoaded = true; 105 var count = snapshot.val() || 0; 106 var formatted = count.toLocaleString('de-DE'); 107 var el = document.getElementById('viewCount'); 108 var el2 = document.getElementById('viewCountStart'); 109 if (el) el.textContent = formatted; 110 if (el2) el2.textContent = formatted; 111 }, 112 function () { 113 hideViewCounters(); 114 } 115); 116 117setTimeout(function () { 118 if (!viewCountLoaded) hideViewCounters(); 119}, 5000); 120 121/** 122 * Starts a live countdown timer showing the time remaining until midnight, 123 * when the daily limit resets. Automatically reloads the page at midnight. 124 */ 125function startLimitTimer() { 126 var el = document.getElementById('limitTimer'); 127 if (!el) return; 128 129 /** 130 * Updates the HH:MM:SS countdown display. 131 * The daily counter key (`today`) is derived from UTC, so the reset
132 * must also be computed against UTC midnight â otherwise a user in 133 * CET/CEST sees a 24h countdown at local midnight while the UTC key 134 * actually rolls over 1-2h later. 135 */ 136 function update() { 137 var now = new Date(); 138 var midnight = new Date( 139 Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0) 140 ); 141 var diff = midnight - now; 142 143 var h = Math.floor(diff / 3600000); 144 var m = Math.floor((diff % 3600000) / 60000); 145 var s = Math.floor((diff % 60000) / 1000); 146 147 el.textContent = 148 String(h).padStart(2, '0') + 149 ':' + 150 String(m).padStart(2, '0') + 151 ':' + 152 String(s).padStart(2, '0'); 153 154 // When midnight arrives, reload to clear the limit 155 if (diff <= 0) { 156 clearInterval(timerInt); 157 window.location.reload(); 158 } 159 } 160 161 update(); 162 var timerInt = setInterval(update, 1000); 163}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.