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

expo-ble

v0.5.0

Published

BLE support for expo apps

Readme

expo-ble


📢 Hey there!

This library is not affiliated with or maintained by the Expo team — it's built by me, Daniel Reiling! 👋

It's the BLE library I wish Expo came with, so I made it myself. Built with Expo's Modules API for seamless integration. Feedback and contributions are always welcome!


🚀 Features

  • Cross-platform: Works seamlessly on iOS and Android
  • Expo SDK compatible: Built using Expo Modules API
  • Expo Config Plugin: Automatic project configuration with zero manual setup
  • TypeScript first: Full TypeScript support with comprehensive type definitions
  • Permission handling: Automatic handling of platform-specific permissions
  • Event-driven architecture: Real-time updates for device discovery and connection states
  • Simple API: Easy-to-use singleton pattern for BLE operations
  • Modern: Uses the latest native APIs (CoreBluetooth on iOS, Android BLE APIs)

📋 Requirements

  • Expo SDK 50+
  • React Native 0.71+
  • iOS 13.0+
  • Android 5.0+ (API level 21+)

📦 Installation

npx expo install expo-ble

Automatic Configuration with Expo Config Plugin

This library includes an Expo Config Plugin that automatically configures your iOS and Android projects. Simply add the plugin to your app.json or app.config.js:

{
  "expo": {
    "plugins": ["expo-ble"]
  }
}

Config Plugin Options

You can customize the plugin behavior by passing options:

{
  "expo": {
    "plugins": [
      [
        "expo-ble",
        {
          "bluetoothAlwaysUsageDescription": "Custom message for Bluetooth permission",
          "androidNeedsLocationPermission": true,
          "iosBackgroundModes": false
        }
      ]
    ]
  }
}

Available options:

  • bluetoothAlwaysUsageDescription (string): iOS usage description for Bluetooth permission
  • bluetoothPeripheralUsageDescription (string): iOS usage description for peripheral mode (optional)
  • androidNeedsLocationPermission (boolean): Whether to add location permission for Android 11 and below (default: true)
  • iosBackgroundModes (boolean): Enable iOS background modes for BLE (default: false)

Manual Configuration

If you're not using the config plugin, you can manually configure your project:

iOS Configuration

Add the following to your Info.plist:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to BLE devices</string>

Android Configuration

The library automatically adds the required permissions to your Android manifest via the config plugin:

  • BLUETOOTH and BLUETOOTH_ADMIN (Android 11 and below)
  • BLUETOOTH_SCAN and BLUETOOTH_CONNECT (Android 12+)
  • ACCESS_FINE_LOCATION (for BLE scanning on Android 11 and below)

For bare React Native projects, run:

npx expo prebuild

🎯 Basic Usage

Import the library

import bleManager, { BleState, NativeDevice } from 'expo-ble';

Initialize and check Bluetooth state

// Initialize the BLE manager
await bleManager.initialize();

// Listen to Bluetooth state changes
bleManager.onStateChange((state: BleState) => {
  console.log('Bluetooth state:', state);
  
  if (state === BleState.PoweredOn) {
    // Bluetooth is ready to use
  }
});

Request permissions

// Request necessary permissions
const result = await bleManager.requestPermissions();

if (result.granted) {
  console.log('Permissions granted!');
} else {
  console.log('Permissions denied');
}

Scan for devices

// Start scanning for BLE devices
await bleManager.startScan({
  serviceUUIDs: [], // Optional: filter by service UUIDs
  timeout: 10000    // Optional: auto-stop after 10 seconds
});

// Listen for discovered devices
bleManager.onDeviceFound((device: NativeDevice) => {
  console.log('Found device:', device.name || device.id);
  console.log('RSSI:', device.rssi);
});

// Stop scanning manually
await bleManager.stopScan();

Connect to a device

try {
  // Connect to a device
  const device = await bleManager.connect(deviceId, {
    timeout: 5000 // 5 second timeout
  });
  
  console.log('Connected to:', device.name);
  
  // Disconnect when done
  await bleManager.disconnect(deviceId);
} catch (error) {
  console.error('Connection failed:', error);
}

📖 Complete Example

import React, { useState, useEffect } from 'react';
import { View, Button, FlatList, Text } from 'react-native';
import bleManager, { BleState, NativeDevice } from 'expo-ble';

export default function App() {
  const [devices, setDevices] = useState<NativeDevice[]>([]);
  const [isScanning, setIsScanning] = useState(false);

  useEffect(() => {
    // Initialize BLE
    bleManager.initialize();
    
    // Subscribe to device discovery
    const unsubscribe = bleManager.onDeviceFound((device) => {
      setDevices(prev => {
        const exists = prev.find(d => d.id === device.id);
        if (exists) {
          return prev.map(d => d.id === device.id ? device : d);
        }
        return [...prev, device];
      });
    });

    return () => {
      unsubscribe();
      bleManager.destroy();
    };
  }, []);

  const startScan = async () => {
    try {
      setDevices([]);
      setIsScanning(true);
      
      await bleManager.startScan({ timeout: 10000 });
      
      setTimeout(() => setIsScanning(false), 10000);
    } catch (error) {
      console.error('Scan failed:', error);
      setIsScanning(false);
    }
  };

  const stopScan = async () => {
    await bleManager.stopScan();
    setIsScanning(false);
  };

  return (
    <View style={{ flex: 1, padding: 20 }}>
      <Button 
        title={isScanning ? "Stop Scan" : "Start Scan"} 
        onPress={isScanning ? stopScan : startScan}
      />
      
      <FlatList
        data={devices}
        keyExtractor={item => item.id}
        renderItem={({ item }) => (
          <View style={{ padding: 10 }}>
            <Text>{item.name || 'Unknown Device'}</Text>
            <Text style={{ fontSize: 12, color: 'gray' }}>
              {item.id} • RSSI: {item.rssi}
            </Text>
          </View>
        )}
      />
    </View>
  );
}

