> ## 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.

# Setup - React Native

> Quick start guide and protocol lifecycle for the Mesh SDK.

# Quick Start

## Basic Setup

```typescript theme={null}
import { OfflineProtocol, MessagePriority } from '@offline-protocol/mesh-sdk';

const protocol = new OfflineProtocol({
  appId: 'my-app',
  profile: 'default',
  // Encryption is enabled and fail-closed by default
});

// Register listeners immediately, before start()
protocol.on('identity_ready', ({ address }) => {
  console.log(`My address: ${address}`);   // "off1q..."
});

protocol.on('message_received', (event) => {
  console.log(`From ${event.sender}: ${event.content}`);
  console.log(`Encrypted: ${event.encrypted}`);
});

protocol.on('neighbor_discovered', (event) => {
  console.log(`Found peer ${event.peer_id} via ${event.transport}`);
});

await protocol.start();

const messageId = await protocol.sendMessage({
  recipient: 'off1q...',        // a peer's address
  content: 'Hello!',
  priority: MessagePriority.High,
});

await protocol.stop();
await protocol.destroy();
```

<Warning>
  Register your event listeners **synchronously, right after construction**. The SDK subscribes
  to native events inside its own constructor, so events can arrive before your handlers are
  attached. Only two event types are held and replayed for a late listener
  (`internet_session_superseded` and `mesh_stopped_by_user`); everything else is dropped when
  unlistened.
</Warning>

## Your address

The device's identity is not something your app chooses. The SDK mints an Ed25519 identity
key on first run and derives a self-certifying `off1…` address from it. Peers verify an
address by re-deriving it from the key its owner presents.

```typescript theme={null}
const address = await protocol.localAddress();   // "off1q..." or null before startup
```

It is `null` until startup completes, because the key lives in storage that is not open
before then. The `identity_ready` event carries the same value the moment it becomes known.

`profile` is a separate thing: it selects *which stored identity* this instance runs as, and
it never leaves the device.

<Card title="Identity & Addressing" icon="fingerprint" href="/docs/mesh-sdk/identity">
  How addressing works, how to reach a peer, and how to migrate from `userId`.
</Card>

## Advanced Configuration

```typescript theme={null}
const protocol = new OfflineProtocol({
  appId: 'my-app',
  profile: 'default',
  transports: {
    ble: { enabled: true },
    internet: {
      enabled: true,
      serverAddress: 'wss://relay.example.com',
      autoReconnect: true,
      authToken: 'your-auth-token',
    },
  },
  encryption: {
    enabled: true,
    autoKeyExchange: true,
    storePending: true,
    requireEncryption: true,   // fail closed: never silent plaintext
  },
  relay: {
    allowRelay: true,
    minBatteryForRelay: 30,
    relayPriority: 'auto',
  },
});
```

See the [Configuration reference](/docs/mesh-sdk/configuration) for every option.

## Protocol Lifecycle

### Complete Flow Example

```typescript theme={null}
import {
  OfflineProtocol,
  MessagePriority,
  ProtocolState,
  MessageReceivedEvent,
  MessageDeliveredEvent,
  NeighborDiscoveredEvent,
  SecureSessionEstablishedEvent,
  ConnectionRequestReceivedEvent,
} from '@offline-protocol/mesh-sdk';

// 1. CREATE PROTOCOL INSTANCE
const protocol = new OfflineProtocol({
  appId: 'my-chat-app',
  profile: 'default',
});

// 2. REGISTER EVENT LISTENERS (before starting)
let myAddress: string | null = null;

protocol.on('identity_ready', (event) => {
  myAddress = event.address;
  console.log(`[IDENTITY] ${myAddress}`);
});

// Track discovered peers
const discoveredPeers = new Map<string, number>(); // address -> rssi

protocol.on('neighbor_discovered', (event: NeighborDiscoveredEvent) => {
  console.log(`[PEER FOUND] ${event.peer_id} via ${event.transport}, RSSI: ${event.rssi}`);
  discoveredPeers.set(event.peer_id, event.rssi ?? -100);
});

protocol.on('neighbor_lost', (event) => {
  console.log(`[PEER LOST] ${event.peer_id}`);
  discoveredPeers.delete(event.peer_id);
});

// Track outgoing messages
protocol.on('message_sent', (event) => {
  console.log(`[SENT] ${event.message_id} to ${event.recipient}`);
});

protocol.on('message_delivered', (event: MessageDeliveredEvent) => {
  console.log(`[DELIVERED] ${event.message_id} in ${event.latency_ms}ms, ${event.hop_count} hops`);
});

// Non-terminal: a retry is scheduled
protocol.on('message_retrying', (event) => {
  console.log(`[RETRY] ${event.message_id} attempt ${event.retry_count}, next at ${event.next_retry_at}`);
});

// Non-terminal: recipient is offline, message stays in the outbox
protocol.on('message_undeliverable', (event) => {
  console.log(`[PARKED] ${event.message_id}: ${event.reason}`);
});

// Terminal
protocol.on('message_failed', (event) => {
  console.log(`[FAILED] ${event.message_id}: ${event.reason} (${event.retry_count} retries)`);
});

// Handle incoming messages
protocol.on('message_received', (event: MessageReceivedEvent) => {
  console.log(`[RECEIVED] From ${event.sender}: ${event.content}`);
  handleIncomingMessage(event);
});

// Monitor transport changes
protocol.on('transport_switched', (event) => {
  console.log(`[TRANSPORT] ${event.from} -> ${event.to}: ${event.reason}`);
});

// Track secure sessions
protocol.on('secure_session_established', (event: SecureSessionEstablishedEvent) => {
  console.log(`[SECURE] Session with ${event.peer_id} (group: ${event.group_id})`);
});

// Handle connection requests
protocol.on('connection_request_received', (event: ConnectionRequestReceivedEvent) => {
  console.log(`[CONNECTION] Request from ${event.sender_name} (${event.sender})`);
});

protocol.on('connection_accepted', (event) => {
  console.log(`[CONNECTION] Accepted by ${event.accepted_by_name}`);
});

// Security warnings are worth surfacing
protocol.on('security_warning', (event) => {
  console.warn(`[SECURITY] ${event.reason_code} for ${event.peer_id}: ${event.reason}`);
});

// 3. START THE PROTOCOL
await protocol.start();

// At this point:
// - MLS is initialized and the identity key is available
// - identity_ready has fired
// - BLE scanning and advertising have begun
// - Internet, Nostr, and Reticulum transports enabled in config are auto-enabled

// 4. SEND A MESSAGE
async function sendChatMessage(recipientAddress: string, text: string) {
  return protocol.sendMessage({
    recipient: recipientAddress,
    content: text,
    priority: MessagePriority.High,
  });
}

// 5. CHECK STATE
const state = await protocol.getState();
if (state === ProtocolState.Running) {
  console.log('Mesh is running');
}

// 6. CLEANUP ON APP EXIT
async function cleanup() {
  await protocol.stop();
  await protocol.destroy();
}
```

<Note>
  `ProtocolState` is a **string** enum (`"Stopped"`, `"Running"`, `"Paused"`). It held numeric
  values through v0.19.0, so `state === ProtocolState.Running` now works where it previously
  could never be true. If your app persisted a `ProtocolState` itself, the old numeric value
  matches nothing, so treat an unrecognized persisted value as `Stopped`.
</Note>

## Event Sequence Timeline

