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

# Installation - React Native

> Step-by-step guide to install and configure the Mesh SDK in your React Native application.

# Installation

## Requirements

| Requirement                        | Minimum |
| ---------------------------------- | ------- |
| React Native                       | 0.70.0  |
| React                              | 16.8.0  |
| iOS deployment target              | 13.0    |
| Android `minSdkVersion`            | 24      |
| Android `compileSdk` / `targetSdk` | 34      |
| JDK (for the Android build)        | 17      |
| Swift                              | 5.5     |
| Node.js                            | 18+     |

<Note>
  The SDK is a native module, so it does not run in **Expo Go**. A custom dev client or a bare
  React Native project is required.
</Note>

## Install the Package

```bash theme={null}
npm install @offline-protocol/mesh-sdk
```

Then, for iOS:

```bash theme={null}
cd ios && pod install
```

That is the whole installation. **Autolinking handles both platforms**, so you do not need a
`pod` entry, a `post_install` hook, a `settings.gradle` include, or a `MainApplication`
registration. Pre-built native libraries ship in the package for both platforms.

`pod install` should list `MeshSdk` under "Auto-linking React Native modules". The iOS native
binary ships as an XCFramework, so CocoaPods picks the right slice per build SDK and **device
and simulator builds both work** with no linker configuration.

<Warning>
  **Upgrading from a version below 0.20.0?** Delete these from your `ios/Podfile`. They now
  break the build:

  1. Any `pod 'MeshSdk', ...` line, with or without `:modular_headers => true`. A surviving
     line pointing into `.../mesh-sdk/ios` fails `pod install` outright with a "no podspec
     found" error.
  2. Any `post_install` hook that configured `MeshSdk`, in particular one setting
     `DEFINES_MODULE`, `SWIFT_INCLUDE_PATHS`, `LIBRARY_SEARCH_PATHS`, or `OTHER_LDFLAGS`.
  3. Any flag naming `offline_protocol_uniffi_sim` or `offline_protocol_uniffi_device`. Those
     archives no longer exist and linking them fails with *library not found*.

  Simulator builds work from 0.20.0 onward. If you previously concluded this SDK was
  device-only, that was a packaging bug, and any workaround you carried must be removed.
</Warning>

## iOS Setup

### Info.plist

```xml theme={null}
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to communicate with nearby devices</string>

<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to communicate with nearby devices</string>

<!-- Required for reliable BLE operation. Without these, iOS throttles or stops
     BLE scanning and advertising, and CoreBluetooth state restoration will not work. -->
<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
    <string>bluetooth-peripheral</string>
</array>
```

Both background modes are effectively mandatory: the SDK creates its central manager with a
state-restoration identifier, which CoreBluetooth only honors when `bluetooth-central` is
declared.

### Additional keys for Wi-Fi Direct

The iOS Wi-Fi Direct path uses MultipeerConnectivity. iOS 14+ refuses local-network discovery
without these:

```xml theme={null}
<key>NSLocalNetworkUsageDescription</key>
<string>This app uses the local network to discover and communicate with nearby devices</string>

<key>NSBonjourServices</key>
<array>
    <string>_offline-proto._tcp</string>
    <string>_offline-proto._udp</string>
</array>
```

<Note>
  The Bonjour service names must match the SDK's MultipeerConnectivity service type, which is
  `offline-proto`. MultipeerConnectivity registers both the TCP and UDP variants, so declare
  both.
</Note>

No entitlements or capabilities are required. The pod links `Foundation`, `CoreBluetooth`,
and `MultipeerConnectivity`.

## Android Setup

### What the SDK already declares

The SDK ships its own `AndroidManifest.xml`, and these entries merge into your app
automatically, so **you do not need to copy them**:

```xml theme={null}
<!-- BLE: Android 11 and below -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />

<!-- BLE: Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- Background mesh operation -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
```

The `neverForLocation` flag on `BLUETOOTH_SCAN` and the `maxSdkVersion="30"` gating propagate
into your merged manifest. `neverForLocation` has Play Console data-safety implications you
inherit, so it is worth knowing it is there.

### What your app must declare

The SDK deliberately does not force these on consumers, but the code requires them:

```xml theme={null}
<!-- Internet, Nostr, and relay transports -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Required for BLE scanning on Android 11 and below, and for Wi-Fi Direct on every level -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- Only if you enable Wi-Fi Direct -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

<!-- Android 13+: without this the mesh keep-alive notification is silently suppressed -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
```

<Warning>
  `POST_NOTIFICATIONS` is easy to miss. `start()` unconditionally launches a foreground service
  that posts a "Mesh Active" notification with a Stop action. On Android 13+ that notification
  is silently suppressed without the runtime grant, so users get no way to see or stop the
  mesh.
