Skip to content
 
 

Repository files navigation

Taskmaster Scoreboard Generator

A static scoreboard (index.html) with an optional phone remote control (control.html) for entering scores from another device. No build step — everything is plain HTML/CSS/JS, served as-is.

Getting started

Prerequisites:

  • Python 3 — runs the local dev server (serve.py). Check with python3 --version.
  • Node.js 18+ — only needed to run the test suite, not for the app itself. Check with node --version.
  • A Firebase project — only needed for the remote-control feature (below); the display page works fine without it.

Clone the repo:

git clone https://github.com/Unicycle77/tm-scoreboard.git
cd tm-scoreboard

Running it locally

npm start

Serves the site at http://localhost:8420. That's all index.html needs on its own — the remote-control feature additionally requires a Firebase project (below).

Setting up remote control (Firebase)

The display and control pages sync over a Firebase Realtime Database. Each person running their own copy of this app sets up their own free Firebase project — nobody shares a database with anyone else's copy of the app. To use this feature (locally or when you deploy your own copy):

  1. Sign in to Firebase. Go to the Firebase console with any Google account (create one first if you don't have one).
  2. Create a project. Click "Add project" / "Create a project", give it any name, and click through the wizard — you can turn off Google Analytics, it isn't used by this app. Everything here fits in Firebase's free Spark plan; no credit card or billing setup is required.
  3. Create the Realtime Database. In the left sidebar: Build → Realtime Database → Create Database. Pick any region. When it asks about security rules, choose "Start in test mode" — you'll lock it down properly in step 6, so don't skip that step (test-mode rules auto-expire after 30 days).
  4. Register a web app. Click the gear icon → Project settingsGeneral tab → scroll to "Your apps" → click the </> (web) icon. Give it any nickname, and you don't need to check "Also set up Firebase Hosting". After clicking "Register app", Firebase shows a firebaseConfig object — keep that page open, you'll copy values from it next.
  5. Configure this app. Copy js/firebase-config.example.js to js/firebase-config.js (gitignored — never commit this file, since it's specific to your own Firebase project) and fill in the values from the firebaseConfig object shown in the previous step:
    window.TM_FIREBASE_CONFIG = {
    	apiKey: "...",
    	authDomain: "...",
    	databaseURL: "...",
    	projectId: "..."
    };
  6. Lock down the database rules. Back in the console: Build → Realtime Database → Rules tab, replace the contents with:
    {
      "rules": {
        "sessions": {
          "$code": {
            ".read": true,
            ".write": true
          }
        }
      }
    }
    This scopes all access to sessions/<code> — anything else in the database is denied by default. Anyone who knows a specific session's code can read/write it (same trust model as the pairing code itself); nothing else is exposed. Click Publish.

That's it — npm start, open index.html, click "Remote" to get a pairing code/QR, and open control.html (or scan the code) on another device. Seeing "1 device connected" after opening both confirms it's wired up correctly.

Security notes

The rules above mean a session's code is its access control — no login, no per-user permissions. That's intentionally lightweight for a casual game night, but it's weaker than "someone has to see your screen or scan your QR code" implies: Firebase Realtime Database enforces no rate limiting and the rules don't require authentication, so a script can guess codes directly against Firebase's API without a human ever seeing one.

Codes are 6 characters from a 31-character alphabet (~888 million possible codes, CODE_LENGTH in js/sync.js) specifically to make that impractical — it was originally 4 characters (~924k codes), which was a small enough space to make untargeted brute-forcing a real, if low-stakes, possibility rather than a purely theoretical one. 6 characters raises the bar significantly; it doesn't make brute-forcing impossible against a well-resourced, targeted attacker, just no longer realistic as an idle/opportunistic attack. Given there's nothing sensitive in a scoreboard and sessions are short-lived, that trade-off is accepted rather than going further. If you want it hardened further:

  • Even longer codes are the cheapest next step — bump CODE_LENGTH in js/sync.js. No new dependencies or infrastructure, at the cost of a code that's more tedious to read aloud or type by hand (QR scanning is unaffected).
  • Firebase App Check is the thorough fix. Attaches an attestation token (via reCAPTCHA v3, free) to every request and lets you require a valid token in the RTDB rules, which blocks scripts talking to Firebase directly outside a real instance of this app in a browser — regardless of code length. Bigger lift: register a reCAPTCHA v3 site, add the App Check SDK to js/vendor/, and initialize it before other Firebase calls in both index.js and control.js.
  • Security-rules-based rate limiting doesn't help here — rules can throttle repeated writes to a known path, but can't limit how many different codes a client tries, since each guess targets an unrelated path with no shared state to check against.

Running the tests

Requires Node 18+ (check with node --version; if you're on an older default, nvm install 20 && nvm use 20 works well).

npm install
npm test          # unit + e2e
npm run test:unit # pure scoring/task logic only, no browser or network needed
npm run test:e2e  # full browser flows via Playwright

The e2e tests drive real pages against your actual Firebase project from step above (js/firebase-config.js must exist), so run the setup section first. Each test cleans up the session it creates afterward.

About

Taskmaster Scoreboard Generator

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages