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](https://github.com/f8al/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](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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# server
|
|
|
|
|
cd server && npm install && npm run dev # :3010
|
|
|
|
|
|
|
|
|
|
# client (separate terminal)
|
|
|
|
|
cd client && npm install && npm run dev # :3080 in prod, :5190 locally
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-18 22:27:21 +02:00
|
|
|
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.
|
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
|
|
|
|
|
|
|
|
## 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.
|