@camstack/system 1.1.50 → 1.1.51

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.
Files changed (51) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/hub-forwarder/index.js +1 -1
  16. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  17. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  18. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  19. package/dist/builtins/local-network/local-network.addon.js +1 -1
  20. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  21. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  22. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  23. package/dist/builtins/platform-probe/index.js +1 -1
  24. package/dist/builtins/platform-probe/index.mjs +1 -1
  25. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  26. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  27. package/dist/builtins/snapshot/index.js +162 -12
  28. package/dist/builtins/snapshot/index.mjs +162 -12
  29. package/dist/builtins/snapshot/snapshot-media-handler.d.ts +38 -0
  30. package/dist/builtins/snapshot/snapshot.addon.d.ts +26 -0
  31. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  32. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  33. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  34. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  35. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  36. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  37. package/dist/builtins/system-config/system-config.addon.js +1 -1
  38. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  39. package/dist/builtins/winston-logging/index.js +1 -1
  40. package/dist/builtins/winston-logging/index.mjs +1 -1
  41. package/dist/{dist-CC3E-_3h.mjs → dist-BWQX9yUj.mjs} +11 -3
  42. package/dist/{dist-CQYo6Zp8.js → dist-C3uHEBtP.js} +10 -2
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.js +34 -22
  45. package/dist/index.mjs +34 -23
  46. package/dist/kernel/addon-installer.d.ts +8 -0
  47. package/dist/kernel/deps/manifest-native-deps.d.ts +1 -1
  48. package/dist/kernel/deps/npm-command.d.ts +38 -0
  49. package/dist/{manifest-python-deps-DqATCsK0.mjs → manifest-python-deps-BA6KA9If.mjs} +268 -124
  50. package/dist/{manifest-python-deps-Wy8dNJpr.js → manifest-python-deps-CVcJ9hDX.js} +279 -123
  51. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Q as streamQualityLabel, Y as snapshotCapability, lt as DeviceType, nt as errMsg, rt as BaseAddon, st as DeviceFeature, z as nodePin } from "../../dist-CC3E-_3h.mjs";
1
+ import { Q as streamQualityLabel, Y as snapshotCapability, lt as DeviceType, nt as errMsg, rt as BaseAddon, st as DeviceFeature, z as nodePin } from "../../dist-BWQX9yUj.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot-coalescing.ts
4
4
  /**
@@ -195,12 +195,102 @@ function raceForResult(promise, timeoutMs) {
195
195
  });
196
196
  }
197
197
  //#endregion
198
+ //#region src/builtins/snapshot/snapshot-media-handler.ts
199
+ /**
200
+ * Parse the handler-relative path (`/<deviceId>.jpg?query`) into a request.
201
+ * Returns null for a malformed / nested / non-numeric id so the handler answers
202
+ * 404 without ever reaching `getMedia`.
203
+ */
204
+ function parseSnapshotMediaRequest(url) {
205
+ const qIdx = url.indexOf("?");
206
+ const rawPath = qIdx === -1 ? url : url.slice(0, qIdx);
207
+ const query = qIdx === -1 ? "" : url.slice(qIdx + 1);
208
+ const segment = rawPath.replace(/^\/+/, "");
209
+ if (segment.length === 0 || segment.includes("/")) return null;
210
+ const idPart = segment.replace(/\.jpe?g$/i, "");
211
+ if (!/^\d+$/.test(idPart)) return null;
212
+ const deviceId = Number.parseInt(idPart, 10);
213
+ if (!Number.isSafeInteger(deviceId)) return null;
214
+ const params = new URLSearchParams(query);
215
+ const rawStream = params.get("streamId");
216
+ const streamId = rawStream !== null && rawStream.length > 0 ? rawStream : void 0;
217
+ const rawForce = params.get("force");
218
+ return {
219
+ deviceId,
220
+ streamId,
221
+ force: rawForce === "1" || rawForce === "true"
222
+ };
223
+ }
224
+ /**
225
+ * Create a data-plane handler that serves per-device snapshots as JPEG images.
226
+ * `deps.getMedia` is called once per request; null → 404, throw → 500. A
227
+ * conditional GET with a matching `If-None-Match` produces a 304.
228
+ */
229
+ function createSnapshotMediaHandler(deps) {
230
+ return async (req, res) => {
231
+ if (req.method !== "GET" && req.method !== "HEAD") {
232
+ res.writeHead(405, { allow: "GET, HEAD" }).end();
233
+ return;
234
+ }
235
+ const parsed = parseSnapshotMediaRequest(req.url ?? "/");
236
+ if (parsed === null) {
237
+ res.writeHead(404).end();
238
+ return;
239
+ }
240
+ let media;
241
+ try {
242
+ media = await deps.getMedia(parsed.deviceId, parsed.streamId, parsed.force);
243
+ } catch {
244
+ const body = "Internal server error";
245
+ res.writeHead(500, {
246
+ "content-type": "text/plain",
247
+ "content-length": String(Buffer.byteLength(body))
248
+ });
249
+ if (req.method === "HEAD") res.end();
250
+ else res.end(body);
251
+ return;
252
+ }
253
+ if (media === null) {
254
+ res.writeHead(404).end();
255
+ return;
256
+ }
257
+ const etag = `"${parsed.deviceId}-${media.capturedAt}"`;
258
+ const cacheControl = `private, max-age=${Math.max(0, Math.floor(media.maxAgeS))}`;
259
+ if (req.headers["if-none-match"] === etag) {
260
+ res.writeHead(304, {
261
+ etag,
262
+ "cache-control": cacheControl
263
+ }).end();
264
+ return;
265
+ }
266
+ res.writeHead(200, {
267
+ "content-type": media.contentType,
268
+ "cache-control": cacheControl,
269
+ etag,
270
+ "content-length": String(media.bytes.byteLength)
271
+ });
272
+ if (req.method === "HEAD") res.end();
273
+ else res.end(Buffer.from(media.bytes));
274
+ };
275
+ }
276
+ //#endregion
198
277
  //#region src/builtins/snapshot/snapshot.addon.ts
