No description
Find a file
Julian Appel dc999d3c2c Abhaengigkeiten aktualisiert, npm audit meldet keine Lücken mehr
Der Stand war grossteils Express-Generator-Boilerplate von 2018 und brachte 19
gemeldete Schwachstellen mit, 4 davon kritisch. Jetzt: 0.

Sicherheitsrelevant:
- express 4.16.4 -> 4.22.2 behebt DoS in body-parser, die cookie-Lücke,
  path-to-regexp, qs sowie Template-Injection/XSS in send und serve-static
- hbs 4.0.6 -> 4.2.1 zieht handlebars nach; die alte Fassung hatte vier
  kritische Meldungen, darunter Remote Code Execution beim Kompilieren von
  Templates
- socket.io 4.7.5 -> 4.8.3 samt Sub-Abhaengigkeiten behebt die unbegrenzte Zahl
  binaerer Attachments und die Memory-Exhaustion in socket.io-parser sowie
  mehrere DoS-Pfade in ws — beides genau der Pfad, der im Betrieb offen im WLAN
  haengt
- cookie-parser, morgan, debug und http-errors auf aktuelle Fassungen

Entfernt: express-ws und ws als direkte Abhaengigkeiten. Beide wurden nirgends
importiert — die WebSocket-Verbindung laeuft ueber socket.io, das sein eigenes
ws mitbringt.

Express bleibt bewusst auf 4.x. Der Sprung auf 5 ist ein Major mit Aenderungen
an Router und Fehlerbehandlung und bringt hier keinen Sicherheitsgewinn.

Verifiziert auf Node 24.19: alle drei Monitoransichten, Adminpanel, 404-Seite
und der komplette Socket.IO-Durchlauf (score, timerDurationLeft, timerEnded,
scoreSideswitch, refresh, Reconnect) unveraendert in Ordnung, keine
Deprecation-Warnungen im Log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 22:06:39 +02:00
scoreboard Abhaengigkeiten aktualisiert, npm audit meldet keine Lücken mehr 2026-08-19 22:06:39 +02:00
.gitignore Added socketio, and moment 2024-04-21 12:48:59 +02:00
README.md Socket.IO teilt sich den HTTP-Port statt eines eigenen Servers auf 3001 2026-08-19 21:08:34 +02:00

Scoreboard-JS

Echtzeit-Anzeigetafel für Sportveranstaltungen — zeigt Timer und Spielstand auf einem separaten Monitor an und wird über ein Admin-Panel gesteuert.


Übersicht

Das System besteht aus zwei Teilen:

Teil URL Beschreibung
Monitor http://<host>:3000/ Vollbildanzeige für den Beamer/Monitor
Admin-Panel http://<host>:3000/admin Steuerung von Timer, Scoreboard und Anzeige

Alle Änderungen vom Admin-Panel werden über WebSockets (Socket.IO) in Echtzeit auf dem Monitor angezeigt — ohne Seiten-Reload.

Der Monitor rendert je nach Serverzustand eine von drei Ansichten:

Ansicht Template Wann
Splashscreen splashscreen.hbs Solange der Splashscreen aktiv ist (Standard beim Start)
Timer + Score indexScore.hbs Splashscreen aus, Scoreboard aktiv (Standard)
Nur Timer index.hbs Splashscreen aus, Scoreboard deaktiviert

Voraussetzungen

  • Node.js (v18 oder neuer)
  • npm

Installation & Start

cd scoreboard
npm install
npm start

Der Server startet mit nodemon und ist anschließend unter Port 3000 erreichbar. Die WebSocket-Verbindung läuft über denselben Port — es ist kein zweiter Port nötig.

Port ändern

Der HTTP-Port lässt sich über die Umgebungsvariable PORT setzen:

PORT=80 npm start

Auf dem Anzeige-Rechner (Raspberry Pi) wird Port 80 verwendet, damit der Kiosk-Browser die Seite ohne Portangabe unter http://localhost öffnen kann. Port 80 erfordert erhöhte Rechte.


Projektstruktur

