@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.
@@ -0,0 +1,27 @@
1
+ // js/messages/index.d.ts
2
+ // Copyright (C) 2026 Presage Technologies, Inc.
3
+ //
4
+ // SPDX-License-Identifier: LicenseRef-Proprietary
5
+ //
6
+ // TypeScript declarations for `@smartspectra/node-sdk/messages`. The
7
+ // generated namespace tree (with every nested type — Breathing, Cardio,
8
+ // Face, Measurement, etc.) lives in `./generated.d.ts`, emitted by pbts
9
+ // alongside `generated.js`. This file is the hand-written facade that
10
+ // names the public exports.
11
+
12
+ import { presage as generatedPresage } from './generated';
13
+
14
+ /** The full `presage.*` namespace as generated by pbjs / pbts. */
15
+ export const presage: typeof generatedPresage;
16
+
17
+ /** Convenience alias for `presage.smartspectra.Metrics`. */
18
+ export const Metrics: typeof generatedPresage.smartspectra.Metrics;
19
+
20
+ /**
21
+ * Decode a Metrics protobuf buffer emitted by the SDK's `metrics` or
22
+ * `accumulatedMetrics` events. Accepts Node's `Buffer`, browser-side
23
+ * `Uint8Array`, or raw `ArrayBuffer`.
24
+ */
25
+ export function decodeMetrics(
26
+ buf: Uint8Array | ArrayBuffer,
27
+ ): generatedPresage.smartspectra.Metrics;
@@ -0,0 +1,67 @@
1
+ // js/messages/index.js
2
+ // Copyright (C) 2026 Presage Technologies, Inc.
3
+ //
4
+ // SPDX-License-Identifier: LicenseRef-Proprietary
5
+ //
6
+ // Public protobuf surface for `@smartspectra/node-sdk`. Ships the
7
+ // generated message classes so host apps don't have to compile their
8
+ // own protobuf module against the SDK's `.proto` files.
9
+ //
10
+ // Usage:
11
+ // const { Metrics, decodeMetrics, presage } = require('@smartspectra/node-sdk/messages');
12
+ // const m = decodeMetrics(buf); // buf: Buffer | Uint8Array | ArrayBuffer
13
+ // console.log(m.cardio?.pulseRate);
14
+ // // Nested types are reachable via the namespace too:
15
+ // const breathing = presage.smartspectra.Breathing;
16
+ //
17
+ // This module purposely avoids `require('../smartspectra')` /
18
+ // `require('../ffi')` so it stays pulled-in cleanly by browser-side
19
+ // bundlers (e.g. esbuild for an Electron renderer). The koffi loader and
20
+ // the C ABI shim live under the package's main entry only.
21
+
22
+ 'use strict';
23
+
24
+ const $root = require('./generated');
25
+
26
+ // pbjs --wrap commonjs emits `module.exports = $root`, where `$root` is
27
+ // the populated `protobuf.roots["default"]` namespace. Pull the `presage`
28
+ // branch off it so we can re-export the full tree without forcing callers
29
+ // to traverse `$root.presage.smartspectra…`.
30
+ const { presage } = $root;
31
+ const Metrics = presage.smartspectra.Metrics;
32
+
33
+ /**
34
+ * Coerce a buffer-shaped value into the `Uint8Array` that
35
+ * `Metrics.decode()` expects. Accepts the three shapes consumers
36
+ * realistically hand us: a Node.js `Buffer` (main process), a plain
37
+ * `Uint8Array` (renderer, from the IPC bridge's `ArrayBuffer` payload),
38
+ * or a bare `ArrayBuffer` (raw transferable, no view).
39
+ *
40
+ * Buffers are already Uint8Array subclasses so they pass through
41
+ * untouched; the Uint8Array branch also no-ops; the ArrayBuffer branch
42
+ * wraps without copying.
43
+ */
44
+ function toUint8(buf) {
45
+ if (buf instanceof Uint8Array) return buf;
46
+ if (buf instanceof ArrayBuffer) return new Uint8Array(buf);
47
+ throw new TypeError(
48
+ '@smartspectra/node-sdk/messages: decodeMetrics expected ' +
49
+ 'Buffer | Uint8Array | ArrayBuffer, got ' + Object.prototype.toString.call(buf));
50
+ }
51
+
52
+ /**
53
+ * Decode a Metrics protobuf buffer emitted by the SDK's `metrics` or
54
+ * `accumulatedMetrics` events. Returns the decoded message object with
55
+ * the full namespace shape (e.g. `out.cardio.pulseRate[i].timestamp`).
56
+ *
57
+ * Throws on malformed input (re-raised from protobufjs).
58
+ */
59
+ function decodeMetrics(buf) {
60
+ return Metrics.decode(toUint8(buf));
61
+ }
62
+
63
+ module.exports = {
64
+ Metrics,
65
+ decodeMetrics,
66
+ presage,
67
+ };
@@ -0,0 +1,116 @@
1
+ // js/preload/index.js
2
+ // Copyright (C) 2026 Presage Technologies, Inc.
3
+ //
4
+ // SPDX-License-Identifier: LicenseRef-Proprietary
5
+ //
6
+ // Preload bridge for `@smartspectra/node-sdk/renderer`. Run inside the
7
+ // preload script of any BrowserWindow that hosts a renderer using the SDK.
8
+ //
9
+ // Apps that already have a preload script can require this file from there;
10
+ // apps without a preload script can point `webPreferences.preload` directly
11
+ // at this file via `require.resolve('@smartspectra/node-sdk/preload')`.
12
+ //
13
+ // The bridge owns the MessagePort in the preload context and exposes
14
+ // `attach()` / `postMessage()` / `onMessage()` / `close()` to the renderer.
15
+ // Keeping the port out of the renderer's main world avoids edge cases in
16
+ // cross-context MessagePort proxying through contextBridge.
17
+
18
+ 'use strict';
19
+
20
+ const { contextBridge, ipcRenderer } = require('electron');
21
+
22
+ const ATTACH_CHANNEL = 'smartspectra:attach';
23
+
24
+ // Preload-context state. One port per preload instance is enough — the
25
+ // renderer-side SmartSpectraSDK is a singleton-per-BrowserWindow in
26
+ // practice.
27
+ let port = null;
28
+ let messageListener = null;
29
+
30
+ // Diagnostic logging — opt-in via the same env var the main-process glue
31
+ // uses. Logs go to the renderer DevTools console (preload shares the
32
+ // renderer's console). Frame messages are logged only on first send and
33
+ // every 60 sends after that, to avoid swamping the console.
34
+ const DIAGNOSTICS = process.env.SMARTSPECTRA_DIAGNOSTICS === '1';
35
+ let postsByKind = Object.create(null);
36
+ function diagLogSend(kind) {
37
+ if (!DIAGNOSTICS) return;
38
+ postsByKind[kind] = (postsByKind[kind] || 0) + 1;
39
+ const count = postsByKind[kind];
40
+ if (kind === 'frame') {
41
+ if (count === 1 || count % 60 === 0) {
42
+ console.log(`[smartspectra/preload] -> frame #${count}`);
43
+ }
44
+ } else {
45
+ console.log(`[smartspectra/preload] -> ${kind}`);
46
+ }
47
+ }
48
+
49
+ const onPortMessage = (event) => {
50
+ if (DIAGNOSTICS) {
51
+ const kind = event.data && event.data.kind;
52
+ console.log(`[smartspectra/preload] <- ${kind || '?'}`);
53
+ }
54
+ if (messageListener) messageListener(event.data);
55
+ };
56
+
57
+ contextBridge.exposeInMainWorld('__smartspectraBridge', {
58
+ /**
59
+ * Mirror of SMARTSPECTRA_DIAGNOSTICS for the renderer module, which is
60
+ * context-isolated and cannot read process.env itself.
61
+ */
62
+ diagnostics: DIAGNOSTICS,
63
+
64
+ /**
65
+ * Open the IPC channel to the main process. Creates a MessageChannel
66
+ * (port pair) in the preload context, ships port2 to main via
67
+ * ipcRenderer.postMessage (which natively transfers MessagePorts), and
68
+ * retains port1 here.
69
+ */
70
+ attach() {
71
+ if (port) return; // idempotent — second attach() is a no-op
72
+ const channel = new MessageChannel();
73
+ port = channel.port1;
74
+ port.onmessage = onPortMessage;
75
+ ipcRenderer.postMessage(ATTACH_CHANNEL, null, [channel.port2]);
76
+ port.start();
77
+ },
78
+
79
+ /**
80
+ * Forward a message to the main process via structured clone. Electron's
81
+ * MessagePortMain silently drops messages whose transfer list contains an
82
+ * ArrayBuffer — `port.postMessage(msg, [ab])` succeeds on the renderer
83
+ * side but never fires `port.on('message')` on the main side — so we
84
+ * always clone. The clone costs one ~3.6 MB copy per 720p frame, which is
85
+ * negligible at 30 fps.
86
+ */
87
+ postMessage(message) {
88
+ if (!port) throw new Error('@smartspectra/node-sdk/preload: postMessage before attach()');
89
+ try {
90
+ port.postMessage(message);
91
+ } catch (err) {
92
+ // Surface the failure to DevTools so it doesn't disappear into
93
+ // contextBridge's promise wrapper.
94
+ console.error(
95
+ `[smartspectra/preload] port.postMessage failed for ${message && message.kind}:`,
96
+ err);
97
+ throw err;
98
+ }
99
+ diagLogSend(message && message.kind);
100
+ },
101
+
102
+ /**
103
+ * Register the callback that receives main-process messages. Replacing
104
+ * an existing callback is supported (only the most recent registration
105
+ * is invoked).
106
+ */
107
+ onMessage(callback) {
108
+ messageListener = typeof callback === 'function' ? callback : null;
109
+ },
110
+
111
+ /** Close the port and clear the listener. Idempotent. */
112
+ close() {
113
+ if (port) { try { port.close(); } catch { /* already closed */ } port = null; }
114
+ messageListener = null;
115
+ },
116
+ });
@@ -0,0 +1,154 @@
1
+ // js/renderer/index.d.ts
2
+ // Copyright (C) 2026 Presage Technologies, Inc.
3
+ //
4
+ // SPDX-License-Identifier: LicenseRef-Proprietary
5
+
6
+ import type {
7
+ ProcessingStatusValue,
8
+ ValidationCodeValue,
9
+ SmartSpectraErrorCodeValue,
10
+ SmartSpectraOptions,
11
+ } from '../../js/index';
12
+
13
+ // Re-export the public value constants and matching type aliases so the
14
+ // renderer-side consumer can import everything from one entry point.
15
+ export {
16
+ ProcessingStatus,
17
+ PixelFormat,
18
+ ValidationCode,
19
+ SmartSpectraErrorCode,
20
+ FrameTransform,
21
+ breathingMetrics,
22
+ cardioMetrics,
23
+ faceMetrics,
24
+ micromotionMetrics,
25
+ edaMetrics,
26
+ defaultSupportedMetrics,
27
+ } from '../../js/index';
28
+ export type {
29
+ ProcessingStatusValue,
30
+ PixelFormatValue,
31
+ ValidationCodeValue,
32
+ SmartSpectraErrorCodeValue,
33
+ FrameTransformValue,
34
+ SmartSpectraOptions,
35
+ } from '../../js/index';
36
+
37
+ /**
38
+ * Renderer constructor options: the shared SDK options plus a renderer-only
39
+ * IPC ack timeout. (`sendTimeoutMs` has no meaning for the main-process SDK,
40
+ * which doesn't go through the preload bridge.)
41
+ */
42
+ export interface RendererSmartSpectraOptions extends SmartSpectraOptions {
43
+ /**
44
+ * Per-call timeout (ms) for renderer→main IPC roundtrips
45
+ * (start/stop/reset/requestInsight/configure). If the main process
46
+ * crashes or stalls, the pending call rejects with an `Error` whose
47
+ * `code` is `'SMARTSPECTRA_IPC_TIMEOUT'` instead of hanging the UI
48
+ * forever. Defaults to 30000; set to 0 to disable.
49
+ */
50
+ sendTimeoutMs?: number;
51
+ }
52
+
53
+ /**
54
+ * Renderer-side `SmartSpectraSDK` for Electron applications. By default
55
+ * the SDK acquires the host's front-facing camera at 1280x720 / 30 fps,
56
+ * locks AE/AWB/focus once the signal pipeline reports Running, and emits
57
+ * the active `MediaStream` through `'streamAvailable'` so the host can
58
+ * show a live preview. Host apps that need finer control (custom
59
+ * resolution, virtual cameras, `<canvas>.captureStream()`, etc.) call
60
+ * `useMediaStream()` before `start()` to override the default.
61
+ *
62
+ * Requires the preload script to expose the bridge — set
63
+ * `webPreferences.preload = require.resolve('@smartspectra/node-sdk/preload')`
64
+ * on the BrowserWindow, and call
65
+ * `bindSmartSpectraIpc(window)` from the main process.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import { SmartSpectraSDK } from '@smartspectra/node-sdk/renderer';
70
+ * import { breathingMetrics, cardioMetrics } from '@smartspectra/node-sdk';
71
+ *
72
+ * const sdk = new SmartSpectraSDK({
73
+ * apiKey: 'YOUR_KEY',
74
+ * requestedMetrics: [...breathingMetrics, ...cardioMetrics],
75
+ * });
76
+ *
77
+ * sdk.on('streamAvailable', (stream) => {
78
+ * document.querySelector('video').srcObject = stream;
79
+ * });
80
+ * sdk.on('metrics', (buf, ts) => { ... });
81
+ *
82
+ * await sdk.start(); // SDK acquires the camera + emits 'streamAvailable'
83
+ * ```
84
+ */
85
+ export declare class SmartSpectraSDK {
86
+ /** SDK package version. */
87
+ static readonly version: string;
88
+
89
+ constructor(options?: RendererSmartSpectraOptions);
90
+
91
+ /** Current ProcessingStatus integer value (read-only). */
92
+ readonly processingStatus: ProcessingStatusValue;
93
+
94
+ /**
95
+ * Override the default camera with a host-supplied `MediaStream` from
96
+ * `navigator.mediaDevices.getUserMedia`, `desktopCapturer`, a
97
+ * `<canvas>.captureStream()`, etc. Optional — if not called, `start()`
98
+ * acquires its own MediaStream with sensible defaults and emits it via
99
+ * the `'streamAvailable'` event. When the host supplies the stream
100
+ * the host also owns its lifecycle; the SDK never stops the original
101
+ * tracks.
102
+ */
103
+ useMediaStream(stream: MediaStream): this;
104
+
105
+ /**
106
+ * Begin processing. If no stream was provided via `useMediaStream()`,
107
+ * the SDK acquires the default front-facing camera (1280x720 / 30 fps)
108
+ * and emits it through `'streamAvailable'` so the host can attach a
109
+ * preview. Resolves once the main-process SDK is running.
110
+ */
111
+ start(): Promise<void>;
112
+
113
+ /**
114
+ * Stop processing. Resolves once the main-process SDK reports idle.
115
+ * Releases any SDK-acquired camera; host-supplied streams are left
116
+ * untouched.
117
+ */
118
+ stop(): Promise<void>;
119
+
120
+ /**
121
+ * Rebuild the processing pipeline after a kError state. Releases any
122
+ * SDK-acquired camera so the next `start()` re-acquires from scratch.
123
+ */
124
+ reset(): Promise<void>;
125
+
126
+ /**
127
+ * Dispatch an on-demand insight prompt. Resolves with the request id
128
+ * assigned to it; the matching response arrives via the `'insight'`
129
+ * event with the same id.
130
+ */
131
+ requestInsight(text: string): Promise<number>;
132
+
133
+ /**
134
+ * Tear down renderer-side resources and instruct the main process to
135
+ * destroy its SDK instance. Idempotent.
136
+ */
137
+ destroy(): void;
138
+
139
+ on(event: 'processingStatus', callback: (status: ProcessingStatusValue) => void): this;
140
+ on(event: 'validationStatus', callback: (code: ValidationCodeValue, timestampUs: number, hint: string) => void): this;
141
+ on(event: 'metrics', callback: (buf: Uint8Array, timestampUs: number) => void): this;
142
+ on(event: 'accumulatedMetrics', callback: (buf: Uint8Array, timestampUs: number) => void): this;
143
+ on(event: 'insight', callback: (buf: Uint8Array, requestId: number) => void): this;
144
+ on(event: 'frameSentThrough', callback: (sent: boolean, timestampUs: number) => void): this;
145
+ on(event: 'error', callback: (code: SmartSpectraErrorCodeValue, message: string, retryable: boolean) => void): this;
146
+ /**
147
+ * Fires once the SDK has acquired (or re-acquired, after a Stop) the
148
+ * default `MediaStream`. Host apps attach this stream to their
149
+ * preview `<video>` element. Not fired when the host supplies its
150
+ * own stream via `useMediaStream()` — the host already owns that
151
+ * reference.
152
+ */
153
+ on(event: 'streamAvailable', callback: (stream: MediaStream) => void): this;
154
+ }