199
278
  /** Default cache window for non-battery cams (seconds). 10s feels live. */
200
279
  var NON_BATTERY_DEFAULT_MAX_AGE_S = 10;
201
280
  /** Default cache window for battery cams (seconds). 1h ≈ "don't wake the cam unless asked". */
202
281
  var BATTERY_DEFAULT_MAX_AGE_S = 3600;
203
282
  /**
283
+ * Effective per-device max cache age (seconds): the operator override
284
+ * (`snapshotMaxAgeS`) when a finite non-negative number, else the battery-aware
285
+ * default. Shared by the freshness gate (`getSnapshot`) and the HTTP endpoint's
286
+ * `Cache-Control: max-age` (so the browser/`expo-image` layer caches for exactly
287
+ * as long as the wrapper considers the frame fresh).
288
+ */
289
+ function effectiveMaxAgeS(prefs, isBattery) {
290
+ const def = isBattery ? BATTERY_DEFAULT_MAX_AGE_S : NON_BATTERY_DEFAULT_MAX_AGE_S;
291
+ return typeof prefs.snapshotMaxAgeS === "number" && prefs.snapshotMaxAgeS >= 0 ? prefs.snapshotMaxAgeS : def;
292
+ }
293
+ /**
204
294
  * SnapshotAddon — wrapper over the `snapshot` capability.
205
295
  *
206
296
  * Activated per-device (toggleable by user; default active). When active,
@@ -240,25 +330,86 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
240
330
  */
241
331
  ownerCache = null;
242
332
  static OWNER_CACHE_TTL_MS = 3e4;
333
+ /**
334
+ * Handle for the `/addon/snapshot/media/<deviceId>.jpg` HTTP data-plane
335
+ * endpoint (§3.1). Null when the host provides no `ctx.dataPlane` facility
336
+ * (the tRPC `getSnapshot` base64 surface still works — the endpoint is a
337
+ * strictly-additive delivery path). Disposed on shutdown.
338
+ */
339
+ mediaDataPlane = null;
243
340
  constructor() {
244
341
  super({ staleTtlMs: 6e4 });
245
342
  }
246
343
  async onInitialize() {
247
344
  this.ctx.logger.info("Snapshot wrapper initialized");
345
+ const provider = {
346
+ getSnapshot: (input) => this.getSnapshot(input),
347
+ invalidateCache: (input) => this.invalidateCache(input),
348
+ getDeviceSettingsContribution: (input) => this.buildDeviceSettingsContribution(input.deviceId),
349
+ getDeviceLiveContribution: async () => null,
350
+ applyDeviceSettingsPatch: (input) => this.saveDeviceSettingsPatch(input.deviceId, input.patch),
351
+ getStatus: async (input) => this.getStatus(input.deviceId),
352
+ getSnapshotOverview: (input) => this.getSnapshotOverview(input)
353
+ };
354
+ await this.serveMediaDataPlane();
248
355
  return [{
249
356
  capability: snapshotCapability,
250
- provider: {
251
- getSnapshot: (input) => this.getSnapshot(input),
252
- invalidateCache: (input) => this.invalidateCache(input),
253
- getDeviceSettingsContribution: (input) => this.buildDeviceSettingsContribution(input.deviceId),
254
- getDeviceLiveContribution: async () => null,
255
- applyDeviceSettingsPatch: (input) => this.saveDeviceSettingsPatch(input.deviceId, input.patch),
256
- getStatus: async (input) => this.getStatus(input.deviceId),
257
- getSnapshotOverview: (input) => this.getSnapshotOverview(input)
258
- }
357
+ provider
259
358
  }];
260
359
  }
