webbluetooth
Version:
Node.js implementation of the Web Bluetooth Specification
436 lines • 18.2 kB
JavaScript
"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