160 lines
8.1 KiB
Markdown
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.
|