360
+ /**
361
+ * Serve the per-device snapshot image endpoint on the addon data-plane
362
+ * (§3.1). Delivery: `GET /addon/snapshot/media/<deviceId>.jpg` → raw JPEG with
363
+ * `ETag` + `Cache-Control: private, max-age=<device maxAgeS>`, so a grid is N
364
+ * independent `<Image>` tiles served from the coalesced server cache (no
365
+ * base64 tax, no batch head-of-line blocking, browser/`expo-image` caching for
366
+ * free). Best-effort: a host without a `dataPlane` facility keeps the tRPC
367
+ * base64 path.
368
+ */
369
+ async serveMediaDataPlane() {
370
+ const handler = createSnapshotMediaHandler({ getMedia: (deviceId, streamId, force) => this.resolveSnapshotMedia(deviceId, streamId, force) });
371
+ try {
372
+ this.mediaDataPlane = await this.ctx.dataPlane?.serve({
373
+ prefix: "media",
374
+ access: "authenticated",
375
+ handler
376
+ }) ?? null;
377
+ this.ctx.logger.info("snapshot media data-plane served", { meta: { baseUrl: this.mediaDataPlane ? `/addon/${this.ctx.id}/media` : "(no dataPlane facility)" } });
378
+ } catch (err) {
379
+ this.ctx.logger.warn("snapshot media data-plane failed to serve", { meta: { error: errMsg(err) } });
380
+ this.mediaDataPlane = null;
381
+ }
382
+ }
383
+ /**
384
+ * Resolve a device (+ optional stream, + force) to renderable JPEG bytes for
385
+ * the HTTP endpoint. Runs the same coalesced capture ladder as the tRPC
386
+ * `getSnapshot` (so every consumer shares one upstream pull), then decodes the
387
+ * base64 cache into raw bytes and stamps the frame identity: `capturedAt` from
388
+ * the cache entry (matching the ETag `getSnapshotOverview` advertises) and the
389
+ * effective per-device `maxAgeS` for `Cache-Control`. Null → 404.
390
+ */
391
+ async resolveSnapshotMedia(deviceId, streamId, force) {
392
+ const image = await this.getSnapshot({
393
+ deviceId,
394
+ ...streamId !== void 0 ? { streamId } : {},
395
+ force
396
+ });
397
+ if (!image) return null;
398
+ const capturedAt = this.cache.get(deviceId)?.ts ?? Date.now();
399
+ const prefs = await this.readDeviceSettings(deviceId).catch(() => ({}));
400
+ const isBattery = (await this.lookupDeviceMeta(deviceId))?.isBattery ?? false;
401
+ return {
402
+ bytes: Buffer.from(image.base64, "base64"),
403
+ contentType: image.contentType,
404
+ capturedAt,
405
+ maxAgeS: effectiveMaxAgeS(prefs, isBattery)
406
+ };
407
+ }
261
408
  async onShutdown() {
409
+ if (this.mediaDataPlane) {
410
+ await this.mediaDataPlane.dispose().catch(() => void 0);
411
+ this.mediaDataPlane = null;
412
+ }
262
413
  this.cache.clear();
263
414
  this.captureFlight.clear();
264
415
  this.ownerCache = null;
@@ -311,8 +462,7 @@ var SnapshotAddon = class SnapshotAddon extends BaseAddon {
311
462
  tags: { deviceId },
312
463
  meta: { stream: effectiveStreamId ?? "auto" }
313
464
  });
