diff --git a/src/adapters/adapter.ts b/src/adapters/adapter.ts index 2ee53025..17b991e0 100644 --- a/src/adapters/adapter.ts +++ b/src/adapters/adapter.ts @@ -23,26 +23,45 @@ * SOFTWARE. */ -import type { BluetoothDevice } from '../device'; -import type { BluetoothRemoteGATTService } from '../service'; -import type { BluetoothRemoteGATTCharacteristic } from '../characteristic'; -import type { BluetoothRemoteGATTDescriptor } from '../descriptor'; +export interface BluetoothDeviceInit { + id: string; + name: string; + _serviceUUIDs: Array; + _adData: Partial; +} + +export interface BluetoothRemoteGATTServiceInit { + _handle: string; + uuid: string; + isPrimary: boolean; +} + +export interface BluetoothRemoteGATTCharacteristicInit { + _handle: string; + uuid: string; + properties: BluetoothCharacteristicProperties; +} + +export interface BluetoothRemoteGATTDescriptorInit { + _handle: string; + uuid: string; +} /** * @hidden */ export interface Adapter extends EventTarget { getEnabled: () => Promise; - getAdapters: () => Array<{ index: number, address: string, active: boolean}>; + getAdapters: () => Array<{ index: number, address: string, active: boolean }>; useAdapter: (index: number) => void; - startScan: (serviceUUIDs: Array, foundFn: (device: Partial) => void) => Promise; + startScan: (serviceUUIDs: Array, foundFn: (device: BluetoothDeviceInit) => void) => Promise; stopScan: () => void; connect: (handle: string, disconnectFn?: () => void) => Promise; disconnect: (handle: string) => Promise; - discoverServices: (handle: string, serviceUUIDs?: Array) => Promise>>; - discoverIncludedServices: (handle: string, serviceUUIDs?: Array) => Promise>>; - discoverCharacteristics: (handle: string, characteristicUUIDs?: Array) => Promise>>; - discoverDescriptors: (handle: string, descriptorUUIDs?: Array) => Promise>>; + discoverServices: (handle: string, serviceUUIDs?: Array) => Promise>; + discoverIncludedServices: (handle: string, serviceUUIDs?: Array) => Promise>; + discoverCharacteristics: (handle: string, characteristicUUIDs?: Array) => Promise>; + discoverDescriptors: (handle: string, descriptorUUIDs?: Array) => Promise>; readCharacteristic: (handle: string) => Promise; writeCharacteristic: (handle: string, value: DataView, withoutResponse?: boolean) => Promise; enableNotify: (handle: string, notifyFn: (value: DataView) => void) => Promise; diff --git a/src/adapters/simpleble-adapter.ts b/src/adapters/simpleble-adapter.ts index 65b7c483..e488ebed 100644 --- a/src/adapters/simpleble-adapter.ts +++ b/src/adapters/simpleble-adapter.ts @@ -23,12 +23,8 @@ * SOFTWARE. */ -import { Adapter as BluetoothAdapter } from './adapter'; +import { Adapter as BluetoothAdapter, BluetoothDeviceInit, BluetoothRemoteGATTServiceInit, BluetoothRemoteGATTCharacteristicInit, BluetoothRemoteGATTDescriptorInit } from './adapter'; import { BluetoothUUID } from '../uuid'; -import { BluetoothDevice } from '../device'; -import { BluetoothRemoteGATTCharacteristic } from '../characteristic'; -import { BluetoothRemoteGATTService } from '../service'; -import { BluetoothRemoteGATTDescriptor } from '../descriptor'; import { isEnabled, getAdapters as simpleBleAdapters, @@ -133,10 +129,23 @@ class PeripheralHandles { } const peripheralHandle = this.parents.get(serviceHandle); + if (!peripheralHandle) { + throw new Error('Peripheral not found for service'); + } + + const peripheral = this.peripherals.get(peripheralHandle); + if (!peripheral) { + throw new Error('Peripheral not found for service'); + } + + const service = this.services.get(serviceHandle); + if (!service) { + throw new Error('Service not found'); + } return { - peripheral: this.peripherals.get(peripheralHandle!)!, - service: this.services.get(serviceHandle)!, + peripheral, + service, characteristics }; } @@ -159,25 +168,78 @@ class PeripheralHandles { public getCharacteristicGraph(characteristicHandle: string): { peripheral: Peripheral, service: Service, characteristic: Characteristic } { const serviceHandle = this.parents.get(characteristicHandle); - const peripheralHandle = this.parents.get(serviceHandle!); + if (!serviceHandle) { + throw new Error('Service not found for characteristic'); + } + + const peripheralHandle = this.parents.get(serviceHandle); + if (!peripheralHandle) { + throw new Error('Peripheral not found for characteristic'); + } + + const peripheral = this.peripherals.get(peripheralHandle); + if (!peripheral) { + throw new Error('Peripheral not found for characteristic'); + } + + const service = this.services.get(serviceHandle); + if (!service) { + throw new Error('Service not found for characteristic'); + } + + const characteristic = this.characteristics.get(characteristicHandle); + if (!characteristic) { + throw new Error('Characteristic not found'); + } return { - peripheral: this.peripherals.get(peripheralHandle!)!, - service: this.services.get(serviceHandle!)!, - characteristic: this.characteristics.get(characteristicHandle)! + peripheral, + service, + characteristic }; } public getDescriptorGraph(descriptorHandle: string): { peripheral: Peripheral, service: Service, characteristic: Characteristic, descriptor: Descriptor } { const characteristicHandle = this.parents.get(descriptorHandle); - const serviceHandle = this.parents.get(characteristicHandle!); - const peripheralHandle = this.parents.get(serviceHandle!); + if (!characteristicHandle) { + throw new Error('Characteristic not found for descriptor'); + } + + const serviceHandle = this.parents.get(characteristicHandle); + if (!serviceHandle) { + throw new Error('Service not found for descriptor'); + } + + const peripheralHandle = this.parents.get(serviceHandle); + if (!peripheralHandle) { + throw new Error('Peripheral not found for descriptor'); + } + + const peripheral = this.peripherals.get(peripheralHandle); + if (!peripheral) { + throw new Error('Peripheral not found for descriptor'); + } + + const service = this.services.get(serviceHandle); + if (!service) { + throw new Error('Service not found for descriptor'); + } + + const characteristic = this.characteristics.get(characteristicHandle); + if (!characteristic) { + throw new Error('Characteristic not found for descriptor'); + } + + const descriptor = this.descriptors.get(descriptorHandle); + if (!descriptor) { + throw new Error('Descriptor not found'); + } return { - peripheral: this.peripherals.get(peripheralHandle!)!, - service: this.services.get(serviceHandle!)!, - characteristic: this.characteristics.get(characteristicHandle!)!, - descriptor: this.descriptors.get(descriptorHandle)! + peripheral, + service, + characteristic, + descriptor }; } } @@ -190,7 +252,7 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { private peripherals = new Map(); private handles = new PeripheralHandles(this.peripherals); - private validDevice(device: Partial, serviceUUIDs: Array): boolean { + private validDevice(device: BluetoothDeviceInit, serviceUUIDs: Array): boolean { if (serviceUUIDs.length === 0) { // Match any device return true; @@ -207,13 +269,12 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { return serviceUUIDs.some(serviceUUID => advertisedUUIDs.indexOf(serviceUUID) >= 0); } - private buildBluetoothDevice(device: Peripheral): Partial { + private buildBluetoothDevice(device: Peripheral): BluetoothDeviceInit { const name = device.identifier; const address = device.address; const rssi = device.rssi; const txPower = device.txPower; - const mtu = device.mtu; const id = address || `${name}`; @@ -239,7 +300,6 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { _adData: { rssi, txPower, - mtu, serviceData, manufacturerData } @@ -269,7 +329,7 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { this.adapter = selected; } - public async startScan(serviceUUIDs: Array, foundFn: (device: Partial) => void): Promise { + public async startScan(serviceUUIDs: Array, foundFn: (device: BluetoothDeviceInit) => void): Promise { if (this.state === false) { throw new Error('adapter not enabled'); } @@ -282,9 +342,9 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { this.adapter.setCallbackOnScanFound(peripheral => { const device = this.buildBluetoothDevice(peripheral); if (this.validDevice(device, serviceUUIDs)) { - if (!foundPeripherals.includes(device.id!)) { - foundPeripherals.push(device.id!); - this.peripherals.set(device.id!, peripheral); + if (!foundPeripherals.includes(device.id)) { + foundPeripherals.push(device.id); + this.peripherals.set(device.id, peripheral); // Only call the found function the first time we find a valid device foundFn(device); } @@ -343,10 +403,10 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { this.handles.deleteHandles(peripheral); } - public async discoverServices(handle: string, serviceUUIDs?: Array): Promise>> { + public async discoverServices(handle: string, serviceUUIDs?: Array): Promise> { const services = this.handles.getServices(handle); - const discovered: Partial[] = []; + const discovered: BluetoothRemoteGATTServiceInit[] = []; for (const [handle, service] of Object.entries(services)) { if (!serviceUUIDs || serviceUUIDs.length === 0 || serviceUUIDs.indexOf(service.uuid) >= 0) { discovered.push({ @@ -360,15 +420,15 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { return discovered; } - public async discoverIncludedServices(_handle: string, _serviceUUIDs?: Array): Promise>> { + public async discoverIncludedServices(_handle: string, _serviceUUIDs?: Array): Promise> { // Currently not implemented return []; } - public async discoverCharacteristics(handle: string, characteristicUUIDs?: Array): Promise>> { + public async discoverCharacteristics(handle: string, characteristicUUIDs?: Array): Promise> { const { peripheral, service, characteristics } = this.handles.getCharacteristics(handle); - const discovered: Partial[] = []; + const discovered: BluetoothRemoteGATTCharacteristicInit[] = []; for (const [handle, characteristic] of Object.entries(characteristics)) { const charUUID = BluetoothUUID.canonicalUUID(characteristic.uuid); @@ -395,8 +455,10 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { if (characteristic.canIndicate) { peripheral.indicate(service.uuid, charUUID, data => { if (this.handles.characteristicEvents.has(handle)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.handles.characteristicEvents.get(handle)!(new DataView(data.buffer)); + const event = this.handles.characteristicEvents.get(handle); + if (event) { + event(new DataView(data.buffer)); + } } }); } @@ -404,8 +466,10 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { if (characteristic.canNotify) { peripheral.notify(service.uuid, charUUID, data => { if (this.handles.characteristicEvents.has(handle)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.handles.characteristicEvents.get(handle)!(new DataView(data.buffer)); + const event = this.handles.characteristicEvents.get(handle); + if (event) { + event(new DataView(data.buffer)); + } } }); } @@ -415,9 +479,9 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { return discovered; } - public async discoverDescriptors(handle: string, descriptorUUIDs?: Array): Promise>> { + public async discoverDescriptors(handle: string, descriptorUUIDs?: Array): Promise> { const descriptors = this.handles.getDescriptors(handle); - const discovered = []; + const discovered = new Array(); for (const [handle, descriptor] of Object.entries(descriptors)) { const descUUID = BluetoothUUID.canonicalUUID(descriptor); diff --git a/src/bluetooth.ts b/src/bluetooth.ts index 8cfa3329..fe738d5e 100644 --- a/src/bluetooth.ts +++ b/src/bluetooth.ts @@ -24,6 +24,7 @@ */ import { adapter, EVENT_ENABLED } from './adapters'; +import { BluetoothDeviceInit } from './adapters/adapter'; import { BluetoothDevice } from './device'; import { BluetoothUUID } from './uuid'; @@ -190,7 +191,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth { } } - private filterDevice(filters: Array, deviceInfo: Partial, validServices: string[]): Partial | undefined { + private filterDevice(filters: Array, deviceInfo: BluetoothDeviceInit, validServices: string[]): BluetoothDeviceInit | undefined { let valid = false; filters.forEach(filter => { @@ -207,7 +208,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth { if (filter.services) { const serviceUUIDs = filter.services.map(BluetoothUUID.getService); const servicesValid = serviceUUIDs.every(serviceUUID => { - return (deviceInfo._serviceUUIDs!.indexOf(serviceUUID) > -1); + return (deviceInfo._serviceUUIDs.indexOf(serviceUUID) > -1); }); if (!servicesValid) return; @@ -216,7 +217,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth { // Service Data if (filter.serviceData) { - if (!deviceInfo._adData?.serviceData) return; + if (!deviceInfo._adData.serviceData) return; const services = [...deviceInfo._adData.serviceData.keys()]; for (const entry of filter.serviceData) { if (!services.includes(entry.service)) return; @@ -225,7 +226,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth { // Manufacturer Data if (filter.manufacturerData) { - if (!deviceInfo._adData?.manufacturerData) return; + if (!deviceInfo._adData.manufacturerData) return; const manufacturers = [...deviceInfo._adData.manufacturerData.keys()]; for (const entry of filter.manufacturerData) { if (!manufacturers.includes(entry.companyIdentifier)) return; @@ -351,12 +352,8 @@ class BluetoothImpl extends EventTarget implements Bluetooth { const allowedServices = validServices.filter((item, index, array) => { return array.indexOf(item) === index; }); - Object.assign(deviceInfo, { - _bluetooth: this, - _allowedServices: allowedServices - }); - const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id!)); + const bluetoothDevice = new BluetoothDevice(deviceInfo, this, allowedServices, () => this.forgetDevice(deviceInfo.id)); const selectFn = () => { complete.call(this, bluetoothDevice); @@ -388,13 +385,8 @@ class BluetoothImpl extends EventTarget implements Bluetooth { }, this.scanTime); adapter.startScan([], deviceInfo => { - if (this.options?.allowAllDevices || this.allowedDevices.has(deviceInfo.id!)) { - Object.assign(deviceInfo, { - _bluetooth: this, - _allowedServices: [] - }); - - const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id!)); + if (this.options.allowAllDevices || this.allowedDevices.has(deviceInfo.id)) { + const bluetoothDevice = new BluetoothDevice(deviceInfo, this, [], () => this.forgetDevice(deviceInfo.id)); devices.push(bluetoothDevice); } }); diff --git a/src/characteristic.ts b/src/characteristic.ts index a4d0e573..ad29ecf6 100644 --- a/src/characteristic.ts +++ b/src/characteristic.ts @@ -27,6 +27,7 @@ import { adapter } from './adapters'; import { BluetoothRemoteGATTDescriptor } from './descriptor'; import { BluetoothUUID } from './uuid'; import { BluetoothRemoteGATTService } from './service'; +import { BluetoothRemoteGATTCharacteristicInit } from './adapters/adapter'; const isView = (source: ArrayBuffer | ArrayBufferView): source is ArrayBufferView => (source as ArrayBufferView).buffer !== undefined; @@ -56,7 +57,7 @@ class BluetoothRemoteGATTCharacteristicImpl extends EventTarget implements Bluet */ public readonly properties: BluetoothCharacteristicProperties; - private _value: DataView; + private _value: DataView = new DataView(new ArrayBuffer(0)); /** * The value of the characteristic */ @@ -86,14 +87,13 @@ class BluetoothRemoteGATTCharacteristicImpl extends EventTarget implements Bluet * Characteristic constructor * @param init A partial class to initialise values */ - constructor(init: Partial) { + constructor(init: BluetoothRemoteGATTCharacteristicInit, service: BluetoothRemoteGATTService) { super(); - this.service = init.service!; - this.uuid = init.uuid!; - this.properties = init.properties!; - this._value = init.value!; - this._handle = init._handle!; + this.service = service; + this.uuid = init.uuid; + this.properties = init.properties; + this._handle = init._handle; } private setValue(value?: DataView, emit?: boolean) { @@ -143,10 +143,7 @@ class BluetoothRemoteGATTCharacteristicImpl extends EventTarget implements Bluet if (!this.descriptors) { const descriptors = await adapter.discoverDescriptors(this._handle); this.descriptors = descriptors.map(descriptorInfo => { - Object.assign(descriptorInfo, { - characteristic: this - }); - return new BluetoothRemoteGATTDescriptor(descriptorInfo); + return new BluetoothRemoteGATTDescriptor(descriptorInfo, this); }); } @@ -228,7 +225,7 @@ class BluetoothRemoteGATTCharacteristicImpl extends EventTarget implements Bluet * Start notifications of changes for the characteristic * @returns Promise containing the characteristic */ - public async startNotifications(): Promise { + public async startNotifications(): Promise { if (!this.service.device.gatt.connected) { throw new Error('startNotifications error: device not connected'); } @@ -244,7 +241,7 @@ class BluetoothRemoteGATTCharacteristicImpl extends EventTarget implements Bluet * Stop notifications of changes for the characteristic * @returns Promise containing the characteristic */ - public async stopNotifications(): Promise { + public async stopNotifications(): Promise { if (!this.service.device.gatt.connected) { throw new Error('stopNotifications error: device not connected'); } diff --git a/src/descriptor.ts b/src/descriptor.ts index b67eaca1..7c4ff583 100644 --- a/src/descriptor.ts +++ b/src/descriptor.ts @@ -24,6 +24,7 @@ */ import { adapter } from './adapters'; +import { BluetoothRemoteGATTDescriptorInit } from './adapters/adapter'; /** * Bluetooth Remote GATT Descriptor class @@ -40,7 +41,7 @@ class BluetoothRemoteGATTDescriptorImpl implements BluetoothRemoteGATTDescriptor */ public readonly uuid: string; - private _value: DataView; + private _value: DataView = new DataView(new ArrayBuffer(0)); /** * The value of the descriptor */ @@ -57,11 +58,10 @@ class BluetoothRemoteGATTDescriptorImpl implements BluetoothRemoteGATTDescriptor * Descriptor constructor * @param init A partial class to initialise values */ - constructor(init: Partial) { - this.characteristic = init.characteristic!; - this.uuid = init.uuid!; - this._value = init.value!; - this._handle = init._handle!; + constructor(init: BluetoothRemoteGATTDescriptorInit, characteristic: BluetoothRemoteGATTCharacteristic) { + this.characteristic = characteristic; + this.uuid = init.uuid; + this._handle = init._handle; } /** diff --git a/src/device.ts b/src/device.ts index 508edb36..4e0ec82e 100644 --- a/src/device.ts +++ b/src/device.ts @@ -23,6 +23,7 @@ * SOFTWARE. */ +import { BluetoothDeviceInit } from './adapters/adapter'; import { BluetoothRemoteGATTServer } from './server'; /** @@ -163,20 +164,18 @@ class BluetoothDeviceImpl extends EventTarget implements BluetoothDevice { * Device constructor * @param init A partial class to initialise values */ - constructor(init: Partial, private forgetFn: () => void) { + constructor(init: BluetoothDeviceInit, bluetooth: Bluetooth, allowedServices: string[], private forgetFn: () => void) { super(); - this.id = init.id!; - this.name = init.name!; - this.gatt = init.gatt!; + this.id = init.id; + this.name = init.name || `Unknown or Unsupported Device (${this.id})`; - this._adData = init._adData!; - this._bluetooth = init._bluetooth!; - this._allowedServices = init._allowedServices!; - this._serviceUUIDs = init._serviceUUIDs!; + this._adData = init._adData; + this._bluetooth = bluetooth; + this._allowedServices = allowedServices; + this._serviceUUIDs = init._serviceUUIDs; - if (!this.name) this.name = `Unknown or Unsupported Device (${this.id})`; - if (!this.gatt) this.gatt = new BluetoothRemoteGATTServer(this); + this.gatt = new BluetoothRemoteGATTServer(this); } /** diff --git a/src/server.ts b/src/server.ts index 6219c07b..a159263b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -124,10 +124,7 @@ class BluetoothRemoteGATTServerImpl implements BluetoothRemoteGATTServer { if (!this.services) { const services = await adapter.discoverServices(this._handle, this.device._allowedServices); this.services = services.map(serviceInfo => { - Object.assign(serviceInfo, { - device: this.device - }); - return new BluetoothRemoteGATTService(serviceInfo); + return new BluetoothRemoteGATTService(serviceInfo, this.device); }); } diff --git a/src/service.ts b/src/service.ts index d37521c8..55c1c109 100644 --- a/src/service.ts +++ b/src/service.ts @@ -27,6 +27,7 @@ import { adapter } from './adapters'; import { BluetoothDevice } from './device'; import { BluetoothRemoteGATTCharacteristic } from './characteristic'; import { BluetoothUUID } from './uuid'; +import { BluetoothRemoteGATTServiceInit } from './adapters/adapter'; /** * Bluetooth Remote GATT Service class @@ -117,13 +118,13 @@ class BluetoothRemoteGATTServiceImpl extends EventTarget implements BluetoothRem * Service constructor * @param init A partial class to initialise values */ - constructor(init: Partial) { + constructor(init: BluetoothRemoteGATTServiceInit, device: BluetoothDevice) { super(); - this.device = init.device!; - this.uuid = init.uuid!; - this.isPrimary = init.isPrimary!; - this._handle = init._handle!; + this.device = device; + this.uuid = init.uuid; + this.isPrimary = init.isPrimary; + this._handle = init._handle; this.dispatchEvent(new CustomEvent('serviceadded', { bubbles: true })); this.device.dispatchEvent(new CustomEvent('serviceadded', { bubbles: true })); @@ -165,10 +166,7 @@ class BluetoothRemoteGATTServiceImpl extends EventTarget implements BluetoothRem if (!this.characteristics) { const characteristics = await adapter.discoverCharacteristics(this._handle); this.characteristics = characteristics.map(characteristicInfo => { - Object.assign(characteristicInfo, { - service: this - }); - return new BluetoothRemoteGATTCharacteristic(characteristicInfo); + return new BluetoothRemoteGATTCharacteristic(characteristicInfo, this); }); } @@ -223,10 +221,7 @@ class BluetoothRemoteGATTServiceImpl extends EventTarget implements BluetoothRem if (!this.services) { const services = await adapter.discoverIncludedServices(this._handle, this.device._allowedServices); this.services = services.map(serviceInfo => { - Object.assign(serviceInfo, { - device: this.device - }); - return new BluetoothRemoteGATTServiceImpl(serviceInfo); + return new BluetoothRemoteGATTServiceImpl(serviceInfo, this.device); }); } diff --git a/tsconfig.json b/tsconfig.json index 34175eca..8ac3cdd8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "ES2017", - "module": "CommonJS", + "target": "es5", + "alwaysStrict": true, "strict": true, "downlevelIteration": true, "noImplicitReturns": true,