npm package: @magicred-1/ble-mesh
Description: BLE Mesh networking library for React Native with seamless permissions
Author: rrigoni
Latest version: 1.4.1

# ble-mesh

A React Native library for BLE (Bluetooth Low Energy) mesh networking. Build decentralized, peer-to-peer messaging apps that work without internet connectivity.

## Features

- **Cross-platform**: Works on both iOS and Android
- **Mesh networking**: Messages automatically relay through peers
- **End-to-end encryption**: Noise protocol for secure private messages
- **File transfer**: Send files over BLE mesh (chunked for reliability)
- **Solana transactions**: Multi-signature transaction signing over BLE
- **Seamless permissions**: Handles permission requests automatically
- **Message deduplication**: Built-in deduplication prevents duplicate messages
- **Peer discovery**: Automatic discovery of nearby devices

## Installation

```bash
npm install ble-mesh
# or
yarn add ble-mesh
```

### iOS

```bash
cd ios && pod install
```

Add the following to your `Info.plist`:

```xml
<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>
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
  <string>bluetooth-peripheral</string>
</array>
```

### Android

Add the following permissions to your `AndroidManifest.xml` (they are already included in the library manifest):

```xml
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
```

## Usage

```typescript
import { BleMesh } from 'ble-mesh';

// Start the mesh service (automatically requests permissions)
await BleMesh.start({ nickname: 'Alice' });

// Listen for events
BleMesh.onPeerListUpdated(({ peers }) => {
  console.log('Connected peers:', peers);
});

BleMesh.onMessageReceived(({ message }) => {
  console.log('Received message:', message);
});

// Send a public broadcast message
await BleMesh.sendMessage('Hello everyone!');

// Send a private encrypted message
await BleMesh.sendPrivateMessage('Secret message', recipientPeerId);

// Get current peers
const peers = await BleMesh.getPeers();

// Stop the service
await BleMesh.stop();
```

## API Reference

### BleMesh

The main singleton instance for interacting with the BLE mesh.

#### Methods

##### `start(config?: MeshServiceConfig): Promise<void>`

Starts the mesh service. Automatically requests required permissions.

```typescript
interface MeshServiceConfig {
  nickname?: string;              // Default: 'anon'
  autoRequestPermissions?: boolean; // Default: true
}
```

##### `stop(): Promise<void>`

Stops the mesh service and disconnects from all peers.

##### `requestPermissions(): Promise<PermissionStatus>`

Requests all required Bluetooth and location permissions. Called automatically by `start()` unless `autoRequestPermissions: false`.

```typescript
interface PermissionStatus {
  bluetooth: boolean;
  bluetoothAdvertise?: boolean; // Android 12+
  bluetoothConnect?: boolean;   // Android 12+
  bluetoothScan?: boolean;      // Android 12+
  location: boolean;
}

const permissions = await BleMesh.requestPermissions();
if (!permissions.bluetooth || !permissions.location) {
  console.error('Required permissions not granted');
}
```

##### `checkPermissions(): Promise<PermissionStatus>`

Checks current permission status without requesting. Useful for showing permission UI state.

```typescript
const permissions = await BleMesh.checkPermissions();
console.log('Bluetooth enabled:', permissions.bluetooth);
console.log('Location enabled:', permissions.location);
```

##### `setNickname(nickname: string): Promise<void>`

Updates your nickname and broadcasts the change to peers.

##### `getMyPeerId(): Promise<string>`

Returns your unique peer ID (derived from your public key fingerprint).

##### `getMyNickname(): Promise<string>`

Returns your current nickname.

##### `getPeers(): Promise<Peer[]>`

Returns an array of all discovered peers.

```typescript
interface Peer {
  peerId: string;
  nickname: string;
  isConnected: boolean;
  rssi?: number;
  lastSeen: number;
  isVerified: boolean;
}
```

##### `sendMessage(content: string, channel?: string): Promise<string>`

Sends a public broadcast message. Returns the message ID.

##### `sendPrivateMessage(content: string, recipientPeerId: string): Promise<string>`

Sends an encrypted private message to a specific peer. Returns the message ID.

##### `sendReadReceipt(messageId: string, recipientPeerId: string): Promise<void>`

Sends a read receipt for a received message.

##### `sendFile(filePath: string, options?: { recipientPeerId?: string; channel?: string }): Promise<string>`

Sends a file over the mesh network. Files are automatically chunked for reliable transmission.

```typescript
// Send to all peers (broadcast)
await BleMesh.sendFile('/path/to/photo.jpg');

// Send to specific peer
await BleMesh.sendFile('/path/to/document.pdf', { recipientPeerId: 'abc123' });
```