scoreboard/
├── app.js                          # Express-App, Middleware, Routen
├── bin/www                         # HTTP-Server, startet die App
├── db.json                         # Persistente Liste bekannter Teamnamen
├── controllers/
│   ├── socketio.js                 # Socket.IO-Server (teilt sich den HTTP-Port)
│   ├── timer.js                    # Timer-Logik (Start, Pause, Reset, IncDec)
│   ├── score.js                    # Score-Logik (Punkte, Teams, Seitenwechsel)
│   ├── db.js                       # Lesen/Schreiben der Teamnamen in db.json
│   ├── etc.js                      # Splashscreen-Status
│   └── cli.js                      # Start/Stop des Kiosk-Browsers auf dem Anzeigerechner
├── routes/
│   ├── admin.js                    # REST-Endpunkte für das Admin-Panel
│   └── index.js                    # Monitor-Ansicht (waehlt das passende Template)
├── views/
│   ├── admin.hbs                   # Admin-Panel (Handlebars-Template)
│   ├── splashscreen.hbs            # Monitor: Vereinslogo
│   ├── index.hbs                   # Monitor: nur Timer
│   └── indexScore.hbs              # Monitor: Timer + Score + Teamnamen
└── public/
    ├── img/                        # Logos für den Splashscreen
    ├── javascripts/
    │   ├── admin.js                # Frontend-Logik Admin-Panel
    │   ├── index.js                # Frontend-Logik Monitor (ohne Score)
    │   ├── indexScore.js           # Frontend-Logik Monitor (mit Score)
    │   └── splashscreen.js         # Frontend-Logik Splashscreen (nur Refresh-Handler)
    └── stylesheets/
        ├── admin.css
        ├── index.css
        ├── indexScore.css
        ├── splashscreen.css
        └── seven-segment.css       # Seven-Segment-Display-Schrift für den Timer

Funktionen

Timer

  • Countdown-Timer mit konfigurierbarer Startzeit (Standard: 7:00 Minuten)
  • Start / Pause
  • Zurücksetzen auf eine neue Zeit (0:00 bis 10:00, in 15-Sekunden-Schritten)
  • Feineinstellung im laufenden Betrieb: ±1s, ±5s, ±10s
  • Nach Ablauf wird timerEnded an alle Clients gesendet und "ENDE" auf dem Monitor angezeigt

Scoreboard

  • Punktestand für Team A und Team B, direkt im Admin-Panel über +1/-1 steuerbar
  • Score direkt auf einen bestimmten Wert setzen
  • Score zurücksetzen (beide Teams auf 0)
  • Scoreboard ein-/ausblenden: Der Monitor wechselt zwischen Timer-only-Ansicht und Timer+Score-Ansicht

Teams konfigurieren

Jedes Team hat folgende Felder:

Feld Beschreibung
name Hauptname des Teams
name2 Zweiter Name (nur bei Spielgemeinschaft)
isSpielgemeinschaft Wenn aktiv, werden beide Namen untereinander angezeigt

Die Namen können frei eingetippt oder per Dropdown aus der Team-Datenbank gewählt werden.

Team-Datenbank

Häufig verwendete Teamnamen werden in db.json gespeichert und stehen im Konfigurationsdialog als Auswahl zur Verfügung. Teams lassen sich im Debugfenster hinzufügen und löschen.

Seitenwechsel (Halbzeit)

Über "Seitenwechsel" tauschen Team A und Team B auf dem Monitor die Seiten — der Spielstand bleibt unverändert, nur die Anzeigepositionen (links/rechts) wechseln.

Spiegelung des Admin-Panels

Sitzt die Bedienung hinter dem Monitor, stimmt die Links/Rechts-Zuordnung im Admin-Panel nicht mehr mit der Sicht auf die Anzeigetafel überein. Der Schalter "Teamanzeige im Adminpanel spiegeln" im Debugfenster dreht die Darstellung im Admin-Panel um — inklusive der +1/-1-Buttons, die dann dem jeweils richtigen Team zugeordnet werden. Die Monitoranzeige bleibt davon unberührt.

Splashscreen

Standardmäßig zeigt der Monitor beim Start das Vereinslogo. Über den Schalter im Debugfenster wird zwischen Splashscreen und Spielanzeige umgeschaltet.

Debugfenster

Sammelt die selten benötigten Schalter: Spiegelung, Scoreboard ein/aus, Splashscreen ein/aus, Monitor neu laden, Kiosk-Browser starten/stoppen sowie die Verwaltung der Team-Datenbank.

Kiosk-Browser steuern

Auf dem Anzeige-Rechner (Raspberry Pi) kann der Chromium-Browser aus dem Admin-Panel heraus im Kiosk-Modus gestartet und wieder beendet werden. Die Funktion ist Linux-spezifisch und setzt einen Benutzer pi mit Zugriff auf Display :0 voraus.

Monitor neu laden

Der Button "Monitor neu laden" sendet ein refresh-Event an alle verbundenen Clients und löst einen Seiten-Reload des Monitors aus. Nötig, wenn zwischen zwei Ansichten gewechselt wurde.


Architektur / Datenfluss

Admin-Panel (Browser)
        │
        │  HTTP REST (fetch)
        ▼
  Express-Server :3000
  routes/admin.js
        │
        ├── controllers/timer.js  ──┐
        ├── controllers/score.js  ──┤
        ├── controllers/etc.js    ──┤
        ├── controllers/db.js ──┐   │
        └── controllers/cli.js  │   │  io.sockets.emit(event, data)
                                │   ▼
                          db.json   Socket.IO (am selben Port)
                                    │
                          WebSocket-Verbindung
                                    │
             ┌──────────────────────┼──────────────────────┐
             ▼                      ▼                      ▼
   Monitor (splashscreen)   Monitor (index)      Monitor (indexScore)
   splashscreen.js          index.js             indexScore.js

