Files
fullhouse/README.md
Fredrik Johansson cb969133ce
All checks were successful
Docker / build-and-push (push) Successful in 47s
Document all four redirect URIs, not just the two local dev ones
Spent a real debugging session chasing a "not matching configuration"
error assuming it was host mode, when it was actually guest PKCE mode
hitting a redirect URI (production, root path) that was simply never
registered — the README only documented the two local dev URIs, not
that host mode and guest mode each need their own entry per
environment (four total), and gave no way to tell which flow's
request was actually failing.

Added the full four-URI table and a note to check the browser's actual
address bar at the point of failure — the error message is identical
regardless of which flow's redirect_uri didn't match.
2026-07-18 22:27:21 +02:00

5.1 KiB

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 four redirect URIs in the Spotify dashboard — one pair per environment, host mode and guest mode each need their own. Exact string match, trailing slash included: guest mode is ${window.location.origin}/ (client/src/lib/pkce.ts) — that trailing slash is not optional, ever, in any environment.

Environment Host mode (/api/spotify/callback) Guest PKCE mode (root, no path)
Local dev http://127.0.0.1:3010/api/spotify/callback http://127.0.0.1:5190/
Production https://<your-domain>/api/spotify/callback https://<your-domain>/

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

All four need to actually exist in the dashboard's list — host mode and guest mode failing to match are indistinguishable from the outside ("redirect_uri: Not matching configuration" either way), so if this error shows up, check the browser's actual address bar at the point of failure to see which flow's redirect_uri is actually being sent before assuming which one is missing.

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.