Skip to content

feat(twitch): channel point redemptions in the chat overlay (#1301) - #1307

Merged
Enubia merged 7 commits into
mainfrom
feat/twitch-channel-point-redemptions
Jul 7, 2026
Merged

feat(twitch): channel point redemptions in the chat overlay (#1301)#1307
Enubia merged 7 commits into
mainfrom
feat/twitch-channel-point-redemptions

Conversation

@Enubia

@Enubia Enubia commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Implements #1301 — Twitch channel point redemptions in the chat overlay.

Streamers can now connect their own Twitch account and, while viewing their own channel, see channel point redemptions inline as event messages — the same way subs and raids appear. Redemptions arrive over an authenticated EventSub WebSocket that layers beside the existing anonymous IRC chat client and never affects core chat if it's absent or fails.

What's included (issues #1302#1305)

  • Secure token store (prefactor) #1302 — Secure token store. OS-keychain-backed TokenStore seam in internal/auth; tests never touch the real OS store.
  • Redemption display path (fixture-driven) #1303 — Redemption display path. RedemptionEvent + RedemptionToMessage map a redemption onto the existing event-message path; distinct accent color; localized (en-US, de-DE) copy composed frontend-side.
  • Connect / disconnect your Twitch account (Device Code Flow + settings UI) #1304 — Connect/disconnect account. Twitch Device Code Grant flow (public client id, no secret), proactive + reactive token refresh, invalid-grant → logged-out, revoke-on-logout; "Twitch Account" settings section with the pending code panel and "Connected as {login}".
  • Live redemption stream via EventSub + channel-match gate #1305 — Live redemption stream. EventSub-over-WebSocket subsystem: pure channel-match activation gate (case-insensitive), welcome/subscribe timing, keepalive deadlines, session_reconnect migration, re-subscribe per session; network failures retry with backoff; auth failures / revocation route to the logged-out path without hammering. Startup with a valid refresh token reopens silently.

Guarantees

  • Non-critical: no EventSub transport or auth failure can block or disrupt the anonymous chat client or the Connect/Disconnect bindings.
  • Channel-match gate: redemptions only appear while viewing the broadcaster's own channel; the subsystem starts/stops on login, channel change, disconnect, and logout.
  • Secrets: access/refresh tokens live only in the OS keychain and in-memory; only the non-secret login is persisted in config. The embedded client id is public (device flow).

Review & testing

Built via subagent-driven-development: each of the four issues passed an individual spec+quality review, followed by a whole-branch correctness review (no Critical findings). Two whole-branch Important findings were fixed and re-reviewed clean:

  • Connected channel is normalized before the gate (a #name/URL/whitespace input no longer silently disables redemptions; the pure gate is unchanged).
  • Concurrent-login guard + the settings UI no longer flips to "Connect" on a stale poll error while still authenticated.

go test ./... + go test -race on the twitch/root packages, go vet, and the frontend suite (75/75) + build all pass. Manually verified locally end-to-end (real device-code login, a live redemption in the overlay, silent startup reopen).

Follow-up (not blocking)

An EventSub reconnection-hardening bundle is deferred to a separate issue: reset backoff after a healthy session, an explicit welcome-window read deadline, and bounded retry on transient (non-auth) subscribe failures.

Closes #1301, #1302, #1303, #1304, #1305

🤖 Generated with Claude Code

Enubia and others added 6 commits July 7, 2026 19:30
Introduces internal/auth with a TokenStore interface and a
KeychainTokenStore implementation (github.com/zalando/go-keyring) so the
upcoming Device Code Flow auth subsystem has a secure, testable place to
keep Twitch tokens. Adds TwitchConfig.Account.Login as the only
non-secret auth state persisted to the JSON config; tokens never touch
disk.

Prefactor slice for #1301, no user-facing behavior.
Add the complete display path for a Twitch channel-point redemption,
proven with fixture payloads and no auth/transport.

- Go: RedemptionEvent (EventSub-shaped) + RedemptionToMessage mapping
  into the existing ChatMessage event shape; EventTypeChannelPoints const
- config: Redemptions toggle in TwitchEvents, defaults on
- regenerate TS bindings (also picks up TwitchAccount from #1302)
- filter: classify channel_points_redemption, gate on events.redemptions,
  existing user blacklist applies via username
- render: distinct violet accent; headline composed from chat.redemption
  translation key (en-US + de-DE); viewer input rendered below
- settings: enabled redemptions toggle row

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an auth manager in internal/auth implementing the Twitch Device Code
Grant Flow (request/poll/validate) with proactive + reactive token refresh,
revoke, and silent startup restore. Injected HTTP client, base URL, clock,
and sleep make the flow deterministically testable against a stub server.

Expose TwitchStartLogin/TwitchLogout/TwitchAuthStatus bindings and
twitch:auth:* progress events. Add a "Twitch Account" section in Twitch
settings (connect / pending panel / connected) backed by a new auth store,
and disable the redemptions toggle with a hint while logged out.

UpdateConfig now preserves the server-owned Twitch.Account so a client
config echo cannot clobber the connected login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Finding 1: treat any 4xx from the refresh-token endpoint as an
unrecoverable grant (return ErrInvalidGrant + clear tokens/state),
instead of matching the literal "invalid_grant" message which real
Twitch refresh failures never send. Device-flow poll handling is
unchanged.

Finding 2: guard all a.config access (read, pointer-replace, save) with
a single sync.Mutex on App. Login writes are funnelled through a
lock-scoped persistTwitchLogin helper; UpdateConfig/GetConfig/
SaveWindowState take the same lock. Locks are released before network
calls and hotkey registration to avoid deadlock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a separate EventSub-over-WebSocket subsystem that opens an
authenticated connection, subscribes to
channel.channel_points_custom_reward_redemption.add on every session,
and feeds notifications through RedemptionToMessage to emit on the same
chat:message path. The anonymous chat client is untouched.

App orchestrates it behind a pure channel-match gate
(ShouldRunRedemptions), starting/stopping on connect, disconnect,
login, logout and startup restore, guarded by a dedicated mutex. Auth
loss (401 -> refresh -> invalid grant) and revocation route to the
logged-out path via onAuthLost; network failures reconnect with backoff.
Chat is never disrupted by EventSub/auth failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rify eventsub session id

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
macos-latest rolled to a newer Xcode whose actool fails on the Icon
Composer appicon.icon input during darwin:common:generate:icons,
breaking the PR build. release.yml was already pinned to macos-15 in
5b718b0; mirror that here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Enubia
Enubia merged commit 7c6f424 into main Jul 7, 2026
4 checks passed
@Enubia
Enubia deleted the feat/twitch-channel-point-redemptions branch July 7, 2026 20:01
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.

Twitch channel point redemptions in the chat overlay

1 participant