Controller+BETADownload

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.