##### `sendTransaction(serializedTransaction: string, options): Promise<string>`

Sends a Solana transaction for any peer to sign as the second signer. Perfect for multi-signature transactions.

> **⚠️ Important**: Transactions require **encrypted sessions** with target peer(s). By default, `sendTransaction()` automatically initiates handshakes with peers that don't have active sessions. To disable auto-handshake, set `ensureSession: false` in options.

**Targeted (specific peer):**
```typescript
await BleMesh.sendTransaction(base64SerializedTx, {
  recipientPeerId: 'abc123',  // Send to specific peer (optional)
  firstSignerPublicKey: senderPubKey,
  secondSignerPublicKey: recipientPubKey,  // Preferred signer (optional)
  description: 'Payment for services',
  ensureSession: true  // Default: auto-initiate handshake if needed
});
```

**Broadcast (any peer can sign):**
```typescript
// Broadcast to all peers - any peer can sign
// Automatically initiates handshakes with all connected peers
await BleMesh.sendTransaction(base64SerializedTx, {
  firstSignerPublicKey: senderPubKey,
  // No recipientPeerId - broadcasts to all
  // No secondSignerPublicKey - open for any signer
  description: 'Open offer - anyone can complete'
});
```

**Manual session management (advanced):**
```typescript
// Check if encrypted session exists
const hasSession = await BleMesh.hasEncryptedSession(peerId);

if (!hasSession) {
  // Manually initiate handshake
  await BleMesh.initiateHandshake(peerId);
  // Wait briefly for handshake to complete
  await new Promise(resolve => setTimeout(resolve, 500));
}

// Now send transaction without auto-handshake
await BleMesh.sendTransaction(base64SerializedTx, {
  recipientPeerId: peerId,
  firstSignerPublicKey: senderPubKey,
  ensureSession: false  // Skip auto-handshake
});
```

##### `respondToTransaction(transactionId: string, recipientPeerId: string, response): Promise<void>`

Responds to a received transaction with the signed transaction or an error.

```typescript
// Approve and sign
await BleMesh.respondToTransaction(txId, senderPeerId, {
  signedTransaction: fullySignedBase64Tx
});

// Reject
await BleMesh.respondToTransaction(txId, senderPeerId, {
  error: 'User rejected transaction'
});
```

##### `hasEncryptedSession(peerId: string): Promise<boolean>`

Checks if an encrypted session exists with the specified peer. Useful for verifying secure communication is established before sending sensitive data.

```typescript
const hasSession = await BleMesh.hasEncryptedSession(peerId);
if (hasSession) {
  console.log('Secure session active');
} else {
  console.log('No encrypted session - need handshake');
}
```

##### `initiateHandshake(peerId: string): Promise<void>`

Initiates a Noise handshake with a peer for encrypted communication. Required before sending private messages or transactions.

```typescript
// Manually establish encrypted session
await BleMesh.initiateHandshake(peerId);
await new Promise(resolve => setTimeout(resolve, 500)); // Wait for completion

// Now send encrypted data
await BleMesh.sendPrivateMessage('Secret data', peerId);
```

##### `sendSolanaNonceTransaction(transaction: SolanaNonceTransaction, recipientPeerId: string): Promise<string>`

Sends a durable transaction using Solana nonce accounts for offline transaction signing.

```typescript
interface SolanaNonceTransaction {
  recentBlockhash: string;
  nonceAccount: string;
  nonceAuthority: string;
  feePayer: string;
  instructions: Array<{
    programId: string;
    keys: Array<{ pubkey: string; isSigner: boolean; isWritable: boolean }>;
    data: string; // base64 encoded
  }>;
}

const txId = await BleMesh.sendSolanaNonceTransaction({
  recentBlockhash: nonceAccountValue,
  nonceAccount: nonceAccountPubkey,
  nonceAuthority: authorityPubkey,
  feePayer: feePayerPubkey,
  instructions: [/* ... */]
}, recipientPeerId);
```

##### `getIdentityFingerprint(): Promise<string>`

Returns your identity fingerprint for verification. Share this with peers to verify your identity.

##### `getPeerFingerprint(peerId: string): Promise<string | null>`

Returns a peer's identity fingerprint for verification. Compare with their shared fingerprint to verify identity.

```typescript
const peerFingerprint = await BleMesh.getPeerFingerprint(peerId);
if (peerFingerprint === sharedFingerprint) {
  console.log('Identity verified!');
} else {
  console.warn('Identity mismatch - possible MITM attack');
}
```

##### `broadcastAnnounce(): Promise<void>`

Forces a broadcast announce to refresh your presence on the mesh. Useful for re-announcing after network connectivity changes.

#### Events

##### `onPeerListUpdated(callback): () => void`

