# Browser connections inside your app Enable **Multiplayer & connections** in Studio → App details → Browser compatibility. MCP creators read `runtime_options_get`, then call `runtime_options_update` with the returned `base_policy_revision` and `multiplayer: true`. Read `capabilities_get` for `peer_connections` availability. This enables the SDK; it does not invent your app's multiplayer rules. `Hackemist.peers` (SDK 1.6.0) connects people inside a creator's own interface. There is no mandatory platform lobby, room creation screen or room code. `examples/browser-peers/index.html` is a complete responsive example with discoverable participants, invitations, acceptance, direct chat, a shared counter, disconnect/block controls and safe message rendering. ## Start and discover ```js const client = await Hackemist.peers.connect({ namespace: 'play', available: false, privacy: 'direct' }); // After the player chooses to appear in your app's participant list: await client.setAvailable(true); const {peers, next} = await client.list(); // Paginate with client.list({after: next}); lists contain at most 32 peers. // Refresh on user action, not on every animation frame. await client.invite(peers[0].id); client.on('invitation', invitation => { // Implement these buttons in your app. Do not silently accept strangers. showInvite(invitation.peer.name, () => client.accept(invitation.id), () => client.decline(invitation.id)); }); client.on('connect', connection => { client.send(connection.id, 'chat', {text: 'Hello!'}); }); client.on('message', event => { // Sender identity belongs to the accepted connection, not the message data. renderMessageAsText(event.peer.name, event.data); }); client.on('disconnect', event => showDisconnected(event.peer, event.reason)); client.on('error', event => showConnectionError(event.code)); ``` The example's `showInvite`, rendering and error functions are app code. Always install invitation/message handlers before making your app discoverable. An app may join a known document or collaboration namespace after its own access checks; the namespace is a grouping label, not an authorization secret. Public participants in the same app/version/namespace can see other available participants. Private documents or confidential groups need additional membership authorization in your backend; do not rely on guessing-resistant namespace names. Presence is opt-in and launch-scoped. Lists contain only available participants in the same app, immutable version, preview/live mode and namespace. No email, account token, global account ID, IP address or other app's participants is returned. Current platform, account, plan and app access are checked before issuing a connection ticket and periodically during participation. The existing signed-in requirement applies unless the administrator explicitly enables guests. Guest connections never grant cloud saves. ## Messages and application state `client.send(connectionId, name, JSON)` uses an ordered reliable data channel, suitable for chat, quiz actions and important state transitions. `{reliable: false}` selects an unordered channel with no retransmissions, suitable for replaceable position updates: ```js client.send(connection.id, 'position', {x, y}, {reliable: false}); ``` It returns `{queued: true}`: accepted by the local transport, not proof of delivery or durable saving. Add application sequence numbers, acknowledgements and resynchronization for important state. The SDK does not replay interrupted actions. A shared counter, collaborative editor or game needs its own conflict rules, initial snapshot and host selection. A direct link does not automatically synchronize arbitrary JavaScript memory or convert a single-player upload into a multiplayer game. Message names and JSON shape/depth/size are bounded. SDK receive/send rates, pending requests, links, ICE candidates and data-channel buffers are bounded. `BACKPRESSURE` means the sender must wait or drop replaceable updates; do not spin or grow an unbounded queue. Treat other participants' data as untrusted and render chat with `textContent`. Browser-side limits protect cooperative SDK users; they are not server-side anti-cheat or proof that a remote browser is honest. `client.connections` lists link IDs, peer display names and connection status. `client.pendingInvitations` retains bounded invitations so an interface can recover one received before its listener was attached. `client.disconnect(id)` ends a link or outgoing invitation; `client.block(peerId)` blocks that launch for the current connection session and ends existing links. Session blocks are not a permanent account block list. `client.setAvailable(false)` hides future discovery without ending accepted links. `client.close()` releases all browser transports and presence. ## Save progress deliberately Use the existing account save SDK for each signed-in player's durable progress. Save changed state/checkpoints and round results at a sensible interval; never save every frame. A peer message is not a saved checkpoint. Personal account saves are not a shared database: persistent team documents, offline messages or an authoritative shared world require a separately authorized data model/backend. Only save player data the app is entitled to collect. Peer-reported scores and wins are untrusted; competitive rewards and economies require game-specific trusted validation. ## Connectivity, privacy and interruption `privacy: 'direct'` allows direct WebRTC connections and uses configured TURN fallback when necessary. A STUN server helps discover a usable path. A network that blocks direct connectivity may still require TURN; without a configured relay a connection can fail. Direct participants can learn each other's network addresses. Explain this before making a player available and inviting/accepting peers. No ICE details are forwarded until the recipient accepts. `privacy: 'relay'` restricts ICE to TURN candidates, preventing the direct peer path from disclosing the player's address to the other participant. It fails explicitly if TURN is not configured. Relay bandwidth is still server/provider traffic. Do not promise server-free connectivity on every network. The service forwards bounded setup signals only; it does not retain SDP/candidates or gameplay/chat history. Signaling authentication must stay connected so access changes can stop SDK-managed links. Disconnect, logout, entitlement revocation or an operator disable closes them. Network reconnect uses fresh authority and restores availability, but app data is never replayed: rediscover/re-invite and synchronize an explicit snapshot. An invitation expires after the configured interval; accepted connection leases renew while both authenticated signaling heartbeats remain active. Expired or revoked participants cannot renew them. Backgrounded mobile browsers can suspend networking; apps must show reconnection state and preserve acknowledged checkpoints. These closures are enforced by the supplied SDK. The platform cannot physically terminate a direct connection between two deliberately modified, cooperating browsers, or revoke data they already received. Revocation stops fresh signaling, credentials and account-save access; existing relay credentials also have a bounded lifetime. Keep confidential access and authoritative game results in an authenticated backend when those guarantees are required. The default is at most four peer links per participant, further limited by the plan's player capacity. Small groups can connect multiple links using the same API; every pair requires acceptance. Large all-to-all groups multiply network and device work. Use the room relay or a separately hosted authoritative service for large groups, public persistent chat, secret game rules or trusted competition. Rendering and simulation remain in each participant's browser. ## Administration and deployment Administration → Multiplayer controls browser connections, invitation limits and maximum links. Existing per-account, app-launch, message and memory bounds remain active. Presence and setup use the existing realtime container and its websocket route: there is no new always-running broker, database or public port for direct connections. Metrics show participant, accepted-pair and pending-invitation counts, without message contents or connection addresses. A realtime restart ends these sessions. Configure STUN URLs and optional TURN URLs/shared secret under **STUN and TURN configuration**. The default public STUN endpoint is Google's `stun:stun.l.google.com:19302`; an operator can replace or remove it. Host your own STUN/TURN service or use a provider if its terms better suit your deployment. STUN alone is not a reliable substitute for TURN. For coturn-compatible REST authentication, set the relay's `use-auth-secret` and `static-auth-secret`, then put that same secret into Hackemist's **TURN shared secret** field. Use at least 24 random characters. Use `turn:turn.example.com:3478?transport=udp` and, if configured on the relay with a valid TLS certificate, `turns:turn.example.com:5349?transport=tcp`. The relay must have working public IP mapping, listening/relay firewall ports, bandwidth/allocation limits and private-network destination restrictions. A normal HTTP reverse proxy cannot forward TURN UDP. A managed provider is compatible only if it supports the same expiring HMAC-SHA1 REST credential scheme; permanent usernames/passwords or provider API keys do not belong in this field. The shared secret is encrypted with `SETTINGS_KEY`; browsers receive only app/peer-scoped ten-minute credentials. The SDK refreshes credentials before expiry and uses a bounded ICE restart for long-running links while retaining their data channels. Only the original caller can initiate renegotiation, after the previous handshake completes and no more than once per minute. Retry attempts are bounded. Browser/network suspension can still interrupt a connection; creators must handle the disconnect/resync events. Keep TURN on a separate service/host when its bandwidth grows. The repository's `docs/TURN_DOKPLOY.md` contains the full operator runbook; creators can also read the [creator guide](/creator-guide). Deploy matching **control, runtime and realtime** images, then reopen the player. Existing room and save APIs remain compatible. Test two independent browsers on different real networks, successful direct play, forced-relay play, blocked invitations, logout/revocation, offline/reconnect and each advertised device. Local browser connectivity cannot establish real-world NAT traversal or relay capacity.