🔧 API Reference

BleManager

The main class for all BLE operations. Access the singleton instance via the default export.

Methods

| Method | Description | Parameters | Returns | |--------|-------------|------------|---------| | initialize() | Initialize the BLE manager | None | Promise<void> | | requestPermissions() | Request BLE permissions | None | Promise<PermissionResponse> | | checkPermissions() | Check current permission status | None | Promise<PermissionStatus> | | startScan(options?) | Start scanning for devices | ScanOptions | Promise<void> | | stopScan() | Stop scanning | None | Promise<void> | | connect(deviceId, options?) | Connect to a device | string, ConnectionOptions | Promise<NativeDevice> | | disconnect(deviceId) | Disconnect from a device | string | Promise<void> | | enable() | Enable Bluetooth (Android only) | None | Promise<void> | | onStateChange(listener) | Listen to state changes | EventListener<BleState> | UnsubscribeFn | | onDeviceFound(listener) | Listen to device discovery | EventListener<NativeDevice> | UnsubscribeFn |

Properties

| Property | Type | Description | |----------|------|-------------| | state | BleState | Current Bluetooth state | | isScanning | boolean | Whether currently scanning | | discoveredDevices | NativeDevice[] | List of discovered devices |

Types

BleState

enum BleState {
  Unknown = 'unknown',
  Resetting = 'resetting',
  Unsupported = 'unsupported',
  Unauthorized = 'unauthorized',
  PoweredOff = 'poweredOff',
  PoweredOn = 'poweredOn'
}

ScanOptions

interface ScanOptions {
  serviceUUIDs?: string[];         // Filter by service UUIDs
  allowDuplicates?: boolean;       // Allow duplicate advertisements
  scanMode?: 'lowPower' | 'balanced' | 'lowLatency'; // Android only
  timeout?: number;                // Auto-stop timeout in ms
}

ConnectionOptions

interface ConnectionOptions {
  autoConnect?: boolean;    // Android only
  timeout?: number;         // Connection timeout in ms
  requestMTU?: number;      // Android only
  priority?: 'high' | 'balanced' | 'lowPower';
}

🛠️ Development

Building the Module

This library uses expo-module-scripts for building. There are two build modes:

Development Build (watch mode):

npm run build

This starts a watcher that automatically rebuilds when you make changes. Keep this running during development.

Production Build (one-time):

npm run build:prod

This performs a one-time build without watch mode. Use this before publishing.

Other Development Commands

npm run clean          # Clean build artifacts
npm run lint           # Lint the code
npm run test           # Run tests
npm run prepare        # Prepare module (runs after install)

Working with the Example App

Navigate to the example/ directory:

cd example
npm start              # Start Expo dev server
npm run ios            # Run on iOS simulator
npm run android        # Run on Android emulator

📦 Publishing

Prerequisites

Before publishing, ensure:

  • All tests pass (npm run test)
  • Code is linted (npm run lint)
  • You have npm publish rights to the package
  • You're logged in to npm (npm login)

Publishing Steps

  1. Clean and build for production:

    npm run clean
    npm run build:prod
  2. Update version:

    # Increment version (patch, minor, or major)
    npm version patch    # 0.1.0 -> 0.1.1
    npm version minor    # 0.1.0 -> 0.2.0
    npm version major    # 0.1.0 -> 1.0.0
  3. Publish to npm:

    npm publish
  4. Push git tags:

    git push origin main --tags

What Gets Published

The published npm package includes:

  • /build/ - Compiled JavaScript and TypeScript definitions
  • /plugin/ - Expo config plugin
  • /ios/ - Native iOS source (Swift files and podspec)
  • /android/ - Native Android source (Kotlin files)
  • Configuration files (package.json, expo-module.config.json, etc.)

The following are excluded (see .npmignore):

  • /example/ - Example app
  • /node_modules/ - Dependencies
  • Test files and development tools

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see below for details:

MIT License

Copyright (c) 2024 Daniel Reiling

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

🙏 Acknowledgments

📱 Platform-Specific Notes

iOS

  • Bluetooth permission is required and will be requested automatically
  • Background modes are not currently supported
  • Minimum iOS version: 13.0

Android

  • Location permission is required for BLE scanning on Android 11 and below
  • Different permissions are used for Android 12+ (BLUETOOTH_SCAN, BLUETOOTH_CONNECT)
  • The library supports programmatic Bluetooth enabling via system dialog

🐛 Troubleshooting

Common Issues

  1. "Bluetooth permission not granted"

    • Ensure you've called requestPermissions() before scanning
    • On iOS, check that NSBluetoothAlwaysUsageDescription is in Info.plist
    • On Android, location permission may be required for older versions
  2. Devices not appearing during scan

    • Ensure Bluetooth is enabled on the device
    • Check that the device is advertising and in range
    • Try scanning without service UUID filters
  3. Connection timeouts

    • Increase the timeout in connection options
    • Ensure the device is in range and not already connected
    • Some devices may require specific connection parameters

📚 Resources


Made with ☕️ by Daniel Reiling