UNPKG

@ledgerhq/live-common

Version:
136 lines 7.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useBleDevicesScanning = void 0; const react_1 = require("react"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const devices_1 = require("@ledgerhq/devices"); const errors_1 = require("@ledgerhq/errors"); const logs_1 = require("@ledgerhq/logs"); const DEFAULT_DEVICE_NAME = "Device"; const DEFAULT_RESTART_SCANNING_TIMEOUT_MS = 4000; /** * Scans the BLE devices around the user * * Warning: if a communication is started with a device, the scanning should be stopped * * Warning: handling of bluetooth (and location for Android) permissions and enabling bluetooth (and location) services are not handled here. * They should be handled (with fallback logic) by the consumer of this hook. * * Reason: depending on the bleTransportListen function and the user's operating system, errors related to denied bluetooth (and location for Android) permissions * or related to disabled bluetooth (and location) services might be different. * For ex: * - on Android, using the current Transport from react-native-hw-transport-ble, if the bluetooth is off, * a BluetoothScanStartFailed error is thrown, not an actual "BluetoothOff" or "BluetoothUnauthorized" error. * It is a problem because this BluetoothScanStartFailed error could happen for other reason than the BLE being off. * On the other side, if the location service (needed for Android) is off, an error "LocationServicesDisabled" is thrown. * * - on iOS, using the current Transport from react-native-hw-transport-ble, if the bluetooth is off, no error is thrown at all. * * @param bleTransportListen The listen function from an implementation of a BLE transport * @param filterByDeviceModelIds An array of device model ids to filter on * @param filterOutDevicesByDeviceIds An array of device ids to filter out * @param stopBleScanning Flag to stop or continue the scanning * @param restartScanningTimeoutMs When a restart is needed (on some specific errors, or for the first restart * that makes the scanning more resilient to a previously paired device with which a communication was happening), * time in ms after which the restart is actually happening * @param enabled flag to enable the hook * @returns An object containing: * - scannedDevices: list of ScannedDevice found by the scanning * - scanningBleError: if an error occurred, a BleError, otherwise null */ const useBleDevicesScanning = ({ bleTransportListen, stopBleScanning, filterByDeviceModelIds, filterOutDevicesByDeviceIds, restartScanningTimeoutMs = DEFAULT_RESTART_SCANNING_TIMEOUT_MS, enabled = true, }) => { const [scanningBleError, setScanningBleError] = (0, react_1.useState)(null); const [scannedDevices, setScannedDevices] = (0, react_1.useState)([]); // To check for duplicates. The ref will persist for the full lifetime of the component // in which the hook is called, and does not re-trigger the hook when being updated. const scannedDevicesRef = (0, react_1.useRef)([]); // To stop, call the unsubscribe and cleaning function, and re-run the scanning const [restartScanningNonce, setRestartScanningNonce] = (0, react_1.useState)(0); // To request a restart of the scanning const [isRestartNeeded, setIsRestartNeeded] = (0, react_1.useState)(true); (0, react_1.useEffect)(() => { if (stopBleScanning || !enabled) { return; } const bleScanningSource = new rxjs_1.Observable(bleTransportListen); // Concat to flatten the events emitted by the scanning, that could arrive too fast const sub = (0, rxjs_1.of)(bleScanningSource) .pipe((0, operators_1.concatAll)()) .subscribe({ next: (event) => { setScanningBleError(null); const { type, descriptor } = event; if (type === "add" && descriptor) { const transportDevice = descriptor; const isScannedDeviceDuplicate = scannedDevicesRef.current.some(d => d.deviceId === transportDevice.id); // Avoiding duplicates if (isScannedDeviceDuplicate) { return; } const shouldScannedDeviceBeFilteredOut = filterOutDevicesByDeviceIds?.some(deviceId => deviceId === transportDevice.id); if (shouldScannedDeviceBeFilteredOut) { return; } if (transportDevice.serviceUUIDs && transportDevice.serviceUUIDs.length > 0) { const bleInfo = (0, devices_1.getInfosForServiceUuid)(transportDevice.serviceUUIDs[0]); if (!bleInfo) { return; } // Filters on the model ids, if asked if (filterByDeviceModelIds && !filterByDeviceModelIds.includes(bleInfo.deviceModel.id)) { return; } const newScannedDevice = { deviceModel: bleInfo.deviceModel, deviceName: transportDevice.localName ?? transportDevice.name ?? DEFAULT_DEVICE_NAME, deviceId: transportDevice.id, bleRssi: transportDevice.rssi, }; setScannedDevices(scannedDevices => [...scannedDevices, newScannedDevice]); scannedDevicesRef.current.push(newScannedDevice); } } }, error: (error) => { (0, logs_1.log)("useBleDevicesScanning:error", `${error.type}: ${error.message}`); if (error instanceof errors_1.HwTransportError && error.type === errors_1.HwTransportErrorType.BluetoothScanStartFailed) { setIsRestartNeeded(true); } setScanningBleError(error); }, }); return () => { sub.unsubscribe(); }; }, [ restartScanningNonce, bleTransportListen, stopBleScanning, filterByDeviceModelIds, filterOutDevicesByDeviceIds, enabled, ]); // Triggers after a defined time a restart of the scanning if needed (0, react_1.useEffect)(() => { let timer; if (isRestartNeeded && !stopBleScanning) { timer = setTimeout(() => { setRestartScanningNonce(prev => prev + 1); setIsRestartNeeded(false); }, restartScanningTimeoutMs); } return () => { if (timer) clearTimeout(timer); }; }, [isRestartNeeded, restartScanningTimeoutMs, stopBleScanning]); return { scannedDevices, scanningBleError, }; }; exports.useBleDevicesScanning = useBleDevicesScanning; //# sourceMappingURL=useBleDevicesScanning.js.map