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

111 lines
5.1 KiB
Markdown

# 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
```
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.