@smartspectra/node-sdk 3.3.0-rc.6 → 3.3.0
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/README.md +5 -0
- package/js/constants.js +1 -0
- package/js/ffi.js +37 -16
- package/js/index.d.ts +1 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -60,6 +60,11 @@ thread-safe trampolines.
|
|
|
60
60
|
| Node.js | >= 20 (Node 24 LTS recommended) |
|
|
61
61
|
| Electron (optional) | >= 28 |
|
|
62
62
|
|
|
63
|
+
You also need an API key from
|
|
64
|
+
[physiology.presagetech.com](https://physiology.presagetech.com/auth/login). An AI assistant
|
|
65
|
+
connected to the [SmartSpectra MCP Server](../docs/mcp-server.md) can fetch it from your account
|
|
66
|
+
for you.
|
|
67
|
+
|
|
63
68
|
The native runtime ships in per-platform npm packages pulled in automatically
|
|
64
69
|
as dependencies — no install script and no system libraries to install.
|
|
65
70
|
|
package/js/constants.js
CHANGED
package/js/ffi.js
CHANGED
|
@@ -262,6 +262,17 @@ class Session {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
this._registeredThunks = [];
|
|
265
|
+
// Held from a successful start*() until native teardown completes
|
|
266
|
+
// (_teardownNative(), the destroy() path) — NOT released by
|
|
267
|
+
// stop()/stopAsync()/reset(). Those calls can return while a worker
|
|
268
|
+
// thread is still mid-relay of a trailing callback (e.g. the idle
|
|
269
|
+
// status transition after stop()); only session_destroy's joins
|
|
270
|
+
// guarantee no SDK thread can still call our trampolines. koffi's
|
|
271
|
+
// registered thunks do NOT ref the libuv loop, so without this a host
|
|
272
|
+
// whose only pending work is awaiting an SDK-callback-resolved
|
|
273
|
+
// promise drains its loop, Node disposes the isolate, and the next
|
|
274
|
+
// relayed callback segfaults the process (exit 139).
|
|
275
|
+
this._keepAlive = null;
|
|
265
276
|
this._destroyed = false;
|
|
266
277
|
// In-flight stopAsync() promise, if any. session_stop runs on a koffi
|
|
267
278
|
// worker thread; destroy() must wait for it to settle before deleting
|
|
@@ -329,6 +340,7 @@ class Session {
|
|
|
329
340
|
} : null;
|
|
330
341
|
const rc = sessionStartFile(this._sess, videoPath, timestampsPath, opts, err);
|
|
331
342
|
if (rc !== 0) throw makeFfiError(err);
|
|
343
|
+
this._takeKeepAlive();
|
|
332
344
|
}
|
|
333
345
|
|
|
334
346
|
// options (optional): { deviceIndex, width, height, fps, frameTransform }
|
|
@@ -344,6 +356,7 @@ class Session {
|
|
|
344
356
|
} : null;
|
|
345
357
|
const rc = sessionStartCamera(this._sess, opts, err);
|
|
346
358
|
if (rc !== 0) throw makeFfiError(err);
|
|
359
|
+
this._takeKeepAlive();
|
|
347
360
|
}
|
|
348
361
|
|
|
349
362
|
startCustom(frameTransform = 0) {
|
|
@@ -351,6 +364,7 @@ class Session {
|
|
|
351
364
|
const err = {};
|
|
352
365
|
const rc = sessionStartCustom(this._sess, frameTransform | 0, err);
|
|
353
366
|
if (rc !== 0) throw makeFfiError(err);
|
|
367
|
+
this._takeKeepAlive();
|
|
354
368
|
}
|
|
355
369
|
|
|
356
370
|
sendFrame(buffer, width, height, strideBytes, pixelFormat, timestampUs) {
|
|
@@ -455,27 +469,21 @@ class Session {
|
|
|
455
469
|
// Returns a promise that resolves once native teardown (including any
|
|
456
470
|
// worker-thread joins inside session_destroy) has completed.
|
|
457
471
|
_teardownNative() {
|
|
458
|
-
if (this._sess === null)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
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).
|
|
472
|
-
sessionStop(this._sess);
|
|
473
|
-
sessionWait(this._sess, 0);
|
|
472
|
+
if (this._sess === null) {
|
|
473
|
+
this._releaseKeepAlive();
|
|
474
|
+
return Promise.resolve();
|
|
475
|
+
}
|
|
476
|
+
// session_destroy performs the required best-effort stop and worker
|
|
477
|
+
// joins. Run that entire drain off the JS thread: an SDK worker may be
|
|
478
|
+
// blocked relaying a callback onto this event loop, so synchronously
|
|
479
|
+
// stopping or waiting here would deadlock both threads. Unregister the
|
|
480
|
+
// thunks only after destroy has joined every possible caller.
|
|
474
481
|
const sess = this._sess;
|
|
475
482
|
this._sess = null;
|
|
476
483
|
return new Promise((resolve) => {
|
|
477
484
|
sessionDestroy.async(sess, () => {
|
|
478
485
|
this._releaseThunks();
|
|
486
|
+
this._releaseKeepAlive();
|
|
479
487
|
resolve();
|
|
480
488
|
});
|
|
481
489
|
});
|
|
@@ -498,6 +506,19 @@ class Session {
|
|
|
498
506
|
for (const t of this._registeredThunks) koffi.unregister(t);
|
|
499
507
|
this._registeredThunks = [];
|
|
500
508
|
}
|
|
509
|
+
|
|
510
|
+
_takeKeepAlive() {
|
|
511
|
+
if (this._keepAlive !== null) return;
|
|
512
|
+
// Max-int32 ms (~24.8 days): the timer exists only to hold a loop
|
|
513
|
+
// reference; it never meaningfully fires.
|
|
514
|
+
this._keepAlive = setInterval(() => {}, 2 ** 31 - 1);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
_releaseKeepAlive() {
|
|
518
|
+
if (this._keepAlive === null) return;
|
|
519
|
+
clearInterval(this._keepAlive);
|
|
520
|
+
this._keepAlive = null;
|
|
521
|
+
}
|
|
501
522
|
}
|
|
502
523
|
|
|
503
524
|
// Wrap a populated smartspectra_error_t into a JS Error with numeric `code`
|
package/js/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartspectra/node-sdk",
|
|
3
|
-
"version": "3.3.0
|
|
3
|
+
"version": "3.3.0",
|
|
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,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"prepare": "node scripts/gen-proto.cjs",
|
|
42
42
|
"build:proto": "node scripts/gen-proto.cjs",
|
|
43
|
-
"test": "node test/log-level-pin.js
|
|
43
|
+
"test": "node --test test/log-level-pin.js test/renderer-timestamp.js test/smoke.js",
|
|
44
44
|
"test:models-allowlist": "node test/models-allowlist.js"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
@@ -69,18 +69,18 @@
|
|
|
69
69
|
"_nativeRuntime_doc": "Per-platform native packages (@smartspectra/node-sdk-<plat>-<arch>) are injected into dependencies at publish time (absent here so source installs don't fetch them). Why hard deps, not optionalDependencies: see js/resolve-native.js.",
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"koffi": "^2.10.0",
|
|
72
|
-
"protobufjs": "^7.5
|
|
73
|
-
"@smartspectra/node-sdk-linux-x64": "3.3.0
|
|
74
|
-
"@smartspectra/node-sdk-linux-arm64": "3.3.0
|
|
75
|
-
"@smartspectra/node-sdk-darwin-arm64": "3.3.0
|
|
76
|
-
"@smartspectra/node-sdk-win32-x64": "3.3.0
|
|
72
|
+
"protobufjs": "^7.6.5",
|
|
73
|
+
"@smartspectra/node-sdk-linux-x64": "3.3.0",
|
|
74
|
+
"@smartspectra/node-sdk-linux-arm64": "3.3.0",
|
|
75
|
+
"@smartspectra/node-sdk-darwin-arm64": "3.3.0",
|
|
76
|
+
"@smartspectra/node-sdk-win32-x64": "3.3.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@types/node": "^24.0.0",
|
|
80
80
|
"protobufjs-cli": "^1.1.3"
|
|
81
81
|
},
|
|
82
|
-
"_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 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.
|
|
82
|
+
"_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
83
|
"overrides": {
|
|
84
|
-
"tmp": "0.2.
|
|
84
|
+
"tmp": "0.2.7"
|
|
85
85
|
}
|
|
86
86
|
}
|