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

@syncmesh/rn-ble

v0.1.0

Published

React Native BLE central and peripheral module built with Expo Modules

Readme

@syncmesh/rn-ble

React Native BLE central and peripheral, for Expo and bare React Native apps. Built on Expo Modules, with one TypeScript surface across iOS (CoreBluetooth) and Android (BluetoothLe*).

Features

  • Central — scan, connect, discover services, read / write / subscribe characteristics, read / write descriptors, MTU, RSSI
  • Peripheral — advertise, publish GATT services and characteristics, push values to subscribers
  • Cancellable transactions with deadline-based timeouts
  • iOS State Preservation & Restoration built in
  • Android bonding (list, create, remove)
  • Expo config plugin that wires permissions, background modes, and the BLE hardware feature flag for you

Platform support

| Platform | Central | Peripheral | Bonding | Background | | --- | --- | --- | --- | --- | | iOS | Yes | Yes | Implicit (CoreBluetooth) | bluetooth-central, bluetooth-peripheral | | Android | Yes | Yes | Yes | OS-managed (declare a foreground service in your app for long-running scans) | | Web | No | No | No | No | | Expo Go | No | No | No | No |

Installation

npm install @syncmesh/rn-ble
# or: yarn add @syncmesh/rn-ble
# or: bun add @syncmesh/rn-ble

Then, depending on your project type:

  • Expo (managed or prebuild) — add the config plugin below, then run npx expo prebuild.
  • Bare React Native — run cd ios && pod install.

Expo Go is not supported; you need a development build.

Expo config plugin

The plugin wires up the iOS Info.plist and Android AndroidManifest.xml entries needed for BLE central + peripheral use, so you don't have to edit those files by hand. It always declares <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" /> on Android — Google Play will only surface the app to BLE-capable devices.

{
  "expo": {
    "plugins": [
      [
        "@syncmesh/rn-ble",
        {
          "neverForLocation": true,
          "modes": ["central", "peripheral"],
          "bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to nearby Bluetooth devices."
        }
      ]
    ]
  }
}

| Option | Type | Default | Platform | Effect | | --- | --- | --- | --- | --- | | neverForLocation | boolean | false | Android | Marks BLUETOOTH_SCAN with usesPermissionFlags="neverForLocation" and caps the legacy ACCESS_*_LOCATION permissions at maxSdkVersion="30". | | modes | ("central" \| "peripheral")[] | [] | iOS | Adds entries to UIBackgroundModes so the app keeps scanning, advertising, or holding GATT connections while backgrounded. | | bluetoothAlwaysPermission | string \| false | A default sentence | iOS | Sets NSBluetoothAlwaysUsageDescription. Pass false to skip writing it. |

Quick start

Central — scan and connect

import { BleManager } from "@syncmesh/rn-ble";

const SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0";

const state = await BleManager.getState();
if (state !== "poweredOn") {
  throw new Error(`Bluetooth not ready: ${state}`);
}

await BleManager.startScan({ serviceUuids: [SERVICE_UUID] });

const scanSub = BleManager.addListener("onScanResult", async (scan) => {
  await BleManager.stopScan();
  const connection = await BleManager.connect(scan.peripheralId);
  const discovery = await BleManager.discoverServices(connection.connectionId);
  console.log(discovery.services);
});

Peripheral — publish and advertise

import { BleManager } from "@syncmesh/rn-ble";

const SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0";
const CHARACTERISTIC_UUID = "12345678-1234-5678-1234-56789abcdef1";

await BleManager.publishServices({
  services: [
    {
      uuid: SERVICE_UUID,
      characteristics: [
        {
          uuid: CHARACTERISTIC_UUID,
          properties: ["read", "write", "notify"],
          initialValueBase64: "aGVsbG8=",
        },
      ],
    },
  ],
});

await BleManager.startAdvertising({
  localName: "SyncMesh Demo",
  serviceUuids: [SERVICE_UUID],
});

Core concepts

  • peripheralId is the platform identifier of the remote device: CBPeripheral.identifier.uuidString on iOS, a Bluetooth address (or randomized address) on Android.
  • connectionId matches peripheralId once a connection is open.
  • All characteristic and descriptor payloads are exchanged as base64 strings.
  • Call discoverServices() before any read, write, subscribe, unsubscribe, readDescriptor, or writeDescriptor.
  • Most per-connection operations accept an optional transactionId you can later pass to cancelTransaction(transactionId).

API

The full typed surface lives in src/types.ts; native implementations are in ios/RNBleModule.swift and android/src/main. Bonding methods (getBondedDevices, createBond, removeBond) and requestConnectionPriority are Android-only and throw RNBleNotSupportedException on iOS.

import { BleManager } from "@syncmesh/rn-ble";
import type {
  BleAdapterState,
  BleAdvertisingOptions,
  BleConnection,
  BleConnectOptions,
  BleDiscoveryResult,
  BlePeripheralSpec,
  BleScanOptions,
  BleScanResult,
  BleWriteType,
} from "@syncmesh/rn-ble";

Platform divergences

  • iOS advertising payload — only localName and serviceUuids are honored. manufacturerDataBase64 / serviceDataBase64 are Android-only.
  • autoConnect — Android pass-through to connectGatt; ignored on iOS (warning logged).
  • requestMtu — Android triggers BluetoothGatt.requestMtu; iOS negotiates automatically and the argument is ignored.
  • scanMode — Android maps to ScanSettings.SCAN_MODE_*; iOS records but does not apply.
  • onRestored — iOS only, fired when CoreBluetooth relaunches the app via State Preservation & Restoration.

Events

Subscribe with BleManager.addListener(name, handler); the returned subscription has .remove(). Payload shapes are in src/types.ts.

| Event | When it fires | | --- | --- | | onStateChanged | Adapter state transitions | | onScanResult | Each peripheral discovered while scanning | | onConnectionStateChanged | Connect, disconnect, disconnect failures | | onCharacteristicValueChanged | A subscribed remote characteristic sends a new value | | onCharacteristicWriteRequested | A remote central writes to one of your published local characteristics | | onCharacteristicReadRequested | A remote central reads one of your published local characteristics | | onSubscribersChanged | Centrals subscribe or unsubscribe from a published characteristic | | onAdvertisingStateChanged | Advertising starts or stops | | onError | Native-side operational failure | | onLog | Diagnostic log from the native module | | onRestored (iOS) | CoreBluetooth relaunched the app in the background via State Preservation & Restoration |

Contributing

Issues and PRs are welcome. To work on the package locally:

bun install
bun run typecheck
bun test
bun run build:plugin   # rebuild the Expo config plugin

License

Apache-2.0 — see LICENSE.