UNPKG

@nebulae/angular-ble

Version:

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

534 lines (533 loc) 76.8 kB
/** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,uselessCode} checked by tsc */ import { Injectable, EventEmitter } from '@angular/core'; 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 { GattServices } from './gatt-services'; import { BrowserWebBluetooth } from '../platform/browser'; import { CypherAesService } from '../cypher/cypher-aes.service'; import { ConsoleLoggerService } from '../logger.service'; import * as i0 from "@angular/core"; import * as i1 from "../platform/browser"; import * as i2 from "../cypher/cypher-aes.service"; import * as i3 from "../logger.service"; export class BluetoothService extends Subject { /** * @param {?} _webBle * @param {?} cypherAesService * @param {?} _console */ constructor(_webBle, cypherAesService, _console) { super(); 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 {?} */ isBluetoothAvailable() { return this.bluetoothAvailable; } /** * get the current device, if the device return null is because the connection has lost * @return {?} the current connceted device */ getDevice$() { return this._device$; } /** * @return {?} */ getNotifierStartedSubject$() { 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) * @return {?} A DataView than contains the characteristic value */ startNotifierListener$(service, characteristic, options) { return defer(() => { of(service) .pipe(tap(() => this._console.log('Inicia el notifier con la instancia: ', this.serviceCharacteristicVsSubscriptionList)), filter(_ => Object.keys(this.serviceCharacteristicVsSubscriptionList).indexOf(`${service}-${characteristic}`) === -1)) .subscribe(() => { this.serviceCharacteristicVsSubscriptionList[`${service}-${characteristic}`] = this.buildNotifierListener$(service, characteristic, options).subscribe(message => { this.notifierSubject.next(message); }); }, err => { this._console.log('[BLE::Info] Error in notifier: ', err); }, () => { }); return of(`notifier as subscribed: service= ${service}, characteristic= ${characteristic}`); }); } /** * @param {?} service * @param {?} characteristic * @return {?} */ stopNotifierListener$(service, characteristic) { return defer(() => { // 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 {?} */ buildNotifierListener$(service, characteristic, options) { return this.getPrimaryService$(service).pipe(tap(serviceInstance => this._console.log(`toma exitosamente el servicio ================> ${serviceInstance}`)), mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic) .pipe(tap(char => this._console.log(`toma exitosamente la caracteristica ================> ${char}`)))), mergeMap((char) => { return defer(() => { // enable the characteristic notifier return char.startNotifications(); }).pipe(retryWhen(error => error.pipe(tap(() => this._console.log('ERROR EN EL startNotifications ================> ')), delay(1000), take(5))), tap(() => { this.notifierStartedSubject.next(true); this._console.log(`incia las notifiaciones de la caracteristica 2 ================> ${characteristic}`); }), mergeMap(_ => { // 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((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((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(data => data.lengthMatches && data.startByteMatches && data.stopByteMatches), // remove all custom data and publish the message data map(result => 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 * @return {?} */ sendAndWaitResponse$(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(([messageResp, _]) => 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 * @return {?} */ subscribeToNotifierListener(filterOptions, cypherMasterKey) { return this.notifierSubject.pipe(map(messageUnformated => { /** @type {?} */ let messageFormmated = /** @type {?} */ (messageUnformated); // validate if the message is cyphered if (cypherMasterKey) { /** @type {?} */ const datablockLength = new DataView(new Uint8Array(messageFormmated.slice(1, 3)).buffer).getInt16(0, false); this.cypherAesService.config(cypherMasterKey); /** @type {?} */ const 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(message => { /** @type {?} */ let availableMessage = false; for (const option of filterOptions) { if (message[option.position] === option.byteToMatch) { availableMessage = true; } else { availableMessage = false; break; } } return availableMessage; })); } /** * 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 {?} */ discoverDevice$(options = /** @type {?} */ ({})) { return defer(() => this._webBle.requestDevice(options)).pipe(mergeMap(device => { this.device = device; this._device$.emit(device); return this.configureDeviceDisconnection$(device).pipe(mapTo(device)); })); } /** * @param {?} device * @return {?} */ configureDeviceDisconnection$(device) { return Observable.create(observer => { of(device) .pipe(mergeMap(dev => fromEvent(device, 'gattserverdisconnected')), take(1)) .subscribe(() => { this._console.log('Se desconecta disp en OnDevice disconnected!!!!!!!'); this.device = null; this._device$.emit(null); }, err => { this._console.log('[BLE::Info] Error in notifier: ', err); observer.error(err); }, () => { }); 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) * @return {?} the connected device */ connectDevice$(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(device => { return defer(() => (/** @type {?} */ (device)).gatt.connect()); })); } /** * Disconnect the current device * @return {?} */ disconnectDevice() { 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 */ readDeviceValue$(service, characteristic) { if (!this.device) { throw new Error('Must start a connection to a device before read the device value'); } return this.getPrimaryService$(service).pipe(mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic)), mergeMap((characteristicValue) => this.readValue$(characteristicValue))); } /** * 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 {?} */ writeDeviceValue$(service, characteristic, value) { if (!this.device) { throw new Error('Must start a connection to a device before read the device value'); } return this.getPrimaryService$(service).pipe(mergeMap(primaryService => this.getCharacteristic$(primaryService, characteristic)), mergeMap((characteristicValue) => this.writeValue$(characteristicValue, value))); } /** * get a primary service instance using the service UIID or GATT identifier * @param {?} service service identifier * @return {?} service instance */ getPrimaryService$(service) { return of(this.device).pipe(mergeMap(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 */ getCharacteristic$(primaryService, characteristic) { return defer(() => primaryService.getCharacteristic(characteristic)); } /** * read the characteristic value * @param {?} characteristic characteristic instance * @return {?} The characteristic data in a DataView object */ readValue$(characteristic) { return from(characteristic .readValue() .then((data) => Promise.resolve(data), (error) => Promise.reject(`${error.message}`))); } /** * write a value in the selected characteristic * @param {?} characteristic the characterisitc where you want write the value * @param {?} value the value to write * @return {?} */ writeValue$(characteristic, value) { return defer(() => characteristic .writeValue(value)); } /** * change the state of the characteristic to enable it * @param {?} service parent service of the characteristic * @param {?} characteristic characteristic to change the state * @param {?=} state new state * @return {?} */ enableCharacteristic$(service, characteristic, state) { state = state || new Uint8Array([1]); return this.setCharacteristicState$(service, characteristic, state); } /** * change the state of the characteristic to disable it * @param {?} service parent service of the characteristic * @param {?} characteristic characteristic to change the state * @param {?=} state new state * @return {?} */ disbaleCharacteristic$(service, characteristic, state) { state = state || new Uint8Array([0]); return this.setCharacteristicState$(service, characteristic, state); } /** * set a state to an specific characteristic * @param {?} service parent service of the characteristic * @param {?} characteristic characteristic to change the state * @param {?} state new state * @return {?} */ setCharacteristicState$(service, characteristic, state) { /** @type {?} */ const primaryService = this.getPrimaryService$(service); return primaryService.pipe(mergeMap(_primaryService => this.getCharacteristic$(_primaryService, characteristic)), map((_characteristic) => this.writeValue$(_characteristic, state))); } /** * Send a message using a notifier characteristic * @param {?} message message to send * @param {?} service service to which the characteristic belongs * @param {?} characteristic feature in which you want to send the notification * @return {?} */ sendToNotifier$(message, service, characteristic) { return this.getPrimaryService$(service).pipe(mergeMap((primaryService) => this.getCharacteristic$(primaryService, characteristic)), mergeMap((char) => { if (message.length > 16) { /** @type {?} */ let obsTest = of(undefined); while (message.length > 16) { obsTest = obsTest.pipe(concat(this.writeValue$(char, message.slice(0, 16)))); message = message.slice(16, message.length); } if (message.length > 0) { obsTest = obsTest.pipe(concat(this.writeValue$(char, message))); } return obsTest; } else { return this.writeValue$(char, message); } })); } /** * The Battery Level characteristic is read using the GATT Read Characteristic * Value sub-procedure and returns the current battery level as a percentage * from 0% to 100%; 0% represents a battery that is fully discharged, 100% * represents a battery that is fully charged * @return {?} */ getBatteryLevel$() { return this.readDeviceValue$(GattServices.BATTERY.SERVICE, GattServices.BATTERY.BATTERY_LEVEL).pipe(map(value => value.getUint8(0))); } /** * This characteristic represents the name of the manufacturer of the device. * @return {?} */ getManufacturerName$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.MANUFACTURER_NAME).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents the model number that is assigned by the device vendor. * @return {?} */ getModelNumber$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.MODEL_NUMBER).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents the serial number for a particular instance of the device. * @return {?} */ getSerialNumber$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SERIAL_NUMBER).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents the hardware revision for the hardware within the device. * @return {?} */ getHardwareRevision$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.HARDWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents the firmware revision for the firmware within the device. * @return {?} */ getFirmwareRevision$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.FIRMWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents the software revision for the software within the device. * @return {?} */ getSoftwareRevision$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SOFTWARE_REVISION).pipe(map(dataView => this.cypherAesService.bytesToText(new Uint8Array(dataView.buffer)))); } /** * This characteristic represents a structure containing an Organizationally Unique Identifier * (OUI) followed by a manufacturer-defined identifier and is unique for each individual instance of the product. * @return {?} */ getSystemId$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.SYSTEM_ID); } /** * The PnP_ID characteristic is a set of values used to create a device ID value that is unique for this device. * @return {?} */ getPnpId$() { return this.readDeviceValue$(GattServices.DEVICE_INFORMATION.SERVICE, GattServices.DEVICE_INFORMATION.PNP_ID); } } BluetoothService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] }, ]; /** @nocollapse */ BluetoothService.ctorParameters = () => [ { type: BrowserWebBluetooth }, { type: CypherAesService }, { type: ConsoleLoggerService } ]; /** @nocollapse */ BluetoothService.ngInjectableDef = i0.defineInjectable({ factory: function BluetoothService_Factory() { return new BluetoothService(i0.inject(i1.BrowserWebBluetooth), i0.inject(i2.CypherAesService), i0.inject(i3.ConsoleLoggerService)); }, token: BluetoothService, providedIn: "root" }); if (false) { /** @type {?} */ BluetoothService.prototype._device$; /** @type {?} */ BluetoothService.prototype.device; /** @type {?} */ BluetoothService.prototype.serviceCharacteristicVsSubscriptionList; /** @type {?} */ BluetoothService.prototype.notifierSubject; /** @type {?} */ BluetoothService.prototype.notifierStartedSubject; /** @type {?} */ BluetoothService.prototype.bluetoothAvailable; /** @type {?} */ BluetoothService.prototype._webBle; /** @type {?} */ BluetoothService.prototype.cypherAesService; /** @type {?} */ BluetoothService.prototype._console; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmx1ZXRvb3RoLnNlcnZpY2UuanMiLCJzb3VyY2VSb290Ijoibmc6Ly9AbmVidWxhZS9hbmd1bGFyLWJsZS8iLCJzb3VyY2VzIjpbImxpYi9ibHVldG9vdGgvYmx1ZXRvb3RoLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7OztBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pELE9BQU8sRUFDTCxHQUFHLEVBQ0gsUUFBUSxFQUNSLE1BQU0sRUFDTixLQUFLLEVBQ0wsTUFBTSxFQUNOLFNBQVMsRUFFVCxJQUFJLEVBQ0osR0FBRyxFQUNILElBQUksRUFDSixPQUFPLEVBQ1AsU0FBUyxFQUNULEtBQUssRUFDTixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sRUFDTCxVQUFVLEVBQ1YsUUFBUSxFQUNSLE9BQU8sRUFDUCxLQUFLLEVBQ0wsU0FBUyxFQUNULEVBQUUsRUFDRixJQUFJLEVBRUwsTUFBTSxNQUFNLENBQUM7QUFDZCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDMUQsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDaEUsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7Ozs7O0FBS3pELE1BQU0sdUJBQXdCLFNBQVEsT0FBeUI7Ozs7OztJQU83RCxZQUNTLFNBQ0Msa0JBQ0Q7UUFFUCxLQUFLLEVBQUUsQ0FBQztRQUpELFlBQU8sR0FBUCxPQUFPO1FBQ04scUJBQWdCLEdBQWhCLGdCQUFnQjtRQUNqQixhQUFRLEdBQVIsUUFBUTt1REFQaUMsRUFBRTsrQkFDMUIsSUFBSSxPQUFPLEVBQUU7c0NBQ04sSUFBSSxPQUFPLEVBQUU7a0NBQ2pCLEtBQUs7UUFPaEMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLFlBQVksRUFBbUIsQ0FB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LENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7O0lBSUQsb0JBQW9CO1FBQ2xCLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FDbEQsQ0FBQyxJQUFJLENBQ0osR0FBRyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQ2IsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFdBQVcsQ0FBQyxJQUFJLFVBQVUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FDbkUsQ0FDRixDQUFDO0tBQ0g7Ozs7OztJQUtELFlBQVk7UUFDVixNQUFNLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUMxQixZQUFZLENBQUMsa0JBQWtCLENBQUMsT0FBTyxFQUN2QyxZQUFZLENBQUMsa0JBQWtCLENBQUMsU0FBUyxDQUMxQyxDQUFDO0tBQ0g7Ozs7O0lBSUQsU0FBUztRQUNQLE1BQU0sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQzFCLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQ3ZDLFlBQVksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQ3ZDLENBQUM7S0FDSDs7O1lBL3BCRixVQUFVLFNBQUM7Z0JBQ1YsVUFBVSxFQUFFLE1BQU07YUFDbkI7Ozs7WUFOUSxtQkFBbUI7WUFDbkIsZ0JBQWdCO1lBQ2hCLG9CQUFvQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUsIEV2ZW50RW1pdHRlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtcbiAgbWFwLFxuICBtZXJnZU1hcCxcbiAgY29uY2F0LFxuICBtYXBUbyxcbiAgZmlsdGVyLFxuICB0YWtlVW50aWwsXG4gIGJ1ZmZlclRvZ2dsZSxcbiAgc2NhbixcbiAgdGFwLFxuICB0YWtlLFxuICB0aW1lb3V0LFxuICByZXRyeVdoZW4sXG4gIGRlbGF5XG59IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7XG4gIE9ic2VydmFibGUsXG4gIGZvcmtKb2luLFxuICBTdWJqZWN0LFxuICBkZWZlcixcbiAgZnJvbUV2ZW50LFxuICBvZixcbiAgZnJvbSxcbiAgaW50ZXJ2YWxcbn0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBHYXR0U2VydmljZXMgfSBmcm9tICcuL2dhdHQtc2VydmljZXMnO1xuaW1wb3J0IHsgQnJvd3NlcldlYkJsdWV0b290aCB9IGZyb20gJy4uL3BsYXRmb3JtL2Jyb3dzZXInO1xuaW1wb3J0IHsgQ3lwaGVyQWVzU2VydmljZSB9IGZyb20gJy4uL2N5cGhlci9jeXBoZXItYWVzLnNlcnZpY2UnO1xuaW1wb3J0IHsgQ29uc29sZUxvZ2dlclNlcnZpY2UgfSBmcm9tICcuLi9sb2dnZXIuc2VydmljZSc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnXG59KVxuZXhwb3J0IGNsYXNzIEJsdWV0b290aFNlcnZpY2UgZXh0ZW5kcyBTdWJqZWN0PEJsdWV0b290aFNlcnZpY2U+IHtcbiAgcHVibGljIF9kZXZpY2UkOiBFdmVudEVtaXR0ZXI8Qmx1ZXRvb3RoRGV2aWNlPjtcbiAgcHJpdmF0ZSBkZXZpY2U6IEJsdWV0b290aERldmljZTtcbiAgcHJpdmF0ZSBzZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3QgPSB7fTtcbiAgcHJpdmF0ZSBub3RpZmllclN1YmplY3QgPSBuZXcgU3ViamVjdCgpO1xuICBwcml2YXRlIG5vdGlmaWVyU3RhcnRlZFN1YmplY3QgPSBuZXcgU3ViamVjdCgpO1xuICBwcml2YXRlIGJsdWV0b290aEF2YWlsYWJsZSA9IGZhbHNlO1xuICBjb25zdHJ1Y3RvcihcbiAgICBwdWJsaWMgX3dlYkJsZTogQnJvd3NlcldlYkJsdWV0b290aCxcbiAgICBwcml2YXRlIGN5cGhlckFlc1NlcnZpY2U6IEN5cGhlckFlc1NlcnZpY2UsXG4gICAgcHVibGljIF9jb25zb2xlOiBDb25zb2xlTG9nZ2VyU2VydmljZVxuICApIHtcbiAgICBzdXBlcigpO1xuICAgIHRoaXMuX2RldmljZSQgPSBuZXcgRXZlbnRFbWl0dGVyPEJsdWV0b290aERldmljZT4oKTtcbiAgICBpZiAoX3dlYkJsZS5fYmxlKSB7XG4gICAgICB0aGlzLmJsdWV0b290aEF2YWlsYWJsZSA9IHRydWU7XG4gICAgfVxuICB9XG5cbiAgaXNCbHVldG9vdGhBdmFpbGFibGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuYmx1ZXRvb3RoQXZhaWxhYmxlO1xuICB9XG4gIC8qKlxuICAgKiBnZXQgdGhlIGN1cnJlbnQgZGV2aWNlLCBpZiB0aGUgZGV2aWNlIHJldHVybiBudWxsIGlzIGJlY2F1c2UgdGhlIGNvbm5lY3Rpb24gaGFzIGxvc3RcbiAgICogQHJldHVybnMgdGhlIGN1cnJlbnQgY29ubmNldGVkIGRldmljZVxuICAgKi9cbiAgZ2V0RGV2aWNlJCgpOiBPYnNlcnZhYmxlPEJsdWV0b290aERldmljZT4ge1xuICAgIHJldHVybiB0aGlzLl9kZXZpY2UkO1xuICB9XG5cbiAgZ2V0Tm90aWZpZXJTdGFydGVkU3ViamVjdCQoKSB7XG4gICAgcmV0dXJuIHRoaXMubm90aWZpZXJTdGFydGVkU3ViamVjdDtcbiAgfVxuICAvKipcbiAgICogc3RhcnQgYSBzdHJlYW0gYnkgbm90aWZpZXJzIGNoYXJhY3RlcmlzdGljc1xuICAgKiBAcGFyYW0gc2VydmljZSBUaGUgc2VydmljZSB0byB3aGljaCB0aGUgY2hhcmFjdGVyaXN0aWMgYmVsb25nc1xuICAgKiBAcGFyYW0gY2hhcmFjdGVyaXN0aWMgVGhlIGNoYXJhY3RlcmlzdGljIHdob3NlIHZhbHVlIHlvdSB3YW50IHRvIGxpc3RlblxuICAgKiBAcGFyYW0gb3B0aW9ucyBvYmplY3QgdGhhdCBjb250YWlucyB0aGVcbiAgICogc3RhcnRCeXRlOm51bWJlciAocmVxdWlyZWQpLCBzdG9wQnl0ZTpudW1iZXIgKHJlcXVpcmVkKSxcbiAgICogbGVuZ3RoUG9zaXRpb246IHsgc3RhcnQ6IG51bWJlciwgZW5kOiBudW1iZXIsIGxlbmd0aFBhZGRpbmc6IG51bWJlciB9IChyZXF1aXJlZClcbiAgICogQHJldHVybnMgQSBEYXRhVmlldyB0aGFuIGNvbnRhaW5zIHRoZSBjaGFyYWN0ZXJpc3RpYyB2YWx1ZVxuICAgKi9cbiAgc3RhcnROb3RpZmllckxpc3RlbmVyJChzZXJ2aWNlLCBjaGFyYWN0ZXJpc3RpYywgb3B0aW9ucykge1xuICAgIHJldHVybiBkZWZlcigoKSA9PiB7XG4gICAgICBvZihzZXJ2aWNlKVxuICAgICAgICAucGlwZShcbiAgICAgICAgICB0YXAoKCkgPT4gdGhpcy5fY29uc29sZS5sb2coJ0luaWNpYSBlbCBub3RpZmllciBjb24gbGEgaW5zdGFuY2lhOiAnLCB0aGlzLnNlcnZpY2VDaGFyYWN0ZXJpc3RpY1ZzU3Vic2NyaXB0aW9uTGlzdCkpLFxuICAgICAgICAgIGZpbHRlcihcbiAgICAgICAgICAgIF8gPT5cbiAgICAgICAgICAgICAgT2JqZWN0LmtleXModGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3QpLmluZGV4T2YoXG4gICAgICAgICAgICAgICAgYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXG4gICAgICAgICAgICAgICkgPT09IC0xXG4gICAgICAgICAgKVxuICAgICAgICApXG4gICAgICAgIC5zdWJzY3JpYmUoXG4gICAgICAgICAgKCkgPT4ge1xuICAgICAgICAgICAgdGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3RbYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXSA9XG4gICAgICAgICAgICAgIHRoaXMuYnVpbGROb3RpZmllckxpc3RlbmVyJChcbiAgICAgICAgICAgICAgICBzZXJ2aWNlLFxuICAgICAgICAgICAgICAgIGNoYXJhY3RlcmlzdGljLFxuICAgICAgICAgICAgICAgIG9wdGlvbnNcbiAgICAgICAgICAgICAgKS5zdWJzY3JpYmUobWVzc2FnZSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5ub3RpZmllclN1YmplY3QubmV4dChtZXNzYWdlKTtcbiAgICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICB9LFxuICAgICAgICAgIGVyciA9PiB7XG4gICAgICAgICAgICB0aGlzLl9jb25zb2xlLmxvZygnW0JMRTo6SW5mb10gRXJyb3IgaW4gbm90aWZpZXI6ICcsIGVycik7XG4gICAgICAgICAgfSxcbiAgICAgICAgICAoKSA9PiB7IH1cbiAgICAgICk7XG4gICAgICByZXR1cm4gb2YoYG5vdGlmaWVyIGFzIHN1YnNjcmliZWQ6IHNlcnZpY2U9ICR7c2VydmljZX0sIGNoYXJhY3RlcmlzdGljPSAke2NoYXJhY3RlcmlzdGljfWApO1xuICAgIH0pO1xuXG4gIH1cblxuICBzdG9wTm90aWZpZXJMaXN0ZW5lciQoc2VydmljZSwgY2hhcmFjdGVyaXN0aWMpIHtcbiAgICByZXR1cm4gZGVmZXIoKCkgPT4ge1xuICAgICAgLy8gdGhpcy5zZXJ2aWNlQ2hhcmFjdGVyaXN0aWNWc1N1YnNjcmlwdGlvbkxpc3RbYCR7c2VydmljZX0tJHtjaGFyYWN0ZXJpc3RpY31gXS51bnN1YnNjcmliZSgpO1xuICAgICAgZGVsZXRlIHRoaXMuc2VydmljZUNoYXJhY3RlcmlzdGljVnNTdWJzY3JpcHRpb25MaXN0W2Ake3NlcnZpY2V9LSR7Y2hhcmFjdGVyaXN0aWN9YF07XG4gICAgICByZXR1cm4gb2YoYHRoZSBub3RpZmllciBvZiB0aGUgY2hhcmFjdGVyaXN0aWMgJHtjaGFyYWN0ZXJpc3RpY30gYXMgYmVlbiBzdG9wcGVkYCk7XG4gICAgfSk7XG4gIH1cblxuICBwcml2YXRlIGJ1aWxkTm90aWZpZXJMaXN0ZW5lciQoc2VydmljZSwgY2hhcmFjdGVyaXN0aWMsIG9wdGlvbnMpIHtcbiAgICByZXR1cm4gdGhpcy5nZXRQcmltYXJ5U2VydmljZSQoc2VydmljZSkucGlwZShcbiAgICAgIHRhcChzZXJ2aWNlSW5zdGFuY2UgPT4gdGhpcy5fY29uc29sZS5sb2coYHRvbWEgZXhpdG9zYW1lbnRlIGVsIHNlcnZpY2lvID09PT09PT09PT09PT09PT0+ICR7c2VydmljZUluc3RhbmNlfWApKSxcbiAgICAgIG1lcmdlTWFwKHByaW1hcnlTZXJ2aWNlID0+XG4gICAgICAgIHRoaXMuZ2V0Q2hhcmFjdGVyaXN0aWMkKHByaW1hcnlTZXJ2aWNlLCBjaGFyYWN0ZXJpc3RpYylcbiAgICAgICAgLnBpcGUodGFwKGNoYXIgPT4gdGhpcy5fY29uc29sZS5sb2coYHRvbWEgZXhpdG9zYW1lbnRlIGxhIGNhcmFjdGVyaXN0aWNhID09PT09PT09PT09PT09PT0+ICR7Y2hhcn1gKSkpXG4gICAgICApLFxuICAgICAgbWVyZ2VNYXAoKGNoYXI6IEJsdWV0b290aFJlbW90ZUdBVFRDaGFyYWN0ZXJpc3RpYykgPT4ge1xuICAgICAgICByZXR1cm4gZGVmZXIoKCkgPT4ge1xuICAgICAgICAgIC8vIGVuYWJsZSB0aGUgY2hhcmFjdGVyaXN0aWMgbm90aWZpZXJcbiAgICAgICAgICByZXR1cm4gY2hhci5zdGFydE5vdGlmaWNhdGlvbnMoKTtcbiAgICAgICAgfSkucGlwZShcbiAgICAgICAgICByZXRyeVdoZW4oZXJyb3IgPT5cbiAgICAgICAgICAgIGVycm9yLnBpcGUoXG4gICAgICAgICAgICAgIHRhcCgoKSA9PiB0aGlzLl9jb25zb2xlLmxvZygnRVJST1IgRU4gRUwgc3RhcnROb3RpZmljYXRpb25zID09PT09PT09PT09PT09PT0+ICcpKSxcbiAgICAgICAgICAgICAgZGVsYXkoMTAwMCksXG4gICAgICAgICAgICAgIHRha2UoNSlcbiAgICAgICA