@camstack/system 1.2.231 → 1.2.233

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 (62) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.d.ts +1 -0
  4. package/dist/addon-utils.js +6 -1
  5. package/dist/addon-utils.mjs +3 -2
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  7. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  9. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  10. package/dist/builtins/alerts/alerts.addon.js +1 -1
  11. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +17 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +17 -1
  14. package/dist/builtins/console-logging/index.js +1 -1
  15. package/dist/builtins/console-logging/index.mjs +1 -1
  16. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  17. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  18. package/dist/builtins/device-manager/device-config-contribution.d.ts +1 -0
  19. package/dist/builtins/device-manager/device-manager.addon.js +46 -3
  20. package/dist/builtins/device-manager/device-manager.addon.mjs +46 -3
  21. package/dist/builtins/device-manager/native-object-detection-config-schema.d.ts +3 -0
  22. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  23. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  24. package/dist/builtins/hub-forwarder/index.js +1 -1
  25. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  26. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  27. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  28. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  29. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  30. package/dist/builtins/local-network/local-network.addon.js +1 -1
  31. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  32. package/dist/builtins/loki-logging/index.js +1 -1
  33. package/dist/builtins/loki-logging/index.mjs +1 -1
  34. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  35. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  36. package/dist/builtins/platform-probe/index.js +1 -1
  37. package/dist/builtins/platform-probe/index.mjs +1 -1
  38. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  39. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  40. package/dist/builtins/snapshot/index.js +5 -140
  41. package/dist/builtins/snapshot/index.mjs +2 -137
  42. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  43. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  44. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
  45. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
  46. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  47. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  48. package/dist/builtins/system-config/system-config.addon.js +1 -1
  49. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  50. package/dist/builtins/winston-logging/index.js +1 -1
  51. package/dist/builtins/winston-logging/index.mjs +1 -1
  52. package/dist/{custom-action-registry-CM03Xda0.mjs → custom-action-registry-CFHYDHAu.mjs} +1 -1
  53. package/dist/{custom-action-registry-DAFhWd0h.js → custom-action-registry-CWFK5UDj.js} +1 -1
  54. package/dist/{dist-Zhe9cTnK.mjs → dist-BIvXRexm.mjs} +291 -18
  55. package/dist/{dist-DUSfUAgz.js → dist-D3lJV9M7.js} +291 -18
  56. package/dist/index.js +2 -2
  57. package/dist/index.mjs +2 -2
  58. package/dist/{retired-settings-keys-BK_geSOq.mjs → retired-settings-keys-CV3ZLGnJ.mjs} +1 -1
  59. package/dist/{retired-settings-keys-D61PxN8L.js → retired-settings-keys-YPsd2Exa.js} +1 -1
  60. package/dist/snapshot-courtesy-LoJNgHHZ.mjs +140 -0
  61. package/dist/snapshot-courtesy-oeTWCXPv.js +165 -0
  62. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ const require_chunk = require("./chunk-Cek0wNdY.js");