314
- const defaultMaxAgeS = isBatteryDevice ? BATTERY_DEFAULT_MAX_AGE_S : NON_BATTERY_DEFAULT_MAX_AGE_S;
315
- const effectiveMaxAgeMs = (typeof prefs.snapshotMaxAgeS === "number" && prefs.snapshotMaxAgeS >= 0 ? prefs.snapshotMaxAgeS : defaultMaxAgeS) * 1e3;
465
+ const effectiveMaxAgeMs = effectiveMaxAgeS(prefs, isBatteryDevice) * 1e3;
316
466
  const decision = decideSnapshotServe({
317
467
  now,
318
468
  cachedAt: hit?.ts ?? null,
@@ -0,0 +1,38 @@
1
+ import { DataPlaneHandler } from '@camstack/types';
2
+ /** A renderable snapshot frame resolved for one device. */
3
+ export interface SnapshotMedia {
4
+ /** Raw image bytes (already decoded from the wrapper's base64 cache). */
5
+ readonly bytes: Uint8Array;
6
+ /** MIME type — practically always `image/jpeg`. */
7
+ readonly contentType: string;
8
+ /** Epoch ms of the cached capture — drives the ETag and frame identity. */
9
+ readonly capturedAt: number;
10
+ /** Effective per-device max cache age (seconds) → `Cache-Control: max-age`. */
11
+ readonly maxAgeS: number;
12
+ }
13
+ export interface SnapshotMediaHandlerDeps {
14
+ /**
15
+ * Resolve a device (+ optional stream, + force) to a renderable frame. Runs
16
+ * the wrapper's coalesced capture ladder; returns null when no frame can be
17
+ * produced (→ 404). Throwing produces an opaque 500 (caller logs).
18
+ */
19
+ readonly getMedia: (deviceId: number, streamId: string | undefined, force: boolean) => Promise<SnapshotMedia | null>;
20
+ }
21
+ /** Parsed `/<deviceId>[.jpg][?streamId=…&force=1]` request. */
22
+ export interface SnapshotMediaRequest {
23
+ readonly deviceId: number;
24
+ readonly streamId: string | undefined;
25
+ readonly force: boolean;
26
+ }
27
+ /**
28
+ * Parse the handler-relative path (`/<deviceId>.jpg?query`) into a request.
29
+ * Returns null for a malformed / nested / non-numeric id so the handler answers
30
+ * 404 without ever reaching `getMedia`.
31
+ */
32
+ export declare function parseSnapshotMediaRequest(url: string): SnapshotMediaRequest | null;
33
+ /**
34
+ * Create a data-plane handler that serves per-device snapshots as JPEG images.
35
+ * `deps.getMedia` is called once per request; null → 404, throw → 500. A
36
+ * conditional GET with a matching `If-None-Match` produces a 304.
37
+ */
38
+ export declare function createSnapshotMediaHandler(deps: SnapshotMediaHandlerDeps): DataPlaneHandler;
@@ -50,8 +50,34 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
50
50
  */
51
51
  private ownerCache;
52
52
  private static readonly OWNER_CACHE_TTL_MS;
53
+ /**
54
+ * Handle for the `/addon/snapshot/media/<deviceId>.jpg` HTTP data-plane
55
+ * endpoint (§3.1). Null when the host provides no `ctx.dataPlane` facility
56
+ * (the tRPC `getSnapshot` base64 surface still works — the endpoint is a
57
+ * strictly-additive delivery path). Disposed on shutdown.
58
+ */
59
+ private mediaDataPlane;
53
60
  constructor();
54
61
  protected onInitialize(): Promise<ProviderRegistration[]>;
62
+ /**
63
+ * Serve the per-device snapshot image endpoint on the addon data-plane
64
+ * (§3.1). Delivery: `GET /addon/snapshot/media/<deviceId>.jpg` → raw JPEG with
65
+ * `ETag` + `Cache-Control: private, max-age=<device maxAgeS>`, so a grid is N
66
+ * independent `<Image>` tiles served from the coalesced server cache (no
67
+ * base64 tax, no batch head-of-line blocking, browser/`expo-image` caching for
68
+ * free). Best-effort: a host without a `dataPlane` facility keeps the tRPC
69
+ * base64 path.
70
+ */
71
+ private serveMediaDataPlane;
72
+ /**
73
+ * Resolve a device (+ optional stream, + force) to renderable JPEG bytes for
74
+ * the HTTP endpoint. Runs the same coalesced capture ladder as the tRPC
75
+ * `getSnapshot` (so every consumer shares one upstream pull), then decodes the
76
+ * base64 cache into raw bytes and stamps the frame identity: `capturedAt` from
77
+ * the cache entry (matching the ETag `getSnapshotOverview` advertises) and the
78
+ * effective per-device `maxAgeS` for `Cache-Control`. Null → 404.
79
+ */
80
+ private resolveSnapshotMedia;
55
81
  protected onShutdown(): Promise<void>;
56
82
  /**
57
83
  * Drop the cached ingest owner whenever addon-level settings change —
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CQYo6Zp8.js");
6
+ const require_dist = require("../../dist-C3uHEBtP.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { D as filesystemBrowseCapability, Z as storageProviderCapability, rt as BaseAddon } from "../../dist-CC3E-_3h.mjs";
1
+ import { D as filesystemBrowseCapability, Z as storageProviderCapability, rt as BaseAddon } from "../../dist-BWQX9yUj.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import { basename, dirname, join, resolve, sep } from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CQYo6Zp8.js");
6
+ const require_dist = require("../../dist-C3uHEBtP.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let better_sqlite3 = require("better-sqlite3");
9
9
  better_sqlite3 = require_chunk.__toESM(better_sqlite3);
@@ -1,4 +1,4 @@
1
- import { J as settingsStoreCapability, mt as asJsonObject, nt as errMsg, rt as BaseAddon, s as RUNTIME_DEFAULTS, wt as parseJsonUnknown } from "../../dist-CC3E-_3h.mjs";
1
+ import { Ct as parseJsonUnknown, J as settingsStoreCapability, nt as errMsg, pt as asJsonObject, rt as BaseAddon, s as RUNTIME_DEFAULTS } from "../../dist-BWQX9yUj.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import Database from "better-sqlite3";
4
4
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CQYo6Zp8.js");
6
+ const require_dist = require("../../dist-C3uHEBtP.js");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path);
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Ct as parseJsonObject, X as storageCapability, rt as BaseAddon, u as StorageLocationTypeSchema } from "../../dist-CC3E-_3h.mjs";
1
+ import { St as parseJsonObject, X as storageCapability, rt as BaseAddon, u as StorageLocationTypeSchema } from "../../dist-BWQX9yUj.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CQYo6Zp8.js");
6
+ const require_dist = require("../../dist-C3uHEBtP.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { nt as errMsg, rt as BaseAddon, xt as hydrateSchema } from "../../dist-CC3E-_3h.mjs";
1
+ import { bt as hydrateSchema, nt as errMsg, rt as BaseAddon } from "../../dist-BWQX9yUj.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CQYo6Zp8.js");
6
+ const require_dist = require("../../dist-C3uHEBtP.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { I as logDestinationCapability, rt as BaseAddon } from "../../dist-CC3E-_3h.mjs";
1
+ import { I as logDestinationCapability, rt as BaseAddon } from "../../dist-BWQX9yUj.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- //#region ../types/dist/sleep-_sv7WKkq.mjs
2
+ //#region ../types/dist/event-category-CFZs3jI4.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -17952,7 +17952,15 @@ var webrtcSessionCapability = {
17952
17952
  */
17953
17953
  disableIpv6: z.boolean().optional(),
17954
17954
  /** Subscriber attribution. See `createSession` for the contract. */
17955
- consumerAttribution: BrokerConsumerAttributionSchema.optional()
17955
+ consumerAttribution: BrokerConsumerAttributionSchema.optional(),
17956
+ /**
17957
+ * Client-side stream hints (viewport, downlink) — same contract as
17958
+ * `createSession.hints`. Client-offer viewers (the RN viewer's
17959
+ * `WebrtcLiveSession`) negotiate via `handleOffer`, so without this
17960
+ * field their hints were silently stripped by the schema and the
17961
+ * adaptive ladder started blind on the client's display size.
17962
+ */
17963
+ hints: webrtcClientHintsSchema.optional()
17956
17964
  }), z.object({
17957
17965
  sessionId: z.string(),
17958
17966
  sdpAnswer: z.string()
@@ -26318,4 +26326,4 @@ function scoreRuntimes(hw) {
26318
26326
  };
26319
26327
  }
26320
26328
  //#endregion
26321
- export { toExpressionValue as $, isCollectionArrayMethod as A, normalizeUnit as B, enumerateItemArrayFields as C, parseJsonObject as Ct, filesystemBrowseCapability as D, scopeKey as Dt, extractNestedAddonId as E, resolveCapMount as Et, localNetworkCapability as F, readNodePin as G, parseStreamParamsFormPatch as H, logDestinationCapability as I, settingsStoreCapability as J, scoreRuntimes as K, looseSchema as L, isVoidInput as M, kebabToCamel as N, getByPath as O, sleep as Ot, lifecycleJobSchema as P, streamQualityLabel as Q, metricsProviderCapability as R, deviceStatusCapability as S, isDeviceConfigCap as St, evaluateLinkExpression as T, readinessKey as Tt, platformProbeCapability as U, objectInputDeclaresAddonId as V, procedureAuthKey as W, storageCapability as X, snapshotCapability as Y, storageProviderCapability as Z, authProviderCapability as _, createEvent as _t, DeviceStatusSchema as a, DEVICE_SETTINGS_CONTRIBUTION_METHODS as at, deviceManagerCapability as b, expandCapMethods as bt, STREAM_PROFILE_META as c, DeviceRole as ct, UserRecordSchema as d, ReadinessRegistry as dt, userManagementCapability as et, addonPagesCapability as f, ReadinessTimeoutError as ft, applyTransform as g, asString as gt, alertsCapability as h, asNumber as ht, CAP_NAMES_WITH_STATUS as i, DATAPLANE_SECRET_HEADER as it, isObjectInput as j, isArrayOutputSchema as k, ScopedTokenSchema as l, DeviceType as lt, addonWidgetsCapability as m, asJsonObject as mt, AlertSchema as n, errMsg as nt, METHOD_ACCESS_MAP as o, DEVICE_STATUS_METHOD as ot, addonSettingsCapability as p, WELL_KNOWN_TAB_MAP as pt, setByPath as q, ApiKeyRecordSchema as r, BaseAddon as rt, RUNTIME_DEFAULTS as s, DeviceFeature as st, ALL_CAPABILITY_DEFINITIONS as t, validateExpressionSource as tt, StorageLocationTypeSchema as u, EventCategory as ut, backupCapability as v, emitDownForOwnedCaps as vt, enumerateSchemaFields as w, parseJsonUnknown as wt, deviceStateCapability as x, hydrateSchema as xt, buildStreamParamsConfigSchema as y, emitReadiness as yt, nodePin as z };
26329
+ export { toExpressionValue as $, isCollectionArrayMethod as A, normalizeUnit as B, enumerateItemArrayFields as C, parseJsonUnknown as Ct, filesystemBrowseCapability as D, sleep as Dt, extractNestedAddonId as E, scopeKey as Et, localNetworkCapability as F, readNodePin as G, parseStreamParamsFormPatch as H, logDestinationCapability as I, settingsStoreCapability as J, scoreRuntimes as K, looseSchema as L, isVoidInput as M, kebabToCamel as N, getByPath as O, EventCategory as Ot, lifecycleJobSchema as P, streamQualityLabel as Q, metricsProviderCapability as R, deviceStatusCapability as S, parseJsonObject as St, evaluateLinkExpression as T, resolveCapMount as Tt, platformProbeCapability as U, objectInputDeclaresAddonId as V, procedureAuthKey as W, storageCapability as X, snapshotCapability as Y, storageProviderCapability as Z, authProviderCapability as _, emitDownForOwnedCaps as _t, DeviceStatusSchema as a, DEVICE_SETTINGS_CONTRIBUTION_METHODS as at, deviceManagerCapability as b, hydrateSchema as bt, STREAM_PROFILE_META as c, DeviceRole as ct, UserRecordSchema as d, ReadinessTimeoutError as dt, userManagementCapability as et, addonPagesCapability as f, WELL_KNOWN_TAB_MAP as ft, applyTransform as g, createEvent as gt, alertsCapability as h, asString as ht, CAP_NAMES_WITH_STATUS as i, DATAPLANE_SECRET_HEADER as it, isObjectInput as j, isArrayOutputSchema as k, ScopedTokenSchema as l, DeviceType as lt, addonWidgetsCapability as m, asNumber as mt, AlertSchema as n, errMsg as nt, METHOD_ACCESS_MAP as o, DEVICE_STATUS_METHOD as ot, addonSettingsCapability as p, asJsonObject as pt, setByPath as q, ApiKeyRecordSchema as r, BaseAddon as rt, RUNTIME_DEFAULTS as s, DeviceFeature as st, ALL_CAPABILITY_DEFINITIONS as t, validateExpressionSource as tt, StorageLocationTypeSchema as u, ReadinessRegistry as ut, backupCapability as v, emitReadiness as vt, enumerateSchemaFields as w, readinessKey as wt, deviceStateCapability as x, isDeviceConfigCap as xt, buildStreamParamsConfigSchema as y, expandCapMethods as yt, nodePin as z };
@@ -1,5 +1,5 @@
1
1
  let zod = require("zod");
2
- //#region ../types/dist/sleep-_sv7WKkq.mjs
2
+ //#region ../types/dist/event-category-CFZs3jI4.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -17952,7 +17952,15 @@ var webrtcSessionCapability = {
17952
17952
  */
17953
17953
  disableIpv6: zod.z.boolean().optional(),
17954
17954
  /** Subscriber attribution. See `createSession` for the contract. */
17955
- consumerAttribution: BrokerConsumerAttributionSchema.optional()
17955
+ consumerAttribution: BrokerConsumerAttributionSchema.optional(),
17956
+ /**
17957
+ * Client-side stream hints (viewport, downlink) — same contract as
17958
+ * `createSession.hints`. Client-offer viewers (the RN viewer's
17959
+ * `WebrtcLiveSession`) negotiate via `handleOffer`, so without this
17960
+ * field their hints were silently stripped by the schema and the
17961
+ * adaptive ladder started blind on the client's display size.
17962
+ */
17963
+ hints: webrtcClientHintsSchema.optional()
17956
17964
  }), zod.z.object({
17957
17965
  sessionId: zod.z.string(),
17958
17966
  sdpAnswer: zod.z.string()
package/dist/index.d.ts CHANGED
@@ -10,6 +10,8 @@ export { proxyToUpstream } from './http/reverse-proxy.js';
10
10
  export type { ReverseProxyOptions } from './http/reverse-proxy.js';
11
11
  export { createFileDataPlaneHandler } from './http/file-data-plane.js';
12
12
  export type { FileDataPlaneOptions } from './http/file-data-plane.js';
13
+ export { createAddonDataPlaneFacility } from './kernel/moleculer/addon-data-plane-facility.js';
14
+ export type { AddonDataPlaneFacility, DataPlaneSink, } from './kernel/moleculer/addon-data-plane-facility.js';
13
15
  export { downloadModel, downloadFile, fetchJson, ensureModel, getModelFilePath, isModelDownloaded, deleteModelFromDisk, collectModelFiles, } from './download/model-downloader.js';
14
16
  export type { DownloadProgressCallback } from './download/model-downloader.js';
15
17
  export { ModelDownloadService } from './download/model-download-service.js';
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-CQYo6Zp8.js");
3
+ const require_dist = require("./dist-C3uHEBtP.js");
4
4
  const require_model_download_service = require("./model-download-service-D-Umz4-L.js");
5
+ const require_manifest_python_deps = require("./manifest-python-deps-CVcJ9hDX.js");
5
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
6
7
  const require_builtins_sqlite_storage_filesystem_storage_addon = require("./builtins/sqlite-storage/filesystem-storage.addon.js");
7
8
  const require_builtins_sqlite_storage_sqlite_settings_addon = require("./builtins/sqlite-storage/sqlite-settings.addon.js");
@@ -21,7 +22,6 @@ const require_builtins_local_auth_local_auth_addon = require("./builtins/local-a
21
22
  require("./builtins/local-auth/index.js");
22
23
  const require_builtins_device_manager_device_manager_addon = require("./builtins/device-manager/device-manager.addon.js");
23
24
  require("./builtins/device-manager/index.js");
24
- const require_manifest_python_deps = require("./manifest-python-deps-Wy8dNJpr.js");
25
25
  const require_custom_action_registry = require("./custom-action-registry-vLYEFTtv.js");
26
26
  let _camstack_types_node = require("@camstack/types/node");
27
27
  let zod = require("zod");
@@ -32,6 +32,7 @@ node_fs = require_chunk.__toESM(node_fs);
32
32
  let node_path = require("node:path");
33
33
  node_path = require_chunk.__toESM(node_path, 1);
34
34
  let node_crypto = require("node:crypto");
35
+ let _camstack_types_addon = require("@camstack/types/addon");
35
36
  let node_child_process = require("node:child_process");
36
37
  let node_util = require("node:util");
37
38
  node_util = require_chunk.__toESM(node_util);
@@ -40,7 +41,6 @@ node_vm = require_chunk.__toESM(node_vm);
40
41
  let node_os = require("node:os");
41
42
  node_os = require_chunk.__toESM(node_os);
42
43
  let node_fs_promises = require("node:fs/promises");
43
- let _camstack_types_addon = require("@camstack/types/addon");
44
44
  let node_events = require("node:events");
45
45
  let node_stream = require("node:stream");
46
46
  node_stream = require_chunk.__toESM(node_stream, 1);
@@ -3064,6 +3064,24 @@ var AddonInstaller = class AddonInstaller {
3064
3064
  else this.installSource = "npm";
3065
3065
  }
3066
3066
  /**
3067
+ * Where a bootstrapped npm lives when the host has none on PATH (packaged
3068
+ * Electron agents ship a bare node binary). Persistent — sibling of the
3069
+ * installed addons, survives restarts, downloaded at most once per host.
3070
+ */
3071
+ get npmCacheDir() {
3072
+ return node_path.join(this.addonsDir, ".npm-bootstrap");
3073
+ }
3074
+ /** Shared options for every npm invocation this installer performs. */
3075
+ npmRunOptions(cwd, timeout) {
3076
+ return {
3077
+ cacheDir: this.npmCacheDir,
3078
+ registry: this.registry,
3079
+ logger: this.logger,
3080
+ cwd,
3081
+ timeout
3082
+ };
3083
+ }
3084
+ /**
3067
3085
  * Derive the bootstrap addon list from a meta-package's runtime dependencies.
3068
3086
  * Single source of truth: the meta-package's `package.json` declares which
3069
3087
  * `@camstack/addon-*` packages the deployment needs. Replaces previously
@@ -3247,17 +3265,16 @@ var AddonInstaller = class AddonInstaller {
3247
3265
  });
3248
3266
  const strippedDeps = stripBundledDeps(pkgData);
3249
3267
  if (strippedDeps["dependencies"] && typeof strippedDeps["dependencies"] === "object" && Object.keys(strippedDeps["dependencies"]).length > 0) try {
3250
- await execFileAsync("npm", [
3268
+ await require_manifest_python_deps.runNpm([
3251
3269
  "install",
3252
3270
  "--omit=dev",
3253
3271
  "--ignore-scripts=false"
3254
- ], {
3255
- cwd: targetDir,
3256
- timeout: 12e4
3257
- });
3258
- } catch {}
3272
+ ], this.npmRunOptions(targetDir, 12e4));
3273
+ } catch (err) {
3274
+ this.logger.warn(`${packageName} — npm install failed (continuing)`, { meta: { error: require_dist.errMsg(err) } });
3275
+ }
3259
3276
  try {
3260
- await require_manifest_python_deps.installManifestNativeDeps(targetDir, pkgData, this.logger, this.registry);
3277
+ await require_manifest_python_deps.installManifestNativeDeps(targetDir, pkgData, this.logger, this.registry, this.npmCacheDir);
3261
3278
  } catch (err) {
3262
3279
  this.logger.warn(`${packageName} — native deps install failed (continuing)`, { meta: { error: require_dist.errMsg(err) } });
3263
3280
  }
@@ -3288,7 +3305,7 @@ var AddonInstaller = class AddonInstaller {
3288
3305
  tmpDir
3289
3306
  ];
3290
3307
  if (this.registry) args.push("--registry", this.registry);
3291
- const { stdout } = await execFileAsync("npm", args, { timeout: 12e4 });
3308
+ const { stdout } = await require_manifest_python_deps.runNpm(args, this.npmRunOptions(tmpDir, 12e4));
3292
3309
  const tgzFiles = node_fs.readdirSync(tmpDir).filter((f) => f.endsWith(".tgz"));
3293
3310
  if (tgzFiles.length === 0) throw new Error(`npm pack produced no tgz. stdout: ${stdout.trim()}`);
3294
3311
  return node_path.join(tmpDir, tgzFiles[0]);
@@ -3429,7 +3446,7 @@ var AddonInstaller = class AddonInstaller {
3429
3446
  if (strippedRuntimeDeps != null && typeof strippedRuntimeDeps === "object" && Object.keys(strippedRuntimeDeps).length > 0) {
3430
3447
  this.logger.info(`${pkgView.name} — installing runtime dependencies`, { meta: { stagingDir: pkgDir } });
3431
3448
  try {
3432
- await execFileAsync("npm", [
3449
+ await require_manifest_python_deps.runNpm([
3433
3450
  "install",
3434
3451
  "--omit=dev",
3435
3452
  "--omit=peer",
@@ -3437,16 +3454,13 @@ var AddonInstaller = class AddonInstaller {
3437
3454
  "--no-fund",
3438
3455
  "--no-package-lock",
3439
3456
  ...this.registry ? ["--registry", this.registry] : []
3440
- ], {
3441
- cwd: pkgDir,
3442
- timeout: 24e4
3443
- });
3457
+ ], this.npmRunOptions(pkgDir, 24e4));
3444
3458
  } catch (err) {
3445
- this.logger.warn(`${pkgView.name} — npm install failed (continuing)`, { meta: { error: err instanceof Error ? err.message : String(err) } });
3459
+ throw new Error(`${pkgView.name} — runtime dependency install failed: ${require_dist.errMsg(err)}`, { cause: err });
3446
3460
  }
3447
3461
  }
3448
3462
  try {
3449
- await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry);
3463
+ await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
3450
3464
  } catch (nativeErr) {
3451
3465
  throw new Error(`${pkgView.name} — native deps install failed: ${require_dist.errMsg(nativeErr)}`, { cause: nativeErr });
3452
3466
  }
@@ -3748,10 +3762,7 @@ var AddonInstaller = class AddonInstaller {
3748
3762
  if (!(!node_fs.existsSync(distDir) || this.isDistIncomplete(pkgData, sourceDir))) return;
3749
3763
  this.logger.info(`${packageName} — building (dist/ missing or incomplete)`);
3750
3764
  try {
3751
- await execFileAsync("npm", ["run", "build"], {
3752
- cwd: sourceDir,
3753
- timeout: 18e4
3754
- });
3765
+ await require_manifest_python_deps.runNpm(["run", "build"], this.npmRunOptions(sourceDir, 18e4));
3755
3766
  } catch (err) {
3756
3767
  const msg = require_dist.errMsg(err);
3757
3768
  this.logger.warn(`${packageName} auto-build failed`, { meta: { error: msg } });
@@ -93515,6 +93526,7 @@ exports.contentTypeFor = require_model_download_service.contentTypeFor;
93515
93526
  exports.copyDirRecursive = copyDirRecursive;
93516
93527
  exports.copyExtraFileDirs = copyExtraFileDirs;
93517
93528
  exports.createAddonContext = require_manifest_python_deps.createAddonContext;
93529
+ exports.createAddonDataPlaneFacility = require_manifest_python_deps.createAddonDataPlaneFacility;
93518
93530
  exports.createAddonService = require_manifest_python_deps.createAddonService;
93519
93531
  exports.createAuthenticatedFileServer = require_model_download_service.createAuthenticatedFileServer;
93520
93532
  exports.createBroker = createBroker;