UNPKG

@neurosity/sdk

Version:
326 lines (325 loc) 14.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BluetoothClient = void 0; const rxjs_1 = require("rxjs"); const rxjs_2 = require("rxjs"); const operators_1 = require("rxjs/operators"); const WebBluetoothTransport_1 = require("./web/WebBluetoothTransport"); const ReactNativeTransport_1 = require("./react-native/ReactNativeTransport"); const binaryBufferToEpoch_1 = require("./utils/binaryBufferToEpoch"); const types_1 = require("./types"); class BluetoothClient { constructor(options) { this.selectedDevice$ = new rxjs_2.ReplaySubject(1); this.osHasBluetoothSupport$ = new rxjs_2.ReplaySubject(1); this.isAuthenticated$ = new rxjs_2.ReplaySubject(1); const { transport, selectedDevice$, osHasBluetoothSupport$, createBluetoothToken } = options !== null && options !== void 0 ? options : {}; if (!transport) { throw new Error(`No bluetooth transport provided.`); } this.transport = transport; // Pass events to the internal selectedDevice$ if selectedDevice$ is passed via options if (selectedDevice$) { selectedDevice$.subscribe(this.selectedDevice$); } // Pass events to the internal osHasBluetoothSupport$ if osHasBluetoothSupport$ is passed via options if (osHasBluetoothSupport$) { osHasBluetoothSupport$.subscribe(this.osHasBluetoothSupport$); } this.osHasBluetoothSupport$ .pipe((0, operators_1.switchMap)((osHasBluetoothSupport) => osHasBluetoothSupport ? this.transport._autoConnect(this.selectedDevice$) : rxjs_2.EMPTY)) .subscribe({ error: (error) => { var _a; this.transport.addLog(`Auto connect: error -> ${(_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error}`); } }); // Auto authentication if (typeof createBluetoothToken === "function") { this.transport.addLog("Auto authentication enabled"); this._autoAuthenticate(createBluetoothToken).subscribe(); } else { this.transport.addLog("Auto authentication not enabled"); } // Auto manage action notifications this.osHasBluetoothSupport$ .pipe((0, operators_1.switchMap)((osHasBluetoothSupport) => osHasBluetoothSupport ? this.transport._autoToggleActionNotifications() : rxjs_2.EMPTY)) .subscribe(); // Multicast metrics (share) this._focus$ = this._subscribeWhileAuthenticated("focus"); this._calm$ = this._subscribeWhileAuthenticated("calm"); this._accelerometer$ = this._subscribeWhileAuthenticated("accelerometer"); this._brainwavesRaw$ = this._subscribeWhileAuthenticated("raw", true // skipJSONDecoding ); this._brainwavesRawUnfiltered$ = this._subscribeWhileAuthenticated("rawUnfiltered", true // skipJSONDecoding ); this._brainwavesPSD$ = this._subscribeWhileAuthenticated("psd"); this._brainwavesPowerByBand$ = this._subscribeWhileAuthenticated("powerByBand"); this._signalQuality$ = this._subscribeWhileAuthenticated("signalQuality"); this._status$ = this._subscribeWhileAuthenticated("status"); this._settings$ = this._subscribeWhileAuthenticated("settings"); this._wifiNearbyNetworks$ = this._subscribeWhileAuthenticated("wifiNearbyNetworks"); this._wifiConnections$ = this._subscribeWhileAuthenticated("wifiConnections"); } _autoAuthenticate(createBluetoothToken) { const REAUTHENTICATE_INTERVAL = 3600000; // 1 hour const reauthenticateInterval$ = (0, rxjs_1.timer)(0, REAUTHENTICATE_INTERVAL).pipe((0, operators_1.tap)(() => { this.transport.addLog(`Auto authentication in progress...`); })); return this.osHasBluetoothSupport$.pipe((0, operators_1.switchMap)((osHasBluetoothSupport) => osHasBluetoothSupport ? this.connection() : rxjs_2.EMPTY), (0, operators_1.switchMap)((connection) => connection === types_1.BLUETOOTH_CONNECTION.CONNECTED ? reauthenticateInterval$ : rxjs_2.EMPTY), (0, operators_1.switchMap)(() => __awaiter(this, void 0, void 0, function* () { return yield this.isAuthenticated(); })), (0, operators_1.tap)(([isAuthenticated]) => __awaiter(this, void 0, void 0, function* () { if (!isAuthenticated) { const token = yield createBluetoothToken(); yield this.authenticate(token); } else { this.transport.addLog(`Already authenticated`); } }))); } enableAutoConnect(autoConnect) { this.transport.enableAutoConnect(autoConnect); } _hasBluetoothSupport() { return __awaiter(this, void 0, void 0, function* () { return yield (0, rxjs_2.firstValueFrom)(this.osHasBluetoothSupport$); }); } authenticate(token) { return __awaiter(this, void 0, void 0, function* () { const hasBluetoothSupport = yield this._hasBluetoothSupport(); if (!hasBluetoothSupport) { const errorMessage = `authenticate method: The OS version does not support Bluetooth.`; this.transport.addLog(errorMessage); return Promise.reject(errorMessage); } yield this.transport.writeCharacteristic("auth", token); const isAuthenticatedResponse = yield this.isAuthenticated(); const [isAuthenticated] = isAuthenticatedResponse; this.transport.addLog(`Authentication ${isAuthenticated ? "succeeded" : "failed"}`); this.isAuthenticated$.next(isAuthenticated); return isAuthenticatedResponse; }); } isAuthenticated() { return __awaiter(this, void 0, void 0, function* () { try { const [isAuthenticated, expiresIn] = yield this.transport.readCharacteristic("auth", true); this.isAuthenticated$.next(isAuthenticated); return [isAuthenticated, expiresIn]; } catch (error) { const failedResponse = [false, null]; this.transport.addLog(`Authentication error -> ${error}`); this.isAuthenticated$.next(false); return failedResponse; } }); } // Method for React Native only scan(options) { if (this.transport instanceof ReactNativeTransport_1.ReactNativeTransport) { return this.transport.scan(options); } if (this.transport instanceof WebBluetoothTransport_1.WebBluetoothTransport) { throw new Error(`scan method is compatibly with the React Native transport only`); } throw new Error(`unknown transport`); } // Argument for React Native only connect(deviceNicknameORPeripheral) { if (this.transport instanceof ReactNativeTransport_1.ReactNativeTransport) { return this.transport.connect(deviceNicknameORPeripheral); } if (this.transport instanceof WebBluetoothTransport_1.WebBluetoothTransport) { return deviceNicknameORPeripheral ? this.transport.connect(deviceNicknameORPeripheral) : this.transport.connect(); } } disconnect() { return this.transport.disconnect(); } connection() { return this.transport.connection(); } logs() { return this.transport.logs$.asObservable(); } getDeviceId() { return __awaiter(this, void 0, void 0, function* () { // This is a public characteristic and does not require authentication return this.transport.readCharacteristic("deviceId"); }); } _withAuthentication(getter) { return __awaiter(this, void 0, void 0, function* () { // First check if the OS supports Bluetooth before checking if the device is authenticated const hasBluetoothSupport = yield this._hasBluetoothSupport(); if (!hasBluetoothSupport) { const errorMessage = `The OS version does not support Bluetooth.`; this.transport.addLog(errorMessage); return Promise.reject(errorMessage); } const isAuthenticated = yield (0, rxjs_2.firstValueFrom)(this.isAuthenticated$); if (!isAuthenticated) { const errorMessage = `Authentication required.`; this.transport.addLog(errorMessage); return Promise.reject(errorMessage); } return yield getter(); }); } _subscribeWhileAuthenticated(characteristicName, skipJSONDecoding = false) { return this.osHasBluetoothSupport$.pipe((0, operators_1.switchMap)((osHasBluetoothSupport) => osHasBluetoothSupport ? this.isAuthenticated$ : rxjs_2.EMPTY), (0, operators_1.distinctUntilChanged)(), (0, operators_1.switchMap)((isAuthenticated) => isAuthenticated ? this.transport.subscribeToCharacteristic({ characteristicName, skipJSONDecoding }) : rxjs_2.EMPTY), (0, operators_1.share)()); } focus() { return this._focus$; } calm() { return this._calm$; } accelerometer() { return this._accelerometer$; } brainwaves(label) { switch (label) { default: case "raw": return (0, rxjs_1.defer)(() => this.getInfo()).pipe((0, operators_1.switchMap)((deviceInfo) => this._brainwavesRaw$.pipe((0, binaryBufferToEpoch_1.binaryBufferToEpoch)(deviceInfo)))); case "rawUnfiltered": return (0, rxjs_1.defer)(() => this.getInfo()).pipe((0, operators_1.switchMap)((deviceInfo) => this._brainwavesRawUnfiltered$.pipe((0, binaryBufferToEpoch_1.binaryBufferToEpoch)(deviceInfo)))); case "psd": return this._brainwavesPSD$; case "powerByBand": return this._brainwavesPowerByBand$; } } signalQuality() { return this._signalQuality$; } addMarker(label) { return __awaiter(this, void 0, void 0, function* () { yield this.dispatchAction({ action: "marker", command: "add", message: { timestamp: Date.now(), label } }); }); } getInfo() { return __awaiter(this, void 0, void 0, function* () { return yield this._withAuthentication(() => (0, rxjs_2.firstValueFrom)(this.transport.subscribeToCharacteristic({ characteristicName: "deviceInfo" }))); }); } status() { return this._status$; } dispatchAction(action) { return __awaiter(this, void 0, void 0, function* () { return yield this._withAuthentication(() => this.transport.dispatchAction({ characteristicName: "actions", action })); }); } settings() { return this._settings$; } haptics(effects) { const metric = "haptics"; return this.dispatchAction({ action: metric, command: "queue", responseRequired: true, responseTimeout: 4000, // @TODO: implement validation logic as per SDK message: { effects } }); } get wifi() { return { nearbyNetworks: () => this._wifiNearbyNetworks$, connections: () => this._wifiConnections$, connect: (ssid, password) => { if (!ssid) { return Promise.reject(`Missing ssid`); } return this.dispatchAction({ action: "wifi", command: "connect", responseRequired: true, responseTimeout: 1000 * 60 * 2, message: { ssid, password: password !== null && password !== void 0 ? password : null } }); }, forgetConnection: (ssid) => { if (!ssid) { return Promise.reject(`Missing ssid`); } return this.dispatchAction({ action: "wifi", command: "forget-connection", responseRequired: true, responseTimeout: 1000 * 15, message: { ssid } }); }, reset: () => { return this.dispatchAction({ action: "wifi", command: "reset", responseRequired: true, responseTimeout: 1000 * 30, message: { // without this, the action will resolve as soon as the // action is received by the OS respondOnSuccess: true } }); }, speedTest: () => { return this.dispatchAction({ action: "wifi", command: "speed-test", responseRequired: true, responseTimeout: 1000 * 60 * 1 // 1 minute }); } }; } } exports.BluetoothClient = BluetoothClient;