npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@magicred-1/ble-mesh

v1.3.9

Published

BLE Mesh networking library for React Native with seamless permissions

Readme

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

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

iOS

cd ios && pod install

Add the following to your Info.plist:

<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):

<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

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.

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.

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.

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.

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.

// 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):

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):

// 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):

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

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

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.

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

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.

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.

BleMesh.onPeerListUpdated(({ peers }) => {
  console.log('Peers updated:', peers);
});
onMessageReceived(callback): () => void

Called when a message is received.

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.

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.

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

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:

Messages can be exchanged between all three platforms.

License

MIT