UNPKG

webbluetooth

Version:

Node.js implementation of the Web Bluetooth Specification

436 lines 18.2 kB
"use strict"; /* * Node Web Bluetooth * Copyright (c) 2017 Rob Moran * * The MIT License (MIT) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAdapters = exports.BluetoothImpl = void 0; var adapters_1 = require("./adapters"); var device_1 = require("./device"); var uuid_1 = require("./uuid"); var events_1 = require("./events"); /** * Bluetooth class */ var BluetoothImpl = /** @class */ (function (_super) { __extends(BluetoothImpl, _super); /** * Bluetooth constructor * @param options Bluetooth initialisation options */ function BluetoothImpl(options) { if (options === void 0) { options = {}; } var _this = _super.call(this) || this; _this.options = options; _this.deviceFound = undefined; _this.scanTime = 10.24 * 1000; _this.scanner = undefined; _this.allowedDevices = new Set(); _this.referringDevice = options.referringDevice; _this.deviceFound = options.deviceFound; if (options.scanTime) { _this.scanTime = options.scanTime * 1000; } if (typeof options.adapterIndex === 'number') { adapters_1.adapter.useAdapter(options.adapterIndex); } adapters_1.adapter.on(adapters_1.EVENT_ENABLED, function (_value) { _this.dispatchEvent(new events_1.DOMEvent(_this, 'availabilitychanged')); }); return _this; } Object.defineProperty(BluetoothImpl.prototype, "oncharacteristicvaluechanged", { set: function (fn) { if (this._oncharacteristicvaluechanged) { this.removeEventListener('characteristicvaluechanged', this._oncharacteristicvaluechanged); this._oncharacteristicvaluechanged = undefined; } if (fn) { this._oncharacteristicvaluechanged = fn; this.addEventListener('characteristicvaluechanged', this._oncharacteristicvaluechanged); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "onserviceadded", { set: function (fn) { if (this._onserviceadded) { this.removeEventListener('serviceadded', this._onserviceadded); this._onserviceadded = undefined; } if (fn) { this._onserviceadded = fn; this.addEventListener('serviceadded', this._onserviceadded); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "onservicechanged", { set: function (fn) { if (this._onservicechanged) { this.removeEventListener('servicechanged', this._onservicechanged); this._onservicechanged = undefined; } if (fn) { this._onservicechanged = fn; this.addEventListener('servicechanged', this._onservicechanged); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "onserviceremoved", { set: function (fn) { if (this._onserviceremoved) { this.removeEventListener('serviceremoved', this._onserviceremoved); this._onserviceremoved = undefined; } if (fn) { this._onserviceremoved = fn; this.addEventListener('serviceremoved', this._onserviceremoved); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "ongattserverdisconnected", { set: function (fn) { if (this._ongattserverdisconnected) { this.removeEventListener('gattserverdisconnected', this._ongattserverdisconnected); this._ongattserverdisconnected = undefined; } if (fn) { this._ongattserverdisconnected = fn; this.addEventListener('gattserverdisconnected', this._ongattserverdisconnected); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "onadvertisementreceived", { set: function (fn) { if (this._onadvertisementreceived) { this.removeEventListener('advertisementreceived', this._onadvertisementreceived); this._onadvertisementreceived = undefined; } if (fn) { this._onadvertisementreceived = fn; this.addEventListener('advertisementreceived', this._onadvertisementreceived); } }, enumerable: false, configurable: true }); Object.defineProperty(BluetoothImpl.prototype, "onavailabilitychanged", { set: function (fn) { if (this._onavailabilitychanged) { this.removeEventListener('availabilitychanged', this._onavailabilitychanged); this._onavailabilitychanged = undefined; } if (fn) { this._onavailabilitychanged = fn; this.addEventListener('availabilitychanged', this._onavailabilitychanged); } }, enumerable: false, configurable: true }); BluetoothImpl.prototype.filterDevice = function (filters, deviceInfo, validServices) { var valid = false; filters.forEach(function (filter) { var e_1, _a, e_2, _b; // Name if (filter.name && filter.name !== deviceInfo.name) return; // NamePrefix if (filter.namePrefix) { if (!deviceInfo.name || filter.namePrefix.length > deviceInfo.name.length) return; if (filter.namePrefix !== deviceInfo.name.substr(0, filter.namePrefix.length)) return; } // Services if (filter.services) { var serviceUUIDs = filter.services.map(uuid_1.BluetoothUUID.getService); var servicesValid = serviceUUIDs.every(function (serviceUUID) { return (deviceInfo._serviceUUIDs.indexOf(serviceUUID) > -1); }); if (!servicesValid) return; validServices = validServices.concat(serviceUUIDs); } // Service Data if (filter.serviceData) { if (!deviceInfo._adData.serviceData) return; var services = __spreadArray([], __read(deviceInfo._adData.serviceData.keys()), false); try { for (var _c = __values(filter.serviceData), _d = _c.next(); !_d.done; _d = _c.next()) { var entry = _d.value; if (!services.includes(entry.service)) return; } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } } // Manufacturer Data if (filter.manufacturerData) { if (!deviceInfo._adData.manufacturerData) return; var manufacturers = __spreadArray([], __read(deviceInfo._adData.manufacturerData.keys()), false); try { for (var _e = __values(filter.manufacturerData), _f = _e.next(); !_f.done; _f = _e.next()) { var entry = _f.value; if (!manufacturers.includes(entry.companyIdentifier)) return; } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_2) throw e_2.error; } } } valid = true; }); if (!valid) return undefined; return deviceInfo; }; BluetoothImpl.prototype.forgetDevice = function (uuid) { this.allowedDevices.delete(uuid); }; /** * Gets the availability of a bluetooth adapter * @returns Promise containing a flag indicating bluetooth availability */ BluetoothImpl.prototype.getAvailability = function () { return adapters_1.adapter.getEnabled(); }; /** * Scans for a device matching optional filters * @param options The options to use when scanning * @returns Promise containing a device which matches the options */ BluetoothImpl.prototype.requestDevice = function (options) { var _this = this; if (options === void 0) { options = { filters: [] }; } if (this.scanner !== undefined) { throw new Error('requestDevice error: request in progress'); } var isFiltered = function (maybeFiltered) { return maybeFiltered.filters !== undefined; }; var isAcceptAll = function (maybeAcceptAll) { return maybeAcceptAll.acceptAllDevices === true; }; var searchUUIDs = []; if (isFiltered(options)) { // Must have a filter if (options.filters.length === 0) { throw new TypeError('requestDevice error: no filters specified'); } // Don't allow empty filters var emptyFilter = options.filters.some(function (filter) { return (Object.keys(filter).length === 0); }); if (emptyFilter) { throw new TypeError('requestDevice error: empty filter specified'); } // Don't allow empty namePrefix var emptyPrefix = options.filters.some(function (filter) { return (typeof filter.namePrefix !== 'undefined' && filter.namePrefix === ''); }); if (emptyPrefix) { throw new TypeError('requestDevice error: empty namePrefix specified'); } options.filters.forEach(function (filter) { if (filter.services) searchUUIDs = searchUUIDs.concat(filter.services.map(uuid_1.BluetoothUUID.getService)); // Unique-ify searchUUIDs = searchUUIDs.filter(function (item, index, array) { return array.indexOf(item) === index; }); }); } else if (!isAcceptAll(options)) { throw new TypeError('requestDevice error: specify filters or acceptAllDevices'); } return new Promise(function (resolve, reject) { var found = false; _this.scanner = setTimeout(function () { _this.cancelRequest(); if (!found) { reject('requestDevice error: no devices found'); } }, _this.scanTime); adapters_1.adapter.startScan(searchUUIDs, function (deviceInfo) { var validServices = []; var complete = function (bluetoothDevice) { _this.allowedDevices.add(bluetoothDevice.id); _this.cancelRequest(); resolve(bluetoothDevice); }; // filter devices if filters specified if (isFiltered(options)) { deviceInfo = _this.filterDevice(options.filters, deviceInfo, validServices); } if (deviceInfo) { found = true; // Add additional services if (options.optionalServices) { validServices = validServices.concat(options.optionalServices.map(uuid_1.BluetoothUUID.getService)); } // Set unique list of allowed services var allowedServices = validServices.filter(function (item, index, array) { return array.indexOf(item) === index; }); Object.assign(deviceInfo, { _bluetooth: _this, _allowedServices: allowedServices }); var bluetoothDevice_1 = new device_1.BluetoothDeviceImpl(deviceInfo, function () { return _this.forgetDevice(deviceInfo.id); }); var selectFn = function () { complete.call(_this, bluetoothDevice_1); }; if (!_this.deviceFound || _this.deviceFound(bluetoothDevice_1, selectFn.bind(_this)) === true) { // If no deviceFound function, or deviceFound returns true, resolve with this device immediately complete.call(_this, bluetoothDevice_1); } } }); }); }; /** * Get all bluetooth devices */ BluetoothImpl.prototype.getDevices = function () { var _this = this; if (this.scanner !== undefined) { throw new Error('getDevices error: request in progress'); } return new Promise(function (resolve) { var devices = []; _this.scanner = setTimeout(function () { _this.cancelRequest(); resolve(devices); }, _this.scanTime); adapters_1.adapter.startScan([], function (deviceInfo) { var _a; if (((_a = _this.options) === null || _a === void 0 ? void 0 : _a.allowAllDevices) || _this.allowedDevices.has(deviceInfo.id)) { Object.assign(deviceInfo, { _bluetooth: _this, _allowedServices: [] }); var bluetoothDevice = new device_1.BluetoothDeviceImpl(deviceInfo, function () { return _this.forgetDevice(deviceInfo.id); }); devices.push(bluetoothDevice); } }); }); }; /** * Cancels the scan for devices */ BluetoothImpl.prototype.cancelRequest = function () { if (this.scanner) { clearTimeout(this.scanner); this.scanner = undefined; adapters_1.adapter.stopScan(); } }; /** * @hidden * Request LE scan (not implemented) */ BluetoothImpl.prototype.requestLEScan = function (_options) { throw new Error('requestLEScan error: method not implemented.'); }; return BluetoothImpl; }(events_1.EventDispatcher)); exports.BluetoothImpl = BluetoothImpl; /** * List available bluetooth adapters */ exports.getAdapters = adapters_1.adapter.getAdapters; //# sourceMappingURL=bluetooth.js.map