Add phone controllers to a JavaScript game
Use ControllerHost on your own server. Phones connect directly to it, with no trip through our service. Your game reads normalized input, assigns players and sends private views.
Building a web page without a server? Use the relay helper instead; it needs no install.
Install
npm install https://controller.plus/downloads/controller-plus-sdk-0.1.0.tgz
The SDK is not on the npm registry yet. Install it from that URL, not from a registry package with the same name.
To try the included example from the SDK source:
npm install
node examples/developer-host/server.mjs
For phones on your network, set PUBLIC_ORIGIN to your computer's reachable address and port. localhost refers to the phone itself when used on a phone. Use HTTPS/WSS for an internet deployment.
Attach it to an existing server
import http from 'node:http';
import {ControllerHost, serveControllerAssets} from '@controller-plus/sdk/node-host';
const origin = process.env.PUBLIC_ORIGIN || 'http://localhost:4340';
const server = http.createServer(async (req, res) => {
if (await serveControllerAssets(req, res)) return;
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Your game server');
});
const controllers = new ControllerHost({
server,
origin,
game: {id: 'my-game', name: 'My game', controls: {primary: 'jump'}}
});
controllers.on('input', ({playerId, input}) => {
// Pass this player's axes and buttons into your existing game actions.
});
server.listen(4340);
serveControllerAssets mounts the browser controller at /join and its assets under /sdk/. Reserve those paths in your application. The SDK is a controller integration, not a replacement for your website.
Show the correct QR
controllers.credentials() returns a player joinURL, a room ID, an engine endpoint and a separate host token. Encode only the player joinURL in the QR shown to guests.
Keep the host token in the authorized game process. Native engine adapters use it to receive phone input and manage views. Do not put it in a public page, scene asset, log file or player QR.
Send a private role
controllers.setView(playerId, {
kind: 'secret-role',
title: 'Midnight Table',
role: 'vampire',
text: 'Keep your role private.',
choices: []
});
Only that player's socket receives this view. Send explicitly public game state with controllers.publish(publicState). Never pass the full game object if it contains secrets.
Check the integration
Join with two controllers, assign different player IDs, press an action on each, disconnect one, then reconnect it. Verify the old held state clears and the returning player keeps the intended seat. Also verify that a spectator or another phone cannot read the private role.
Display the pairing QR
await host.qrSVG() returns a QR SVG for your game UI; await host.qrPNG() returns PNG bytes for engines that accept a texture. Both encode the current player invite. The local developer example displays the QR on the host computer and saves engine credentials to an ignored owner-only .local/developer-session.json file. It does not print those credentials to shared logs.