2
+ let sharp = require("sharp");
3
+ sharp = require_chunk.__toESM(sharp);
4
+ //#region src/builtins/snapshot/snapshot-courtesy.ts
5
+ /**
6
+ * State treatment for a camera that cannot produce a current frame.
7
+ *
8
+ * ── Why this exists ───────────────────────────────────────────────────────
9
+ * A disabled, offline or sleeping camera has no frame, and until 2026-08-11 the
10
+ * snapshot service simply had nothing to say about it: the media route answered
11
+ * **404** and the client painted "Unavailable". Measured on the live grid that
12
+ * day, 10 of 26 tiles were 404s — one genuinely disabled camera plus nine dead
13
+ * legacy rows. A 404 is indistinguishable from a broken camera, so an operator
14
+ * who deliberately switched a camera off saw the same thing as a fault ([D62]:
15
+ * "an off switch is REPORTED off; disabled must never look like broken").
16
+ *
17
+ * So the service answers with an image that SAYS what is going on, carrying
18
+ * the camera's own name. When a real cached frame exists it remains visible
19
+ * under the treatment; otherwise the generated courtesy background is used.
20
+ *
21
+ * ── Why sharp and not ffmpeg ──────────────────────────────────────────────
22
+ * The first draft of this file shelled out to `ffmpeg` with a `drawtext`
23
+ * filter. That was written before the resize path was measured, and it was the
24
+ * wrong call for the same reason: this runs in the snapshot wrapper, a system
25
+ * builtin loaded in the hub's ROOT process, so every render was a fork + exec
26
+ * on the loop that serves the API — 52 ms against sharp's 4.8 ms in that
27
+ * container, to draw two lines of text.
28
+ *
29
+ * `terminal-frame-renderer.ts` already renders text this way (SVG → sharp), so
30
+ * this is the house pattern rather than a new one. It also drops the font-PATH
31
+ * probing the ffmpeg version needed: an SVG names a font FAMILY and fontconfig
32
+ * resolves it — verified in the hub image, where `fc-match "DejaVu Sans Mono"`
33
+ * answers with the real file.
34
+ *
35
+ * Rendering is pure input → bytes with no I/O of its own, and the geometry and
36
+ * escaping are separated out so they stay testable without rasterizing.
37
+ */
38
+ /** Default tile size when the caller asks for no particular width. */
39
+ var COURTESY_DEFAULT_WIDTH = 640;
40
+ /**
41
+ * Font stack for the rendered text. Family names, not paths — librsvg resolves
42
+ * them through fontconfig, and the trailing generics keep a host without DejaVu
43
+ * rendering something legible instead of nothing.
44
+ */
45
+ var COURTESY_FONT_STACK = "DejaVu Sans,DejaVu Sans Mono,Helvetica,Arial,sans-serif";
46
+ /** The word the frame carries. Deliberately the operator's vocabulary. */
47
+ function courtesyLabel(reason) {
48
+ switch (reason) {
49
+ case "disabled": return "Disabled";
50
+ case "offline": return "Offline";
51
+ case "sleeping": return "Sleeping";
52
+ case "unreachable": return "Unreachable";
53
+ case "waking": return "Waking";
54
+ }
55
+ }
56
+ /**
57
+ * Background per reason. A disabled camera is a DELIBERATE state and must not
58
+ * read as an alarm, so it is neutral grey; offline is a fault and is warmer.
59
+ */
60
+ function courtesyBackground(reason) {
61
+ switch (reason) {
62
+ case "disabled": return "#2b2b31";
63
+ case "offline": return "#3a2b2b";
64
+ case "sleeping": return "#232b3a";
65
+ case "waking": return "#243247";
66
+ case "unreachable": return "#3a2b2b";
67
+ }
68
+ }
69
+ /** XML escaping for text placed inside an SVG `<text>` node. */
70
+ function escapeCourtesyText(value) {
71
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
72
+ }
73
+ /**
74
+ * The SVG the frame is rasterized from: the state large and centred, the
75
+ * camera's name under it, smaller and dimmer.
76
+ *
77
+ * Sizes derive from the width so a 240 px grid tile and a 1920 px full-bleed
78
+ * frame read the same, with a floor so a thumbnail stays legible.
79
+ */
80
+ function buildCourtesySvg(spec) {
81
+ return buildStateSvg(spec, false);
82
+ }
83
+ /**
84
+ * The same state treatment as a courtesy frame, but translucent so the last
85
+ * real camera frame remains visible underneath it.
86
+ */
87
+ function buildCourtesyOverlaySvg(spec) {
88
+ return buildStateSvg(spec, true);
89
+ }
90
+ function buildStateSvg(spec, overlay) {
91
+ const stateSize = Math.max(12, Math.round(spec.width / 12));
92
+ const nameSize = Math.max(9, Math.round(spec.width / 26));
93
+ const state = escapeCourtesyText(courtesyLabel(spec.reason));
94
+ const name = escapeCourtesyText(spec.deviceName);
95
+ const midY = spec.height / 2;
96
+ const washOpacity = overlay ? "0.62" : "1";
97
+ const textStroke = Math.max(1, Math.round(spec.width / 320));
98
+ return [
99
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
100
+ `<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="${washOpacity}"/>`,
101
+ `<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
102
+ `<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
103
+ `<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
104
+ `</g></svg>`
105
+ ].join("");
106
+ }
107
+ /** Cache key — a courtesy frame is a pure function of these four. */
108
+ function courtesyCacheKey(spec) {
109
+ return `${spec.reason}:${String(spec.width)}x${String(spec.height)}:${spec.deviceName}`;
110
+ }
111
+ /**
112
+ * Render the frame.
113
+ *
114
+ * Rejects on failure — the caller decides what to do, exactly as `resizeJpeg`
115
+ * does, so a broken courtesy path is never mistaken for a broken camera.
116
+ */
117
+ function renderCourtesyJpeg(spec) {
118
+ return (0, sharp.default)(Buffer.from(buildCourtesySvg(spec))).jpeg({ quality: 82 }).toBuffer().then((bytes) => {
119
+ if (bytes.length === 0) throw new Error("courtesy frame produced no bytes");
120
+ return bytes;
121
+ });
122
+ }
123
+ /**
124
+ * Paint the state treatment over a real cached frame.
125
+ *
126
+ * The source dimensions are authoritative: overlays preserve the original
127
+ * frame rather than resizing it to the generated courtesy-frame default.
128
+ */
129
+ async function renderCourtesyOverlayJpeg(background, spec) {
130
+ const metadata = await (0, sharp.default)(background).metadata();
131
+ if (!metadata.width || !metadata.height) throw new Error("cached snapshot has no renderable dimensions");
132
+ const overlay = Buffer.from(buildCourtesyOverlaySvg({
133
+ ...spec,
134
+ width: metadata.width,
135
+ height: metadata.height
136
+ }));
137
+ const bytes = await (0, sharp.default)(background).composite([{ input: overlay }]).jpeg({ quality: 82 }).toBuffer();
138
+ if (bytes.length === 0) throw new Error("courtesy overlay produced no bytes");
139
+ return bytes;
140
+ }
141
+ //#endregion
142
+ Object.defineProperty(exports, "COURTESY_DEFAULT_WIDTH", {
143
+ enumerable: true,
144
+ get: function() {
145
+ return COURTESY_DEFAULT_WIDTH;
146
+ }
147
+ });
148
+ Object.defineProperty(exports, "courtesyCacheKey", {
149
+ enumerable: true,
150
+ get: function() {
151
+ return courtesyCacheKey;
152
+ }
153
+ });
154
+ Object.defineProperty(exports, "renderCourtesyJpeg", {
155
+ enumerable: true,
156
+ get: function() {
157
+ return renderCourtesyJpeg;
158
+ }
159
+ });
160
+ Object.defineProperty(exports, "renderCourtesyOverlayJpeg", {
161
+ enumerable: true,
162
+ get: function() {
163
+ return renderCourtesyOverlayJpeg;
164
+ }
165
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/system",
3
- "version": "1.2.231",
3
+ "version": "1.2.233",
4
4
  "description": "Core addon for CamStack — builtins, pipeline, process management, auth, logging, events",
5
5
  "keywords": [
6
6
  "camstack",