Navidrome Jam — synchronized music playback for personal music libraries. Extension to Navidrome music server (Spotify Jam for FLAC).
Critical: Clients stream audio directly from Navidrome, NOT through the sync server. The sync server only broadcasts playback commands.
Server: express, socket.io, express-rate-limit, cors, dotenv, busboy, ssh2-sftp-client, resend (ES modules, no TypeScript)
Client: react 19, socket.io-client, crypto-js (MD5 for Subsonic auth), vite 7, eslint
cd server
npm install && npm run dev # Dev with hot-reload (node --watch)
npm start # ProductionEnvironment: Copy .env.example to .env. Note: .env.example is incomplete — see deployment section for all vars.
cd client
npm install && npm run dev # Vite dev server (http://localhost:5173)
npm run build # Production build → dist/
npm run preview # Preview production buildEnvironment: Copy .env.example to .env — needs VITE_NAVIDROME_URL and VITE_JAM_SERVER_URL.
# Sync only (no Navidrome required):
cd server && npm run dev
# Open server/test-client.html in 2+ browser windows
# Full stack (requires Navidrome):
# Terminal 1: cd server && npm run dev
# Terminal 2: cd client && npm run dev
# Open http://localhost:5173 in 2+ browserscd client && npm run lint # ESLint client
cd server && node --check src/index.js && node --check src/roomManager.js && node --check src/sftpUploader.js
cd client && npm run build # Verify production buildNo automated tests — manual testing with test-client.html or full stack.
Three files: index.js (Express + Socket.io, REST endpoints, WebSocket handlers, admin panel), roomManager.js (room state with grace periods), sftpUploader.js (SFTP upload pipeline to PikaPods).
Key design: room state snapshots to Railway volume every 30s + SIGTERM, 5-min grace period for empty rooms, invite codes/waitlist/deleted codes persist to JSON on volume, canControl() authorization (host OR co-host), trust proxy for Railway.
Three screens in App.jsx: Login → Room Selection → Jam Session.
Service layer: navidrome.js (Subsonic API + MD5 auth), jamClient.js (Socket.io wrapper with custom event emitter), NavidromeContext.jsx/JamContext.jsx (create/destroy on mount/unmount — prevents duplicate listeners during Vite HMR).
Visual theme: Windows 98 / GeoCities. CSS variables (--win-bg, --win-light, --win-dark, --titlebar-*). Transport icons via CSS borders; repeat/like via SVG mask-image data URIs.
- Client: Vercel (auto-deploys on push to main) — https://jam.zhgnv.com
- Server: Railway (auto-deploys on push to main, root:
/server) — https://navidrome-jam-production.up.railway.app - Navidrome: PikaPods — https://airborne-unicorn.pikapod.net
- See:
VERCEL_QUICKSTART.md,DEPLOYMENT.md
Critical for WebSocket: Sync server needs persistent connections → Railway, not Vercel Functions.
CORS: Socket.io accepts CLIENT_URL origins + any *.vercel.app (preview deploys).
Server .env.example is incomplete. Full list of production vars:
CLIENT_URL— deployed client URL (CORS)NAVIDROME_URL,NAVIDROME_ADMIN_USER,NAVIDROME_ADMIN_PASS— for registration (if unset, registration disabled gracefully)DATA_DIR— Railway volume mount (/data)RESEND_API_KEY,RESEND_FROM_EMAIL— invite code emailsTELEGRAM_BOT_TOKEN,TELEGRAM_ADMIN_CHAT_ID— waitlist notificationsRAILWAY_PUBLIC_DOMAIN— action token URLs in Telegram messagesPIKAPODS_SFTP_HOST,PIKAPODS_SFTP_PORT,PIKAPODS_SFTP_USER,PIKAPODS_SFTP_PASS,PIKAPODS_MUSIC_PATH— user uploads
Server-rendered Win98 HTML at /admin?key=NAVIDROME_ADMIN_PASS. Invite codes, waitlist, upload stats, server stats.
Users without invite codes join a waitlist. Admin gets Telegram notification with inline "Send Code" button — one-click to email invite and remove from waitlist. Uses one-time action tokens (GET endpoints, no webhook needed).
Check docs/plans/ before planning new features:
2026-02-15-room-settings-design.md— Kick user + password protection2026-02-15-queue-dnd-design.md— Queue drag-and-drop reordering2026-02-15-room-history-design.md— Room history / session logs2026-02-15-strategic-bets.md— Federation vs Bandcamp strategy
main— production (auto-deploys)feature/jam-with-boo— Valentine's edition at boo.zhgnv.com (separate domain, kawaii avatars, comma-separatedCLIENT_URLfor multi-domain CORS)
Read when working on internals: Architecture — Navidrome API endpoints, sync protocol, WebSocket/API patterns, user uploads, persistence.