UNPKG

@nebulae/angular-ble

Version:

A Web Bluetooth (Bluetooth Low Energy) module for angular (v2+)

1,238 lines (1,233 loc) 151 kB
import { Injectable, NgModule, InjectionToken, defineInjectable, EventEmitter, inject } from '@angular/core'; import { ModeOfOperation, Counter, utils } from 'aes-js'; import { __extends, __read, __values } from 'tslib'; import { map, mergeMap, concat, mapTo, filter, takeUntil, scan, tap, take, timeout, retryWhen, delay } from 'rxjs/operators'; import { Observable, forkJoin, Subject, defer, fromEvent, of, from } from 'rxjs'; import { CommonModule } from '@angular/common'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var BrowserWebBluetooth = /** @class */ (function () { function BrowserWebBluetooth() { this._ble = navigator.bluetooth; if (!this._ble) { console.log('error cargando bluetooth'); // bluetoothService.setBluetoothAvailable(false); // throw new Error('Your browser does not support Smart Bluetooth. See http://caniuse.com/#search=Bluetooth for more details.'); } } /** * @param {?} options * @return {?} */ BrowserWebBluetooth.prototype.requestDevice = /** * @param {?} options * @return {?} */ function (options) { return this._ble.requestDevice(options); }; BrowserWebBluetooth.decorators = [ { type: Injectable }, ]; /** @nocollapse */ BrowserWebBluetooth.ctorParameters = function () { return []; }; return BrowserWebBluetooth; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ /** @type {?} */ var GattServices = { GENERIC_ACCESS: { SERVICE: 'generic_access', DEVICE_NAME: 'device_name', APPEARANCE: 'appearance', PRIVACY_FLAG: 'privacy_flag', RECONNECTION_ADDRESS: 'reconnection_address', PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS: 'peripheral_preferred_connection_parameters' }, BATTERY: { SERVICE: 'battery_service', BATTERY_LEVEL: 'battery_level' }, DEVICE_INFORMATION: { SERVICE: 'device_information', MANUFACTURER_NAME: 'manufacturer_name_string', MODEL_NUMBER: 'model_number_string', SERIAL_NUMBER: 'serial_number_string', HARDWARE_REVISION: 'hardware_revision_string', FIRMWARE_REVISION: 'firmware_revision_string', SOFTWARE_REVISION: 'software_revision_string', SYSTEM_ID: 'system_id', PNP_ID: 'pnp_id' } }; /** @type {?} */ var GATT_SERVICES = Object.keys(GattServices).map(function (key) { return GattServices[key].SERVICE; }); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var CypherAesService = /** @class */ (function () { function CypherAesService() { this.masterKey = []; this.initialVector = []; this.encryptMethod = 'CBC'; this.isStaticInitialVector = true; this.isConfigExecuted = false; } /** * Initial config used to initalice all required params * @param masterKey key used to encrypt and decrypt * @param initialVector vector used to encrypt abd decrypt except when ECB encrypt method is used * @param encryptMethod type of encrypt method is used, the possible options are: CBC, CTR, CFB, OFB, ECB * @param additionalEncryptMethodParams configuration params used by the selected encrypt method. * Note: if the method CTR or CFB is used this param is required otherwise is an optinal param. * By CTR require the param counter and by CFB require the param segmentSize * @param isStaticInitialVector defines if the initial vector is changed or not when the data are encrypted or not */ /** * Initial config used to initalice all required params * @param {?} masterKey key used to encrypt and decrypt * @param {?=} initialVector vector used to encrypt abd decrypt except when ECB encrypt method is used * @param {?=} encryptMethod type of encrypt method is used, the possible options are: CBC, CTR, CFB, OFB, ECB * @param {?=} additionalEncryptMethodParams configuration params used by the selected encrypt method. * Note: if the method CTR or CFB is used this param is required otherwise is an optinal param. * By CTR require the param counter and by CFB require the param segmentSize * @param {?=} isStaticInitialVector defines if the initial vector is changed or not when the data are encrypted or not * @return {?} */ CypherAesService.prototype.config = /** * Initial config used to initalice all required params * @param {?} masterKey key used to encrypt and decrypt * @param {?=} initialVector vector used to encrypt abd decrypt except when ECB encrypt method is used * @param {?=} encryptMethod type of encrypt method is used, the possible options are: CBC, CTR, CFB, OFB, ECB * @param {?=} additionalEncryptMethodParams configuration params used by the selected encrypt method. * Note: if the method CTR or CFB is used this param is required otherwise is an optinal param. * By CTR require the param counter and by CFB require the param segmentSize * @param {?=} isStaticInitialVector defines if the initial vector is changed or not when the data are encrypted or not * @return {?} */ function (masterKey, initialVector, encryptMethod, additionalEncryptMethodParams, isStaticInitialVector) { if (initialVector === void 0) { initialVector = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } if (encryptMethod === void 0) { encryptMethod = 'CBC'; } if (additionalEncryptMethodParams === void 0) { additionalEncryptMethodParams = {}; } if (isStaticInitialVector === void 0) { isStaticInitialVector = true; } this.isConfigExecuted = true; this.masterKey = masterKey; this.initialVector = initialVector; this.encryptMethod = encryptMethod; this.isStaticInitialVector = isStaticInitialVector; this.additionalEncryptMethodParams = additionalEncryptMethodParams; if (!isStaticInitialVector) { this.enctrypMethodInstance = this.generateEncryptMethodInstance(); } }; /** * Encrypt the data using the encrypt method previously configured * @param dataArrayBuffer data to encrypt */ /** * Encrypt the data using the encrypt method previously configured * @param {?} dataArrayBuffer data to encrypt * @return {?} */ CypherAesService.prototype.encrypt = /** * Encrypt the data using the encrypt method previously configured * @param {?} dataArrayBuffer data to encrypt * @return {?} */ function (dataArrayBuffer) { if (!this.isConfigExecuted) { throw new Error('Must configurate cypher-aes before call this method, use the method config()'); } if (this.encryptMethod === 'CBC' || this.encryptMethod === 'ECB') { dataArrayBuffer = this.addPadding(dataArrayBuffer); } return this.isStaticInitialVector ? this.generateEncryptMethodInstance().encrypt(dataArrayBuffer) : this.enctrypMethodInstance.encrypt(dataArrayBuffer); }; /** * Decrypt the data using the encrypt method previously configured * @param dataArrayBuffer data to decrypt */ /** * Decrypt the data using the encrypt method previously configured * @param {?} dataArrayBuffer data to decrypt * @return {?} */ CypherAesService.prototype.decrypt = /** * Decrypt the data using the encrypt method previously configured * @param {?} dataArrayBuffer data to decrypt * @return {?} */ function (dataArrayBuffer) { if (!this.isConfigExecuted) { throw new Error('Must configurate cypher-aes before call this method, use the method config()'); } return this.isStaticInitialVector ? this.generateEncryptMethodInstance().decrypt(dataArrayBuffer) : this.enctrypMethodInstance.decrypt(dataArrayBuffer); }; /** * Change the current initalVector * @param initialVector new initalVector */ /** * Change the current initalVector * @param {?} initialVector new initalVector * @return {?} */ CypherAesService.prototype.changeInitialVector = /** * Change the current initalVector * @param {?} initialVector new initalVector * @return {?} */ function (initialVector) { if (!this.isStaticInitialVector) { this.enctrypMethodInstance = this.generateEncryptMethodInstance(); } this.initialVector = initialVector; }; /** * Change the current encyptMethod * @param encryptMethod new encryptMethod */ /** * Change the current encyptMethod * @param {?} encryptMethod new encryptMethod * @return {?} */ CypherAesService.prototype.changeEncryptMethod = /** * Change the current encyptMethod * @param {?} encryptMethod new encryptMethod * @return {?} */ function (encryptMethod) { if (!this.isStaticInitialVector) { this.enctrypMethodInstance = this.generateEncryptMethodInstance(); } this.encryptMethod = encryptMethod; }; /** * Change the current isStaticInitialVector * @param isStaticInitialVector new isStaticInitalVector */ /** * Change the current isStaticInitialVector * @param {?} isStaticInitialVector new isStaticInitalVector * @return {?} */ CypherAesService.prototype.changeStaticInitialVector = /** * Change the current isStaticInitialVector * @param {?} isStaticInitialVector new isStaticInitalVector * @return {?} */ function (isStaticInitialVector) { if (!isStaticInitialVector) { this.enctrypMethodInstance = this.generateEncryptMethodInstance(); } this.isStaticInitialVector = isStaticInitialVector; }; /** * Change the current masterKey * @param masterKey new masterKey */ /** * Change the current masterKey * @param {?} masterKey new masterKey * @return {?} */ CypherAesService.prototype.changeMasterKey = /** * Change the current masterKey * @param {?} masterKey new masterKey * @return {?} */ function (masterKey) { if (!this.isStaticInitialVector) { this.enctrypMethodInstance = this.generateEncryptMethodInstance(); } this.masterKey = masterKey; }; /** * Add padding to the list * @param {?} arrayBuffer * @return {?} */ CypherAesService.prototype.addPadding = /** * Add padding to the list * @param {?} arrayBuffer * @return {?} */ function (arrayBuffer) { /** @type {?} */ var paddingLength = Math.ceil(Array.from(arrayBuffer).length / 16) * 16; /** @type {?} */ var paddingList = new Array(paddingLength - Array.from(arrayBuffer).length).fill(0); return new Uint8Array(Array.from(arrayBuffer).concat(paddingList)); }; /** * generate a instance of the encrypt method using the current method configured * @return {?} */ CypherAesService.prototype.generateEncryptMethodInstance = /** * generate a instance of the encrypt method using the current method configured * @return {?} */ function () { /** @type {?} */ var enctrypMethodInstance; switch (this.encryptMethod) { case 'CBC': enctrypMethodInstance = new ModeOfOperation.cbc(this.masterKey, this.initialVector); break; case 'CTR': if (!this.additionalEncryptMethodParams.counter) { throw new Error('additionalEncryptMethodParams.counter is required to use encrypt method CTR'); } enctrypMethodInstance = new ModeOfOperation.ctr(this.masterKey, this.initialVector, new Counter(this.additionalEncryptMethodParams.counter)); break; case 'CFB': if (!this.additionalEncryptMethodParams.segmentSize) { throw new Error('additionalEncryptMethodParams.segmentSize is required to use encrypt method CFB'); } enctrypMethodInstance = new ModeOfOperation.cfb(this.masterKey, this.initialVector, this.additionalEncryptMethodParams.segmentSize); break; case 'OFB': enctrypMethodInstance = new ModeOfOperation.ofb(this.masterKey, this.initialVector); break; case 'ECB': enctrypMethodInstance = new ModeOfOperation.ecb(this.masterKey); break; } return enctrypMethodInstance; }; // #region UTILS /** * Convert the text to bytes * @param text Text to convert */ /** * Convert the text to bytes * @param {?} text Text to convert * @return {?} */ CypherAesService.prototype.textToBytes = /** * Convert the text to bytes * @param {?} text Text to convert * @return {?} */ function (text) { return utils.utf8.toBytes(text); }; /** * Convert the bytes to text * @param bytes Bytes to convert */ /** * Convert the bytes to text * @param {?} bytes Bytes to convert * @return {?} */ CypherAesService.prototype.bytesToText = /** * Convert the bytes to text * @param {?} bytes Bytes to convert * @return {?} */ function (bytes) { return utils.utf8.fromBytes(bytes); }; /** * Convert the bytes to hex * @param bytes bytes to convert */ /** * Convert the bytes to hex * @param {?} bytes bytes to convert * @return {?} */ CypherAesService.prototype.bytesTohex = /** * Convert the bytes to hex * @param {?} bytes bytes to convert * @return {?} */ function (bytes) { return utils.hex.fromBytes(bytes); }; /** * Convert the hex to bytes * @param hex Hex to convert */ /** * Convert the hex to bytes * @param {?} hex Hex to convert * @return {?} */ CypherAesService.prototype.hexToBytes = /** * Convert the hex to bytes * @param {?} hex Hex to convert * @return {?} */ function (hex) { return utils.hex.toBytes(hex); }; // #endregion /** * @param {?} key * @return {?} */ CypherAesService.prototype.generateSubkeys = /** * @param {?} key * @return {?} */ function (key) { /** @type {?} */ var const_Zero = new Uint8Array(16); /** @type {?} */ var const_Rb = new Buffer('00000000000000000000000000000087', 'hex'); /** @type {?} */ var enctrypMethodInstance = new ModeOfOperation.cbc(key, new Uint8Array(16)); /** @type {?} */ var lEncrypted = enctrypMethodInstance.encrypt(const_Zero); /** @type {?} */ var l = new Buffer(this.bytesTohex(lEncrypted), 'hex'); /** @type {?} */ var subkey1 = this.bitShiftLeft(l); // tslint:disable-next-line:no-bitwise if (l[0] & 0x80) { subkey1 = this.xor(subkey1, const_Rb); } /** @type {?} */ var subkey2 = this.bitShiftLeft(subkey1); // tslint:disable-next-line:no-bitwise if (subkey1[0] & 0x80) { subkey2 = this.xor(subkey2, const_Rb); } return { subkey1: subkey1, subkey2: subkey2 }; }; /** * @param {?} key * @param {?} message * @return {?} */ CypherAesService.prototype.aesCmac = /** * @param {?} key * @param {?} message * @return {?} */ function (key, message) { console.log('INICIA CIFRADO!!!!!!!!!!!!!!!!'); /** @type {?} */ var subkeys = this.generateSubkeys(key); /** @type {?} */ var blockCount = Math.ceil(message.length / 16); /** @type {?} */ var lastBlockCompleteFlag; /** @type {?} */ var lastBlock; /** @type {?} */ var lastBlockIndex; if (blockCount === 0) { blockCount = 1; lastBlockCompleteFlag = false; } else { lastBlockCompleteFlag = message.length % 16 === 0; } lastBlockIndex = blockCount - 1; if (lastBlockCompleteFlag) { lastBlock = this.xor(this.getMessageBlock(message, lastBlockIndex), subkeys.subkey1); } else { lastBlock = this.xor(this.getPaddedMessageBlock(message, lastBlockIndex), subkeys.subkey2); } /** @type {?} */ var x = new Buffer('00000000000000000000000000000000', 'hex'); /** @type {?} */ var y; /** @type {?} */ var enctrypMethodInstance; for (var index = 0; index < lastBlockIndex; index++) { enctrypMethodInstance = new ModeOfOperation.cbc(key, new Uint8Array(16)); y = this.xor(x, this.getMessageBlock(message, index)); /** @type {?} */ var xEncrypted = enctrypMethodInstance.encrypt(y); console.log('X normal ===============> ', this.bytesTohex(y)); console.log('X encrypted ==============> ', this.bytesTohex(xEncrypted)); x = new Buffer(this.bytesTohex(xEncrypted), 'hex'); } y = this.xor(lastBlock, x); enctrypMethodInstance = new ModeOfOperation.cbc(key, new Uint8Array(16)); /** @type {?} */ var yEncrypted = enctrypMethodInstance.encrypt(y); console.log('Y normal ==============> ', this.bytesTohex(y)); console.log('Y encrypted ==============> ', this.bytesTohex(yEncrypted)); return yEncrypted; }; /** * @param {?} message * @param {?} blockIndex * @return {?} */ CypherAesService.prototype.getMessageBlock = /** * @param {?} message * @param {?} blockIndex * @return {?} */ function (message, blockIndex) { /** @type {?} */ var block = new Buffer(16); /** @type {?} */ var start = blockIndex * 16; /** @type {?} */ var end = start + 16; /** @type {?} */ var blockI = 0; for (var i = start; i < end; i++) { block[blockI] = message[i]; blockI++; } return block; }; /** * @param {?} message * @param {?} blockIndex * @return {?} */ CypherAesService.prototype.getPaddedMessageBlock = /** * @param {?} message * @param {?} blockIndex * @return {?} */ function (message, blockIndex) { /** @type {?} */ var block = new Buffer(16); /** @type {?} */ var start = blockIndex * 16; /** @type {?} */ var end = message.length; block.fill(0); /** @type {?} */ var blockI = 0; for (var i = start; i < end; i++) { block[blockI] = message[i]; blockI++; } block[end - start] = 0x80; return block; }; /** * @param {?} buffer * @return {?} */ CypherAesService.prototype.bitShiftLeft = /** * @param {?} buffer * @return {?} */ function (buffer) { /** @type {?} */ var shifted = new Buffer(buffer.length); /** @type {?} */ var last = buffer.length - 1; for (var index = 0; index < last; index++) { // tslint:disable-next-line:no-bitwise shifted[index] = buffer[index] << 1; // tslint:disable-next-line:no-bitwise if (buffer[index + 1] & 0x80) { shifted[index] += 0x01; } } // tslint:disable-next-line:no-bitwise shifted[last] = buffer[last] << 1; return shifted; }; /** * @param {?} bufferA * @param {?} bufferB * @return {?} */ CypherAesService.prototype.xor = /** * @param {?} bufferA * @param {?} bufferB * @return {?} */ function (bufferA, bufferB) { /** @type {?} */ var length = Math.min(bufferA.length, bufferB.length); /** @type {?} */ var output = new Buffer(length); for (var index = 0; index < length; index++) { // tslint:disable-next-line:no-bitwise output[index] = bufferA[index] ^ bufferB[index]; } return output; }; CypherAesService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ CypherAesService.ctorParameters = function () { return []; }; /** @nocollapse */ CypherAesService.ngInjectableDef = defineInjectable({ factory: function CypherAesService_Factory() { return new CypherAesService(); }, token: CypherAesService, providedIn: "root" }); return CypherAesService; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var ConsoleLoggerService = /** @class */ (function () { function ConsoleLoggerService() { } /** * @param {...?} args * @return {?} */ ConsoleLoggerService.prototype.log = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.log.apply(console, args); }; /** * @param {...?} args * @return {?} */ ConsoleLoggerService.prototype.error = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.error.apply(console, args); }; /** * @param {...?} args * @return {?} */ ConsoleLoggerService.prototype.warn = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } console.warn.apply(console, args); }; ConsoleLoggerService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ ConsoleLoggerService.ngInjectableDef = defineInjectable({ factory: function ConsoleLoggerService_Factory() { return new ConsoleLoggerService(); }, token: ConsoleLoggerService, providedIn: "root" }); return ConsoleLoggerService; }()); var NoLoggerService = /** @class */ (function () { function NoLoggerService() { } /** * @param {...?} args * @return {?} */ NoLoggerService.prototype.log = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } }; /** * @param {...?} args * @return {?} */ NoLoggerService.prototype.error = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } }; /** * @param {...?} args * @return {?} */ NoLoggerService.prototype.warn = /** * @param {...?} args * @return {?} */ function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } }; NoLoggerService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ NoLoggerService.ngInjectableDef = defineInjectable({ factory: function NoLoggerService_Factory() { return new NoLoggerService(); }, token: NoLoggerService, providedIn: "root" }); return NoLoggerService; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ var BluetoothService = /** @class */ (function (_super) { __extends(BluetoothService, _super); function BluetoothService(_webBle, cypherAesService, _console) { var _this = _super.call(this) || this; _this._webBle = _webBle; _this.cypherAesService = cypherAesService; _this._console = _console; _this.serviceCharacteristicVsSubscriptionList = {}; _this.notifierSubject = new Subject(); _this.notifierStartedSubject = new Subject(); _this.bluetoothAvailable = false; _this._device$ = new EventEmitter(); if (_webBle._ble) { _this.bluetoothAvailable = true; } return _this; } /** * @return {?} */ BluetoothService.prototype.isBluetoothAvailable = /** * @return {?} */ function () { return this.bluetoothAvailable; }; /** * get the current device, if the device return null is because the connection has lost * @returns the current connceted device */ /** * get the current device, if the device return null is because the connection has lost * @return {?} the current connceted device */ BluetoothService.prototype.getDevice$ = /** * get the current device, if the device return null is because the connection has lost * @return {?} the current connceted device */ function () { return this._device$; }; /** * @return {?} */ BluetoothService.prototype.getNotifierStartedSubject$ = /** * @return {?} */ function () { return this.notifierStartedSubject; }; /** * start a stream by notifiers characteristics * @param service The service to which the characteristic belongs * @param characteristic The characteristic whose value you want to listen * @param options object that contains the * startByte:number (required), stopByte:number (required), * lengthPosition: { start: number, end: number, lengthPadding: number } (required) * @returns A DataView than contains the characteristic value */ /** * start a stream by notifiers characteristics * @param {?} service The service to which the characteristic belongs * @param {?} characteristic The characteristic whose value you want to listen * @param {?} options object that contains the * startByte:number (required), stopByte:number (required), * lengthPosition: { start: number, end: number, lengthPadding: number } (required) * @return {?} A DataView than contains the characteristic value */ BluetoothService.prototype.startNotifierListener$ = /** * start a stream by notifiers characteristics * @param {?} service The service to which the characteristic belongs * @param {?} characteristic The characteristic whose value you want to listen * @param {?} options object that contains the * startByte:number (required), stopByte:number (required), * lengthPosition: { start: number, end: number, lengthPadding: number } (required) * @return {?} A DataView than contains the characteristic value */ function (service, characteristic, options) { var _this = this; return defer(function () { of(service) .pipe(tap(function () { return _this._console.log('Inicia el notifier con la instancia: ', _this.serviceCharacteristicVsSubscriptionList); }), filter(function (_) { return Object.keys(_this.serviceCharacteristicVsSubscriptionList).indexOf(service + "-" + characteristic) === -1; })) .subscribe(function () { _this.serviceCharacteristicVsSubscriptionList[service + "-" + characteristic] = _this.buildNotifierListener$(service, characteristic, options).subscribe(function (message) { _this.notifierSubject.next(message); }); }, function (err) { _this._console.log('[BLE::Info] Error in notifier: ', err); }, function () { }); return of("notifier as subscribed: service= " + service + ", characteristic= " + characteristic); }); }; /** * @param {?} service * @param {?} characteristic * @return {?} */ BluetoothService.prototype.stopNotifierListener$ = /** * @param {?} service * @param {?} characteristic * @return {?} */ function (service, characteristic) { var _this = this; return defer(function () { // this.serviceCharacteristicVsSubscriptionList[`${service}-${characteristic}`].unsubscribe(); delete _this.serviceCharacteristicVsSubscriptionList[service + "-" + characteristic]; return of("the notifier of the characteristic " + characteristic + " as been stopped"); }); }; /** * @param {?} service * @param {?} characteristic * @param {?} options * @return {?} */ BluetoothService.prototype.buildNotifierListener$ = /** * @param {?} service * @param {?} characteristic * @param {?} options * @return {?} */ function (service, characteristic, options) { var _this = this; return this.getPrimaryService$(service).pipe(tap(function (serviceInstance) { return _this._console.log("toma exitosamente el servicio ================> " + serviceInstance); }), mergeMap(function (primaryService) { return _this.getCharacteristic$(primaryService, characteristic) .pipe(tap(function (char) { return _this._console.log("toma exitosamente la caracteristica ================> " + char); })); }), mergeMap(function (char) { return defer(function () { // enable the characteristic notifier return char.startNotifications(); }).pipe(retryWhen(function (error) { return error.pipe(tap(function () { return _this._console.log('ERROR EN EL startNotifications ================> '); }), delay(1000), take(5)); }), tap(function () { _this.notifierStartedSubject.next(true); _this._console.log("incia las notifiaciones de la caracteristica 2 ================> " + characteristic); }), mergeMap(function (_) { // start the lister from the even characteristicvaluechanged to get all changes on the specific // characteristic return fromEvent(char, 'characteristicvaluechanged').pipe(takeUntil(fromEvent(char, 'gattserverdisconnected')), map(function (event) { // get a event from the characteristic and map that return { startByteMatches: false, stopByteMatches: false, lengthMatches: false, messageLength: 0, timestamp: Date.now(), data: Array.from(new Uint8Array((/** @type {?} */ ((/** @type {?} */ (event.target)) .value)).buffer)) }; }), scan(function (acc, value) { acc.timestamp = acc.timestamp === 0 ? value.timestamp : acc.timestamp; // if the current accumulator value is a valid message, then is restarted to get the next // message if ((acc.lengthMatches && acc.startByteMatches && acc.stopByteMatches) || acc.timestamp + 1000 < Date.now()) { acc = { startByteMatches: false, stopByteMatches: false, lengthMatches: false, messageLength: 0, timestamp: 0, data: [] }; } // validate the start byte if (!acc.startByteMatches && value.data[0] === options.startByte) { // get the message length using the start and end position acc.messageLength = new DataView(new Uint8Array(value.data.slice(options.lengthPosition.start, options.lengthPosition.end)).buffer).getInt16(0, false) + (options.lengthPosition.lengthPadding ? options.lengthPosition.lengthPadding : 0); // valid that the initial byte was found acc.startByteMatches = true; } if (!acc.stopByteMatches && value.data[value.data.length - 1] === options.stopByte) { // valid that the end byte was found acc.stopByteMatches = true; } if (acc.startByteMatches) { // merge the new data bytes to the old bytes acc.data = acc.data.concat(value.data); } acc.lengthMatches = acc.startByteMatches && acc.stopByteMatches && acc.messageLength === acc.data.length; return acc; }, { startByteMatches: false, stopByteMatches: false, lengthMatches: false, messageLength: 0, timestamp: 0, data: [] }), // only publish the complete and valid message filter(function (data) { return data.lengthMatches && data.startByteMatches && data.stopByteMatches; }), // remove all custom data and publish the message data map(function (result) { return result.data; })); })); })); }; /** * Send a request to the device and wait a unique response * @param message Message to send * @param service The service to which the characteristic belongs * @param characteristic The characteristic whose value you want to send the message * @param responseType filter to use to identify the response, Sample: [{position: 3, byteToMatch: 0x83}, * {position: 13, byteToMatch: 0x45}] * @param cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted */ /** * Send a request to the device and wait a unique response * @param {?} message Message to send * @param {?} service The service to which the characteristic belongs * @param {?} characteristic The characteristic whose value you want to send the message * @param {?} responseType filter to use to identify the response, Sample: [{position: 3, byteToMatch: 0x83}, * {position: 13, byteToMatch: 0x45}] * @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted * @return {?} */ BluetoothService.prototype.sendAndWaitResponse$ = /** * Send a request to the device and wait a unique response * @param {?} message Message to send * @param {?} service The service to which the characteristic belongs * @param {?} characteristic The characteristic whose value you want to send the message * @param {?} responseType filter to use to identify the response, Sample: [{position: 3, byteToMatch: 0x83}, * {position: 13, byteToMatch: 0x45}] * @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted * @return {?} */ function (message, service, characteristic, responseType, cypherMasterKey) { this._console.log('[BLE::Info] Send message to device: ', this.cypherAesService.bytesTohex(message)); return forkJoin(this.subscribeToNotifierListener(responseType, cypherMasterKey).pipe(take(1)), this.sendToNotifier$(message, service, characteristic)).pipe(map(function (_a) { var _b = __read(_a, 2), messageResp = _b[0], _ = _b[1]; return messageResp; }), timeout(3000)); }; /** * Subscribe to the notifiers filtering by byte checking * @param filterOptions must specific the position and the byte to match Sample: * [{position: 3, byteToMatch: 0x83}, {position: 13, byteToMatch: 0x45}] * @param cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted */ /** * Subscribe to the notifiers filtering by byte checking * @param {?} filterOptions must specific the position and the byte to match Sample: * [{position: 3, byteToMatch: 0x83}, {position: 13, byteToMatch: 0x45}] * @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted * @return {?} */ BluetoothService.prototype.subscribeToNotifierListener = /** * Subscribe to the notifiers filtering by byte checking * @param {?} filterOptions must specific the position and the byte to match Sample: * [{position: 3, byteToMatch: 0x83}, {position: 13, byteToMatch: 0x45}] * @param {?=} cypherMasterKey master key to decrypt the message, only use this para if the message to receive is encrypted * @return {?} */ function (filterOptions, cypherMasterKey) { var _this = this; return this.notifierSubject.pipe(map(function (messageUnformated) { /** @type {?} */ var messageFormmated = /** @type {?} */ (messageUnformated); // validate if the message is cyphered if (cypherMasterKey) { /** @type {?} */ var datablockLength = new DataView(new Uint8Array(messageFormmated.slice(1, 3)).buffer).getInt16(0, false); _this.cypherAesService.config(cypherMasterKey); /** @type {?} */ var datablock = Array.from(_this.cypherAesService.decrypt((/** @type {?} */ (messageUnformated)).slice(3, datablockLength + 3))); // merge the datablock and the message messageFormmated = (/** @type {?} */ (messageUnformated)) .slice(0, 3) .concat(datablock) .concat((/** @type {?} */ (messageUnformated)).slice(-2)); } _this._console.log('[BLE::Info] Notification reived from device: ', _this.cypherAesService.bytesTohex(messageFormmated)); return messageFormmated; }), // filter the message using the filter options filter(function (message) { /** @type {?} */ var availableMessage = false; try { for (var filterOptions_1 = __values(filterOptions), filterOptions_1_1 = filterOptions_1.next(); !filterOptions_1_1.done; filterOptions_1_1 = filterOptions_1.next()) { var option = filterOptions_1_1.value; if (message[option.position] === option.byteToMatch) { availableMessage = true; } else { availableMessage = false; break; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (filterOptions_1_1 && !filterOptions_1_1.done && (_a = filterOptions_1.return)) _a.call(filterOptions_1); } finally { if (e_1) throw e_1.error; } } return availableMessage; var e_1, _a; })); }; /** * Start a request to the browser to list all available bluetooth devices * @param {?=} options Options to request the devices the structure is: * acceptAllDevices: true|false * filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info) * optionalServices: [] (services that are going to be used in * communication with the device, must use the UIID or GATT identfier to list ther services) * @return {?} */ BluetoothService.prototype.discoverDevice$ = /** * Start a request to the browser to list all available bluetooth devices * @param {?=} options Options to request the devices the structure is: * acceptAllDevices: true|false * filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info) * optionalServices: [] (services that are going to be used in * communication with the device, must use the UIID or GATT identfier to list ther services) * @return {?} */ function (options) { var _this = this; if (options === void 0) { options = /** @type {?} */ ({}); } return defer(function () { return _this._webBle.requestDevice(options); }).pipe(mergeMap(function (device) { _this.device = device; _this._device$.emit(device); return _this.configureDeviceDisconnection$(device).pipe(mapTo(device)); })); }; /** * @param {?} device * @return {?} */ BluetoothService.prototype.configureDeviceDisconnection$ = /** * @param {?} device * @return {?} */ function (device) { var _this = this; return Observable.create(function (observer) { of(device) .pipe(mergeMap(function (dev) { return fromEvent(device, 'gattserverdisconnected'); }), take(1)) .subscribe(function () { _this._console.log('Se desconecta disp en OnDevice disconnected!!!!!!!'); _this.device = null; _this._device$.emit(null); }, function (err) { _this._console.log('[BLE::Info] Error in notifier: ', err); observer.error(err); }, function () { }); observer.next("DisconnectionEvent as been register"); }); }; /** * Discover all available devices and connect to a selected device * @param options Options to request the devices the structure is: * acceptAllDevices: true|false * filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info) * optionalServices: [] (services that are going to be used in * communication with the device, must use the UIID or GATT identfier to list ther services) * @returns the connected device */ /** * Discover all available devices and connect to a selected device * @param {?=} options Options to request the devices the structure is: * acceptAllDevices: true|false * filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info) * optionalServices: [] (services that are going to be used in * communication with the device, must use the UIID or GATT identfier to list ther services) * @return {?} the connected device */ BluetoothService.prototype.connectDevice$ = /** * Discover all available devices and connect to a selected device * @param {?=} options Options to request the devices the structure is: * acceptAllDevices: true|false * filters: BluetoothDataFilterInit (see https://webbluetoothcg.github.io/web-bluetooth/#dictdef-bluetoothlescanfilterinit for more info) * optionalServices: [] (services that are going to be used in * communication with the device, must use the UIID or GATT identfier to list ther services) * @return {?} the connected device */ function (options) { if (!options) { options = { acceptAllDevices: true, optionalServices: [] }; } else if (!options.optionalServices) { options.optionalServices = []; } options.optionalServices.push(GattServices.GENERIC_ACCESS.SERVICE); options.optionalServices.push(GattServices.BATTERY.SERVICE); options.optionalServices.push(GattServices.DEVICE_INFORMATION.SERVICE); return this.discoverDevice$(options).pipe(mergeMap(function (device) { return defer(function () { return (/** @type {?} */ (device)).gatt.connect(); }); })); }; /** * Disconnect the current device */ /** * Disconnect the current device * @return {?} */ BluetoothService.prototype.disconnectDevice = /** * Disconnect the current device * @return {?} */ function () { if (this.device) { this._console.log('se deconecta dispositivo'); this.device.gatt.disconnect(); } }; /** * get a data from the device using the characteristic * @param service UUID or GATT identifier service * @param characteristic UUID or GATT identifier characteristic * @return The characteristic data in a DataView object */ /** * get a data from the device using the characteristic * @param {?} service UUID or GATT identifier service * @param {?} characteristic UUID or GATT identifier characteristic * @return {?} The characteristic data in a DataView object */ BluetoothService.prototype.readDeviceValue$ = /** * get a data from the device using the characteristic * @param {?} service UUID or GATT identifier service * @param {?} characteristic UUID or GATT identifier characteristic * @return {?} The characteristic data in a DataView object */ function (service, characteristic) { var _this = this; if (!this.device) { throw new Error('Must start a connection to a device before read the device value'); } return this.getPrimaryService$(service).pipe(mergeMap(function (primaryService) { return _this.getCharacteristic$(primaryService, characteristic); }), mergeMap(function (characteristicValue) { return _this.readValue$(characteristicValue); })); }; /** * write a value in the selected characteristic * @param characteristic the characterisitc where you want write the value * @param value value the value to write */ /** * write a value in the selected characteristic * @param {?} service * @param {?} characteristic the characterisitc where you want write the value * @param {?} value value the value to write * @return {?} */ BluetoothService.prototype.writeDeviceValue$ = /** * write a value in the selected characteristic * @param {?} service * @param {?} characteristic the characterisitc where you want write the value * @param {?} value value the value to write * @return {?} */ function (service, characteristic, value) { var _this = this; if (!this.device) { throw new Error('Must start a connection to a device before read the device value'); } return this.getPrimaryService$(service).pipe(mergeMap(function (primaryService) { return _this.getCharacteristic$(primaryService, characteristic); }), mergeMap(function (characteristicValue) { return _this.writeValue$(characteristicValue, value); })); }; /** * get a primary service instance using the service UIID or GATT identifier * @param service service identifier * @return service instance */ /** * get a primary service instance using the service UIID or GATT identifier * @param {?} service service identifier * @return {?} service instance */ BluetoothService.prototype.getPrimaryService$ = /** * get a primary service instance using the service UIID or GATT identifier * @param {?} service service identifier * @return {?} service instance */ function (service) { return of(this.device).pipe(mergeMap(function (device) { return device.gatt.getPrimaryService(service); })); }; /** * Get a characterisitic instance using the service instance and a characteristic UUID * @param primaryService service instance * @param characteristic characterisitic identifier * @return characteristic instance */ /** * Get a characterisitic instance using the service instance and a characteristic UUID * @param {?} primaryService service instance * @param {?} characteristic characterisitic identifier * @return {?} characteristic instance */ BluetoothService.prototype.getCharacteristic$ = /** * Get a characterisitic instance using the service instance and a characteristic UUID * @param {?} primaryService service instance * @param {?} characteristic characterisitic identifier * @return {?} characteristic instance */ function (primaryService, characteristic) { return defer(function () { return primaryService.getCharacteristic(characteristic); }); }; /** * read the characteristic value * @param {?} characteristic characteristic instance * @return {?} The characteristic data in a DataView object */ BluetoothService.prototyp