@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.
Files changed (130) hide show
  1. package/dist/browser/neurosity.iife.js +2419 -1009
  2. package/dist/browser/neurosity.js +180 -148
  3. package/dist/browser/neurosity.js.map +1 -1
  4. package/dist/cjs/__tests__/Neurosity.test.d.ts +1 -0
  5. package/dist/cjs/__tests__/Neurosity.test.js +201 -0
  6. package/dist/cjs/__tests__/WebBluetoothTransport.test.d.ts +1 -0
  7. package/dist/cjs/__tests__/WebBluetoothTransport.test.js +201 -0
  8. package/dist/cjs/__tests__/accelerometer.test.d.ts +1 -0
  9. package/dist/cjs/__tests__/accelerometer.test.js +158 -0
  10. package/dist/cjs/__tests__/auth.test.d.ts +1 -0
  11. package/dist/cjs/__tests__/auth.test.js +239 -0
  12. package/dist/cjs/__tests__/brainwaves.test.d.ts +1 -0
  13. package/dist/cjs/__tests__/brainwaves.test.js +291 -0
  14. package/dist/cjs/__tests__/device.test.d.ts +1 -0
  15. package/dist/cjs/__tests__/device.test.js +281 -0
  16. package/dist/cjs/__tests__/haptics.test.d.ts +1 -0
  17. package/dist/cjs/__tests__/haptics.test.js +162 -0
  18. package/dist/cjs/__tests__/metrics.test.d.ts +1 -0
  19. package/dist/cjs/__tests__/metrics.test.js +178 -0
  20. package/dist/cjs/__tests__/oauth.test.d.ts +1 -0
  21. package/dist/cjs/__tests__/oauth.test.js +138 -0
  22. package/dist/cjs/__tests__/settings.test.d.ts +1 -0
  23. package/dist/cjs/__tests__/settings.test.js +130 -0
  24. package/dist/cjs/__tests__/setup/webBluetooth.setup.d.ts +11 -0
  25. package/dist/cjs/__tests__/setup/webBluetooth.setup.js +35 -0
  26. package/dist/cjs/__tests__/setup.d.ts +0 -0
  27. package/dist/cjs/__tests__/setup.js +7 -0
  28. package/dist/cjs/__tests__/streaming.test.d.ts +1 -0
  29. package/dist/cjs/__tests__/streaming.test.js +259 -0
  30. package/dist/cjs/__tests__/timesync.test.d.ts +1 -0
  31. package/dist/cjs/__tests__/timesync.test.js +54 -0
  32. package/dist/cjs/__tests__/utils.test.d.ts +1 -0
  33. package/dist/cjs/__tests__/utils.test.js +281 -0
  34. package/dist/cjs/api/bluetooth/BluetoothClient.d.ts +6 -6
  35. package/dist/cjs/api/bluetooth/BluetoothTransport.d.ts +1 -1
  36. package/dist/cjs/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
  37. package/dist/cjs/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
  38. package/dist/cjs/api/bluetooth/types/index.d.ts +2 -2
  39. package/dist/cjs/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
  40. package/dist/cjs/api/bluetooth/utils/stitch.d.ts +1 -1
  41. package/dist/cjs/api/bluetooth/utils/textCodec.d.ts +1 -1
  42. package/dist/cjs/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
  43. package/dist/cjs/api/firebase/FirebaseDevice.d.ts +1 -1
  44. package/dist/cjs/api/firebase/FirebaseUser.d.ts +1 -1
  45. package/dist/cjs/api/index.js +1 -1
  46. package/dist/cjs/timesync/Timesync.d.ts +1 -1
  47. package/dist/cjs/types/awareness.d.ts +1 -1
  48. package/dist/cjs/types/brainwaves.d.ts +25 -12
  49. package/dist/cjs/types/credentials.d.ts +4 -4
  50. package/dist/cjs/types/deviceInfo.d.ts +4 -4
  51. package/dist/cjs/types/epoch.d.ts +1 -1
  52. package/dist/cjs/types/experiment.d.ts +1 -1
  53. package/dist/cjs/types/hapticEffects.d.ts +1 -1
  54. package/dist/cjs/types/marker.d.ts +1 -1
  55. package/dist/cjs/types/metrics.d.ts +2 -2
  56. package/dist/cjs/types/oauth.d.ts +4 -4
  57. package/dist/cjs/types/sample.d.ts +2 -2
  58. package/dist/cjs/types/signalQuality.d.ts +1 -1
  59. package/dist/cjs/types/skill.d.ts +2 -2
  60. package/dist/cjs/types/user.d.ts +3 -3
  61. package/dist/cjs/utils/oauth.d.ts +1 -1
  62. package/dist/cjs/utils/transferDevice.d.ts +3 -3
  63. package/dist/cjs/utils/whileOnline.d.ts +1 -1
  64. package/dist/electron/index.js +1 -1
  65. package/dist/electron/index.js.map +1 -1
  66. package/dist/esm/__tests__/Neurosity.test.d.ts +1 -0
  67. package/dist/esm/__tests__/Neurosity.test.js +199 -0
  68. package/dist/esm/__tests__/WebBluetoothTransport.test.d.ts +1 -0
  69. package/dist/esm/__tests__/WebBluetoothTransport.test.js +199 -0
  70. package/dist/esm/__tests__/accelerometer.test.d.ts +1 -0
  71. package/dist/esm/__tests__/accelerometer.test.js +156 -0
  72. package/dist/esm/__tests__/auth.test.d.ts +1 -0
  73. package/dist/esm/__tests__/auth.test.js +237 -0
  74. package/dist/esm/__tests__/brainwaves.test.d.ts +1 -0
  75. package/dist/esm/__tests__/brainwaves.test.js +289 -0
  76. package/dist/esm/__tests__/device.test.d.ts +1 -0
  77. package/dist/esm/__tests__/device.test.js +279 -0
  78. package/dist/esm/__tests__/haptics.test.d.ts +1 -0
  79. package/dist/esm/__tests__/haptics.test.js +160 -0
  80. package/dist/esm/__tests__/metrics.test.d.ts +1 -0
  81. package/dist/esm/__tests__/metrics.test.js +176 -0
  82. package/dist/esm/__tests__/oauth.test.d.ts +1 -0
  83. package/dist/esm/__tests__/oauth.test.js +133 -0
  84. package/dist/esm/__tests__/settings.test.d.ts +1 -0
  85. package/dist/esm/__tests__/settings.test.js +128 -0
  86. package/dist/esm/__tests__/setup/webBluetooth.setup.d.ts +11 -0
  87. package/dist/esm/__tests__/setup/webBluetooth.setup.js +35 -0
  88. package/dist/esm/__tests__/setup.d.ts +0 -0
  89. package/dist/esm/__tests__/setup.js +7 -0
  90. package/dist/esm/__tests__/streaming.test.d.ts +1 -0
  91. package/dist/esm/__tests__/streaming.test.js +257 -0
  92. package/dist/esm/__tests__/timesync.test.d.ts +1 -0
  93. package/dist/esm/__tests__/timesync.test.js +52 -0
  94. package/dist/esm/__tests__/utils.test.d.ts +1 -0
  95. package/dist/esm/__tests__/utils.test.js +279 -0
  96. package/dist/esm/api/bluetooth/BluetoothClient.d.ts +6 -6
  97. package/dist/esm/api/bluetooth/BluetoothTransport.d.ts +1 -1
  98. package/dist/esm/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
  99. package/dist/esm/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
  100. package/dist/esm/api/bluetooth/types/index.d.ts +2 -2
  101. package/dist/esm/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
  102. package/dist/esm/api/bluetooth/utils/stitch.d.ts +1 -1
  103. package/dist/esm/api/bluetooth/utils/textCodec.d.ts +1 -1
  104. package/dist/esm/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
  105. package/dist/esm/api/firebase/FirebaseDevice.d.ts +1 -1
  106. package/dist/esm/api/firebase/FirebaseUser.d.ts +1 -1
  107. package/dist/esm/api/index.js +2 -2
  108. package/dist/esm/neurosity.mjs +2480 -1007
  109. package/dist/esm/timesync/Timesync.d.ts +1 -1
  110. package/dist/esm/types/awareness.d.ts +1 -1
  111. package/dist/esm/types/brainwaves.d.ts +25 -12
  112. package/dist/esm/types/credentials.d.ts +4 -4
  113. package/dist/esm/types/deviceInfo.d.ts +4 -4
  114. package/dist/esm/types/epoch.d.ts +1 -1
  115. package/dist/esm/types/experiment.d.ts +1 -1
  116. package/dist/esm/types/hapticEffects.d.ts +1 -1
  117. package/dist/esm/types/marker.d.ts +1 -1
  118. package/dist/esm/types/metrics.d.ts +2 -2
  119. package/dist/esm/types/oauth.d.ts +4 -4
  120. package/dist/esm/types/sample.d.ts +2 -2
  121. package/dist/esm/types/signalQuality.d.ts +1 -1
  122. package/dist/esm/types/skill.d.ts +2 -2
  123. package/dist/esm/types/user.d.ts +3 -3
  124. package/dist/esm/utils/oauth.d.ts +1 -1
  125. package/dist/esm/utils/transferDevice.d.ts +3 -3
  126. package/dist/esm/utils/whileOnline.d.ts +1 -1
  127. package/dist/examples/neurosity.iife.js +2419 -1009
  128. package/dist/examples/neurosity.js +180 -148
  129. package/dist/examples/neurosity.mjs +2480 -1007
  130. 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,7 @@
1
+ /// <reference types="jest" />
2
+ // Increase the default timeout for async operations
3
+ jest.setTimeout(10000);
4
+ // Add custom matchers if needed
5
+ expect.extend({
6
+ // Add custom matchers here if needed in the future
7
+ });
@@ -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 {};