Der gesamte Spielzustand (Timer, Score, Teams, Schalter) liegt im Arbeitsspeicher der Controller. Ein Serverneustart setzt ihn zurück; einzig die Teamnamen in db.json bleiben erhalten.

Bricht die WebSocket-Verbindung ab — auf dem Pi, der das WLAN selbst aufspannt, kommt das vor — verbindet sich der Client automatisch neu und holt sich beim connect-Event den aktuellen Stand über die REST-Endpunkte. Ein Wechsel der Ansicht während der Trennung (Splashscreen, Scoreboard ein/aus) wird dabei nicht bemerkt; dafür ist "Monitor neu laden" gedacht.

WebSocket-Events

Event Richtung Beschreibung
timerDurationLeft Server → Client Aktuelle Restzeit als String MM:SS
timerEnded Server → Client Timer abgelaufen
score Server → Client Aktueller Spielstand als String X:Y
scoreSideswitch Server → Client Seitenwechsel ausgelöst — Frontend neu laden
refresh Server → Client Kompletten Seiten-Reload auslösen

REST-API (Admin-Endpunkte)

Alle Endpunkte sind unter /admin erreichbar.

Allgemein

Methode Endpunkt Beschreibung
GET /admin/refreshMonitor Sendet refresh an alle Monitors
GET /admin/openBrowser Startet den Kiosk-Browser auf dem Anzeigerechner
GET /admin/killBrowser Beendet den Kiosk-Browser

Anzeige (etc)

Methode Endpunkt Beschreibung
GET /admin/etcGetValues Aktuellen Splashscreen-Status abrufen
GET /admin/etcToggleSplashscreen Splashscreen ein-/ausblenden

Antwortformat:

{ "splashscreenEnabled": true }

Timer

Methode Endpunkt Body Beschreibung
GET /admin/timerStart Timer starten
GET /admin/timerPause Timer pausieren
POST /admin/timerReset { duration: <Sekunden> } Timer zurücksetzen
POST /admin/timerIncDec { value: <Sekunden> } Timer erhöhen (positiv) / verringern (negativ)
GET /admin/timerGetValues Aktuelle Timer-Werte abrufen

timerStart und timerPause antworten mit 406, wenn die Aktion nicht möglich ist (Timer läuft bereits, ist bereits pausiert oder abgelaufen).

Antwortformat timerGetValues:

{
  "isPaused": true,
  "duration": "PT7M",
  "durationLeft": "PT7M",
  "print": "07:00"
}

Scoreboard

Methode Endpunkt Body Beschreibung
GET /admin/scoreToggle Scoreboard ein-/ausblenden
GET /admin/scoreGetValues Aktuelle Score-Werte abrufen
GET /admin/scoreClearScore Spielstand beider Teams auf 0
GET /admin/scoreToggleSideswitch Seiten der Teams tauschen
GET /admin/scoreToggleReferenceMirrored Admin-Panel-Darstellung spiegeln
POST /admin/scoreSetScore { team, score } Score direkt setzen
POST /admin/scoreAlterScore { team, dir } Score um 1 ändern (dir: "inc" / "dec")
POST /admin/scoreConfigTeams { teamA: { name, name2, isSpielgemeinschaft }, teamB: { ... } } Teams konfigurieren

team ist jeweils "teamA" oder "teamB".

Antwortformat scoreGetValues:

{
  "enabled": true,
  "teamA": { "name": "Team A", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
  "teamB": { "name": "Team B", "name2": "", "score": 0, "isSpielgemeinschaft": 0 },
  "sideswitch": false,
  "referenceMirrored": false,
  "print": "0:0",
  "teams": ["Arheilgen", "Prechtal"]
}

Team-Datenbank

Methode Endpunkt Body Beschreibung
GET /admin/dbGetValues Alle gespeicherten Teamnamen abrufen
POST /admin/dbAddTeam { teamName } Teamnamen hinzufügen (Duplikate und Leerstrings werden ignoriert)
POST /admin/dbDeleteTeam { teamName } Teamnamen löschen

Antwortformat:

{ "teams": ["Arheilgen", "Prechtal"] }

Abhängigkeiten

Paket Verwendung
express HTTP-Server und Routing
socket.io WebSocket-Kommunikation
moment Timer-Berechnung und Zeitformatierung
hbs Handlebars als View-Engine
morgan HTTP-Request-Logging
nodemon Automatischer Server-Neustart bei Dateiänderungen