```
+=======================================================================+
|                        PROTOCOL LIFECYCLE                             |
+=======================================================================+
|                                                                       |
|  1. new OfflineProtocol({ appId, profile })                           |
|     |                                                                 |
|     v                                                                 |
|  2. protocol.on('...', handler)  <- Register listeners SYNCHRONOUSLY  |
|     |                                                                 |
|     v                                                                 |
|  3. await protocol.start()                                            |
|     |                                                                 |
|     +--> MLS initializes, identity key is minted or loaded            |
|     +--> identity_ready { address }                                   |
|     +--> BLE advertising starts (device becomes discoverable)         |
|     +--> BLE scanning starts (looking for other devices)              |
|     +--> Configured internet / nostr / reticulum transports enable    |
|     |                                                                 |
|     v                                                                 |
|  +---------------------------------------------------------------+   |
|  |  PEER DISCOVERY PHASE                                         |   |
|  |                                                               |   |
|  |  * neighbor_discovered { peer_id, transport, rssi }           |   |
|  |  * secure_session_established { peer_id, group_id }           |   |
|  |                                                               |   |
|  |  MeshController evaluates peers and forms connections:        |   |
|  |    - MEMBER for same cluster                                  |   |
|  |    - BRIDGE for different clusters                            |   |
|  +---------------------------------------------------------------+   |
|     |                                                                 |
|     v                                                                 |
|  +---------------------------------------------------------------+   |
|  |  MESSAGING PHASE                                              |   |
|  |                                                               |   |
|  |  protocol.sendMessage({ recipient, content, priority })        |   |
|  |     |                                                          |   |
|  |     v                                                          |   |
|  |  message_sent { message_id, recipient, content, ... }          |   |
|  |     |                                                          |   |
|  |     +--> [OK]        message_delivered { message_id, ... }     |   |
|  |     +--> [RETRY]     message_retrying { next_retry_at }        |   |
|  |     +--> [OFFLINE]   message_undeliverable  (parked, repeats)  |   |
|  |     +--> [QUEUED]    message_deferred { reason }               |   |
|  |     +--> [TERMINAL]  message_failed { message_id, reason }     |   |
|  |                                                                |   |
|  |  INCOMING: message_received { sender, content, ... }           |   |
|  +---------------------------------------------------------------+   |
|     |                                                                 |
|     v                                                                 |
|  * neighbor_lost { peer_id }                                          |
|     |                                                                 |
|     v                                                                 |
|  4. await protocol.stop()                                             |
|     |                                                                 |
|     +--> BLE scanning and advertising stop                            |
|     +--> All connections closed                                       |
|     |                                                                 |
|     v                                                                 |
|  5. await protocol.destroy()  <- Release resources                    |
|                                                                       |
+=======================================================================+
```

## What Happens Under the Hood

### On `protocol.start()`

1. **Native protocol is created** (lazily; the constructor does not create it)
2. **MLS initializes** against iOS Keychain / Android EncryptedSharedPreferences, *before*
   transports start, so a peer can never be discovered before key exchange is possible
3. **`identity_ready` fires** with this device's `off1…` address
4. **BLE Manager initializes**, scanning for the Offline Protocol service UUID, and
   advertising this device with mesh metadata (degree, free slots, battery, uptime)
5. **Configured transports auto-enable**: internet, Nostr, and Reticulum

`start()` throws if the protocol is already started.

### On Peer Discovery

1. **BLE scan detects an advertisement** from another device
2. **MeshController evaluates the candidate**: connection budget (default max 4), peer score
   (RSSI, availability, battery, uptime, stability, load), and whether this is a cluster
   bridge opportunity
3. **If accepted**, the BLE connection is established and `neighbor_discovered` fires with the
   peer's canonical `off1…` address as `peer_id`
4. **If at capacity**, a lower-scoring peer may be evicted to make room

### On `protocol.sendMessage()`

1. **Message created** with a unique ID, TTL, timestamp, and priority
2. **`message_sent` fires** immediately
3. **DORS selects a transport** across BLE, Wi-Fi Direct, Internet, Reticulum, and Nostr
4. **ACK tracking begins**
5. On ACK, **`message_delivered`** fires
6. On a failed attempt, **`message_retrying`** fires with the scheduled retry time
7. If no transport is available, **`message_deferred`** fires and the message is persisted to
   the outbox
8. If the relay reports the recipient unreachable, **`message_undeliverable`** fires and the
   message is parked. This repeats on an escalating probe and is **not** terminal
9. Only **`message_failed`** is terminal

### On `protocol.stop()`

1. BLE scanning and advertising stop
2. All peer connections close
3. `neighbor_lost` fires for each disconnected peer
4. The protocol core stops

### Diagnostic Events

```typescript theme={null}
protocol.on('diagnostic', (event) => {
  console.log(`[${event.level.toUpperCase()}] ${event.message}`, event.context);
});
```

<Card title="Next Steps" icon="arrow-right" href="/docs/mesh-sdk/configuration">
  Explore the configuration reference
</Card>
