@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,133 @@
|
|
|
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
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
11
|
+
var t = {};
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
15
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
16
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
17
|
+
t[p[i]] = s[p[i]];
|
|
18
|
+
}
|
|
19
|
+
return t;
|
|
20
|
+
};
|
|
21
|
+
import { createOAuthURL } from "../api/https/createOAuthURL";
|
|
22
|
+
import { getOAuthToken } from "../api/https/getOAuthToken";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
// Mock axios
|
|
25
|
+
jest.mock("axios", () => ({
|
|
26
|
+
get: jest.fn().mockImplementation((url, config) => {
|
|
27
|
+
var _a;
|
|
28
|
+
if (!((_a = config === null || config === void 0 ? void 0 : config.params) === null || _a === void 0 ? void 0 : _a.client_id)) {
|
|
29
|
+
return Promise.reject(new Error("Missing required parameter: clientId"));
|
|
30
|
+
}
|
|
31
|
+
return Promise.resolve({
|
|
32
|
+
data: {
|
|
33
|
+
url: `/oauth/createOAuthURL?client_id=${config.params.client_id}&redirect_uri=${encodeURIComponent(config.params.redirect_uri)}&response_type=${config.params.response_type}&state=${config.params.state}&scope=${encodeURIComponent(config.params.scope)}`
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}),
|
|
37
|
+
post: jest.fn().mockImplementation((url, data) => {
|
|
38
|
+
if (!data.clientId || !data.clientSecret || !data.userId) {
|
|
39
|
+
return Promise.reject(new Error("Missing OAuth credentials"));
|
|
40
|
+
}
|
|
41
|
+
if (data.clientId === "expired") {
|
|
42
|
+
return Promise.reject(new Error("Token expired"));
|
|
43
|
+
}
|
|
44
|
+
return Promise.resolve({
|
|
45
|
+
data: {
|
|
46
|
+
userId: "test-user-id",
|
|
47
|
+
accessToken: "test-access-token",
|
|
48
|
+
refreshToken: "test-refresh-token",
|
|
49
|
+
expiresIn: 3600
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
})
|
|
53
|
+
}));
|
|
54
|
+
// Mock getOAuthToken
|
|
55
|
+
jest.mock("../api/https/getOAuthToken", () => {
|
|
56
|
+
return {
|
|
57
|
+
__esModule: true,
|
|
58
|
+
getOAuthToken: jest
|
|
59
|
+
.fn()
|
|
60
|
+
.mockImplementation((query, options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
+
try {
|
|
62
|
+
const baseURL = options.emulator
|
|
63
|
+
? `http://${options.emulatorHost}:${options.emulatorFunctionsPort}/neurosity-device/us-central1`
|
|
64
|
+
: "https://us-central1-neurosity-device.cloudfunctions.net";
|
|
65
|
+
const response = yield axios.post(baseURL, query);
|
|
66
|
+
return response.data;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}))
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
describe("OAuth and Token Management", () => {
|
|
75
|
+
const options = {
|
|
76
|
+
emulator: true,
|
|
77
|
+
emulatorHost: "localhost",
|
|
78
|
+
emulatorFunctionsPort: 5001
|
|
79
|
+
};
|
|
80
|
+
describe("OAuth URL Creation", () => {
|
|
81
|
+
const config = {
|
|
82
|
+
clientId: "test-client-id",
|
|
83
|
+
redirectUri: "http://localhost:3000/callback",
|
|
84
|
+
responseType: "token",
|
|
85
|
+
state: "random-state",
|
|
86
|
+
scope: ["read:devices-info", "read:brainwaves"]
|
|
87
|
+
};
|
|
88
|
+
it("should create OAuth URL with valid credentials", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
+
const url = yield createOAuthURL(config, options);
|
|
90
|
+
expect(url).toContain(`client_id=${config.clientId}`);
|
|
91
|
+
expect(url).toContain(`redirect_uri=${encodeURIComponent(config.redirectUri)}`);
|
|
92
|
+
expect(url).toContain(`response_type=${config.responseType}`);
|
|
93
|
+
expect(url).toContain(`state=${config.state}`);
|
|
94
|
+
expect(url).toContain(`scope=${encodeURIComponent(config.scope.join(","))}`);
|
|
95
|
+
}));
|
|
96
|
+
it("should reject OAuth URL creation with missing credentials", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
const { clientId } = config, invalidConfig = __rest(config, ["clientId"]);
|
|
98
|
+
yield expect(createOAuthURL(invalidConfig, options)).rejects.toThrow("Missing required parameter: clientId");
|
|
99
|
+
}));
|
|
100
|
+
});
|
|
101
|
+
describe("OAuth Token Management", () => {
|
|
102
|
+
it("should get OAuth token with valid credentials", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
const query = {
|
|
104
|
+
clientId: "test-client-id",
|
|
105
|
+
clientSecret: "test-client-secret",
|
|
106
|
+
userId: "test-user-id"
|
|
107
|
+
};
|
|
108
|
+
const token = yield getOAuthToken(query, options);
|
|
109
|
+
expect(token).toEqual({
|
|
110
|
+
userId: "test-user-id",
|
|
111
|
+
accessToken: "test-access-token",
|
|
112
|
+
refreshToken: "test-refresh-token",
|
|
113
|
+
expiresIn: 3600
|
|
114
|
+
});
|
|
115
|
+
}));
|
|
116
|
+
it("should handle expired token", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
+
const query = {
|
|
118
|
+
clientId: "expired",
|
|
119
|
+
clientSecret: "test-client-secret",
|
|
120
|
+
userId: "test-user-id"
|
|
121
|
+
};
|
|
122
|
+
yield expect(getOAuthToken(query, options)).rejects.toThrow("Token expired");
|
|
123
|
+
}));
|
|
124
|
+
it("should handle missing credentials", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
const query = {
|
|
126
|
+
clientId: "test-client-id",
|
|
127
|
+
clientSecret: "",
|
|
128
|
+
userId: ""
|
|
129
|
+
};
|
|
130
|
+
yield expect(getOAuthToken(query, options)).rejects.toThrow("Missing OAuth credentials");
|
|
131
|
+
}));
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
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 { BehaviorSubject, of } from "rxjs";
|
|
11
|
+
import { take } from "rxjs/operators";
|
|
12
|
+
import { Neurosity } from "../Neurosity";
|
|
13
|
+
// Mock CloudClient
|
|
14
|
+
jest.mock("../api/index", () => {
|
|
15
|
+
const mockSettings = new BehaviorSubject({
|
|
16
|
+
lsl: false,
|
|
17
|
+
supportAccess: false,
|
|
18
|
+
activityLogging: false
|
|
19
|
+
});
|
|
20
|
+
const mockCloudClient = {
|
|
21
|
+
login: jest.fn(),
|
|
22
|
+
logout: jest.fn(),
|
|
23
|
+
onAuthStateChanged: jest.fn(),
|
|
24
|
+
onDeviceChange: jest.fn(),
|
|
25
|
+
status: jest.fn(),
|
|
26
|
+
didSelectDevice: jest.fn().mockResolvedValue(true),
|
|
27
|
+
observeNamespace: jest.fn((namespace) => {
|
|
28
|
+
if (namespace === "settings") {
|
|
29
|
+
return mockSettings;
|
|
30
|
+
}
|
|
31
|
+
return of(null);
|
|
32
|
+
}),
|
|
33
|
+
changeSettings: jest.fn((settings) => __awaiter(void 0, void 0, void 0, function* () {
|
|
34
|
+
// Validate settings
|
|
35
|
+
const validKeys = ["lsl", "bluetooth", "timesync", "deviceNickname"];
|
|
36
|
+
const hasInvalidKey = Object.keys(settings).some((key) => !validKeys.includes(key));
|
|
37
|
+
if (hasInvalidKey) {
|
|
38
|
+
throw new Error("Invalid settings");
|
|
39
|
+
}
|
|
40
|
+
mockSettings.next(Object.assign(Object.assign({}, mockSettings.value), settings));
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
})),
|
|
43
|
+
userClaims: {
|
|
44
|
+
scopes: ["settings"]
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
CloudClient: jest.fn().mockImplementation(() => mockCloudClient)
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
const testDeviceId = "mock-device-id";
|
|
52
|
+
describe("Settings", () => {
|
|
53
|
+
let neurosity;
|
|
54
|
+
let cloudClient;
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
neurosity = new Neurosity({
|
|
57
|
+
deviceId: testDeviceId
|
|
58
|
+
});
|
|
59
|
+
cloudClient = neurosity.cloudClient;
|
|
60
|
+
});
|
|
61
|
+
describe("Settings Management", () => {
|
|
62
|
+
it("should get current settings", (done) => {
|
|
63
|
+
neurosity
|
|
64
|
+
.settings()
|
|
65
|
+
.pipe(take(1))
|
|
66
|
+
.subscribe({
|
|
67
|
+
next: (settings) => {
|
|
68
|
+
expect(settings).toBeDefined();
|
|
69
|
+
expect(settings.lsl).toBe(false);
|
|
70
|
+
expect(settings.supportAccess).toBe(false);
|
|
71
|
+
expect(settings.activityLogging).toBe(false);
|
|
72
|
+
done();
|
|
73
|
+
},
|
|
74
|
+
error: done
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it("should update settings", (done) => {
|
|
78
|
+
const newSettings = {
|
|
79
|
+
lsl: true,
|
|
80
|
+
bluetooth: true,
|
|
81
|
+
timesync: true
|
|
82
|
+
};
|
|
83
|
+
neurosity
|
|
84
|
+
.changeSettings(newSettings)
|
|
85
|
+
.then(() => {
|
|
86
|
+
neurosity
|
|
87
|
+
.settings()
|
|
88
|
+
.pipe(take(1))
|
|
89
|
+
.subscribe({
|
|
90
|
+
next: (settings) => {
|
|
91
|
+
expect(settings.lsl).toBe(true);
|
|
92
|
+
expect(settings.supportAccess).toBe(true);
|
|
93
|
+
expect(settings.activityLogging).toBe(true);
|
|
94
|
+
done();
|
|
95
|
+
},
|
|
96
|
+
error: done
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
.catch(done);
|
|
100
|
+
});
|
|
101
|
+
it("should update device nickname", (done) => {
|
|
102
|
+
neurosity
|
|
103
|
+
.changeSettings({ supportAccess: true })
|
|
104
|
+
.then(() => {
|
|
105
|
+
neurosity
|
|
106
|
+
.settings()
|
|
107
|
+
.pipe(take(1))
|
|
108
|
+
.subscribe({
|
|
109
|
+
next: (settings) => {
|
|
110
|
+
expect(settings.supportAccess).toBe(true);
|
|
111
|
+
done();
|
|
112
|
+
},
|
|
113
|
+
error: done
|
|
114
|
+
});
|
|
115
|
+
})
|
|
116
|
+
.catch(done);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("Error Handling", () => {
|
|
120
|
+
it("should handle invalid settings changes", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
121
|
+
const invalidChanges = {
|
|
122
|
+
invalidSetting: true
|
|
123
|
+
};
|
|
124
|
+
// @ts-ignore - Testing invalid settings
|
|
125
|
+
yield expect(neurosity.changeSettings(invalidChanges)).rejects.toThrow("Invalid settings");
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup file for Web Bluetooth tests
|
|
3
|
+
* This configures a minimal browser-like environment for testing Web Bluetooth functionality
|
|
4
|
+
*/
|
|
5
|
+
/// <reference types="jest" />
|
|
6
|
+
declare const mockBluetooth: {
|
|
7
|
+
getDevices: jest.Mock<any, any, any>;
|
|
8
|
+
requestDevice: jest.Mock<any, any, any>;
|
|
9
|
+
};
|
|
10
|
+
declare const mockNavigator: Navigator;
|
|
11
|
+
declare const mockWindow: Window & typeof globalThis;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup file for Web Bluetooth tests
|
|
3
|
+
* This configures a minimal browser-like environment for testing Web Bluetooth functionality
|
|
4
|
+
*/
|
|
5
|
+
// Mock window and navigator.bluetooth
|
|
6
|
+
const mockBluetooth = {
|
|
7
|
+
getDevices: jest.fn(),
|
|
8
|
+
requestDevice: jest.fn()
|
|
9
|
+
};
|
|
10
|
+
const mockNavigator = {
|
|
11
|
+
bluetooth: mockBluetooth
|
|
12
|
+
};
|
|
13
|
+
const mockWindow = {
|
|
14
|
+
navigator: mockNavigator
|
|
15
|
+
};
|
|
16
|
+
// Set up global object for Node environment
|
|
17
|
+
global.window = mockWindow;
|
|
18
|
+
global.navigator = mockWindow.navigator;
|
|
19
|
+
// Mock isWebBluetoothSupported
|
|
20
|
+
jest.mock("../../api/bluetooth/web/isWebBluetoothSupported", () => ({
|
|
21
|
+
isWebBluetoothSupported: jest.fn().mockReturnValue(true)
|
|
22
|
+
}));
|
|
23
|
+
// Mock IPK constants
|
|
24
|
+
jest.mock("@neurosity/ipk", () => ({
|
|
25
|
+
BLUETOOTH_PRIMARY_SERVICE_UUID_HEX: "test-service-uuid",
|
|
26
|
+
BLUETOOTH_COMPANY_IDENTIFIER_HEX: 0x1234,
|
|
27
|
+
BLUETOOTH_DEVICE_NAME_PREFIXES: ["Crown"],
|
|
28
|
+
BLUETOOTH_CHUNK_DELIMITER: "\n",
|
|
29
|
+
BLUETOOTH_CHARACTERISTICS: {
|
|
30
|
+
ACTION: "test-action-uuid",
|
|
31
|
+
ACTION_STATUS: "test-action-status-uuid",
|
|
32
|
+
COMMAND: "test-command-uuid",
|
|
33
|
+
STATE: "test-state-uuid"
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,257 @@
|
|
|
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 { firstValueFrom, take, of } from "rxjs";
|
|
12
|
+
import { STATUS } from "../types/status";
|
|
13
|
+
// Mock Firebase modules
|
|
14
|
+
jest.mock("../api/firebase", () => ({
|
|
15
|
+
FirebaseApp: jest.fn().mockImplementation(() => ({
|
|
16
|
+
disconnect: jest.fn(),
|
|
17
|
+
useEmulator: jest.fn()
|
|
18
|
+
})),
|
|
19
|
+
FirebaseUser: jest.fn().mockImplementation(() => ({
|
|
20
|
+
login: jest.fn(),
|
|
21
|
+
logout: jest.fn(),
|
|
22
|
+
onAuthStateChanged: jest.fn().mockReturnValue(of(null)),
|
|
23
|
+
onUserClaimsChange: jest.fn().mockReturnValue(of({}))
|
|
24
|
+
})),
|
|
25
|
+
FirebaseDevice: jest.fn().mockImplementation(() => ({
|
|
26
|
+
disconnect: jest.fn(),
|
|
27
|
+
getInfo: jest.fn(),
|
|
28
|
+
dispatchAction: jest.fn()
|
|
29
|
+
}))
|
|
30
|
+
}));
|
|
31
|
+
describe("Data Streaming", () => {
|
|
32
|
+
let neurosity;
|
|
33
|
+
const testDeviceId = "test-device-id";
|
|
34
|
+
const options = {
|
|
35
|
+
deviceId: testDeviceId,
|
|
36
|
+
emulator: true,
|
|
37
|
+
skill: {
|
|
38
|
+
id: "test-skill-id",
|
|
39
|
+
bundleId: "test-bundle-id",
|
|
40
|
+
spec: "1.0.0",
|
|
41
|
+
name: "Test Skill",
|
|
42
|
+
description: "A test skill",
|
|
43
|
+
metrics: ["brainwaves"],
|
|
44
|
+
userId: "test-user-id",
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
status: "active",
|
|
47
|
+
thumbnail: "test-thumbnail"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
neurosity = new Neurosity(options);
|
|
52
|
+
// Mock cloudClient with userClaims for OAuth scope validation
|
|
53
|
+
// and required methods
|
|
54
|
+
neurosity["cloudClient"] = {
|
|
55
|
+
userClaims: {
|
|
56
|
+
scopes: ["brainwaves"]
|
|
57
|
+
},
|
|
58
|
+
onDeviceChange: jest.fn().mockReturnValue(of({
|
|
59
|
+
deviceId: testDeviceId,
|
|
60
|
+
status: STATUS.ONLINE
|
|
61
|
+
})),
|
|
62
|
+
osVersion: jest.fn().mockReturnValue(of("1.0.0")),
|
|
63
|
+
status: jest.fn().mockReturnValue(of({ state: STATUS.ONLINE })),
|
|
64
|
+
metrics: {
|
|
65
|
+
subscribe: jest.fn().mockReturnValue(of({})),
|
|
66
|
+
on: jest.fn().mockImplementation((subscription, callback) => {
|
|
67
|
+
// Simulate metric data
|
|
68
|
+
callback({});
|
|
69
|
+
return jest.fn();
|
|
70
|
+
}),
|
|
71
|
+
unsubscribe: jest.fn()
|
|
72
|
+
},
|
|
73
|
+
subscriptionManager: {
|
|
74
|
+
add: jest.fn(),
|
|
75
|
+
remove: jest.fn(),
|
|
76
|
+
removeAll: jest.fn()
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
// Mock _osHasBluetoothSupport to return false to avoid Bluetooth-related code paths
|
|
80
|
+
neurosity["_osHasBluetoothSupport"] = jest.fn().mockReturnValue(of(false));
|
|
81
|
+
});
|
|
82
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
try {
|
|
84
|
+
yield neurosity.logout();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// Ignore logout errors in cleanup
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
describe("Brainwaves", () => {
|
|
91
|
+
it("should stream raw brainwaves data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
92
|
+
const mockRawData = {
|
|
93
|
+
data: [
|
|
94
|
+
[1, 2, 3, 4, 5, 6, 7, 8],
|
|
95
|
+
[9, 10, 11, 12, 13, 14, 15, 16]
|
|
96
|
+
],
|
|
97
|
+
info: {
|
|
98
|
+
samplingRate: 256,
|
|
99
|
+
startTime: Date.now(),
|
|
100
|
+
channelNames: ["test-channel-1", "test-channel-2"]
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
// Mock the getCloudMetric function to return our mock data
|
|
104
|
+
neurosity["_getCloudMetricDependencies"] = jest.fn().mockReturnValue({
|
|
105
|
+
options,
|
|
106
|
+
cloudClient: neurosity["cloudClient"],
|
|
107
|
+
onDeviceChange: neurosity["cloudClient"].onDeviceChange,
|
|
108
|
+
status: neurosity["cloudClient"].status,
|
|
109
|
+
getCloudMetric: jest.fn().mockReturnValue(of(mockRawData))
|
|
110
|
+
});
|
|
111
|
+
const brainwaves = yield firstValueFrom(neurosity.brainwaves("raw").pipe(take(1)));
|
|
112
|
+
expect(brainwaves).toBeDefined();
|
|
113
|
+
if ("data" in brainwaves) {
|
|
114
|
+
expect(Array.isArray(brainwaves.data)).toBe(true);
|
|
115
|
+
expect(brainwaves.data.length).toBe(2);
|
|
116
|
+
expect(brainwaves.data[0].length).toBe(8);
|
|
117
|
+
expect(brainwaves.info.samplingRate).toBe(256);
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
it("should stream power by band data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
121
|
+
const mockPowerByBand = {
|
|
122
|
+
gamma: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
|
|
123
|
+
beta: [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
|
|
124
|
+
alpha: [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
|
|
125
|
+
theta: [0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1],
|
|
126
|
+
delta: [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2],
|
|
127
|
+
info: {
|
|
128
|
+
samplingRate: 256,
|
|
129
|
+
startTime: Date.now(),
|
|
130
|
+
channelNames: ["test-channel-1", "test-channel-2"]
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
// Mock the getCloudMetric function to return our mock data
|
|
134
|
+
neurosity["_getCloudMetricDependencies"] = jest.fn().mockReturnValue({
|
|
135
|
+
options,
|
|
136
|
+
cloudClient: neurosity["cloudClient"],
|
|
137
|
+
onDeviceChange: neurosity["cloudClient"].onDeviceChange,
|
|
138
|
+
status: neurosity["cloudClient"].status,
|
|
139
|
+
getCloudMetric: jest.fn().mockReturnValue(of(mockPowerByBand))
|
|
140
|
+
});
|
|
141
|
+
const powerByBand = yield firstValueFrom(neurosity.brainwaves("powerByBand").pipe(take(1)));
|
|
142
|
+
expect(powerByBand).toBeDefined();
|
|
143
|
+
if ("gamma" in powerByBand) {
|
|
144
|
+
expect(powerByBand.gamma).toBeDefined();
|
|
145
|
+
expect(powerByBand.beta).toBeDefined();
|
|
146
|
+
expect(powerByBand.alpha).toBeDefined();
|
|
147
|
+
expect(powerByBand.theta).toBeDefined();
|
|
148
|
+
expect(powerByBand.delta).toBeDefined();
|
|
149
|
+
expect(powerByBand.gamma.length).toBe(8);
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
it("should stream PSD data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
153
|
+
const mockPSD = {
|
|
154
|
+
label: "psd",
|
|
155
|
+
psd: [
|
|
156
|
+
[1, 2, 3, 4],
|
|
157
|
+
[5, 6, 7, 8]
|
|
158
|
+
],
|
|
159
|
+
freqs: [0, 2, 4, 6],
|
|
160
|
+
info: {
|
|
161
|
+
samplingRate: 256,
|
|
162
|
+
startTime: Date.now(),
|
|
163
|
+
notchFrequency: "50",
|
|
164
|
+
channelNames: ["test-channel-1", "test-channel-2"]
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
// Mock the getCloudMetric function to return our mock data
|
|
168
|
+
neurosity["_getCloudMetricDependencies"] = jest.fn().mockReturnValue({
|
|
169
|
+
options,
|
|
170
|
+
cloudClient: neurosity["cloudClient"],
|
|
171
|
+
onDeviceChange: neurosity["cloudClient"].onDeviceChange,
|
|
172
|
+
status: neurosity["cloudClient"].status,
|
|
173
|
+
getCloudMetric: jest.fn().mockReturnValue(of(mockPSD))
|
|
174
|
+
});
|
|
175
|
+
const psd = yield firstValueFrom(neurosity.brainwaves("psd").pipe(take(1)));
|
|
176
|
+
expect(psd).toBeDefined();
|
|
177
|
+
if ("psd" in psd) {
|
|
178
|
+
expect(Array.isArray(psd.psd)).toBe(true);
|
|
179
|
+
expect(Array.isArray(psd.freqs)).toBe(true);
|
|
180
|
+
expect(psd.info.samplingRate).toBe(256);
|
|
181
|
+
}
|
|
182
|
+
}));
|
|
183
|
+
it("should throw error when OAuth scope is missing", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
184
|
+
// Remove brainwaves scope
|
|
185
|
+
neurosity["cloudClient"].userClaims.scopes = [];
|
|
186
|
+
// Mock the getCloudMetric function to throw an error
|
|
187
|
+
neurosity["_getCloudMetricDependencies"] = jest.fn().mockReturnValue({
|
|
188
|
+
options,
|
|
189
|
+
cloudClient: neurosity["cloudClient"],
|
|
190
|
+
onDeviceChange: neurosity["cloudClient"].onDeviceChange,
|
|
191
|
+
status: neurosity["cloudClient"].status,
|
|
192
|
+
getCloudMetric: jest.fn().mockImplementation(() => {
|
|
193
|
+
throw new Error("Neurosity SDK: No permission to access the brainwaves metric. To access this metric, edit the skill's permissions");
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
// Mock the metrics subscription to throw an error
|
|
197
|
+
neurosity["cloudClient"].metrics.subscribe = jest
|
|
198
|
+
.fn()
|
|
199
|
+
.mockImplementation(() => {
|
|
200
|
+
throw new Error("Neurosity SDK: No permission to access the brainwaves metric. To access this metric, edit the skill's permissions");
|
|
201
|
+
});
|
|
202
|
+
yield expect(firstValueFrom(neurosity.brainwaves("raw").pipe(take(1)))).rejects.toThrow("Neurosity SDK: No permission to access the brainwaves metric. To access this metric, edit the skill's permissions");
|
|
203
|
+
}));
|
|
204
|
+
});
|
|
205
|
+
describe("Metrics", () => {
|
|
206
|
+
// TODO: Issue #XYZ - Metric streaming tests need to be implemented
|
|
207
|
+
// These tests require proper mocking of the metric data streams
|
|
208
|
+
it.skip("should stream focus data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
209
|
+
const focus = yield firstValueFrom(neurosity.focus().pipe(take(1)));
|
|
210
|
+
expect(focus).toBeDefined();
|
|
211
|
+
expect(typeof focus.probability).toBe("number");
|
|
212
|
+
expect(focus.probability).toBeGreaterThanOrEqual(0);
|
|
213
|
+
expect(focus.probability).toBeLessThanOrEqual(1);
|
|
214
|
+
}));
|
|
215
|
+
it.skip("should stream calm data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
|
+
const calm = yield firstValueFrom(neurosity.calm().pipe(take(1)));
|
|
217
|
+
expect(calm).toBeDefined();
|
|
218
|
+
expect(typeof calm.probability).toBe("number");
|
|
219
|
+
expect(calm.probability).toBeGreaterThanOrEqual(0);
|
|
220
|
+
expect(calm.probability).toBeLessThanOrEqual(1);
|
|
221
|
+
}));
|
|
222
|
+
it.skip("should stream kinesis data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
223
|
+
const kinesis = yield firstValueFrom(neurosity.kinesis("someAction").pipe(take(1)));
|
|
224
|
+
expect(kinesis).toBeDefined();
|
|
225
|
+
expect(typeof kinesis.probability).toBe("number");
|
|
226
|
+
expect(kinesis.probability).toBeGreaterThanOrEqual(0);
|
|
227
|
+
expect(kinesis.probability).toBeLessThanOrEqual(1);
|
|
228
|
+
}));
|
|
229
|
+
});
|
|
230
|
+
describe("Accelerometer", () => {
|
|
231
|
+
// TODO: Issue #XYZ - Accelerometer streaming tests need to be implemented
|
|
232
|
+
// These tests require proper mocking of the accelerometer data
|
|
233
|
+
it.skip("should stream accelerometer data", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
+
const accelerometer = yield firstValueFrom(neurosity.accelerometer().pipe(take(1)));
|
|
235
|
+
expect(accelerometer).toBeDefined();
|
|
236
|
+
expect(accelerometer.x).toBeDefined();
|
|
237
|
+
expect(accelerometer.y).toBeDefined();
|
|
238
|
+
expect(accelerometer.z).toBeDefined();
|
|
239
|
+
}));
|
|
240
|
+
});
|
|
241
|
+
describe("Error Handling", () => {
|
|
242
|
+
// TODO: Issue #XYZ - Error handling tests need to be implemented
|
|
243
|
+
// These tests require proper error simulation and handling
|
|
244
|
+
it.skip("should handle subscription errors gracefully", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
245
|
+
// Mock a subscription error
|
|
246
|
+
const errorSubscription = neurosity.brainwaves("raw").pipe(take(1));
|
|
247
|
+
yield expect(firstValueFrom(errorSubscription)).rejects.toThrow();
|
|
248
|
+
}));
|
|
249
|
+
it.skip("should handle rate limiting", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
250
|
+
// Test rapid subscription creation and cleanup
|
|
251
|
+
const subscriptions = Array(10)
|
|
252
|
+
.fill(null)
|
|
253
|
+
.map(() => neurosity.brainwaves("raw").pipe(take(1)));
|
|
254
|
+
yield expect(Promise.all(subscriptions.map((sub) => firstValueFrom(sub)))).rejects.toThrow();
|
|
255
|
+
}));
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Neurosity } from "../Neurosity";
|
|
2
|
+
// Mock CloudClient
|
|
3
|
+
jest.mock("../api", () => {
|
|
4
|
+
const originalModule = jest.requireActual("../api");
|
|
5
|
+
class MockCloudClient {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.user = null;
|
|
8
|
+
this.userClaims = { scopes: ["brainwaves"] };
|
|
9
|
+
this.getTimesyncOffset = jest.fn().mockImplementation(() => {
|
|
10
|
+
return 150; // Mock 150ms offset
|
|
11
|
+
});
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return Object.assign(Object.assign({}, originalModule), { CloudClient: jest
|
|
16
|
+
.fn()
|
|
17
|
+
.mockImplementation((options) => new MockCloudClient(options)) });
|
|
18
|
+
});
|
|
19
|
+
describe("Timesync", () => {
|
|
20
|
+
let neurosity;
|
|
21
|
+
const testDeviceId = "test-device-id";
|
|
22
|
+
describe("with timesync enabled", () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
neurosity = new Neurosity({
|
|
25
|
+
deviceId: testDeviceId,
|
|
26
|
+
emulator: true,
|
|
27
|
+
timesync: true
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
it("should get timesync offset", () => {
|
|
31
|
+
const offset = neurosity.getTimesyncOffset();
|
|
32
|
+
expect(offset).toBeDefined();
|
|
33
|
+
expect(typeof offset).toBe("number");
|
|
34
|
+
expect(offset).toBe(150);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("with timesync disabled", () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
neurosity = new Neurosity({
|
|
40
|
+
deviceId: testDeviceId,
|
|
41
|
+
emulator: true,
|
|
42
|
+
timesync: false
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it("should return 0 when timesync is disabled", () => {
|
|
46
|
+
const offset = neurosity.getTimesyncOffset();
|
|
47
|
+
expect(offset).toBeDefined();
|
|
48
|
+
expect(typeof offset).toBe("number");
|
|
49
|
+
expect(offset).toBe(0);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|