@smartspectra/node-sdk 3.4.0-rc.5 → 3.4.0-rc.7

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.
@@ -18,6 +18,7 @@ export {
18
18
  ValidationCode,
19
19
  SmartSpectraErrorCode,
20
20
  FrameTransform,
21
+ MetricType,
21
22
  breathingMetrics,
22
23
  cardioMetrics,
23
24
  faceMetrics,
@@ -31,6 +32,7 @@ export type {
31
32
  ValidationCodeValue,
32
33
  SmartSpectraErrorCodeValue,
33
34
  FrameTransformValue,
35
+ MetricTypeValue,
34
36
  SmartSpectraOptions,
35
37
  } from '../../js/index';
36
38
 
@@ -5,7 +5,8 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const { Session, ensurePreconfigured } = require('./ffi');
8
+ const { Session, ensurePreconfigured, availableCameras } = require('./ffi');
9
+ const { CameraSelection, normalizeCameraSelection, normalizeCameraOptions } = require('./camera-selection');
9
10
  const {
10
11
  ProcessingStatus,
11
12
  SmartSpectraLogLevel,
@@ -13,6 +14,7 @@ const {
13
14
  ValidationCode,
14
15
  SmartSpectraErrorCode,
15
16
  FrameTransform,
17
+ MetricType,
16
18
  breathingMetrics,
17
19
  cardioMetrics,
18
20
  faceMetrics,
@@ -41,6 +43,9 @@ class SmartSpectraSDK {
41
43
  /** SDK package version (matches `package.json#version`). */
42
44
  static get version() { return PACKAGE_VERSION; }
43
45
 
46
+ /** Discover cameras without a session, authentication, or capture. */
47
+ static availableCameras() { return availableCameras(); }
48
+
44
49
  /**
45
50
  * @param {Object} [options]
46
51
  * @param {string} [options.apiKey]
@@ -64,7 +69,9 @@ class SmartSpectraSDK {
64
69
  // consumed by start(). Shape: { kind: 'custom'|'camera'|'file', ... }.
65
70
  // The last use* call wins.
66
71
  this._source = null;
72
+ this._explicitCameraSelection = false;
67
73
  this._session = null; // lazy: created on start()
74
+ this._destroyPromise = null;
68
75
  }
69
76
 
70
77
  // ---------- public API --------------------------------------------------
@@ -92,6 +99,7 @@ class SmartSpectraSDK {
92
99
  * Spatial transform applied to every pushed frame.
93
100
  */
94
101
  useCustomInput(frameTransform = DEFAULT_FRAME_TRANSFORM) {
102
+ if (this._explicitCameraSelection) this._requireSourceSelectionAllowed();
95
103
  this._source = { kind: 'custom', frameTransform: frameTransform | 0 };
96
104
  return this;
97
105
  }
@@ -101,14 +109,28 @@ class SmartSpectraSDK {
101
109
  * pumps frames internally on start() — no sendFrame() needed. Returns
102
110
  * `this` for chaining.
103
111
  *
104
- * @param {Object} [options] { deviceIndex?, width?, height?, fps?, frameTransform? }
112
+ * Selection is stopped-only and resolved at startup. Explicit requests never
113
+ * fall back. Stop/start/reset retain the selection. Capture options are separate.
114
+ * The deprecated useCamera(options) overload retains index-based capture
115
+ * and the legacy source lifecycle; omit options for camera index zero.
105
116
  *
106
117
  * Captures from a camera in THIS process (headless / Node use). In an
107
118
  * Electron app the camera is normally owned by the renderer via
108
119
  * getUserMedia — use the renderer SDK's useMediaStream() there instead.
109
120
  */
110
- useCamera(options = null) {
111
- this._source = { kind: 'camera', options };
121
+ useCamera(selection = null, options = {}) {
122
+ const isSelection = selection !== null && typeof selection === 'object' && 'kind' in selection;
123
+ if (!isSelection && arguments.length < 2) {
124
+ if (this._explicitCameraSelection) this._requireSourceSelectionAllowed();
125
+ this._source = { kind: 'camera', options: selection };
126
+ this._explicitCameraSelection = false;
127
+ return this;
128
+ }
129
+ this._requireSourceSelectionAllowed();
130
+ const request = normalizeCameraSelection(selection);
131
+ const capture = normalizeCameraOptions(options);
132
+ this._source = { kind: 'camera', selection: request, options: capture };
133
+ this._explicitCameraSelection = true;
112
134
  return this;
113
135
  }
114
136
 
@@ -123,6 +145,7 @@ class SmartSpectraSDK {
123
145
  * { timestampsPath?, interframeDelayMs?, startOffsetMs?, maxDurationMs?, frameTransform? }
124
146
  */
125
147
  useFile(videoPath, options = null) {
148
+ if (this._explicitCameraSelection) this._requireSourceSelectionAllowed();
126
149
  if (typeof videoPath !== 'string' || !videoPath) {
127
150
  throw new TypeError('useFile: videoPath must be a non-empty string');
128
151
  }
@@ -134,9 +157,10 @@ class SmartSpectraSDK {
134
157
  * Initialize the session and start the configured input source. Returns
135
158
  * once running — for custom input the graph is armed for sendFrame(); for
136
159
  * camera/file the SDK pumps frames itself. Configure a source with
137
- * useCustomInput() / useCamera() / useFile() first.
160
+ * useCustomInput(), useCamera(), or useFile() first.
138
161
  */
139
162
  start() {
163
+ if (this._explicitCameraSelection) this._requireSourceSelectionAllowed();
140
164
  const source = this._source;
141
165
  if (!source) {
142
166
  throw new Error(
@@ -149,7 +173,8 @@ class SmartSpectraSDK {
149
173
  this._session.startCustom(source.frameTransform);
150
174
  break;
151
175
  case 'camera':
152
- this._session.startCamera(source.options);
176
+ if (source.selection) this._session.startCamera(source.selection, source.options);
177
+ else this._session.startLegacyCamera(source.options);
153
178
  break;
154
179
  case 'file':
155
180
  this._session.startFile(
@@ -177,14 +202,14 @@ class SmartSpectraSDK {
177
202
  }
178
203
 
179
204
  /**
180
- * Recover from a kError state. Rebuilds the internal graph. The input
181
- * source is discarded — call useCustomInput() / useCamera() / useFile()
182
- * AND start() again to drive new input.
205
+ * Recover from a kError state. Clears the input source for legacy callers.
206
+ * After useCamera(selection, options), retains the selected input.
207
+ * Typed selection requires any pending stopAsync() to settle before reset.
183
208
  */
184
209
  reset() {
185
210
  if (!this._session) return;
186
- this._session.reset();
187
- this._source = null;
211
+ this._session.reset(this._explicitCameraSelection);
212
+ if (!this._explicitCameraSelection) this._source = null;
188
213
  }
189
214
 
190
215
  /**
@@ -239,15 +264,26 @@ class SmartSpectraSDK {
239
264
  * replacement session, since SDK state is process-global.
240
265
  */
241
266
  destroy() {
267
+ if (this._destroyPromise) return this._destroyPromise;
242
268
  if (!this._session) return Promise.resolve();
243
269
  const teardown = this._session.destroy();
244
270
  this._session = null;
245
271
  this._source = null;
246
- return teardown;
272
+ this._destroyPromise = Promise.resolve(teardown).finally(() => { this._destroyPromise = null; });
273
+ return this._destroyPromise;
247
274
  }
248
275
 
249
276
  // ---------- internal ----------------------------------------------------
250
277
 
278
+ _requireSourceSelectionAllowed() {
279
+ if (this._destroyPromise) {
280
+ throw Object.assign(new Error('SmartSpectra is shutting down'), {
281
+ code: SmartSpectraErrorCode.kInvalidState, retryable: false,
282
+ });
283
+ }
284
+ this._session?.assertSourceSelectionAllowed();
285
+ }
286
+
251
287
  _ensureSession() {
252
288
  if (this._session) return;
253
289
  // ARTERIAL_PRESSURE_TRACE, EDA_TRACE, and micromotion metrics need the
@@ -289,12 +325,14 @@ class SmartSpectraSDK {
289
325
 
290
326
  module.exports = {
291
327
  SmartSpectraSDK,
328
+ CameraSelection,
292
329
  SmartSpectraLogLevel,
293
330
  ProcessingStatus,
294
331
  PixelFormat,
295
332
  ValidationCode,
296
333
  SmartSpectraErrorCode,
297
334
  FrameTransform,
335
+ MetricType,
298
336
  breathingMetrics,
299
337
  cardioMetrics,
300
338
  faceMetrics,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartspectra/node-sdk",
3
- "version": "3.4.0-rc.5",
3
+ "version": "3.4.0-rc.7",
4
4
  "description": "Node.js (Electron) FFI binding for SmartSpectra vitals measurement",
5
5
  "author": "Presage Technologies, Inc.",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -40,7 +40,8 @@
40
40
  "scripts": {
41
41
  "prepare": "node scripts/gen-proto.cjs",
42
42
  "build:proto": "node scripts/gen-proto.cjs",
43
- "test": "node --test test/log-level-pin.js test/renderer-timestamp.js test/smoke.js",
43
+ "test": "npm run test:types && node --test test/log-level-pin.js test/renderer-timestamp.js test/camera-selection.js test/reset-lifecycle.js test/smoke.js",
44
+ "test:types": "tsc --noEmit --strict --skipLibCheck --target es2022 --module nodenext test/camera-selection-types.ts",
44
45
  "test:models-allowlist": "node test/models-allowlist.js"
45
46
  },
46
47
  "keywords": [
@@ -70,14 +71,15 @@
70
71
  "dependencies": {
71
72
  "koffi": "^2.10.0",
72
73
  "protobufjs": "^7.6.5",
73
- "@smartspectra/node-sdk-linux-x64": "3.4.0-rc.5",
74
- "@smartspectra/node-sdk-linux-arm64": "3.4.0-rc.5",
75
- "@smartspectra/node-sdk-darwin-arm64": "3.4.0-rc.5",
76
- "@smartspectra/node-sdk-win32-x64": "3.4.0-rc.5"
74
+ "@smartspectra/node-sdk-linux-x64": "3.4.0-rc.7",
75
+ "@smartspectra/node-sdk-linux-arm64": "3.4.0-rc.7",
76
+ "@smartspectra/node-sdk-darwin-arm64": "3.4.0-rc.7",
77
+ "@smartspectra/node-sdk-win32-x64": "3.4.0-rc.7"
77
78
  },
78
79
  "devDependencies": {
79
80
  "@types/node": "^24.0.0",
80
- "protobufjs-cli": "^1.1.3"
81
+ "protobufjs-cli": "^1.1.3",
82
+ "typescript": "^6.0.2"
81
83
  },
82
84
  "_overrides_doc": "tmp is pulled in transitively (protobufjs-cli → tmp) as a build-only devDependency; it is never a runtime dependency and is not shipped to consumers (the published tarball ships js/ + prebuilds only, no node_modules). Pinned to an exact version (not a >= range) to clear GHSA-ph9p-34f9-6g65 (path traversal, affects tmp <0.2.6) and GHSA-7c78-jf6q-g5cm (type-confusion bypass of the 0.2.6 _assertPath guard, affects tmp >=0.2.6) AND to keep installs deterministic — an open range would let a future install pull a brand-new, not-yet-vetted release. Bump deliberately. Drop this once protobufjs-cli ships a release whose tmp range is already >=0.2.7.",
83
85
  "overrides": {