UNPKG

react-native-ble-nitro

Version:

High-performance React Native BLE library built on Nitro Modules

557 lines 21.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = void 0; exports.mapNativeBLEStateToBLEState = mapNativeBLEStateToBLEState; exports.mapAndroidScanModeToNativeAndroidScanMode = mapAndroidScanModeToNativeAndroidScanMode; exports.convertNativeBleDeviceToBleDevice = convertNativeBleDeviceToBleDevice; exports.arrayBufferToByteArray = arrayBufferToByteArray; exports.byteArrayToArrayBuffer = byteArrayToArrayBuffer; const NativeBleNitroFactory_1 = __importDefault(require("./specs/NativeBleNitroFactory")); const NativeBleNitro_1 = require("./specs/NativeBleNitro"); var BLEState; (function (BLEState) { BLEState["Unknown"] = "Unknown"; BLEState["Resetting"] = "Resetting"; BLEState["Unsupported"] = "Unsupported"; BLEState["Unauthorized"] = "Unauthorized"; BLEState["PoweredOff"] = "PoweredOff"; BLEState["PoweredOn"] = "PoweredOn"; })(BLEState || (exports.BLEState = BLEState = {})); ; var AndroidScanMode; (function (AndroidScanMode) { AndroidScanMode["LowLatency"] = "LowLatency"; AndroidScanMode["Balanced"] = "Balanced"; AndroidScanMode["LowPower"] = "LowPower"; AndroidScanMode["Opportunistic"] = "Opportunistic"; })(AndroidScanMode || (exports.AndroidScanMode = AndroidScanMode = {})); 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]; } function mapAndroidScanModeToNativeAndroidScanMode(scanMode) { const map = { LowLatency: NativeBleNitro_1.AndroidScanMode.LowLatency, Balanced: NativeBleNitro_1.AndroidScanMode.Balanced, LowPower: NativeBleNitro_1.AndroidScanMode.LowPower, Opportunistic: NativeBleNitro_1.AndroidScanMode.Opportunistic, }; return map[scanMode]; } function convertNativeBleDeviceToBleDevice(nativeBleDevice) { return { ...nativeBleDevice, serviceUUIDs: BleNitroManager.normalizeGattUUIDs(nativeBleDevice.serviceUUIDs), manufacturerData: { companyIdentifiers: nativeBleDevice.manufacturerData.companyIdentifiers.map(entry => ({ id: entry.id, data: arrayBufferToByteArray(entry.data) })) } }; } function arrayBufferToByteArray(buffer) { return Array.from(new Uint8Array(buffer)); } function byteArrayToArrayBuffer(data) { return new Uint8Array(data).buffer; } class BleNitroManager { constructor(options) { var _a, _b; this._isScanning = false; this._connectedDevices = {}; this._restoredState = null; this._restoreStateIdentifier = null; this._restoredStateCallback = (_a = options === null || options === void 0 ? void 0 : options.onRestoredState) !== null && _a !== void 0 ? _a : null; this._restoreStateIdentifier = (_b = options === null || options === void 0 ? void 0 : options.restoreIdentifier) !== null && _b !== void 0 ? _b : null; this.Instance = NativeBleNitroFactory_1.default.create(options === null || options === void 0 ? void 0 : options.restoreIdentifier, (peripherals) => this.onNativeRestoreStateCallback(peripherals)); } onNativeRestoreStateCallback(peripherals) { if (!this._restoreStateIdentifier) return; const bleDevices = peripherals.map((peripheral) => convertNativeBleDeviceToBleDevice(peripheral)); bleDevices.forEach((device) => { this._connectedDevices[device.id] = device.isConnected; }); if (this._restoredStateCallback) { this._restoredStateCallback(bleDevices); } else { this._restoredState = bleDevices; } } /** * * Registers callback and returns restored peripheral state in it. Not working from 1.7.x upwards for singleton implementation! * @deprecated This method is deprecated and will be removed in 2.x, use onRestoredState option in BleNitroManageroptions instead! */ onRestoredState(callback) { if (!this._restoreStateIdentifier) return; 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 void */ startScan(filter = {}, callback, onError) { var _a, _b, _c; if (this._isScanning) { return; } // Create native scan filter with defaults const nativeFilter = { serviceUUIDs: filter.serviceUUIDs || [], rssiThreshold: (_a = filter.rssiThreshold) !== null && _a !== void 0 ? _a : -100, allowDuplicates: (_b = filter.allowDuplicates) !== null && _b !== void 0 ? _b : false, androidScanMode: mapAndroidScanModeToNativeAndroidScanMode((_c = filter.androidScanMode) !== null && _c !== void 0 ? _c : AndroidScanMode.Balanced), }; // Create callback wrapper const scanCallback = (device, error) => { if (error && !device) { this._isScanning = false; onError === null || onError === void 0 ? void 0 : 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 this.Instance.startScan(nativeFilter, scanCallback); this._isScanning = true; } /** * Stop scanning for Bluetooth devices * @returns void */ stopScan() { if (!this._isScanning) { return; } this.Instance.stopScan(); this._isScanning = false; } /** * Check if currently scanning for devices * @returns Boolean indicating if currently scanning */ isScanning() { this._isScanning = this.Instance.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 = this.Instance.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 deviceId when connected */ connect(deviceId, onDisconnect, autoConnectAndroid) { return new Promise((resolve, reject) => { // Check if already connected if (this._connectedDevices[deviceId]) { resolve(deviceId); return; } this.Instance.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, autoConnectAndroid !== null && autoConnectAndroid !== void 0 ? autoConnectAndroid : false); }); } /** * Scans for a device and connects to it * @param deviceId ID of the device to connect to * @param scanTimeout Optional timeout for the scan in milliseconds (default: 5000ms) * @returns Promise resolving deviceId when connected */ findAndConnect(deviceId, options) { const isConnected = this.isConnected(deviceId); if (isConnected) { return Promise.resolve(deviceId); } if (this._isScanning) { this.stopScan(); } return new Promise((resolve, reject) => { var _a; const timeoutScan = setTimeout(() => { this.stopScan(); reject(new Error('Scan timed out')); }, (_a = options === null || options === void 0 ? void 0 : options.scanTimeout) !== null && _a !== void 0 ? _a : 5000); this.startScan(undefined, (device) => { if (device.id === deviceId) { this.stopScan(); clearTimeout(timeoutScan); this.connect(deviceId, options === null || options === void 0 ? void 0 : options.onDisconnect, options === null || options === void 0 ? void 0 : options.autoConnectAndroid).then(async (connectedDeviceId) => { resolve(connectedDeviceId); }).catch((error) => { reject(error); }); } }); }); } /** * 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; } this.Instance.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 Boolean indicating if device is connected */ isConnected(deviceId) { return this.Instance.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 = this.Instance.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; } this.Instance.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; } this.Instance.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 = this.Instance.getServices(deviceId); resolve(BleNitroManager.normalizeGattUUIDs(services)); }); } /** * Get characteristics for a service * @param deviceId ID of the device * @param serviceId ID of the service * @returns array of characteristic UUIDs */ getCharacteristics(deviceId, serviceId) { if (!this._connectedDevices[deviceId]) { throw new Error('Device not connected'); } const characteristics = this.Instance.getCharacteristics(deviceId, BleNitroManager.normalizeGattUUID(serviceId)); return BleNitroManager.normalizeGattUUIDs(characteristics); } /** * Get services and characteristics for a connected device * @param deviceId ID of the device * @returns Promise resolving to array of service and characteristic UUIDs * @see getServices * @see getCharacteristics */ async getServicesWithCharacteristics(deviceId) { await this.discoverServices(deviceId); const services = await this.getServices(deviceId); return services.map((service) => { return { uuid: service, characteristics: this.getCharacteristics(deviceId, service), }; }); } /** * 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 ByteArray */ readCharacteristic(deviceId, serviceId, characteristicId) { return new Promise((resolve, reject) => { // Check if connected first if (!this._connectedDevices[deviceId]) { reject(new Error('Device not connected')); return; } this.Instance.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; } this.Instance.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 to AsyncSubscription when subscription is established */ async subscribeToCharacteristic(deviceId, serviceId, characteristicId, callback) { return new Promise((resolve, reject) => { // Check if connected first if (!this._connectedDevices[deviceId]) { reject(new Error('Device not connected')); return; } this.Instance.subscribeToCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), (charId, data) => { callback(charId, arrayBufferToByteArray(data)); }, (success, error) => { if (!success) { reject(new Error(error || 'Failed to subscribe to characteristic')); return; } const sub = { remove: async () => { await this.unsubscribeFromCharacteristic(deviceId, serviceId, characteristicId).catch(() => { }); } }; resolve(sub); }); }); } /** * 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; } this.Instance.unsubscribeFromCharacteristic(deviceId, BleNitroManager.normalizeGattUUID(serviceId), BleNitroManager.normalizeGattUUID(characteristicId), (success, error) => { if (success) { resolve(); } else { reject(new Error(error)); } }); }); } /** * Check if Bluetooth is enabled * @returns returns Boolean according 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) => { this.Instance.requestBluetoothEnable((success, error) => { if (success) { resolve(true); } else { reject(new Error(error)); } }); }); } /** * Get the current Bluetooth state * @returns Bluetooth state * @see BLEState */ state() { return mapNativeBLEStateToBLEState(this.Instance.state()); } /** * Subscribe to Bluetooth state changes * @param callback Callback function called when state changes * @param emitInitial Whether to emit initial state callback * @returns Subscription * @see BLEState */ subscribeToStateChange(callback, emitInitial = false) { if (emitInitial) { const state = this.state(); callback(state); } this.Instance.subscribeToStateChange((nativeState) => { callback(mapNativeBLEStateToBLEState(nativeState)); }); return { remove: () => { this.Instance.unsubscribeFromStateChange(); }, }; } /** * Open Bluetooth settings * @returns Promise resolving when settings are opened */ openSettings() { return this.Instance.openSettings(); } } exports.BleNitroManager = BleNitroManager; //# sourceMappingURL=manager.js.map