Skip to content

SiriusXM: sign in with a password, and each side's own feed found for them - #23

Merged
ralyodio merged 2 commits into
mainfrom
radio-team-feeds
Sep 4, 2026
Merged

ralyodio merged 2 commits into
mainfrom
radio-team-feeds

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Two things, both from feedback on #22.

The password door

The settings form asked for an email and had SiriusXM send a code — one of the two doors the SiriusXM player offers, and not the one people reach for. Read from the player's own bundle (endpoint table, 2026-09-03): authenticatePassword posts {handle, password} to identity/v1/identities/authenticate/password with the anonymous session's token and answers with an identity grant, which sessions/authenticated turns into a session exactly as the OTP grant is. So the form now takes email and password, with Email me a code instead beside it. The password is used for that one call and is never stored, logged or echoed (a test pins that); what is kept is the session, sealed as before. Tried first without a bearer like the code path; the anonymous session is minted only if the gateway insists.

Each side's own feed, found for them

For the leagues SiriusXM carries by team — NFL, NBA, MLB, NHL, WNBA, the college conferences, MLS — a fixture's page and a team's page draw an On SiriusXM section that looks up each side's own broadcast as soon as the page is up, rather than behind a button. The team name is parsed (packages/radio/src/teams.js: the provider's nickname when there is one, else the last word with club suffixes stripped), SiriusXM is searched for it on the reader's session, and only channels that actually name the team are kept — full name, a distinctive nickname of four letters or more, or the school for a college. Cached across readers for ten minutes (misses three), so a busy fixture costs one search per side, not one per view. Both sides settle separately, and the section says in words whether a side has no feed yet or the lookup broke. Nothing upstream at render: app.js fetches /radio/find?event=|team= once the section is up.

Also: SiriusXM failures are logged with what SXM said, so a failed sign-in can be read from the service logs. Genrewatch stays untouched; a test pins the brand default.

Tests: 1112 pass at the previous push; the password path adds three more against the fake gateway.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ac4GWmd6Nfd8JReX9u7kH1

ralyodio and others added 2 commits September 3, 2026 23:58
For the leagues SiriusXM carries by team -- NFL, NBA, MLB, NHL, WNBA, the
college conferences, MLS -- a fixture's page and a team's page now draw an
"On SiriusXM" section that looks up each side's own broadcast as soon as the
page is up, rather than behind a button. The team name is parsed
(packages/radio/src/teams.js), SiriusXM is searched for it on the reader's
session, and only channels that actually name the team are kept: the full
name, a distinctive nickname, or the school for a college. Lookups are cached
across readers for ten minutes; one search per side per fixture, not per view.

Nothing changes for leagues without team feeds, and nothing at all on
genrewatch, where the rail stays off. SiriusXM failures are now logged with
what SXM said, so a failed sign-in can be read from the service logs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ac4GWmd6Nfd8JReX9u7kH1
The settings form asked for an email and had SiriusXM send a code, which is
one of the two doors the SiriusXM player offers and not the one people reach
for. Read from the player's bundle: authenticatePassword posts {handle,
password} with the anonymous session's token and answers with an identity
grant, which sessions/authenticated turns into a session exactly as the OTP
grant is. So the form now takes email and password, with 'Email me a code
instead' beside it. The password is used for that one call and is never
stored, logged or echoed; what is kept is the session, sealed as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ac4GWmd6Nfd8JReX9u7kH1
@ralyodio ralyodio changed the title Each side's own SiriusXM feed, found for them SiriusXM: sign in with a password, and each side's own feed found for them Sep 4, 2026
@ralyodio
ralyodio merged commit 81a4f51 into main Sep 4, 2026
2 checks passed
@ralyodio
ralyodio deleted the radio-team-feeds branch September 4, 2026 00:03
@ralyodio ralyodio mentioned this pull request Sep 4, 2026
ralyodio added a commit that referenced this pull request Sep 22, 2026
…79)

Twice now Redis has gone away and tipoffwatch has stayed down until a person
noticed: 2026-09-13, and again on 2026-09-22 while the volume was being cleared.
Both times the shape was identical. `/healthz` answered 200 in 50ms, `/` returned
nothing at all, Railway reported the service Online, and restarting Redis did not
fix it -- the wedge is on this side of the socket, so only redeploying the web
service brought the site back.

The reason it cannot recover on its own is one line in the shared client. BullMQ
requires `maxRetriesPerRequest: null`, and that turns a disconnect into a hang: a
command issued while the socket is down is queued until it returns rather than
rejected. Verified rather than assumed -- a bare `ping()` against a dead port with
the app's own options never settles. The page render catches Redis errors and
falls through to Postgres, but there is no error to catch, so the request simply
stops. `/healthz` touches neither dependency, which is why every liveness signal
Railway has stayed green through both outages.

genrewatch already solved this for Postgres in its PR #23, after the same failure
on its pool: probe through the client the requests actually use, and exit when it
stops answering. A separate connection is the one thing guaranteed to look
healthy during this failure, which is why the probe has to share the client.

So the watchdog is ported here and given a subject, and both dependencies get
one: `select 1` on the shared `sql` handle, `PING` on the shared ioredis
connection. Exiting reads as drastic for a web server and is the cheapest correct
move -- the failure is process-local state no request can repair, a restart
demonstrably clears it, and Railway replaces the container in about a minute.
Hanging forever is not the safer option, it is the outage.

Redis gets one more failure than the pool, four probes rather than three. A
healthy Redis here is routinely unreachable for a while, because it restarts by
reading an RDB off the volume before accepting anything: 26 seconds now, 124
before the event streams were trimmed. Boot already throws if Redis is absent, so
an impatient watchdog would turn one Redis deploy into a deploy loop on this
service.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant