@smartspectra/node-sdk 3.3.0-rc.3 → 3.3.0-rc.6

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/js/constants.js CHANGED
@@ -48,6 +48,7 @@ const ValidationCode = Object.freeze({
48
48
  kNoFaceFound: 1,
49
49
  kMultipleFacesFound: 2,
50
50
  kFaceNotCentered: 3,
51
+ /** @deprecated Use kFaceTooClose / kFaceTooFar instead. */
51
52
  kFaceSizeOutOfRange: 4,
52
53
  kTooDark: 5,
53
54
  kTooBright: 6,
@@ -55,6 +56,10 @@ const ValidationCode = Object.freeze({
55
56
  kCameraTuning: 10,
56
57
  kFrameRateTooLow: 11,
57
58
  kExcessiveMotion: 12,
59
+ kFaceTooClose: 13,
60
+ kFaceTooFar: 14,
61
+ kFaceTooHigh: 15,
62
+ kFaceTooLow: 16,
58
63
  });
59
64
 
60
65
  const SmartSpectraErrorCode = Object.freeze({
package/js/ffi.js CHANGED
@@ -13,6 +13,9 @@
13
13
 
14
14
  'use strict';
15
15
 
16
+ const fs = require('fs');
17
+ const os = require('os');
18
+ const path = require('path');
16
19
  const koffi = require('koffi');
17
20
  const { resolveNativeLibrary } = require('./resolve-native');
18
21
 
@@ -444,21 +447,38 @@ class Session {
444
447
  () => this._teardownNative(),
445
448
  () => this._teardownNative());
446
449
  } else {
447
- this._teardownNative();
448
- this._teardownPromise = Promise.resolve();
450
+ this._teardownPromise = this._teardownNative();
449
451
  }
450
452
  return this._teardownPromise;
451
453
  }
452
454
 
455
+ // Returns a promise that resolves once native teardown (including any
456
+ // worker-thread joins inside session_destroy) has completed.
453
457
  _teardownNative() {
454
- if (this._sess === null) return;
455
- // stop → wait → destroy → unregister. session_destroy joins worker
456
- // threads, so no SDK thread can call our trampolines after it returns.
458
+ if (this._sess === null) return Promise.resolve();
459
+ // stop → wait → destroy → unregister. session_destroy joins every
460
+ // worker thread SpawnWorker ever started for this session — including
461
+ // one that's mid-flight relaying an onError/onStatus callback to JS
462
+ // (e.g. an async graph failure). That relay can only complete once
463
+ // the JS event loop is free to service it, so running session_destroy
464
+ // synchronously here would deadlock: this call IS the JS thread, and
465
+ // it would be blocked joining a thread that's blocked waiting on this
466
+ // same thread's event loop. Run it on a koffi worker thread instead —
467
+ // the same reason stopAsync() runs session_stop off-thread above —
468
+ // so the event loop stays free no matter what a worker is mid-relay
469
+ // of. _releaseThunks() only runs after the callback fires, matching
470
+ // the original synchronous ordering (unregister only after the joins
471
+ // that guarantee no SDK thread can still call our trampolines).
457
472
  sessionStop(this._sess);
458
473
  sessionWait(this._sess, 0);
459
- sessionDestroy(this._sess);
474
+ const sess = this._sess;
460
475
  this._sess = null;
461
- this._releaseThunks();
476
+ return new Promise((resolve) => {
477
+ sessionDestroy.async(sess, () => {
478
+ this._releaseThunks();
479
+ resolve();
480
+ });
481
+ });
462
482
  }
463
483
 
464
484
  // ---------- internal ----------------------------------------------------
@@ -490,9 +510,11 @@ function makeFfiError(err) {
490
510
  return e;
491
511
  }
492
512
 
493
- // Internal one-time SDK initialization. Called by the Electron main-process
494
- // glue in `@smartspectra/node-sdk/main`. Not part of the package's public
495
- // exports map — host apps don't reach for it.
513
+ // Internal one-time SDK initialization. Called directly by the Electron
514
+ // main-process glue in `@smartspectra/node-sdk/main` (which namespaces its
515
+ // own cache directory under app.getPath('cache')). Not part of the package's
516
+ // public exports map — host apps don't reach for it; use ensurePreconfigured()
517
+ // below for the plain-Node default instead.
496
518
  function preconfigure(sdkDirectory, deviceId) {
497
519
  if (typeof sdkDirectory !== 'string' || sdkDirectory.length === 0) {
498
520
  throw new TypeError('preconfigure: directory must be a non-empty string');
@@ -502,7 +524,48 @@ function preconfigure(sdkDirectory, deviceId) {
502
524
  if (rc !== 0) throw makeFfiError(err);
503
525
  }
504
526
 
527
+ // Per-OS writable cache directory, used as the default when nothing else
528
+ // (e.g. Electron's app.getPath('cache')) has already resolved one.
529
+ function defaultSdkDirectory() {
530
+ const base = process.platform === 'darwin'
531
+ ? path.join(os.homedir(), 'Library', 'Caches')
532
+ : process.platform === 'win32'
533
+ ? (process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'))
534
+ : (process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'));
535
+ return path.join(base, 'SmartSpectraSDK');
536
+ }
537
+
538
+ let autoPreconfigureAttempted = false;
539
+
540
+ // Idempotent, best-effort preconfigure for hosts that never call preconfigure()
541
+ // themselves. ARTERIAL_PRESSURE_TRACE, EDA_TRACE, and micromotion metrics need
542
+ // the device-key cache directory Preconfigure() sets up (smartspectra.h); without
543
+ // it their encrypted-model load fails downstream and Session.start*() throws —
544
+ // only Electron's main-process glue (js/main/index.js) called this previously,
545
+ // so a plain Node host requesting one of those metrics had no way to succeed.
546
+ // Runs once per process; failures are logged, not thrown, so metrics that don't
547
+ // need EMD still work. Called from SmartSpectraSDK._ensureSession() so every
548
+ // entry point — plain Node or Electron — gets it before a session can start;
549
+ // Preconfigure() itself only resolves the directory/device-identity once, so a
550
+ // prior, more specific call (e.g. Electron's app-namespaced one) always wins.
551
+ function ensurePreconfigured() {
552
+ if (autoPreconfigureAttempted) return;
553
+ autoPreconfigureAttempted = true;
554
+ const sdkDirectory = defaultSdkDirectory();
555
+ try {
556
+ // nosemgrep: detect-non-literal-fs-filename - safe: derived from OS-standard cache path env vars / os.homedir(), not untrusted input.
557
+ fs.mkdirSync(sdkDirectory, { recursive: true, mode: 0o700 });
558
+ preconfigure(sdkDirectory);
559
+ } catch (err) {
560
+ console.error(
561
+ `SmartSpectra: automatic preconfigure failed (${err.message || err}). ` +
562
+ 'Arterial pressure, EDA, and micromotion metrics will not be available ' +
563
+ `until preconfigure() succeeds for ${sdkDirectory}; other metrics still work.`);
564
+ }
565
+ }
566
+
505
567
  module.exports = {
506
568
  Session,
507
569
  preconfigure,
570
+ ensurePreconfigured,
508
571
  };
package/js/index.d.ts CHANGED
@@ -46,6 +46,7 @@ export declare const ValidationCode: {
46
46
  readonly kNoFaceFound: 1;
47
47
  readonly kMultipleFacesFound: 2;
48
48
  readonly kFaceNotCentered: 3;
49
+ /** @deprecated Use kFaceTooClose / kFaceTooFar instead. */
49
50
  readonly kFaceSizeOutOfRange: 4;
50
51
  readonly kTooDark: 5;
51
52
  readonly kTooBright: 6;
@@ -53,6 +54,10 @@ export declare const ValidationCode: {
53
54
  readonly kCameraTuning: 10;
54
55
  readonly kFrameRateTooLow: 11;
55
56
  readonly kExcessiveMotion: 12;
57
+ readonly kFaceTooClose: 13;
58
+ readonly kFaceTooFar: 14;
59
+ readonly kFaceTooHigh: 15;
60
+ readonly kFaceTooLow: 16;
56
61
  };
57
62
  export type ValidationCodeValue = typeof ValidationCode[keyof typeof ValidationCode];
58
63
 
@@ -596,6 +596,9 @@ export namespace presage {
596
596
 
597
597
  /** Confidence score for the HRV measurement, expressed as a percentage in the range [0.0, 100.0] */
598
598
  confidence?: (number|null);
599
+
600
+ /** Whether the HRV measurement is considered stable/reliable */
601
+ stable?: (boolean|null);
599
602
  }
600
603
 
601
604
  /** Represents a single HRV measurement computed over some period of pulse data. */
@@ -625,6 +628,9 @@ export namespace presage {
625
628
  /** Confidence score for the HRV measurement, expressed as a percentage in the range [0.0, 100.0] */
626
629
  public confidence: number;
627
630
 
631
+ /** Whether the HRV measurement is considered stable/reliable */
632
+ public stable: boolean;
633
+
628
634
  /**
629
635
  * Creates a new Hrv instance using the specified properties.
630
636
  * @param [properties] Properties to set