# 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](web-games.md); it needs no install.

## Install

```sh
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:

```sh
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

```js
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

```js
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.
