feat: connect Twungeon to live Twitch

This commit is contained in:
2026-08-17 12:02:35 -07:00
parent 5bc70dd9b2
commit df984690ce
13 changed files with 523 additions and 55 deletions
+40 -9
View File
@@ -10,7 +10,8 @@ provide the variables through your process manager.
- A Twitch developer application and a development channel.
- A Twitch Extension with identity sharing enabled. Twungeon requires the
numeric `user_id`; anonymous or opaque-only viewers fail closed.
- A broadcaster user access token that can read chat and check followers.
- A confidential Twitch developer application whose OAuth callback is the
public Twungeon `/oauth/callback` URL.
- The Extension shared secret, copied exactly as the base64 value supplied by
the Extension Manager.
@@ -20,16 +21,19 @@ rotating tokens or upgrading packages.
## Required environment
Set `TWITCH_ENABLED=true`, then provide:
Provide the following values first. Keep `TWITCH_ENABLED=false` until OAuth,
Extension, and Channel Points setup are complete; then switch it to `true` for
live operation.
| Variable | Purpose |
| --- | --- |
| `TWITCH_CLIENT_ID` | Developer application/Extension client ID |
| `TWITCH_CLIENT_ID` | Confidential OAuth application client ID |
| `TWITCH_CLIENT_SECRET` | Server-only application secret |
| `TWITCH_BROADCASTER_ID` | Numeric channel owner ID |
| `TWITCH_CHANNEL_LOGIN` | Channel login joined by Twurple chat |
| `TWITCH_BOT_ACCESS_TOKEN` | Broadcaster user access token |
| `TWITCH_EXTENSION_SECRET` | Base64 Extension shared secret |
| `TWITCH_OAUTH_REDIRECT_URI` | Exact HTTPS OAuth callback registered with Twitch |
| `TWITCH_TOKEN_FILE` | Restricted file used for access and refresh tokens |
| `CHANNEL_POINTS_RESURRECTION_REWARD_ID` | Stable ID of the resurrection custom reward |
| `PUBLIC_BASE_URL` | Public HTTPS backend origin |
@@ -40,13 +44,40 @@ Twungeon subscribes only to the configured custom reward and uses Twitch's
stable redemption ID as the deduplication key. The broadcaster owns the reward
cost in Twitch; the backend does not duplicate it.
## Broadcaster OAuth authorization
1. Register a **Confidential** Twitch application with an exact HTTPS redirect
such as `https://twungeon.example/oauth/callback`.
2. Configure `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET`,
`TWITCH_CHANNEL_LOGIN`, `TWITCH_OAUTH_REDIRECT_URI`, and
`TWITCH_TOKEN_FILE` while leaving `TWITCH_ENABLED=false`.
3. Restart Twungeon and confirm `/oauth/status` reports `configured: true` and
`authorized: false`.
4. Open `/oauth/login` in a browser and authorize using the configured
broadcaster account. Twungeon requests only `chat:read`,
`moderator:read:followers`, and `channel:read:redemptions`.
5. Confirm the callback reports success and `/oauth/status` reports
`authorized: true`. The callback validates the client ID, broadcaster login,
and required scopes before storing the token.
6. Record the numeric broadcaster ID shown by the callback as
`TWITCH_BROADCASTER_ID`.
The access and refresh tokens are stored atomically at `TWITCH_TOKEN_FILE` with
owner-only permissions. Twurple refreshes the access token when necessary and
Twungeon replaces the stored token without printing either token. The token
directory must be writable only by the Twungeon service account. Never place the
token file inside the repository or a web-served directory.
## Extension configuration
1. Host the built static files and backend at an HTTPS origin allowed by the
Extension configuration. Twitch embeds the UI in an iframe and supplies the
Extension Helper JWT through `onAuthorized`.
2. Point the viewer/mobile video component to the application root.
3. Enable identity sharing. A JWT without `user_id`, with the wrong
1. Create a Twitch Extension separately from the confidential OAuth
application. In the Extension Manager, use a testing base URI ending in
`/`, select a video component, and set its viewer path to `extension`.
2. Enable **Request Identity Link**. The viewer must click **Share Twitch
identity** in the component; the Extension Helper then invokes
`requestIdShare()` from that user gesture and supplies a new JWT through
`onAuthorized`.
3. A JWT without `user_id`, with the wrong
`channel_id`, an expired signature, or an `external` role is rejected.
4. Keep the Extension secret only in the backend environment. It must never be
included in the UI bundle or URL.