@neurosity/sdk 6.5.9 → 6.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/neurosity.iife.js +2419 -1009
- package/dist/browser/neurosity.js +180 -148
- package/dist/browser/neurosity.js.map +1 -1
- package/dist/cjs/__tests__/Neurosity.test.d.ts +1 -0
- package/dist/cjs/__tests__/Neurosity.test.js +201 -0
- package/dist/cjs/__tests__/WebBluetoothTransport.test.d.ts +1 -0
- package/dist/cjs/__tests__/WebBluetoothTransport.test.js +201 -0
- package/dist/cjs/__tests__/accelerometer.test.d.ts +1 -0
- package/dist/cjs/__tests__/accelerometer.test.js +158 -0
- package/dist/cjs/__tests__/auth.test.d.ts +1 -0
- package/dist/cjs/__tests__/auth.test.js +239 -0
- package/dist/cjs/__tests__/brainwaves.test.d.ts +1 -0
- package/dist/cjs/__tests__/brainwaves.test.js +291 -0
- package/dist/cjs/__tests__/device.test.d.ts +1 -0
- package/dist/cjs/__tests__/device.test.js +281 -0
- package/dist/cjs/__tests__/haptics.test.d.ts +1 -0
- package/dist/cjs/__tests__/haptics.test.js +162 -0
- package/dist/cjs/__tests__/metrics.test.d.ts +1 -0
- package/dist/cjs/__tests__/metrics.test.js +178 -0
- package/dist/cjs/__tests__/oauth.test.d.ts +1 -0
- package/dist/cjs/__tests__/oauth.test.js +138 -0
- package/dist/cjs/__tests__/settings.test.d.ts +1 -0
- package/dist/cjs/__tests__/settings.test.js +130 -0
- package/dist/cjs/__tests__/setup/webBluetooth.setup.d.ts +11 -0
- package/dist/cjs/__tests__/setup/webBluetooth.setup.js +35 -0
- package/dist/cjs/__tests__/setup.d.ts +0 -0
- package/dist/cjs/__tests__/setup.js +7 -0
- package/dist/cjs/__tests__/streaming.test.d.ts +1 -0
- package/dist/cjs/__tests__/streaming.test.js +259 -0
- package/dist/cjs/__tests__/timesync.test.d.ts +1 -0
- package/dist/cjs/__tests__/timesync.test.js +54 -0
- package/dist/cjs/__tests__/utils.test.d.ts +1 -0
- package/dist/cjs/__tests__/utils.test.js +281 -0
- package/dist/cjs/api/bluetooth/BluetoothClient.d.ts +6 -6
- package/dist/cjs/api/bluetooth/BluetoothTransport.d.ts +1 -1
- package/dist/cjs/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
- package/dist/cjs/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
- package/dist/cjs/api/bluetooth/types/index.d.ts +2 -2
- package/dist/cjs/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
- package/dist/cjs/api/bluetooth/utils/stitch.d.ts +1 -1
- package/dist/cjs/api/bluetooth/utils/textCodec.d.ts +1 -1
- package/dist/cjs/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
- package/dist/cjs/api/firebase/FirebaseDevice.d.ts +1 -1
- package/dist/cjs/api/firebase/FirebaseUser.d.ts +1 -1
- package/dist/cjs/api/index.js +1 -1
- package/dist/cjs/timesync/Timesync.d.ts +1 -1
- package/dist/cjs/types/awareness.d.ts +1 -1
- package/dist/cjs/types/brainwaves.d.ts +25 -12
- package/dist/cjs/types/credentials.d.ts +4 -4
- package/dist/cjs/types/deviceInfo.d.ts +4 -4
- package/dist/cjs/types/epoch.d.ts +1 -1
- package/dist/cjs/types/experiment.d.ts +1 -1
- package/dist/cjs/types/hapticEffects.d.ts +1 -1
- package/dist/cjs/types/marker.d.ts +1 -1
- package/dist/cjs/types/metrics.d.ts +2 -2
- package/dist/cjs/types/oauth.d.ts +4 -4
- package/dist/cjs/types/sample.d.ts +2 -2
- package/dist/cjs/types/signalQuality.d.ts +1 -1
- package/dist/cjs/types/skill.d.ts +2 -2
- package/dist/cjs/types/user.d.ts +3 -3
- package/dist/cjs/utils/oauth.d.ts +1 -1
- package/dist/cjs/utils/transferDevice.d.ts +3 -3
- package/dist/cjs/utils/whileOnline.d.ts +1 -1
- package/dist/electron/index.js +1 -1
- package/dist/electron/index.js.map +1 -1
- package/dist/esm/__tests__/Neurosity.test.d.ts +1 -0
- package/dist/esm/__tests__/Neurosity.test.js +199 -0
- package/dist/esm/__tests__/WebBluetoothTransport.test.d.ts +1 -0
- package/dist/esm/__tests__/WebBluetoothTransport.test.js +199 -0
- package/dist/esm/__tests__/accelerometer.test.d.ts +1 -0
- package/dist/esm/__tests__/accelerometer.test.js +156 -0
- package/dist/esm/__tests__/auth.test.d.ts +1 -0
- package/dist/esm/__tests__/auth.test.js +237 -0
- package/dist/esm/__tests__/brainwaves.test.d.ts +1 -0
- package/dist/esm/__tests__/brainwaves.test.js +289 -0
- package/dist/esm/__tests__/device.test.d.ts +1 -0
- package/dist/esm/__tests__/device.test.js +279 -0
- package/dist/esm/__tests__/haptics.test.d.ts +1 -0
- package/dist/esm/__tests__/haptics.test.js +160 -0
- package/dist/esm/__tests__/metrics.test.d.ts +1 -0
- package/dist/esm/__tests__/metrics.test.js +176 -0
- package/dist/esm/__tests__/oauth.test.d.ts +1 -0
- package/dist/esm/__tests__/oauth.test.js +133 -0
- package/dist/esm/__tests__/settings.test.d.ts +1 -0
- package/dist/esm/__tests__/settings.test.js +128 -0
- package/dist/esm/__tests__/setup/webBluetooth.setup.d.ts +11 -0
- package/dist/esm/__tests__/setup/webBluetooth.setup.js +35 -0
- package/dist/esm/__tests__/setup.d.ts +0 -0
- package/dist/esm/__tests__/setup.js +7 -0
- package/dist/esm/__tests__/streaming.test.d.ts +1 -0
- package/dist/esm/__tests__/streaming.test.js +257 -0
- package/dist/esm/__tests__/timesync.test.d.ts +1 -0
- package/dist/esm/__tests__/timesync.test.js +52 -0
- package/dist/esm/__tests__/utils.test.d.ts +1 -0
- package/dist/esm/__tests__/utils.test.js +279 -0
- package/dist/esm/api/bluetooth/BluetoothClient.d.ts +6 -6
- package/dist/esm/api/bluetooth/BluetoothTransport.d.ts +1 -1
- package/dist/esm/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
- package/dist/esm/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
- package/dist/esm/api/bluetooth/types/index.d.ts +2 -2
- package/dist/esm/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
- package/dist/esm/api/bluetooth/utils/stitch.d.ts +1 -1
- package/dist/esm/api/bluetooth/utils/textCodec.d.ts +1 -1
- package/dist/esm/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
- package/dist/esm/api/firebase/FirebaseDevice.d.ts +1 -1
- package/dist/esm/api/firebase/FirebaseUser.d.ts +1 -1
- package/dist/esm/api/index.js +2 -2
- package/dist/esm/neurosity.mjs +2480 -1007
- package/dist/esm/timesync/Timesync.d.ts +1 -1
- package/dist/esm/types/awareness.d.ts +1 -1
- package/dist/esm/types/brainwaves.d.ts +25 -12
- package/dist/esm/types/credentials.d.ts +4 -4
- package/dist/esm/types/deviceInfo.d.ts +4 -4
- package/dist/esm/types/epoch.d.ts +1 -1
- package/dist/esm/types/experiment.d.ts +1 -1
- package/dist/esm/types/hapticEffects.d.ts +1 -1
- package/dist/esm/types/marker.d.ts +1 -1
- package/dist/esm/types/metrics.d.ts +2 -2
- package/dist/esm/types/oauth.d.ts +4 -4
- package/dist/esm/types/sample.d.ts +2 -2
- package/dist/esm/types/signalQuality.d.ts +1 -1
- package/dist/esm/types/skill.d.ts +2 -2
- package/dist/esm/types/user.d.ts +3 -3
- package/dist/esm/utils/oauth.d.ts +1 -1
- package/dist/esm/utils/transferDevice.d.ts +3 -3
- package/dist/esm/utils/whileOnline.d.ts +1 -1
- package/dist/examples/neurosity.iife.js +2419 -1009
- package/dist/examples/neurosity.js +180 -148
- package/dist/examples/neurosity.mjs +2480 -1007
- package/package.json +17 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import { describe } from "node:test";
|
|
12
|
+
import { Neurosity } from "../Neurosity";
|
|
13
|
+
import { firstValueFrom, take, of, ReplaySubject } from "rxjs";
|
|
14
|
+
import { STATUS } from "../types/status";
|
|
15
|
+
// Mock Firebase modules
|
|
16
|
+
jest.mock("../api/firebase", () => {
|
|
17
|
+
const mockFirebaseApp = jest.fn().mockImplementation(() => ({
|
|
18
|
+
disconnect: jest.fn(),
|
|
19
|
+
useEmulator: jest.fn()
|
|
20
|
+
}));
|
|
21
|
+
mockFirebaseApp.prototype.constructor = mockFirebaseApp;
|
|
22
|
+
const mockFirebaseUser = jest.fn().mockImplementation(() => ({
|
|
23
|
+
login: jest.fn().mockResolvedValue({}),
|
|
24
|
+
logout: jest.fn().mockResolvedValue({}),
|
|
25
|
+
onAuthStateChanged: jest.fn().mockReturnValue(of(null)),
|
|
26
|
+
onUserClaimsChange: jest.fn().mockReturnValue(of({}))
|
|
27
|
+
}));
|
|
28
|
+
mockFirebaseUser.prototype.constructor = mockFirebaseUser;
|
|
29
|
+
const mockFirebaseDevice = jest.fn().mockImplementation(() => ({
|
|
30
|
+
disconnect: jest.fn(),
|
|
31
|
+
getInfo: jest.fn().mockResolvedValue({}),
|
|
32
|
+
selectDevice: jest.fn().mockResolvedValue({}),
|
|
33
|
+
dispatchAction: jest.fn()
|
|
34
|
+
}));
|
|
35
|
+
mockFirebaseDevice.prototype.constructor = mockFirebaseDevice;
|
|
36
|
+
return {
|
|
37
|
+
FirebaseApp: mockFirebaseApp,
|
|
38
|
+
FirebaseUser: mockFirebaseUser,
|
|
39
|
+
FirebaseDevice: mockFirebaseDevice
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// Mock CloudClient
|
|
43
|
+
jest.mock("../api", () => {
|
|
44
|
+
const originalModule = jest.requireActual("../api");
|
|
45
|
+
class MockCloudClient {
|
|
46
|
+
constructor(options) {
|
|
47
|
+
this.user = null;
|
|
48
|
+
this.userClaims = { scopes: ["brainwaves"] };
|
|
49
|
+
this.subscriptionManager = {
|
|
50
|
+
add: jest.fn(),
|
|
51
|
+
remove: jest.fn(),
|
|
52
|
+
removeAll: jest.fn()
|
|
53
|
+
};
|
|
54
|
+
this._selectedDevice = new ReplaySubject(1);
|
|
55
|
+
this.login = jest.fn().mockResolvedValue({});
|
|
56
|
+
this.logout = jest.fn().mockResolvedValue({});
|
|
57
|
+
this.getInfo = jest.fn().mockResolvedValue({});
|
|
58
|
+
this.selectDevice = jest.fn().mockResolvedValue({});
|
|
59
|
+
this.didSelectDevice = jest.fn().mockResolvedValue(true);
|
|
60
|
+
this.onDeviceChange = jest.fn().mockReturnValue(of({
|
|
61
|
+
deviceId: "test-device-id",
|
|
62
|
+
status: STATUS.ONLINE
|
|
63
|
+
}));
|
|
64
|
+
this.osVersion = jest.fn().mockReturnValue(of("1.0.0"));
|
|
65
|
+
this.status = jest.fn().mockReturnValue(of({ state: STATUS.ONLINE }));
|
|
66
|
+
this.metrics = {
|
|
67
|
+
subscribe: jest.fn().mockReturnValue(of({})),
|
|
68
|
+
on: jest.fn().mockImplementation((subscription, callback) => {
|
|
69
|
+
callback({});
|
|
70
|
+
return jest.fn();
|
|
71
|
+
}),
|
|
72
|
+
unsubscribe: jest.fn()
|
|
73
|
+
};
|
|
74
|
+
this.options = options;
|
|
75
|
+
this._selectedDevice.next(undefined);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Object.assign(Object.assign({}, originalModule), { CloudClient: jest
|
|
79
|
+
.fn()
|
|
80
|
+
.mockImplementation((options) => new MockCloudClient(options)) });
|
|
81
|
+
});
|
|
82
|
+
describe("Neurosity", () => {
|
|
83
|
+
let neurosity;
|
|
84
|
+
const testDeviceId = "test-device-id";
|
|
85
|
+
const testEmail = "test@example.com";
|
|
86
|
+
const testPassword = "test-password";
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
jest.clearAllMocks();
|
|
89
|
+
neurosity = new Neurosity({
|
|
90
|
+
deviceId: testDeviceId,
|
|
91
|
+
emulator: true
|
|
92
|
+
});
|
|
93
|
+
// Mock _osHasBluetoothSupport to return false to avoid Bluetooth-related code paths
|
|
94
|
+
neurosity["_osHasBluetoothSupport"] = jest.fn().mockReturnValue(of(false));
|
|
95
|
+
});
|
|
96
|
+
describe("Initialization", () => {
|
|
97
|
+
test("should initialize with default options when no options provided", () => {
|
|
98
|
+
const instance = new Neurosity();
|
|
99
|
+
expect(instance).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
test("should initialize with provided deviceId", () => {
|
|
102
|
+
const instance = new Neurosity({ deviceId: testDeviceId });
|
|
103
|
+
expect(instance).toBeDefined();
|
|
104
|
+
});
|
|
105
|
+
test("should initialize with custom options", () => {
|
|
106
|
+
const instance = new Neurosity({
|
|
107
|
+
deviceId: testDeviceId,
|
|
108
|
+
emulator: true,
|
|
109
|
+
timesync: false
|
|
110
|
+
});
|
|
111
|
+
expect(instance).toBeDefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe("Authentication", () => {
|
|
115
|
+
test("should handle login with email and password", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
yield expect(neurosity.login({ email: testEmail, password: testPassword })).resolves.not.toThrow();
|
|
117
|
+
}));
|
|
118
|
+
test("should handle logout", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
119
|
+
yield expect(neurosity.logout()).resolves.not.toThrow();
|
|
120
|
+
}));
|
|
121
|
+
});
|
|
122
|
+
describe("Device Management", () => {
|
|
123
|
+
test("should get device info", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
124
|
+
const mockInfo = {
|
|
125
|
+
deviceId: testDeviceId,
|
|
126
|
+
deviceNickname: "Test Device",
|
|
127
|
+
channelNames: ["CH1", "CH2"],
|
|
128
|
+
channels: 2,
|
|
129
|
+
samplingRate: 250,
|
|
130
|
+
manufacturer: "Neurosity",
|
|
131
|
+
model: "Crown",
|
|
132
|
+
modelName: "Crown",
|
|
133
|
+
modelVersion: "v1",
|
|
134
|
+
apiVersion: "1.0.0",
|
|
135
|
+
osVersion: "1.0.0",
|
|
136
|
+
emulator: false
|
|
137
|
+
};
|
|
138
|
+
neurosity["cloudClient"].getInfo.mockResolvedValueOnce(mockInfo);
|
|
139
|
+
const info = yield neurosity.getInfo();
|
|
140
|
+
expect(info).toEqual(mockInfo);
|
|
141
|
+
}));
|
|
142
|
+
test("should handle device selection", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
143
|
+
const mockDevice = {
|
|
144
|
+
deviceId: testDeviceId,
|
|
145
|
+
deviceNickname: "Test Device",
|
|
146
|
+
channelNames: ["CH1", "CH2"],
|
|
147
|
+
channels: 2,
|
|
148
|
+
samplingRate: 250,
|
|
149
|
+
manufacturer: "Neurosity",
|
|
150
|
+
model: "Crown",
|
|
151
|
+
modelName: "Crown",
|
|
152
|
+
modelVersion: "v1",
|
|
153
|
+
apiVersion: "1.0.0",
|
|
154
|
+
osVersion: "1.0.0",
|
|
155
|
+
emulator: false
|
|
156
|
+
};
|
|
157
|
+
neurosity["cloudClient"].selectDevice.mockResolvedValueOnce(mockDevice);
|
|
158
|
+
yield expect(neurosity.selectDevice((devices) => {
|
|
159
|
+
const device = devices.find((d) => d.deviceId === testDeviceId);
|
|
160
|
+
if (!device) {
|
|
161
|
+
throw new Error("Device not found");
|
|
162
|
+
}
|
|
163
|
+
return device;
|
|
164
|
+
})).resolves.not.toThrow();
|
|
165
|
+
}));
|
|
166
|
+
});
|
|
167
|
+
describe("Streaming", () => {
|
|
168
|
+
test("should stream brainwaves", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
const mockRawData = {
|
|
170
|
+
data: [
|
|
171
|
+
[1, 2, 3, 4, 5, 6, 7, 8],
|
|
172
|
+
[9, 10, 11, 12, 13, 14, 15, 16]
|
|
173
|
+
],
|
|
174
|
+
info: {
|
|
175
|
+
samplingRate: 256,
|
|
176
|
+
startTime: Date.now()
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
// Mock the getCloudMetric function to return our mock data
|
|
180
|
+
neurosity["_getCloudMetricDependencies"] = jest.fn().mockReturnValue({
|
|
181
|
+
options: {
|
|
182
|
+
deviceId: testDeviceId,
|
|
183
|
+
emulator: true
|
|
184
|
+
},
|
|
185
|
+
cloudClient: neurosity["cloudClient"],
|
|
186
|
+
onDeviceChange: neurosity["cloudClient"].onDeviceChange,
|
|
187
|
+
status: neurosity["cloudClient"].status,
|
|
188
|
+
getCloudMetric: jest.fn().mockReturnValue(of(mockRawData))
|
|
189
|
+
});
|
|
190
|
+
const brainwaves = yield firstValueFrom(neurosity.brainwaves("raw").pipe(take(1)));
|
|
191
|
+
expect(brainwaves).toBeDefined();
|
|
192
|
+
if ("data" in brainwaves) {
|
|
193
|
+
expect(Array.isArray(brainwaves.data)).toBe(true);
|
|
194
|
+
expect(brainwaves.data.length).toBe(2);
|
|
195
|
+
expect(brainwaves.data[0].length).toBe(8);
|
|
196
|
+
}
|
|
197
|
+
}));
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "./setup/webBluetooth.setup";
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import "./setup/webBluetooth.setup";
|
|
11
|
+
import { BLUETOOTH_PRIMARY_SERVICE_UUID_HEX } from "@neurosity/ipk";
|
|
12
|
+
import { BLUETOOTH_DEVICE_NAME_PREFIXES } from "@neurosity/ipk";
|
|
13
|
+
import { BLUETOOTH_COMPANY_IDENTIFIER_HEX } from "@neurosity/ipk";
|
|
14
|
+
import { NEVER } from "rxjs";
|
|
15
|
+
import { WebBluetoothTransport } from "../api/bluetooth/web/WebBluetoothTransport";
|
|
16
|
+
import { BLUETOOTH_CONNECTION, TRANSPORT_TYPE } from "../api/bluetooth/types";
|
|
17
|
+
// Get the mock function for isWebBluetoothSupported
|
|
18
|
+
const mockIsWebBluetoothSupported = jest.requireMock("../api/bluetooth/web/isWebBluetoothSupported").isWebBluetoothSupported;
|
|
19
|
+
describe("WebBluetoothTransport", () => {
|
|
20
|
+
let transport;
|
|
21
|
+
let mockDevice;
|
|
22
|
+
let mockServer;
|
|
23
|
+
let mockService;
|
|
24
|
+
let mockCharacteristic;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
// Mock device and GATT objects
|
|
27
|
+
mockCharacteristic = {
|
|
28
|
+
uuid: "characteristic-uuid",
|
|
29
|
+
properties: {
|
|
30
|
+
write: true,
|
|
31
|
+
notify: true
|
|
32
|
+
},
|
|
33
|
+
startNotifications: jest.fn().mockResolvedValue(undefined),
|
|
34
|
+
addEventListener: jest.fn(),
|
|
35
|
+
removeEventListener: jest.fn(),
|
|
36
|
+
writeValue: jest.fn().mockResolvedValue(undefined)
|
|
37
|
+
};
|
|
38
|
+
mockService = {
|
|
39
|
+
uuid: BLUETOOTH_PRIMARY_SERVICE_UUID_HEX,
|
|
40
|
+
getCharacteristics: jest.fn().mockResolvedValue([mockCharacteristic])
|
|
41
|
+
};
|
|
42
|
+
mockServer = {
|
|
43
|
+
connected: true,
|
|
44
|
+
getPrimaryService: jest.fn().mockResolvedValue(mockService),
|
|
45
|
+
connect: jest.fn().mockResolvedValue(undefined),
|
|
46
|
+
disconnect: jest.fn()
|
|
47
|
+
};
|
|
48
|
+
mockDevice = {
|
|
49
|
+
id: "test-device",
|
|
50
|
+
name: "Test Device",
|
|
51
|
+
gatt: mockServer,
|
|
52
|
+
watchAdvertisements: jest.fn().mockResolvedValue(undefined),
|
|
53
|
+
addEventListener: jest.fn(),
|
|
54
|
+
removeEventListener: jest.fn()
|
|
55
|
+
};
|
|
56
|
+
// Mock navigator.bluetooth methods
|
|
57
|
+
window.navigator.bluetooth.getDevices.mockResolvedValue([]);
|
|
58
|
+
window.navigator.bluetooth.requestDevice.mockResolvedValue(mockDevice);
|
|
59
|
+
transport = new WebBluetoothTransport();
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
jest.clearAllMocks();
|
|
63
|
+
});
|
|
64
|
+
it("should initialize with correct type and default options", () => {
|
|
65
|
+
expect(transport.type).toBe(TRANSPORT_TYPE.WEB);
|
|
66
|
+
expect(transport.options.autoConnect).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
it("should throw error if Web Bluetooth is not supported", () => {
|
|
69
|
+
mockIsWebBluetoothSupported.mockReturnValueOnce(false);
|
|
70
|
+
expect(() => new WebBluetoothTransport()).toThrow("Web Bluetooth is not supported");
|
|
71
|
+
});
|
|
72
|
+
it("should enable/disable auto connect", (done) => {
|
|
73
|
+
transport.enableAutoConnect(false);
|
|
74
|
+
transport.connection().subscribe((connection) => {
|
|
75
|
+
expect(connection).toBe(BLUETOOTH_CONNECTION.DISCONNECTED);
|
|
76
|
+
done();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it("should handle connection state changes", (done) => {
|
|
80
|
+
const states = [];
|
|
81
|
+
transport.connection().subscribe((state) => {
|
|
82
|
+
states.push(state);
|
|
83
|
+
if (states.length === 2) {
|
|
84
|
+
expect(states).toEqual([
|
|
85
|
+
BLUETOOTH_CONNECTION.DISCONNECTED,
|
|
86
|
+
BLUETOOTH_CONNECTION.CONNECTED
|
|
87
|
+
]);
|
|
88
|
+
done();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
transport.connection$.next(BLUETOOTH_CONNECTION.CONNECTED);
|
|
92
|
+
});
|
|
93
|
+
it("should request device with correct options", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
+
const deviceNickname = "Test Device";
|
|
95
|
+
yield transport.requestDevice(deviceNickname);
|
|
96
|
+
expect(window.navigator.bluetooth.requestDevice).toHaveBeenCalledWith({
|
|
97
|
+
filters: [
|
|
98
|
+
{
|
|
99
|
+
name: deviceNickname
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
manufacturerData: [
|
|
103
|
+
{
|
|
104
|
+
companyIdentifier: BLUETOOTH_COMPANY_IDENTIFIER_HEX
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
optionalServices: [BLUETOOTH_PRIMARY_SERVICE_UUID_HEX]
|
|
110
|
+
});
|
|
111
|
+
}));
|
|
112
|
+
it("should request device with prefixes when no nickname provided", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
|
+
yield transport.requestDevice();
|
|
114
|
+
expect(window.navigator.bluetooth.requestDevice).toHaveBeenCalledWith({
|
|
115
|
+
filters: [
|
|
116
|
+
...BLUETOOTH_DEVICE_NAME_PREFIXES.map((namePrefix) => ({
|
|
117
|
+
namePrefix
|
|
118
|
+
})),
|
|
119
|
+
{
|
|
120
|
+
manufacturerData: [
|
|
121
|
+
{
|
|
122
|
+
companyIdentifier: BLUETOOTH_COMPANY_IDENTIFIER_HEX
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
optionalServices: [BLUETOOTH_PRIMARY_SERVICE_UUID_HEX]
|
|
128
|
+
});
|
|
129
|
+
}));
|
|
130
|
+
it("should connect to device and setup GATT server", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
131
|
+
mockServer.connect.mockResolvedValueOnce(mockServer);
|
|
132
|
+
yield transport.getServerServiceAndCharacteristics(mockDevice);
|
|
133
|
+
expect(mockServer.connect).toHaveBeenCalled();
|
|
134
|
+
expect(mockServer.getPrimaryService).toHaveBeenCalledWith(BLUETOOTH_PRIMARY_SERVICE_UUID_HEX);
|
|
135
|
+
expect(mockService.getCharacteristics).toHaveBeenCalled();
|
|
136
|
+
expect(transport.connection$.getValue()).toBe(BLUETOOTH_CONNECTION.CONNECTED);
|
|
137
|
+
}));
|
|
138
|
+
it("should handle disconnection", (done) => {
|
|
139
|
+
mockServer.connect.mockResolvedValueOnce(mockServer);
|
|
140
|
+
transport.device = mockDevice;
|
|
141
|
+
transport.server = mockServer;
|
|
142
|
+
transport.connection$.next(BLUETOOTH_CONNECTION.CONNECTED);
|
|
143
|
+
transport.connection().subscribe((connection) => {
|
|
144
|
+
if (connection === BLUETOOTH_CONNECTION.DISCONNECTED) {
|
|
145
|
+
done();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
// Simulate disconnection
|
|
149
|
+
const disconnectCallback = mockDevice.addEventListener.mock.calls.find(([eventName]) => eventName === "gattserverdisconnected")[1];
|
|
150
|
+
disconnectCallback();
|
|
151
|
+
});
|
|
152
|
+
it("should auto connect when enabled", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
+
const selectedDevice$ = NEVER;
|
|
154
|
+
const autoConnect$ = transport._autoConnect(selectedDevice$);
|
|
155
|
+
// Subscribe to auto connect observable
|
|
156
|
+
autoConnect$.subscribe();
|
|
157
|
+
// Verify auto connect is enabled
|
|
158
|
+
expect(transport.options.autoConnect).toBe(true);
|
|
159
|
+
}));
|
|
160
|
+
it("should get paired devices", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
161
|
+
const devices = [mockDevice];
|
|
162
|
+
window.navigator.bluetooth.getDevices.mockResolvedValueOnce(devices);
|
|
163
|
+
const result = yield transport._getPairedDevices();
|
|
164
|
+
expect(result).toEqual(devices);
|
|
165
|
+
}));
|
|
166
|
+
it("should check connection status", () => {
|
|
167
|
+
transport.connection$.next(BLUETOOTH_CONNECTION.CONNECTED);
|
|
168
|
+
expect(transport.isConnected()).toBe(true);
|
|
169
|
+
transport.connection$.next(BLUETOOTH_CONNECTION.DISCONNECTED);
|
|
170
|
+
expect(transport.isConnected()).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
it("should add logs", (done) => {
|
|
173
|
+
const testLog = "Test log message";
|
|
174
|
+
transport.logs$.subscribe((log) => {
|
|
175
|
+
expect(log).toBe(testLog);
|
|
176
|
+
done();
|
|
177
|
+
});
|
|
178
|
+
transport.addLog(testLog);
|
|
179
|
+
});
|
|
180
|
+
it("should handle connection errors", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
181
|
+
const error = new Error("Connection failed");
|
|
182
|
+
mockServer.connect.mockRejectedValueOnce(error);
|
|
183
|
+
yield expect(transport.getServerServiceAndCharacteristics(mockDevice)).rejects.toThrow("Connection failed");
|
|
184
|
+
expect(transport.connection$.getValue()).not.toBe(BLUETOOTH_CONNECTION.CONNECTED);
|
|
185
|
+
}));
|
|
186
|
+
it("should handle service discovery errors", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
187
|
+
mockServer.connect.mockResolvedValueOnce(mockServer);
|
|
188
|
+
mockServer.getPrimaryService.mockRejectedValueOnce(new Error("Service not found"));
|
|
189
|
+
yield expect(transport.getServerServiceAndCharacteristics(mockDevice)).rejects.toThrow("Service not found");
|
|
190
|
+
expect(transport.connection$.getValue()).not.toBe(BLUETOOTH_CONNECTION.CONNECTED);
|
|
191
|
+
}));
|
|
192
|
+
it("should handle characteristic discovery errors", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
193
|
+
mockServer.connect.mockResolvedValueOnce(mockServer);
|
|
194
|
+
mockServer.getPrimaryService.mockResolvedValueOnce(mockService);
|
|
195
|
+
mockService.getCharacteristics.mockRejectedValueOnce(new Error("Characteristics not found"));
|
|
196
|
+
yield expect(transport.getServerServiceAndCharacteristics(mockDevice)).rejects.toThrow("Characteristics not found");
|
|
197
|
+
expect(transport.connection$.getValue()).not.toBe(BLUETOOTH_CONNECTION.CONNECTED);
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Neurosity } from "../Neurosity";
|
|
11
|
+
import { BehaviorSubject, Subscription } from "rxjs";
|
|
12
|
+
import { take } from "rxjs/operators";
|
|
13
|
+
import { STATUS } from "../types/status";
|
|
14
|
+
// Mock CloudClient
|
|
15
|
+
jest.mock("../api", () => {
|
|
16
|
+
const originalModule = jest.requireActual("../api");
|
|
17
|
+
class MockCloudClient {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.user = null;
|
|
20
|
+
this.userClaims = { scopes: ["brainwaves"] };
|
|
21
|
+
this.subscriptionManager = {
|
|
22
|
+
add: jest.fn(),
|
|
23
|
+
remove: jest.fn(),
|
|
24
|
+
removeAll: jest.fn()
|
|
25
|
+
};
|
|
26
|
+
this._accelerometerData = new BehaviorSubject({
|
|
27
|
+
x: 0.1,
|
|
28
|
+
y: -0.2,
|
|
29
|
+
z: 9.8,
|
|
30
|
+
timestamp: Date.now(),
|
|
31
|
+
acceleration: 9.8,
|
|
32
|
+
inclination: 0,
|
|
33
|
+
orientation: 0,
|
|
34
|
+
pitch: 0,
|
|
35
|
+
roll: 0
|
|
36
|
+
});
|
|
37
|
+
this.bluetoothClient = {
|
|
38
|
+
accelerometer: jest.fn().mockReturnValue(this._accelerometerData),
|
|
39
|
+
connection: jest.fn().mockReturnValue(new BehaviorSubject("connected"))
|
|
40
|
+
};
|
|
41
|
+
this._selectedDevice = new BehaviorSubject({
|
|
42
|
+
deviceId: "test-device-id",
|
|
43
|
+
deviceNickname: "Test Device",
|
|
44
|
+
channelNames: ["CH1", "CH2"],
|
|
45
|
+
channels: 2,
|
|
46
|
+
samplingRate: 250,
|
|
47
|
+
manufacturer: "Neurosity",
|
|
48
|
+
model: "Crown",
|
|
49
|
+
modelName: "Crown",
|
|
50
|
+
modelVersion: "3",
|
|
51
|
+
apiVersion: "1.0.0",
|
|
52
|
+
osVersion: "16.0.0",
|
|
53
|
+
emulator: false
|
|
54
|
+
});
|
|
55
|
+
this.getInfo = jest.fn().mockResolvedValue(this._selectedDevice.value);
|
|
56
|
+
this.selectDevice = jest
|
|
57
|
+
.fn()
|
|
58
|
+
.mockImplementation(() => __awaiter(this, void 0, void 0, function* () { return this._selectedDevice.value; }));
|
|
59
|
+
this.didSelectDevice = jest.fn().mockResolvedValue(true);
|
|
60
|
+
this.onDeviceChange = jest.fn().mockReturnValue(this._selectedDevice);
|
|
61
|
+
this.status = jest.fn().mockReturnValue(new BehaviorSubject({
|
|
62
|
+
state: STATUS.ONLINE,
|
|
63
|
+
charging: false,
|
|
64
|
+
battery: 100,
|
|
65
|
+
sleepMode: false,
|
|
66
|
+
sleepModeReason: null,
|
|
67
|
+
lastHeartbeat: Date.now(),
|
|
68
|
+
ssid: "test-network"
|
|
69
|
+
}));
|
|
70
|
+
this.osVersion = jest.fn().mockReturnValue(new BehaviorSubject("16.0.0"));
|
|
71
|
+
this.metrics = {
|
|
72
|
+
subscribe: jest.fn().mockImplementation(() => {
|
|
73
|
+
const subscription = new Subscription();
|
|
74
|
+
subscription.add(this._accelerometerData.subscribe());
|
|
75
|
+
return subscription;
|
|
76
|
+
}),
|
|
77
|
+
on: jest.fn().mockImplementation((subscription, callback) => {
|
|
78
|
+
const sub = this._accelerometerData.subscribe(callback);
|
|
79
|
+
return () => sub.unsubscribe();
|
|
80
|
+
})
|
|
81
|
+
};
|
|
82
|
+
this.options = options;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Object.assign(Object.assign({}, originalModule), { CloudClient: jest
|
|
86
|
+
.fn()
|
|
87
|
+
.mockImplementation((options) => new MockCloudClient(options)) });
|
|
88
|
+
});
|
|
89
|
+
describe("Accelerometer", () => {
|
|
90
|
+
let neurosity;
|
|
91
|
+
const testDeviceId = "test-device-id";
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
neurosity = new Neurosity({
|
|
94
|
+
deviceId: testDeviceId,
|
|
95
|
+
emulator: true
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
describe("Accelerometer Data", () => {
|
|
99
|
+
it("should get accelerometer readings", (done) => {
|
|
100
|
+
neurosity
|
|
101
|
+
.accelerometer()
|
|
102
|
+
.pipe(take(1))
|
|
103
|
+
.subscribe({
|
|
104
|
+
next: (accel) => {
|
|
105
|
+
expect(accel).toBeDefined();
|
|
106
|
+
expect(typeof accel.x).toBe("number");
|
|
107
|
+
expect(typeof accel.y).toBe("number");
|
|
108
|
+
expect(typeof accel.z).toBe("number");
|
|
109
|
+
expect(accel.timestamp).toBeDefined();
|
|
110
|
+
// Check for reasonable values
|
|
111
|
+
expect(accel.x).toBe(0.1);
|
|
112
|
+
expect(accel.y).toBe(-0.2);
|
|
113
|
+
expect(accel.z).toBe(9.8); // Approximately Earth's gravity
|
|
114
|
+
done();
|
|
115
|
+
},
|
|
116
|
+
error: done
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
it("should provide continuous accelerometer updates", (done) => {
|
|
120
|
+
neurosity
|
|
121
|
+
.accelerometer()
|
|
122
|
+
.pipe(take(1))
|
|
123
|
+
.subscribe({
|
|
124
|
+
next: (reading) => {
|
|
125
|
+
expect(reading.x).toBeDefined();
|
|
126
|
+
expect(reading.y).toBeDefined();
|
|
127
|
+
expect(reading.z).toBeDefined();
|
|
128
|
+
expect(reading.timestamp).toBeDefined();
|
|
129
|
+
done();
|
|
130
|
+
},
|
|
131
|
+
error: done
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("Error Handling", () => {
|
|
136
|
+
it("should handle device offline state", (done) => {
|
|
137
|
+
// Mock device going offline
|
|
138
|
+
const cloudClient = neurosity.cloudClient;
|
|
139
|
+
cloudClient.status.mockReturnValueOnce(new BehaviorSubject({
|
|
140
|
+
state: STATUS.OFFLINE,
|
|
141
|
+
charging: false,
|
|
142
|
+
battery: 100,
|
|
143
|
+
sleepMode: false,
|
|
144
|
+
sleepModeReason: null,
|
|
145
|
+
lastHeartbeat: Date.now(),
|
|
146
|
+
ssid: "test-network"
|
|
147
|
+
}));
|
|
148
|
+
neurosity.accelerometer().subscribe({
|
|
149
|
+
error: (err) => {
|
|
150
|
+
expect(err).toBeDefined();
|
|
151
|
+
done();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|