</Warning>

### ProGuard / R8

Nothing to configure. The SDK ships `consumerProguardFiles`, so its keep rules are applied to
your release build automatically. Without them, release builds would silently lose the BLE
mesh transport while debug builds kept working, but the rules are already handled for you.

## Runtime Permissions

### Android

The SDK checks permissions but never requests them. Your app must request them before calling
`start()`, or the SDK emits a `diagnostic` event at `error` level with "Missing Bluetooth
permissions".

```typescript theme={null}
import { PermissionsAndroid, Platform } from 'react-native';

async function requestMeshPermissions(): Promise<boolean> {
  if (Platform.OS !== 'android') return true;

  const permissions: string[] = [];
  const apiLevel = Platform.Version as number;

  if (apiLevel >= 31) {
    permissions.push(
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
      PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
    );
  } else {
    permissions.push(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
      PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
    );
  }

  if (apiLevel >= 33) {
    permissions.push(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
    // Only if you enable Wi-Fi Direct:
    // permissions.push(PermissionsAndroid.PERMISSIONS.NEARBY_WIFI_DEVICES);
  }

  const results = await PermissionsAndroid.requestMultiple(permissions);
  return Object.values(results).every(
    (r) => r === PermissionsAndroid.RESULTS.GRANTED,
  );
}
```

`BLUETOOTH` and `BLUETOOTH_ADMIN` are install-time permissions on API 30 and below, so they
do not need a runtime request.

You can also check and prompt for Bluetooth itself:

```typescript theme={null}
if (!(await protocol.isBluetoothEnabled())) {
  await protocol.requestEnableBluetooth();  // Android only; returns false on iOS
}
```

### iOS

No runtime API call is needed. The system prompts on first CoreBluetooth use, driven by the
Info.plist usage descriptions.

## Optional: Android Mesh Wake

This is the **only** manual native edit the SDK requires, and only if you want the mesh to
recover after Android kills your process. Add to your app's `AndroidManifest.xml`, inside
`<application>`:

```xml theme={null}
<meta-data android:name="com.offlineprotocol.MESH_WAKE_ENABLED" android:value="true" />
<!-- Optional; default 60, clamped to 10-300 -->
<meta-data android:name="com.offlineprotocol.MESH_WAKE_TIMEOUT_SECONDS" android:value="60" />
```

Then register the task at **module scope** in `index.js`, not inside a component:

```javascript theme={null}
import { AppRegistry } from 'react-native';
import { registerMeshWakeTask } from '@offline-protocol/mesh-sdk';
import App from './App';

AppRegistry.registerComponent('MyApp', () => App);

registerMeshWakeTask(async ({ reason }) => {
  // Persist inbound messages before start(), be idempotent, and resolve promptly.
  await protocol.start();
});
```

Both halves are required; the manifest flag alone does nothing. See
[`registerMeshWakeTask`](/docs/mesh-sdk/methods#android-mesh-wake) for the four caller obligations.

<Note>
  Mesh wake requires React Native 0.76.5+ when the New Architecture is enabled. Headless tasks
  did not work under bridgeless before 0.76 and were patchy until 0.76.5. On RN 0.84 and 0.85 a
  core bug (fixed in 0.86) can leave the wake service running after the task finishes.
</Note>

## Troubleshooting

### Linking error

If you see the "doesn't seem to be linked" error:

1. Run `pod install` (iOS) and **rebuild natively**. A JS-only reload will not pick up native changes.
2. Confirm `pod install` output lists `MeshSdk` under "Auto-linking React Native modules".
3. Run `npx react-native config` and check `platforms.ios.podspecPath` ends in `MeshSdk.podspec`.
4. Remove any leftover manual `pod 'MeshSdk'` line; it can shadow the autolinked pod.
5. Verify you are not using Expo Go.

### Simulator link failures

`Undefined symbols`, or "building for iOS Simulator, but linking in object file built for
iOS", means a leftover manual `post_install` hook is still linking archives by hand. Remove
it per the upgrade warning above.

### Upgrading to 0.21.0

A native rebuild is required, because new source files are compiled in on both platforms. Run
`pod install` for iOS. A JS-only update will not pick them up.

<Warning>
  Downgrading is not a rollback. The first launch on 0.21.0 moves delivery state out of the
  credential store into the app container and deletes the old copy. An older build comes up
  with an empty outbox, an empty pending queue, and an **empty block list**, meaning every
  previously blocked peer is silently unblocked. Roll forward with a hotfix rather than reverting the binary.
</Warning>

<Card title="Next Steps" icon="arrow-right" href="/docs/mesh-sdk/setup-rn">
  Set up the SDK in your app
</Card>
