@nativescript-community/ble
Version:
Connect to and interact with Bluetooth LE peripherals.
1,060 lines (1,059 loc) • 76.1 kB
JavaScript
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 { }