@smartspectra/node-sdk 3.2.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/LICENSE +13 -0
- package/README.md +409 -0
- package/js/constants.js +125 -0
- package/js/ffi.js +494 -0
- package/js/index.d.ts +282 -0
- package/js/index.js +60 -0
- package/js/main/index.d.ts +32 -0
- package/js/main/index.js +404 -0
- package/js/messages/generated.d.ts +2083 -0
- package/js/messages/generated.js +5810 -0
- package/js/messages/index.d.ts +27 -0
- package/js/messages/index.js +67 -0
- package/js/preload/index.js +116 -0
- package/js/renderer/index.d.ts +154 -0
- package/js/renderer/index.js +670 -0
- package/js/resolve-native.js +113 -0
- package/js/smartspectra.js +293 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Presage Security, Inc. d/b/a Presage Technologies. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is proprietary to Presage and is made available solely under Presage's Terms of Service, available at <https://physiology.presagetech.com/tos>. Use of this software constitutes acceptance of those Terms. No license, express or implied, is granted to any Presage trademark, trade name, or logo.
|
|
6
|
+
|
|
7
|
+
## No Warranty
|
|
8
|
+
|
|
9
|
+
The software is provided "AS IS," without warranty of any kind, express or implied, to the fullest extent permitted by applicable law.
|
|
10
|
+
|
|
11
|
+
## Questions
|
|
12
|
+
|
|
13
|
+
For licensing questions, contact <support@presagetech.com>.
|
package/README.md
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Node.js SDK
|
|
3
|
+
description: Build Node.js apps — including Electron desktop apps — with SmartSpectra via a packaged native runtime loaded through koffi.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @smartspectra/node-sdk
|
|
7
|
+
|
|
8
|
+
Pure-FFI Node.js (Electron) binding for SmartSpectra vitals measurement.
|
|
9
|
+
[koffi](https://koffi.dev) loads the SmartSpectra C ABI shim at runtime —
|
|
10
|
+
no native addon, no `binding.gyp`, no `electron-rebuild`, no `node-gyp`.
|
|
11
|
+
|
|
12
|
+
## Start here
|
|
13
|
+
|
|
14
|
+
- Electron sample: [electron-quickstart](https://github.com/Presage-Security/SmartSpectra/tree/main/nodejs/samples/electron-quickstart)
|
|
15
|
+
- API reference: <https://smartspectra.presagetech.com/docs/nodejs/api-reference>
|
|
16
|
+
- Metrics guide: <https://smartspectra.presagetech.com/docs/nodejs/metrics>
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
┌──────────────────────────┐
|
|
22
|
+
│ your Node / Electron app │
|
|
23
|
+
└───────────┬──────────────┘
|
|
24
|
+
│ require('@smartspectra/node-sdk')
|
|
25
|
+
┌───────────▼──────────────┐
|
|
26
|
+
│ js/index.js │ public surface
|
|
27
|
+
│ js/smartspectra.js │ SmartSpectraSDK class
|
|
28
|
+
│ js/ffi.js │ koffi types + Session
|
|
29
|
+
│ js/resolve-native.js │ shim path resolver
|
|
30
|
+
└───────────┬──────────────┘
|
|
31
|
+
│ koffi.load()
|
|
32
|
+
┌────────────────────────────────────────┐
|
|
33
|
+
│ @smartspectra/node-sdk-<plat>-<arch>/ │ self-contained native runtime,
|
|
34
|
+
│ libsmartspectra_capi.* │ shipped as a dependency package;
|
|
35
|
+
│ …bundled runtime libraries… │ load paths pre-relocated to
|
|
36
|
+
│ │ @loader_path / $ORIGIN /
|
|
37
|
+
└────────────────────────────────────────┘ adjacent-DLL search
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The native runtime ships in per-platform packages
|
|
41
|
+
(`@smartspectra/node-sdk-<plat>-<arch>`) pulled in as dependencies of the main
|
|
42
|
+
package. There is **no install script** in the published tarball and no
|
|
43
|
+
postinstall download; the binding loads the package matching your machine at
|
|
44
|
+
runtime. SDK-thread callbacks are marshalled onto the V8 event loop by koffi
|
|
45
|
+
thread-safe trampolines.
|
|
46
|
+
|
|
47
|
+
## Supported Platforms
|
|
48
|
+
|
|
49
|
+
| Platform | Status | Notes |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| macOS Apple Silicon (`darwin-arm64`) | Supported | Electron and headless Node workflows supported |
|
|
52
|
+
| Linux x64 (`linux-x64`) | Supported | Requires glibc 2.35+ |
|
|
53
|
+
| Linux ARM64 (`linux-arm64`) | Supported | Requires glibc 2.35+ |
|
|
54
|
+
| Windows x64 (`win32-x64`) | Supported | Electron and headless Node workflows supported |
|
|
55
|
+
|
|
56
|
+
## Common Prerequisites
|
|
57
|
+
|
|
58
|
+
| Requirement | Version |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| Node.js | >= 18 (Node 20 LTS recommended) |
|
|
61
|
+
| Electron (optional) | >= 28 |
|
|
62
|
+
|
|
63
|
+
The native runtime ships in per-platform npm packages pulled in automatically
|
|
64
|
+
as dependencies — no install script and no system libraries to install.
|
|
65
|
+
|
|
66
|
+
Supported platforms: `darwin-arm64`, `linux-x64`, `linux-arm64`, `win32-x64`.
|
|
67
|
+
|
|
68
|
+
On Linux the native runtime is built on Ubuntu 22.04 (Jammy), so it requires
|
|
69
|
+
glibc >= 2.35 — Ubuntu 22.04+, Debian 12+, or any distribution at least that
|
|
70
|
+
new. glibc and libstdc++ are provided by your system, not bundled.
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install @smartspectra/node-sdk
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Early adopters can track the latest release candidate with the `rc` dist-tag:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install @smartspectra/node-sdk@rc
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The native packages (`@smartspectra/node-sdk-<platform>-<arch>`) come in as
|
|
85
|
+
dependencies and the binding loads the one matching your `platform`/`arch` at
|
|
86
|
+
runtime. Each is self-contained — it bundles everything the binding loads at
|
|
87
|
+
runtime, with paths already rewritten at publish time, so nothing needs to be on
|
|
88
|
+
PATH and no system libraries are required. There is no install script.
|
|
89
|
+
|
|
90
|
+
> **Note** — `npm install` downloads the native runtime for every supported
|
|
91
|
+
> platform (a few hundred MB total), not just your host's. This is expected;
|
|
92
|
+
> only the package matching your machine is loaded at runtime.
|
|
93
|
+
|
|
94
|
+
If your platform is unsupported (no `@smartspectra/node-sdk-<plat>-<arch>` is
|
|
95
|
+
published for it), the first `require()` fails with an actionable error naming
|
|
96
|
+
the missing package.
|
|
97
|
+
|
|
98
|
+
## Pick Your Integration Path
|
|
99
|
+
|
|
100
|
+
- Electron desktop app: use `@smartspectra/node-sdk/main`,
|
|
101
|
+
`@smartspectra/node-sdk/preload`, and `@smartspectra/node-sdk/renderer`.
|
|
102
|
+
- Headless or server-side Node process: use `@smartspectra/node-sdk`
|
|
103
|
+
directly and push frames with `useCustomInput()` / `sendFrame()`.
|
|
104
|
+
- Runnable reference app: [electron-quickstart](https://github.com/Presage-Security/SmartSpectra/tree/main/nodejs/samples/electron-quickstart)
|
|
105
|
+
|
|
106
|
+
## Headless Node Quickstart
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import {
|
|
110
|
+
SmartSpectraSDK, PixelFormat, FrameTransform, ProcessingStatus,
|
|
111
|
+
breathingMetrics, cardioMetrics,
|
|
112
|
+
decodeMetrics, setMetricsClass,
|
|
113
|
+
} from '@smartspectra/node-sdk';
|
|
114
|
+
|
|
115
|
+
// Optional: override the default Metrics decoder.
|
|
116
|
+
// import { Metrics } from './generated/metrics_pb';
|
|
117
|
+
// setMetricsClass(Metrics);
|
|
118
|
+
|
|
119
|
+
const sdk = new SmartSpectraSDK({
|
|
120
|
+
apiKey: 'YOUR_API_KEY',
|
|
121
|
+
requestedMetrics: [...breathingMetrics, ...cardioMetrics],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
sdk.on('processingStatus', (status) => console.log('Processing status:', status));
|
|
125
|
+
sdk.on('validationStatus', (code, ts, hint) =>
|
|
126
|
+
console.log('Validation:', code, hint, 'at', ts, 'µs'));
|
|
127
|
+
sdk.on('metrics', (buf, ts) => {
|
|
128
|
+
const m = decodeMetrics(buf);
|
|
129
|
+
console.log('Metrics at', ts, 'µs');
|
|
130
|
+
});
|
|
131
|
+
sdk.on('error', (code, message, retryable) =>
|
|
132
|
+
console.error('SmartSpectra error', code, message, 'retryable=', retryable));
|
|
133
|
+
|
|
134
|
+
sdk.useCustomInput(FrameTransform.kNone);
|
|
135
|
+
sdk.start();
|
|
136
|
+
|
|
137
|
+
// In your capture loop:
|
|
138
|
+
sdk.sendFrame(rgbBuf, width, height, width * 3, PixelFormat.kRGB, captureTsUs);
|
|
139
|
+
|
|
140
|
+
// On shutdown:
|
|
141
|
+
await sdk.destroy();
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## API reference
|
|
145
|
+
|
|
146
|
+
The full API — `SmartSpectraSDK` constructor options, methods, events, error
|
|
147
|
+
codes, and enums — lives in the [API reference](docs/api-reference.md),
|
|
148
|
+
generated from the SDK's bundled TypeScript declarations so it tracks the
|
|
149
|
+
published package. For which metrics to request and how to read the decoded
|
|
150
|
+
payloads, see the [metrics guide](docs/metrics.md).
|
|
151
|
+
|
|
152
|
+
<!-- The two members below are not yet emitted by docs-site/scripts/generate-api.ts
|
|
153
|
+
(it doesn't capture fields on the ambient `declare class`); documented here
|
|
154
|
+
until that generator gap is fixed, then they move to the API reference. -->
|
|
155
|
+
Two instance members aren't yet in the generated reference:
|
|
156
|
+
|
|
157
|
+
| Member | Type | Notes |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| `sdk.processingStatus` | `ProcessingStatusValue` | Current processing status (read-only). |
|
|
160
|
+
| `SmartSpectraSDK.version` | `string` | Static; the SDK package version. |
|
|
161
|
+
|
|
162
|
+
## Electron integration
|
|
163
|
+
|
|
164
|
+
Runnable sample at [electron-quickstart](https://github.com/Presage-Security/SmartSpectra/tree/main/nodejs/samples/electron-quickstart).
|
|
165
|
+
See its [README](https://github.com/Presage-Security/SmartSpectra/blob/main/nodejs/samples/electron-quickstart/README.md).
|
|
166
|
+
|
|
167
|
+
The package ships these entry points:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
@smartspectra/node-sdk → SmartSpectraSDK (low-level, main process)
|
|
171
|
+
@smartspectra/node-sdk/main → bindSmartSpectraIpc(window)
|
|
172
|
+
@smartspectra/node-sdk/preload → preload bridge (contextBridge)
|
|
173
|
+
@smartspectra/node-sdk/renderer → SmartSpectraSDK (renderer-side, MediaStream input)
|
|
174
|
+
@smartspectra/node-sdk/messages → decodeMetrics(buf) + the generated Metrics class
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Main process
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { app, BrowserWindow } from 'electron';
|
|
181
|
+
import { bindSmartSpectraIpc } from '@smartspectra/node-sdk/main';
|
|
182
|
+
|
|
183
|
+
app.whenReady().then(() => {
|
|
184
|
+
const win = new BrowserWindow({
|
|
185
|
+
webPreferences: {
|
|
186
|
+
preload: require.resolve('@smartspectra/node-sdk/preload'),
|
|
187
|
+
contextIsolation: true,
|
|
188
|
+
sandbox: true,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
bindSmartSpectraIpc(win);
|
|
192
|
+
win.loadFile('index.html');
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`bindSmartSpectraIpc` listens on a single private IPC channel, accepts the
|
|
197
|
+
MessagePort the preload ships from the renderer, and owns one
|
|
198
|
+
`SmartSpectraSDK` per renderer connection. SDK teardown happens
|
|
199
|
+
automatically on window close.
|
|
200
|
+
|
|
201
|
+
### Renderer
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { SmartSpectraSDK } from '@smartspectra/node-sdk/renderer';
|
|
205
|
+
import { breathingMetrics, cardioMetrics } from '@smartspectra/node-sdk';
|
|
206
|
+
|
|
207
|
+
const sdk = new SmartSpectraSDK({
|
|
208
|
+
apiKey: 'YOUR_KEY',
|
|
209
|
+
requestedMetrics: [...breathingMetrics, ...cardioMetrics],
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
sdk.on('streamAvailable', (stream) => { videoEl.srcObject = stream; });
|
|
213
|
+
sdk.on('metrics', (buf, ts) => { /* render dashboard */ });
|
|
214
|
+
sdk.on('validationStatus', (code, ts, hint) => { /* show user hint */ });
|
|
215
|
+
sdk.on('error', (code, msg, retryable) => { /* surface in UI */ });
|
|
216
|
+
|
|
217
|
+
await sdk.start(); // SDK acquires the front camera + emits 'streamAvailable'
|
|
218
|
+
|
|
219
|
+
await sdk.requestInsight('How is my breathing?');
|
|
220
|
+
await sdk.stop(); // SDK releases the camera; next start() re-acquires
|
|
221
|
+
sdk.destroy();
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Defaults: front-facing camera at 1280x720 / 30 fps, AE/AWB/focus locked
|
|
225
|
+
once the graph reports `Running`. Call `sdk.useMediaStream(stream)` before
|
|
226
|
+
`sdk.start()` to override with a virtual camera,
|
|
227
|
+
`<canvas>.captureStream()`, `desktopCapturer`, etc. Host-supplied streams
|
|
228
|
+
are managed by the host; SDK-acquired streams are released on
|
|
229
|
+
`stop()` / `reset()` / `destroy()`.
|
|
230
|
+
|
|
231
|
+
The renderer uses `MediaStreamTrackProcessor` + `OffscreenCanvas` to
|
|
232
|
+
extract RGBA pixels and ships each frame to the main process via the
|
|
233
|
+
MessagePort. Graph callbacks flow back through the same port.
|
|
234
|
+
|
|
235
|
+
Each control call (`start` / `stop` / `reset` / `requestInsight`) waits for
|
|
236
|
+
an ack from the main process. If the main process crashes or stalls, the
|
|
237
|
+
call rejects after `sendTimeoutMs` (default `30000`) with an `Error` whose
|
|
238
|
+
`code` is `'SMARTSPECTRA_IPC_TIMEOUT'`, so the UI surfaces "main process
|
|
239
|
+
unresponsive" instead of hanging forever. Pass `sendTimeoutMs` to the
|
|
240
|
+
constructor to tune it, or `0` to disable.
|
|
241
|
+
|
|
242
|
+
### Preload
|
|
243
|
+
|
|
244
|
+
If the app already has a preload script, require this module from it:
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
require('@smartspectra/node-sdk/preload');
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Otherwise point `webPreferences.preload` directly:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
preload: require.resolve('@smartspectra/node-sdk/preload')
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Content Security Policy
|
|
257
|
+
|
|
258
|
+
The renderer runs a Web Worker from a `blob:` URL. If your app sets a CSP,
|
|
259
|
+
allow `blob:` in `worker-src`:
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<meta http-equiv="Content-Security-Policy"
|
|
263
|
+
content="default-src 'self'; worker-src 'self' blob:;">
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
A `Refused to create a worker from 'blob:…'` console error at `start()`
|
|
267
|
+
time means the policy is blocking the worker.
|
|
268
|
+
|
|
269
|
+
### Permission flow
|
|
270
|
+
|
|
271
|
+
Camera permission flows through Electron's native handler — the SDK calls
|
|
272
|
+
`navigator.mediaDevices.getUserMedia()` (or the host's stream, if supplied
|
|
273
|
+
via `useMediaStream()`) and Electron surfaces the OS prompt + indicator
|
|
274
|
+
LED. To grant programmatically:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import { session } from 'electron';
|
|
278
|
+
|
|
279
|
+
session.defaultSession.setPermissionRequestHandler((wc, permission, callback, details) => {
|
|
280
|
+
// Grant only the camera, and only to your own bundled page — deny everything
|
|
281
|
+
// else so a window that later loads remote content can't auto-grant the camera.
|
|
282
|
+
const url = (details && details.requestingUrl) || (wc && wc.getURL()) || '';
|
|
283
|
+
callback(permission === 'media' && url.startsWith('file://'));
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
On macOS, add `NSCameraUsageDescription` to `Info.plist`. Windows and
|
|
288
|
+
Linux need no additional entitlements.
|
|
289
|
+
|
|
290
|
+
## Electron desktop packaging
|
|
291
|
+
|
|
292
|
+
The installed platform package
|
|
293
|
+
`node_modules/@smartspectra/node-sdk-<plat>-<arch>/` ships the full native
|
|
294
|
+
closure — every library `libsmartspectra_capi` needs at runtime, with
|
|
295
|
+
install_names / RPATHs pre-relocated to `@loader_path` (macOS), `$ORIGIN`
|
|
296
|
+
(Linux), or adjacent-directory search (Windows). Include that directory as an
|
|
297
|
+
extra resource and you're done.
|
|
298
|
+
|
|
299
|
+
### electron-builder
|
|
300
|
+
|
|
301
|
+
Use one block per target OS. electron-builder's `${platform}` macro expands to
|
|
302
|
+
the **build host's** platform, not the build target — so a single
|
|
303
|
+
`${platform}` entry ships the wrong (or no) closure on a cross-build
|
|
304
|
+
(e.g. `electron-builder --win` on a Mac). The per-OS `mac`/`win`/`linux` blocks
|
|
305
|
+
are applied only to their matching target and are cross-build-safe:
|
|
306
|
+
|
|
307
|
+
```jsonc
|
|
308
|
+
// package.json
|
|
309
|
+
{
|
|
310
|
+
"build": {
|
|
311
|
+
"mac": { "extraResources": [{ "from": "node_modules/@smartspectra/node-sdk-darwin-${arch}/", "to": "smartspectra/" }] },
|
|
312
|
+
"win": { "extraResources": [{ "from": "node_modules/@smartspectra/node-sdk-win32-${arch}/", "to": "smartspectra/" }] },
|
|
313
|
+
"linux": { "extraResources": [{ "from": "node_modules/@smartspectra/node-sdk-linux-${arch}/", "to": "smartspectra/" }] }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### electron-forge
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
// forge.config.js
|
|
322
|
+
module.exports = {
|
|
323
|
+
packagerConfig: {
|
|
324
|
+
extraResource: [
|
|
325
|
+
`node_modules/@smartspectra/node-sdk-${process.platform}-${process.arch}/`,
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
> **Cross-builds:** `process.platform`/`process.arch` resolve at config-load
|
|
332
|
+
> time to the **host**, not the build target — so this single line is correct
|
|
333
|
+
> only for native (per-OS-CI) builds. To cross-build, switch on the target
|
|
334
|
+
> (`--platform`/`--arch` passed to `electron-forge package`) and emit the
|
|
335
|
+
> matching `node_modules/@smartspectra/node-sdk-<target>/` path.
|
|
336
|
+
|
|
337
|
+
### macOS code signing
|
|
338
|
+
|
|
339
|
+
The bundled `.dylib` files are ad-hoc signed so dyld will load them; for
|
|
340
|
+
distribution you'll want to re-sign with your own identity. With
|
|
341
|
+
electron-builder:
|
|
342
|
+
|
|
343
|
+
```jsonc
|
|
344
|
+
{
|
|
345
|
+
"build": {
|
|
346
|
+
"mac": {
|
|
347
|
+
"hardenedRuntime": true,
|
|
348
|
+
"entitlements": "build/entitlements.mac.plist",
|
|
349
|
+
"extendInfo": { "NSCameraUsageDescription": "Vitals measurement" }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
In `entitlements.mac.plist`, allow loading the bundled dylibs:
|
|
356
|
+
|
|
357
|
+
```xml
|
|
358
|
+
<key>com.apple.security.cs.disable-library-validation</key>
|
|
359
|
+
<true/>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Alternative: re-sign every `.dylib` under
|
|
363
|
+
`node_modules/@smartspectra/node-sdk-darwin-arm64/` with your own identity
|
|
364
|
+
before packaging (preferable for notarization-strict deployments).
|
|
365
|
+
|
|
366
|
+
### Windows code signing
|
|
367
|
+
|
|
368
|
+
Authenticode-sign each `.dll` under
|
|
369
|
+
`node_modules/@smartspectra/node-sdk-win32-x64/` alongside your app's main
|
|
370
|
+
executable. Windows resolves adjacent DLLs first, so
|
|
371
|
+
placing them in the same directory as `<your-app>.exe` is the simplest
|
|
372
|
+
layout.
|
|
373
|
+
|
|
374
|
+
### Linux
|
|
375
|
+
|
|
376
|
+
No signing required. The bundled `.so` files have `$ORIGIN` RPATHs so
|
|
377
|
+
they resolve siblings without any `LD_LIBRARY_PATH` plumbing.
|
|
378
|
+
|
|
379
|
+
**glibc floor — Ubuntu 22.04 (Jammy, `GLIBC_2.35`).** The runtime closure is
|
|
380
|
+
built on a Jammy base, so a packaged app (e.g. an AppImage) runs on Ubuntu
|
|
381
|
+
22.04 and any newer distribution. glibc and the C++ runtime come from the
|
|
382
|
+
host system — they are not vendored in the platform package.
|
|
383
|
+
|
|
384
|
+
## Performance notes
|
|
385
|
+
|
|
386
|
+
- koffi adds ~100 ns per FFI call — negligible for `sendFrame()` at 30 fps.
|
|
387
|
+
- SDK callbacks arrive on worker threads; koffi marshals them onto the V8
|
|
388
|
+
event loop, so listeners run on the main thread.
|
|
389
|
+
- In Electron, the renderer-side SDK (`@smartspectra/node-sdk/renderer`)
|
|
390
|
+
captures frames in the renderer and ships them to the main process —
|
|
391
|
+
simplest to wire and fine for typical use. For the lowest latency at high
|
|
392
|
+
resolution, capture in the main process instead (`desktopCapturer` or an
|
|
393
|
+
off-screen renderer), since renderer→main IPC adds frame-time latency that
|
|
394
|
+
scales with resolution.
|
|
395
|
+
|
|
396
|
+
## Troubleshooting
|
|
397
|
+
|
|
398
|
+
| Symptom | Likely cause | Fix |
|
|
399
|
+
| --- | --- | --- |
|
|
400
|
+
| `the native runtime package "@smartspectra/node-sdk-<plat>-<arch>" is not installed` on require | platform unsupported, or a partial/offline install dropped the dependency | Confirm your platform is supported, then re-run `npm install` |
|
|
401
|
+
| `Cannot open shared object file` / `LoadLibrary failed` on require | platform package present but its bundled closure is incomplete or corrupt | Reinstall the platform package (`npm install`) |
|
|
402
|
+
| `kAuthenticationFailed` | API key issue | Check the key + the keychain entitlement on macOS |
|
|
403
|
+
| `kNonMonotonicTimestamp` | Wall-clock timestamps | Use `process.hrtime.bigint() / 1000n` (monotonic) |
|
|
404
|
+
|
|
405
|
+
## Support
|
|
406
|
+
|
|
407
|
+
- Docs site: <https://smartspectra.presagetech.com/docs/nodejs>
|
|
408
|
+
- GitHub issues: <https://github.com/Presage-Security/SmartSpectra/issues>
|
|
409
|
+
- Email: <support@presagetech.com>
|
package/js/constants.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// constants.js
|
|
2
|
+
// Copyright (C) 2026 Presage Technologies, Inc.
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: LicenseRef-Proprietary
|
|
5
|
+
//
|
|
6
|
+
// Pure-value exports — public enums and metric bundles. No Node-only
|
|
7
|
+
// dependencies so this module can be bundled into a browser renderer
|
|
8
|
+
// without dragging in koffi, fs, or path.
|
|
9
|
+
//
|
|
10
|
+
// Integer values must match the SmartSpectra C++ headers and
|
|
11
|
+
// metric_types.proto wire values; stable across SDK versions.
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const ProcessingStatus = Object.freeze({
|
|
16
|
+
kUninitialized: 0,
|
|
17
|
+
kIdle: 1,
|
|
18
|
+
kStarting: 2,
|
|
19
|
+
kRunning: 3,
|
|
20
|
+
kStopping: 4,
|
|
21
|
+
kError: 5,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const PixelFormat = Object.freeze({
|
|
25
|
+
kRGB: 0,
|
|
26
|
+
kBGR: 1,
|
|
27
|
+
kRGBA: 2,
|
|
28
|
+
kBGRA: 3,
|
|
29
|
+
kNV12: 4,
|
|
30
|
+
kNV21: 5,
|
|
31
|
+
kYUYV: 6,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const ValidationCode = Object.freeze({
|
|
35
|
+
kOk: 0,
|
|
36
|
+
kNoFaceFound: 1,
|
|
37
|
+
kMultipleFacesFound: 2,
|
|
38
|
+
kFaceNotCentered: 3,
|
|
39
|
+
kFaceSizeOutOfRange: 4,
|
|
40
|
+
kTooDark: 5,
|
|
41
|
+
kTooBright: 6,
|
|
42
|
+
kChestNotVisible: 7,
|
|
43
|
+
kCameraTuning: 10,
|
|
44
|
+
kFrameRateTooLow: 11,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const SmartSpectraErrorCode = Object.freeze({
|
|
48
|
+
kOk: 0,
|
|
49
|
+
kInvalidState: 1,
|
|
50
|
+
kAuthenticationFailed: 2,
|
|
51
|
+
kConfigurationFailed: 3,
|
|
52
|
+
kCreditExhausted: 4,
|
|
53
|
+
kNetworkError: 5,
|
|
54
|
+
kServerError: 6,
|
|
55
|
+
kInputUnavailable: 7,
|
|
56
|
+
kProcessingFailed: 8,
|
|
57
|
+
kFrameConversionFailed: 9,
|
|
58
|
+
kNonMonotonicTimestamp: 10,
|
|
59
|
+
kTimestampGap: 11,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Spatial transform applied to each pushed frame before it enters the
|
|
63
|
+
// graph. Pass to `useCustomInput(frameTransform)`.
|
|
64
|
+
const FrameTransform = Object.freeze({
|
|
65
|
+
kNone: 0,
|
|
66
|
+
kRotate90CW: 1,
|
|
67
|
+
kRotate90CCW: 2,
|
|
68
|
+
kRotate180: 3,
|
|
69
|
+
kMirrorHorizontal: 4,
|
|
70
|
+
kMirrorVertical: 5,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// MetricType integer codes grouped by measurement type. Spread into
|
|
74
|
+
// `requestedMetrics`; omitting `requestedMetrics` is equivalent to passing
|
|
75
|
+
// `breathingMetrics`.
|
|
76
|
+
const breathingMetrics = Object.freeze([
|
|
77
|
+
0, // CHEST_BREATHING
|
|
78
|
+
1, // ABDOMEN_BREATHING
|
|
79
|
+
2, // BREATHING_RATE
|
|
80
|
+
3, // BREATHING_AMPLITUDE
|
|
81
|
+
4, // APNEA
|
|
82
|
+
5, // RESPIRATORY_LINE_LENGTH
|
|
83
|
+
6, // BASELINE
|
|
84
|
+
7, // INHALE_EXHALE_RATIO
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const cardioMetrics = Object.freeze([
|
|
88
|
+
15, // PULSE_RATE
|
|
89
|
+
16, // ARTERIAL_PRESSURE_TRACE
|
|
90
|
+
17, // HRV
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const faceMetrics = Object.freeze([
|
|
94
|
+
11, // FACE_LANDMARKS
|
|
95
|
+
12, // BLINKING
|
|
96
|
+
13, // TALKING
|
|
97
|
+
14, // EXPRESSIONS
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const micromotionMetrics = Object.freeze([
|
|
101
|
+
8, // GLUTES_MICROMOTION
|
|
102
|
+
9, // KNEES_MICROMOTION
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const edaMetrics = Object.freeze([
|
|
106
|
+
10, // EDA_TRACE
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// The default metric bundle used when `requestedMetrics` is omitted; currently
|
|
110
|
+
// equals the breathing bundle.
|
|
111
|
+
const defaultSupportedMetrics = breathingMetrics;
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
ProcessingStatus,
|
|
115
|
+
PixelFormat,
|
|
116
|
+
ValidationCode,
|
|
117
|
+
SmartSpectraErrorCode,
|
|
118
|
+
FrameTransform,
|
|
119
|
+
breathingMetrics,
|
|
120
|
+
cardioMetrics,
|
|
121
|
+
faceMetrics,
|
|
122
|
+
micromotionMetrics,
|
|
123
|
+
edaMetrics,
|
|
124
|
+
defaultSupportedMetrics,
|
|
125
|
+
};
|