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