UNPKG

@nativescript-community/ble

Version:

Connect to and interact with Bluetooth LE peripherals.

1,648 lines 76.1 kB
import { Trace } from '@nativescript/core';
import { BluetoothCommon, BluetoothError, CLog, CLogTypes, bluetoothEnabled, prepareArgs, shortenUuidIfAssignedNumber } from './index.common';
export { BluetoothError, BleTraceCategory, CallbackType, MatchMode, MatchNum, ScanMode } from './index.common';
function nativeEncoding(encoding) {
    switch (encoding) {
        case 'utf-8':
            return NSUTF8StringEncoding;
        case 'latin2':
        case 'iso-8859-2':
            return NSISOLatin2StringEncoding;
        case 'shift-jis':
            return NSShiftJISStringEncoding;
        case 'iso-2022-jp':
            return NSISO2022JPStringEncoding;
        case 'euc-jp':
            return NSJapaneseEUCStringEncoding;
        case 'windows-1250':
            return NSWindowsCP1250StringEncoding;
        case 'windows-1251':
            return NSWindowsCP1251StringEncoding;
        case 'windows-1252':
            return NSWindowsCP1252StringEncoding;
        case 'windows-1253':
            return NSWindowsCP1253StringEncoding;
        case 'windows-1254':
            return NSWindowsCP1254StringEncoding;
        case 'utf-16be':
            return NSUTF16BigEndianStringEncoding;
        case 'utf-16le':
            return NSUTF16LittleEndianStringEncoding;
        default:
        case 'iso-8859-1':
        case 'latin1':
            return NSISOLatin1StringEncoding;
    }
}
function valueToNSData(value, encoding = 'iso-8859-1') {
    if (value instanceof NSData) {
        return value;
    }
    else if (value instanceof ArrayBuffer) {
        // for ArrayBuffer to NSData
        return NSData.dataWithData(value);
    }
    else if (value.buffer) {
        // typed array
        return NSData.dataWithData(value.buffer);
    }
    else if (Array.isArray(value)) {
        return NSData.dataWithData(new Uint8Array(value).buffer);
    }
    const type = typeof value;
    if (type === 'string') {
        return NSString.stringWithString(value).dataUsingEncoding(nativeEncoding(encoding));
    }
    else if (type === 'number') {
        return NSData.dataWithData(new Uint8Array([value]).buffer);
    }
    return null;
}
function valueToString(value) {
    if (value instanceof NSData) {
        const data = new Uint8Array(interop.bufferFromData(value));
        const result = [];
        data.forEach((v, i) => (result[i] = v));
        return result;
    }
    return value;
}
import { iOSNativeHelper } from '@nativescript/core/utils/native-helper';
const FIXED_IOS_MTU = 185;
var CBPeripheralDelegateImpl = /** @class */ (function (_super) {
    __extends(CBPeripheralDelegateImpl, _super);
    function CBPeripheralDelegateImpl() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    CBPeripheralDelegateImpl.prototype.addSubDelegate = function (delegate) {
        var index = this.subDelegates.indexOf(delegate);
        if (index === -1) {
            this.subDelegates.push(delegate);
        }
    };
    CBPeripheralDelegateImpl.prototype.removeSubDelegate = function (delegate) {
        var index = this.subDelegates.indexOf(delegate);
        if (index !== -1) {
            this.subDelegates.splice(index, 1);
        }
    };
    CBPeripheralDelegateImpl.new = function () {
        return _super.new.call(this);
    };
    CBPeripheralDelegateImpl.prototype.initWithOwner = function (owner) {
        this._owner = owner;
        this.subDelegates = [];
        this.onNotifyCallbacks = {};
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.initWithOwner: ".concat(owner.get()));
        }
        return this;
    };
    /**
     * Invoked when you discover the peripheral’s available services.
     * This method is invoked when your app calls the discoverServices(_:) method.
     * If the services of the peripheral are successfully discovered, you can access them through the peripheral’s services property.
     * If successful, the error parameter is nil.
     * If unsuccessful, the error parameter returns the cause of the failure.
     * @param peripheral [CBPeripheral] - The peripheral that the services belong to.
     * @param error [NSError] - If an error occurred, the cause of the failure.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidDiscoverServices = function (peripheral, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidDiscoverServices ---- peripheral: ".concat(peripheral, ", ").concat(error, ", ").concat(this));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidDiscoverServices) {
                d.peripheralDidDiscoverServices(peripheral, error);
            }
        });
    };
    /**
     * Invoked when you discover the included services of a specified service.
     * @param peripheral [CBPeripheral] - The peripheral providing this information.
     * @param service [CBService] - The CBService object containing the included service.
     * @param error [NSError] - If an error occurred, the cause of the failure.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidDiscoverIncludedServicesForServiceError = function (peripheral, service, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidDiscoverIncludedServicesForServiceError ---- peripheral: ".concat(peripheral, ", service: ").concat(service, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidDiscoverIncludedServicesForServiceError) {
                d.peripheralDidDiscoverIncludedServicesForServiceError(peripheral, service, error);
            }
        });
    };
    /**
     * Invoked when you discover the characteristics of a specified service.
     * @param peripheral [CBPeripheral] - The peripheral providing this information.
     * @param service [CBService] - The CBService object containing the included service.
     * @param error [NSError] - If an error occurred, the cause of the failure.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidDiscoverCharacteristicsForServiceError = function (peripheral, service, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidDiscoverCharacteristicsForServiceError ---- peripheral: ".concat(peripheral, ", service: ").concat(service, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidDiscoverCharacteristicsForServiceError) {
                d.peripheralDidDiscoverCharacteristicsForServiceError(peripheral, service, error);
            }
        });
    };
    /**
     * Invoked when you discover the descriptors of a specified characteristic.
     * @param peripheral [CBPeripheral] - The peripheral providing this information.
     * @param characteristic [CBCharacteristic] - The characteristic that the characteristic descriptors belong to.
     * @param error [NSError] - If an error occurred, the cause of the failure.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidDiscoverDescriptorsForCharacteristicError = function (peripheral, characteristic, error) {
        // NOTE that this cb won't be invoked bc we currently don't discover descriptors
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidDiscoverDescriptorsForCharacteristicError ---- peripheral: ".concat(peripheral, ", characteristic: ").concat(characteristic, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidDiscoverDescriptorsForCharacteristicError) {
                d.peripheralDidDiscoverDescriptorsForCharacteristicError(peripheral, characteristic, error);
            }
        });
    };
    /**
     * Invoked when you retrieve a specified characteristic’s value, or when
     * the peripheral device notifies your app that the characteristic’s
     * value has changed.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidUpdateValueForCharacteristicError = function (peripheral, characteristic, error) {
        if (!characteristic) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.warning, 'CBPeripheralDelegateImpl.peripheralDidUpdateValueForCharacteristicError ---- No CBCharacteristic.');
            }
            return;
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidUpdateValueForCharacteristicError) {
                d.peripheralDidUpdateValueForCharacteristicError(peripheral, characteristic, error);
            }
        });
        if (error !== null) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.error, "CBPeripheralDelegateImpl.peripheralDidUpdateValueForCharacteristicError ---- ".concat(error));
            }
            return;
        }
        if (characteristic.isNotifying) {
            var pUUID = NSUUIDToString(peripheral.identifier);
            var cUUID = CBUUIDToString(characteristic.UUID);
            var sUUID = CBUUIDToString(characteristic.service.UUID);
            var key = sUUID + '/' + cUUID;
            if (this.onNotifyCallbacks[key]) {
                this.onNotifyCallbacks[key]({
                    // type: 'notification',
                    peripheralUUID: pUUID,
                    serviceUUID: sUUID,
                    characteristicUUID: cUUID,
                    ios: characteristic.value,
                    value: toArrayBuffer(characteristic.value)
                });
            }
            else {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, '----- CALLBACK IS GONE -----');
                }
            }
        }
    };
    /**
     * Invoked when you retrieve a specified characteristic descriptor’s value.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidUpdateValueForDescriptorError = function (peripheral, descriptor, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidUpdateValueForDescriptorError ---- peripheral: ".concat(peripheral, ", descriptor: ").concat(descriptor, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidUpdateValueForDescriptorError) {
                d.peripheralDidUpdateValueForDescriptorError(peripheral, descriptor, error);
            }
        });
    };
    /**
     * Invoked when you write data to a characteristic’s value.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidWriteValueForCharacteristicError = function (peripheral, characteristic, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidWriteValueForCharacteristicError ---- peripheral: ".concat(peripheral, ", characteristic: ").concat(characteristic, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidWriteValueForCharacteristicError) {
                d.peripheralDidWriteValueForCharacteristicError(peripheral, characteristic, error);
            }
        });
    };
    /**
     * Invoked when the peripheral receives a request to start or stop
     * providing notifications for a specified characteristic’s value.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidUpdateNotificationStateForCharacteristicError = function (peripheral, characteristic, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidUpdateNotificationStateForCharacteristicError ---- peripheral: ".concat(peripheral, ", characteristic: ").concat(characteristic, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidUpdateNotificationStateForCharacteristicError) {
                d.peripheralDidUpdateNotificationStateForCharacteristicError(peripheral, characteristic, error);
            }
        });
        if (error) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.error, "CBPeripheralDelegateImpl.peripheralDidUpdateNotificationStateForCharacteristicError ---- ".concat(error));
            }
        }
        else {
            if (characteristic.isNotifying) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidUpdateNotificationStateForCharacteristicError ---- Notification began on ".concat(characteristic));
                }
            }
            else {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidUpdateNotificationStateForCharacteristicError ---- Notification stopped on  ".concat(characteristic));
                }
                // Bluetooth._manager.cancelPeripheralConnection(peripheral);
            }
        }
    };
    /**
     * IInvoked when you write data to a characteristic descriptor’s value.
     */
    CBPeripheralDelegateImpl.prototype.peripheralDidWriteValueForDescriptorError = function (peripheral, descriptor, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBPeripheralDelegateImpl.peripheralDidWriteValueForDescriptorError ---- peripheral: ".concat(peripheral, ", descriptor: ").concat(descriptor, ", error: ").concat(error));
        }
        this.subDelegates.forEach(function (d) {
            if (d.peripheralDidWriteValueForDescriptorError) {
                d.peripheralDidWriteValueForDescriptorError(peripheral, descriptor, error);
            }
        });
    };
    CBPeripheralDelegateImpl.ObjCProtocols = [CBPeripheralDelegate];
    return CBPeripheralDelegateImpl;
}(NSObject));
var CBCentralManagerDelegateImpl = /** @class */ (function (_super) {
    __extends(CBCentralManagerDelegateImpl, _super);
    function CBCentralManagerDelegateImpl() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    CBCentralManagerDelegateImpl.prototype.addSubDelegate = function (delegate) {
        var index = this.subDelegates.indexOf(delegate);
        if (index === -1) {
            this.subDelegates.push(delegate);
        }
    };
    CBCentralManagerDelegateImpl.prototype.removeSubDelegate = function (delegate) {
        var index = this.subDelegates.indexOf(delegate);
        if (index !== -1) {
            this.subDelegates.splice(index, 1);
        }
    };
    CBCentralManagerDelegateImpl.new = function () {
        return _super.new.call(this);
    };
    CBCentralManagerDelegateImpl.prototype.initWithOwner = function (owner) {
        this._owner = owner;
        this.subDelegates = [];
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBCentralManagerDelegateImpl.initWithOwner: ".concat(this._owner));
        }
        // this._callback = callback;
        return this;
    };
    /**
     * Invoked when a connection is successfully created with a peripheral.
     * This method is invoked when a call to connect(_:options:) is successful.
     * You typically implement this method to set the peripheral’s delegate and to discover its services.
     * @param central [CBCentralManager] - The central manager providing this information.
     * @param peripheral [CBPeripheral] - The peripheral that has been connected to the system.
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerDidConnectPeripheral = function (central, peripheral) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "----- CBCentralManagerDelegateImpl centralManager:didConnectPeripheral: ".concat(peripheral));
        }
        this._owner.get().onPeripheralConnected(peripheral);
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "----- CBCentralManagerDelegateImpl centralManager:didConnectPeripheral, let's discover service");
        }
        this.subDelegates.forEach(function (d) {
            if (d.centralManagerDidConnectPeripheral) {
                d.centralManagerDidConnectPeripheral(central, peripheral);
            }
        });
    };
    /**
     * Invoked when an existing connection with a peripheral is torn down.
     * This method is invoked when a peripheral connected via the connect(_:options:) method is disconnected.
     * If the disconnection was not initiated by cancelPeripheralConnection(_:), the cause is detailed in error.
     * After this method is called, no more methods are invoked on the peripheral device’s CBPeripheralDelegate object.
     * Note that when a peripheral is disconnected, all of its services, characteristics, and characteristic descriptors are invalidated.
     * @param central [CBCentralManager] - The central manager providing this information.
     * @param peripheral [CBPeripheral] - The peripheral that has been disconnected.
     * @param error? [NSError] - If an error occurred, the cause of the failure.
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerDidDisconnectPeripheralError = function (central, peripheral, error) {
        // this event needs to be honored by the client as any action afterwards crashes the app
        var UUID = NSUUIDToString(peripheral.identifier);
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, 'CBCentralManagerDelegate.centralManagerDidDisconnectPeripheralError ----', central, peripheral, error);
        }
        this._owner.get().onPeripheralDisconnected(peripheral);
        this.subDelegates.forEach(function (d) {
            if (d.centralManagerDidDisconnectPeripheralError) {
                d.centralManagerDidDisconnectPeripheralError(central, peripheral, error);
            }
        });
    };
    /**
     * Invoked when the central manager fails to create a connection with a peripheral.
     * This method is invoked when a connection initiated via the connect(_:options:) method fails to complete.
     * Because connection attempts do not time out, a failed connection usually indicates a transient issue, in which case you may attempt to connect to the peripheral again.
     * @param central [CBCentralManager] - The central manager providing this information.
     * @param peripheral [CBPeripheral] - The peripheral that failed to connect.
     * @param error? [NSError] - The cause of the failure.
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerDidFailToConnectPeripheralError = function (central, peripheral, error) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, 'CBCentralManagerDelegate.centralManagerDidFailToConnectPeripheralError ----', central, peripheral, error);
        }
        this.subDelegates.forEach(function (d) {
            if (d.centralManagerDidDisconnectPeripheralError) {
                d.centralManagerDidFailToConnectPeripheralError(central, peripheral, error);
            }
        });
    };
    /**
     * Invoked when the central manager discovers a peripheral while scanning.
     * The advertisement data can be accessed through the keys listed in Advertisement Data Retrieval Keys.
     * You must retain a local copy of the peripheral if any command is to be performed on it.
     * In use cases where it makes sense for your app to automatically connect to a peripheral that is located within a certain range, you can use RSSI data to determine the proximity of a discovered peripheral device.
     * @param central [CBCentralManager] - The central manager providing the update.
     * @param peripheral [CBPeripheral] - The discovered peripheral.
     * @param advData [NSDictionary<string, any>] - A dictionary containing any advertisement data.
     * @param RSSI [NSNumber] - The current received signal strength indicator (RSSI) of the peripheral, in decibels.
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerDidDiscoverPeripheralAdvertisementDataRSSI = function (central, peripheral, advData, RSSI) {
        var UUIDString = NSUUIDToString(peripheral.identifier);
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBCentralManagerDelegateImpl.centralManagerDidDiscoverPeripheralAdvertisementDataRSSI ---- ".concat(peripheral.name, " @ ").concat(UUIDString, " @ ").concat(RSSI, " @ ").concat(advData));
        }
        var owner = this._owner && this._owner.get();
        if (!owner) {
            return;
        }
        owner.adddDiscoverPeripheral(peripheral);
        var advertismentData = new AdvertismentData(advData);
        var payload = {
            UUID: UUIDString,
            name: peripheral.name,
            localName: advertismentData.localName,
            RSSI: RSSI,
            advertismentData: advertismentData,
            nativeDevice: peripheral,
            state: this._owner.get()._getState(peripheral.state),
            manufacturerId: advertismentData.manufacturerId
        };
        owner._advData[UUIDString] = advertismentData;
        if (owner._onDiscovered) {
            owner._onDiscovered(payload);
        }
        owner.sendEvent(Bluetooth.device_discovered_event, payload);
    };
    /**
     * Invoked when the central manager’s state is updated.
     * You implement this required method to ensure that Bluetooth low energy is supported and available to use on the central device.
     * You should issue commands to the central manager only when the state of the central manager is powered on, as indicated by the poweredOn constant.
     * A state with a value lower than poweredOn implies that scanning has stopped and that any connected peripherals have been disconnected.
     * If the state moves below poweredOff, all CBPeripheral objects obtained from this central manager become invalid and must be retrieved or discovered again.
     * For a complete list and discussion of the possible values representing the state of the central manager, see the CBCentralManagerState enumeration in CBCentralManager.
     * @param central [CBCentralManager] - The central manager providing this information.
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerDidUpdateState = function (central) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBCentralManagerDelegateImpl.centralManagerDidUpdateState: ".concat(central.state));
        }
        if (central.state === CBManagerState.Unsupported) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.warning, 'CBCentralManagerDelegateImpl.centralManagerDidUpdateState ---- This hardware does not support Bluetooth Low Energy.');
            }
        }
        this._owner.get().state = central.state;
    };
    /**
     * Invoked when the central manager is about to be restored by the system.
     * @param central [CBCentralManager] - The central manager providing this information.
     * @param dict [NSDictionary<string, any>] - A dictionary containing information about the central manager that was preserved by the system at the time the app was terminated.
     * For the available keys to this dictionary, see Central Manager State Restoration Options.
     * @link - https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/central_manager_state_restoration_options
     */
    CBCentralManagerDelegateImpl.prototype.centralManagerWillRestoreState = function (central, dict) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, "CBCentralManagerDelegateImpl.centralManagerWillRestoreState ---- central: ".concat(central, ", dict: ").concat(dict));
        }
    };
    CBCentralManagerDelegateImpl.ObjCProtocols = [CBCentralManagerDelegate];
    return CBCentralManagerDelegateImpl;
}(NSObject));
export class AdvertismentData {
    constructor(advData) {
        this.advData = advData;
    }
    get manufacturerData() {
        const data = this.advData.objectForKey(CBAdvertisementDataManufacturerDataKey);
        if (data && data.length > 2) {
            return toArrayBuffer(data.subdataWithRange(NSMakeRange(2, data.length - 2)));
        }
        return undefined;
    }
    get data() {
        return toArrayBuffer(this.advData);
    }
    get manufacturerId() {
        const data = this.advData.objectForKey(CBAdvertisementDataManufacturerDataKey);
        if (data && data.length >= 2) {
            const manufacturerIdBuffer = toArrayBuffer(data.subdataWithRange(NSMakeRange(0, 2)));
            return new DataView(manufacturerIdBuffer, 0).getUint16(0, true);
        }
        return -1;
    }
    get txPowerLevel() {
        return this.advData.objectForKey(CBAdvertisementDataTxPowerLevelKey) || Number.MIN_VALUE;
    }
    get localName() {
        return this.advData.objectForKey(CBAdvertisementDataLocalNameKey);
    }
    get flags() {
        return -1;
    }
    get serviceUUIDs() {
        const result = [];
        const serviceUuids = this.advData.objectForKey(CBAdvertisementDataServiceUUIDsKey);
        if (serviceUuids) {
            for (let i = 0; i < serviceUuids.count; i++) {
                result.push(CBUUIDToString(serviceUuids[i]));
            }
        }
        return result;
    }
    get overtflow() {
        const result = [];
        const serviceUuids = this.advData.objectForKey(CBAdvertisementDataOverflowServiceUUIDsKey);
        if (serviceUuids) {
            for (let i = 0; i < serviceUuids.count; i++) {
                result.push(CBUUIDToString(serviceUuids[i]));
            }
        }
        return result;
    }
    get solicitedServices() {
        const result = [];
        const serviceUuids = this.advData.objectForKey(CBAdvertisementDataSolicitedServiceUUIDsKey);
        if (serviceUuids) {
            for (let i = 0; i < serviceUuids.count; i++) {
                result.push(CBUUIDToString(serviceUuids[i]));
            }
        }
        return result;
    }
    get connectable() {
        return this.advData.objectForKey(CBAdvertisementDataIsConnectable);
    }
    get serviceData() {
        const result = {};
        const obj = this.advData.objectForKey(CBAdvertisementDataServiceDataKey);
        if (obj) {
            obj.enumerateKeysAndObjectsUsingBlock((key, data) => {
                result[CBUUIDToString(key)] = toArrayBuffer(data);
            });
        }
        return result;
    }
}
let _bluetoothInstance;
export function getBluetoothInstance() {
    if (!_bluetoothInstance) {
        _bluetoothInstance = new Bluetooth();
    }
    return _bluetoothInstance;
}
export function toArrayBuffer(value) {
    return value ? interop.bufferFromData(value) : null;
}
function _getProperties(characteristic) {
    const props = characteristic.properties;
    return {
        // broadcast: (props & CBCharacteristicPropertyBroadcast) === CBCharacteristicPropertyBroadcast,
        broadcast: (props & 1 /* CBCharacteristicProperties.PropertyBroadcast */) === 1 /* CBCharacteristicProperties.PropertyBroadcast */,
        read: (props & 2 /* CBCharacteristicProperties.PropertyRead */) === 2 /* CBCharacteristicProperties.PropertyRead */,
        broadcast2: (props & 1 /* CBCharacteristicProperties.PropertyBroadcast */) === 1 /* CBCharacteristicProperties.PropertyBroadcast */,
        read2: (props & 2 /* CBCharacteristicProperties.PropertyRead */) === 2 /* CBCharacteristicProperties.PropertyRead */,
        write: (props & 8 /* CBCharacteristicProperties.PropertyWrite */) === 8 /* CBCharacteristicProperties.PropertyWrite */,
        writeWithoutResponse: (props & 4 /* CBCharacteristicProperties.PropertyWriteWithoutResponse */) === 4 /* CBCharacteristicProperties.PropertyWriteWithoutResponse */,
        notify: (props & 16 /* CBCharacteristicProperties.PropertyNotify */) === 16 /* CBCharacteristicProperties.PropertyNotify */,
        indicate: (props & 32 /* CBCharacteristicProperties.PropertyIndicate */) === 32 /* CBCharacteristicProperties.PropertyIndicate */,
        authenticatedSignedWrites: (props & 64 /* CBCharacteristicProperties.PropertyAuthenticatedSignedWrites */) === 64 /* CBCharacteristicProperties.PropertyAuthenticatedSignedWrites */,
        extendedProperties: (props & 128 /* CBCharacteristicProperties.PropertyExtendedProperties */) === 128 /* CBCharacteristicProperties.PropertyExtendedProperties */,
        notifyEncryptionRequired: (props & 256 /* CBCharacteristicProperties.PropertyNotifyEncryptionRequired */) === 256 /* CBCharacteristicProperties.PropertyNotifyEncryptionRequired */,
        indicateEncryptionRequired: (props & 512 /* CBCharacteristicProperties.PropertyIndicateEncryptionRequired */) === 512 /* CBCharacteristicProperties.PropertyIndicateEncryptionRequired */
    };
}
export function NSUUIDToString(uuid) {
    return uuid.toString().toLowerCase();
}
export function CBUUIDToString(uuid) {
    return uuid.UUIDString.toLowerCase();
}
export class Bluetooth extends BluetoothCommon {
    clear() {
        // doing nothing on ios
    }
    set state(state) {
        if (this._state !== state) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, 'BLE state change', state);
            }
            this._state = state;
            this.sendEvent(BluetoothCommon.bluetooth_status_event, {
                state: state === 2 /* CBManagerState.Unsupported */ ? 'unsupported' : state === 5 /* CBManagerState.PoweredOn */ ? 'on' : 'off'
            });
        }
    }
    get state() {
        return this._state;
    }
    get centralDelegate() {
        if (!this._centralDelegate) {
            this._centralDelegate = CBCentralManagerDelegateImpl.new().initWithOwner(new WeakRef(this));
        }
        return this._centralDelegate;
    }
    ensureCentralManager() {
        if (!this._centralManager) {
            const options = new NSMutableDictionary([this.showPowerAlertPopup], [CBCentralManagerOptionShowPowerAlertKey]);
            if (this.restoreIdentifier) {
                options.setObjectForKey(this.restoreIdentifier, CBCentralManagerOptionRestoreIdentifierKey);
            }
            this._centralManager = CBCentralManager.alloc().initWithDelegateQueueOptions(this.centralDelegate, null, options);
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, `creating CBCentralManager: ${this._centralManager}`);
            }
        }
    }
    get centralManager() {
        this.ensureCentralManager();
        return this._centralManager;
    }
    constructor(restoreIdentifierOrOptions = 'ns_bluetooth', showPowerAlertPopup = false) {
        super();
        this.showPowerAlertPopup = showPowerAlertPopup;
        this._centralDelegate = null;
        this._centralManager = null;
        this._discoverPeripherals = {};
        this._connectedPeripherals = {};
        this._connectCallbacks = {};
        this._disconnectCallbacks = {};
        // _advData is used to store Advertisment Data so that we can send it to connection callback
        this._advData = {};
        this._onDiscovered = null;
        this._state = 2 /* CBManagerState.Unsupported */;
        if (typeof restoreIdentifierOrOptions === 'object') {
            if (restoreIdentifierOrOptions.restoreIdentifier === undefined) {
                this.restoreIdentifier = 'ns_bluetooth';
            }
            else {
                this.restoreIdentifier = restoreIdentifierOrOptions.restoreIdentifier;
            }
            this.showPowerAlertPopup = !!restoreIdentifierOrOptions.showPowerAlertPopup;
        }
        else {
            this.restoreIdentifier = restoreIdentifierOrOptions;
        }
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, 'Creating Bluetooth instance', this.restoreIdentifier);
        }
    }
    onListenerAdded(eventName, count) {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, 'onListenerAdded', eventName, count);
        }
        if (eventName === Bluetooth.bluetooth_status_event) {
            // ensure centralManager is set to have status event
            this.ensureCentralManager();
        }
    }
    _getState(state) {
        if (state === 1 /* CBPeripheralState.Connecting */) {
            return 'connecting';
        }
        else if (state === 2 /* CBPeripheralState.Connected */) {
            return 'connected';
        }
        else if (state === 0 /* CBPeripheralState.Disconnected */) {
            return 'disconnected';
        }
        else {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.warning, '_getState ---- Unexpected state, returning "disconnected" for state of', state);
            }
            return 'disconnected';
        }
    }
    prepareConnectedPeripheralDelegate(peripheral) {
        if (!peripheral.delegate) {
            const UUID = NSUUIDToString(peripheral.identifier);
            const delegate = CBPeripheralDelegateImpl.new().initWithOwner(new WeakRef(this));
            CFRetain(delegate);
            peripheral.delegate = delegate;
        }
    }
    onPeripheralDisconnected(peripheral) {
        const UUID = NSUUIDToString(peripheral.identifier);
        const cb = this._disconnectCallbacks[UUID];
        if (cb) {
            cb({
                UUID,
                name: peripheral.name
            });
            delete this._disconnectCallbacks[UUID];
        }
        this.sendEvent(Bluetooth.device_disconnected_event, {
            UUID,
            name: peripheral.name
        });
        if (peripheral.delegate) {
            CFRelease(peripheral.delegate);
            peripheral.delegate = null;
        }
        delete this._connectedPeripherals[UUID];
    }
    onPeripheralConnected(peripheral) {
        const UUID = NSUUIDToString(peripheral.identifier);
        this.prepareConnectedPeripheralDelegate(peripheral);
        this._connectedPeripherals[UUID] = peripheral;
    }
    async isBluetoothEnabled() {
        if (this._state === 2 /* CBManagerState.Unsupported */) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, 'isBluetoothEnabled', 'central manager not ready, waiting for it to start');
            }
            this.ensureCentralManager();
            return new Promise((resolve) => {
                this.once(BluetoothCommon.bluetooth_status_event, () => {
                    resolve(this._isEnabled());
                });
            });
        }
        return this._isEnabled();
    }
    startScanning(args) {
        const methodName = 'startScanning';
        return new Promise((resolve, reject) => {
            try {
                this._discoverPeripherals = {};
                this._onDiscovered = args.onDiscovered;
                let services = null;
                if (args.filters) {
                    services = [];
                    args.filters.forEach((f) => {
                        if (f.serviceUUID) {
                            services.push(CBUUID.UUIDWithString(f.serviceUUID));
                        }
                    });
                }
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, methodName, '---- services:', services);
                }
                const options = new NSMutableDictionary();
                if (!args.avoidDuplicates) {
                    options.setObjectForKey(true, CBCentralManagerScanOptionAllowDuplicatesKey);
                }
                // TODO: check on the services as any casting
                this.centralManager.scanForPeripheralsWithServicesOptions(services, options);
                if (this.scanningReferTimer) {
                    clearTimeout(this.scanningReferTimer.timer);
                    this.scanningReferTimer.resolve();
                }
                this.scanningReferTimer = {};
                if (args.seconds) {
                    this.scanningReferTimer.timer = setTimeout(() => {
                        // note that by now a manual 'stop' may have been invoked, but that doesn't hurt
                        this.centralManager.stopScan();
                        resolve();
                    }, args.seconds * 1000);
                    this.scanningReferTimer.resolve = resolve;
                }
                else {
                    resolve();
                }
                if (__DEV__) {
                }
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        });
    }
    enable() {
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, 'enable ---- Not possible on iOS');
        }
        return this.isBluetoothEnabled();
    }
    openBluetoothSettings(url) {
        return Promise.reject(new BluetoothError(BluetoothCommon.msg_cant_open_settings));
        // return this.isBluetoothEnabled().then(isEnabled => {
        //     if (!isEnabled) {
        //         return Promise.resolve().then(() => {
        //             const settingsUrl = NSURL.URLWithString(url || UIApplicationOpenSettingsURLString);
        //             if (UIApplication.sharedApplication.canOpenURL(settingsUrl)) {
        //                 UIApplication.sharedApplication.openURLOptionsCompletionHandler(settingsUrl, null, function(success) {
        //                     // we get the callback for opening the URL, not enabling the bluetooth!
        //                     if (success) {
        //                         return Promise.reject(undefined);
        //                     } else {
        //                         return Promise.reject(BluetoothCommon.msg_cant_open_settings);
        //                     }
        //                 });
        //             }
        //         });
        //     }
        //     return null;
        // });
    }
    stopScanning() {
        const methodName = 'stopScanning';
        return new Promise((resolve, reject) => {
            try {
                this.centralManager.stopScan();
                if (this.scanningReferTimer) {
                    this.scanningReferTimer.resolve && this.scanningReferTimer.resolve();
                    clearTimeout(this.scanningReferTimer.timer);
                    this.scanningReferTimer = null;
                }
                resolve();
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName
                }));
            }
        });
    }
    clearAdvertismentCache() {
        this._advData = {};
    }
    async getDevice(args) {
        const methodName = 'getDevice';
        if (!args.UUID) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: BluetoothCommon.UUIDKey,
                arguments: args
            }));
        }
        return this.findDiscoverPeripheral(args.UUID);
    }
    async connect(args) {
        const methodName = 'connect';
        try {
            if (!args.UUID) {
                return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                    method: methodName,
                    type: BluetoothCommon.UUIDKey,
                    arguments: args
                }));
            }
            const connectingUUID = args.UUID;
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, '----', args.UUID);
            }
            const peripheral = this.findDiscoverPeripheral(args.UUID);
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, '---- peripheral found', peripheral);
            }
            if (!peripheral) {
                throw new BluetoothError(BluetoothCommon.msg_no_peripheral, {
                    method: methodName,
                    arguments: args
                });
            }
            else {
                await new Promise((resolve, reject) => {
                    const subD = {
                        centralManagerDidConnectPeripheral: (central, peripheral) => {
                            const UUID = NSUUIDToString(peripheral.identifier);
                            if (UUID === connectingUUID) {
                                resolve();
                                this._centralDelegate.removeSubDelegate(subD);
                            }
                        },
                        centralManagerDidFailToConnectPeripheralError: (central, peripheral, error) => {
                            const UUID = NSUUIDToString(peripheral.identifier);
                            if (UUID === connectingUUID) {
                                reject(new BluetoothError(error.localizedDescription, {
                                    method: methodName,
                                    status: error.code
                                }));
                                this._centralDelegate.removeSubDelegate(subD);
                            }
                        }
                    };
                    if (Trace.isEnabled()) {
                        CLog(CLogTypes.info, methodName, '---- Connecting to peripheral with UUID:', connectingUUID, this._centralDelegate, this._centralManager);
                    }
                    this.centralDelegate.addSubDelegate(subD);
                    this._connectCallbacks[connectingUUID] = args.onConnected;
                    this._disconnectCallbacks[connectingUUID] = args.onDisconnected;
                    if (Trace.isEnabled()) {
                        CLog(CLogTypes.info, methodName, '----about to connect:', connectingUUID, this._centralDelegate, this._centralManager);
                    }
                    this.centralManager.connectPeripheralOptions(peripheral, null);
                });
                let services, mtu = FIXED_IOS_MTU;
                if (args.autoDiscoverAll !== false) {
                    services = (await this.discoverAll({ peripheralUUID: connectingUUID }))?.services;
                }
                else if (args.serviceUUIDs) {
                    services = (await this.discoverAll({ peripheralUUID: connectingUUID, serviceUUIDs: args.serviceUUIDs }))?.services;
                }
                if (!!args.autoMaxMTU) {
                    mtu = await this.requestMtu({ peripheralUUID: connectingUUID, value: FIXED_IOS_MTU });
                }
                const adv = this._advData[connectingUUID];
                const dataToSend = {
                    UUID: connectingUUID,
                    name: peripheral.name,
                    state: this._getState(peripheral.state),
                    services,
                    nativeDevice: peripheral,
                    localName: adv?.localName,
                    manufacturerId: adv?.manufacturerId,
                    advertismentData: adv,
                    mtu
                };
                // delete this._advData[connectingUUID];
                const cb = this._connectCallbacks[connectingUUID];
                if (cb) {
                    cb(dataToSend);
                    delete this._connectCallbacks[connectingUUID];
                }
                this.sendEvent(Bluetooth.device_connected_event, dataToSend);
                return dataToSend;
            }
        }
        catch (ex) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.error, methodName, '---- error:', ex);
            }
            throw new BluetoothError(ex.message, {
                stack: ex.stack,
                nativeException: ex.nativeException,
                method: methodName,
                arguments: args
            });
        }
    }
    async disconnect(args) {
        const methodName = 'disconnect';
        try {
            if (!args.UUID) {
                throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                    method: methodName,
                    type: BluetoothCommon.UUIDKey,
                    arguments: args
                });
            }
            const pUUID = args.UUID;
            const peripheral = this.findPeripheral(pUUID);
            if (!peripheral) {
                throw new BluetoothError(BluetoothCommon.msg_no_peripheral, {
                    method: methodName,
                    arguments: args
                });
            }
            else {
                // no need to send an error when already disconnected, but it's wise to check it
                if (peripheral.state !== 0 /* CBPeripheralState.Disconnected */) {
                    return new Promise((resolve, reject) => {
                        const subD = {
                            centralManagerDidDisconnectPeripheralError: (central, peripheral, error) => {
                                const UUID = NSUUIDToString(peripheral.identifier);
                                if (UUID === pUUID) {
                                    if (error) {
                                        reject(new BluetoothError(error.localizedDescription, {
                                            method: methodName,
                                            status: error.code
                                        }));
                                    }
                                    else {
                                        resolve();
                                    }
                                    this._centralDelegate.removeSubDelegate(subD);
                                }
                            }
                        };
                        this.centralDelegate.addSubDelegate(subD);
                        if (Trace.isEnabled()) {
                            CLog(CLogTypes.info, methodName, '---- Disconnecting peripheral with UUID', pUUID);
                        }
                        this.centralManager.cancelPeripheralConnection(peripheral);
                    });
                }
            }
        }
        catch (ex) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.error, methodName, '---- error:', ex);
            }
            throw new BluetoothError(ex.message, {
                stack: ex.stack,
                nativeException: ex.nativeException,
                method: methodName,
                arguments: args
            });
        }
    }
    async isConnected(args) {
        const methodName = 'isConnected';
        try {
            if (!args.UUID) {
                throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                    method: methodName,
                    type: BluetoothCommon.UUIDKey,
                    arguments: args
                });
            }
            const peripheral = this.findPeripheral(args.UUID);
            if (peripheral === null) {
                return false;
            }
            else {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, methodName, '---- checking connection with peripheral UUID:', args.UUID);
                }
                return peripheral.state === 2 /* CBPeripheralState.Connected */;
            }
        }
        catch (ex) {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.error, methodName, '---- error:', ex);
            }
            throw new BluetoothError(ex.message, {
                stack: ex.stack,
                nativeException: ex.nativeException,
                method: methodName,
                arguments: args
            });
        }
    }
    findPeripheral(UUID) {
        let result = this._connectedPeripherals[UUID] || this._discoverPeripherals[UUID];
        if (!result) {
            const periphs = this.centralManager.retrievePeripheralsWithIdentifiers([NSUUID.alloc().initWithUUIDString(UUID)]);
            if (periphs.count > 0) {
                result = periphs.objectAtIndex(0);
                this.prepareConnectedPeripheralDelegate(result);
            }
        }
        return result;
    }
    adddDiscoverPeripheral(peripheral) {
        const UUID = NSUUIDToString(peripheral.identifier);
        if (!this._discoverPeripherals[UUID]) {
            this._discoverPeripherals[UUID] = peripheral;
        }
    }
    findDiscoverPeripheral(UUID) {
        let result = this._discoverPeripherals[UUID];
        if (!result) {
            const periphs = this.centralManager.retrievePeripheralsWithIdentifiers([NSUUID.alloc().initWithUUIDString(UUID)]);
            if (periphs.count > 0) {
                result = periphs.objectAtIndex(0);
            }
        }
        return result;
    }
    read(args) {
        const methodName = 'read';
        return this._getWrapper(args, 2 /* CBCharacteristicProperties.PropertyRead */).then((wrapper) => new Promise((resolve, reject) => {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID} characteristicUUID:${args.characteristicUUID}`);
            }
            const pUUID = args.peripheralUUID;
            const p = wrapper.peripheral;
            let timeoutTimer;
            if (args.timeout > 0) {
                timeoutTimer = setTimeout(() => {
                    // we need to try catch because the simple fact of creating a new Error actually throws.
                    // so we will get an uncaughtException
                    try {
                        reject(new Error('timeout'));
                    }
                    catch { }
                }, args.timeout);
            }
            const subD = {
                peripheralDidUpdateValueForCharacteristicError: (peripheral, characteristic, error) => {
                    if (!characteristic) {
                        return;
                    }
                    const UUID = NSUUIDToString(peripheral.identifier);
                    const cUUID = CBUUIDToString(characteristic.UUID);
                    const sUUID = CBUUIDToString(characteristic.service.UUID);
                    if (UUID === pUUID && cUUID === args.characteristicUUID && sUUID === args.serviceUUID) {
                        timeoutTimer && clearTimeout(timeoutTimer);
                        if (Trace.isEnabled()) {
                            CLog(CLogTypes.info, methodName, '---- peripheralDidUpdateValueForCharacteristicError', error);
                        }
                        if (error) {
                            reject(new BluetoothError(error.localizedDescription, {
                                method: 'peripheralDidUpdateValueForCharacteristicError',
                                status: error.code,
                                arguments: args
                            }));
                        }
                        else {
                            resolve({
                                characteristicUUID: cUUID,
                                ios: characteristic.value,
                                value: toArrayBuffer(characteristic.value)
                            });
                        }
                        p.delegate.removeSubDelegate(subD);
                    }
                }
            };
            p.delegate.addSubDelegate(subD);
            try {
                p.readValueForCharacteristic(wrapper.characteristic);
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                timeoutTimer && clearTimeout(timeoutTimer);
                reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        }));
    }
    requestMtu(args) {
        const methodName = 'requestMtu';
        if (!args.value) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: 'value',
                arguments: args
            }));
        }
        const peripheral = this.findPeripheral(args.peripheralUUID);
        if (!peripheral) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_no_peripheral, { method: methodName, arguments: args }));
        }
        return Promise.resolve(Math.min(peripheral.maximumWriteValueLengthForType(1 /* CBCharacteristicWriteType.WithoutResponse */), peripheral.maximumWriteValueLengthForType(0 /* CBCharacteristicWriteType.WithResponse */)));
    }
    async readRssi(args) {
        const methodName = 'readRssi';
        const p = await this._getDeviceWrapper(args);
        return new Promise((resolve, reject) => {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID}`);
            }
            const pUUID = args.peripheralUUID;
            const subD = {
                peripheralDidReadRSSIError: (peripheral, rssi, error) => {
                    if (Trace.isEnabled()) {
                        CLog(CLogTypes.info, methodName, '---- peripheralDidWriteValueForCharacteristicError', error);
                    }
                    const UUID = NSUUIDToString(peripheral.identifier);
                    if (UUID === pUUID) {
                        if (error) {
                            reject(new BluetoothError(error.localizedDescription, {
                                method: methodName,
                                status: error.code
                            }));
                        }
                        else {
                            resolve(rssi);
                        }
                        p.delegate.removeSubDelegate(subD);
                    }
                }
            };
            p.delegate.addSubDelegate(subD);
            try {
                p.readRSSI();
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                p.delegate.removeSubDelegate(subD);
                reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        });
    }
    write(args) {
        const methodName = 'write';
        if (!args.value) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: 'value',
                arguments: args
            }));
        }
        return this._getWrapper(args, 8 /* CBCharacteristicProperties.PropertyWrite */).then((wrapper) => new Promise((resolve, reject) => {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID} characteristicUUID:${args.characteristicUUID}`);
            }
            const valueEncoded = valueToNSData(args.value, args.encoding);
            if (valueEncoded === null) {
                return reject(new BluetoothError(BluetoothCommon.msg_invalid_value, {
                    method: methodName,
                    arguments: args
                }));
            }
            const pUUID = args.peripheralUUID;
            const p = wrapper.peripheral;
            let timeoutTimer;
            if (args.timeout > 0) {
                timeoutTimer = setTimeout(() => {
                    // we need to try catch because the simple fact of creating a new Error actually throws.
                    // so we will get an uncaughtException
                    try {
                        reject(new Error('timeout'));
                    }
                    catch { }
                }, args.timeout);
            }
            const subD = {
                peripheralDidWriteValueForCharacteristicError: (peripheral, characteristic, error) => {
                    if (!characteristic) {
                        return;
                    }
                    if (Trace.isEnabled()) {
                        CLog(CLogTypes.info, methodName, '---- peripheralDidWriteValueForCharacteristicError', error);
                    }
                    const UUID = NSUUIDToString(peripheral.identifier);
                    const cUUID = CBUUIDToString(characteristic.UUID);
                    const sUUID = CBUUIDToString(characteristic.service.UUID);
                    if (UUID === pUUID && cUUID === args.characteristicUUID && sUUID === args.serviceUUID) {
                        timeoutTimer && clearTimeout(timeoutTimer);
                        if (error) {
                            reject(new BluetoothError(error.localizedDescription, {
                                method: methodName,
                                status: error.code
                            }));
                        }
                        else {
                            resolve();
                        }
                        p.delegate.removeSubDelegate(subD);
                    }
                }
            };
            p.delegate.addSubDelegate(subD);
            try {
                p.writeValueForCharacteristicType(valueEncoded, wrapper.characteristic, 0 /* CBCharacteristicWriteType.WithResponse */);
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                timeoutTimer && clearTimeout(timeoutTimer);
                p.delegate.removeSubDelegate(subD);
                return reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, JSON.stringify(valueToString(valueEncoded)));
            }
        }));
    }
    writeWithoutResponse(args) {
        const methodName = 'writeWithoutResponse';
        if (!args.value) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: 'value',
                arguments: args
            }));
        }
        return this._getWrapper(args, 4 /* CBCharacteristicProperties.PropertyWriteWithoutResponse */).then((wrapper) => {
            try {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID} characteristicUUID:${args.characteristicUUID}`);
                }
                const valueEncoded = valueToNSData(args.value, args.encoding);
                if (valueEncoded === null) {
                    return Promise.reject(new BluetoothError(BluetoothCommon.msg_invalid_value, {
                        method: methodName,
                        arguments: args
                    }));
                }
                wrapper.peripheral.writeValueForCharacteristicType(valueEncoded, wrapper.characteristic, 1 /* CBCharacteristicWriteType.WithoutResponse */);
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, methodName, JSON.stringify(valueToString(valueEncoded)));
                }
                return null;
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                return Promise.reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        });
    }
    startNotifying(args) {
        const methodName = 'startNotifying';
        return this._getWrapper(args, 16 /* CBCharacteristicProperties.PropertyNotify */).then((wrapper) => {
            try {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID} characteristicUUID:${args.characteristicUUID}`);
                }
                const cb = args.onNotify ||
                    function (result) {
                        if (Trace.isEnabled()) {
                            CLog(CLogTypes.info, methodName, '---- No "onNotify" callback function specified for "startNotifying()"');
                        }
                    };
                const delegate = wrapper.peripheral.delegate;
                const key = args.serviceUUID + '/' + args.characteristicUUID;
                delegate.onNotifyCallbacks[key] = cb;
                wrapper.peripheral.setNotifyValueForCharacteristic(true, wrapper.characteristic);
                return null;
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                return Promise.reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        });
    }
    stopNotifying(args) {
        const methodName = 'stopNotifying';
        return this._getWrapper(args, 16 /* CBCharacteristicProperties.PropertyNotify */).then((wrapper) => {
            if (Trace.isEnabled()) {
                CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID} characteristicUUID:${args.characteristicUUID}`);
            }
            try {
                const peripheral = this.findPeripheral(args.peripheralUUID);
                const key = args.serviceUUID + '/' + args.characteristicUUID;
                delete peripheral.delegate.onNotifyCallbacks[key];
                peripheral.setNotifyValueForCharacteristic(false, wrapper.characteristic);
                return null;
            }
            catch (ex) {
                if (Trace.isEnabled()) {
                    CLog(CLogTypes.error, methodName, '---- error:', ex);
                }
                return Promise.reject(new BluetoothError(ex.message, {
                    stack: ex.stack,
                    nativeException: ex.nativeException,
                    method: methodName,
                    arguments: args
                }));
            }
        });
    }
    discoverServices(args) {
        const methodName = 'discoverServices';
        if (!args.peripheralUUID) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: BluetoothCommon.peripheralUUIDKey,
                arguments: args
            }));
        }
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID}`);
        }
        const pUUID = args.peripheralUUID;
        const p = this.findPeripheral(pUUID);
        if (!p) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_no_peripheral, {
                method: methodName,
                arguments: args
            }));
        }
        if (p.state !== 2 /* CBPeripheralState.Connected */) {
            const error = new BluetoothError(BluetoothCommon.msg_peripheral_not_connected, {
                method: methodName,
                arguments: args
            });
            return Promise.reject(error);
        }
        return new Promise((resolve, reject) => {
            const subD = {
                peripheralDidDiscoverServices: (peripheral, error) => {
                    const UUID = NSUUIDToString(peripheral.identifier);
                    if (UUID === pUUID) {
                        if (error) {
                            reject(new BluetoothError(error.localizedDescription, {
                                method: methodName,
                                status: error.code
                            }));
                        }
                        else {
                            const cbServices = iOSNativeHelper.collections.nsArrayToJSArray(peripheral.services);
                            resolve({
                                ios: cbServices,
                                services: cbServices.map((cbs) => ({ UUID: CBUUIDToString(cbs.UUID), name: cbs.UUID.toString() }))
                            });
                        }
                        p.delegate.removeSubDelegate(subD);
                    }
                }
            };
            p.delegate.addSubDelegate(subD);
            p.discoverServices(args.serviceUUIDs ? args.serviceUUIDs.map((s) => CBUUID.UUIDWithString(s)) : null);
        });
    }
    discoverCharacteristics(args) {
        const methodName = 'discoverCharacteristics';
        if (!args.peripheralUUID) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: BluetoothCommon.peripheralUUIDKey,
                arguments: args
            }));
        }
        if (!args.serviceUUID) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                method: methodName,
                type: BluetoothCommon.serviceUUIDKey,
                arguments: args
            }));
        }
        if (Trace.isEnabled()) {
            CLog(CLogTypes.info, methodName, `---- peripheralUUID:${args.peripheralUUID} serviceUUID:${args.serviceUUID}`);
        }
        const pUUID = args.peripheralUUID;
        const p = this.findPeripheral(pUUID);
        if (!p) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_no_peripheral, {
                method: methodName,
                arguments: args
            }));
        }
        if (p.state !== 2 /* CBPeripheralState.Connected */) {
            const error = new BluetoothError(BluetoothCommon.msg_peripheral_not_connected, {
                method: methodName,
                arguments: args
            });
            return Promise.reject(error);
        }
        const serviceUUID = CBUUID.UUIDWithString(args.serviceUUID);
        const service = this._findService(serviceUUID, p);
        if (!service) {
            return Promise.reject(new BluetoothError(BluetoothCommon.msg_no_service, {
                method: methodName,
                arguments: args
            }));
        }
        return new Promise((resolve, reject) => {
            const subD = {
                peripheralDidDiscoverCharacteristicsForServiceError: (peripheral, service, error) => {
                    const UUID = NSUUIDToString(peripheral.identifier);
                    const sUuidLong = CBUUIDToString(service.UUID);
                    const sUuidShort = shortenUuidIfAssignedNumber(sUuidLong);
                    if (Trace.isEnabled()) {
                        CLog(CLogTypes.info, `discoverCharacteristics [UUID]: ${UUID}, [pUUID]: ${pUUID}, [args.serviceUUID]: ${args.serviceUUID}, [sUuidLong]: ${sUuidLong}, [sUuidShort]: ${sUuidShort}`);
                    }
                    if (UUID === pUUID && (sUuidLong === args.serviceUUID || sUuidShort === args.serviceUUID)) {
                        if (error) {
                            reject(new BluetoothError(error.localizedDescription, {
                                method: methodName,
                                status: error.code
                            }));
                        }
                        else {
                            const cbChars = iOSNativeHelper.collections.nsArrayToJSArray(service.characteristics);
                            resolve({
                                ios: cbChars,
                                characteristics: cbChars.map((cbs) => ({
                                    UUID: CBUUIDToString(cbs.UUID),
                                    serviceUUID: CBUUIDToString(service.UUID),
                                    name: CBUUIDToString(cbs.UUID),
                                    // see serviceAndCharacteristicInfo in CBPer+Ext of Cordova plugin
                                    value: toArrayBuffer(cbs.value),
                                    properties: _getProperties(cbs),
                                    // descriptors: this._getDescriptors(characteristic), // TODO we're not currently discovering these
                                    isNotifying: cbs.isNotifying
                                    // permissions: characteristic.permissions // prolly not too useful - don't think we need this for iOS (BradMartin)
                                }))
                            });
                        }
                        p.delegate.removeSubDelegate(subD);
                    }
                }
            };
            p.delegate.addSubDelegate(subD);
            p.discoverCharacteristicsForService(args.characteristicUUIDs ? args.characteristicUUIDs.map((s) => CBUUID.UUIDWithString(s)) : null, service);
        });
    }
    _isEnabled() {
        return this._state === 5 /* CBManagerState.PoweredOn */;
    }
    _findService(UUID, peripheral) {
        for (let i = 0; i < peripheral.services?.count; i++) {
            const service = peripheral.services.objectAtIndex(i);
            // TODO this may need a different compare, see Cordova plugin's findServiceFromUUID function
            if (UUID.isEqual(service.UUID)) {
                return service;
            }
        }
        // service not found on this peripheral
        return null;
    }
    _findCharacteristic(UUID, service, property) {
        for (let i = 0; i < service.characteristics.count; i++) {
            const characteristic = service.characteristics.objectAtIndex(i);
            if (UUID.isEqual(characteristic.UUID)) {
                if (property && characteristic.properties) {
                    if (property === property) {
                        return characteristic;
                    }
                }
                else {
                    return characteristic;
                }
            }
        }
        // characteristic not found on this service
        return null;
    }
    async _getDeviceWrapper(args) {
        // prepareArgs should be called before hand
        if (!args.peripheralUUID) {
            throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                type: BluetoothCommon.peripheralUUIDKey,
                arguments: args
            });
        }
        const peripheral = this.findPeripheral(args.peripheralUUID);
        if (!peripheral) {
            throw new BluetoothError(BluetoothCommon.msg_no_peripheral, { arguments: args });
        }
        if (peripheral.state !== 2 /* CBPeripheralState.Connected */) {
            throw new BluetoothError(BluetoothCommon.msg_peripheral_not_connected, {
                arguments: args
            });
        }
        // with that all being checked, let's return a wrapper object containing all the stuff we found here
        return peripheral;
    }
    async _getWrapper(args, property) {
        // prepareArgs should be called before hand
        if (!args.peripheralUUID) {
            throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                type: BluetoothCommon.peripheralUUIDKey,
                arguments: args
            });
        }
        if (!args.serviceUUID) {
            throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                type: BluetoothCommon.serviceUUIDKey,
                arguments: args
            });
        }
        if (!args.characteristicUUID) {
            throw new BluetoothError(BluetoothCommon.msg_missing_parameter, {
                type: BluetoothCommon.characteristicUUIDKey,
                arguments: args
            });
        }
        const peripheral = await this._getDeviceWrapper(args);
        const serviceUUID = CBUUID.UUIDWithString(args.serviceUUID);
        const service = this._findService(serviceUUID, peripheral);
        if (!service) {
            throw new BluetoothError(BluetoothCommon.msg_no_service, {
                arguments: args
            });
        }
        const characteristicUUID = CBUUID.UUIDWithString(args.characteristicUUID);
        let characteristic = this._findCharacteristic(characteristicUUID, service, property);
        // Special handling for INDICATE. If charateristic with notify is not found, check for indicate.
        // if (property === CBCharacteristicPropertyNotify && !characteristic) {
        if (property === 16 /* CBCharacteristicProperties.PropertyNotify */ && !characteristic) {
            characteristic = this._findCharacteristic(characteristicUUID, service, 32 /* CBCharacteristicProperties.PropertyIndicate */);
            // characteristic = this._findCharacteristic(characteristicUUID, service, CBCharacteristicProperties.PropertyIndicate PropertyIndicate);
        }
        // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
        if (!characteristic) {
            characteristic = this._findCharacteristic(characteristicUUID, service, null);
        }
        if (!characteristic) {
            throw new BluetoothError(BluetoothCommon.msg_no_characteristic, {
                arguments: args
            });
        }
        // with that all being checked, let's return a wrapper object containing all the stuff we found here
        return {
            peripheral,
            service,
            characteristic
        };
    }
}
__decorate([
    bluetoothEnabled
], Bluetooth.prototype, "startScanning", null);
__decorate([
    bluetoothEnabled
], Bluetooth.prototype, "stopScanning", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "getDevice", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "connect", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "disconnect", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "isConnected", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "read", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "requestMtu", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "readRssi", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "write", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "writeWithoutResponse", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "startNotifying", null);
__decorate([
    prepareArgs
], Bluetooth.prototype, "stopNotifying", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "discoverServices", null);
__decorate([
    bluetoothEnabled,
    prepareArgs
], Bluetooth.prototype, "discoverCharacteristics", null);
__decorate([
    bluetoothEnabled
], Bluetooth.prototype, "_getDeviceWrapper", null);
__decorate([
    bluetoothEnabled
], Bluetooth.prototype, "_getWrapper", null);
//# sourceMappingURL=index.ios.js.map