Small staff control panel for VATSIM Scandinavia livestream operations. The React client talks to a Node/Socket.IO server, which controls OBS through obs-websocket-js.
/- staff control dashboard/overlay/map- OBS browser-source map focused on EKDK / Danish airspace/overlay/bug- OBS browser-source CPH Live 2026 watermark bug/overlay/atc- OBS browser-source watched ATC position panel
- Node.js 18+
- OBS Studio with the WebSocket server enabled
- OBS WebSocket reachable from the server host
- TrackAudio running locally when using the TrackAudio controls
npm run install:all
cp server/.env.example server/.envThen edit server/.env:
CONTROL_PASSWORD: shared staff password for the control panelJWT_SECRET: long random string used to sign session tokensOBS_HOST,OBS_PORT,OBS_PASSWORD: OBS WebSocket connection detailsTRACKAUDIO_WS_URL: optional TrackAudio SDK WebSocket URL, defaultws://127.0.0.1:49080/wsSCHEDULE_TIME_ZONE: optional schedule clock timezone, defaultEurope/CopenhagenPORT: HTTP port for the Node serverREDIS_URL: optional Redis URL for cached VATSIM snapshotsVATSIM_REFRESH_INTERVAL_MS: optional VATSIM polling interval, default15000AIRCRAFT_HISTORY_LIMIT: optional samples kept per aircraft, default3AIRCRAFT_HISTORY_MAX_AGE_MS: optional maximum aircraft sample age, default600000EVENT_STATS_AIRPORT: optional airport counted by the bug stats, defaultEKCHEVENT_STATS_AIRPORT_LAT,EVENT_STATS_AIRPORT_LON: optional event airport position, defaults to EKCHDEFAULT_TRANSITION_ALTITUDE_FT,DEFAULT_TRANSITION_LEVEL: optional fallback map label transition values, defaults4000and45
Sector map data is generated as GeoJSON during npm run build from the EuroScope files in local-data/sectors and written to server/sectors.json.
npm run devThe Vite client runs on http://localhost:5173 and proxies API and Socket.IO traffic to the server on http://localhost:3001.
npm run build
npm startIn production, the server serves the built client from client/dist.
Add http://localhost:3001/overlay/map as an OBS browser source to show the EKDK map overlay.
Add http://localhost:3001/overlay/bug as a separate OBS browser source for the CPH Live 2026 watermark bug.
Add http://localhost:3001/overlay/atc as a separate OBS browser source for the watched ATC position panel.
The bug rotates to the stats layout every 5 minutes for 12 seconds by default; tune with ?intervalMinutes=5&showSeconds=12. The stats are live from /api/event-stats; green and red query params can still override the numbers for testing.
docker compose up --buildCompose starts the production app and Redis. The app uses host networking so it can reach OBS on 127.0.0.1:4455, and listens on http://localhost:3001.
Keep OBS_HOST, OBS_PORT, and OBS_PASSWORD in server/.env. Redis is published on 127.0.0.1:6379 for the app container.
- Socket connections require a valid JWT from
/api/auth/login. - Do not publish a real
server/.env; keep onlyserver/.env.examplein source control and rotate secrets before sharing deployments. - Put the app behind HTTPS before exposing it outside localhost or a trusted LAN.
- OBS command payloads are validated server-side before being sent to OBS.
- TrackAudio commands are proxied through the authenticated Socket.IO server; TrackAudio itself only needs its local SDK WebSocket.
- The server runs a lightweight VATSIM worker and exposes the latest snapshot at
/api/vatsim-data. - The worker also stores rolling aircraft samples at
/api/aircraft-historyso overlays can animate between VATSIM updates. - Event stats are tracked from the VATSIM feed at
/api/event-statsand persisted in Redis when available. - Aircraft labels resolve airline logos through
/api/airline-logo/:code, backed by Kiwi logos with local overrides inserver/airline-logo-overrides.json. - When
REDIS_URLis set, VATSIM snapshots and aircraft history are cached in Redis; otherwise the server uses in-memory cache. - Login attempts are rate-limited in memory. For multiple server instances, move rate limiting to shared storage or put it behind a trusted proxy-level limiter.