All checks were successful
Docker / build-and-push (push) Successful in 47s
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.
111 lines
5.1 KiB
Markdown
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.
|