Called when the peer list changes.

```typescript
BleMesh.onPeerListUpdated(({ peers }) => {
  console.log('Peers updated:', peers);
});
```

##### `onMessageReceived(callback): () => void`

Called when a message is received.

```typescript
BleMesh.onMessageReceived(({ message }) => {
  console.log('Message from', message.senderNickname, ':', message.content);
});
```

##### `onConnectionStateChanged(callback): () => void`

Called when the connection state changes.

##### `onReadReceipt(callback): () => void`

Called when a read receipt is received.

##### `onDeliveryAck(callback): () => void`

Called when a delivery acknowledgment is received.

##### `onFileReceived(callback): () => void`

Called when a file is received.

```typescript
BleMesh.onFileReceived(({ file }) => {
  console.log('Received file:', file.fileName);
  console.log('Size:', file.fileSize);
  console.log('Data (base64):', file.data);
  // Save or display the file...
});
```

##### `onTransactionReceived(callback): () => void`

Called when a Solana transaction is received for signing.

```typescript
BleMesh.onTransactionReceived(({ transaction }) => {
  console.log('Transaction to sign:', transaction.id);
  console.log('Serialized:', transaction.serializedTransaction);
  console.log('First signer:', transaction.firstSignerPublicKey);
  
  // Check if a specific second signer is required
  if (transaction.secondSignerPublicKey) {
    console.log('Required second signer:', transaction.secondSignerPublicKey);
    // Verify this is your public key before signing
  } else if (transaction.openForAnySigner) {
    console.log('Any peer can sign this transaction');
  }
  
  // Sign with your wallet...
  const signedTx = await yourWallet.signTransaction(transaction.serializedTransaction);
  
  // Respond
  await BleMesh.respondToTransaction(transaction.id, transaction.senderPeerId, {
    signedTransaction: signedTx
  });
});
```

##### `onTransactionResponse(callback): () => void`

Called when a transaction response is received (signed or rejected).

```typescript
BleMesh.onTransactionResponse(({ response }) => {
  if (response.signedTransaction) {
    console.log('Transaction fully signed!');
    // Broadcast to Solana network...
  } else if (response.error) {
    console.log('Transaction rejected:', response.error);
  }
});
```

##### `onError(callback): () => void`

Called when an error occurs.

## Solana Multi-Signature Transaction Flow

### Targeted Transaction (Specific Peer)

```
Alice (First Signer)                    Bob (Second Signer)
     |                                        |
     | 1. Create transaction                  |
     | 2. Sign with Alice's key               |
     | 3. Send via BleMesh.sendTransaction()  |
     |    with recipientPeerId                |
     |--------------------------------------->|
     |                                        |
     |     4. onTransactionReceived event     |
     |<---------------------------------------|
     |                                        |
     |     5. Review & sign with Bob's key    |
     |<---------------------------------------|
     |                                        |
     | 6. respondToTransaction()              |
     |--------------------------------------->|
     |                                        |
     | 7. Broadcast fully signed tx           |
     v   to Solana network                    v
```

### Broadcast Transaction (Any Peer Can Sign)

```
Alice (First Signer)         Bob          Carol         Dave
     |                       |             |             |
     | 1. Create tx          |             |             |
     | 2. Sign               |             |             |
     | 3. Broadcast          |             |             |
     |---------------------->|------------>|------------>|
     |                       |             |             |
     |                       | 4. All receive            |
     |                       |    onTransactionReceived  |
     |                       |             |             |
     |                       | 5. Carol decides to sign  |
     |                       |             |             |
     |                       |<------------|------------>|
     |                       | 6. Carol responds         |
     |<----------------------|-------------|-------------|
     |                       |             |             |
     | 7. Alice receives     |             |             |
     |    signed tx          |             |             |
     v                       v             v             v
```

### MTU and Transaction Size Limits

The library automatically handles BLE MTU limitations:

| Feature | Max Size | Handling |
|---------|----------|----------|
| Simple messages | ~450 bytes | Single packet |
| Small transactions | ~450 bytes | Single packet |
| Large transactions | Unlimited | Automatic chunking (400 byte chunks) |
| Files | Unlimited | Chunked (180 byte chunks) |

**For Solana transactions:**
- Most transactions (simple transfers) fit in a single packet
- Complex transactions (multi-instruction, large memos) are automatically chunked
- Chunking is transparent to the application layer
- The receiver automatically reassembles chunks before decrypting

## Protocol Compatibility

This library uses the same binary protocol as:
- [bitchat (iOS)](https://github.com/permissionlesstech/bitchat)
- [bitchat-android](https://github.com/permissionlesstech/bitchat-android)

Messages can be exchanged between all three platforms.

## License

MIT