diff --git a/src/adapters/adapter.ts b/src/adapters/adapter.ts index cb43550b..4323e341 100644 --- a/src/adapters/adapter.ts +++ b/src/adapters/adapter.ts @@ -23,25 +23,24 @@ * SOFTWARE. */ -import { EventEmitter } from 'events'; -import { BluetoothDeviceImpl } from '../device'; -import { BluetoothRemoteGATTServiceImpl } from '../service'; -import { BluetoothRemoteGATTCharacteristicImpl } from '../characteristic'; -import { BluetoothRemoteGATTDescriptorImpl } from '../descriptor'; +import { BluetoothDevice } from '../device'; +import { BluetoothRemoteGATTService } from '../service'; +import { BluetoothRemoteGATTCharacteristic } from '../characteristic'; +import { BluetoothRemoteGATTDescriptor } from '../descriptor'; /** * @hidden */ -export interface Adapter extends EventEmitter { +export interface Adapter extends EventTarget { getEnabled: () => Promise; - startScan: (serviceUUIDs: Array, foundFn: (device: Partial) => void) => Promise; + startScan: (serviceUUIDs: Array, foundFn: (device: Partial) => 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: () => void) => Promise; diff --git a/src/adapters/simpleble-adapter.ts b/src/adapters/simpleble-adapter.ts index cc34b47c..1ddc614f 100644 --- a/src/adapters/simpleble-adapter.ts +++ b/src/adapters/simpleble-adapter.ts @@ -23,13 +23,12 @@ * SOFTWARE. */ -import { EventEmitter } from 'events'; import { Adapter as BluetoothAdapter } from './adapter'; import { BluetoothUUID } from '../uuid'; -import { BluetoothDeviceImpl } from '../device'; -import { BluetoothRemoteGATTCharacteristicImpl } from '../characteristic'; -import { BluetoothRemoteGATTServiceImpl } from '../service'; -import { BluetoothRemoteGATTDescriptorImpl } from '../descriptor'; +import { BluetoothDevice } from '../device'; +import { BluetoothRemoteGATTCharacteristic } from '../characteristic'; +import { BluetoothRemoteGATTService } from '../service'; +import { BluetoothRemoteGATTDescriptor } from '../descriptor'; import { isEnabled, getAdapters, @@ -42,7 +41,7 @@ import { /** * @hidden */ -export class SimplebleAdapter extends EventEmitter implements BluetoothAdapter { +export class SimplebleAdapter extends EventTarget implements BluetoothAdapter { private adapter: Adapter; private peripherals = new Map(); private servicesByPeripheral = new Map(); @@ -53,7 +52,7 @@ export class SimplebleAdapter extends EventEmitter implements BluetoothAdapter { private descriptors = new Map(); private charEvents = new Map void>(); - private validDevice(device: Partial, serviceUUIDs: Array): boolean { + private validDevice(device: Partial, serviceUUIDs: Array): boolean { if (serviceUUIDs.length === 0) { // Match any device return true; @@ -70,7 +69,7 @@ export class SimplebleAdapter extends EventEmitter implements BluetoothAdapter { return serviceUUIDs.some(serviceUUID => advertisedUUIDs.indexOf(serviceUUID) >= 0); } - private buildBluetoothDevice(device: Peripheral): Partial { + private buildBluetoothDevice(device: Peripheral): Partial { const name = device.identifier; const address = device.address; const rssi = device.rssi; @@ -215,7 +214,7 @@ export class SimplebleAdapter extends EventEmitter implements BluetoothAdapter { } } - public async discoverServices(id: string, serviceUUIDs?: Array): Promise>> { + public async discoverServices(id: string, serviceUUIDs?: Array): Promise>> { const peripheral = this.peripherals.get(id); if (!peripheral) { throw new Error('Peripheral not found'); @@ -234,12 +233,12 @@ export class SimplebleAdapter extends EventEmitter 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(serviceUuid: string, characteristicUUIDs?: Array): Promise>> { + public async discoverCharacteristics(serviceUuid: string, characteristicUUIDs?: Array): Promise>> { const peripheral = this.peripheralByService.get(serviceUuid); const characteristics = this.characteristicsByService.get(serviceUuid); const discovered = []; @@ -288,7 +287,7 @@ export class SimplebleAdapter extends EventEmitter implements BluetoothAdapter { return discovered; } - public async discoverDescriptors(charUuid: string, descriptorUUIDs?: Array): Promise>> { + public async discoverDescriptors(charUuid: string, descriptorUUIDs?: Array): Promise>> { const descriptors = this.descriptors.get(charUuid); const discovered = []; diff --git a/src/bluetooth.ts b/src/bluetooth.ts index 368241f0..4ac42c30 100644 --- a/src/bluetooth.ts +++ b/src/bluetooth.ts @@ -24,9 +24,9 @@ */ import { adapter, EVENT_ENABLED } from './adapters'; -import { BluetoothDeviceImpl, BluetoothDeviceEvents } from './device'; +import { BluetoothDevice, BluetoothDeviceEvents } from './device'; import { BluetoothUUID } from './uuid'; -import { EventDispatcher, DOMEvent } from './events'; +import { DOMEvent } from './events'; /** * Bluetooth Options interface @@ -54,19 +54,31 @@ export interface BluetoothOptions { } /** - * @hidden + * Events for {@link Bluetooth}. */ export interface BluetoothEvents extends BluetoothDeviceEvents { /** * Bluetooth Availability Changed event */ - availabilitychanged: Event; + availabilitychanged: BluetoothAdvertisingEvent; } /** - * Bluetooth class + * Bluetooth class. + * + * ### Events + * + * | Name | Event | Description | + * | ---- | ----- | ----------- | + * | `advertisementreceived` | {@link BluetoothAdvertisingEvent} | Advertisement received. | + * | `availabilitychanged` | {@link Event} | Bluetooth availability changed. | + * | `characteristicvaluechanged` | {@link Event} | The value of a BLE Characteristic has changed. | + * | `gattserverdisconnected` | {@link Event} | GATT server has been disconnected. | + * | `serviceadded` | {@link Event} | A new service is available. | + * | `servicechanged` | {@link Event} | An existing service has changed. | + * | `serviceremoved` | {@link Event} | A service is unavailable. | */ -export class BluetoothImpl extends EventDispatcher implements Bluetooth { +export class Bluetooth extends EventTarget { /** * Referring device for the bluetooth instance */ @@ -77,6 +89,20 @@ export class BluetoothImpl extends EventDispatcher implements B private scanner = undefined; private allowedDevices = new Set(); + public addEventListener( + type: K, + callback: (this: this, event: BluetoothEvents[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + /** @hidden */ + public addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void { + super.addEventListener(type, callback, options); + } + /** * Bluetooth constructor * @param options Bluetooth initialisation options @@ -89,7 +115,7 @@ export class BluetoothImpl extends EventDispatcher implements B this.scanTime = options.scanTime * 1000; } - adapter.on(EVENT_ENABLED, _value => { + adapter.addEventListener(EVENT_ENABLED, _value => { this.dispatchEvent(new DOMEvent(this, 'availabilitychanged')); }); } @@ -166,8 +192,8 @@ export class BluetoothImpl extends EventDispatcher implements B } } - private _onavailabilitychanged: (ev: Event) => void; - public set onavailabilitychanged(fn: (ev: Event) => void) { + private _onavailabilitychanged: (ev: BluetoothAdvertisingEvent) => void; + public set onavailabilitychanged(fn: (ev: BluetoothAdvertisingEvent) => void) { if (this._onavailabilitychanged) { this.removeEventListener('availabilitychanged', this._onavailabilitychanged); this._onavailabilitychanged = undefined; @@ -178,7 +204,7 @@ export class BluetoothImpl extends EventDispatcher implements B } } - private filterDevice(filters: Array, deviceInfo: Partial, validServices): Partial | undefined { + private filterDevice(filters: Array, deviceInfo: Partial, validServices): Partial | undefined { let valid = false; filters.forEach(filter => { @@ -310,7 +336,7 @@ export class BluetoothImpl extends EventDispatcher implements B } }, this.scanTime); - adapter.startScan(searchUUIDs, deviceInfo => { + adapter.startScan(searchUUIDs, (deviceInfo: BluetoothDevice) => { let validServices = []; const complete = (bluetoothDevice: BluetoothDevice) => { @@ -321,7 +347,7 @@ export class BluetoothImpl extends EventDispatcher implements B // filter devices if filters specified if (isFiltered(options)) { - deviceInfo = this.filterDevice(options.filters, deviceInfo, validServices); + deviceInfo = this.filterDevice(options.filters, deviceInfo, validServices) as BluetoothDevice; } if (deviceInfo) { @@ -341,7 +367,7 @@ export class BluetoothImpl extends EventDispatcher implements B _allowedServices: allowedServices }); - const bluetoothDevice = new BluetoothDeviceImpl(deviceInfo, () => this.forgetDevice(deviceInfo.id)); + const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id)); const selectFn = () => { complete.call(this, bluetoothDevice); @@ -372,14 +398,14 @@ export class BluetoothImpl extends EventDispatcher implements B resolve(devices); }, this.scanTime); - adapter.startScan([], deviceInfo => { + adapter.startScan([], (deviceInfo: BluetoothDevice) => { if (this.options?.allowAllDevices || this.allowedDevices.has(deviceInfo.id)) { Object.assign(deviceInfo, { _bluetooth: this, _allowedServices: [] }); - const bluetoothDevice = new BluetoothDeviceImpl(deviceInfo, () => this.forgetDevice(deviceInfo.id)); + const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id)); devices.push(bluetoothDevice); } }); diff --git a/src/characteristic.ts b/src/characteristic.ts index 8f49ebbd..6165eca1 100644 --- a/src/characteristic.ts +++ b/src/characteristic.ts @@ -24,15 +24,15 @@ */ import { adapter } from './adapters'; -import { BluetoothRemoteGATTDescriptorImpl } from './descriptor'; +import { BluetoothRemoteGATTDescriptor } from './descriptor'; import { BluetoothUUID } from './uuid'; -import { BluetoothRemoteGATTServiceImpl } from './service'; -import { EventDispatcher, DOMEvent } from './events'; +import { BluetoothRemoteGATTService } from './service'; +import { DOMEvent } from './events'; const isView = (source: ArrayBuffer | ArrayBufferView): source is ArrayBufferView => (source as ArrayBufferView).buffer !== undefined; /** - * @hidden + * Events for {@link BluetoothRemoteGATTCharacteristic}. */ export interface CharacteristicEvents { /** @@ -42,14 +42,20 @@ export interface CharacteristicEvents { } /** - * Bluetooth Remote GATT Characteristic class + * Bluetooth Remote GATT Characteristic class. + * + * ### Events + * + * | Name | Event | Description | + * | ---- | ----- | ----------- | + * | `characteristicvaluechanged` | {@link Event} | The value of a BLE Characteristic has changed. | */ -export class BluetoothRemoteGATTCharacteristicImpl extends EventDispatcher implements BluetoothRemoteGATTCharacteristic { +export class BluetoothRemoteGATTCharacteristic extends EventTarget { /** * The service the characteristic is related to */ - public readonly service: BluetoothRemoteGATTServiceImpl = undefined; + public readonly service: BluetoothRemoteGATTService = undefined; /** * The unique identifier of the characteristic @@ -88,7 +94,7 @@ export class BluetoothRemoteGATTCharacteristicImpl extends EventDispatcher) { + constructor(init: Partial) { super(); this.service = init.service; @@ -99,6 +105,28 @@ export class BluetoothRemoteGATTCharacteristicImpl extends EventDispatcher( + type: K, + callback: (this: this, event: CharacteristicEvents[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + /** @hidden */ + public addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void { + super.addEventListener(type, callback, options); + } + private setValue(value?: DataView, emit?: boolean) { this._value = value; if (emit) { @@ -147,7 +175,7 @@ export class BluetoothRemoteGATTCharacteristicImpl extends EventDispatcher) { + super(); this.characteristic = init.characteristic; this.uuid = init.uuid; this._value = init.value; diff --git a/src/device.ts b/src/device.ts index 11fb93e7..99b1d64b 100644 --- a/src/device.ts +++ b/src/device.ts @@ -23,12 +23,11 @@ * SOFTWARE. */ -import { BluetoothRemoteGATTServerImpl } from './server'; +import { BluetoothRemoteGATTServer } from './server'; import { ServiceEvents } from './service'; -import { EventDispatcher } from './events'; /** - * @hidden + * Events for {@link BluetoothDevice}. */ export interface BluetoothDeviceEvents extends ServiceEvents { /** @@ -42,9 +41,20 @@ export interface BluetoothDeviceEvents extends ServiceEvents { } /** - * Bluetooth Device class + * Bluetooth Device class. + * + * ### Events + * + * | Name | Event | Description | + * | ---- | ----- | ----------- | + * | `advertisementreceived` | {@link BluetoothAdvertisingEvent} | Advertisement received. | + * | `characteristicvaluechanged` | {@link Event} | The value of a BLE Characteristic has changed. | + * | `gattserverdisconnected` | {@link Event} | GATT server has been disconnected. | + * | `serviceadded` | {@link Event} | A new service is available. | + * | `servicechanged` | {@link Event} | An existing service has changed. | + * | `serviceremoved` | {@link Event} | A service is unavailable. | */ -export class BluetoothDeviceImpl extends EventDispatcher implements BluetoothDevice { +export class BluetoothDevice extends EventTarget { /** * The unique identifier of the device @@ -91,6 +101,21 @@ export class BluetoothDeviceImpl extends EventDispatcher */ public readonly _serviceUUIDs: Array = []; + /** Events. */ + public addEventListener( + type: K, + callback: (this: this, event: BluetoothDeviceEvents[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + /** @hidden */ + public addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void { + super.addEventListener(type, callback, options); + } + private _oncharacteristicvaluechanged: (ev: Event) => void; public set oncharacteristicvaluechanged(fn: (ev: Event) => void) { if (this._oncharacteristicvaluechanged) { @@ -167,7 +192,7 @@ export class BluetoothDeviceImpl extends EventDispatcher * Device constructor * @param init A partial class to initialise values */ - constructor(init: Partial, private forgetFn: () => void) { + constructor(init: Partial, private forgetFn: () => void) { super(); this.id = init.id; @@ -180,7 +205,7 @@ export class BluetoothDeviceImpl extends EventDispatcher this._serviceUUIDs = init._serviceUUIDs; if (!this.name) this.name = `Unknown or Unsupported Device (${this.id})`; - if (!this.gatt) this.gatt = new BluetoothRemoteGATTServerImpl(this); + if (!this.gatt) this.gatt = new BluetoothRemoteGATTServer(this); } /** diff --git a/src/events.ts b/src/events.ts index 674f520e..bcecb00d 100644 --- a/src/events.ts +++ b/src/events.ts @@ -23,40 +23,6 @@ * SOFTWARE. */ -import { EventEmitter } from 'events'; - -/** - * @hidden - */ -export class EventDispatcher { - protected emitter = new EventEmitter(); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private isEventListenerObject = (listener: any): listener is EventListenerObject => (listener as EventListenerObject).handleEvent !== undefined; - - public addEventListener(type: K, listener: (this: this, ev: T[K]) => void): void; - public addEventListener(type: K, listener: EventListener): void; - public addEventListener(type: string, listener: EventListener | ((ev: T[K]) => void)): void { - if (listener) { - const handler = this.isEventListenerObject(listener) ? listener.handleEvent : listener; - this.emitter.addListener(type, handler); - } - } - - public removeEventListener(type: K, callback: (this: this, ev: T[K]) => void): void; - public removeEventListener(type: K, callback: EventListener): void; - public removeEventListener(type: K, callback: EventListener | ((ev: T[K]) => void)): void { - if (callback) { - const handler = this.isEventListenerObject(callback) ? callback.handleEvent : callback; - this.emitter.removeListener(type as string, handler); - } - } - - public dispatchEvent(event: Event): boolean { - return this.emitter.emit(event.type, event); - } -} - /** * @hidden */ diff --git a/src/index.ts b/src/index.ts index cbdcf322..a4b0b1b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,17 +23,18 @@ * SOFTWARE. */ -import { BluetoothImpl, BluetoothOptions } from './bluetooth'; +import { Bluetooth, BluetoothOptions } from './bluetooth'; +export type { BluetoothEvents } from './bluetooth'; /** * Default bluetooth instance synonymous with `navigator.bluetooth` */ -export const bluetooth = new BluetoothImpl(); +export const bluetooth = new Bluetooth(); /** * Bluetooth class for creating new instances */ -export { BluetoothImpl as Bluetooth, BluetoothOptions }; +export { Bluetooth, BluetoothOptions }; /** * Helper methods and enums diff --git a/src/server.ts b/src/server.ts index 93df7926..167d25b6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -25,19 +25,19 @@ import { BluetoothUUID } from './uuid'; import { adapter } from './adapters'; -import { BluetoothRemoteGATTServiceImpl } from './service'; +import { BluetoothRemoteGATTService } from './service'; import { DOMEvent } from './events'; -import { BluetoothDeviceImpl } from './device'; +import { BluetoothDevice } from './device'; /** * Bluetooth Remote GATT Server class */ -export class BluetoothRemoteGATTServerImpl implements BluetoothRemoteGATTServer { +export class BluetoothRemoteGATTServer extends EventTarget { /** * The device the gatt server is related to */ - public readonly device: BluetoothDeviceImpl = undefined; + public readonly device: BluetoothDevice = undefined; private _connected = false; /** @@ -54,7 +54,8 @@ export class BluetoothRemoteGATTServerImpl implements BluetoothRemoteGATTServer * Server constructor * @param device Device the gatt server relates to */ - constructor(device: BluetoothDeviceImpl) { + constructor(device: BluetoothDevice) { + super(); this.device = device; this.handle = this.device.id; } @@ -125,7 +126,7 @@ export class BluetoothRemoteGATTServerImpl implements BluetoothRemoteGATTServer Object.assign(serviceInfo, { device: this.device }); - return new BluetoothRemoteGATTServiceImpl(serviceInfo); + return new BluetoothRemoteGATTService(serviceInfo); }); } diff --git a/src/service.ts b/src/service.ts index b758454a..7d8120d8 100644 --- a/src/service.ts +++ b/src/service.ts @@ -24,13 +24,13 @@ */ import { adapter } from './adapters'; -import { BluetoothDeviceImpl } from './device'; -import { BluetoothRemoteGATTCharacteristicImpl, CharacteristicEvents } from './characteristic'; +import { BluetoothDevice } from './device'; +import { BluetoothRemoteGATTCharacteristic, CharacteristicEvents } from './characteristic'; import { BluetoothUUID } from './uuid'; -import { EventDispatcher, DOMEvent } from './events'; +import { DOMEvent } from './events'; /** - * @hidden + * Events for {@link BluetoothRemoteGATTService}. */ export interface ServiceEvents extends CharacteristicEvents { /** @@ -48,14 +48,23 @@ export interface ServiceEvents extends CharacteristicEvents { } /** - * Bluetooth Remote GATT Service class + * Bluetooth Remote GATT Service class. + * + * ### Events + * + * | Name | Event | Description | + * | ---- | ----- | ----------- | + * | `characteristicvaluechanged` | {@link Event} | The value of a BLE Characteristic has changed. | + * | `serviceadded` | {@link Event} | A new service is available. | + * | `servicechanged` | {@link Event} | An existing service has changed. | + * | `serviceremoved` | {@link Event} | A service is unavailable. | */ -export class BluetoothRemoteGATTServiceImpl extends EventDispatcher implements BluetoothRemoteGATTService { +export class BluetoothRemoteGATTService extends EventTarget { /** * The device the service is related to */ - public readonly device: BluetoothDeviceImpl = undefined; + public readonly device: BluetoothDevice = undefined; /** * The unique identifier of the service @@ -71,6 +80,20 @@ export class BluetoothRemoteGATTServiceImpl extends EventDispatcher = undefined; private characteristics: Array = undefined; + public addEventListener( + type: K, + callback: (this: this, event: ServiceEvents[K]) => void, + options?: boolean | AddEventListenerOptions + ): void; + /** @hidden */ + public addEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void { + super.addEventListener(type, callback, options); + } + private _oncharacteristicvaluechanged: (ev: Event) => void; public set oncharacteristicvaluechanged(fn: (ev: Event) => void) { if (this._oncharacteristicvaluechanged) { @@ -123,7 +146,7 @@ export class BluetoothRemoteGATTServiceImpl extends EventDispatcher) { + constructor(init: Partial) { super(); this.device = init.device; @@ -175,7 +198,7 @@ export class BluetoothRemoteGATTServiceImpl extends EventDispatcher