Webapp and stream overlay for displaying and remotely updating a Pokemon team roster, connected peer-to-peer over WebRTC.
Two parts work together:
┌────────────┐ websocket ┌───────────────┐ websocket ┌───────────────────┐
│ webapp │ ─────────────► │ signal server │ ◄───────────── │ overlay (OBS or │
│ (remote) │ └───────────────┘ │ SE custom widget)│
│ │ ◄════════ WebRTC data channel (P2P) ══════════► │ │
└────────────┘ └───────────────────┘
- Webapp (
packages/webapp) — the streamer's remote control: search any Pokemon (via PokeAPI), pick an appearance (official artwork, per-game sprites, Home renders, shiny variants), then drag & drop (or tap-to-assign) onto a six-slot roster. Layout controls (row / column / grid / staggered arrangements, spacing, stagger, per-slot scale and offsets) update the on-stream overlay live, with a WYSIWYG 16:9 preview. - Overlay (
packages/widget) — shows the team on stream. Two flavors from one codebase: a StreamElements custom widget (state persisted in the SE store) and a standalone OBS browser source (state persisted in localStorage, configured entirely by URL). - Shared (
packages/shared) — protocol types, team-state model/migration, and the layout math both sides render with.
The webapp and overlay pair through a lightweight websocket signal server and then talk directly over an encrypted WebRTC data channel. Auth is password-based, with self-signed JWTs issued by the overlay for passwordless reconnects (changing the password invalidates outstanding tokens). The connection self-heals: heartbeat, automatic reconnection with backoff, and signal-server reconnection on the overlay side.
Requires Node 22+.
npm install
# Terminal 1: local signal server (stand-in for webrtc-ws-server)
npm run signal-server
# Terminal 2: overlay dev server (http://localhost:5173)
npm start -w packages/widget
# Terminal 3: webapp dev server (http://localhost:5174)
npm start -w packages/webapp -- --port 5174Then:
- Open the widget dev harness (mocks the StreamElements environment):
http://localhost:5173/dev/?username=devchannel&password=test— or the standalone overlay:http://localhost:5173/overlay/?c=devchannel&s=test - Open the webapp and connect with username
devchannel, passwordtest.
In dev builds the signal server defaults to ws://localhost:8080; set VITE_SIGNAL_SERVER to override.
Checks: npm run lint, npm test, npm run build.
Due to the nature of WebRTC communications, this project requires a deployment of a websocket signal server to pair peers. Specifically, this project has been made using webrtc-ws-server. Ensure such a server is deployed and can be accessed.
Once a signal server is live, with the appropriate environment variable VITE_SIGNAL_SERVER=wss://signalserver set, run:
npm install
npm run build- Webapp:
./packages/webapp/build - SE widget script:
./packages/widget/build - Standalone overlay:
./packages/widget/build/overlay
The GitHub Pages workflow (.github/workflows/deploy.yml) deploys the webapp at the site root with the widget at /widget/ and the overlay at /widget/overlay/ (the VITE_SIGNAL_SERVER repository secret must be configured).
| Variable | Required | Purpose |
|---|---|---|
VITE_SIGNAL_SERVER |
production builds | websocket signal server address |
VITE_ICE_SERVERS |
no | comma-separated ICE server list (defaults to Google STUN) |
VITE_OVERLAY_URL |
no | overlay base URL used by the webapp's Overlay Setup dialog |
VITE_WIDGET_URL |
no | widget assets base URL used for StreamElements snippet generation (defaults to <origin>/widget/) |
VITE_MAX_TOKEN_LIFE |
no | auth token lifetime in ms (default 24h) |
The easiest way: open https://poketeam.stream and click New here? Set up your overlay on the login screen. The wizard walks you through StreamElements or OBS setup with copy-paste snippets, then verifies the connection live — you land in the app already connected.
The manual steps below are the offline fallback.
- Within StreamElements, add a
Custom widget(found within theStatic / Customsection). - With the empty custom widget selected, select
OPEN EDITORfrom the left side menu. - Under
HTML, highlight and delete any default content, then copy & paste the following:
<script type="module" src="https://poketeam.stream/widget/poke-team-stream-widget.js"></script>
<link rel="stylesheet" href="https://poketeam.stream/widget/poke-team-stream-widget.css">- Under
CSSandJS, highlight and delete any default content. These should be empty. - Under
FIELDS, highlight and delete any default content, then copy & paste the following:
{
"password": {
"label": "Set a password for website access",
"type": "password",
"value": ""
}
}- Under
DATA, highlight and delete any default content, then copy & paste{}. - Save with
DONE, then fill out the new field on the left side menu:Set a password for website access— anything you like; it's the password you'll use on the website, tied to your Twitch channel name. It's stored within StreamElements storage and not sent to any third parties.
Note: substitute https://poketeam.stream for your deployed webapp/widget server URL as needed.
- In the webapp, use the onboarding wizard's OBS path — or, once connected, the menu (⋮) → Overlay setup.
- Enter your channel name, generate (or choose) a secret, and copy the browser source URL.
- In OBS: add a Browser source, paste the URL, set 1920×1080. The background is transparent.
- Connect from the webapp with your channel name + that secret.
Run either the StreamElements widget or the OBS overlay for a given channel — not both at once (they pair under the same key).
Unit tests (npm test, vitest) cover the shared state migration, layout math, message parsing, and the JWT auth flow. The manual end-to-end checklist lives in docs/testing.md.