Files
Twungeon/docs/twitch-setup.md
T

160 lines
8.1 KiB
Markdown

# Twitch setup
This guide connects the Twungeon proof of concept to a Twitch development
channel. Never commit real values: copy `.env.example` to an ignored `.env` or
provide the variables through your process manager.
## Prerequisites
- Node.js 22 or newer and npm.
- 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 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.
The installed Twurple packages are locked in `package-lock.json`. Re-check
their release documentation and Twitch's current scope requirements before
rotating tokens or upgrading packages.
## Required environment
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` | 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_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 |
The user token currently needs `chat:read`, `moderator:read:followers`, and
`channel:read:redemptions`. The follower and Channel Points subscriptions use
the broadcaster token, so its subject must match `TWITCH_BROADCASTER_ID`.
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
Twungeon's production viewer is a **Video Overlay Extension**. Twitch's current
Extension Manager labels this asset type **Video - Fullscreen** and its path
field **Video - Fullscreen View Path**. This is a manual dashboard setting; the
repository has no manifest that can change an Extension version in Twitch.
1. Create a Twitch Extension separately from the confidential OAuth
application, or create a new test version of the existing Twungeon
Extension.
2. On **Asset Hosting**, set **Testing Base URI** to the public HTTPS Twungeon
origin with a trailing `/`.
3. Under **Type of Extension**, enable **Video - Fullscreen** (the video-overlay
placement) and set **Video - Fullscreen View Path** to `extension`. Do not
use **Video - Component** for the production viewer. If a component entry is
retained temporarily for development, it may point at the same shared page,
but the overlay placement is the supported viewer experience.
4. On **Capabilities**, enable **Request Identity Link**. The viewer must click
**Share Twitch identity** in the overlay; the Extension Helper then invokes
`requestIdShare()` from that user gesture and supplies a new JWT through
`onAuthorized`.
5. Put the version in Local Test or Hosted Test and activate it in the channel's
video-overlay slot. Changing an already released version may require a new
Extension version and Twitch review; the repository cannot submit or
activate that version automatically.
6. Test the overlay while live in normal, theater, fullscreen, and a narrow
player. The video player and stream supply the game frame; `/extension`
should show only the upper-left controls on a transparent canvas.
7. A JWT without `user_id`, with the wrong
`channel_id`, an expired signature, or an `external` role is rejected.
8. Keep the Extension secret only in the backend environment. It must never be
included in the UI bundle or URL.
9. Start with `npm run build && npm start`. Confirm `/health` returns `ready:
true` and `twitchMode: "configured"` after chat connects.
The Extension JWT is exchanged for a random 15-minute Twungeon session. A
refresh obtains a new Twitch JWT, re-verifies it, and restores the existing
character; it does not create a player.
## Channel Points reward
1. In the broadcaster dashboard, create one custom reward for resurrection.
2. Choose its title and cost in Twitch. Disable viewer text input unless the
channel has a separate moderation reason to keep it.
3. Enable **Skip Reward Requests Queue** because Twungeon consumes the
redemption immediately and uses read-only EventSub access rather than
managing fulfillment status.
4. Retrieve the reward's stable ID through the Twitch API or Twitch CLI using
the broadcaster token, and set it as
`CHANNEL_POINTS_RESURRECTION_REWARD_ID`.
5. Do not reuse that reward for another action. Redemptions of every other
reward are ignored by the domain.
Bits, Cheers, and Bits-in-Extensions products are outside the MVP and have no
gameplay handler.
## Event flow
1. A follower types `!spawn` in chat.
2. Twurple supplies the stable chatter ID; the API checks that ID against the
broadcaster's followers.
3. The Extension sends its current Twitch JWT to
`POST /api/extension/session`.
4. The backend verifies signature, expiry, channel, role, and numeric user ID.
5. Controls enable only when the verified ID equals the chat-spawn owner ID.
6. A redemption of the configured Channel Points reward by the dead player
invokes resurrection exactly once.
## Troubleshooting and rotation
- **Chat disconnected:** check token validity, `chat:read`, channel login, and
outbound WebSocket access. Existing game state continues; new Twitch inputs
fail closed until reconnect.
- **Follower rejected:** ensure the token belongs to the broadcaster and has
`moderator:read:followers`; confirm the numeric broadcaster ID.
- **Extension returns `UNAUTHENTICATED`:** check the base64 secret, JWT expiry,
allowed channel, and identity-sharing capability. Never log the JWT.
- **`IDENTITY_NOT_BOUND`:** compare the chat and verified Extension numeric IDs;
display names are deliberately ignored for ownership.
- **Reward does not resurrect:** confirm the configured reward ID, the
`channel:read:redemptions` scope, the dead character owner ID, and that the
redemption was not already processed.
- **Public iframe fails:** confirm TLS, allowed origins, Extension asset paths,
and that WebSocket traffic reaches `/ws`.
To rotate credentials, stop new Twitch intake, replace the server environment,
restart, verify `/health`, test one follower check and Extension login, then
revoke the old token/secret. A full process restart creates a new in-memory run,
as required by the MVP persistence boundary.