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