"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