This repository provides a generic remote-control bridge between Bitfocus Companion and any web app, using:
- A local WebSocket server (Companion module) that dynamically renders actions, variables, presets, and image feedbacks
- Per-site Tampermonkey userscripts that adapt a website’s UI/DOM/API to Companion
It lets you build integrations for sites without public APIs (e.g., Spotify, Google Slides, LiveU, Royalcast) using simple JavaScript.
- A Tampermonkey userscript runs on a target page.
- It opens a WebSocket to the local Companion module:
ws://<host>:<port>orwss://<host>:(port+1). - On connect, it sends a “register” payload describing its capabilities:
- Actions (things Companion can trigger)
- Variables (values Companion can read)
- Presets (ready-made buttons)
- The Companion module generates Companion UI dynamically.
- Actions, variables, presets, and image feedbacks are created from the registry.
- No hard-coded per-site schemas in the module.
- You press a button in Companion → Companion sends
invoketo the correct tab.
- The userscript calls DOM methods (e.g.,
.click()), sets inputs, or performsfetchin the page.
- When page state changes → userscript sends
variableUpdateback to Companion.
- Variables update live; presets and feedbacks react immediately.
clients/template.js: minimal starter template for new adapters (simple actions/variables)picker.js: advanced template with an in-page widget (status, host/port, logs) and an element picker to create watchers (variables) and click actions dynamicallyspotify.js,googleslides.js,liveu.js,royalcast.js,flowics.js: examples of real integrations
companion-module-websocket-server/(Companion module)src/main.ts: WebSocket server + dynamic Companion layersrc/config.ts: module config (host/port, UUID handling)
- Install Bitfocus Companion and load this module
- Build the module:
- In
companion-module-websocket-server/install deps (yarn or npm) - Build:
yarn build(ornpm run build)
- In
- Start the module in Companion
- Install a userscript in your browser
- Install Tampermonkey
- Create a new userscript and paste the contents of one of the files in
clients/- For a clean start: use
clients/template.js - For rapid prototyping with an on-page picker: use
clients/picker.js - Update the
@matchto your target site
- For a clean start: use
- Open the target site and connect
- Userscripts connect to
ws://127.0.0.1:3000by default - The module also exposes WSS on
(port+1)with a self-signed cert (no files required)- Example:
wss://127.0.0.1:3001
- Example:
- If you use
picker.js, use the top-right widget to selectws/wss, host and port, and see logs
On connect, send a register message:
{
"type": "register",
"registry": {
"uuid": "0000",
"source": { "site": "Spotify", "title": "Window Title" },
"actions": {
"play": { "name": "Play", "parameters": { "type": "object" } }
},
"variables": {
"songTitle": { "name": "Song Title", "type": "string", "value": "" }
},
"presets": {
"play_btn": { "name": "Play", "action": "play" }
}
}
}Send variable updates as:
{
"type": "variableUpdate",
"uuid": "0000",
"values": { "songTitle": "My Track" }
}The module namespaces variables internally as ${uuid}_${varKey} and actions as ${uuid}__<site>__${actionKey}.
- Each userscript creates a short tab id: first 4 chars of a UUID (e.g.,
0000) - Config option “Treat all clients as one (ignore UUIDs from tabs)” creates a logical client id per site (e.g.,
default_spotify), while still routing invokes with the original short id - In presets, use
{{uuid}}inside text to get the active id without hardcoding
If a variable ends with Url or Png64, the module auto-adds a feedback that sets a button background:
*Url: fetched and cached to PNG64*Png64: used directly
Examples:
- Spotify
albumArtUrl→ “Image Background – Album Art URL” - Google Slides
gsSlideCurrPng64→ “Image Background – Current Slide (PNG64)”
clients/template.js(minimal): connect, one action, one variable, handle invokeclients/picker.js(advanced): widget + picker to create watchers (variables) and click actions; persists tolocalStorageclients/googleslides.js: presenter popout hook + dom-to-imageclients/spotify.js: DOM observers, presets with variables, volume up/downclients/liveu.js: per-unit variables and modem details with rate-limited polling
- ≤ 1 request/second per site
- Use tokens from storage and page
fetchwhen possible to avoid CORS - Keep
TAB_UUIDto 4 chars in labels - Wrap DOM/fetch in
try/catch; change-detect before sending updates
- Variables blank: ensure register is sent before updates; verify connection; reload after toggling ignore-UUID setting
- Image not showing: variable must end with
UrlorPng64; forUrl, the image must be publicly fetchable - Use WSS:
wss://<host>:(port+1)– self-signed cert generated in memory
- Copy
clients/template.js→clients/<yoursite>.jsand set@match - Register one action and one variable
- Observe or poll the DOM; send
variableUpdatewhen values change - Add presets (text can include variables; attach image feedbacks)
- For discovery, use
clients/picker.jsto create watchers and clickers quickly
MIT. Example scripts are provided as-is.