> ## Documentation Index
> Fetch the complete documentation index at: https://www.offlineprotocol.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs target Mesh SDK v0.31.0. Match the installed package and binding before generating code. Start at /getting-started/agents for task-specific reading paths. Apps that call enableTelemetry on 0.26.0 to 0.29.0 should update to 0.30.0 or later: the telemetry uploader trusts only the telemetry endpoint's issuing roots from 0.30.0.
> Call the company and product Offline Protocol, never Offline alone. Current packages: @offline-protocol/mesh-sdk 0.31.0 (React Native), offline-protocol-sdk 0.31.0 on PyPI (Python 3.10 to 3.13; wheels for macOS 14+ arm64, Linux x86_64 and aarch64 with glibc 2.34+, Windows x86_64; other hosts build from source; extras lan and http add LAN discovery, the HTTP front and offline-protocol-verify), the offline-protocol crates 0.31.0 on crates.io, the OfflineProtocolSDK Swift package 0.31.0 (preview, iOS 13+ only), the Android library com.offlineprotocol:offline-protocol-sdk 0.31.0 on Maven Central (preview, minSdk 24), the Java binding com.offlineprotocol:offline-protocol-java 0.31.0 on Maven Central (JDK 22+, native library from the GitHub release's desktop archives), the C header offline_protocol.h with the native library in the GitHub release's desktop archives, the Ruby binding offline_protocol 0.31.0 on RubyGems (Ruby 3.3+, native library from the GitHub release's desktop archives), the browser binding (experimental; the offline-protocol-web crate 0.31.0 on crates.io, with the JavaScript in bindings/web built from the SDK repository, not on npm), the offline_protocol Flutter plugin (preview, not on pub.dev; a path dependency on the SDK repository at tag v0.31.0; MeshSdk carries the React Native module's native surface), the service container image ghcr.io/offline-protocol/offline-protocol-service 0.31.0 (linux/amd64 and linux/arm64), @offline-protocol/id-react 0.2.0, @offline-protocol/id-react-native 0.3.3, @offline-protocol/pol 0.1.2 and @offline-protocol/cli 0.2.9. Canonical docs URLs start with https://www.offlineprotocol.com/docs.
> The Mesh SDK runs in a native app or gateway, and from 0.31.0 in a browser through the experimental browser binding (a full node in a Web Worker; see /mesh-sdk/web). The OfflineID SDK for React does not provide browser mesh transport. Local mesh operation does not require a portal account or API key. The Mesh SDK appId is a mesh app identifier the developer chooses, not the portal App ID (app_...) that OfflineID and Proof of Location require.
> In v0.31.0 service RPC bodies are sealed in the 1:1 MLS session once the recipient runs 0.29 or later and the session is confirmed; discovery, the first request to a provider never met, and requests to older peers are signed plaintext, so keep secrets out of service bodies unless the event says encrypted: true. The HTTP front (offline-protocol-service --http) always carries calls inside encrypted direct messages but does not authorize callers. Message delivery, durable local acceptance and backend commit are distinct outcomes. Use the workflow guide for the required application logic.
> Offline Protocol CLI 0.2.9 is on npm (@offline-protocol/cli, command offline). Its local MCP server, named offline-protocol, runs with npx -y @offline-protocol/cli@0.2.9 mcp serve and requires no login or key. Hosted MCP is at https://mcp.offlineprotocol.com/mcp with an app API key in Authorization: Bearer and the matching App ID in x-app-id; organization keys are rejected, and Claude Desktop and Claude.ai cannot send these headers; there, use the public read-only endpoint https://mcp.offlineprotocol.com/public/mcp as a custom connector, or local MCP to scaffold or edit projects. Follow /tools/overview for setup and do not invent commands beyond it. MCP provides integration context and planning, not mesh execution; file-writing tools are local only.
> In v0.31.0 phone Wi-Fi peer streams carry data: Android over Wi-Fi Direct (the SDK forms the group with autoAccept on Android 10+) and over the Wi-Fi network it is on (DNS-SD), iOS over Network framework (LAN or AWDL). Android and iOS meet over that slot only on a shared Wi-Fi network that allows multicast; iOS 0.28 and later does not see iOS 0.27 or earlier over it. Otherwise use BLE or a provisioned relay across platforms. The Swift and Android packages are previews; the Android library is on Maven Central from 0.29.0 only, so never give a Gradle line for an earlier version. The receiver core ACKs before application persistence; use application acceptance for durable workflows.
> Proof of Location is Sepolia testnet witness evidence, not zero-knowledge proof or proof of presence. The geohash is public onchain. Read /proof-of-location/security before integration.

# Browser binding (experimental)

> Run the Mesh SDK v0.31.0 in a browser: a full node in a Web Worker with OPFS storage, WebRTC links, nearby rooms, Nostr relays and Web Bluetooth.

From v0.31.0 the SDK runs in the browser. The engine is compiled to WebAssembly and runs as a full node inside a dedicated Web Worker: it mints its own identity, forms MLS sessions, creates and administers groups, and relays like any other node. The page moves bytes between devices; the engine proves who is on the other end of every link and encrypts every message.

The binding is experimental. The engine is the `offline-protocol-web` crate on crates.io (0.31.0). The JavaScript package that runs it, `bindings/web` in the SDK repository, is not on npm yet, so you build it from the repository.

## What a browser node does

| Capability | How it works |
| - | - |
| Durable identity and groups | Each storage category is a sealed append log in origin-private (OPFS) files, so a node keeps its address and its groups across reloads. `storage: "memory"` gives a new device on every start instead. |
| Tabs of one origin | Tabs link over a `BroadcastChannel` with no codes and no server. |
| Devices on one network | WebRTC data channels, set up with two connection codes and no server. With no ICE server configured, links stay on the local network. |
| A room code instead of two codes | `NostrSignaller` moves the two codes through Nostr relays the application names, so a person types one short room code. |
| A nearby room | `NearbyRoom` lists the browsers behind the same network address for one application and links to the one a person picks. The relays carry only the setup; the link runs on the local network. |
| The Nostr transport | A node started with `nostr: { relays }` reaches any device by its address over the application's relays, phones included, and can publish and resolve usernames. |
| Web Bluetooth | In Chromium, `BluetoothCarrier` links the page to a phone or a board running the SDK as a Bluetooth LE peripheral, after a person picks the device in the browser's chooser. |
| Groups | Create a group, invite and remove members, and read a group's members and epoch. |
| Ephemeral sends | `{ ephemeral: true }` on a direct or group send goes once over the live link, for state the next send replaces, such as a game's positions. |

## Browsers

CI runs every test page in Chromium, Firefox and WebKit, and the storage conformance suites on real OPFS files in Chrome and Firefox. The SDK's two-device checklist has been run between Chrome on a laptop and Chrome on an Android phone over one Wi-Fi network, in both directions.

| Requirement | Detail |
| - | - |
| A secure context | OPFS, Web Locks and WebRTC exist only over HTTPS or on `localhost`. A LAN address over plain HTTP is neither, and the node does not start there. |
| Web Bluetooth | Chromium only. Firefox and Safari have no Web Bluetooth, and neither does any browser on iOS. |
| One node per account | A durable node holds a Web Lock for its account, so a second tab for the same `appId` and `profile` is refused. Give each tab its own `profile`, or use `storage: "memory"`. |

## Build it from the repository

Check out the SDK at the release tag, install the matching `wasm-bindgen` command-line tool, and build the module and its JavaScript glue into `bindings/web/pkg`:

```bash theme={null}
git clone --branch v0.31.0 --depth 1 https://github.com/Offline-Protocol/offline-protocol-sdk.git
cd offline-protocol-sdk
cargo install wasm-bindgen-cli --version 0.2.105 --locked   # once; must match Cargo.lock
bash bindings/web/scripts/build.sh
```

The build script refuses a `wasm-bindgen` version that differs from the one in `Cargo.lock`. To see two nodes talk, serve the repository root and open the [two-tabs example](https://github.com/Offline-Protocol/offline-protocol-sdk/tree/v0.31.0/examples/web-two-tabs) in two tabs:

```bash theme={null}
python3 -m http.server 8000 --bind 127.0.0.1
# open http://127.0.0.1:8000/examples/web-two-tabs/ in two tabs
```

Each tab shows its own `off1…` address and, within a second or two, the other tab as a neighbour with an encrypted session. The [lobby example](https://github.com/Offline-Protocol/offline-protocol-sdk/tree/v0.31.0/examples/web-lobby) is a two-player game between devices with durable nodes, WebRTC links, a nearby room, an MLS group and ephemeral game state ten times a second.

## Quick start

Import the client from `bindings/web/src/index.js` and serve `bindings/web/src` and the built `bindings/web/pkg` together, as the examples do: the worker loads the module from `../pkg`. Every engine event is dispatched as a DOM event named by its `type`, with the engine's JSON as `detail`:

```javascript theme={null}
import { OfflineNode } from "./offline-protocol-sdk/bindings/web/src/index.js";

// Durable by default: the same appId and profile are the same device after a reload.
const node = await OfflineNode.start({ appId: "com.example.app" });
console.log("this device is", node.address);

node.addEventListener("neighbor_discovered", ({ detail }) => {
  console.log("neighbour", detail.peer_id);
});
node.addEventListener("secure_session_established", ({ detail }) => {
  console.log("encrypted session with", detail.peer_id);
});
node.addEventListener("message_received", ({ detail }) => {
  console.log(detail.sender, detail.content);
});
node.addEventListener("fatal", () => {
  // The worker ended. Start the node again; a durable node comes back as the same device.
});

await node.sendMessage("off1...", "hello");
addEventListener("pagehide", () => node.stop());
```

`OfflineNode` also has `establishSecureSession`, `createInvite`, `parseInvite`, `resolveUsername`, `createGroup`, `inviteToGroup`, `removeFromGroup`, `groupInfo`, `sendGroupMessage`, `stats` and `stop`. The full surface, with each option, is in [`bindings/web/src/index.d.ts`](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/bindings/web/src/index.d.ts).

## Link devices

**Over WebRTC with two codes.** `new WebRtcCarrier(node)` makes links between devices. One side calls `offer()` and shows its code; the other passes it to `answer()` and shows its answer; the first passes that to `accept()`. Each code carries every candidate, so it can cross by paste, QR code or a room. Pass `iceServers` with your own STUN or TURN servers when devices cannot reach each other directly; the SDK names none. A `connection` event reports `connected`, `disconnected` and `closed` for each link.

**With a room code.** `new NostrSignaller(node, { relays })` moves the two codes through your relays. The host calls `host(room, carrier)` with a code from `newRoom()`, and each joiner calls `join(room, carrier)`. Generated room codes carry eighty bits, and a hosted room redials dropped relays and stays reachable until it is closed.

**In a nearby room.** `new NearbyRoom(node, carrier, { relays, iceServers, label, admit })` derives a room from the application id and the network's public address, which a STUN server you name reports. Each device announces itself, sees the others in `peers`, and calls `connect(peer)` to link to one. Your `admit` callback decides every offer, so ask the person before you answer `true`.

**Over Web Bluetooth.** Start the node with `ble: true`, create one `BluetoothCarrier`, and call `request()` inside a click to open the browser's chooser. The carrier verifies the device's identity, writes its hello, and only then announces the peer. `known()` returns devices this origin was granted before, so a page can reconnect without the chooser.

## Reach devices over Nostr

Start the node with your relays to carry the Nostr transport from the worker:

```javascript theme={null}
const node = await OfflineNode.start({
  appId: "com.example.app",
  nostr: { relays: ["wss://relay.example.com"], usernameDiscovery: true },
});
```

A message or `establishSecureSession` to a device with no session and no live link asks the relays for the key packages that device published, and the session forms from the first one that opens. `establishSecureSession` answers `"pending"` until then. With `usernameDiscovery`, the node publishes its `profile` as a username and `resolveUsername` returns every claim for a name in one `username_resolved` event. The relays are always the application's: `start` refuses `nostr` without a list of `ws://` or `wss://` URLs.

## What your page provides

* A secure context, and the engine module where the worker can fetch it, or passed to `OfflineNode.start({ wasm })` when the worker starts from a Blob URL.
* A `fatal` listener that starts the node again or tells the person the node ended.
* A way to move the two WebRTC codes, or relays for a `NostrSignaller` room, and the joining address shown to the people involved before anything is shared with it.
* Your own STUN or TURN servers to cross networks, your own Nostr relays for the Nostr transport and rooms, and an `admit` decision for a nearby room.
* A click that calls `BluetoothCarrier.request()`, because the browser opens its chooser only inside a user gesture.

Framing, identity, encryption and storage are the engine's. A browser build has no telemetry.

## Reference

* [Web binding contract](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/docs/bridges/web.md): the worker, storage, WebRTC, rooms, the Nostr transport, Web Bluetooth, and the tests CI runs.
* [Two-device checklist](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/docs/bridges/web-devices.md), with its recorded runs.
* [Threat model](https://github.com/Offline-Protocol/offline-protocol-sdk/blob/v0.31.0/docs/security/threat-model.md), R26 to R28, R30 and R31: the store key, connection codes, room codes, nearby rooms and the Bluetooth chooser.
* [`offline-protocol-web` on crates.io](https://crates.io/crates/offline-protocol-web).

The SDK is AGPL-3.0-only with a commercial license available; see [licensing](https://www.offlineprotocol.com/docs/operations/licensing.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.