import { Injectable, NgZone } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Node } from './node'; // Hacky way to require noble. Find a solution to use // import * as noble from 'noble'; // without the compiler flipping out. const noble = window['require']('noble'); @Injectable() export class NodeManagerService { peripherals: Observable = this.discoveredPeripherals().publishReplay().refCount(); nodes: Observable = this.peripherals .scan((nodes: Node[], peripherals: any[]) => { const newNodes = peripherals .filter(peripheral => !nodes.find(node => node.id === peripheral.id)) .map(peripheral => new Node(peripheral, this.zone)); return nodes.concat(newNodes); }, []); state: Observable = new Observable(observer => { const listener = state => { this.zone.run(() => observer.next(state)); }; noble.on('stateChange', listener); return () => noble.removeListener('stateChange', listener); }); constructor(public zone: NgZone) {} /** * Returns an observable that spits out every new discovered peripheral */ private discoveredPeripheral(): Observable { const observable = new Observable(observer => { const listener = (peripheral: any) => { this.zone.run(() => observer.next(peripheral)); }; noble.on('discover', listener); return () => noble.removeListener('discover', listener); }); return observable; } /** * Returns an observable that keeps a list of all discovered peripherals */ private discoveredPeripherals(): Observable { const filterBF = function filterPeripheral(value) { const name: string = value.advertisement.localName; return name ? name.startsWith('BF ') : false; }; // * replace old peripherals with new ones // * add timestamp to indicate peripheral's last sign of life const scanner = function scanner(accumulator: Object[], peripheral) { peripheral.seen = new Date(); const index = accumulator.findIndex((p: any) => p.id === peripheral.id); if (index < 0) { return accumulator.concat(peripheral); } return accumulator.map((value, i) => i === index ? peripheral : value); }; return this.discoveredPeripheral() .filter(filterBF) .scan(scanner, []); } /* PUBLIC FUNCTIONS */ startScanning() { if (noble.state === 'poweredOn') { noble.startScanning([], /* allow duplicates */ true); return true; } else { return false; } } stopScanning() { noble.stopScanning(); } getNode(id): Observable { return this.nodes .map(nodes => nodes.find(node => node.id === id)) .filter(node => !!node); } }