# How Controller+ connects

Controller+ moves a phone's input into a game with as few hops as possible. This page explains who talks to whom, what is encrypted, what runs where, and what we are building next.

## The pieces

**The phone controller.** It runs in the phone's browser, with nothing to install, or in the Controller+ app for iPhone (Android is coming). Both speak the same protocol.

**The host.** This is what the phone connects to, and it is one of three things:

1. **The Controller+ desktop app** on your computer. It turns phones into gamepads or game keys for any game on that computer.
2. **Your game's own server**, built with the SDK's `ControllerHost` or an engine plugin.
3. **The Controller+ relay** at controller.plus, for browser games that have no server.

**The QR code.** It holds everything the phone needs: the host's address, a room code and a join token. The desktop app's QR also holds its encrypted port and the exact fingerprint of its certificate.

<figure>
<svg viewBox="0 0 720 250" role="img" aria-labelledby="routes-title routes-desc" style="width:100%;height:auto">
<title id="routes-title">The three routes in use today</title>
<desc id="routes-desc">A phone connects straight to a computer on the same network, straight to a game server, or through the controller.plus relay that the browser game also connects to.</desc>
<g font-family="system-ui, sans-serif" font-size="15" fill="currentColor">
<text x="10" y="40" font-weight="700">Local network</text>
<rect x="190" y="18" width="110" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="221" y="41">Phone</text>
<line x1="300" y1="36" x2="520" y2="36" stroke="#2e9a52" stroke-width="4"/><text x="335" y="28" font-size="13">Wi-Fi, a few ms</text>
<rect x="520" y="18" width="190" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="536" y="41">Your computer (app)</text>
<text x="10" y="120" font-weight="700">Game server</text>
<rect x="190" y="98" width="110" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="221" y="121">Phone</text>
<line x1="300" y1="116" x2="520" y2="116" stroke="#2458c4" stroke-width="4"/><text x="335" y="108" font-size="13">straight to the game</text>
<rect x="520" y="98" width="190" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="550" y="121">Game's server</text>
<text x="10" y="200" font-weight="700">Relay</text>
<rect x="190" y="178" width="110" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="221" y="201">Phone</text>
<line x1="300" y1="196" x2="380" y2="196" stroke="#d33a3a" stroke-width="4"/>
<rect x="380" y="178" width="120" height="36" rx="10" fill="none" stroke="#d33a3a" stroke-width="2"/><text x="398" y="201">our relay</text>
<line x1="500" y1="196" x2="520" y2="196" stroke="#d33a3a" stroke-width="4"/>
<rect x="520" y="178" width="190" height="36" rx="10" fill="none" stroke="currentColor" stroke-width="2"/><text x="545" y="201">Browser game tab</text>
</g>
</svg>
</figure>

## The three routes in use today

| Route | Used by | Path | What the phone shows |
| --- | --- | --- | --- |
| Local network | The desktop app | Phone to your computer over Wi-Fi. Browser controllers use port 4320; the app uses the encrypted port 4321. Nothing goes over the internet. | `LAN` and a ping of a few milliseconds |
| Game server | Games with their own server | Phone straight to the game's server. Our service is not involved. | `SERVER` |
| Relay | Browser games without a server: the homepage demo, the example games, a ten-line web game | The game tab and the phones both connect out to `wss://controller.plus/relay/ws`, and the relay passes messages within the room. | `RELAY` and a ping that includes two internet legs |

### Why the homepage demo uses the relay

A web page cannot accept incoming connections; a browser can only connect out. A game running in a browser tab, with no server and no desktop app, therefore has no address a phone can reach. Both sides meet at the relay, which is why the demo shows **VIA RELAY**. The routes below remove that limitation.

## What travels, and how fast

Phones and hosts exchange small JSON messages over a WebSocket:

- `hello` and `welcome` pair the phone with the host and give it a player seat.
- `input` carries the sticks and sixteen buttons with a sequence number. It is sent when something changes and refreshed about 30 times a second, so a lost moment heals quickly.
- `view` gives a phone its own private screen: a secret role, a vote, a drawing pad.
- `feedback` plays sounds and rumble on the phone.
- `ping` and `pong` measure the round trip. The phone shows the smoothed value on its screen; it is the time to the host and back, not the whole touch-to-screen delay.

## Security

- **Join tokens.** The QR carries a join token. The desktop app can issue a new QR at any time, and relay rooms close after 30 idle minutes.
- **Pinned encryption for the app.** The app connects to the desktop app over TLS and accepts only the certificate whose fingerprint was in the QR, so only the computer you scanned can answer. No certificate authority is involved.
- **Remembered computers.** When the app pairs with a desktop app, each side keeps a pairing key: the phone in its Keychain, the computer only as a hash. The phone reconnects without scanning until either side forgets the other.
- **Mouse and keyboard.** Control of the computer itself needs the encrypted app connection and the owner's approval on the computer. Browser controllers can play, but cannot move your mouse or type.
- **No accounts, no stored input.** Relay rooms live in memory; we keep counts for operations, not what players pressed.

## What runs where

| Part | Built with |
| --- | --- |
| Browser controller | HTML and JavaScript, served by controller.plus or by the desktop app |
| iPhone app | Swift and UIKit |
| Android app | Kotlin (in progress) |
| Desktop app | A Node.js runtime in a single executable, plus a small native helper per system: Swift with CoreHID on macOS, C with the ViGEmBus driver (an Xbox 360 controller) on Windows, `uinput` on Linux |
| Relay and website | A Node.js service at controller.plus |
| SDK | JavaScript, with Godot, Unity, Unreal and C/C++ packages |

## Next: the connection ladder

*Planned; not built yet.* The goal is that every session takes the shortest path that works, and the relay becomes the last resort instead of the default for browser games:

1. **Local first.** When the desktop app is running, a browser game on the same computer will reach it on `localhost`, and phones reach it over Wi-Fi. Web games then stay entirely on your network, at local-network latency.
2. **The Controller+ port.** The desktop app listens on fixed ports: 4320, and 4321 for encrypted connections. If you forward 4321 on your router, or let the app map it automatically (an opt-in setting), phones outside your home can connect to your computer directly. Pinned encryption and pairing keys protect it.
3. **Peer to peer.** When no direct path exists, the relay only introduces the two sides. Input then flows directly between phone and game over a WebRTC data channel, which crosses most home routers.
4. **Relay.** It always works, at the cost of an extra internet leg, so it is used only when nothing else connects.

Developers stay in control: a game's connection manifest lists the routes it allows and in what order, and it can rule out our relay entirely. See [Connection information](connections.md) for what the phone displays and [Protocol](protocol.md) for the message formats.
