react-native-ble-nitro
Version:
High-performance React Native BLE library built on Nitro Modules
480 lines (479 loc) • 17.6 kB
JavaScript
import BleNitroNative from './specs/NativeBleNitro';
import { AndroidScanMode as NativeAndroidScanMode, } from './specs/NativeBleNitro';
export var BLEState;
(function (BLEState) {
BLEState["Unknown"] = "Unknown";
BLEState["Resetting"] = "Resetting";
BLEState["Unsupported"] = "Unsupported";
BLEState["Unauthorized"] = "Unauthorized";
BLEState["PoweredOff"] = "PoweredOff";
BLEState["PoweredOn"] = "PoweredOn";
})(BLEState || (BLEState = {}));
;
export var AndroidScanMode;
(function (AndroidScanMode) {
AndroidScanMode["LowLatency"] = "LowLatency";
AndroidScanMode["Balanced"] = "Balanced";
AndroidScanMode["LowPower"] = "LowPower";
AndroidScanMode["Opportunistic"] = "Opportunistic";
})(AndroidScanMode || (AndroidScanMode = {}));
export function mapNativeBLEStateToBLEState(nativeState) {
const map = {
0: BLEState.Unknown,
1: BLEState.Resetting,
2: BLEState.Unsupported,
3: BLEState.Unauthorized,
4: BLEState.PoweredOff,
5: BLEState.PoweredOn,
};
return map[nativeState];
}
export function mapAndroidScanModeToNativeAndroidScanMode(scanMode) {
const map = {
LowLatency: NativeAndroidScanMode.LowLatency,
Balanced: NativeAndroidScanMode.Balanced,
LowPower: NativeAndroidScanMode.LowPower,
Opportunistic: NativeAndroidScanMode.Opportunistic,
};
return map[scanMode];
}
export function convertNativeBleDeviceToBleDevice(nativeBleDevice) {
return {
...nativeBleDevice,
serviceUUIDs: BleNitroManager.normalizeGattUUIDs(nativeBleDevice.serviceUUIDs),
manufacturerData: {
companyIdentifiers: nativeBleDevice.manufacturerData.companyIdentifiers.map(entry => ({
id: entry.id,
data: arrayBufferToByteArray(entry.data)
}))
}
};
}
export function arrayBufferToByteArray(buffer) {
return Array.from(new Uint8Array(buffer));
}
export function byteArrayToArrayBuffer(data) {
return new Uint8Array(data).buffer;
}
export class BleNitroManager {
constructor(options) {
this._isScanning = false;
this._connectedDevices = {};
this._restoredStateCallback = null;
this._restoredState = null;
this._restoredStateCallback = options?.onRestoredState || null;
BleNitroNative.setRestoreStateCallback((peripherals) => this.onNativeRestoreStateCallback(peripherals));
}
onNativeRestoreStateCallback(peripherals) {
const bleDevices = peripherals.map((peripheral) => convertNativeBleDeviceToBleDevice(peripheral));
if (this._restoredStateCallback) {
this._restoredStateCallback(bleDevices);
}
else {
this._restoredState = bleDevices;
}
}
onRestoredState(callback) {
if (this._restoredState) {
callback(this._restoredState);
this._restoredState = null;
}
this._restoredStateCallback = callback;
}
/**
* Converts a 16- oder 32-Bit UUID to a 128-Bit UUID
*
* @param uuid 16-, 32- or 128-Bit UUID as string
* @returns Full 128-Bit UUID
*/
static normalizeGattUUID(uuid) {
const cleanUuid = uuid.toLowerCase();
// 128-Bit UUID → normalisieren
if (cleanUuid.length === 36 && cleanUuid.includes("-")) {
return cleanUuid;
}
// GATT-Service UUIDs
// 16- oder 32-Bit UUID → 128-Bit UUID
const padded = cleanUuid.padStart(8, "0");
return `${padded}-0000-1000-8000-00805f9b34fb`;
}
static normalizeGattUUIDs(uuids) {
return uuids.map((uuid) => BleNitroManager.normalizeGattUUID(uuid));
}
/**
* Start scanning for Bluetooth devices
* @param filter Optional scan filter
* @param callback Callback function called when a device is found
* @returns Promise resolving to success state
*/
startScan(filter = {}, callback, onError) {
if (this._isScanning) {
return;
}
// Create native scan filter with defaults
const nativeFilter = {
serviceUUIDs: filter.serviceUUIDs || [],
rssiThreshold: filter.rssiThreshold ?? -100,
allowDuplicates: filter.allowDuplicates ?? false,
androidScanMode: mapAndroidScanModeToNativeAndroidScanMode(filter.androidScanMode ?? AndroidScanMode.Balanced),
};
// Create callback wrapper
const scanCallback = (device, error) => {
if (error && !device) {
this._isScanning = false;
onError?.(error);
return;
}
device = device; // eslint-disable-line @typescript-eslint/no-non-null-assertion
// Convert manufacturer data to Uint8Arrays
const convertedDevice = convertNativeBleDeviceToBleDevice(device);
callback(convertedDevice);
};
// Start scan
BleNitroNative.startScan(nativeFilter, scanCallback);
this._isScanning = true;
}
/**
* Stop scanning for Bluetooth devices
* @returns Promise resolving to success state
*/
stopScan() {
if (!this._isScanning) {
return;
}
BleNitroNative.stopScan();
this._isScanning = false;
}
/**
* Check if currently scanning for devices
* @returns Promise resolving to scanning state
*/
isScanning() {
this._isScanning = BleNitroNative.isScanning();
return this._isScanning;
}
/**
* Get all currently connected devices
* @param services Optional list of service UUIDs to filter by
* @returns Array of connected devices
*/
getConnectedDevices(services) {
const devices = BleNitroNative.getConnectedDevices(services || []);
// Normalize service UUIDs - manufacturer data already comes as ArrayBuffers
return devices.map(device => convertNativeBleDeviceToBleDevice(device));
}
/**
* Connect to a Bluetooth device
* @param deviceId ID of the device to connect to
* @param onDisconnect Optional callback for disconnect events
* @returns Promise resolving when connected
*/
connect(deviceId, onDisconnect) {
return new Promise((resolve, reject) => {
// Check if already connected
if (this._connectedDevices[deviceId]) {
resolve(deviceId);
return;
}
BleNitroNative.connect(deviceId, (success, connectedDeviceId, error) => {
if (success) {
this._connectedDevices[deviceId] = true;
resolve(connectedDeviceId);
}
else {
reject(new Error(error));
}
}, onDisconnect ? (deviceId, interrupted, error) => {
// Remove from connected devices when disconnected
delete this._connectedDevices[deviceId];
onDisconnect(deviceId, interrupted, error);
} : undefined);
});
}
/**
* Disconnect from a Bluetooth device
* @param deviceId ID of the device to disconnect from
* @returns Promise resolving when disconnected
*/
disconnect(deviceId) {
return new Promise((resolve, reject) => {
// Check if already disconnected
if (!this._connectedDevices[deviceId]) {
resolve();
return;
}
BleNitroNative.disconnect(deviceId, (success, error) => {
if (success) {
delete this._connectedDevices[deviceId];
resolve();
}
else {
reject(new Error(error));
}
});
});
}
/**
* Check if connected to a device
* @param deviceId ID of the device to check
* @returns Promise resolving to connection state
*/
isConnected(deviceId) {
return BleNitroNative.isConnected(deviceId);
}
/**
* Request a new MTU size
* @param deviceId ID of the device
* @param mtu New MTU size, min is 23, max is 517
* @returns On Android: new MTU size; on iOS: current MTU size as it is handled by iOS itself; on error: -1
*/
requestMTU(deviceId, mtu) {
mtu = parseInt(mtu.toString(), 10);
const deviceMtu = BleNitroNative.requestMTU(deviceId, mtu);
return deviceMtu;
}
/**
* Read RSSI for a connected device
* @param deviceId ID of the device
* @returns Promise resolving to RSSI value
*/
readRSSI(deviceId) {
return new Promise((resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
BleNitroNative.readRSSI(deviceId, (success, rssi, error) => {
if (success) {
resolve(rssi);
}
else {
reject(new Error(error));
}
});
});
}
/**
* Discover services for a connected device
* @param deviceId ID of the device
* @returns Promise resolving when services are discovered
*/
discoverServices(deviceId) {
return new Promise((resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
BleNitroNative.discoverServices(deviceId, (success, error) => {
if (success) {
resolve(true);
}
else {
reject(new Error(error));
}
});
});
}
/**
* Get services for a connected device
* @param deviceId ID of the device
* @returns Promise resolving to array of service UUIDs
*/
getServices(deviceId) {
return new Promise(async (resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
const success = await this.discoverServices(deviceId);
if (!success) {
reject(new Error('Failed to discover services'));
return;
}
const services = BleNitroNative.getServices(deviceId);
resolve(BleNitroManager.normalizeGattUUIDs(services));
});
}
/**
* Get characteristics for a service
* @param deviceId ID of the device
* @param serviceId ID of the service
* @returns Promise resolving to array of characteristic UUIDs
*/
getCharacteristics(deviceId, serviceId) {
if (!this._connectedDevices[deviceId]) {
throw new Error('Device not connected');
}
const characteristics = BleNitroNative.getCharacteristics(deviceId, BleNitroManager.normalizeGattUUID(serviceId));
return BleNitroManager.normalizeGattUUIDs(characteristics);
}
/**
* Read a characteristic value
* @param deviceId ID of the device
* @param serviceId ID of the service
* @param characteristicId ID of the characteristic
* @returns Promise resolving to the characteristic data as ArrayBuffer
*/
readCharacteristic(deviceId, serviceId, characteristicId) {
return new Promise((resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
BleNitroNative.readCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), (success, data, error) => {
if (success) {
resolve(arrayBufferToByteArray(data));
}
else {
reject(new Error(error));
}
});
});
}
/**
* Write a value to a characteristic
* @param deviceId ID of the device
* @param serviceId ID of the service
* @param characteristicId ID of the characteristic
* @param data Data to write as ByteArray (number[])
* @param withResponse Whether to wait for response
* @returns Promise resolving with response data (empty ByteArray when withResponse=false)
*/
writeCharacteristic(deviceId, serviceId, characteristicId, data, withResponse = true) {
return new Promise((resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
BleNitroNative.writeCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), byteArrayToArrayBuffer(data), withResponse, (success, responseData, error) => {
if (success) {
// Convert ArrayBuffer response to ByteArray
const responseByteArray = arrayBufferToByteArray(responseData);
resolve(responseByteArray);
}
else {
reject(new Error(error));
}
});
});
}
/**
* Subscribe to characteristic notifications
* @param deviceId ID of the device
* @param serviceId ID of the service
* @param characteristicId ID of the characteristic
* @param callback Callback function called when notification is received
* @returns Promise resolving when subscription is complete
*/
subscribeToCharacteristic(deviceId, serviceId, characteristicId, callback) {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
throw new Error('Device not connected');
}
let _success = false;
BleNitroNative.subscribeToCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), (charId, data) => {
callback(charId, arrayBufferToByteArray(data));
}, (success, error) => {
_success = success;
if (!success) {
throw new Error(error);
}
});
return {
remove: () => {
if (!_success) {
return;
}
this.unsubscribeFromCharacteristic(deviceId, serviceId, characteristicId).catch(() => { });
}
};
}
/**
* Unsubscribe from characteristic notifications
* @param deviceId ID of the device
* @param serviceId ID of the service
* @param characteristicId ID of the characteristic
* @returns Promise resolving when unsubscription is complete
*/
unsubscribeFromCharacteristic(deviceId, serviceId, characteristicId) {
return new Promise((resolve, reject) => {
// Check if connected first
if (!this._connectedDevices[deviceId]) {
reject(new Error('Device not connected'));
return;
}
BleNitroNative.unsubscribeFromCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), (success, error) => {
if (success) {
resolve();
}
else {
reject(new Error(error));
}
});
});
}
/**
* Check if Bluetooth is enabled
* @returns Promise resolving to Bluetooth state
*/
isBluetoothEnabled() {
return this.state() === BLEState.PoweredOn;
}
/**
* Request to enable Bluetooth (Android only)
* @returns Promise resolving when Bluetooth is enabled
*/
requestBluetoothEnable() {
return new Promise((resolve, reject) => {
BleNitroNative.requestBluetoothEnable((success, error) => {
if (success) {
resolve(true);
}
else {
reject(new Error(error));
}
});
});
}
/**
* Get the current Bluetooth state
* @returns Promise resolving to Bluetooth state
* @see BLEState
*/
state() {
return mapNativeBLEStateToBLEState(BleNitroNative.state());
}
/**
* Subscribe to Bluetooth state changes
* @param callback Callback function called when state changes
* @param emitInitial Whether to emit initial state callback
* @returns Promise resolving when subscription is complete
* @see BLEState
*/
subscribeToStateChange(callback, emitInitial = false) {
if (emitInitial) {
const state = this.state();
callback(state);
}
BleNitroNative.subscribeToStateChange((nativeState) => {
callback(mapNativeBLEStateToBLEState(nativeState));
});
return {
remove: () => {
BleNitroNative.unsubscribeFromStateChange();
},
};
}
/**
* Open Bluetooth settings
* @returns Promise resolving when settings are opened
*/
openSettings() {
return BleNitroNative.openSettings();
}
}