@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
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// resolve-native.js
|
|
2
|
+
// Copyright (C) 2026 Presage Technologies, Inc.
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: LicenseRef-Proprietary
|
|
5
|
+
//
|
|
6
|
+
// Picks the right libsmartspectra_capi.{dll,dylib,so} for the current
|
|
7
|
+
// platform+arch and returns a path that koffi.load() can consume.
|
|
8
|
+
//
|
|
9
|
+
// The native runtime closure (shim + libsmartspectra + OpenCV + Vulkan +
|
|
10
|
+
// graph data) ships in per-platform packages — `@smartspectra/node-sdk-<plat>-<arch>`
|
|
11
|
+
// — declared as regular (HARD) dependencies of the main package. There is no
|
|
12
|
+
// postinstall download and no install script in the published tarball.
|
|
13
|
+
//
|
|
14
|
+
// Why hard deps and not optionalDependencies gated by os/cpu: GitLab's npm
|
|
15
|
+
// registry strips optionalDependencies/os/cpu from the packument, so optional
|
|
16
|
+
// gating never resolves. As hard deps every platform installs on every host,
|
|
17
|
+
// and this resolver picks the one matching process.platform/process.arch. See
|
|
18
|
+
// .session-logs/nodejs-bundle-all-platforms.md and package.json#_nativeRuntime_doc.
|
|
19
|
+
//
|
|
20
|
+
// Resolution order:
|
|
21
|
+
// 1. SMARTSPECTRA_CAPI_PATH env var — full path to the shared library.
|
|
22
|
+
// The sole dev/override escape hatch (e.g. running against a local
|
|
23
|
+
// source build, or a custom-bundled closure). In a source checkout the
|
|
24
|
+
// platform packages are NOT installed, so dev uses this override.
|
|
25
|
+
// 2. The host platform package's root, located via require.resolve. The shim
|
|
26
|
+
// sits at the package root; the rest of the closure sits beside it with
|
|
27
|
+
// install_names / RPATHs pre-rewritten so it loads with zero env vars.
|
|
28
|
+
//
|
|
29
|
+
// If neither resolves, we throw a single clear error naming the missing
|
|
30
|
+
// platform package and the SMARTSPECTRA_CAPI_PATH override.
|
|
31
|
+
|
|
32
|
+
'use strict';
|
|
33
|
+
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
|
|
37
|
+
const LIB_BASENAME = {
|
|
38
|
+
darwin: 'libsmartspectra_capi.dylib',
|
|
39
|
+
linux: 'libsmartspectra_capi.so',
|
|
40
|
+
win32: 'smartspectra_capi.dll',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// `<platform>-<arch>` — matches process.platform / process.arch and the
|
|
44
|
+
// per-platform package suffix.
|
|
45
|
+
function platformArch() {
|
|
46
|
+
return `${process.platform}-${process.arch}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function platformPackageName() {
|
|
50
|
+
return `@smartspectra/node-sdk-${platformArch()}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveNativeLibrary() {
|
|
54
|
+
const libName = LIB_BASENAME[process.platform];
|
|
55
|
+
if (!libName) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`@smartspectra/node-sdk: unsupported platform ${process.platform}. ` +
|
|
58
|
+
`Supported: darwin (macOS), linux, win32 (Windows).`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const override = process.env.SMARTSPECTRA_CAPI_PATH;
|
|
62
|
+
if (override) {
|
|
63
|
+
if (!fs.existsSync(override)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`@smartspectra/node-sdk: SMARTSPECTRA_CAPI_PATH=${override} ` +
|
|
66
|
+
`points at a file that does not exist.`);
|
|
67
|
+
}
|
|
68
|
+
return override;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const platformPkg = platformPackageName();
|
|
72
|
+
let pkgRoot;
|
|
73
|
+
try {
|
|
74
|
+
// Resolve the package's own package.json, then take its directory —
|
|
75
|
+
// require.resolve(pkgName) would need a "main"/"exports" entry the
|
|
76
|
+
// binary-only package intentionally lacks.
|
|
77
|
+
pkgRoot = path.dirname(require.resolve(`${platformPkg}/package.json`));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// A corrupt install (broken symlink, permissions) surfaces as something
|
|
80
|
+
// other than a missing module — don't bury its real cause under the
|
|
81
|
+
// "not installed" guidance, which would send the user down the wrong path.
|
|
82
|
+
if (e && e.code !== 'MODULE_NOT_FOUND') {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`@smartspectra/node-sdk: failed to resolve the native runtime package ` +
|
|
85
|
+
`"${platformPkg}" (${e.code || 'unknown error'}): ${e.message}`);
|
|
86
|
+
}
|
|
87
|
+
throw new Error(
|
|
88
|
+
`@smartspectra/node-sdk: the native runtime package "${platformPkg}" is not installed.\n` +
|
|
89
|
+
`It is a regular dependency of @smartspectra/node-sdk, so this usually means your ` +
|
|
90
|
+
`platform (${platformArch()}) is unsupported, or the install was incomplete ` +
|
|
91
|
+
`(e.g. \`npm install --omit=optional\` does NOT skip it, but a partial/offline ` +
|
|
92
|
+
`install might).\n` +
|
|
93
|
+
`Supported platforms: darwin-arm64, linux-x64, linux-arm64, win32-x64 ` +
|
|
94
|
+
`(glibc only — musl/Alpine Linux is not supported).\n` +
|
|
95
|
+
`To point at a custom build, set SMARTSPECTRA_CAPI_PATH to the shared library path.`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const libPath = path.join(pkgRoot, libName);
|
|
99
|
+
if (!fs.existsSync(libPath)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`@smartspectra/node-sdk: "${platformPkg}" is installed but ${libName} is missing from it ` +
|
|
102
|
+
`(${libPath}). The package may be corrupt — try reinstalling.`);
|
|
103
|
+
}
|
|
104
|
+
return libPath;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
resolveNativeLibrary,
|
|
109
|
+
// Exposed for diagnostics and tests.
|
|
110
|
+
LIB_BASENAME,
|
|
111
|
+
platformArch,
|
|
112
|
+
platformPackageName,
|
|
113
|
+
};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// smartspectra.js
|
|
2
|
+
// Copyright (C) 2026 Presage Technologies, Inc.
|
|
3
|
+
//
|
|
4
|
+
// SPDX-License-Identifier: LicenseRef-Proprietary
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { Session } = require('./ffi');
|
|
9
|
+
const {
|
|
10
|
+
ProcessingStatus,
|
|
11
|
+
PixelFormat,
|
|
12
|
+
ValidationCode,
|
|
13
|
+
SmartSpectraErrorCode,
|
|
14
|
+
FrameTransform,
|
|
15
|
+
breathingMetrics,
|
|
16
|
+
cardioMetrics,
|
|
17
|
+
faceMetrics,
|
|
18
|
+
micromotionMetrics,
|
|
19
|
+
edaMetrics,
|
|
20
|
+
defaultSupportedMetrics,
|
|
21
|
+
} = require('./constants');
|
|
22
|
+
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
23
|
+
|
|
24
|
+
const SUPPORTED_EVENTS = new Set([
|
|
25
|
+
'processingStatus',
|
|
26
|
+
'validationStatus',
|
|
27
|
+
'metrics',
|
|
28
|
+
'accumulatedMetrics',
|
|
29
|
+
'insight',
|
|
30
|
+
'error',
|
|
31
|
+
'frameSentThrough',
|
|
32
|
+
'videoOutput',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// FrameTransform.kNone — kept here so the constructor doesn't depend on the
|
|
36
|
+
// FrameTransform object declared later.
|
|
37
|
+
const DEFAULT_FRAME_TRANSFORM = 0;
|
|
38
|
+
|
|
39
|
+
class SmartSpectraSDK {
|
|
40
|
+
/** SDK package version (matches `package.json#version`). */
|
|
41
|
+
static get version() { return PACKAGE_VERSION; }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Object} [options]
|
|
45
|
+
* @param {string} [options.apiKey]
|
|
46
|
+
* @param {number[]} [options.requestedMetrics]
|
|
47
|
+
* MetricType integer codes. Defaults to `breathingMetrics`.
|
|
48
|
+
* @param {boolean} [options.enableAccumulatedOutput=false]
|
|
49
|
+
*/
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this._options = {
|
|
52
|
+
apiKey: options.apiKey ?? '',
|
|
53
|
+
requestedMetrics: options.requestedMetrics,
|
|
54
|
+
enableAccumulatedOutput: !!options.enableAccumulatedOutput,
|
|
55
|
+
};
|
|
56
|
+
this._listeners = new Map();
|
|
57
|
+
// Pending input source set by useCustomInput()/useCamera()/useFile() and
|
|
58
|
+
// consumed by start(). Shape: { kind: 'custom'|'camera'|'file', ... }.
|
|
59
|
+
// The last use* call wins.
|
|
60
|
+
this._source = null;
|
|
61
|
+
this._session = null; // lazy: created on start()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------- public API --------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register a callback for a named event. Returns `this` for chaining.
|
|
68
|
+
* One listener per event; re-registration replaces.
|
|
69
|
+
*/
|
|
70
|
+
on(event, callback) {
|
|
71
|
+
if (typeof event !== 'string' || typeof callback !== 'function') {
|
|
72
|
+
throw new TypeError('on(event: string, callback: Function)');
|
|
73
|
+
}
|
|
74
|
+
if (!SUPPORTED_EVENTS.has(event)) {
|
|
75
|
+
throw new RangeError(`on(): unsupported event ${event}`);
|
|
76
|
+
}
|
|
77
|
+
this._listeners.set(event, callback);
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Select the custom frame-push input source (you push frames via
|
|
83
|
+
* sendFrame() after start()). Returns `this` for chaining.
|
|
84
|
+
*
|
|
85
|
+
* @param {number} [frameTransform=FrameTransform.kNone]
|
|
86
|
+
* Spatial transform applied to every pushed frame.
|
|
87
|
+
*/
|
|
88
|
+
useCustomInput(frameTransform = DEFAULT_FRAME_TRANSFORM) {
|
|
89
|
+
this._source = { kind: 'custom', frameTransform: frameTransform | 0 };
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Select a live camera as the input source. The SDK opens the camera and
|
|
95
|
+
* pumps frames internally on start() — no sendFrame() needed. Returns
|
|
96
|
+
* `this` for chaining.
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} [options] { deviceIndex?, width?, height?, fps?, frameTransform? }
|
|
99
|
+
*
|
|
100
|
+
* Captures from a camera in THIS process (headless / Node use). In an
|
|
101
|
+
* Electron app the camera is normally owned by the renderer via
|
|
102
|
+
* getUserMedia — use the renderer SDK's useMediaStream() there instead.
|
|
103
|
+
*/
|
|
104
|
+
useCamera(options = null) {
|
|
105
|
+
this._source = { kind: 'camera', options };
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Select a pre-recorded video file as the input source. start() begins
|
|
111
|
+
* playback (non-blocking — it runs on SDK worker threads); use
|
|
112
|
+
* waitUntilComplete() or watch the `'processingStatus'` event for the idle
|
|
113
|
+
* transition to detect end-of-file. Returns `this` for chaining.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} videoPath
|
|
116
|
+
* @param {Object} [options]
|
|
117
|
+
* { timestampsPath?, interframeDelayMs?, startOffsetMs?, maxDurationMs?, frameTransform? }
|
|
118
|
+
*/
|
|
119
|
+
useFile(videoPath, options = null) {
|
|
120
|
+
if (typeof videoPath !== 'string' || !videoPath) {
|
|
121
|
+
throw new TypeError('useFile: videoPath must be a non-empty string');
|
|
122
|
+
}
|
|
123
|
+
this._source = { kind: 'file', videoPath, options };
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Initialize the session and start the configured input source. Returns
|
|
129
|
+
* once running — for custom input the graph is armed for sendFrame(); for
|
|
130
|
+
* camera/file the SDK pumps frames itself. Configure a source with
|
|
131
|
+
* useCustomInput() / useCamera() / useFile() first.
|
|
132
|
+
*/
|
|
133
|
+
start() {
|
|
134
|
+
const source = this._source;
|
|
135
|
+
if (!source) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'start(): no input source configured. Call useCustomInput(), ' +
|
|
138
|
+
'useCamera(), or useFile() first.');
|
|
139
|
+
}
|
|
140
|
+
this._ensureSession();
|
|
141
|
+
switch (source.kind) {
|
|
142
|
+
case 'custom':
|
|
143
|
+
this._session.startCustom(source.frameTransform);
|
|
144
|
+
break;
|
|
145
|
+
case 'camera':
|
|
146
|
+
this._session.startCamera(source.options);
|
|
147
|
+
break;
|
|
148
|
+
case 'file':
|
|
149
|
+
this._session.startFile(
|
|
150
|
+
source.videoPath,
|
|
151
|
+
(source.options && source.options.timestampsPath) || null,
|
|
152
|
+
source.options);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stop() {
|
|
158
|
+
if (!this._session) return;
|
|
159
|
+
this._session.stop();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Async variant of `stop()`. The underlying C++ stop blocks until the
|
|
164
|
+
* processing pipeline drains — can take multiple seconds. Prefer this in
|
|
165
|
+
* event-loop-sensitive contexts (e.g. the Electron main process serving
|
|
166
|
+
* IPC) so the runtime stays responsive.
|
|
167
|
+
*/
|
|
168
|
+
stopAsync() {
|
|
169
|
+
if (!this._session) return Promise.resolve();
|
|
170
|
+
return this._session.stopAsync();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Recover from a kError state. Rebuilds the internal graph. The input
|
|
175
|
+
* source is discarded — call useCustomInput() / useCamera() / useFile()
|
|
176
|
+
* AND start() again to drive new input.
|
|
177
|
+
*/
|
|
178
|
+
reset() {
|
|
179
|
+
if (!this._session) return;
|
|
180
|
+
this._session.reset();
|
|
181
|
+
this._source = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Block until the session reaches a terminal state (idle or error), or the
|
|
186
|
+
* timeout elapses. Returns true if the session settled, false on timeout.
|
|
187
|
+
* `timeoutMs <= 0` (the default) waits indefinitely. With no active session
|
|
188
|
+
* there is nothing to wait for, so it returns true immediately.
|
|
189
|
+
*
|
|
190
|
+
* Useful after useFile() + start() to block until end-of-file.
|
|
191
|
+
*/
|
|
192
|
+
waitUntilComplete(timeoutMs = 0) {
|
|
193
|
+
if (!this._session) return true;
|
|
194
|
+
return this._session.wait(timeoutMs);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Dispatch an on-demand insight prompt against the running session.
|
|
199
|
+
* Returns the request id; the matching Insight response is delivered
|
|
200
|
+
* asynchronously through the `'insight'` event with the same id.
|
|
201
|
+
*/
|
|
202
|
+
requestInsight(text) {
|
|
203
|
+
if (typeof text !== 'string') {
|
|
204
|
+
throw new TypeError('requestInsight: text must be a string');
|
|
205
|
+
}
|
|
206
|
+
this._ensureSession();
|
|
207
|
+
return this._session.requestInsight(text);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Current ProcessingStatus integer value (kUninitialized=0..kError=5). */
|
|
211
|
+
get processingStatus() {
|
|
212
|
+
return this._session ? this._session.getStatus() : 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Push a raw video frame. Requires useCustomInput() + start() first.
|
|
217
|
+
* Returns true on success; throws on input-validation or SDK-level
|
|
218
|
+
* failure.
|
|
219
|
+
*/
|
|
220
|
+
sendFrame(buffer, width, height, strideBytes, pixelFormat, timestampUs) {
|
|
221
|
+
if (!this._session || !this._source || this._source.kind !== 'custom') {
|
|
222
|
+
throw new Error('sendFrame(): call useCustomInput() + start() first');
|
|
223
|
+
}
|
|
224
|
+
return this._session.sendFrame(
|
|
225
|
+
buffer, width, height, strideBytes, pixelFormat, timestampUs);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Tear down the session. Idempotent. Always call on shutdown so the SDK
|
|
230
|
+
* worker threads join cleanly before the koffi trampolines are released.
|
|
231
|
+
* Returns a promise that resolves once native teardown completes (a
|
|
232
|
+
* stopAsync() in flight defers it); await it before standing up a
|
|
233
|
+
* replacement session, since SDK state is process-global.
|
|
234
|
+
*/
|
|
235
|
+
destroy() {
|
|
236
|
+
if (!this._session) return Promise.resolve();
|
|
237
|
+
const teardown = this._session.destroy();
|
|
238
|
+
this._session = null;
|
|
239
|
+
this._source = null;
|
|
240
|
+
return teardown;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ---------- internal ----------------------------------------------------
|
|
244
|
+
|
|
245
|
+
_ensureSession() {
|
|
246
|
+
if (this._session) return;
|
|
247
|
+
// Listeners may be added/replaced after start(), so the lookup
|
|
248
|
+
// happens at emit time rather than capture time.
|
|
249
|
+
this._session = new Session(this._options, {
|
|
250
|
+
onStatus: (status) => this._emit('processingStatus', status),
|
|
251
|
+
onValidation: (code, hint, ts) =>
|
|
252
|
+
this._emit('validationStatus', code, Number(ts), hint),
|
|
253
|
+
onMetrics: (buf, ts) => this._emit('metrics', buf, Number(ts)),
|
|
254
|
+
onError: (code, message, retryable) =>
|
|
255
|
+
this._emit('error', code, message, retryable),
|
|
256
|
+
onFrame: (sent, ts) =>
|
|
257
|
+
this._emit('frameSentThrough', sent, Number(ts)),
|
|
258
|
+
onAccumulatedMetrics: (buf, ts) =>
|
|
259
|
+
this._emit('accumulatedMetrics', buf, Number(ts)),
|
|
260
|
+
onInsight: (buf, requestId) =>
|
|
261
|
+
this._emit('insight', buf, requestId),
|
|
262
|
+
onVideoOutput: (buf, width, height, stride, pixelFormat, ts) =>
|
|
263
|
+
this._emit('videoOutput', buf, width, height, stride, pixelFormat, Number(ts)),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_emit(event, ...args) {
|
|
268
|
+
const cb = this._listeners.get(event);
|
|
269
|
+
if (!cb) return;
|
|
270
|
+
try {
|
|
271
|
+
cb(...args);
|
|
272
|
+
} catch (e) {
|
|
273
|
+
// Isolate listener exceptions so a user-code throw on a
|
|
274
|
+
// worker-thread callback doesn't tear down the SDK.
|
|
275
|
+
console.error(`SmartSpectraSDK: listener for '${event}' threw:`, e);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = {
|
|
281
|
+
SmartSpectraSDK,
|
|
282
|
+
ProcessingStatus,
|
|
283
|
+
PixelFormat,
|
|
284
|
+
ValidationCode,
|
|
285
|
+
SmartSpectraErrorCode,
|
|
286
|
+
FrameTransform,
|
|
287
|
+
breathingMetrics,
|
|
288
|
+
cardioMetrics,
|
|
289
|
+
faceMetrics,
|
|
290
|
+
micromotionMetrics,
|
|
291
|
+
edaMetrics,
|
|
292
|
+
defaultSupportedMetrics,
|
|
293
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@smartspectra/node-sdk",
|
|
3
|
+
"version": "3.2.0-rc.6",
|
|
4
|
+
"description": "Node.js (Electron) FFI binding for SmartSpectra vitals measurement",
|
|
5
|
+
"author": "Presage Technologies, Inc.",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"main": "js/index.js",
|
|
8
|
+
"types": "js/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./js/index.d.ts",
|
|
12
|
+
"default": "./js/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./main": {
|
|
15
|
+
"types": "./js/main/index.d.ts",
|
|
16
|
+
"default": "./js/main/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./preload": "./js/preload/index.js",
|
|
19
|
+
"./renderer": {
|
|
20
|
+
"types": "./js/renderer/index.d.ts",
|
|
21
|
+
"default": "./js/renderer/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./messages": {
|
|
24
|
+
"types": "./js/messages/index.d.ts",
|
|
25
|
+
"default": "./js/messages/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"js/**/*.js",
|
|
31
|
+
"js/**/*.d.ts",
|
|
32
|
+
"LICENSE",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"prepare": "node scripts/gen-proto.cjs",
|
|
37
|
+
"build:proto": "node scripts/gen-proto.cjs",
|
|
38
|
+
"test": "node test/smoke.js",
|
|
39
|
+
"test:models-allowlist": "node test/models-allowlist.js"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"smartspectra",
|
|
43
|
+
"vitals",
|
|
44
|
+
"physiology",
|
|
45
|
+
"electron",
|
|
46
|
+
"nodejs",
|
|
47
|
+
"ffi",
|
|
48
|
+
"koffi"
|
|
49
|
+
],
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/Presage-Security/SmartSpectra.git"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://smartspectra.presagetech.com/",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/Presage-Security/SmartSpectra/issues"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"registry": "https://registry.npmjs.org/"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"_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.",
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"koffi": "^2.10.0",
|
|
67
|
+
"protobufjs": "^7.5.0",
|
|
68
|
+
"@smartspectra/node-sdk-linux-x64": "3.2.0-rc.6",
|
|
69
|
+
"@smartspectra/node-sdk-linux-arm64": "3.2.0-rc.6",
|
|
70
|
+
"@smartspectra/node-sdk-darwin-arm64": "3.2.0-rc.6",
|
|
71
|
+
"@smartspectra/node-sdk-win32-x64": "3.2.0-rc.6"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/node": "^20.0.0",
|
|
75
|
+
"protobufjs-cli": "^1.1.3"
|
|
76
|
+
},
|
|
77
|
+
"_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.6.",
|
|
78
|
+
"overrides": {
|
|
79
|
+
"tmp": "0.2.6"
|
|
80
|
+
}
|
|
81
|
+
}
|