ctx.network APIs run in both modes. Only one line of configuration picks the
mode.
Turning it on
In the workspacenanoforge.config.ts:
How it works
nf buildbuilds the server app a second time, for the browser, into the client’s output (server/). The engine picks browser builds of@nanoforge-dev/ecs/serverand@nanoforge-dev/network/serveron its own.nf startsetsNETWORK_MODE=p2pfor both apps. The Bun server process then only pairs players for the WebRTC handshake, onLISTENING_TCP_PORT. Game traffic never goes through it, and one process serves any number of games. The server app still starts there, with no players: its sends do nothing.- A page opened without
?room=hosts: its client app starts the server app in the same tab and joins it through an in-memory loopback. The server app gets the client’s environment. - A page opened with
?room=CODEjoins that game’s host over WebRTC. - The host and its client app keep separate worlds, exactly as on two machines.
Inviting players
The host logs its invite link to the console. The game can also show it:Developing, testing and deploying
- Editor: play runs the game in dedicated mode. The game code is the same in both modes.
- Locally:
nf build && nf start, open the game, then open the logged invite link in a second tab. - Hosted: build and enable a host as usual. Each player who opens the game without a link hosts their own game, so one host serves many games at once.
Players coming and going
- A player joins.
server.clients.onConnectfires on the host once the player’s four channels are open, so the listener can already send to it. - A player leaves.
server.clients.onDisconnectfires as soon as the signaling server reports it gone, a data channel closes, or its connection fails. - The host leaves. The game ends: the signaling server closes the room, and
each player’s
isConnected()turnsfalseabout a second later.
Limits
- Message size. Keep each packet at or below 64 KiB: WebRTC data channels cap message size.
ReliableUnorderedis really unordered, unlike over a dedicated server’s WebSocket.- NAT. A direct connection fails for some pairs of players, typically on
strict corporate or mobile networks. A TURN server in
ICE_SERVERSrelays them; the default list only has STUN servers. - Trust. The host runs the server app, so it can cheat. Check what players
send with
definePacket’svalidate. Anyone with the room code can join. - Background tabs. Browsers slow down timers in hidden tabs, so the server app in the host’s tab times its ticks with a Web Worker: the game keeps its pace for every player when the host switches tabs. Only the host’s own view slows down while hidden.
- Server environment. The server app gets the client’s environment:
NANOFORGE_SERVER_*variables do not reach it in this mode.
Signaling protocol
JSON text frames, at most 64 KiB each:
The host welcomes a player (
{type:"welcome", id, token}) once its data
channels are open, so players speak the same protocol as with a dedicated UDP
server.