@camstack/types 1.2.309 → 1.2.311

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-C3gDpn7G.js");
2
+ const require_sleep = require("./sleep-DyF95STh.js");
3
3
  const require_event_category = require("./event-category-DmS_tLsU.js");
4
4
  const require_composition = require("./composition-BLWAivB2.js");
5
5
  const require_err_msg = require("./err-msg-Dx2Kor0g.js");
@@ -16116,7 +16116,143 @@ var filesystemBrowseCapability = {
16116
16116
  }
16117
16117
  };
16118
16118
  //#endregion
16119
+ //#region src/capabilities/viewer-link-url.ts
16120
+ /** The central link domain the viewer app claims (Universal Links / App Links). */
16121
+ var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
16122
+ /** Where the hub serves the viewer SPA. */
16123
+ var HUB_VIEWER_MOUNT = "/viewer/camstack";
16124
+ /** True when `baseUrl` names the CamStack app link rather than a hub address. */
16125
+ function isAppLinkBase(baseUrl) {
16126
+ if (baseUrl === null || baseUrl === void 0 || baseUrl === "") return false;
16127
+ try {
16128
+ return new URL(baseUrl).origin === CAMSTACK_APP_LINK_ORIGIN;
16129
+ } catch {
16130
+ return false;
16131
+ }
16132
+ }
16133
+ /**
16134
+ * The base a viewer link is built on, from the flagged notification address:
16135
+ * the effective address is the explicit choice, else AUTO's `resolved`. The
16136
+ * one reading of that answer — the NC's tap and the Network page's preview
16137
+ * both go through it.
16138
+ */
16139
+ function viewerLinkBaseOf(endpoint) {
16140
+ const flagged = endpoint.baseUrl ?? endpoint.resolved;
16141
+ return isAppLinkBase(flagged) ? {
16142
+ form: "app",
16143
+ hubBaseUrl: endpoint.appLinkHub ?? null
16144
+ } : {
16145
+ form: "hub",
16146
+ hubBaseUrl: flagged
16147
+ };
16148
+ }
16149
+ /**
16150
+ * Reduce a base URL to the bare origin the central page accepts as `hub=`,
16151
+ * or `null` when it cannot be one: not http(s), carries credentials, or does
16152
+ * not parse. A path/query/fragment is dropped — the central page mounts the
16153
+ * viewer under the origin itself.
16154
+ */
16155
+ function toAppLinkHubOrigin(baseUrl) {
16156
+ let parsed;
16157
+ try {
16158
+ parsed = new URL(baseUrl);
16159
+ } catch {
16160
+ return null;
16161
+ }
16162
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
16163
+ if (parsed.username !== "" || parsed.password !== "") return null;
16164
+ if (parsed.hostname === "") return null;
16165
+ return parsed.origin;
16166
+ }
16167
+ /**
16168
+ * The hub address an app link carries as `hub=`, chosen from the hub's OWN
16169
+ * advertised palette so it always agrees with what the Network page lists:
16170
+ * the public tunnel first, then the mesh (Tailscale MagicDNS) — HTTPS only,
16171
+ * because the app and the hosted viewer reach it from off the LAN. `null`
16172
+ * when the hub advertises neither.
16173
+ */
16174
+ function pickAppLinkHub(endpoints) {
16175
+ const rank = (e) => {
16176
+ if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
16177
+ if (e.interfaceKind === "public") return 0;
16178
+ if (e.interfaceKind === "vpn") return 1;
16179
+ return null;
16180
+ };
16181
+ return endpoints.flatMap((e) => {
16182
+ const r = rank(e);
16183
+ const origin = toAppLinkHubOrigin(e.baseUrl);
16184
+ return r === null || origin === null ? [] : [{
16185
+ r,
16186
+ p: e.priority,
16187
+ origin
16188
+ }];
16189
+ }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
16190
+ }
16191
+ /** The viewer route for a destination, or null when it names nothing. */
16192
+ function viewerRoute(destination) {
16193
+ switch (destination.kind) {
16194
+ case "camera": {
16195
+ if (!Number.isInteger(destination.deviceId) || destination.deviceId <= 0) return null;
16196
+ const at = destination.atMs;
16197
+ return {
16198
+ path: `/camera/${String(destination.deviceId)}`,
16199
+ query: at !== void 0 && Number.isFinite(at) ? [["at", String(Math.trunc(at))]] : []
16200
+ };
16201
+ }
16202
+ case "events": return {
16203
+ path: "/events",
16204
+ query: []
16205
+ };
16206
+ case "notifications": return {
16207
+ path: "/notifications",
16208
+ query: []
16209
+ };
16210
+ }
16211
+ }
16212
+ function encodeQuery(query) {
16213
+ if (query.length === 0) return "";
16214
+ return `?${query.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}`;
16215
+ }
16216
+ /**
16217
+ * The link for a viewer destination, or `null` when none can be minted: an
16218
+ * unknown destination, or the `hub` form with no reachable base (a dead link
16219
+ * is worse than none).
16220
+ */
16221
+ function buildViewerLink(input) {
16222
+ const route = viewerRoute(input.destination);
16223
+ if (route === null) return null;
16224
+ const raw = input.base.hubBaseUrl;
16225
+ const base = raw === null ? "" : raw.replace(/\/+$/, "");
16226
+ if (input.base.form === "app") {
16227
+ const origin = base === "" ? null : toAppLinkHubOrigin(base);
16228
+ const query = origin === null ? route.query : [...route.query, ["hub", origin]];
16229
+ return `${CAMSTACK_APP_LINK_ORIGIN}${route.path}${encodeQuery(query)}`;
16230
+ }
16231
+ if (base === "" || isAppLinkBase(base)) return null;
16232
+ return `${base}${HUB_VIEWER_MOUNT}${route.path}${encodeQuery(route.query)}`;
16233
+ }
16234
+ //#endregion
16119
16235
  //#region src/capabilities/hub-address.ts
16236
+ var HUB_ADDRESS_ROLES = [
16237
+ "appConnections",
16238
+ "notificationTaps",
16239
+ "internal",
16240
+ "publicEndpoint"
16241
+ ];
16242
+ var SINGLE_HUB_ADDRESS_ROLES = [
16243
+ "notificationTaps",
16244
+ "internal",
16245
+ "publicEndpoint"
16246
+ ];
16247
+ function isSingleHubAddressRole(role) {
16248
+ return role !== "appConnections";
16249
+ }
16250
+ var AUTOMATIC_HUB_ADDRESS_FLAGS = {
16251
+ appConnections: [],
16252
+ notificationTaps: null,
16253
+ internal: null,
16254
+ publicEndpoint: null
16255
+ };
16120
16256
  var IPV4_LITERAL = /^\d{1,3}(?:\.\d{1,3}){3}$/;
16121
16257
  function hostnameOf(baseUrl) {
16122
16258
  try {
@@ -16126,6 +16262,9 @@ function hostnameOf(baseUrl) {
16126
16262
  return null;
16127
16263
  }
16128
16264
  }
16265
+ function trimSlash(baseUrl) {
16266
+ return baseUrl.replace(/\/+$/, "");
16267
+ }
16129
16268
  function isMesh(endpoint) {
16130
16269
  return endpoint.kind === "public" && endpoint.interfaceKind === "vpn";
16131
16270
  }
@@ -16140,11 +16279,11 @@ function isLanIpv4Literal(endpoint) {
16140
16279
  const host = hostnameOf(endpoint.baseUrl);
16141
16280
  return host !== null && IPV4_LITERAL.test(host);
16142
16281
  }
16143
- /** Lower = better; `null` = never this audience's answer. */
16144
- function rankFor(audience, endpoint) {
16145
- if (endpoint.kind === "loopback" || endpoint.kind === "app-link") return null;
16282
+ /** Lower = better; `null` = never this role's AUTOMATIC answer. */
16283
+ function autoRankFor(role, endpoint) {
16284
+ if (endpoint.kind === "loopback" || endpoint.kind === "app-link" || endpoint.kind === "custom") return null;
16146
16285
  if (hostnameOf(endpoint.baseUrl) === null) return null;
16147
- if (audience === "cluster") {
16286
+ if (role === "internal") {
16148
16287
  if (isLanIpv4Literal(endpoint)) return 0;
16149
16288
  if (isPlausibleLan(endpoint)) return 1;
16150
16289
  if (isMesh(endpoint)) return 2;
@@ -16170,11 +16309,11 @@ function isLoopbackHost(hostname) {
16170
16309
  return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h);
16171
16310
  }
16172
16311
  /**
16173
- * Validate an operator-typed override: an http(s) URL with a host, no
16312
+ * Validate an operator-typed address: an http(s) URL with a host, no
16174
16313
  * credentials, no path, and never loopback (it would name whoever dials it).
16175
16314
  * Answers the normalised origin (`scheme://host[:port]`).
16176
16315
  */
16177
- function validateHubAddressOverride(raw) {
16316
+ function validateCustomAddress(raw) {
16178
16317
  const trimmed = raw.trim();
16179
16318
  if (trimmed === "") return {
16180
16319
  ok: false,
@@ -16214,47 +16353,130 @@ function validateHubAddressOverride(raw) {
16214
16353
  baseUrl: parsed.origin
16215
16354
  };
16216
16355
  }
16356
+ /** The reason a plain-http public endpoint is refused unless accepted. */
16357
+ var PUBLIC_ENDPOINT_HTTP_REASON = "The public endpoint needs an https:// address: passkeys and most identity providers refuse a plain-http origin. Accept http only when sign-in and notification links never leave your own network.";
16217
16358
  /**
16218
- * The hub's base URL for `audience`, without a trailing slash, or `null` when
16219
- * the palette holds nothing that audience can reach. A valid operator
16220
- * `override` wins outright; an empty one means automatic. Ties inside a rank
16221
- * go to the palette's own `priority` (its preferred interface first).
16359
+ * May `baseUrl` carry `role`? The address check above, plus two role rules:
16360
+ * the app link carries `notificationTaps` only (it is not a hub address), and
16361
+ * a plain-http address carries `publicEndpoint` only when the operator
16362
+ * explicitly accepted it (`acceptInsecureHttp`) — a LAN-only password or
16363
+ * magic-link sign-in works over http, a passkey or an OIDC callback does not.
16222
16364
  */
16223
- function pickHubBaseUrl(endpoints, audience, override = null) {
16224
- return resolveHubAddress(endpoints, audience, override).resolved;
16225
- }
16226
- /** {@link pickHubBaseUrl}, saying where the answer came from. */
16227
- function resolveHubAddress(endpoints, audience, override) {
16228
- if (override !== null && override !== "") {
16229
- const check = validateHubAddressOverride(override);
16230
- if (check.ok) return {
16231
- override,
16232
- resolved: check.baseUrl,
16233
- source: "override"
16234
- };
16235
- }
16236
- const auto = pickFromPalette(endpoints, audience);
16237
- return {
16238
- override: override === null || override === "" ? null : override,
16239
- resolved: auto,
16240
- source: auto === null ? "none" : "auto"
16365
+ function checkRoleAddress(role, baseUrl, options = {}) {
16366
+ if (isAppLinkBase(baseUrl)) return role === "notificationTaps" ? {
16367
+ ok: true,
16368
+ baseUrl: CAMSTACK_APP_LINK_ORIGIN
16369
+ } : {
16370
+ ok: false,
16371
+ reason: "The CamStack app link is not a hub address — only notification taps can use it."
16241
16372
  };
16373
+ const check = validateCustomAddress(baseUrl);
16374
+ if (!check.ok) return check;
16375
+ if (role === "publicEndpoint" && check.baseUrl.startsWith("http:") && options.acceptInsecureHttp !== true) return {
16376
+ ok: false,
16377
+ reason: PUBLIC_ENDPOINT_HTTP_REASON
16378
+ };
16379
+ return check;
16380
+ }
16381
+ /** A stored flag that still validates (stored values are external data). */
16382
+ function validFlag(role, flag) {
16383
+ if (flag === null || flag === "") return null;
16384
+ const check = checkRoleAddress(role, flag, { acceptInsecureHttp: true });
16385
+ return check.ok ? check.baseUrl : null;
16242
16386
  }
16243
- function pickFromPalette(endpoints, audience) {
16387
+ function pickAuto(endpoints, role) {
16244
16388
  const best = endpoints.flatMap((endpoint) => {
16245
- const rank = rankFor(audience, endpoint);
16389
+ const rank = autoRankFor(role, endpoint);
16246
16390
  return rank === null ? [] : [{
16247
16391
  rank,
16248
16392
  priority: endpoint.priority,
16249
16393
  endpoint
16250
16394
  }];
16251
16395
  }).toSorted((a, b) => a.rank - b.rank || a.priority - b.priority)[0];
16252
- return best === void 0 ? null : best.endpoint.baseUrl.replace(/\/+$/, "");
16396
+ return best === void 0 ? null : trimSlash(best.endpoint.baseUrl);
16397
+ }
16398
+ function resolveHubRole(endpoints, role, flag) {
16399
+ const flagged = validFlag(role, flag);
16400
+ if (flagged !== null) return {
16401
+ flagged,
16402
+ resolved: flagged,
16403
+ source: "flagged"
16404
+ };
16405
+ const auto = pickAuto(endpoints, role);
16406
+ return {
16407
+ flagged: null,
16408
+ resolved: auto,
16409
+ source: auto === null ? "none" : "auto"
16410
+ };
16253
16411
  }
16254
- /** The bare hostname of {@link pickHubBaseUrl}'s answer, or `null`. */
16255
- function pickHubHostname(endpoints, audience, override = null) {
16256
- const baseUrl = pickHubBaseUrl(endpoints, audience, override);
16257
- return baseUrl === null ? null : hostnameOf(baseUrl);
16412
+ /** `internal`: what agents and LAN devices download from. */
16413
+ function resolveInternalAddress(endpoints, flags) {
16414
+ return resolveHubRole(endpoints, "internal", flags.internal);
16415
+ }
16416
+ /**
16417
+ * `publicEndpoint`: every URL that must work off the LAN — sign-in links, OIDC
16418
+ * / OAuth2 callbacks, the relying party, and notification buttons,
16419
+ * attachments, media and admin links.
16420
+ */
16421
+ function resolvePublicEndpoint(endpoints, flags) {
16422
+ return resolveHubRole(endpoints, "publicEndpoint", flags.publicEndpoint);
16423
+ }
16424
+ /** `notificationTaps`: the flagged address, else the CamStack app link (D709). */
16425
+ function resolveNotificationTapAddress(flags) {
16426
+ const flagged = validFlag("notificationTaps", flags.notificationTaps);
16427
+ if (flagged !== null && !isAppLinkBase(flagged)) return {
16428
+ flagged,
16429
+ resolved: flagged,
16430
+ source: "flagged"
16431
+ };
16432
+ return {
16433
+ flagged: null,
16434
+ resolved: CAMSTACK_APP_LINK_ORIGIN,
16435
+ source: "auto"
16436
+ };
16437
+ }
16438
+ /** AUTO app connections: LAN IPv4 + the public tunnel. Never IPv6, mesh, loopback or custom. */
16439
+ function isDefaultAppConnectionEndpoint(endpoint) {
16440
+ if (endpoint.kind === "lan-ipv4") return true;
16441
+ return endpoint.kind === "public" && endpoint.interfaceKind === "public";
16442
+ }
16443
+ /**
16444
+ * The endpoints the apps race: the flagged ones that are in `endpoints`
16445
+ * (which carries the custom addresses flagged `appConnections`), else AUTO. A flagged
16446
+ * set none of which is currently up falls back to AUTO rather than racing
16447
+ * nothing.
16448
+ */
16449
+ function selectAppConnectionEndpoints(endpoints, flags) {
16450
+ const auto = endpoints.filter(isDefaultAppConnectionEndpoint);
16451
+ if (flags.appConnections.length === 0) return auto;
16452
+ const wanted = new Set(flags.appConnections);
16453
+ const explicit = endpoints.filter((e) => wanted.has(e.baseUrl));
16454
+ return explicit.length > 0 ? explicit : auto;
16455
+ }
16456
+ /** `appConnections`: what the apps try when connecting. */
16457
+ function resolveAppConnections(endpoints, flags) {
16458
+ const wanted = new Set(flags.appConnections);
16459
+ const selected = selectAppConnectionEndpoints(endpoints, flags);
16460
+ const usedFlags = selected.some((e) => wanted.has(e.baseUrl));
16461
+ return {
16462
+ flagged: flags.appConnections,
16463
+ resolved: selected.map((e) => e.baseUrl),
16464
+ source: selected.length === 0 ? "none" : usedFlags ? "flagged" : "auto"
16465
+ };
16466
+ }
16467
+ /**
16468
+ * Every role at once, each through its own resolver. `appPalette` is what
16469
+ * the apps race (it carries the custom addresses flagged `appConnections`);
16470
+ * `hubPalette` is the detected HTTPS palette `internal` and `publicEndpoint`
16471
+ * resolve over.
16472
+ */
16473
+ function resolveHubAddressRoles(appPalette, hubPalette, flags) {
16474
+ return {
16475
+ appConnections: resolveAppConnections(appPalette, flags),
16476
+ notificationTaps: resolveNotificationTapAddress(flags),
16477
+ internal: resolveInternalAddress(hubPalette, flags),
16478
+ publicEndpoint: resolvePublicEndpoint(hubPalette, flags)
16479
+ };
16258
16480
  }
16259
16481
  /** The bare hostname of a base URL, or `null` when it does not parse. */
16260
16482
  function hubAddressHostname(baseUrl) {
@@ -22058,7 +22280,13 @@ var NcAlarmModeDelaysSchema = zod.z.object({
22058
22280
  * Absent = false (logged, not sent). Even when true it is not sent when that same sensor
22059
22281
  * is the one that starts the entry delay — the entry-delay notification already names it.
22060
22282
  */
22061
- notifySensorRewatched: zod.z.boolean().optional()
22283
+ notifySensorRewatched: zod.z.boolean().optional(),
22284
+ /**
22285
+ * D735: notify "ignored" when a sensor this arm bypassed really MOVED while armed (its own
22286
+ * change time is after the arm) and set nothing off. Absent = false (logged, not sent). A
22287
+ * re-sync, a recovery from `unavailable` or a duplicate is never announced, switch or not.
22288
+ */
22289
+ notifySensorIgnored: zod.z.boolean().optional()
22062
22290
  });
22063
22291
  var NcAlarmModeDelaysListSchema = zod.z.array(NcAlarmModeDelaysSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
22064
22292
  /**
@@ -22077,7 +22305,9 @@ var NcAlarmModeDelaysEditSchema = zod.z.object({
22077
22305
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
22078
22306
  notifyDuringEntryDelay: zod.z.boolean().nullable().optional(),
22079
22307
  /** D706: set the switch, or `null` to clear it (back to the default, false). */
22080
- notifySensorRewatched: zod.z.boolean().nullable().optional()
22308
+ notifySensorRewatched: zod.z.boolean().nullable().optional(),
22309
+ /** D735: set the switch, or `null` to clear it (back to the default, false). */
22310
+ notifySensorIgnored: zod.z.boolean().nullable().optional()
22081
22311
  });
22082
22312
  var NcAlarmModeDelaysEditListSchema = zod.z.array(NcAlarmModeDelaysEditSchema).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
22083
22313
  /** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
@@ -32796,122 +33026,6 @@ var CLIP_PLAYBACK_OPTIONS = {
32796
33026
  }
32797
33027
  };
32798
33028
  //#endregion
32799
- //#region src/capabilities/viewer-link-url.ts
32800
- /** The central link domain the viewer app claims (Universal Links / App Links). */
32801
- var CAMSTACK_APP_LINK_ORIGIN = "https://camstack.zentik.app";
32802
- /** Where the hub serves the viewer SPA. */
32803
- var HUB_VIEWER_MOUNT = "/viewer/camstack";
32804
- /** True when `baseUrl` names the CamStack app link rather than a hub address. */
32805
- function isAppLinkBase(baseUrl) {
32806
- if (baseUrl === null || baseUrl === void 0 || baseUrl === "") return false;
32807
- try {
32808
- return new URL(baseUrl).origin === CAMSTACK_APP_LINK_ORIGIN;
32809
- } catch {
32810
- return false;
32811
- }
32812
- }
32813
- /**
32814
- * The base a viewer link is built on, from the flagged notification address:
32815
- * the effective address is the explicit choice, else AUTO's `resolved`. The
32816
- * one reading of that answer — the NC's tap and the Network page's preview
32817
- * both go through it.
32818
- */
32819
- function viewerLinkBaseOf(endpoint) {
32820
- const flagged = endpoint.baseUrl ?? endpoint.resolved;
32821
- return isAppLinkBase(flagged) ? {
32822
- form: "app",
32823
- hubBaseUrl: endpoint.appLinkHub ?? null
32824
- } : {
32825
- form: "hub",
32826
- hubBaseUrl: flagged
32827
- };
32828
- }
32829
- /**
32830
- * Reduce a base URL to the bare origin the central page accepts as `hub=`,
32831
- * or `null` when it cannot be one: not http(s), carries credentials, or does
32832
- * not parse. A path/query/fragment is dropped — the central page mounts the
32833
- * viewer under the origin itself.
32834
- */
32835
- function toAppLinkHubOrigin(baseUrl) {
32836
- let parsed;
32837
- try {
32838
- parsed = new URL(baseUrl);
32839
- } catch {
32840
- return null;
32841
- }
32842
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
32843
- if (parsed.username !== "" || parsed.password !== "") return null;
32844
- if (parsed.hostname === "") return null;
32845
- return parsed.origin;
32846
- }
32847
- /**
32848
- * The hub address an app link carries as `hub=`, chosen from the hub's OWN
32849
- * advertised palette so it always agrees with what the Network page lists:
32850
- * the public tunnel first, then the mesh (Tailscale MagicDNS) — HTTPS only,
32851
- * because the app and the hosted viewer reach it from off the LAN. `null`
32852
- * when the hub advertises neither.
32853
- */
32854
- function pickAppLinkHub(endpoints) {
32855
- const rank = (e) => {
32856
- if (e.kind !== "public" || !e.baseUrl.startsWith("https://")) return null;
32857
- if (e.interfaceKind === "public") return 0;
32858
- if (e.interfaceKind === "vpn") return 1;
32859
- return null;
32860
- };
32861
- return endpoints.flatMap((e) => {
32862
- const r = rank(e);
32863
- const origin = toAppLinkHubOrigin(e.baseUrl);
32864
- return r === null || origin === null ? [] : [{
32865
- r,
32866
- p: e.priority,
32867
- origin
32868
- }];
32869
- }).toSorted((a, b) => a.r - b.r || a.p - b.p)[0]?.origin ?? null;
32870
- }
32871
- /** The viewer route for a destination, or null when it names nothing. */
32872
- function viewerRoute(destination) {
32873
- switch (destination.kind) {
32874
- case "camera": {
32875
- if (!Number.isInteger(destination.deviceId) || destination.deviceId <= 0) return null;
32876
- const at = destination.atMs;
32877
- return {
32878
- path: `/camera/${String(destination.deviceId)}`,
32879
- query: at !== void 0 && Number.isFinite(at) ? [["at", String(Math.trunc(at))]] : []
32880
- };
32881
- }
32882
- case "events": return {
32883
- path: "/events",
32884
- query: []
32885
- };
32886
- case "notifications": return {
32887
- path: "/notifications",
32888
- query: []
32889
- };
32890
- }
32891
- }
32892
- function encodeQuery(query) {
32893
- if (query.length === 0) return "";
32894
- return `?${query.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&")}`;
32895
- }
32896
- /**
32897
- * The link for a viewer destination, or `null` when none can be minted: an
32898
- * unknown destination, or the `hub` form with no reachable base (a dead link
32899
- * is worse than none).
32900
- */
32901
- function buildViewerLink(input) {
32902
- const route = viewerRoute(input.destination);
32903
- if (route === null) return null;
32904
- const raw = input.base.hubBaseUrl;
32905
- const base = raw === null ? "" : raw.replace(/\/+$/, "");
32906
- if (input.base.form === "app") {
32907
- const origin = base === "" ? null : toAppLinkHubOrigin(base);
32908
- const query = origin === null ? route.query : [...route.query, ["hub", origin]];
32909
- return `${CAMSTACK_APP_LINK_ORIGIN}${route.path}${encodeQuery(query)}`;
32910
- }
32911
- if (base === "" || isAppLinkBase(base)) return null;
32912
- return `${base}${HUB_VIEWER_MOUNT}${route.path}${encodeQuery(route.query)}`;
32913
- }
32914
- //#endregion
32915
33029
  //#region src/capabilities/webrtc-session.cap.ts
32916
33030
  /**
32917
33031
  * Optional client-side hints sent at session creation to help the provider
@@ -33419,6 +33533,35 @@ var webrtcSessionCapability = {
33419
33533
  alive: zod.z.boolean()
33420
33534
  })),
33421
33535
  /**
33536
+ * The viewer's downlink probe, for ONE live session (D733).
33537
+ *
33538
+ * The player measures `/adaptive-probe` after the session's first decoded
33539
+ * frame (D635). It used to act on the reading itself, by opening a SECOND
33540
+ * session with `hints.downlinkMbps` — every adaptive live open became two
33541
+ * sessions, two ICE + DTLS handshakes and two server pipelines. It now
33542
+ * hands the measurement to the session it already has; the provider picks
33543
+ * the rung exactly as it would from an offer's hints (same viewport
33544
+ * ceiling) and moves the session there — in place when the negotiated
33545
+ * codec carries the new rendition, by the existing re-offer
33546
+ * (`getSessionState.pendingRenegotiation`) when it cannot.
33547
+ *
33548
+ * A MEASUREMENT, never a tier: the client does not choose. A probe only
33549
+ * raises the rung; lowering it stays the ladder's call on delivery.
33550
+ * `held` answers why nothing moved (`not-adaptive`, `pinned`,
33551
+ * `already-there`, `recorded`, …) — a client that gets ANY answer must not
33552
+ * fall back to a reconnect; only a provider that does not know the method
33553
+ * (an older hub) leaves the client its legacy second session.
33554
+ */
33555
+ applyDownlinkProbe: require_sleep.method(zod.z.object({
33556
+ deviceId: zod.z.number().int().nonnegative(),
33557
+ sessionId: zod.z.string(),
33558
+ downlinkMbps: zod.z.number().nonnegative()
33559
+ }), zod.z.object({
33560
+ outcome: zod.z.enum(["switching", "held"]),
33561
+ tier: require_sleep.CamProfileSchema.nullable(),
33562
+ reason: zod.z.string()
33563
+ }), { kind: "mutation" }),
33564
+ /**
33422
33565
  * The SERVER's half of the stream-debug overlay, for one live session.
33423
33566
  *
33424
33567
  * The client can measure its own transport (`getStats()`); it cannot see
@@ -37840,7 +37983,8 @@ var ConnectionEndpointSchema = zod.z.object({
37840
37983
  "lan-ipv6",
37841
37984
  "public",
37842
37985
  "loopback",
37843
- "app-link"
37986
+ "app-link",
37987
+ "custom"
37844
37988
  ]),
37845
37989
  /**
37846
37990
  * Originating-interface kind — `lan`/`wifi`/`docker`/`vpn`/`other`/
@@ -37909,7 +38053,7 @@ var GetConnectionEndpointsResultSchema = zod.z.object({
37909
38053
  localPort: AdvertisedLocalPortSchema
37910
38054
  });
37911
38055
  /**
37912
- * The address flagged for notification links. `baseUrl: null` = AUTO, which
38056
+ * The address flagged for notification taps (`notificationTaps`). `baseUrl: null` = AUTO, which
37913
38057
  * since D709 is the CamStack app link; `resolved` reports the effective value,
37914
38058
  * so the UI can show it either way.
37915
38059
  */
@@ -37943,13 +38087,12 @@ var ViewerEndpointsSchema = zod.z.object({
37943
38087
  resolved: zod.z.array(zod.z.string()).readonly()
37944
38088
  });
37945
38089
  /**
37946
- * One audience's hub address (see `hub-address.ts`): the operator's stored
37947
- * override, the address in use, and where it came from.
38090
+ * DEPRECATED compatibility shape of `getHubAddresses` (D729 amendment, kept
38091
+ * for addons built against it): `cluster` is the `internal` role, `browser`
38092
+ * the `publicEndpoint` role; `override` is the flagged address.
37948
38093
  */
37949
38094
  var HubAddressSnapshotSchema = zod.z.object({
37950
- /** The operator's override, or null for automatic. */
37951
38095
  override: zod.z.string().nullable(),
37952
- /** The address in use, or null when nothing qualifies (never loopback). */
37953
38096
  resolved: zod.z.string().nullable(),
37954
38097
  source: zod.z.enum([
37955
38098
  "override",
@@ -37958,11 +38101,63 @@ var HubAddressSnapshotSchema = zod.z.object({
37958
38101
  ])
37959
38102
  });
37960
38103
  var HubAddressesSchema = zod.z.object({
37961
- /** "Hub address for agents" — agents' bundle/model pulls, LAN devices. */
37962
38104
  cluster: HubAddressSnapshotSchema,
37963
- /** "Public address for sign-in links" — e-mail links, OIDC, WebAuthn, OAuth2. */
37964
38105
  browser: HubAddressSnapshotSchema
37965
38106
  });
38107
+ /**
38108
+ * What an address is used for (`hub-address.ts`, D734). `appConnections` is
38109
+ * many-valued; `notificationTaps`, `publicEndpoint` and `internal` are carried
38110
+ * by at most one address.
38111
+ */
38112
+ var HubAddressRoleSchema = zod.z.enum(HUB_ADDRESS_ROLES);
38113
+ var RoleSourceSchema = zod.z.enum([
38114
+ "flagged",
38115
+ "auto",
38116
+ "none"
38117
+ ]);
38118
+ /** One single-valued role: the flagged address, the one in use, and why. */
38119
+ var HubAddressRoleResolutionSchema = zod.z.object({
38120
+ /** The address carrying the flag, or null = automatic. */
38121
+ flagged: zod.z.string().nullable(),
38122
+ /** The address in use, or null when nothing qualifies (never loopback). */
38123
+ resolved: zod.z.string().nullable(),
38124
+ source: RoleSourceSchema
38125
+ });
38126
+ var HubAppConnectionsResolutionSchema = zod.z.object({
38127
+ /** The addresses flagged `appConnections`, or empty = automatic. */
38128
+ flagged: zod.z.array(zod.z.string()).readonly(),
38129
+ /** What the apps race right now. */
38130
+ resolved: zod.z.array(zod.z.string()).readonly(),
38131
+ source: RoleSourceSchema
38132
+ });
38133
+ /**
38134
+ * One row of the Network page's Addresses table: a detected endpoint, an
38135
+ * operator-entered custom address, or the CamStack app link — flagged the
38136
+ * same way. A URL that is both detected and entered is ONE row.
38137
+ */
38138
+ var HubAddressRowSchema = ConnectionEndpointSchema.extend({
38139
+ /** In the hub's detected palette right now. */
38140
+ detected: zod.z.boolean(),
38141
+ /** Entered by the operator (removable). */
38142
+ custom: zod.z.boolean(),
38143
+ /** The roles explicitly flagged on this address. */
38144
+ flags: zod.z.array(HubAddressRoleSchema).readonly(),
38145
+ /** The roles this address serves right now — flagged or automatic. */
38146
+ inUse: zod.z.array(HubAddressRoleSchema).readonly(),
38147
+ /** The roles this address may be flagged with. */
38148
+ eligible: zod.z.array(HubAddressRoleSchema).readonly()
38149
+ });
38150
+ var HubAddressBookSchema = zod.z.object({
38151
+ rows: zod.z.array(HubAddressRowSchema).readonly(),
38152
+ roles: zod.z.object({
38153
+ appConnections: HubAppConnectionsResolutionSchema,
38154
+ notificationTaps: HubAddressRoleResolutionSchema,
38155
+ internal: HubAddressRoleResolutionSchema,
38156
+ publicEndpoint: HubAddressRoleResolutionSchema
38157
+ }),
38158
+ /** The `hub=` an app link carries (D709), or null. */
38159
+ appLinkHub: zod.z.string().nullable()
38160
+ });
37966
38161
  var AllowedAddressesSchema = zod.z.object({
37967
38162
  /**
37968
38163
  * Allowlist of interface addresses operators have explicitly opted
@@ -38053,19 +38248,18 @@ var localNetworkCapability = {
38053
38248
  * tunnel always emits `https://` regardless. */
38054
38249
  scheme: zod.z.enum(["http", "https"]).optional(),
38055
38250
  /** Append the CamStack app link entry (`kind: 'app-link'`, D709) —
38056
- * an address notification links can be flagged with, not a hub
38251
+ * an address notification taps can be flagged with, not a hub
38057
38252
  * route. Default `false`: racers and rankers never see it. */
38058
38253
  includeAppLink: zod.z.boolean().optional()
38059
38254
  }), GetConnectionEndpointsResultSchema),
38060
38255
  /**
38061
- * The address notification links are flagged with. `baseUrl: null` means
38062
- * AUTO, which is the CamStack app link (D709): viewer taps go through
38063
- * `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the app. Any
38064
- * hub address the palette lists can be flagged instead; then viewer taps,
38065
- * attachment links and action buttons all use it. Signed media and
38066
- * buttons never use the app link — with it flagged (or AUTO) they keep
38067
- * the ranked hub ladder (a configured origin, a connected tunnel, then
38068
- * the LAN), never loopback.
38256
+ * The `notificationTaps` role (D734): where TAPPING a notification opens.
38257
+ * `baseUrl: null` means AUTO, which is the CamStack app link (D709): taps
38258
+ * go through `https://camstack.zentik.app/…?hub=<appLinkHub>` and open the
38259
+ * app. Any hub address the palette lists can be flagged instead; then a
38260
+ * tap opens that address. Only the tap: action buttons, attachment /
38261
+ * media links and admin links follow the `publicEndpoint` role
38262
+ * (`getAddresses().roles.publicEndpoint`), never this one.
38069
38263
  *
38070
38264
  * Why an explicit marker: the automatic ranking cannot know which of
38071
38265
  * several reachable addresses the RECIPIENT can actually open. A link is
@@ -38075,46 +38269,80 @@ var localNetworkCapability = {
38075
38269
  */
38076
38270
  getNotificationEndpoint: require_sleep.method(zod.z.void(), NotificationEndpointSchema),
38077
38271
  /**
38078
- * Mark which endpoint notification artifacts use. `baseUrl: null` restores
38079
- * AUTO. The value is stored verbatim (not an index into the candidate
38080
- * list): interfaces come and go, and an index would silently re-point at a
38081
- * different address after a reboot.
38272
+ * @deprecated Use `setAddressRole({ role: 'notificationTaps', … })`. It
38273
+ * writes the same `notificationTaps` flag, with the same validation. `baseUrl: null` restores automatic; an
38274
+ * address that is not detected becomes a custom address.
38082
38275
  */
38083
38276
  setNotificationEndpoint: require_sleep.method(zod.z.object({ baseUrl: zod.z.string().nullable() }), NotificationEndpointSchema, { kind: "mutation" }),
38084
38277
  /**
38085
- * The endpoints the SDK / viewer races for API access. Empty `baseUrls`
38086
- * means AUTO: every LAN IPv4 address plus the public tunnel, never IPv6,
38087
- * never mesh, never loopback. `resolved` is that set (or the operator's
38088
- * explicit subset) as it stands right now.
38278
+ * The `appConnections` role as shipped apps read it: the flagged addresses
38279
+ * (detected or custom), or empty for AUTO — every LAN IPv4 address plus
38280
+ * the public tunnel, never IPv6, mesh, loopback or a custom address.
38281
+ * `resolved` is the set as it stands right now; the custom ones in it are
38282
+ * also in `getConnectionEndpoints`, which the apps filter by it.
38089
38283
  */
38090
38284
  getViewerEndpoints: require_sleep.method(zod.z.void(), ViewerEndpointsSchema),
38091
38285
  /**
38092
- * Replace the viewer race set. Empty `baseUrls` restores AUTO. Stored
38093
- * verbatim (not indices) so a temporarily-down tunnel is not silently
38094
- * dropped from the operator's choice.
38286
+ * @deprecated Use `setAddressRole({ role: 'appConnections', … })`. It
38287
+ * replaces the same `appConnections` flags.
38288
+ * Empty `baseUrls` restores automatic.
38095
38289
  */
38096
38290
  setViewerEndpoints: require_sleep.method(zod.z.object({ baseUrls: zod.z.array(zod.z.string()).readonly() }), ViewerEndpointsSchema, { kind: "mutation" }),
38097
38291
  /**
38098
- * The hub's address for each audience — agents (`cluster`) and sign-in
38099
- * links (`browser`) — as every consumer must read it: the operator's
38100
- * override when set, else the automatic pick from this cap's palette,
38101
- * never loopback. The ONE answer to "at which address is this hub
38102
- * reached?"; there is no environment variable for it.
38292
+ * The Network page's Addresses table and every role's answer — the ONE
38293
+ * reading of "which address is this hub reached at, for what": detected
38294
+ * endpoints and custom addresses with their role flags, and per role the
38295
+ * flagged address, the one in use and whether it is automatic. Agent
38296
+ * deliveries and LAN devices read `roles.internal`; sign-in, OAuth2 and
38297
+ * the notification centre's buttons / media / admin links read
38298
+ * `roles.publicEndpoint`; nothing ranks the palette itself. `scheme` is
38299
+ * the one the caller shows LAN rows in (as `getConnectionEndpoints`);
38300
+ * `internal` and `publicEndpoint` are always resolved over the HTTPS
38301
+ * palette.
38103
38302
  */
38104
- getHubAddresses: require_sleep.method(zod.z.void(), HubAddressesSchema),
38303
+ getAddresses: require_sleep.method(zod.z.object({ scheme: zod.z.enum(["http", "https"]).optional() }).optional(), HubAddressBookSchema),
38105
38304
  /**
38106
- * Set or clear (`baseUrl: null`) one audience's override. Validated: an
38107
- * http(s) origin with a host, no credentials, no path, never loopback —
38108
- * an invalid value is refused with the reason, nothing is stored.
38305
+ * Add a custom address. Validated: an http(s) origin with a host, no
38306
+ * credentials, no path, never loopback — an invalid one is refused with
38307
+ * the reason and nothing is stored. Adding an address that already exists
38308
+ * only updates its label.
38109
38309
  */
38110
- setHubAddress: require_sleep.method(zod.z.object({
38111
- audience: zod.z.enum(["cluster", "browser"]),
38112
- baseUrl: zod.z.string().nullable()
38113
- }), HubAddressesSchema, {
38310
+ addCustomAddress: require_sleep.method(zod.z.object({
38311
+ baseUrl: zod.z.string(),
38312
+ label: zod.z.string().max(80)
38313
+ }), HubAddressBookSchema, {
38314
+ kind: "mutation",
38315
+ auth: "admin"
38316
+ }),
38317
+ /** Remove a custom address. Its role flags go with it (those roles turn automatic). */
38318
+ removeCustomAddress: require_sleep.method(zod.z.object({ baseUrl: zod.z.string() }), HubAddressBookSchema, {
38114
38319
  kind: "mutation",
38115
38320
  auth: "admin"
38116
38321
  }),
38117
38322
  /**
38323
+ * Flag (`enabled: true`) or unflag an address for a role. For a
38324
+ * single-valued role, flagging moves the flag off every other address.
38325
+ * `baseUrl: null` returns the role to automatic. The address must be a row
38326
+ * of the table (detected, custom, or the app link for `notificationTaps`);
38327
+ * `publicEndpoint` on a plain-http address is refused unless
38328
+ * `acceptInsecureHttp`, and never takes the app link.
38329
+ */
38330
+ setAddressRole: require_sleep.method(zod.z.object({
38331
+ role: HubAddressRoleSchema,
38332
+ baseUrl: zod.z.string().nullable(),
38333
+ enabled: zod.z.boolean(),
38334
+ acceptInsecureHttp: zod.z.boolean().optional()
38335
+ }), HubAddressBookSchema, {
38336
+ kind: "mutation",
38337
+ auth: "admin"
38338
+ }),
38339
+ /**
38340
+ * @deprecated Read `getAddresses().roles.internal` / `.publicEndpoint`.
38341
+ * Kept so an addon built against the D729 amendment keeps resolving:
38342
+ * `cluster` = the `internal` role, `browser` = the `publicEndpoint` role.
38343
+ */
38344
+ getHubAddresses: require_sleep.method(zod.z.void(), HubAddressesSchema),
38345
+ /**
38118
38346
  * Read the operator's allowlist. Empty = "auto" (no filter). Used
38119
38347
  * by the admin UI's address selector to seed its checkbox state.
38120
38348
  */
@@ -53757,12 +53985,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
53757
53985
  addonId: null,
53758
53986
  access: "view"
53759
53987
  },
53988
+ "localNetwork.addCustomAddress": {
53989
+ capName: "local-network",
53990
+ capScope: "system",
53991
+ addonId: null,
53992
+ access: "create"
53993
+ },
53760
53994
  "localNetwork.downloadCa": {
53761
53995
  capName: "local-network",
53762
53996
  capScope: "system",
53763
53997
  addonId: null,
53764
53998
  access: "view"
53765
53999
  },
54000
+ "localNetwork.getAddresses": {
54001
+ capName: "local-network",
54002
+ capScope: "system",
54003
+ addonId: null,
54004
+ access: "view"
54005
+ },
53766
54006
  "localNetwork.getAllowedAddresses": {
53767
54007
  capName: "local-network",
53768
54008
  capScope: "system",
@@ -53817,6 +54057,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
53817
54057
  addonId: null,
53818
54058
  access: "create"
53819
54059
  },
54060
+ "localNetwork.removeCustomAddress": {
54061
+ capName: "local-network",
54062
+ capScope: "system",
54063
+ addonId: null,
54064
+ access: "delete"
54065
+ },
53820
54066
  "localNetwork.resetAllowlistToBestMatch": {
53821
54067
  capName: "local-network",
53822
54068
  capScope: "system",
@@ -53829,13 +54075,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
53829
54075
  addonId: null,
53830
54076
  access: "create"
53831
54077
  },
53832
- "localNetwork.setAllowedAddresses": {
54078
+ "localNetwork.setAddressRole": {
53833
54079
  capName: "local-network",
53834
54080
  capScope: "system",
53835
54081
  addonId: null,
53836
54082
  access: "create"
53837
54083
  },
53838
- "localNetwork.setHubAddress": {
54084
+ "localNetwork.setAllowedAddresses": {
53839
54085
  capName: "local-network",
53840
54086
  capScope: "system",
53841
54087
  addonId: null,
@@ -58089,6 +58335,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
58089
58335
  addonId: null,
58090
58336
  access: "create"
58091
58337
  },
58338
+ "webrtcSession.applyDownlinkProbe": {
58339
+ capName: "webrtc-session",
58340
+ capScope: "device",
58341
+ addonId: null,
58342
+ access: "create"
58343
+ },
58092
58344
  "webrtcSession.closeSession": {
58093
58345
  capName: "webrtc-session",
58094
58346
  capScope: "device",
@@ -65363,6 +65615,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
65363
65615
  form: "single",
65364
65616
  optional: false
65365
65617
  }],
65618
+ "webrtcSession.applyDownlinkProbe": [{
65619
+ name: "deviceId",
65620
+ form: "single",
65621
+ optional: false
65622
+ }],
65366
65623
  "webrtcSession.closeSession": [{
65367
65624
  name: "deviceId",
65368
65625
  form: "single",
@@ -66403,8 +66660,11 @@ function createSystemProxy(api) {
66403
66660
  setNotificationEndpoint: (input) => dispatch("localNetwork", "setNotificationEndpoint", "mutation", input),
66404
66661
  getViewerEndpoints: (input) => dispatch("localNetwork", "getViewerEndpoints", "query", input),
66405
66662
  setViewerEndpoints: (input) => dispatch("localNetwork", "setViewerEndpoints", "mutation", input),
66663
+ getAddresses: (input) => dispatch("localNetwork", "getAddresses", "query", input),
66664
+ addCustomAddress: (input) => dispatch("localNetwork", "addCustomAddress", "mutation", input),
66665
+ removeCustomAddress: (input) => dispatch("localNetwork", "removeCustomAddress", "mutation", input),
66666
+ setAddressRole: (input) => dispatch("localNetwork", "setAddressRole", "mutation", input),
66406
66667
  getHubAddresses: (input) => dispatch("localNetwork", "getHubAddresses", "query", input),
66407
- setHubAddress: (input) => dispatch("localNetwork", "setHubAddress", "mutation", input),
66408
66668
  getAllowedAddresses: (input) => dispatch("localNetwork", "getAllowedAddresses", "query", input),
66409
66669
  setAllowedAddresses: (input) => dispatch("localNetwork", "setAllowedAddresses", "mutation", input),
66410
66670
  resetAllowlistToBestMatch: (input) => dispatch("localNetwork", "resetAllowlistToBestMatch", "mutation", input),
@@ -68657,17 +68917,23 @@ var NC_ALARM_DELAY_FIELDS = [
68657
68917
  * all read this one constant.
68658
68918
  */
68659
68919
  var NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC = 30;
68660
- var NC_ALARM_NOTIFY_FIELDS = ["notifyDuringEntryDelay", "notifySensorRewatched"];
68920
+ var NC_ALARM_NOTIFY_FIELDS = [
68921
+ "notifyDuringEntryDelay",
68922
+ "notifySensorRewatched",
68923
+ "notifySensorIgnored"
68924
+ ];
68661
68925
  /**
68662
68926
  * The switches `mode` runs on. There is no global value: absent is OFF (operator decision
68663
68927
  * 2026-10-06 — the entry-delay notification is the only one by default, and "watched again"
68664
- * is not sent). An answer from an older hub carries no switch and reads as off.
68928
+ * is not sent; 2026-10-07 — nor is "ignored", D735). An answer from an older hub carries no
68929
+ * switch and reads as off.
68665
68930
  */
68666
68931
  function resolveModeNotifyPolicy(settings, mode) {
68667
68932
  const entry = (settings.modeDelays ?? []).find((e) => e.mode === mode);
68668
68933
  return {
68669
68934
  notifyDuringEntryDelay: entry?.notifyDuringEntryDelay === true,
68670
- notifySensorRewatched: entry?.notifySensorRewatched === true
68935
+ notifySensorRewatched: entry?.notifySensorRewatched === true,
68936
+ notifySensorIgnored: entry?.notifySensorIgnored === true
68671
68937
  };
68672
68938
  }
68673
68939
  /**
@@ -68759,6 +69025,7 @@ function applyModeDelaysEdits(current, edits) {
68759
69025
  const schedule = editedSchedule(base.schedule, edit.schedule);
68760
69026
  const duringEntry = editedFlag(base.notifyDuringEntryDelay, edit.notifyDuringEntryDelay);
68761
69027
  const rewatched = editedFlag(base.notifySensorRewatched, edit.notifySensorRewatched);
69028
+ const ignored = editedFlag(base.notifySensorIgnored, edit.notifySensorIgnored);
68762
69029
  if (exit !== void 0) next.exitDelaySec = exit;
68763
69030
  if (entry !== void 0) next.entryDelaySec = entry;
68764
69031
  if (triggered !== void 0) next.triggeredDurationSec = triggered;
@@ -68766,10 +69033,11 @@ function applyModeDelaysEdits(current, edits) {
68766
69033
  if (schedule !== void 0) next.schedule = schedule;
68767
69034
  if (duringEntry !== void 0) next.notifyDuringEntryDelay = duringEntry;
68768
69035
  if (rewatched !== void 0) next.notifySensorRewatched = rewatched;
69036
+ if (ignored !== void 0) next.notifySensorIgnored = ignored;
68769
69037
  if (at >= 0) out[at] = next;
68770
69038
  else out.push(next);
68771
69039
  }
68772
- 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);
69040
+ 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 || e.notifySensorIgnored !== void 0);
68773
69041
  }
68774
69042
  /**
68775
69043
  * Put ONE device on, or take it off, ONE mode's device-id list. A no-op answers
@@ -73591,6 +73859,7 @@ exports.AUDIO_NOT_CLASSIFIED_REASONS = AUDIO_NOT_CLASSIFIED_REASONS;
73591
73859
  exports.AUDIO_PRESETS = require_composition.AUDIO_PRESETS;
73592
73860
  exports.AUDIO_SCALE_EPOCH_ISO = AUDIO_SCALE_EPOCH_ISO;
73593
73861
  exports.AUDIO_SCALE_EPOCH_MS = AUDIO_SCALE_EPOCH_MS;
73862
+ exports.AUTOMATIC_HUB_ADDRESS_FLAGS = AUTOMATIC_HUB_ADDRESS_FLAGS;
73594
73863
  exports.AccessoriesStatusSchema = AccessoriesStatusSchema;
73595
73864
  exports.AccessoryKind = AccessoryKind;
73596
73865
  exports.AddBrokerInputSchema = AddBrokerInputSchema;
@@ -74158,12 +74427,14 @@ exports.HAP_KEYFRAME_INTERVAL_SEC = HAP_KEYFRAME_INTERVAL_SEC;
74158
74427
  exports.HF_BASE_URL = HF_BASE_URL;
74159
74428
  exports.HF_REPO = HF_REPO;
74160
74429
  exports.HOURS_PER_DAY = HOURS_PER_DAY;
74430
+ exports.HUB_ADDRESS_ROLES = HUB_ADDRESS_ROLES;
74161
74431
  exports.HUB_VIEWER_MOUNT = HUB_VIEWER_MOUNT;
74162
74432
  exports.HWACCEL_OPTIONS = HWACCEL_OPTIONS;
74163
74433
  exports.HealthStatusSchema = HealthStatusSchema;
74164
74434
  exports.HfModelResolutionSchema = HfModelResolutionSchema;
74165
74435
  exports.HistoryPointSchema = HistoryPointSchema;
74166
74436
  exports.HistoryResolutionEnum = HistoryResolutionEnum;
74437
+ exports.HubAddressRoleSchema = HubAddressRoleSchema;
74167
74438
  exports.HumidifierStatusSchema = HumidifierStatusSchema;
74168
74439
  exports.HumiditySensorStatusSchema = HumiditySensorStatusSchema;
74169
74440
  exports.HvacModeSchema = HvacModeSchema;
@@ -74638,6 +74909,7 @@ exports.PIPELINE_OWNER_CAPABILITY_NAMES = PIPELINE_OWNER_CAPABILITY_NAMES;
74638
74909
  exports.PRIVACY_MASK_CAP_NAME = PRIVACY_MASK_CAP_NAME;
74639
74910
  exports.PROVIDER_KIND_CAP_NAMES = PROVIDER_KIND_CAP_NAMES;
74640
74911
  exports.PROVIDER_PIN_FIELD = require_sleep.PROVIDER_PIN_FIELD;
74912
+ exports.PUBLIC_ENDPOINT_HTTP_REASON = PUBLIC_ENDPOINT_HTTP_REASON;
74641
74913
  exports.PYTHON_SCRIPT = PYTHON_SCRIPT;
74642
74914
  exports.PackageUpdateSchema = PackageUpdateSchema;
74643
74915
  exports.PackageVersionInfoSchema = PackageVersionInfoSchema;
@@ -74823,6 +75095,7 @@ exports.SCOPE_PRESETS = SCOPE_PRESETS;
74823
75095
  exports.SENSOR_FEATURES = SENSOR_FEATURES;
74824
75096
  exports.SENSOR_MAP = SENSOR_MAP;
74825
75097
  exports.SHARE_VIEW_KINDS = require_sleep.SHARE_VIEW_KINDS;
75098
+ exports.SINGLE_HUB_ADDRESS_ROLES = SINGLE_HUB_ADDRESS_ROLES;
74826
75099
  exports.SOURCE_CAPS = require_composition.SOURCE_CAPS;
74827
75100
  exports.SOURCE_CAP_ACTIVE_FIELD = require_composition.SOURCE_CAP_ACTIVE_FIELD;
74828
75101
  exports.SOURCE_CAP_CHANGED_AT_FIELD = require_composition.SOURCE_CAP_CHANGED_AT_FIELD;
@@ -75170,6 +75443,7 @@ exports.canConvertUnit = canConvertUnit;
75170
75443
  exports.canonicalEgressPlan = canonicalEgressPlan;
75171
75444
  exports.carbonMonoxideCapability = carbonMonoxideCapability;
75172
75445
  exports.cellsToRects = cellsToRects;
75446
+ exports.checkRoleAddress = checkRoleAddress;
75173
75447
  exports.classifyBearerPrincipal = classifyBearerPrincipal;
75174
75448
  exports.classifyStream = classifyStream;
75175
75449
  exports.classifyStreams = classifyStreams;
@@ -75404,6 +75678,7 @@ exports.isBatteryPresenceFault = isBatteryPresenceFault;
75404
75678
  exports.isClusterScopedStep = isClusterScopedStep;
75405
75679
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
75406
75680
  exports.isDefaultAlarmProfileId = isDefaultAlarmProfileId;
75681
+ exports.isDefaultAppConnectionEndpoint = isDefaultAppConnectionEndpoint;
75407
75682
  exports.isDeployableToAgent = isDeployableToAgent;
75408
75683
  exports.isDetectionMacroClass = isDetectionMacroClass;
75409
75684
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
@@ -75433,6 +75708,7 @@ exports.isRootSuppliableStepId = isRootSuppliableStepId;
75433
75708
  exports.isSameAddonId = isSameAddonId;
75434
75709
  exports.isScheduleActive = isScheduleActive;
75435
75710
  exports.isSecretConfigField = isSecretConfigField;
75711
+ exports.isSingleHubAddressRole = isSingleHubAddressRole;
75436
75712
  exports.isSoftwareDecode = require_composition.isSoftwareDecode;
75437
75713
  exports.isSourceCap = require_composition.isSourceCap;
75438
75714
  exports.isStateLabelKey = isStateLabelKey;
@@ -75549,8 +75825,6 @@ exports.pickAppLinkHub = pickAppLinkHub;
75549
75825
  exports.pickClusterStepModels = pickClusterStepModels;
75550
75826
  exports.pickClusterStepSettings = pickClusterStepSettings;
75551
75827
  exports.pickDetailCropConvention = pickDetailCropConvention;
75552
- exports.pickHubBaseUrl = pickHubBaseUrl;
75553
- exports.pickHubHostname = pickHubHostname;
75554
75828
  exports.pickInvalidClusterStepModels = pickInvalidClusterStepModels;
75555
75829
  exports.pickNativeLeaseOverride = pickNativeLeaseOverride;
75556
75830
  exports.pickPreferredRtspEntry = pickPreferredRtspEntry;
@@ -75611,6 +75885,7 @@ exports.resetPoolBaseline = resetPoolBaseline;
75611
75885
  exports.resolveAddonExecution = resolveAddonExecution;
75612
75886
  exports.resolveAddonGroup = resolveAddonGroup;
75613
75887
  exports.resolveAddonPlacement = resolveAddonPlacement;
75888
+ exports.resolveAppConnections = resolveAppConnections;
75614
75889
  exports.resolveBucketMs = resolveBucketMs;
75615
75890
  exports.resolveCapMount = require_sleep.resolveCapMount;
75616
75891
  exports.resolveClipSearchMinScore = resolveClipSearchMinScore;
@@ -75624,15 +75899,18 @@ exports.resolveDeviceProfile = resolveDeviceProfile;
75624
75899
  exports.resolveEgressDecodeHwAccel = resolveEgressDecodeHwAccel;
75625
75900
  exports.resolveExportProfiles = resolveExportProfiles;
75626
75901
  exports.resolveFormat = resolveFormat;
75627
- exports.resolveHubAddress = resolveHubAddress;
75902
+ exports.resolveHubAddressRoles = resolveHubAddressRoles;
75628
75903
  exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
75904
+ exports.resolveInternalAddress = resolveInternalAddress;
75629
75905
  exports.resolveLocationIcon = resolveLocationIcon;
75630
75906
  exports.resolveLocationMode = resolveLocationMode;
75631
75907
  exports.resolveMethodAuth = resolveMethodAuth;
75632
75908
  exports.resolveModeDelays = resolveModeDelays;
75633
75909
  exports.resolveModeNotifyPolicy = resolveModeNotifyPolicy;
75634
75910
  exports.resolveMutate = resolveMutate;
75911
+ exports.resolveNotificationTapAddress = resolveNotificationTapAddress;
75635
75912
  exports.resolvePoolMemoryPolicy = resolvePoolMemoryPolicy;
75913
+ exports.resolvePublicEndpoint = resolvePublicEndpoint;
75636
75914
  exports.resolveRecordingProfiles = resolveRecordingProfiles;
75637
75915
  exports.resolveRootSupply = resolveRootSupply;
75638
75916
  exports.resolveRunnerId = resolveRunnerId;
@@ -75671,6 +75949,7 @@ exports.scoreRuntimes = scoreRuntimes;
75671
75949
  exports.scriptRunnerCapability = scriptRunnerCapability;
75672
75950
  exports.searchDisplayIcons = searchDisplayIcons;
75673
75951
  exports.secureActuationFor = secureActuationFor;
75952
+ exports.selectAppConnectionEndpoints = selectAppConnectionEndpoints;
75674
75953
  exports.selectAssignedProfileSlots = require_sleep.selectAssignedProfileSlots;
75675
75954
  exports.sendTimesForEditor = sendTimesForEditor;
75676
75955
  exports.sendTimesOfSchedule = sendTimesOfSchedule;
@@ -75763,8 +76042,8 @@ exports.userManagementCapability = userManagementCapability;
75763
76042
  exports.userPasskeysCapability = userPasskeysCapability;
75764
76043
  exports.vacuumControlCapability = vacuumControlCapability;
75765
76044
  exports.validateComposition = validateComposition;
76045
+ exports.validateCustomAddress = validateCustomAddress;
75766
76046
  exports.validateExpressionSource = require_composition.validateExpressionSource;
75767
- exports.validateHubAddressOverride = validateHubAddressOverride;
75768
76047
  exports.validateRecipeBounds = validateRecipeBounds;
75769
76048
  exports.valveCapability = valveCapability;
75770
76049
  exports.vectorDimFromBase64 = vectorDimFromBase64;