camstack 1.2.178 → 1.2.179

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.
@@ -24024,9 +24024,9 @@ var require_zod = __commonJS({
24024
24024
  }
24025
24025
  });
24026
24026
 
24027
- // ../system/dist/dist-CXVXFdJX.js
24028
- var require_dist_CXVXFdJX = __commonJS({
24029
- "../system/dist/dist-CXVXFdJX.js"(exports) {
24027
+ // ../system/dist/dist-DePB7uwU.js
24028
+ var require_dist_DePB7uwU = __commonJS({
24029
+ "../system/dist/dist-DePB7uwU.js"(exports) {
24030
24030
  "use strict";
24031
24031
  var zod = require_zod();
24032
24032
  __require("crypto");
@@ -39852,6 +39852,7 @@ var require_dist_CXVXFdJX = __commonJS({
39852
39852
  "detection-blind",
39853
39853
  "nodes",
39854
39854
  "addons",
39855
+ "addon-updates",
39855
39856
  "updates"
39856
39857
  ]);
39857
39858
  var NcStateSummaryPreviewSectionSchema = zod.z.object({
@@ -40238,7 +40239,19 @@ var require_dist_CXVXFdJX = __commonJS({
40238
40239
  * When this mode may be armed. Absent / null = always allowed (same as a
40239
40240
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
40240
40241
  */
40241
- schedule: NcScheduleSchema.optional()
40242
+ schedule: NcScheduleSchema.optional(),
40243
+ /**
40244
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
40245
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
40246
+ * only one, and the matches ride the triggered notification's mosaic (D705).
40247
+ */
40248
+ notifyDuringEntryDelay: zod.z.boolean().optional(),
40249
+ /**
40250
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
40251
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
40252
+ * is the one that starts the entry delay — the entry-delay notification already names it.
40253
+ */
40254
+ notifySensorRewatched: zod.z.boolean().optional()
40242
40255
  });
40243
40256
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
40244
40257
  var NcAlarmModeDelaysEditSchema = zod.z.object({
@@ -40248,7 +40261,11 @@ var require_dist_CXVXFdJX = __commonJS({
40248
40261
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
40249
40262
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
40250
40263
  /** Set a schedule, or `null` to clear (mode always allowed again). */
40251
- schedule: NcScheduleSchema.nullable().optional()
40264
+ schedule: NcScheduleSchema.nullable().optional(),
40265
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
40266
+ notifyDuringEntryDelay: zod.z.boolean().nullable().optional(),
40267
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
40268
+ notifySensorRewatched: zod.z.boolean().nullable().optional()
40252
40269
  });
40253
40270
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
40254
40271
  var NcAlarmNonBlockingEditSchema = zod.z.object({
@@ -44218,6 +44235,11 @@ var require_dist_CXVXFdJX = __commonJS({
44218
44235
  "analyzer",
44219
44236
  "device-activity"
44220
44237
  ]);
44238
+ var SessionTriggerSchema = zod.z.object({
44239
+ source: MotionSourceEnum,
44240
+ triggeredBy: MotionTriggeredBySchema,
44241
+ sensorName: zod.z.string().optional()
44242
+ });
44221
44243
  var MotionSourcesSchema = zod.z.array(MotionSourceEnum);
44222
44244
  var MOTION_SOURCE_LABELS = {
44223
44245
  analyzer: "Frame-diff Analyzer (motion addon)",
@@ -44365,6 +44387,14 @@ var require_dist_CXVXFdJX = __commonJS({
44365
44387
  */
44366
44388
  motionSources: MotionSourcesSchema.default(["analyzer"]),
44367
44389
  /**
44390
+ * What opened this attach, on a SESSION attach only (see
44391
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
44392
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
44393
+ * behind it, and every attach from an orchestrator older than the field — the
44394
+ * runner then names no source on the phase event.
44395
+ */
44396
+ sessionTrigger: SessionTriggerSchema.optional(),
44397
+ /**
44368
44398
  * WHICH root detector runs for this camera. The detection counterpart of
44369
44399
  * {@link motionSources}, same shape and same discipline: a per-camera list
44370
44400
  * of sources the system understands, vendor-agnostic, owned here.
@@ -48105,6 +48135,36 @@ var require_dist_CXVXFdJX = __commonJS({
48105
48135
  })
48106
48136
  }
48107
48137
  };
48138
+ var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
48139
+ function toAppLinkHubOrigin(baseUrl) {
48140
+ let parsed;
48141
+ try {
48142
+ parsed = new URL(baseUrl);
48143
+ } catch {
48144
+ return null;
48145
+ }
48146
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
48147
+ if (parsed.username !== "" || parsed.password !== "") return null;
48148
+ if (parsed.hostname === "") return null;
48149
+ return parsed.origin;
48150
+ }
48151
+ function pickAppLinkHub(endpoints) {
48152
+ const rank2 = (e) => {
48153
+ if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
48154
+ if (e.interfaceKind === "public") return 0;
48155
+ if (e.interfaceKind === "vpn") return 1;
48156
+ return null;
48157
+ };
48158
+ return endpoints.flatMap((e) => {
48159
+ const r = rank2(e);
48160
+ const origin = toAppLinkHubOrigin(e.baseUrl);
48161
+ return r === null || origin === null ? [] : [{
48162
+ r,
48163
+ p: e.priority,
48164
+ origin
48165
+ }];
48166
+ }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
48167
+ }
48108
48168
  var webrtcClientHintsSchema = zod.z.object({
48109
48169
  viewportWidth: zod.z.number().int().positive().optional(),
48110
48170
  viewportHeight: zod.z.number().int().positive().optional(),
@@ -51947,7 +52007,7 @@ var require_dist_CXVXFdJX = __commonJS({
51947
52007
  name: zod.z.string(),
51948
52008
  family: zod.z.enum(["IPv4", "IPv6"]),
51949
52009
  address: zod.z.string(),
51950
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
52010
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
51951
52011
  cidr: zod.z.string(),
51952
52012
  /** OS netmask. Stable across calls so the SDK can hash on it. */
51953
52013
  netmask: zod.z.string(),
@@ -51996,7 +52056,8 @@ var require_dist_CXVXFdJX = __commonJS({
51996
52056
  "lan-ipv4",
51997
52057
  "lan-ipv6",
51998
52058
  "public",
51999
- "loopback"
52059
+ "loopback",
52060
+ "app-link"
52000
52061
  ]),
52001
52062
  /**
52002
52063
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -52061,8 +52122,20 @@ var require_dist_CXVXFdJX = __commonJS({
52061
52122
  var NotificationEndpointSchema = zod.z.object({
52062
52123
  /** The operator's explicit choice, or null for AUTO. */
52063
52124
  baseUrl: zod.z.string().nullable(),
52064
- /** What the ranking currently resolves to (null when nothing is reachable). */
52065
- resolved: zod.z.string().nullable()
52125
+ /**
52126
+ * The effective flagged address: the explicit choice, else (AUTO) the
52127
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
52128
+ * answered the public tunnel / mesh here, or null.
52129
+ */
52130
+ resolved: zod.z.string().nullable(),
52131
+ /**
52132
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
52133
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
52134
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
52135
+ * hub advertises neither; the app link is then minted without `hub=`.
52136
+ * Absent on a pre-D709 hub.
52137
+ */
52138
+ appLinkHub: zod.z.string().nullable()
52066
52139
  });
52067
52140
  var ViewerEndpointsSchema = zod.z.object({
52068
52141
  /** The operator's explicit race set, or empty for AUTO. */
@@ -52126,7 +52199,7 @@ var require_dist_CXVXFdJX = __commonJS({
52126
52199
  * every client a second authority on a socket only the hub binds. A viewer
52127
52200
  * configured with a port-less public URL (`https://camstack.example.top`)
52128
52201
  * infers 443 from the scheme, asks with 443, and was handed
52129
- * `https://192.168.x.x:443` for a hub listening on 4443. That candidate
52202
+ * `https://192.168.10.9:443` for a hub listening on 4443. That candidate
52130
52203
  * cannot ever answer, and losing its race reads in the log exactly like
52131
52204
  * "the LAN was tried and it was slower" — so an operator sitting on his own
52132
52205
  * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
@@ -52159,13 +52232,21 @@ var require_dist_CXVXFdJX = __commonJS({
52159
52232
  * Pass `'https'` when the caller is itself loaded over HTTPS
52160
52233
  * to avoid mixed-content blocks in the browser. The public
52161
52234
  * tunnel always emits `https://` regardless. */
52162
- scheme: zod.z.enum(["http", "https"]).optional()
52235
+ scheme: zod.z.enum(["http", "https"]).optional(),
52236
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
52237
+ * an address notification links can be flagged with, not a hub
52238
+ * route. Default `false`: racers and rankers never see it. */
52239
+ includeAppLink: zod.z.boolean().optional()
52163
52240
  }), GetConnectionEndpointsResultSchema),
52164
52241
  /**
52165
- * The endpoint OUTBOUND artifacts must be addressed by — notification
52166
- * attachment links and deep links. `baseUrl: null` means AUTO: the first
52167
- * public candidate (a configured origin, then a connected tunnel, then the
52168
- * LAN), never loopback.
52242
+ * The address notification links are flagged with. `baseUrl: null` means
52243
+ * AUTO, which is the CamStack app link (D709): viewer taps go through
52244
+ * `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the app. Any
52245
+ * hub address the palette lists can be flagged instead; then viewer taps,
52246
+ * attachment links and action buttons all use it. Signed media and
52247
+ * buttons never use the app link — with it flagged (or AUTO) they keep
52248
+ * the ranked hub ladder (a configured origin, a connected tunnel, then
52249
+ * the LAN), never loopback.
52169
52250
  *
52170
52251
  * Why an explicit marker: the automatic ranking cannot know which of
52171
52252
  * several reachable addresses the RECIPIENT can actually open. A link is
@@ -52524,7 +52605,7 @@ var require_dist_CXVXFdJX = __commonJS({
52524
52605
  tags: zod.z.array(zod.z.string()).readonly(),
52525
52606
  /** True when the peer advertises itself as an exit-node candidate. */
52526
52607
  exitNodeOption: zod.z.boolean(),
52527
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
52608
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
52528
52609
  advertisedRoutes: zod.z.array(zod.z.string()).readonly(),
52529
52610
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
52530
52611
  userLogin: zod.z.string().nullable(),
@@ -72192,12 +72273,14 @@ var require_dist_CXVXFdJX = __commonJS({
72192
72273
  {
72193
72274
  id: "addons",
72194
72275
  family: "system",
72195
- label: "Addons",
72196
- kinds: [
72197
- "addon-crash-loop",
72198
- "addon-update-available",
72199
- "addon-updated"
72200
- ]
72276
+ label: "Addons stopped",
72277
+ kinds: ["addon-crash-loop"]
72278
+ },
72279
+ {
72280
+ id: "addon-updates",
72281
+ family: "system",
72282
+ label: "Addon updates",
72283
+ kinds: ["addon-update-available", "addon-updated"]
72201
72284
  },
72202
72285
  {
72203
72286
  id: "updates",
@@ -72327,13 +72410,15 @@ var require_dist_CXVXFdJX = __commonJS({
72327
72410
  },
72328
72411
  {
72329
72412
  name: "summaryAddons",
72330
- label: "Summary: addons",
72331
- example: "@camstack/addon-recorder 1.2.3 \u2192 1.2.4",
72332
- kinds: [
72333
- "addon-crash-loop",
72334
- "addon-update-available",
72335
- "addon-updated"
72336
- ]
72413
+ label: "Summary: addons stopped",
72414
+ example: "hub \u2014 recorder stopped after repeated crashes",
72415
+ kinds: ["addon-crash-loop"]
72416
+ },
72417
+ {
72418
+ name: "summaryAddonUpdates",
72419
+ label: "Summary: addon updates",
72420
+ example: "hub:\n\u2022 @camstack/addon-recorder 1.2.3 \u2192 1.2.4",
72421
+ kinds: ["addon-update-available", "addon-updated"]
72337
72422
  },
72338
72423
  {
72339
72424
  name: "summaryUpdates",
@@ -72417,6 +72502,7 @@ var require_dist_CXVXFdJX = __commonJS({
72417
72502
  [...RULE_BODY_ONLY],
72418
72503
  [...RULE_TEXT],
72419
72504
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
72505
+ ([...RULE_TEXT], [...COMBINED_ALARM]),
72420
72506
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
72421
72507
  ([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
72422
72508
  ([...RULE_TEXT], [...CONSUMABLE]),
@@ -72868,6 +72954,12 @@ var require_dist_CXVXFdJX = __commonJS({
72868
72954
  return CAMERA_SWITCH_ORDER;
72869
72955
  }
72870
72956
  });
72957
+ Object.defineProperty(exports, "CAMSTACK_APP_LINK_ORIGIN", {
72958
+ enumerable: true,
72959
+ get: function() {
72960
+ return CAMSTACK_APP_LINK_ORIGIN;
72961
+ }
72962
+ });
72871
72963
  Object.defineProperty(exports, "CAP_NAMES_WITH_STATUS", {
72872
72964
  enumerable: true,
72873
72965
  get: function() {
@@ -73702,6 +73794,12 @@ var require_dist_CXVXFdJX = __commonJS({
73702
73794
  return parseStreamParamsFormPatch;
73703
73795
  }
73704
73796
  });
73797
+ Object.defineProperty(exports, "pickAppLinkHub", {
73798
+ enumerable: true,
73799
+ get: function() {
73800
+ return pickAppLinkHub;
73801
+ }
73802
+ });
73705
73803
  Object.defineProperty(exports, "planComposition", {
73706
73804
  enumerable: true,
73707
73805
  get: function() {
@@ -73930,7 +74028,7 @@ var require_alerts_addon = __commonJS({
73930
74028
  [Symbol.toStringTag]: { value: "Module" }
73931
74029
  });
73932
74030
  require_chunk_Cek0wNdY();
73933
- var require_dist10 = require_dist_CXVXFdJX();
74031
+ var require_dist10 = require_dist_DePB7uwU();
73934
74032
  function deviceLabelFromEventData(data) {
73935
74033
  const name = stringNonEmpty(data["name"]) ?? stringNonEmpty(data["deviceName"]);
73936
74034
  if (name !== void 0) return name;
@@ -74754,7 +74852,7 @@ var require_calendars_addon = __commonJS({
74754
74852
  [Symbol.toStringTag]: { value: "Module" }
74755
74853
  });
74756
74854
  require_chunk_Cek0wNdY();
74757
- var require_dist10 = require_dist_CXVXFdJX();
74855
+ var require_dist10 = require_dist_DePB7uwU();
74758
74856
  var require_settle_sources = require_settle_sources_Bhsy57y();
74759
74857
  var zod = require_zod();
74760
74858
  var CALENDAR_COLOR_PALETTE = [
@@ -74968,11 +75066,11 @@ var require_calendars = __commonJS({
74968
75066
  }
74969
75067
  });
74970
75068
 
74971
- // ../system/dist/composition-sources-CAFDvTRs.js
74972
- var require_composition_sources_CAFDvTRs = __commonJS({
74973
- "../system/dist/composition-sources-CAFDvTRs.js"(exports) {
75069
+ // ../system/dist/composition-sources-0nuWOdNd.js
75070
+ var require_composition_sources_0nuWOdNd = __commonJS({
75071
+ "../system/dist/composition-sources-0nuWOdNd.js"(exports) {
74974
75072
  "use strict";
74975
- var require_dist10 = require_dist_CXVXFdJX();
75073
+ var require_dist10 = require_dist_DePB7uwU();
74976
75074
  function normalizedNativeOwner(owner) {
74977
75075
  const trimmed = owner?.trim();
74978
75076
  return trimmed ? trimmed : null;
@@ -75087,8 +75185,8 @@ var require_composer_addon = __commonJS({
75087
75185
  [Symbol.toStringTag]: { value: "Module" }
75088
75186
  });
75089
75187
  require_chunk_Cek0wNdY();
75090
- var require_dist10 = require_dist_CXVXFdJX();
75091
- var require_composition_sources = require_composition_sources_CAFDvTRs();
75188
+ var require_dist10 = require_dist_DePB7uwU();
75189
+ var require_composition_sources = require_composition_sources_0nuWOdNd();
75092
75190
  var zod = require_zod();
75093
75191
  function isRecord$2(value) {
75094
75192
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -76138,6 +76236,10 @@ var require_composer_addon = __commonJS({
76138
76236
  value: outcome.value
76139
76237
  };
76140
76238
  }
76239
+ function hasObservedRoseLevel(memory) {
76240
+ for (const call of memory.calls.values()) if (call.kind === "rose" && call.priorLevel !== void 0) return true;
76241
+ return false;
76242
+ }
76141
76243
  function strictFiniteAdvance(previous, next) {
76142
76244
  return typeof next === "number" && Number.isFinite(next) && (previous === null || next > previous);
76143
76245
  }
@@ -76177,7 +76279,7 @@ var require_composer_addon = __commonJS({
76177
76279
  const record = next.get(key);
76178
76280
  if (record === void 0) continue;
76179
76281
  const candidateMemory = require_dist10.cloneStatefulExpressionMemory(record.expressionMemory);
76180
- if (record.outcome?.state === "unavailable") require_dist10.disarmRoseMemory(candidateMemory);
76282
+ if (record.outcome?.state === "unavailable" && hasObservedRoseLevel(record.expressionMemory)) require_dist10.disarmRoseMemory(candidateMemory);
76181
76283
  const outcome = require_dist10.evaluateCompositionField({
76182
76284
  source: record.plan.source,
76183
76285
  read,
@@ -78871,7 +78973,7 @@ var require_console_logging = __commonJS({
78871
78973
  [Symbol.toStringTag]: { value: "Module" }
78872
78974
  });
78873
78975
  require_chunk_Cek0wNdY();
78874
- var require_dist10 = require_dist_CXVXFdJX();
78976
+ var require_dist10 = require_dist_DePB7uwU();
78875
78977
  var require_formatter = require_formatter_DqAKDlvN();
78876
78978
  var LEVEL_RANK = {
78877
78979
  debug: 0,
@@ -82940,8 +83042,8 @@ var require_core_blocks_addon = __commonJS({
82940
83042
  "use strict";
82941
83043
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
82942
83044
  var require_chunk = require_chunk_Cek0wNdY();
82943
- var require_dist10 = require_dist_CXVXFdJX();
82944
- var require_composition_sources = require_composition_sources_CAFDvTRs();
83045
+ var require_dist10 = require_dist_DePB7uwU();
83046
+ var require_composition_sources = require_composition_sources_0nuWOdNd();
82945
83047
  var node_crypto = __require("crypto");
82946
83048
  var _camstack_types_node = require_node();
82947
83049
  var node_fs_promises = __require("fs/promises");
@@ -84813,11 +84915,11 @@ var require_core_blocks = __commonJS({
84813
84915
  }
84814
84916
  });
84815
84917
 
84816
- // ../system/dist/retired-settings-keys-D24IW4qo.js
84817
- var require_retired_settings_keys_D24IW4qo = __commonJS({
84818
- "../system/dist/retired-settings-keys-D24IW4qo.js"(exports) {
84918
+ // ../system/dist/retired-settings-keys-BTKZdPn6.js
84919
+ var require_retired_settings_keys_BTKZdPn6 = __commonJS({
84920
+ "../system/dist/retired-settings-keys-BTKZdPn6.js"(exports) {
84819
84921
  "use strict";
84820
- var require_dist10 = require_dist_CXVXFdJX();
84922
+ var require_dist10 = require_dist_DePB7uwU();
84821
84923
  function settingsStoreIsAuthoritativeHere(env) {
84822
84924
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
84823
84925
  return raw === "" || raw === "hub";
@@ -85106,8 +85208,8 @@ var require_device_manager_addon = __commonJS({
85106
85208
  [Symbol.toStringTag]: { value: "Module" }
85107
85209
  });
85108
85210
  require_chunk_Cek0wNdY();
85109
- var require_dist10 = require_dist_CXVXFdJX();
85110
- var require_retired_settings_keys = require_retired_settings_keys_D24IW4qo();
85211
+ var require_dist10 = require_dist_DePB7uwU();
85212
+ var require_retired_settings_keys = require_retired_settings_keys_BTKZdPn6();
85111
85213
  var zod = require_zod();
85112
85214
  var node_crypto = __require("crypto");
85113
85215
  var _camstack_types_node = require_node();
@@ -93302,7 +93404,7 @@ var require_hub_forwarder = __commonJS({
93302
93404
  [Symbol.toStringTag]: { value: "Module" }
93303
93405
  });
93304
93406
  require_chunk_Cek0wNdY();
93305
- var require_dist10 = require_dist_CXVXFdJX();
93407
+ var require_dist10 = require_dist_DePB7uwU();
93306
93408
  var require_formatter = require_formatter_DqAKDlvN();
93307
93409
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
93308
93410
  var HubForwarderDestination = class {
@@ -93439,7 +93541,7 @@ var require_liveness_monitor_addon = __commonJS({
93439
93541
  "use strict";
93440
93542
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
93441
93543
  require_chunk_Cek0wNdY();
93442
- var require_dist10 = require_dist_CXVXFdJX();
93544
+ var require_dist10 = require_dist_DePB7uwU();
93443
93545
  var NO_DEVICES = "liveness:no-devices";
93444
93546
  var ALL_OFFLINE = "liveness:all-devices-offline";
93445
93547
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -93655,7 +93757,7 @@ var require_local_auth_addon = __commonJS({
93655
93757
  [Symbol.toStringTag]: { value: "Module" }
93656
93758
  });
93657
93759
  var require_chunk = require_chunk_Cek0wNdY();
93658
- var require_dist10 = require_dist_CXVXFdJX();
93760
+ var require_dist10 = require_dist_DePB7uwU();
93659
93761
  var node_crypto = __require("crypto");
93660
93762
  node_crypto = require_chunk.__toESM(node_crypto);
93661
93763
  var crypto$1 = __require("crypto");
@@ -101509,7 +101611,7 @@ var require_loki_logging = __commonJS({
101509
101611
  [Symbol.toStringTag]: { value: "Module" }
101510
101612
  });
101511
101613
  require_chunk_Cek0wNdY();
101512
- var require_dist10 = require_dist_CXVXFdJX();
101614
+ var require_dist10 = require_dist_DePB7uwU();
101513
101615
  function sanitizeLabelName(raw) {
101514
101616
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
101515
101617
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -102212,7 +102314,7 @@ var require_native_metrics_addon = __commonJS({
102212
102314
  [Symbol.toStringTag]: { value: "Module" }
102213
102315
  });
102214
102316
  var require_chunk = require_chunk_Cek0wNdY();
102215
- var require_dist10 = require_dist_CXVXFdJX();
102317
+ var require_dist10 = require_dist_DePB7uwU();
102216
102318
  var require_proc_stat = require_proc_stat_DL_WUOhl();
102217
102319
  var node_fs_promises = __require("fs/promises");
102218
102320
  var node_os = __require("os");
@@ -105052,7 +105154,7 @@ var require_filesystem_storage_addon = __commonJS({
105052
105154
  [Symbol.toStringTag]: { value: "Module" }
105053
105155
  });
105054
105156
  var require_chunk = require_chunk_Cek0wNdY();
105055
- var require_dist10 = require_dist_CXVXFdJX();
105157
+ var require_dist10 = require_dist_DePB7uwU();
105056
105158
  var node_crypto = __require("crypto");
105057
105159
  var node_fs_promises = __require("fs/promises");
105058
105160
  var node_path = __require("path");
@@ -106334,8 +106436,8 @@ var require_sqlite_settings_addon = __commonJS({
106334
106436
  [Symbol.toStringTag]: { value: "Module" }
106335
106437
  });
106336
106438
  var require_chunk = require_chunk_Cek0wNdY();
106337
- var require_dist10 = require_dist_CXVXFdJX();
106338
- var require_retired_settings_keys = require_retired_settings_keys_D24IW4qo();
106439
+ var require_dist10 = require_dist_DePB7uwU();
106440
+ var require_retired_settings_keys = require_retired_settings_keys_BTKZdPn6();
106339
106441
  var require_sqlite_snapshot_protocol = require_sqlite_snapshot_protocol_B9OndqZv();
106340
106442
  var require_wal_checkpoint_policy = require_wal_checkpoint_policy_d2z2qPqx();
106341
106443
  var node_crypto = __require("crypto");
@@ -109531,7 +109633,7 @@ var require_storage_orchestrator_addon = __commonJS({
109531
109633
  [Symbol.toStringTag]: { value: "Module" }
109532
109634
  });
109533
109635
  var require_chunk = require_chunk_Cek0wNdY();
109534
- var require_dist10 = require_dist_CXVXFdJX();
109636
+ var require_dist10 = require_dist_DePB7uwU();
109535
109637
  var require_hub_cap_forward = require_hub_cap_forward_DmHNjbB0();
109536
109638
  var zod = require_zod();
109537
109639
  var node_crypto = __require("crypto");
@@ -113258,7 +113360,7 @@ var require_system_config_addon = __commonJS({
113258
113360
  [Symbol.toStringTag]: { value: "Module" }
113259
113361
  });
113260
113362
  require_chunk_Cek0wNdY();
113261
- var require_dist10 = require_dist_CXVXFdJX();
113363
+ var require_dist10 = require_dist_DePB7uwU();
113262
113364
  var SECTION_TITLES = {
113263
113365
  server: "Server",
113264
113366
  auth: "Authentication"
@@ -131319,7 +131421,7 @@ var require_winston_logging = __commonJS({
131319
131421
  [Symbol.toStringTag]: { value: "Module" }
131320
131422
  });
131321
131423
  var require_chunk = require_chunk_Cek0wNdY();
131322
- var require_dist10 = require_dist_CXVXFdJX();
131424
+ var require_dist10 = require_dist_DePB7uwU();
131323
131425
  var require_formatter = require_formatter_DqAKDlvN();
131324
131426
  var node_path = __require("path");
131325
131427
  node_path = require_chunk.__toESM(node_path);
@@ -154984,12 +155086,12 @@ var require_dist2 = __commonJS({
154984
155086
  }
154985
155087
  });
154986
155088
 
154987
- // ../system/dist/child-cap-dispatch-CvYlCq60.js
154988
- var require_child_cap_dispatch_CvYlCq60 = __commonJS({
154989
- "../system/dist/child-cap-dispatch-CvYlCq60.js"(exports) {
155089
+ // ../system/dist/child-cap-dispatch-CRqYKHAb.js
155090
+ var require_child_cap_dispatch_CRqYKHAb = __commonJS({
155091
+ "../system/dist/child-cap-dispatch-CRqYKHAb.js"(exports) {
154990
155092
  "use strict";
154991
155093
  var require_chunk = require_chunk_Cek0wNdY();
154992
- var require_dist10 = require_dist_CXVXFdJX();
155094
+ var require_dist10 = require_dist_DePB7uwU();
154993
155095
  var require_proc_stat = require_proc_stat_DL_WUOhl();
154994
155096
  require_hub_cap_forward_DmHNjbB0();
154995
155097
  var node_crypto = __require("crypto");
@@ -169528,7 +169630,7 @@ var require_dist3 = __commonJS({
169528
169630
  "use strict";
169529
169631
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
169530
169632
  var require_chunk = require_chunk_Cek0wNdY();
169531
- var require_dist10 = require_dist_CXVXFdJX();
169633
+ var require_dist10 = require_dist_DePB7uwU();
169532
169634
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
169533
169635
  require_alerts();
169534
169636
  var require_builtins_calendars_calendars_addon = require_calendars_addon();
@@ -169559,7 +169661,7 @@ var require_dist3 = __commonJS({
169559
169661
  var require_builtins_winston_logging_index = require_winston_logging();
169560
169662
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_ey4mbofz();
169561
169663
  var require_tls$1 = require_tls_ue_Xefmv();
169562
- var require_child_cap_dispatch = require_child_cap_dispatch_CvYlCq60();
169664
+ var require_child_cap_dispatch = require_child_cap_dispatch_CRqYKHAb();
169563
169665
  var require_resource_monitor = require_resource_monitor_BgWGRxN5();
169564
169666
  var require_custom_action_registry = require_custom_action_registry_acBY3mHA();
169565
169667
  var zod = require_zod();
@@ -169874,6 +169976,29 @@ var require_dist3 = __commonJS({
169874
169976
  function canServeDataPlane(state) {
169875
169977
  return state.hasEntry && state.isForked && state.childKnown;
169876
169978
  }
169979
+ function edgeOwnedResponseHeaders(upRes, clientRes) {
169980
+ const out = {};
169981
+ for (const [name2, value] of Object.entries(upRes.headers)) {
169982
+ if (value === void 0) continue;
169983
+ const lower = name2.toLowerCase();
169984
+ if (lower.startsWith("access-control-")) continue;
169985
+ out[lower] = value;
169986
+ }
169987
+ const edgeVary = clientRes.getHeader("vary");
169988
+ const upVary = out["vary"];
169989
+ if (typeof edgeVary === "string" && edgeVary.length > 0 && upVary !== void 0) {
169990
+ const tokens = /* @__PURE__ */ new Set();
169991
+ const merged = [];
169992
+ for (const part of [edgeVary, String(upVary)].join(",").split(",")) {
169993
+ const token2 = part.trim();
169994
+ if (token2.length === 0 || tokens.has(token2.toLowerCase())) continue;
169995
+ tokens.add(token2.toLowerCase());
169996
+ merged.push(token2);
169997
+ }
169998
+ out["vary"] = merged.join(", ");
169999
+ }
170000
+ return out;
170001
+ }
169877
170002
  function proxyToUpstream(opts) {
169878
170003
  const { baseUrl, secret, upstreamPath, clientReq, clientRes, replayBody } = opts;
169879
170004
  const base = new URL(baseUrl);
@@ -169891,17 +170016,11 @@ var require_dist3 = __commonJS({
169891
170016
  path: upstreamPath,
169892
170017
  headers
169893
170018
  }, (upRes) => {
169894
- clientRes.writeHead(upRes.statusCode ?? 502, {
169895
- "access-control-allow-origin": "*",
169896
- ...upRes.headers
169897
- });
170019
+ clientRes.writeHead(upRes.statusCode ?? 502, edgeOwnedResponseHeaders(upRes, clientRes));
169898
170020
  upRes.pipe(clientRes);
169899
170021
  });
169900
170022
  upstream.on("error", (err) => {
169901
- if (!clientRes.headersSent) clientRes.writeHead(502, {
169902
- "content-type": "application/json",
169903
- "access-control-allow-origin": "*"
169904
- });
170023
+ if (!clientRes.headersSent) clientRes.writeHead(502, { "content-type": "application/json" });
169905
170024
  clientRes.end(JSON.stringify({
169906
170025
  error: "data-plane upstream error",
169907
170026
  detail: String(err)
@@ -176139,10 +176258,19 @@ var require_dist3 = __commonJS({
176139
176258
  };
176140
176259
  }
176141
176260
  var DEFAULT_DATA_PATH = "camstack-data";
176261
+ var originListSchema = zod.z.preprocess((value) => typeof value === "string" ? value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) : value, zod.z.array(zod.z.string()));
176142
176262
  var serverSchema = zod.z.object({
176143
176263
  port: zod.z.number().default(4443),
176144
176264
  host: zod.z.string().default("0.0.0.0"),
176145
- dataPath: zod.z.string().default(DEFAULT_DATA_PATH)
176265
+ dataPath: zod.z.string().default(DEFAULT_DATA_PATH),
176266
+ /**
176267
+ * EXTRA browser origins allowed to call this hub cross-origin (D707), on
176268
+ * top of the built-ins: the hub's own origin, the hosted viewer PWA and the
176269
+ * native app's opaque `null` origin. Bootstrap-owned on purpose — widening
176270
+ * who may read hub responses in a browser is a security decision, and the
176271
+ * policy is live from the first request, before the settings store exists.
176272
+ */
176273
+ corsOrigins: originListSchema.default([])
176146
176274
  });
176147
176275
  var authSchema = zod.z.object({
176148
176276
  jwtSecret: zod.z.string().nullable().default(null),
@@ -178612,7 +178740,8 @@ var require_dist3 = __commonJS({
178612
178740
  CAMSTACK_DATA: "server.dataPath",
178613
178741
  CAMSTACK_JWT_SECRET: "auth.jwtSecret",
178614
178742
  CAMSTACK_ADMIN_USER: "auth.adminUsername",
178615
- CAMSTACK_ADMIN_PASS: "auth.adminPassword"
178743
+ CAMSTACK_ADMIN_PASS: "auth.adminPassword",
178744
+ CAMSTACK_CORS_ORIGINS: "server.corsOrigins"
178616
178745
  };
178617
178746
  function loadBootstrapConfig(configPath) {
178618
178747
  const merged = applyEnvOverrides((0, _camstack_types_addon.asJsonObject)(loadYaml(configPath)) ?? {});
@@ -266129,6 +266258,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266129
266258
  "detection-blind",
266130
266259
  "nodes",
266131
266260
  "addons",
266261
+ "addon-updates",
266132
266262
  "updates"
266133
266263
  ]);
266134
266264
  var NcStateSummaryPreviewSectionSchema = zod.z.object({
@@ -267051,7 +267181,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267051
267181
  * When this mode may be armed. Absent / null = always allowed (same as a
267052
267182
  * rule with no `schedule`). Evaluated with {@link isScheduleActive}.
267053
267183
  */
267054
- schedule: NcScheduleSchema.optional()
267184
+ schedule: NcScheduleSchema.optional(),
267185
+ /**
267186
+ * D706: while this mode's ENTRY DELAY runs, do the alarm rules' own pushes go out (as an
267187
+ * intrusion naming the locations)? Absent = false — the entry-delay notification is the
267188
+ * only one, and the matches ride the triggered notification's mosaic (D705).
267189
+ */
267190
+ notifyDuringEntryDelay: zod.z.boolean().optional(),
267191
+ /**
267192
+ * D706: notify "watched again" when a sensor this arm bypassed closes and opens again.
267193
+ * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
267194
+ * is the one that starts the entry delay — the entry-delay notification already names it.
267195
+ */
267196
+ notifySensorRewatched: zod.z.boolean().optional()
267055
267197
  });
267056
267198
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
267057
267199
  var NcAlarmModeDelaysEditSchema = zod.z.object({
@@ -267061,7 +267203,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267061
267203
  triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional(),
267062
267204
  postTriggeredAction: NcAlarmPostTriggeredActionSchema.nullable().optional(),
267063
267205
  /** Set a schedule, or `null` to clear (mode always allowed again). */
267064
- schedule: NcScheduleSchema.nullable().optional()
267206
+ schedule: NcScheduleSchema.nullable().optional(),
267207
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
267208
+ notifyDuringEntryDelay: zod.z.boolean().nullable().optional(),
267209
+ /** D706: set the switch, or `null` to clear it (back to the default, false). */
267210
+ notifySensorRewatched: zod.z.boolean().nullable().optional()
267065
267211
  });
267066
267212
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
267067
267213
  var NcAlarmNonBlockingEditSchema = zod.z.object({
@@ -271148,6 +271294,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
271148
271294
  "analyzer",
271149
271295
  "device-activity"
271150
271296
  ]);
271297
+ var SessionTriggerSchema = zod.z.object({
271298
+ source: MotionSourceEnum,
271299
+ triggeredBy: MotionTriggeredBySchema,
271300
+ sensorName: zod.z.string().optional()
271301
+ });
271151
271302
  var MotionSourcesSchema = zod.z.array(MotionSourceEnum);
271152
271303
  var MOTION_SOURCE_LABELS = {
271153
271304
  analyzer: "Frame-diff Analyzer (motion addon)",
@@ -271299,6 +271450,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
271299
271450
  */
271300
271451
  motionSources: MotionSourcesSchema.default(["analyzer"]),
271301
271452
  /**
271453
+ * What opened this attach, on a SESSION attach only (see
271454
+ * {@link SessionTriggerSchema}). OPTIONAL with no default: absent on a
271455
+ * standing attach, an occupancy burst, a re-dispatch with no motion event
271456
+ * behind it, and every attach from an orchestrator older than the field — the
271457
+ * runner then names no source on the phase event.
271458
+ */
271459
+ sessionTrigger: SessionTriggerSchema.optional(),
271460
+ /**
271302
271461
  * WHICH root detector runs for this camera. The detection counterpart of
271303
271462
  * {@link motionSources}, same shape and same discipline: a per-camera list
271304
271463
  * of sources the system understands, vendor-agnostic, owned here.
@@ -275311,6 +275470,94 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
275311
275470
  rates: CLIP_BROKER_PACED_RATES
275312
275471
  }
275313
275472
  };
275473
+ var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
275474
+ var HUB_VIEWER_MOUNT = "/viewer/camstack";
275475
+ function isAppLinkBase(baseUrl) {
275476
+ if (baseUrl === null || baseUrl === void 0 || baseUrl === "") return false;
275477
+ try {
275478
+ return new URL(baseUrl).origin === CAMSTACK_APP_LINK_ORIGIN;
275479
+ } catch {
275480
+ return false;
275481
+ }
275482
+ }
275483
+ function viewerLinkBaseOf(endpoint) {
275484
+ const flagged = endpoint.baseUrl ?? endpoint.resolved;
275485
+ return isAppLinkBase(flagged) ? {
275486
+ form: "app",
275487
+ hubBaseUrl: endpoint.appLinkHub ?? null
275488
+ } : {
275489
+ form: "hub",
275490
+ hubBaseUrl: flagged
275491
+ };
275492
+ }
275493
+ function toAppLinkHubOrigin(baseUrl) {
275494
+ let parsed;
275495
+ try {
275496
+ parsed = new URL(baseUrl);
275497
+ } catch {
275498
+ return null;
275499
+ }
275500
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
275501
+ if (parsed.username !== "" || parsed.password !== "") return null;
275502
+ if (parsed.hostname === "") return null;
275503
+ return parsed.origin;
275504
+ }
275505
+ function pickAppLinkHub(endpoints) {
275506
+ const rank2 = (e) => {
275507
+ if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
275508
+ if (e.interfaceKind === "public") return 0;
275509
+ if (e.interfaceKind === "vpn") return 1;
275510
+ return null;
275511
+ };
275512
+ return endpoints.flatMap((e) => {
275513
+ const r = rank2(e);
275514
+ const origin = toAppLinkHubOrigin(e.baseUrl);
275515
+ return r === null || origin === null ? [] : [{
275516
+ r,
275517
+ p: e.priority,
275518
+ origin
275519
+ }];
275520
+ }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
275521
+ }
275522
+ function viewerRoute(destination) {
275523
+ switch (destination.kind) {
275524
+ case "camera": {
275525
+ if (!Number.isInteger(destination.deviceId) || destination.deviceId <= 0) return null;
275526
+ const at = destination.atMs;
275527
+ return {
275528
+ path: `/camera/${String(destination.deviceId)}`,
275529
+ query: at !== void 0 && Number.isFinite(at) ? [["at", String(Math.trunc(at))]] : []
275530
+ };
275531
+ }
275532
+ case "events":
275533
+ return {
275534
+ path: "/events",
275535
+ query: []
275536
+ };
275537
+ case "notifications":
275538
+ return {
275539
+ path: "/notifications",
275540
+ query: []
275541
+ };
275542
+ }
275543
+ }
275544
+ function encodeQuery(query) {
275545
+ if (query.length === 0) return "";
275546
+ return `?${query.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}`;
275547
+ }
275548
+ function buildViewerLink(input) {
275549
+ const route = viewerRoute(input.destination);
275550
+ if (route === null) return null;
275551
+ const raw = input.base.hubBaseUrl;
275552
+ const base = raw === null ? "" : raw.replace(/\/+$/, "");
275553
+ if (input.base.form === "app") {
275554
+ const origin = base === "" ? null : toAppLinkHubOrigin(base);
275555
+ const query = origin === null ? route.query : [...route.query, ["hub", origin]];
275556
+ return `${CAMSTACK_APP_LINK_ORIGIN}${route.path}${encodeQuery(query)}`;
275557
+ }
275558
+ if (base === "" || isAppLinkBase(base)) return null;
275559
+ return `${base}${HUB_VIEWER_MOUNT}${route.path}${encodeQuery(route.query)}`;
275560
+ }
275314
275561
  var webrtcClientHintsSchema = zod.z.object({
275315
275562
  viewportWidth: zod.z.number().int().positive().optional(),
275316
275563
  viewportHeight: zod.z.number().int().positive().optional(),
@@ -279172,7 +279419,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279172
279419
  name: zod.z.string(),
279173
279420
  family: zod.z.enum(["IPv4", "IPv6"]),
279174
279421
  address: zod.z.string(),
279175
- /** CIDR shorthand (e.g. `192.168.x.x/24`). Empty when OS doesn't expose. */
279422
+ /** CIDR shorthand (e.g. `192.168.10.10/24`). Empty when OS doesn't expose. */
279176
279423
  cidr: zod.z.string(),
279177
279424
  /** OS netmask. Stable across calls so the SDK can hash on it. */
279178
279425
  netmask: zod.z.string(),
@@ -279221,7 +279468,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279221
279468
  "lan-ipv4",
279222
279469
  "lan-ipv6",
279223
279470
  "public",
279224
- "loopback"
279471
+ "loopback",
279472
+ "app-link"
279225
279473
  ]),
279226
279474
  /**
279227
279475
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -279286,8 +279534,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279286
279534
  var NotificationEndpointSchema = zod.z.object({
279287
279535
  /** The operator's explicit choice, or null for AUTO. */
279288
279536
  baseUrl: zod.z.string().nullable(),
279289
- /** What the ranking currently resolves to (null when nothing is reachable). */
279290
- resolved: zod.z.string().nullable()
279537
+ /**
279538
+ * The effective flagged address: the explicit choice, else (AUTO) the
279539
+ * CamStack app link `https://camstack.zentik.app` (D709). A pre-D709 hub
279540
+ * answered the public tunnel / mesh here, or null.
279541
+ */
279542
+ resolved: zod.z.string().nullable(),
279543
+ /**
279544
+ * The hub origin an app link carries as `hub=` (D709): the hub's best
279545
+ * public HTTPS address — the tunnel, then the mesh (Tailscale MagicDNS) —
279546
+ * picked from this cap's own palette by `pickAppLinkHub`. `null` when the
279547
+ * hub advertises neither; the app link is then minted without `hub=`.
279548
+ * Absent on a pre-D709 hub.
279549
+ */
279550
+ appLinkHub: zod.z.string().nullable()
279291
279551
  });
279292
279552
  var ViewerEndpointsSchema = zod.z.object({
279293
279553
  /** The operator's explicit race set, or empty for AUTO. */
@@ -279351,7 +279611,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279351
279611
  * every client a second authority on a socket only the hub binds. A viewer
279352
279612
  * configured with a port-less public URL (`https://camstack.example.top`)
279353
279613
  * infers 443 from the scheme, asks with 443, and was handed
279354
- * `https://192.168.x.x:443` for a hub listening on 4443. That candidate
279614
+ * `https://192.168.10.9:443` for a hub listening on 4443. That candidate
279355
279615
  * cannot ever answer, and losing its race reads in the log exactly like
279356
279616
  * "the LAN was tried and it was slower" — so an operator sitting on his own
279357
279617
  * WiFi stayed on the public tunnel for weeks, with the LAN candidate born
@@ -279384,13 +279644,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279384
279644
  * Pass `'https'` when the caller is itself loaded over HTTPS
279385
279645
  * to avoid mixed-content blocks in the browser. The public
279386
279646
  * tunnel always emits `https://` regardless. */
279387
- scheme: zod.z.enum(["http", "https"]).optional()
279647
+ scheme: zod.z.enum(["http", "https"]).optional(),
279648
+ /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
279649
+ * an address notification links can be flagged with, not a hub
279650
+ * route. Default `false`: racers and rankers never see it. */
279651
+ includeAppLink: zod.z.boolean().optional()
279388
279652
  }), GetConnectionEndpointsResultSchema),
279389
279653
  /**
279390
- * The endpoint OUTBOUND artifacts must be addressed by — notification
279391
- * attachment links and deep links. `baseUrl: null` means AUTO: the first
279392
- * public candidate (a configured origin, then a connected tunnel, then the
279393
- * LAN), never loopback.
279654
+ * The address notification links are flagged with. `baseUrl: null` means
279655
+ * AUTO, which is the CamStack app link (D709): viewer taps go through
279656
+ * `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the app. Any
279657
+ * hub address the palette lists can be flagged instead; then viewer taps,
279658
+ * attachment links and action buttons all use it. Signed media and
279659
+ * buttons never use the app link — with it flagged (or AUTO) they keep
279660
+ * the ranked hub ladder (a configured origin, a connected tunnel, then
279661
+ * the LAN), never loopback.
279394
279662
  *
279395
279663
  * Why an explicit marker: the automatic ranking cannot know which of
279396
279664
  * several reachable addresses the RECIPIENT can actually open. A link is
@@ -279749,7 +280017,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
279749
280017
  tags: zod.z.array(zod.z.string()).readonly(),
279750
280018
  /** True when the peer advertises itself as an exit-node candidate. */
279751
280019
  exitNodeOption: zod.z.boolean(),
279752
- /** Subnet routes the peer advertises (e.g. `["192.168.x.x/24"]`). */
280020
+ /** Subnet routes the peer advertises (e.g. `["192.168.10.0/24"]`). */
279753
280021
  advertisedRoutes: zod.z.array(zod.z.string()).readonly(),
279754
280022
  /** Owner account login (e.g. `foo@example.com`). Null for tagged peers. */
279755
280023
  userLogin: zod.z.string().nullable(),
@@ -305258,12 +305526,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305258
305526
  {
305259
305527
  id: "addons",
305260
305528
  family: "system",
305261
- label: "Addons",
305262
- kinds: [
305263
- "addon-crash-loop",
305264
- "addon-update-available",
305265
- "addon-updated"
305266
- ]
305529
+ label: "Addons stopped",
305530
+ kinds: ["addon-crash-loop"]
305531
+ },
305532
+ {
305533
+ id: "addon-updates",
305534
+ family: "system",
305535
+ label: "Addon updates",
305536
+ kinds: ["addon-update-available", "addon-updated"]
305267
305537
  },
305268
305538
  {
305269
305539
  id: "updates",
@@ -305873,6 +306143,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305873
306143
  "entryDelaySec",
305874
306144
  "triggeredDurationSec"
305875
306145
  ];
306146
+ var NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC = 30;
306147
+ var NC_ALARM_NOTIFY_FIELDS = ["notifyDuringEntryDelay", "notifySensorRewatched"];
306148
+ function resolveModeNotifyPolicy(settings, mode) {
306149
+ const entry = (settings.modeDelays ?? []).find((e) => e.mode === mode);
306150
+ return {
306151
+ notifyDuringEntryDelay: entry?.notifyDuringEntryDelay === true,
306152
+ notifySensorRewatched: entry?.notifySensorRewatched === true
306153
+ };
306154
+ }
305876
306155
  function derivePostTriggeredAction(action, triggeredDurationSec) {
305877
306156
  if (action !== void 0) return action;
305878
306157
  return triggeredDurationSec > 0 ? "rearmAfter" : "doNothing";
@@ -305915,6 +306194,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305915
306194
  if (edit === void 0) return current;
305916
306195
  return edit === null ? void 0 : edit;
305917
306196
  }
306197
+ function editedFlag(current, edit) {
306198
+ if (edit === void 0) return current;
306199
+ return edit === null ? void 0 : edit;
306200
+ }
305918
306201
  function editedSchedule(current, edit) {
305919
306202
  if (edit === void 0) return current;
305920
306203
  return edit === null ? void 0 : edit;
@@ -305945,15 +306228,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
305945
306228
  const triggered = edited(base.triggeredDurationSec, edit.triggeredDurationSec);
305946
306229
  const action = editedAction(base.postTriggeredAction, edit.postTriggeredAction);
305947
306230
  const schedule = editedSchedule(base.schedule, edit.schedule);
306231
+ const duringEntry = editedFlag(base.notifyDuringEntryDelay, edit.notifyDuringEntryDelay);
306232
+ const rewatched = editedFlag(base.notifySensorRewatched, edit.notifySensorRewatched);
305948
306233
  if (exit !== void 0) next.exitDelaySec = exit;
305949
306234
  if (entry !== void 0) next.entryDelaySec = entry;
305950
306235
  if (triggered !== void 0) next.triggeredDurationSec = triggered;
305951
306236
  if (action !== void 0) next.postTriggeredAction = action;
305952
306237
  if (schedule !== void 0) next.schedule = schedule;
306238
+ if (duringEntry !== void 0) next.notifyDuringEntryDelay = duringEntry;
306239
+ if (rewatched !== void 0) next.notifySensorRewatched = rewatched;
305953
306240
  if (at >= 0) out[at] = next;
305954
306241
  else out.push(next);
305955
306242
  }
305956
- return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0 || e.postTriggeredAction !== void 0 || e.schedule !== void 0);
306243
+ return out.filter((e) => e.exitDelaySec !== void 0 || e.entryDelaySec !== void 0 || e.triggeredDurationSec !== void 0 || e.postTriggeredAction !== void 0 || e.schedule !== void 0 || e.notifyDuringEntryDelay !== void 0 || e.notifySensorRewatched !== void 0);
305957
306244
  }
305958
306245
  function applyModeDeviceListEdit(lists, edit) {
305959
306246
  const copy2 = lists.map((l) => ({
@@ -306080,6 +306367,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306080
306367
  const got = entry?.postTriggeredAction;
306081
306368
  if (edit.postTriggeredAction === null ? got !== void 0 : got !== edit.postTriggeredAction) return false;
306082
306369
  }
306370
+ for (const field of NC_ALARM_NOTIFY_FIELDS) {
306371
+ const want = edit[field];
306372
+ if (want === void 0) continue;
306373
+ const got = entry?.[field];
306374
+ if (want === null ? got !== void 0 : got !== want) return false;
306375
+ }
306083
306376
  if (edit.schedule !== void 0) {
306084
306377
  const got = entry?.schedule;
306085
306378
  if (edit.schedule === null ? got !== void 0 : !sameSchedule(got, edit.schedule)) return false;
@@ -306418,13 +306711,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306418
306711
  },
306419
306712
  {
306420
306713
  name: "summaryAddons",
306421
- label: "Summary: addons",
306422
- example: "@camstack/addon-recorder 1.2.3 \u2192 1.2.4",
306423
- kinds: [
306424
- "addon-crash-loop",
306425
- "addon-update-available",
306426
- "addon-updated"
306427
- ]
306714
+ label: "Summary: addons stopped",
306715
+ example: "hub \u2014 recorder stopped after repeated crashes",
306716
+ kinds: ["addon-crash-loop"]
306717
+ },
306718
+ {
306719
+ name: "summaryAddonUpdates",
306720
+ label: "Summary: addon updates",
306721
+ example: "hub:\n\u2022 @camstack/addon-recorder 1.2.3 \u2192 1.2.4",
306722
+ kinds: ["addon-update-available", "addon-updated"]
306428
306723
  },
306429
306724
  {
306430
306725
  name: "summaryUpdates",
@@ -306765,6 +307060,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
306765
307060
  kinds: ["system"],
306766
307061
  systemEventKinds: [...DEVICE_EVENTS]
306767
307062
  },
307063
+ {
307064
+ name: "locations",
307065
+ label: "Intrusion locations",
307066
+ example: "Ingresso, Corridoio, Salone",
307067
+ group: "system",
307068
+ families: ["rule"],
307069
+ fields: [...RULE_TEXT],
307070
+ kinds: ["system"],
307071
+ systemEventKinds: [...COMBINED_ALARM]
307072
+ },
306768
307073
  {
306769
307074
  name: "deviceId",
306770
307075
  label: "Device id",
@@ -307387,7 +307692,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
307387
307692
  approximated: times.length > 0
307388
307693
  };
307389
307694
  }
307390
- var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|Updates)\s*\}\}/;
307695
+ var SUMMARY_CONTENT_VAR = /\{\{\s*summary(?:Text|Devices|Streams|Battery|Consumables|Tanks|Detection|Nodes|Addons|AddonUpdates|Updates)\s*\}\}/;
307391
307696
  function templateCarriesSummary(body) {
307392
307697
  if (body === void 0 || body.trim().length === 0) return true;
307393
307698
  return SUMMARY_CONTENT_VAR.test(body);
@@ -309755,6 +310060,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
309755
310060
  exports.CAMERA_SWITCH_CATALOG = CAMERA_SWITCH_CATALOG;
309756
310061
  exports.CAMERA_SWITCH_ORDER = CAMERA_SWITCH_ORDER;
309757
310062
  exports.CAMERA_TRIGGER = CAMERA_TRIGGER;
310063
+ exports.CAMSTACK_APP_LINK_ORIGIN = CAMSTACK_APP_LINK_ORIGIN;
309758
310064
  exports.CAM_PROFILE_ORDER = require_sleep.CAM_PROFILE_ORDER;
309759
310065
  exports.CAPABILITY_NAMES = CAPABILITY_NAMES;
309760
310066
  exports.CAPABILITY_ROUTER_KEYS = CAPABILITY_ROUTER_KEYS;
@@ -310191,6 +310497,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310191
310497
  exports.HF_BASE_URL = HF_BASE_URL;
310192
310498
  exports.HF_REPO = HF_REPO;
310193
310499
  exports.HOURS_PER_DAY = HOURS_PER_DAY;
310500
+ exports.HUB_VIEWER_MOUNT = HUB_VIEWER_MOUNT;
310194
310501
  exports.HWACCEL_OPTIONS = HWACCEL_OPTIONS;
310195
310502
  exports.HealthStatusSchema = HealthStatusSchema;
310196
310503
  exports.HfModelResolutionSchema = HfModelResolutionSchema;
@@ -310408,9 +310715,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310408
310715
  exports.NC_ALARM_CONTROLLABLE_CAPS = NC_ALARM_CONTROLLABLE_CAPS;
310409
310716
  exports.NC_ALARM_DEFAULT_PROFILE_ID = NC_ALARM_DEFAULT_PROFILE_ID;
310410
310717
  exports.NC_ALARM_DEFAULT_PROFILE_NAME = NC_ALARM_DEFAULT_PROFILE_NAME;
310718
+ exports.NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC = NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC;
310411
310719
  exports.NC_ALARM_DELAY_FIELDS = NC_ALARM_DELAY_FIELDS;
310412
310720
  exports.NC_ALARM_DELAY_MAX_SEC = NC_ALARM_DELAY_MAX_SEC;
310413
310721
  exports.NC_ALARM_NONBLOCKING_MAX = NC_ALARM_NONBLOCKING_MAX;
310722
+ exports.NC_ALARM_NOTIFY_FIELDS = NC_ALARM_NOTIFY_FIELDS;
310414
310723
  exports.NC_ALARM_OPENING_EVENT_KINDS = NC_ALARM_OPENING_EVENT_KINDS;
310415
310724
  exports.NC_ALARM_SENSORS_MAX = NC_ALARM_SENSORS_MAX;
310416
310725
  exports.NC_ALARM_SYSTEM_DEFAULT_DELAYS = NC_ALARM_SYSTEM_DEFAULT_DELAYS;
@@ -310886,6 +311195,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
310886
311195
  exports.ServerUpdateCheckResultSchema = ServerUpdateCheckResultSchema;
310887
311196
  exports.ServerUpdateOutcomeSchema = ServerUpdateOutcomeSchema;
310888
311197
  exports.ServerUpdateStateSchema = ServerUpdateStateSchema;
311198
+ exports.SessionTriggerSchema = SessionTriggerSchema;
310889
311199
  exports.SetCameraSensorsInputSchema = SetCameraSensorsInputSchema;
310890
311200
  exports.SetLoggingSettingsInputSchema = SetLoggingSettingsInputSchema;
310891
311201
  exports.SetSiteLocationInputSchema = SetSiteLocationInputSchema;
@@ -311169,6 +311479,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311169
311479
  exports.buildRoleScopes = buildRoleScopes;
311170
311480
  exports.buildStreamParamsConfigSchema = buildStreamParamsConfigSchema;
311171
311481
  exports.buildVideoArgs = require_composition.buildVideoArgs;
311482
+ exports.buildViewerLink = buildViewerLink;
311172
311483
  exports.builtCapabilityBlockers = builtCapabilityBlockers;
311173
311484
  exports.buttonCapability = buttonCapability;
311174
311485
  exports.calendarSourceCapability = calendarSourceCapability;
@@ -311398,6 +311709,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311398
311709
  exports.invocationFromEncodeProfile = require_composition.invocationFromEncodeProfile;
311399
311710
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
311400
311711
  exports.isAlarmControllableCap = isAlarmControllableCap;
311712
+ exports.isAppLinkBase = isAppLinkBase;
311401
311713
  exports.isArrayOutputSchema = isArrayOutputSchema;
311402
311714
  exports.isAudioChunkFormat = require_sleep.isAudioChunkFormat;
311403
311715
  exports.isAudioClassName = isAudioClassName;
@@ -311544,6 +311856,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311544
311856
  exports.patchAudio = patchAudio;
311545
311857
  exports.petFeederCapability = petFeederCapability;
311546
311858
  exports.pickAccessoryControl = pickAccessoryControl;
311859
+ exports.pickAppLinkHub = pickAppLinkHub;
311547
311860
  exports.pickClusterStepModels = pickClusterStepModels;
311548
311861
  exports.pickClusterStepSettings = pickClusterStepSettings;
311549
311862
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -311623,6 +311936,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311623
311936
  exports.resolveLocationMode = resolveLocationMode;
311624
311937
  exports.resolveMethodAuth = resolveMethodAuth;
311625
311938
  exports.resolveModeDelays = resolveModeDelays;
311939
+ exports.resolveModeNotifyPolicy = resolveModeNotifyPolicy;
311626
311940
  exports.resolveMutate = resolveMutate;
311627
311941
  exports.resolvePoolMemoryPolicy = resolvePoolMemoryPolicy;
311628
311942
  exports.resolveRecordingProfiles = resolveRecordingProfiles;
@@ -311731,6 +312045,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311731
312045
  exports.terminalSessionCapability = terminalSessionCapability;
311732
312046
  exports.textToHtml = textToHtml;
311733
312047
  exports.timeOfDayToMinutes = timeOfDayToMinutes;
312048
+ exports.toAppLinkHubOrigin = toAppLinkHubOrigin;
311734
312049
  exports.toDeviceSummary = toDeviceSummary;
311735
312050
  exports.toExpressionValue = require_composition.toExpressionValue;
311736
312051
  exports.toNodeId = require_sleep.toNodeId;
@@ -311758,6 +312073,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
311758
312073
  exports.vectorStoreCapability = vectorStoreCapability;
311759
312074
  exports.vibrationCapability = vibrationCapability;
311760
312075
  exports.videoclipsCapability = videoclipsCapability;
312076
+ exports.viewerLinkBaseOf = viewerLinkBaseOf;
311761
312077
  exports.viewerUiCapability = require_sleep.viewerUiCapability;
311762
312078
  exports.waterHeaterCapability = waterHeaterCapability;
311763
312079
  exports.weatherCapability = weatherCapability;
@@ -469412,6 +469728,224 @@ var require_frame_policy = __commonJS({
469412
469728
  }
469413
469729
  });
469414
469730
 
469731
+ // ../../server/backend/dist/api/cors/cors-policy.js
469732
+ var require_cors_policy = __commonJS({
469733
+ "../../server/backend/dist/api/cors/cors-policy.js"(exports) {
469734
+ "use strict";
469735
+ Object.defineProperty(exports, "__esModule", { value: true });
469736
+ exports.CORS_PREFLIGHT_MAX_AGE_SEC = exports.CORS_EXPOSED_HEADERS = exports.CORS_ALLOWED_HEADERS = exports.CORS_ALLOWED_METHODS = exports.OPAQUE_ORIGIN = exports.VIEWER_PWA_ORIGIN = void 0;
469737
+ exports.normalizeOrigin = normalizeOrigin;
469738
+ exports.parseConfiguredOrigins = parseConfiguredOrigins;
469739
+ exports.builtInOrigins = builtInOrigins;
469740
+ exports.judgeCorsOrigin = judgeCorsOrigin;
469741
+ exports.corsResponseHeaders = corsResponseHeaders;
469742
+ exports.appendVary = appendVary;
469743
+ exports.VIEWER_PWA_ORIGIN = "https://camstack.zentik.app";
469744
+ exports.OPAQUE_ORIGIN = "null";
469745
+ exports.CORS_ALLOWED_METHODS = "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS";
469746
+ exports.CORS_ALLOWED_HEADERS = "authorization, content-type, range, if-range, if-none-match, cache-control, trpc-accept, x-trpc-source, x-trpc-batch-mode";
469747
+ exports.CORS_EXPOSED_HEADERS = "content-range, accept-ranges, content-length, content-disposition, etag, retry-after, x-camstack-reason, x-camstack-reason-code, x-camstack-disposition";
469748
+ exports.CORS_PREFLIGHT_MAX_AGE_SEC = 7200;
469749
+ function normalizeOrigin(raw) {
469750
+ const trimmed = raw.trim();
469751
+ if (trimmed === exports.OPAQUE_ORIGIN)
469752
+ return exports.OPAQUE_ORIGIN;
469753
+ let url;
469754
+ try {
469755
+ url = new URL(trimmed);
469756
+ } catch {
469757
+ return null;
469758
+ }
469759
+ if (url.protocol !== "https:" && url.protocol !== "http:")
469760
+ return null;
469761
+ if (url.username !== "" || url.password !== "")
469762
+ return null;
469763
+ if (url.pathname !== "/" && url.pathname !== "" || url.search !== "" || url.hash !== "") {
469764
+ return null;
469765
+ }
469766
+ return url.origin;
469767
+ }
469768
+ function parseConfiguredOrigins(value) {
469769
+ if (!Array.isArray(value))
469770
+ return { origins: [], rejected: [] };
469771
+ const origins = [];
469772
+ const rejected = [];
469773
+ for (const entry of value) {
469774
+ if (typeof entry !== "string") {
469775
+ rejected.push(String(entry));
469776
+ continue;
469777
+ }
469778
+ const normalized = normalizeOrigin(entry);
469779
+ if (normalized === null)
469780
+ rejected.push(entry);
469781
+ else
469782
+ origins.push(normalized);
469783
+ }
469784
+ return { origins, rejected };
469785
+ }
469786
+ function builtInOrigins(publicOrigin) {
469787
+ const entries = [exports.VIEWER_PWA_ORIGIN, exports.OPAQUE_ORIGIN];
469788
+ const pub = publicOrigin !== void 0 ? normalizeOrigin(publicOrigin) : null;
469789
+ if (pub !== null && pub !== exports.OPAQUE_ORIGIN)
469790
+ entries.push(pub);
469791
+ return entries;
469792
+ }
469793
+ function firstHost(value) {
469794
+ if (value === void 0)
469795
+ return null;
469796
+ const first = value.split(",")[0]?.trim().toLowerCase() ?? "";
469797
+ return first.length > 0 ? first : null;
469798
+ }
469799
+ function hostOfOrigin(origin) {
469800
+ if (origin === exports.OPAQUE_ORIGIN)
469801
+ return null;
469802
+ try {
469803
+ return new URL(origin).host.toLowerCase();
469804
+ } catch {
469805
+ return null;
469806
+ }
469807
+ }
469808
+ function judgeCorsOrigin(facts, listed) {
469809
+ const raw = facts.origin;
469810
+ if (raw === void 0 || raw.length === 0)
469811
+ return { kind: "no-origin" };
469812
+ const normalized = normalizeOrigin(raw);
469813
+ if (normalized === null)
469814
+ return { kind: "denied", origin: raw };
469815
+ if (listed.has(normalized))
469816
+ return { kind: "allowed", origin: raw, reason: "listed" };
469817
+ const originHost = hostOfOrigin(normalized);
469818
+ if (originHost !== null) {
469819
+ const ownHosts = [firstHost(facts.host), firstHost(facts.forwardedHost)];
469820
+ if (ownHosts.includes(originHost))
469821
+ return { kind: "allowed", origin: raw, reason: "own-host" };
469822
+ }
469823
+ return { kind: "denied", origin: raw };
469824
+ }
469825
+ function corsResponseHeaders(origin, preflight) {
469826
+ const base = {
469827
+ "access-control-allow-origin": origin,
469828
+ "access-control-expose-headers": exports.CORS_EXPOSED_HEADERS
469829
+ };
469830
+ if (preflight === null)
469831
+ return base;
469832
+ return {
469833
+ ...base,
469834
+ "access-control-allow-methods": exports.CORS_ALLOWED_METHODS,
469835
+ "access-control-allow-headers": exports.CORS_ALLOWED_HEADERS,
469836
+ "access-control-max-age": String(exports.CORS_PREFLIGHT_MAX_AGE_SEC),
469837
+ // A public origin (the PWA) calling a LAN hub is a "private network"
469838
+ // request to Chromium; its preflight must be answered explicitly.
469839
+ ...preflight.requestPrivateNetwork ? { "access-control-allow-private-network": "true" } : {}
469840
+ };
469841
+ }
469842
+ function appendVary(existing, token2) {
469843
+ if (existing === void 0 || existing.trim().length === 0)
469844
+ return token2;
469845
+ const parts = existing.split(",").map((p) => p.trim().toLowerCase());
469846
+ if (parts.includes("*") || parts.includes(token2.toLowerCase()))
469847
+ return existing;
469848
+ return `${existing}, ${token2}`;
469849
+ }
469850
+ }
469851
+ });
469852
+
469853
+ // ../../server/backend/dist/api/cors/register-cors-policy.js
469854
+ var require_register_cors_policy = __commonJS({
469855
+ "../../server/backend/dist/api/cors/register-cors-policy.js"(exports) {
469856
+ "use strict";
469857
+ Object.defineProperty(exports, "__esModule", { value: true });
469858
+ exports.createCorsPolicy = createCorsPolicy;
469859
+ exports.registerCorsPolicy = registerCorsPolicy;
469860
+ exports.refuseForeignWsUpgrade = refuseForeignWsUpgrade;
469861
+ var cors_policy_js_1 = require_cors_policy();
469862
+ var MAX_LOGGED_DENIALS = 256;
469863
+ function headerValue(value) {
469864
+ if (Array.isArray(value))
469865
+ return value[0];
469866
+ return value;
469867
+ }
469868
+ function varyText(value) {
469869
+ if (typeof value === "string")
469870
+ return value;
469871
+ if (Array.isArray(value))
469872
+ return value.filter((v) => typeof v === "string").join(", ");
469873
+ return void 0;
469874
+ }
469875
+ function createCorsPolicy(deps) {
469876
+ const builtIns = (0, cors_policy_js_1.builtInOrigins)(deps.publicOrigin);
469877
+ let lastConfigured = /* @__PURE__ */ Symbol("unset");
469878
+ let listed = new Set(builtIns);
469879
+ const loggedDenials = /* @__PURE__ */ new Set();
469880
+ const currentList = () => {
469881
+ const configured = deps.configuredOrigins();
469882
+ if (configured === lastConfigured)
469883
+ return listed;
469884
+ lastConfigured = configured;
469885
+ const parsed = (0, cors_policy_js_1.parseConfiguredOrigins)(configured);
469886
+ if (parsed.rejected.length > 0) {
469887
+ deps.logger.warn("cors: ignored configured origins that are not http(s) origins", {
469888
+ meta: { rejected: parsed.rejected, setting: "server.corsOrigins" }
469889
+ });
469890
+ }
469891
+ listed = /* @__PURE__ */ new Set([...builtIns, ...parsed.origins]);
469892
+ return listed;
469893
+ };
469894
+ return {
469895
+ judge(headers) {
469896
+ const verdict = (0, cors_policy_js_1.judgeCorsOrigin)({
469897
+ origin: headerValue(headers.origin),
469898
+ host: headerValue(headers.host),
469899
+ forwardedHost: headerValue(headers["x-forwarded-host"])
469900
+ }, currentList());
469901
+ if (verdict.kind === "denied" && !loggedDenials.has(verdict.origin)) {
469902
+ if (loggedDenials.size < MAX_LOGGED_DENIALS)
469903
+ loggedDenials.add(verdict.origin);
469904
+ deps.logger.warn("cors: refused a cross-origin caller not on the allowlist", {
469905
+ meta: {
469906
+ origin: verdict.origin,
469907
+ host: headerValue(headers.host) ?? null,
469908
+ hint: "add it to server.corsOrigins (config.yaml) or CAMSTACK_CORS_ORIGINS"
469909
+ }
469910
+ });
469911
+ }
469912
+ return verdict;
469913
+ }
469914
+ };
469915
+ }
469916
+ function registerCorsPolicy(fastify, policy) {
469917
+ fastify.addHook("onRequest", async (request, reply) => {
469918
+ const vary = (0, cors_policy_js_1.appendVary)(varyText(reply.getHeader("vary")), "Origin");
469919
+ reply.header("vary", vary);
469920
+ reply.raw.setHeader("vary", vary);
469921
+ const verdict = policy.judge(request.headers);
469922
+ const isPreflight = request.method === "OPTIONS" && request.headers["access-control-request-method"] !== void 0;
469923
+ if (verdict.kind === "allowed") {
469924
+ const headers = (0, cors_policy_js_1.corsResponseHeaders)(verdict.origin, isPreflight ? {
469925
+ requestPrivateNetwork: headerValue(request.headers["access-control-request-private-network"]) === "true"
469926
+ } : null);
469927
+ for (const [name, value] of Object.entries(headers)) {
469928
+ reply.header(name, value);
469929
+ reply.raw.setHeader(name, value);
469930
+ }
469931
+ }
469932
+ if (request.method === "OPTIONS") {
469933
+ return reply.code(204).send();
469934
+ }
469935
+ return void 0;
469936
+ });
469937
+ }
469938
+ function refuseForeignWsUpgrade(policy, headers, socket) {
469939
+ const verdict = policy.judge(headers);
469940
+ if (verdict.kind !== "denied")
469941
+ return false;
469942
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
469943
+ socket.destroy();
469944
+ return true;
469945
+ }
469946
+ }
469947
+ });
469948
+
469415
469949
  // ../../server/backend/dist/boot/integration-id-backfill.js
469416
469950
  var require_integration_id_backfill = __commonJS({
469417
469951
  "../../server/backend/dist/boot/integration-id-backfill.js"(exports) {
@@ -485897,16 +486431,6 @@ var require_manual_boot = __commonJS({
485897
486431
  getHttpAdapter() {
485898
486432
  return { getInstance: () => fastify };
485899
486433
  },
485900
- enableCors() {
485901
- fastify.addHook("onRequest", async (request, reply) => {
485902
- reply.header("access-control-allow-origin", "*");
485903
- reply.header("access-control-allow-methods", "GET,POST,PUT,DELETE,OPTIONS,PATCH");
485904
- reply.header("access-control-allow-headers", "content-type,authorization");
485905
- if (request.method === "OPTIONS") {
485906
- reply.status(204).send();
485907
- }
485908
- });
485909
- },
485910
486434
  enableShutdownHooks() {
485911
486435
  if (shutdownHooksRegistered)
485912
486436
  return;
@@ -486017,6 +486541,7 @@ var require_main4 = __commonJS({
486017
486541
  var spa_static_1 = require_spa_static();
486018
486542
  var spa_mount_1 = require_spa_mount();
486019
486543
  var frame_policy_1 = require_frame_policy();
486544
+ var register_cors_policy_1 = require_register_cors_policy();
486020
486545
  var cap_providers_1 = require_cap_providers();
486021
486546
  var logging_settings_1 = require_logging_settings();
486022
486547
  var request_census_settings_1 = require_request_census_settings();
@@ -486183,8 +486708,14 @@ var require_main4 = __commonJS({
486183
486708
  const fastifyOpts = tlsOptions ? { https: tlsOptions } : {};
486184
486709
  const app = await (0, manual_boot_1.bootManual)({ infra, fastifyOpts });
486185
486710
  app.enableShutdownHooks();
486186
- app.enableCors();
486187
486711
  const fastify = app.getHttpAdapter().getInstance();
486712
+ const corsConfig = app.get(config_service_1.ConfigService);
486713
+ const corsPolicy = (0, register_cors_policy_1.createCorsPolicy)({
486714
+ configuredOrigins: () => corsConfig.get("server.corsOrigins"),
486715
+ publicOrigin: process.env.CAMSTACK_PUBLIC_ORIGIN,
486716
+ logger: app.get(logging_service_1.LoggingService).createLogger("cors")
486717
+ });
486718
+ (0, register_cors_policy_1.registerCorsPolicy)(fastify, corsPolicy);
486188
486719
  await fastify.register(cookie_1.default);
486189
486720
  (0, frame_policy_1.registerFramePolicy)(fastify);
486190
486721
  fastify.addHook("onRequest", async (request) => {
@@ -486767,6 +487298,8 @@ var require_main4 = __commonJS({
486767
487298
  httpServer.on("upgrade", (request, socket, head) => {
486768
487299
  const pathname = (request.url ?? "").split("?")[0];
486769
487300
  if (pathname === "/trpc") {
487301
+ if ((0, register_cors_policy_1.refuseForeignWsUpgrade)(corsPolicy, request.headers, socket))
487302
+ return;
486770
487303
  wss.handleUpgrade(request, socket, head, (ws) => {
486771
487304
  wss.emit("connection", ws, request);
486772
487305
  });