UNPKG

@ermitsrl/bluetooth-le

Version:

Capacitor plugin for Bluetooth Low Energy

331 lines 14.6 kB
import { WebPlugin } from '@capacitor/core'; import { hexStringToDataView, mapToObject, webUUIDToString } from './conversion'; import { runWithTimeout } from './timeout'; export class BluetoothLeWeb extends WebPlugin { constructor() { super(...arguments); this.deviceMap = new Map(); this.discoveredDevices = new Map(); this.scan = null; this.DEFAULT_CONNECTION_TIMEOUT = 10000; this.onAdvertisementReceivedCallback = this.onAdvertisementReceived.bind(this); this.onDisconnectedCallback = this.onDisconnected.bind(this); this.onCharacteristicValueChangedCallback = this.onCharacteristicValueChanged.bind(this); } async initialize() { if (typeof navigator === 'undefined' || !navigator.bluetooth) { throw this.unavailable('Web Bluetooth API not available in this browser.'); } const isAvailable = await navigator.bluetooth.getAvailability(); if (!isAvailable) { throw this.unavailable('No Bluetooth radio available.'); } } async isEnabled() { // not available on web return { value: true }; } async enable() { throw this.unavailable('enable is not available on web.'); } async disable() { throw this.unavailable('disable is not available on web.'); } async startEnabledNotifications() { // not available on web } async stopEnabledNotifications() { // not available on web } async isLocationEnabled() { throw this.unavailable('isLocationEnabled is not available on web.'); } async openLocationSettings() { throw this.unavailable('openLocationSettings is not available on web.'); } async openBluetoothSettings() { throw this.unavailable('openBluetoothSettings is not available on web.'); } async openAppSettings() { throw this.unavailable('openAppSettings is not available on web.'); } async setDisplayStrings() { // not available on web } async requestDevice(options) { const filters = this.getFilters(options); const device = await navigator.bluetooth.requestDevice({ filters: filters.length ? filters : undefined, optionalServices: options === null || options === void 0 ? void 0 : options.optionalServices, acceptAllDevices: filters.length === 0, }); this.deviceMap.set(device.id, device); const bleDevice = this.getBleDevice(device); return bleDevice; } async requestLEScan(options) { this.requestBleDeviceOptions = options; const filters = this.getFilters(options); await this.stopLEScan(); this.discoveredDevices = new Map(); navigator.bluetooth.removeEventListener('advertisementreceived', this.onAdvertisementReceivedCallback); navigator.bluetooth.addEventListener('advertisementreceived', this.onAdvertisementReceivedCallback); this.scan = await navigator.bluetooth.requestLEScan({ filters: filters.length ? filters : undefined, acceptAllAdvertisements: filters.length === 0, keepRepeatedDevices: options === null || options === void 0 ? void 0 : options.allowDuplicates, }); } onAdvertisementReceived(event) { var _a, _b; const deviceId = event.device.id; this.deviceMap.set(deviceId, event.device); const isNew = !this.discoveredDevices.has(deviceId); if (isNew || ((_a = this.requestBleDeviceOptions) === null || _a === void 0 ? void 0 : _a.allowDuplicates)) { this.discoveredDevices.set(deviceId, true); const device = this.getBleDevice(event.device); const result = { device, localName: device.name, rssi: event.rssi, txPower: event.txPower, manufacturerData: mapToObject(event.manufacturerData), serviceData: mapToObject(event.serviceData), uuids: (_b = event.uuids) === null || _b === void 0 ? void 0 : _b.map(webUUIDToString), }; this.notifyListeners('onScanResult', result); } } async stopLEScan() { var _a; if ((_a = this.scan) === null || _a === void 0 ? void 0 : _a.active) { this.scan.stop(); } this.scan = null; } async getDevices(_options) { const devices = await navigator.bluetooth.getDevices(); const bleDevices = devices.map((device) => { this.deviceMap.set(device.id, device); const bleDevice = this.getBleDevice(device); return bleDevice; }); return { devices: bleDevices }; } async getConnectedDevices(_options) { const devices = await navigator.bluetooth.getDevices(); const bleDevices = devices .filter((device) => { var _a; return (_a = device.gatt) === null || _a === void 0 ? void 0 : _a.connected; }) .map((device) => { this.deviceMap.set(device.id, device); const bleDevice = this.getBleDevice(device); return bleDevice; }); return { devices: bleDevices }; } async scanNetworks(_options) { throw this.unavailable('scanNetworks is not available on web.'); } async provision(_options) { throw this.unavailable('provision is not available on web.'); } async connect(options) { var _a, _b; const device = this.getDeviceFromMap(options.deviceId); device.removeEventListener('gattserverdisconnected', this.onDisconnectedCallback); device.addEventListener('gattserverdisconnected', this.onDisconnectedCallback); const timeoutError = Symbol(); if (device.gatt === undefined) { throw new Error('No gatt server available.'); } try { const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : this.DEFAULT_CONNECTION_TIMEOUT; await runWithTimeout(device.gatt.connect(), timeout, timeoutError); } catch (error) { // cancel pending connect call, does not work yet in chromium because of a bug: // https://bugs.chromium.org/p/chromium/issues/detail?id=684073 await ((_b = device.gatt) === null || _b === void 0 ? void 0 : _b.disconnect()); if (error === timeoutError) { throw new Error('Connection timeout'); } else { throw error; } } } onDisconnected(event) { const deviceId = event.target.id; const key = `disconnected|${deviceId}`; this.notifyListeners(key, null); } async createBond(_options) { throw this.unavailable('createBond is not available on web.'); } async isBonded(_options) { throw this.unavailable('isBonded is not available on web.'); } async setPin(_options) { throw this.unavailable('setPin is not available on web.'); } async disconnect(options) { var _a; (_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === void 0 ? void 0 : _a.disconnect(); } async getServices(options) { var _a, _b; const services = (_b = (await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === void 0 ? void 0 : _a.getPrimaryServices()))) !== null && _b !== void 0 ? _b : []; const bleServices = []; for (const service of services) { const characteristics = await service.getCharacteristics(); const bleCharacteristics = []; for (const characteristic of characteristics) { bleCharacteristics.push({ uuid: characteristic.uuid, properties: this.getProperties(characteristic), descriptors: await this.getDescriptors(characteristic), }); } bleServices.push({ uuid: service.uuid, characteristics: bleCharacteristics }); } return { services: bleServices }; } async getDescriptors(characteristic) { try { const descriptors = await characteristic.getDescriptors(); return descriptors.map((descriptor) => ({ uuid: descriptor.uuid, })); } catch (_a) { return []; } } getProperties(characteristic) { return { broadcast: characteristic.properties.broadcast, read: characteristic.properties.read, writeWithoutResponse: characteristic.properties.writeWithoutResponse, write: characteristic.properties.write, notify: characteristic.properties.notify, indicate: characteristic.properties.indicate, authenticatedSignedWrites: characteristic.properties.authenticatedSignedWrites, reliableWrite: characteristic.properties.reliableWrite, writableAuxiliaries: characteristic.properties.writableAuxiliaries, }; } async getCharacteristic(options) { var _a; const service = await ((_a = this.getDeviceFromMap(options.deviceId).gatt) === null || _a === void 0 ? void 0 : _a.getPrimaryService(options === null || options === void 0 ? void 0 : options.service)); return service === null || service === void 0 ? void 0 : service.getCharacteristic(options === null || options === void 0 ? void 0 : options.characteristic); } async getDescriptor(options) { const characteristic = await this.getCharacteristic(options); return characteristic === null || characteristic === void 0 ? void 0 : characteristic.getDescriptor(options === null || options === void 0 ? void 0 : options.descriptor); } async discoverServices(_options) { throw this.unavailable('discoverServices is not available on web.'); } async readRssi(_options) { throw this.unavailable('readRssi is not available on web.'); } async read(options) { const characteristic = await this.getCharacteristic(options); const value = await (characteristic === null || characteristic === void 0 ? void 0 : characteristic.readValue()); return { value }; } async write(options) { const characteristic = await this.getCharacteristic(options); let dataView; if (typeof options.value === 'string') { dataView = hexStringToDataView(options.value); } else { dataView = options.value; } await (characteristic === null || characteristic === void 0 ? void 0 : characteristic.writeValueWithResponse(dataView)); } async writeWithoutResponse(options) { const characteristic = await this.getCharacteristic(options); let dataView; if (typeof options.value === 'string') { dataView = hexStringToDataView(options.value); } else { dataView = options.value; } await (characteristic === null || characteristic === void 0 ? void 0 : characteristic.writeValueWithoutResponse(dataView)); } async readDescriptor(options) { const descriptor = await this.getDescriptor(options); const value = await (descriptor === null || descriptor === void 0 ? void 0 : descriptor.readValue()); return { value }; } async writeDescriptor(options) { const descriptor = await this.getDescriptor(options); let dataView; if (typeof options.value === 'string') { dataView = hexStringToDataView(options.value); } else { dataView = options.value; } await (descriptor === null || descriptor === void 0 ? void 0 : descriptor.writeValue(dataView)); } async startNotifications(options) { const characteristic = await this.getCharacteristic(options); characteristic === null || characteristic === void 0 ? void 0 : characteristic.removeEventListener('characteristicvaluechanged', this.onCharacteristicValueChangedCallback); characteristic === null || characteristic === void 0 ? void 0 : characteristic.addEventListener('characteristicvaluechanged', this.onCharacteristicValueChangedCallback); await (characteristic === null || characteristic === void 0 ? void 0 : characteristic.startNotifications()); } onCharacteristicValueChanged(event) { var _a, _b; const characteristic = event.target; const key = `notification|${(_a = characteristic.service) === null || _a === void 0 ? void 0 : _a.device.id}|${(_b = characteristic.service) === null || _b === void 0 ? void 0 : _b.uuid}|${characteristic.uuid}`; this.notifyListeners(key, { value: characteristic.value, }); } async stopNotifications(options) { const characteristic = await this.getCharacteristic(options); await (characteristic === null || characteristic === void 0 ? void 0 : characteristic.stopNotifications()); } getFilters(options) { var _a; const filters = []; for (const service of (_a = options === null || options === void 0 ? void 0 : options.services) !== null && _a !== void 0 ? _a : []) { filters.push({ services: [service], name: options === null || options === void 0 ? void 0 : options.name, namePrefix: options === null || options === void 0 ? void 0 : options.namePrefix, }); } if (((options === null || options === void 0 ? void 0 : options.name) || (options === null || options === void 0 ? void 0 : options.namePrefix)) && filters.length === 0) { filters.push({ name: options.name, namePrefix: options.namePrefix, }); } return filters; } getDeviceFromMap(deviceId) { const device = this.deviceMap.get(deviceId); if (device === undefined) { throw new Error('Device not found. Call "requestDevice", "requestLEScan" or "getDevices" first.'); } return device; } getBleDevice(device) { var _a; const bleDevice = { deviceId: device.id, // use undefined instead of null if name is not available name: (_a = device.name) !== null && _a !== void 0 ? _a : undefined, }; return bleDevice; } } //# sourceMappingURL=web.js.map