Fredrik Johansson d99c44cb9d
All checks were successful
Docker / build-and-push (push) Successful in 1m18s
Initial build: fullhouse, live music bingo from a real Spotify playlist
Based on bingo-bango by f8al (MIT) — same core card-generation idea
("both facets": a square is either an exact song title or an artist,
and an artist square lights up on any of their tracks), reworked with
a real host/guest split, live multiplayer sessions, and cover-art
squares. f8al credited in the footer, README, and PROPOSAL.md.

Three ways to play: host a live session (server-side Spotify
connection, join code + QR, caller screen, auto-call from real
playback), join someone else's session (no login needed), or solo
(guest PKCE login or a pasted public playlist URL, one-off card).

Genuinely verified against a real Spotify account throughout, not
just built and assumed working — including two real bugs found and
fixed along the way (Spotify's playlist-tracks endpoint quietly
renamed to /items with a reshaped response; reading playlist tracks
needs real user auth even for public playlists, so the "no guest
login" public-playlist path routes through the host's connection
instead of a dead-end app-only token). Both Docker images built and
run together on a real network with the nginx proxy verified working
end-to-end, and auto-call confirmed detecting an actual track playing
live through Spotify Connect within one poll tick.
2026-07-18 22:05:37 +02:00

fullhouse

Music bingo, generated from a real Spotify playlist. Based on bingo-bango by f8al (MIT licensed) — same core idea (a playlist becomes bingo cards, a square is either an exact song title or an artist, marking a track lights up both the title square and any of that artist's squares), reworked with a real host/guest split, live multiplayer sessions, and cover-art squares.

See PROPOSAL.md for the original design writeup.

Three ways to play

  1. Host a live session — connect your own Spotify (once, server-side), pick a playlist, get a join code + QR. A caller screen shows what's playing; guests' cards sync automatically, no login required on their end at all.
  2. Join a session — enter a host's code, get a card, watch it sync.
  3. Play solo — log into your own Spotify (PKCE, fully client-side, no server involvement) or paste any public playlist URL, get a one-off card with no session.

Real bugs found and fixed while building this

Worth documenting since they weren't obvious and would trip up anyone extending this:

  • Spotify quietly renamed /playlists/{id}/tracks to /playlists/{id}/items, with a reshaped response (entry.item instead of entry.track). The old path returns a bare 403 with no useful error message — looks exactly like a permissions problem, isn't one. Confirmed directly against the live API, not assumed from docs.
  • Reading playlist tracks needs real user authentication, even for fully public playlists — a pure Client Credentials (app-only) token gets 401 "Valid user authentication required" on the /items endpoint. The public-playlist-by-URL feature routes through the host's already-authenticated connection instead of a dead-end app token — guests still never log into anything, it's just genuinely user-authenticated on the host's behalf.
  • Algorithmic/Spotify-owned editorial playlists (Discover Weekly, your own "Top 100" style auto-generated lists, etc.) are restricted from third-party API access — this is real and documented (Spotify's Nov 2024 API policy change), not a bug. They just won't appear as usable pools.
  • Never point the client at an absolute cross-origin API URL — it gets silently CORS-blocked with no server-side CORS headers configured, and default error handling can mask that as "not connected" instead of a clear network error. Use relative /api/* paths everywhere; Vite's dev proxy and nginx's prod proxy both handle the same-origin routing.
  • Vite's dev server can bind to IPv6 loopback ([::1]) only, depending on the host's DNS resolver order — silently unreachable via 127.0.0.1, which matters a lot here since Spotify's redirect-URI security policy only exempts the literal 127.0.0.1 IP, not localhost. vite.config.ts sets server.host explicitly.

Local development

# server
cd server && npm install && npm run dev   # :3010

# client (separate terminal)
cd client && npm install && npm run dev   # :3080 in prod, :5190 locally

Register two redirect URIs in the Spotify dashboard (exact string match, trailing slash matters):

  • http://127.0.0.1:3010/api/spotify/callback — host mode
  • http://127.0.0.1:5190/ — guest PKCE mode (root, no path)

(127.0.0.1, not localhost — Spotify's HTTPS-required-for-redirect policy only exempts the literal loopback IP.)

Deploying

docker compose up using docker-compose.yml / .env (copy .env.example). Two images: web (nginx serving the built client, proxying /api/* same-origin to the api container) and api (the Express server). CI builds and pushes both on push to main.

VITE_SPOTIFY_CLIENT_ID gets baked into the client at build time (Vite env vars are compile-time, not runtime) — passed as a Docker build arg in CI, sourced from a SPOTIFY_CLIENT_ID repo secret.

Status

Verified end-to-end against a real Spotify account, not just built and assumed working: host OAuth, real playlist listing and track reading (after finding and fixing the /items rename), a live two-browser session with the guest's card syncing on poll, self-mark toggling, guest PKCE login and playlist reading, the public-playlist-by-URL path, and auto-call correctly detecting a real track playing through Spotify Connect within one poll tick.

License

MIT. Retains attribution to f8al's original bingo-bango design.

Description
Music bingo, inspired by https://github.com/f8al/bingo-bango
Readme 84 KiB
Languages
TypeScript 53%
JavaScript 30.9%
CSS 12.7%
Dockerfile 2%
HTML 1.4%