@camstack/addon-post-analysis 1.2.337 → 1.2.339
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/{clip-model-registry-CD0UYoAb.mjs → clip-model-registry-BKVUtnRA.mjs} +299 -140
- package/dist/{clip-model-registry-CML1YOOa.js → clip-model-registry-DlJT7zn4.js} +299 -140
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DGaCYQ8R.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CF8ypDMO.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BImFB0o5.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BRmdbHeK.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-Dlhtn22B.mjs → hostInit-DYVlvRBm.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +494 -429
- package/dist/pipeline-analytics/index.mjs +494 -429
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D7NMYqUM.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-bIOxWlPv.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, $n as encodeVectorBase64, $r as systemEventDeliveryProblems, $t as RECORDING_SOURCE_CAMSTACK_ADDON, A as EMBEDDING_MODEL_FAILED, Ai as literal, An as applyModeDelaysEdits, Ar as plateGalleryCapability, At as NcLegacyRuleActionSequenceSchema, B as LabelAttributionSchema, Bi as union, Bn as coreBlockIdFromAddonId, Br as rootSupplyServes, Bt as NcSnoozeInputSchema, C as CoverStateSchema, Cn as TrackSideSchema, Cr as linkedStepOf, Ct as NcAlarmConfigSchema, D as DETECTION_PIPELINE_CAP_NAME, Di as array, Dn as alarmDeviceSecureOnArm, Dr as occupancyScope, Dt as NcAlarmSettingsPatchSchema, E as DETECTION_MACRO_CLASSES, Ei as _enum, En as addonWidgetsSourceCapability, Er as notificationRulesCapability, Et as NcAlarmSensorsSchema, F as EVENT_OWNER_TYPES, Fi as record, Fn as audioModeOf, Fr as resolveClipSearchMinScore, Ft as NcRuleSchema, G as MAX_BIRTH_DECISION_RECORDS, Gn as deriveRecordingMode, Gr as ruleKindOf, Gt as NcTaxonomySchema, H as ListTrackRelationsResultSchema, Hi as EventCategory, Hn as createAlarmProfile, Hr as ruleBelongsToAlarm$1, Ht as NcSnoozeSuppressedSchema, I as EXPORT_DENSE_MAX_RANGES, Ii as strictObject, In as buildEventKindDescriptor, Ir as resolveLocationMode, It as NcRuleTargetSchema, J as MAX_TRACK_RELATIONS, Jr as sensorRaisedOf, Jt as NcTemplateVarDescriptorSchema, K as MAX_BIRTH_LATENCY_PROXY_MS, Kn as describeSystemEventDeliveryProblem, Kr as sceneMonitorCapability, Kt as NcTemplatePreviewInputSchema, L as FIRST_LEVEL_MACRO_CLASSES, Li as string, Ln as buildViewerLink, Lr as resolveModeDelays, Lt as NcRuleUpdateInputSchema, M as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Mi as number, Mn as applySecureOnArmEdit, Mr as readMotionTriggeredBy, Mt as NcRuleActionsSchema, N as EVENT_DENSITY_BATCH_MAX, Ni as object, Nn as assertTimelapseCadences, Nr as readTimelapseGeneratedAt, Nt as NcRuleInputSchema, O as DeclaredDevices, Oi as boolean, On as alarmPanelCapability, Ot as NcConditionDescriptorSchema, P as EVENT_KIND_BY_CAP, Pi as partialRecord, Pn as audioMetricsCapability, Pr as renameAlarmProfile, Pt as NcRulePatchSchema, Q as NC_ALARM_DEFAULT_PROFILE_ID, Qr as syncActiveAlarmProfileSettings, Qt as RECORDING_EXPORT_MAX_READ_BYTES, R as FULL_IMAGE_BBOX, Ri as templateLiteral, Rn as cloneAlarmSettings, Rr as resolveModeNotifyPolicy, Rt as NcScheduleSchema, S as ClearTrackRelationResultSchema, Si as nodePin, Sn as TrackRelationSchema, Sr as kebabToCamel, St as NC_TEMPLATE_VARS, T as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ti as ZodObject, Tn as activateAlarmProfile, Tr as memoizePinnedFfmpeg, Tt as NcAlarmSensorsInputSchema, U as LockStateSchema, Un as deleteAlarmProfile, Ur as ruleDeviceCompatCapQueries, Ut as NcStateSummarySectionIdSchema, V as ListTrackRelationsInputSchema, Vi as unknown, Vn as cosineSimilarity$1, Vr as ruleAutoTriggersAlarm, Vt as NcSnoozeSchema, W as MAX_ARCHIVED_DEBUG_NOTES, Wn as derivePostTriggeredAction, Wr as ruleGroupingApplies, Wt as NcSystemEventKindSchema, X as MediaFileKindEnum, Xn as embeddingRefusalOf, Xr as storageOccupancyCapability, Xt as OpsLogEntrySchema, Y as MOTION_CLOSE_AFTER_MS, Yr as servedTemplateVars, Yt as OPS_LOG_DEFAULT_LIMIT, Z as MotionTriggeredBySchema, Zr as subKindsOf, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as createEvent, _n as TimelapseRuleInputSchema, _r as isRootSuppliableStepId, _t as NC_SENSOR_TRANSITION_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as templateVarsFor, an as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ar as gifDeriveSpeed, at as NC_AUDIO_CONFIRM_HITS_DEFAULT, b as COCO_TO_MACRO, bi as hydrateSchema, bn as TrackPuritySchema, br as isSystemEventSummaryRule, bt as NC_TANK_SYSTEM_EVENT_KINDS, c as resolveClusterModelPin, ci as zoneAnalyticsCapability, cn as SCENE_DEFAULT_UNCOVERED_POLICY, cr as isAppLinkBase, ct as NC_CLIP_PRE_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as evaluateSensorEdge, dn as SceneMonitorSchema, dr as isDetectionMacroClass, dt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, ei as systemEventFamilyProblems, en as RebuildObjectEmbeddingsRefusalSchema, er as ensureAlarmProfiles, et as NC_ALARM_DELAY_FIELDS, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as isSourceCap, fn as SetTrackPurityInputSchema, fr as isEventKindLabelKey, ft as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, g as ArchivedDebugNoteSchema, gi as CamProfileSchema, gn as TRACK_RELATIONS_DEFAULT_LIMIT, gr as isRetiredClearKey, gt as NC_GROUPING_MIN_SEC, h as AlarmArmModeSchema, hi as BaseAddon, hn as TIMELAPSE_DENSE_FLOOR_SEC, hr as isMediaOwnerType, ht as NC_GROUPING_MAX_SEC, ii as templateCarriesSummary, in as SCENE_DEFAULT_ANCHOR_THRESHOLD, ir as failureContributionCapability, it as NC_ALARM_TRIGGERED_MAX_SEC, j as EMBEDDING_REQUIREMENTS_FAILED, ji as looseObject, jn as applyNonBlockingEdit, jr as readDeviceStateFrom, jt as NcRuleActionSchema, k as DetectionSourceSchema, ki as discriminatedUnion, kn as applyBlockingEdit, kr as pipelineAnalyticsCapability, kt as NcConditionsSchema, l as ALL_CAPABILITY_DEFINITIONS, li as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, ln as SCENE_DIVERGED, lr as isAudioClassName, lt as NC_CLIP_RETENTION_MAX_SEC, m as AUDIO_MACRO_LABELS, mi as errMsg, mn as SetTrackRelationInputSchema, mr as isKnownTemplateVar, mt as NC_GROUPING_DEFAULT_SEC, ni as systemEventSummaryScheduleOf, nn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, nr as evictionPolicyOfLocation, nt as NC_ALARM_NOTIFY_FIELDS, o as FACE_EMBEDDING_STEP_ID, oi as vectorDimFromBase64, on as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, or as gifFromMp4Args, ot as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as DeviceType, pn as SetTrackPurityResultSchema, pr as isEventOwnerType, pt as NC_DEFAULT_SNOOZE_MINUTES, q as MAX_TRACK_PURITIES, qn as describeSystemEventFamilyProblem, qr as secureActuationFor, qt as NcTemplatePreviewSchema, ri as systemEventSummarySplitOf, rn as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, rr as faceGalleryCapability, rt as NC_ALARM_SYSTEM_EVENT_KINDS, s as readClusterModelPinStrict, si as viewerLinkBaseOf, sn as SCENE_DEFAULT_QUIET_SECONDS, sr as isAlarmControllableCap, st as NC_CLIP_POST_ROLL_DEFAULT_SEC, t as BUILTIN_CLIP_MODELS, ti as systemEventFilterApplies, tn as RetrainStatusSchema, tr as eventKindLabel, tt as NC_ALARM_DELAY_MAX_SEC, u as APPEARANCE_BIN_COUNT, ui as canonicalHash, un as SCENE_RESET_RECAPTURES, ur as isDefaultAlarmProfileId, ut as NC_CONDITION_CATALOG, v as BaseDevice, vi as customAction, vn as TimelapseRulePatchSchema, vr as isScheduleActive, vt as NC_SNOOZE_MAX_MINUTES, w as DEFAULT_EVENT_COLOR, wi as sleep$1, wn as TrackSourceSchema, wr as mayWriteToLocation, wt as NcAlarmPostTriggeredActionSchema, x as ClearTrackRelationInputSchema, xi as isDeviceScopedCap, xn as TrackRelationKindSchema, xr as isVectorIndexDimConflict, xt as NC_TAXONOMY, y as BirthDecisionRecordSchema, yi as defineCustomActions, yn as TimelapseRuleSchema, yr as isSystemDelivery, yt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, z as FailureCounters, zi as tuple, zn as consumableLowPercentOf, zt as NcSharedSequenceSchema } from "../clip-model-registry-
|
|
1
|
+
import { $ as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, $n as encodeVectorBase64, $r as systemEventDeliveryProblems, $t as RECORDING_SOURCE_CAMSTACK_ADDON, A as EMBEDDING_MODEL_FAILED, Ai as literal, An as applyModeDelaysEdits, Ar as plateGalleryCapability, At as NcLegacyRuleActionSequenceSchema, B as LabelAttributionSchema, Bi as union, Bn as coreBlockIdFromAddonId, Br as rootSupplyServes, Bt as NcSnoozeInputSchema, C as CoverStateSchema, Cn as TrackSideSchema, Cr as linkedStepOf, Ct as NcAlarmConfigSchema, D as DETECTION_PIPELINE_CAP_NAME, Di as array, Dn as alarmDeviceSecureOnArm, Dr as occupancyScope, Dt as NcAlarmSettingsPatchSchema, E as DETECTION_MACRO_CLASSES, Ei as _enum, En as addonWidgetsSourceCapability, Er as notificationRulesCapability, Et as NcAlarmSensorsSchema, F as EVENT_OWNER_TYPES, Fi as record, Fn as audioModeOf, Fr as resolveClipSearchMinScore, Ft as NcRuleSchema, G as MAX_BIRTH_DECISION_RECORDS, Gn as deriveRecordingMode, Gr as ruleKindOf, Gt as NcTaxonomySchema, H as ListTrackRelationsResultSchema, Hi as EventCategory, Hn as createAlarmProfile, Hr as ruleBelongsToAlarm$1, Ht as NcSnoozeSuppressedSchema, I as EXPORT_DENSE_MAX_RANGES, Ii as strictObject, In as buildEventKindDescriptor, Ir as resolveLocationMode, It as NcRuleTargetSchema, J as MAX_TRACK_RELATIONS, Jr as sensorRaisedOf, Jt as NcTemplateVarDescriptorSchema, K as MAX_BIRTH_LATENCY_PROXY_MS, Kn as describeSystemEventDeliveryProblem, Kr as sceneMonitorCapability, Kt as NcTemplatePreviewInputSchema, L as FIRST_LEVEL_MACRO_CLASSES, Li as string, Ln as buildViewerLink, Lr as resolveModeDelays, Lt as NcRuleUpdateInputSchema, M as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Mi as number, Mn as applySecureOnArmEdit, Mr as readMotionTriggeredBy, Mt as NcRuleActionsSchema, N as EVENT_DENSITY_BATCH_MAX, Ni as object, Nn as assertTimelapseCadences, Nr as readTimelapseGeneratedAt, Nt as NcRuleInputSchema, O as DeclaredDevices, Oi as boolean, On as alarmPanelCapability, Ot as NcConditionDescriptorSchema, P as EVENT_KIND_BY_CAP, Pi as partialRecord, Pn as audioMetricsCapability, Pr as renameAlarmProfile, Pt as NcRulePatchSchema, Q as NC_ALARM_DEFAULT_PROFILE_ID, Qr as syncActiveAlarmProfileSettings, Qt as RECORDING_EXPORT_MAX_READ_BYTES, R as FULL_IMAGE_BBOX, Ri as templateLiteral, Rn as cloneAlarmSettings, Rr as resolveModeNotifyPolicy, Rt as NcScheduleSchema, S as ClearTrackRelationResultSchema, Si as nodePin, Sn as TrackRelationSchema, Sr as kebabToCamel, St as NC_TEMPLATE_VARS, T as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ti as ZodObject, Tn as activateAlarmProfile, Tr as memoizePinnedFfmpeg, Tt as NcAlarmSensorsInputSchema, U as LockStateSchema, Un as deleteAlarmProfile, Ur as ruleDeviceCompatCapQueries, Ut as NcStateSummarySectionIdSchema, V as ListTrackRelationsInputSchema, Vi as unknown, Vn as cosineSimilarity$1, Vr as ruleAutoTriggersAlarm, Vt as NcSnoozeSchema, W as MAX_ARCHIVED_DEBUG_NOTES, Wn as derivePostTriggeredAction, Wr as ruleGroupingApplies, Wt as NcSystemEventKindSchema, X as MediaFileKindEnum, Xn as embeddingRefusalOf, Xr as storageOccupancyCapability, Xt as OpsLogEntrySchema, Y as MOTION_CLOSE_AFTER_MS, Yr as servedTemplateVars, Yt as OPS_LOG_DEFAULT_LIMIT, Z as MotionTriggeredBySchema, Zr as subKindsOf, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as createEvent, _n as TimelapseRuleInputSchema, _r as isRootSuppliableStepId, _t as NC_SENSOR_TRANSITION_KINDS, a as CLIP_EMBEDDING_STEP_ID, ai as templateVarsFor, an as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ar as gifDeriveSpeed, at as NC_AUDIO_CONFIRM_HITS_DEFAULT, b as COCO_TO_MACRO, bi as hydrateSchema, bn as TrackPuritySchema, br as isSystemEventSummaryRule, bt as NC_TANK_SYSTEM_EVENT_KINDS, c as resolveClusterModelPin, ci as zoneAnalyticsCapability, cn as SCENE_DEFAULT_UNCOVERED_POLICY, cr as isAppLinkBase, ct as NC_CLIP_PRE_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as evaluateSensorEdge, dn as SceneMonitorSchema, dr as isDetectionMacroClass, dt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, ei as systemEventFamilyProblems, en as RebuildObjectEmbeddingsRefusalSchema, er as ensureAlarmProfiles, et as NC_ALARM_DELAY_FIELDS, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as isSourceCap, fn as SetTrackPurityInputSchema, fr as isEventKindLabelKey, ft as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, g as ArchivedDebugNoteSchema, gi as CamProfileSchema, gn as TRACK_RELATIONS_DEFAULT_LIMIT, gr as isRetiredClearKey, gt as NC_GROUPING_MIN_SEC, h as AlarmArmModeSchema, hi as BaseAddon, hn as TIMELAPSE_DENSE_FLOOR_SEC, hr as isMediaOwnerType, ht as NC_GROUPING_MAX_SEC, ii as templateCarriesSummary, in as SCENE_DEFAULT_ANCHOR_THRESHOLD, ir as failureContributionCapability, it as NC_ALARM_TRIGGERED_MAX_SEC, j as EMBEDDING_REQUIREMENTS_FAILED, ji as looseObject, jn as applyNonBlockingEdit, jr as readDeviceStateFrom, jt as NcRuleActionSchema, k as DetectionSourceSchema, ki as discriminatedUnion, kn as applyBlockingEdit, kr as pipelineAnalyticsCapability, kt as NcConditionsSchema, l as ALL_CAPABILITY_DEFINITIONS, li as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, ln as SCENE_DIVERGED, lr as isAudioClassName, lt as NC_CLIP_RETENTION_MAX_SEC, m as AUDIO_MACRO_LABELS, mi as errMsg, mn as SetTrackRelationInputSchema, mr as isKnownTemplateVar, mt as NC_GROUPING_DEFAULT_SEC, ni as systemEventSummaryScheduleOf, nn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, nr as evictionPolicyOfLocation, nt as NC_ALARM_NOTIFY_FIELDS, o as FACE_EMBEDDING_STEP_ID, oi as vectorDimFromBase64, on as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, or as gifFromMp4Args, ot as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as DeviceType, pn as SetTrackPurityResultSchema, pr as isEventOwnerType, pt as NC_DEFAULT_SNOOZE_MINUTES, q as MAX_TRACK_PURITIES, qn as describeSystemEventFamilyProblem, qr as secureActuationFor, qt as NcTemplatePreviewSchema, ri as systemEventSummarySplitOf, rn as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, rr as faceGalleryCapability, rt as NC_ALARM_SYSTEM_EVENT_KINDS, s as readClusterModelPinStrict, si as viewerLinkBaseOf, sn as SCENE_DEFAULT_QUIET_SECONDS, sr as isAlarmControllableCap, st as NC_CLIP_POST_ROLL_DEFAULT_SEC, t as BUILTIN_CLIP_MODELS, ti as systemEventFilterApplies, tn as RetrainStatusSchema, tr as eventKindLabel, tt as NC_ALARM_DELAY_MAX_SEC, u as APPEARANCE_BIN_COUNT, ui as canonicalHash, un as SCENE_RESET_RECAPTURES, ur as isDefaultAlarmProfileId, ut as NC_CONDITION_CATALOG, v as BaseDevice, vi as customAction, vn as TimelapseRulePatchSchema, vr as isScheduleActive, vt as NC_SNOOZE_MAX_MINUTES, w as DEFAULT_EVENT_COLOR, wi as sleep$1, wn as TrackSourceSchema, wr as mayWriteToLocation, wt as NcAlarmPostTriggeredActionSchema, x as ClearTrackRelationInputSchema, xi as isDeviceScopedCap, xn as TrackRelationKindSchema, xr as isVectorIndexDimConflict, xt as NC_TAXONOMY, y as BirthDecisionRecordSchema, yi as defineCustomActions, yn as TimelapseRuleSchema, yr as isSystemDelivery, yt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, z as FailureCounters, zi as tuple, zn as consumableLowPercentOf, zt as NcSharedSequenceSchema } from "../clip-model-registry-BKVUtnRA.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -1401,6 +1401,12 @@ async function readAlarmOpenings(ids, loadRuntime, logger) {
|
|
|
1401
1401
|
function isPhase(value) {
|
|
1402
1402
|
return value === "open" || value === "closed";
|
|
1403
1403
|
}
|
|
1404
|
+
/**
|
|
1405
|
+
* A change time that cannot be a move: `0` is a placeholder and `1` is the
|
|
1406
|
+
* "known value, unknown change time" convention providers stamp on a replay or
|
|
1407
|
+
* a recovery (`HM_SEEDED_CHANGED_AT`, `HA_SEEDED_CHANGED_AT`).
|
|
1408
|
+
*/
|
|
1409
|
+
var NO_CHANGE_TIME = 1;
|
|
1404
1410
|
var NcAlarmExclusions = class {
|
|
1405
1411
|
entries = [];
|
|
1406
1412
|
/** Replace the whole set — one arm, one set of exclusions. */
|
|
@@ -1409,7 +1415,9 @@ var NcAlarmExclusions = class {
|
|
|
1409
1415
|
deviceId,
|
|
1410
1416
|
phase: "open",
|
|
1411
1417
|
at,
|
|
1412
|
-
announced: false
|
|
1418
|
+
announced: false,
|
|
1419
|
+
moved: false,
|
|
1420
|
+
lastMoveAt: 0
|
|
1413
1421
|
}));
|
|
1414
1422
|
}
|
|
1415
1423
|
/** Every excluded sensor, in the order it was excluded. */
|
|
@@ -1433,24 +1441,53 @@ var NcAlarmExclusions = class {
|
|
|
1433
1441
|
* An unreadable state changes nothing (D49): a failed read must never lift an
|
|
1434
1442
|
* exclusion the operator asked for, and must never swallow the close that
|
|
1435
1443
|
* would have.
|
|
1444
|
+
*
|
|
1445
|
+
* `changedAt` is the reading's OWN change time, when the caller has one (a
|
|
1446
|
+
* sensor event's slice; the reconcile tick has none). It decides only whether
|
|
1447
|
+
* an open in phase `open` really MOVED ({@link takeMoved}) — never a lift.
|
|
1436
1448
|
*/
|
|
1437
|
-
observe(deviceId, reading, _at) {
|
|
1449
|
+
observe(deviceId, reading, _at, changedAt) {
|
|
1438
1450
|
if (reading === void 0) return "unchanged";
|
|
1439
1451
|
const current = this.entries.find((e) => e.deviceId === deviceId);
|
|
1440
1452
|
if (current === void 0) return "unchanged";
|
|
1441
1453
|
if (reading === "secure") {
|
|
1442
|
-
if (current.phase === "closed") return
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1454
|
+
if (current.phase === "closed") return this.replace({
|
|
1455
|
+
...current,
|
|
1456
|
+
moved: false
|
|
1457
|
+
}, "unchanged");
|
|
1458
|
+
return this.replace({
|
|
1459
|
+
...current,
|
|
1460
|
+
phase: "closed",
|
|
1461
|
+
moved: false
|
|
1462
|
+
}, "closed");
|
|
1463
|
+
}
|
|
1464
|
+
if (current.phase === "open") {
|
|
1465
|
+
const fresh = changedAt !== void 0 && Number.isFinite(changedAt) && changedAt > Math.max(current.at, current.lastMoveAt, NO_CHANGE_TIME);
|
|
1466
|
+
const redelivered = current.moved && changedAt === current.lastMoveAt;
|
|
1467
|
+
return this.replace({
|
|
1468
|
+
...current,
|
|
1469
|
+
moved: fresh || redelivered,
|
|
1470
|
+
lastMoveAt: fresh ? changedAt : current.lastMoveAt
|
|
1471
|
+
}, "unchanged");
|
|
1448
1472
|
}
|
|
1449
|
-
if (current.phase === "open") return "unchanged";
|
|
1450
1473
|
this.entries = this.entries.filter((e) => e.deviceId !== deviceId);
|
|
1451
1474
|
return "rearmed";
|
|
1452
1475
|
}
|
|
1453
1476
|
/**
|
|
1477
|
+
* Did the last reading of this sensor really move it (D735)? Consumed: the
|
|
1478
|
+
* press it caused asks once, and a later press of the same reading is a
|
|
1479
|
+
* duplicate. False for a sensor that is not excluded.
|
|
1480
|
+
*/
|
|
1481
|
+
takeMoved(deviceId) {
|
|
1482
|
+
const current = this.entries.find((e) => e.deviceId === deviceId);
|
|
1483
|
+
if (current === void 0 || !current.moved) return false;
|
|
1484
|
+
this.replace({
|
|
1485
|
+
...current,
|
|
1486
|
+
moved: false
|
|
1487
|
+
}, "unchanged");
|
|
1488
|
+
return true;
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1454
1491
|
* Mark this sensor's ignored trigger as announced — true only the FIRST time
|
|
1455
1492
|
* in this arm (the caller then announces and persists), false for a repeat
|
|
1456
1493
|
* or for a sensor that is not excluded.
|
|
@@ -1473,6 +1510,11 @@ var NcAlarmExclusions = class {
|
|
|
1473
1510
|
this.entries = this.entries.filter((e) => e.deviceId !== deviceId);
|
|
1474
1511
|
return true;
|
|
1475
1512
|
}
|
|
1513
|
+
/** Swap one entry for its next version — the set is replaced, never mutated. */
|
|
1514
|
+
replace(next, change) {
|
|
1515
|
+
this.entries = this.entries.map((e) => e.deviceId === next.deviceId ? next : e);
|
|
1516
|
+
return change;
|
|
1517
|
+
}
|
|
1476
1518
|
/** The arm this belongs to is over. */
|
|
1477
1519
|
clear() {
|
|
1478
1520
|
this.entries = [];
|
|
@@ -1504,7 +1546,9 @@ var NcAlarmExclusions = class {
|
|
|
1504
1546
|
deviceId: item,
|
|
1505
1547
|
phase: "open",
|
|
1506
1548
|
at: 0,
|
|
1507
|
-
announced: false
|
|
1549
|
+
announced: false,
|
|
1550
|
+
moved: false,
|
|
1551
|
+
lastMoveAt: 0
|
|
1508
1552
|
});
|
|
1509
1553
|
continue;
|
|
1510
1554
|
}
|
|
@@ -1518,7 +1562,9 @@ var NcAlarmExclusions = class {
|
|
|
1518
1562
|
deviceId,
|
|
1519
1563
|
phase,
|
|
1520
1564
|
at,
|
|
1521
|
-
announced: rec["announced"] === true
|
|
1565
|
+
announced: rec["announced"] === true,
|
|
1566
|
+
moved: false,
|
|
1567
|
+
lastMoveAt: 0
|
|
1522
1568
|
});
|
|
1523
1569
|
}
|
|
1524
1570
|
this.entries = out;
|
|
@@ -1609,7 +1655,8 @@ function modeDelaysFromPersisted(raw) {
|
|
|
1609
1655
|
}
|
|
1610
1656
|
if (item.notifyDuringEntryDelay !== void 0) kept.notifyDuringEntryDelay = item.notifyDuringEntryDelay;
|
|
1611
1657
|
if (item.notifySensorRewatched !== void 0) kept.notifySensorRewatched = item.notifySensorRewatched;
|
|
1612
|
-
if (
|
|
1658
|
+
if (item.notifySensorIgnored !== void 0) kept.notifySensorIgnored = item.notifySensorIgnored;
|
|
1659
|
+
if (kept.notifyDuringEntryDelay !== void 0 || kept.notifySensorRewatched !== void 0 || kept.notifySensorIgnored !== void 0 || kept.exitDelaySec !== void 0 || kept.entryDelaySec !== void 0 || kept.triggeredDurationSec !== void 0 || kept.postTriggeredAction !== void 0 || kept.schedule !== void 0) entries.push(kept);
|
|
1613
1660
|
}
|
|
1614
1661
|
return {
|
|
1615
1662
|
entries,
|
|
@@ -1820,6 +1867,10 @@ function alarmPushDecision(phase, heard) {
|
|
|
1820
1867
|
switch (phase.kind) {
|
|
1821
1868
|
case "none":
|
|
1822
1869
|
case "triggers-now": return { verdict: { kind: "plain" } };
|
|
1870
|
+
case "excluded": return { verdict: {
|
|
1871
|
+
kind: "suppress",
|
|
1872
|
+
reason: "alarm-sensor-excluded"
|
|
1873
|
+
} };
|
|
1823
1874
|
case "triggered": return { verdict: { kind: "intrusion" } };
|
|
1824
1875
|
case "starts-entry-delay": return heard.pending ? { verdict: {
|
|
1825
1876
|
kind: "suppress",
|
|
@@ -1862,17 +1913,6 @@ function intrusionDeviceOf(ref) {
|
|
|
1862
1913
|
//#endregion
|
|
1863
1914
|
//#region src/notification-center/alarm/alarm-transitions.ts
|
|
1864
1915
|
/**
|
|
1865
|
-
* How long a note handed to {@link NcAlarmAttribution.note} stays valid.
|
|
1866
|
-
*
|
|
1867
|
-
* The note is placed IMMEDIATELY before the `alarm-panel.trigger` call it
|
|
1868
|
-
* describes, in the same process, so the real gap is milliseconds. The bound
|
|
1869
|
-
* exists only so a sequence that failed before reaching its trigger step — or a
|
|
1870
|
-
* `wait` that outlived the incident — cannot leave an attribution lying around
|
|
1871
|
-
* for an unrelated manual trigger to pick up half an hour later.
|
|
1872
|
-
* Mis-attributing an alarm would put somebody else's photograph on it.
|
|
1873
|
-
*/
|
|
1874
|
-
var TRIGGER_ATTRIBUTION_TTL_MS = 3e4;
|
|
1875
|
-
/**
|
|
1876
1916
|
* How many followers one incident keeps (D705). The mosaic tiles at most
|
|
1877
1917
|
* `NC_GROUPED_MOSAIC_MAX_TILES` (6) and the sentence counts every member, so this
|
|
1878
1918
|
* bounds RAM and the frozen row, not the picture: a camera that keeps matching for
|
|
@@ -1888,88 +1928,41 @@ function subjectKeyOf(ref) {
|
|
|
1888
1928
|
* WHO the panel's current countdown belongs to.
|
|
1889
1929
|
*
|
|
1890
1930
|
* Small and separate because its whole content is a LIFETIME, and a lifetime
|
|
1891
|
-
* is what goes wrong: the
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
1931
|
+
* is what goes wrong: the trigger and the siren are up to `entryDelaySec`
|
|
1932
|
+
* apart, and in between the operator may disarm, re-arm, or press trigger by
|
|
1933
|
+
* hand. Each of those must drop the attribution, or tonight's alarm arrives
|
|
1934
|
+
* carrying last night's photograph.
|
|
1935
|
+
*
|
|
1936
|
+
* It holds NO pending press. A rule press carries its source IN the call
|
|
1937
|
+
* (`NcAlarmPanelDevice.triggerFromRule`), synchronously, so there is nothing
|
|
1938
|
+
* to hand over and nothing to overwrite. The single "next trigger is this
|
|
1939
|
+
* rule's" slot this class used to hold was written before an awaited cap call
|
|
1940
|
+
* and read when the call landed; two matches 1 ms apart overwrote it, one press
|
|
1941
|
+
* landed with no source, skipped the exclusion check and sounded the siren off
|
|
1942
|
+
* an excluded contact (2026-10-07 02:32Z, D735).
|
|
1896
1943
|
*/
|
|
1897
1944
|
var NcAlarmAttribution = class {
|
|
1898
|
-
noted = null;
|
|
1899
1945
|
claimed = null;
|
|
1900
1946
|
/** The incident's other subjects (D705) — replaced, never mutated. */
|
|
1901
1947
|
followed = [];
|
|
1902
|
-
/** "The next trigger is this rule's." Consumed by the trigger that follows:
|
|
1903
|
-
* {@link claim} when it is accepted, {@link follow} when an entry delay
|
|
1904
|
-
* already running refused it (D705), {@link dropPending} when an excluded
|
|
1905
|
-
* sensor made it. */
|
|
1906
|
-
note(source, now) {
|
|
1907
|
-
this.noted = {
|
|
1908
|
-
source,
|
|
1909
|
-
at: now
|
|
1910
|
-
};
|
|
1911
|
-
}
|
|
1912
|
-
/**
|
|
1913
|
-
* The note that has not been claimed yet — WITHOUT consuming it.
|
|
1914
|
-
*
|
|
1915
|
-
* The panel has to know which SENSOR is about to set the alarm off before it
|
|
1916
|
-
* decides whether to accept the trigger at all (an excluded contact does
|
|
1917
|
-
* not), and asking must not spend the note: a trigger that is accepted still
|
|
1918
|
-
* needs it to build the combined notification. Expired reads as absent, on
|
|
1919
|
-
* the same bound {@link claim} uses.
|
|
1920
|
-
*/
|
|
1921
|
-
pending(now) {
|
|
1922
|
-
const noted = this.noted;
|
|
1923
|
-
if (noted === null) return null;
|
|
1924
|
-
if (now - noted.at > TRIGGER_ATTRIBUTION_TTL_MS) return null;
|
|
1925
|
-
return noted.source;
|
|
1926
|
-
}
|
|
1927
|
-
/** Throw the un-claimed note away — the trigger it described was REFUSED, and
|
|
1928
|
-
* a note left behind would be inherited by the next real one. */
|
|
1929
|
-
dropPending() {
|
|
1930
|
-
this.noted = null;
|
|
1931
|
-
}
|
|
1932
1948
|
/**
|
|
1933
|
-
* Called by
|
|
1934
|
-
*
|
|
1935
|
-
* rather than inheriting it.
|
|
1949
|
+
* Called by EVERY accepted trigger with the source that pressed it — `null`
|
|
1950
|
+
* for a manual or external press, so it clears whatever a rule left behind
|
|
1951
|
+
* rather than inheriting it. A new countdown is a new incident: nothing the
|
|
1952
|
+
* last one collected rides it.
|
|
1936
1953
|
*/
|
|
1937
|
-
claim(
|
|
1938
|
-
|
|
1939
|
-
this.noted = null;
|
|
1954
|
+
claim(source) {
|
|
1955
|
+
this.claimed = source;
|
|
1940
1956
|
this.followed = [];
|
|
1941
|
-
if (noted === null) {
|
|
1942
|
-
this.claimed = null;
|
|
1943
|
-
return {
|
|
1944
|
-
source: null,
|
|
1945
|
-
expired: null
|
|
1946
|
-
};
|
|
1947
|
-
}
|
|
1948
|
-
if (now - noted.at > TRIGGER_ATTRIBUTION_TTL_MS) {
|
|
1949
|
-
this.claimed = null;
|
|
1950
|
-
return {
|
|
1951
|
-
source: null,
|
|
1952
|
-
expired: noted.source
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
this.claimed = noted.source;
|
|
1956
|
-
return {
|
|
1957
|
-
source: noted.source,
|
|
1958
|
-
expired: null
|
|
1959
|
-
};
|
|
1960
1957
|
}
|
|
1961
1958
|
/**
|
|
1962
|
-
* A
|
|
1963
|
-
*
|
|
1964
|
-
* a note left behind would be claimed by the next, unrelated incident.
|
|
1959
|
+
* A rule press the panel REFUSED because its entry delay is already running
|
|
1960
|
+
* (D705): its source is another subject of the SAME incident.
|
|
1965
1961
|
*
|
|
1966
1962
|
* Only an incident a rule started collects followers: the combined row is the
|
|
1967
1963
|
* source rule's, and promoting a follower to source would change the sentence.
|
|
1968
1964
|
*/
|
|
1969
|
-
follow(
|
|
1970
|
-
const source = this.pending(now);
|
|
1971
|
-
this.noted = null;
|
|
1972
|
-
if (source === null) return { kind: "none" };
|
|
1965
|
+
follow(source) {
|
|
1973
1966
|
const claimed = this.claimed;
|
|
1974
1967
|
if (claimed === null) return {
|
|
1975
1968
|
kind: "no-incident",
|
|
@@ -2223,7 +2216,8 @@ var ncAlarmPanelSchema = object({
|
|
|
2223
2216
|
schedule: unknown().optional(),
|
|
2224
2217
|
/** D706 — the mode's notification switches; absent = off. */
|
|
2225
2218
|
notifyDuringEntryDelay: boolean().optional(),
|
|
2226
|
-
notifySensorRewatched: boolean().optional()
|
|
2219
|
+
notifySensorRewatched: boolean().optional(),
|
|
2220
|
+
notifySensorIgnored: boolean().optional()
|
|
2227
2221
|
})).default([]),
|
|
2228
2222
|
/**
|
|
2229
2223
|
* Active alarm configuration profile id. Empty / absent on a blob older than
|
|
@@ -2299,6 +2293,7 @@ function settingsBlobFromPersisted(raw) {
|
|
|
2299
2293
|
if (rec["schedule"] !== void 0) entry.schedule = rec["schedule"];
|
|
2300
2294
|
if (typeof rec["notifyDuringEntryDelay"] === "boolean") entry.notifyDuringEntryDelay = rec["notifyDuringEntryDelay"];
|
|
2301
2295
|
if (typeof rec["notifySensorRewatched"] === "boolean") entry.notifySensorRewatched = rec["notifySensorRewatched"];
|
|
2296
|
+
if (typeof rec["notifySensorIgnored"] === "boolean") entry.notifySensorIgnored = rec["notifySensorIgnored"];
|
|
2302
2297
|
modeDelaysLoose.push(entry);
|
|
2303
2298
|
}
|
|
2304
2299
|
const truncatedTriggered = Math.max(0, Math.min(3600, Math.trunc(triggered)));
|
|
@@ -2875,21 +2870,26 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2875
2870
|
this.onPublish = hook;
|
|
2876
2871
|
}
|
|
2877
2872
|
/**
|
|
2878
|
-
*
|
|
2873
|
+
* A RULE pressed the panel, and says which match it was — in the call itself.
|
|
2879
2874
|
*
|
|
2880
2875
|
* The join between a rule's notification and the alarm's. `alarm-panel.trigger`
|
|
2881
2876
|
* takes nothing but a device id — by design, since a Home Assistant panel
|
|
2882
|
-
* provides the same cap — so
|
|
2883
|
-
*
|
|
2884
|
-
*
|
|
2885
|
-
*
|
|
2886
|
-
*
|
|
2877
|
+
* provides the same cap — so a rule press on THIS panel never goes through the
|
|
2878
|
+
* cap: the notification centre calls this method, in-process and
|
|
2879
|
+
* synchronously, with the match that pressed it. The source is never parked
|
|
2880
|
+
* anywhere for a later call to pick up: a single "next trigger is this rule's"
|
|
2881
|
+
* slot, written before an awaited cap call and read when it landed, was
|
|
2882
|
+
* overwritten by a second match 1 ms later, and the press that found it empty
|
|
2883
|
+
* skipped the exclusion check and sounded the siren off an excluded contact
|
|
2884
|
+
* (2026-10-07 02:32Z, D735).
|
|
2885
|
+
*
|
|
2886
|
+
* The FIRST accepted press claims the source and owns the countdown; a rule
|
|
2887
2887
|
* pressing again while the entry delay runs is refused as a trigger and its
|
|
2888
|
-
*
|
|
2888
|
+
* source becomes a FOLLOWER of the same incident (D705) — the triggered
|
|
2889
2889
|
* notification tiles the source and every follower as one mosaic.
|
|
2890
2890
|
*/
|
|
2891
|
-
|
|
2892
|
-
this.
|
|
2891
|
+
triggerFromRule(source, now = Date.now()) {
|
|
2892
|
+
return this.press(source, now);
|
|
2893
2893
|
}
|
|
2894
2894
|
/**
|
|
2895
2895
|
* Where the alarm stands for ONE rule match, read BEFORE that match presses the panel
|
|
@@ -2898,14 +2898,19 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2898
2898
|
* sensor is ignored, a running countdown or a triggered alarm refuses the press, an armed
|
|
2899
2899
|
* panel starts the countdown (or fires at once when the mode's entry delay is 0).
|
|
2900
2900
|
*
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
*
|
|
2904
|
-
*
|
|
2905
|
-
*
|
|
2901
|
+
* The subject that STARTED the countdown is still its starter on its later matches (a
|
|
2902
|
+
* person who stays in view is not "another intrusion during the delay"). A rule press
|
|
2903
|
+
* lands synchronously ({@link triggerFromRule}), so the machine this reads already holds
|
|
2904
|
+
* every earlier press of the same evaluation — there is no press "in flight" to guess at.
|
|
2905
|
+
*
|
|
2906
|
+
* A match of a sensor the current arm EXCLUDES is `excluded`: its press will be ignored,
|
|
2907
|
+
* and the rule's own push is not sent either (D735) — the panel's bypass logic speaks for it.
|
|
2906
2908
|
*/
|
|
2907
2909
|
classifyRuleMatch(ref, now = Date.now()) {
|
|
2908
|
-
if (this.exclusions.blocks(intrusionDeviceOf(ref))) return {
|
|
2910
|
+
if (this.exclusions.blocks(intrusionDeviceOf(ref))) return {
|
|
2911
|
+
kind: "excluded",
|
|
2912
|
+
sensorDeviceId: intrusionDeviceOf(ref)
|
|
2913
|
+
};
|
|
2909
2914
|
const mode = armModeOf(this.machine.target, NC_ALARM_MODES);
|
|
2910
2915
|
if (mode === null) return { kind: "none" };
|
|
2911
2916
|
const state = stateAt(this.machine, now);
|
|
@@ -2927,17 +2932,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
2927
2932
|
};
|
|
2928
2933
|
}
|
|
2929
2934
|
if (!isArmed(this.machine, now)) return { kind: "none" };
|
|
2930
|
-
|
|
2931
|
-
const inFlight = this.attribution.pending(now);
|
|
2932
|
-
if (inFlight !== null && subjectKeyOf(inFlight) !== key) return immediate ? {
|
|
2933
|
-
kind: "triggered",
|
|
2934
|
-
mode
|
|
2935
|
-
} : {
|
|
2936
|
-
kind: "entry-delay",
|
|
2937
|
-
mode,
|
|
2938
|
-
notify: this.notifyPolicy(mode).notifyDuringEntryDelay
|
|
2939
|
-
};
|
|
2940
|
-
return immediate ? {
|
|
2935
|
+
return this.delaysFor(mode).entryDelaySec === 0 ? {
|
|
2941
2936
|
kind: "triggers-now",
|
|
2942
2937
|
mode
|
|
2943
2938
|
} : {
|
|
@@ -3118,7 +3113,7 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3118
3113
|
* sensor is already lifted when the rule it fires reaches the panel.
|
|
3119
3114
|
*/
|
|
3120
3115
|
observeOpening(deviceId, reading, now = Date.now(), options = {}) {
|
|
3121
|
-
const change = this.exclusions.observe(deviceId, reading, now);
|
|
3116
|
+
const change = this.exclusions.observe(deviceId, reading, now, options.changedAt);
|
|
3122
3117
|
if (change === "unchanged") return "unchanged";
|
|
3123
3118
|
this.persist();
|
|
3124
3119
|
if (change === "closed") return "closed";
|
|
@@ -3168,13 +3163,12 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3168
3163
|
event: "lifted"
|
|
3169
3164
|
}, now);
|
|
3170
3165
|
}
|
|
3171
|
-
/** Did THIS sensor start the running entry delay
|
|
3166
|
+
/** Did THIS sensor start the running entry delay? A rule press lands synchronously, so
|
|
3167
|
+
* a press of the evaluation that lifted it has already landed when this is asked. */
|
|
3172
3168
|
startsEntryDelay(deviceId, now) {
|
|
3173
|
-
|
|
3174
|
-
const
|
|
3175
|
-
|
|
3176
|
-
if (!isArmed(this.machine, now) || state === "triggered") return false;
|
|
3177
|
-
return sensorOf(this.attribution.pending(now)) === deviceId;
|
|
3169
|
+
if (stateAt(this.machine, now) !== "pending") return false;
|
|
3170
|
+
const starter = this.attribution.current();
|
|
3171
|
+
return starter !== null && intrusionDeviceOf(starter) === deviceId;
|
|
3178
3172
|
}
|
|
3179
3173
|
/**
|
|
3180
3174
|
* A DELETED device leaves the current arm's exclusions: it can never report
|
|
@@ -3400,64 +3394,78 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3400
3394
|
},
|
|
3401
3395
|
disarm: async () => this.disarm(),
|
|
3402
3396
|
trigger: async () => {
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3397
|
+
this.press(null, Date.now());
|
|
3398
|
+
}
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3401
|
+
/**
|
|
3402
|
+
* THE ENTRY DELAY LIVES OR DIES HERE. The one body every press runs — a rule's
|
|
3403
|
+
* ({@link triggerFromRule}, with its source) and a person's (the cap, `null`).
|
|
3404
|
+
*
|
|
3405
|
+
* It used to set `fired: true` outright, calling itself "a panic button", while
|
|
3406
|
+
* the entry-delay transition sat in a method NO caller ever reached — so the
|
|
3407
|
+
* panel shipped with `pending` unreachable. The doc comment describing the right
|
|
3408
|
+
* design is precisely what made it look implemented.
|
|
3409
|
+
*
|
|
3410
|
+
* Synchronous, end to end: the source is a parameter, never a slot, so two
|
|
3411
|
+
* presses cannot exchange their sources however close together they land.
|
|
3412
|
+
*/
|
|
3413
|
+
press(source, now) {
|
|
3414
|
+
const sensor = source !== null ? intrusionDeviceOf(source) : void 0;
|
|
3415
|
+
if (sensor !== void 0 && this.exclusions.blocks(sensor)) {
|
|
3416
|
+
const moved = this.exclusions.takeMoved(sensor);
|
|
3417
|
+
this.ctx.logger.info("alarm trigger ignored — the sensor was open when the alarm was armed", {
|
|
3418
|
+
tags: { deviceId: sensor },
|
|
3419
|
+
meta: {
|
|
3420
|
+
panelDeviceId: this.id,
|
|
3421
|
+
ruleId: source?.ruleId,
|
|
3422
|
+
rule: source?.ruleName,
|
|
3423
|
+
state: stateAt(this.machine, now),
|
|
3424
|
+
moved
|
|
3419
3425
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3426
|
+
});
|
|
3427
|
+
this.announceIgnoredTrigger(sensor, now, moved);
|
|
3428
|
+
return "ignored-excluded";
|
|
3429
|
+
}
|
|
3430
|
+
const armed = armModeOf(this.machine.target, NC_ALARM_MODES);
|
|
3431
|
+
const resolved = this.delaysFor(armed);
|
|
3432
|
+
const next = trigger(this.machine, resolved, now);
|
|
3433
|
+
if (next === this.machine) {
|
|
3434
|
+
const state = stateAt(this.machine, now);
|
|
3435
|
+
this.ctx.logger.info(ignoredTriggerMessage(state), {
|
|
3436
|
+
tags: { deviceId: this.id },
|
|
3437
|
+
meta: {
|
|
3438
|
+
state,
|
|
3439
|
+
...source !== null ? {
|
|
3440
|
+
ruleId: source.ruleId,
|
|
3441
|
+
rule: source.ruleName
|
|
3442
|
+
} : {}
|
|
3435
3443
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
});
|
|
3458
|
-
this.publish(now);
|
|
3444
|
+
});
|
|
3445
|
+
if (state === "pending" && source !== null) this.reportFollow(this.attribution.follow(source));
|
|
3446
|
+
return "refused";
|
|
3447
|
+
}
|
|
3448
|
+
this.attribution.claim(source);
|
|
3449
|
+
this.machine = next;
|
|
3450
|
+
this.ctx.logger.info("alarm delays applied", {
|
|
3451
|
+
tags: { deviceId: this.id },
|
|
3452
|
+
meta: {
|
|
3453
|
+
mode: armed,
|
|
3454
|
+
phase: "trigger",
|
|
3455
|
+
entryDelaySec: resolved.entryDelaySec,
|
|
3456
|
+
triggeredDurationSec: resolved.triggeredDurationSec,
|
|
3457
|
+
source: {
|
|
3458
|
+
entry: resolved.source.entryDelaySec,
|
|
3459
|
+
siren: resolved.source.triggeredDurationSec
|
|
3460
|
+
},
|
|
3461
|
+
...source !== null ? {
|
|
3462
|
+
ruleId: source.ruleId,
|
|
3463
|
+
rule: source.ruleName
|
|
3464
|
+
} : { by: "manual" }
|
|
3459
3465
|
}
|
|
3460
3466
|
});
|
|
3467
|
+
this.publish(now);
|
|
3468
|
+
return "accepted";
|
|
3461
3469
|
}
|
|
3462
3470
|
status(now = Date.now()) {
|
|
3463
3471
|
return {
|
|
@@ -3538,7 +3546,6 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3538
3546
|
* stated: the drop is work the operator would otherwise never see go missing.
|
|
3539
3547
|
*/
|
|
3540
3548
|
reportFollow(outcome) {
|
|
3541
|
-
if (outcome.kind === "none") return;
|
|
3542
3549
|
const source = outcome.source;
|
|
3543
3550
|
const meta = {
|
|
3544
3551
|
panelDeviceId: this.id,
|
|
@@ -3567,13 +3574,15 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3567
3574
|
});
|
|
3568
3575
|
}
|
|
3569
3576
|
/**
|
|
3570
|
-
*
|
|
3571
|
-
*
|
|
3572
|
-
*
|
|
3573
|
-
*
|
|
3574
|
-
*
|
|
3577
|
+
* "Ignored" is TOLD only when the excluded sensor really MOVED while armed and the
|
|
3578
|
+
* mode's `notifySensorIgnored` switch is on (D735, default off) — and then ONCE per
|
|
3579
|
+
* sensor per arm (the mark rides the persisted exclusion), and only while the panel is
|
|
3580
|
+
* armed: during the exit delay `trigger()` would have dropped it anyway, so the
|
|
3581
|
+
* exclusion is not what ignored it. A contact that was open all along and was merely
|
|
3582
|
+
* re-published (a provider reconnect, a Zigbee2MQTT restart) moved nothing, and four
|
|
3583
|
+
* such pushes reached the operator in 60 ms on 2026-10-07. Every refusal is a line.
|
|
3575
3584
|
*/
|
|
3576
|
-
announceIgnoredTrigger(deviceId, now) {
|
|
3585
|
+
announceIgnoredTrigger(deviceId, now, moved) {
|
|
3577
3586
|
const state = stateAt(this.machine, now);
|
|
3578
3587
|
if (!isArmed(this.machine, now)) {
|
|
3579
3588
|
this.ctx.logger.debug("alarm bypass not announced — the panel is not armed yet", {
|
|
@@ -3595,6 +3604,29 @@ var NcAlarmPanelDevice = class extends BaseDevice {
|
|
|
3595
3604
|
});
|
|
3596
3605
|
return;
|
|
3597
3606
|
}
|
|
3607
|
+
if (!moved) {
|
|
3608
|
+
this.ctx.logger.debug("alarm bypass not announced — the sensor did not move since the arm", {
|
|
3609
|
+
tags: { deviceId },
|
|
3610
|
+
meta: {
|
|
3611
|
+
panelDeviceId: this.id,
|
|
3612
|
+
state,
|
|
3613
|
+
reason: "not-moved"
|
|
3614
|
+
}
|
|
3615
|
+
});
|
|
3616
|
+
return;
|
|
3617
|
+
}
|
|
3618
|
+
const mode = armModeOf(this.machine.target, NC_ALARM_MODES);
|
|
3619
|
+
if (mode === null || !this.notifyPolicy(mode).notifySensorIgnored) {
|
|
3620
|
+
this.ctx.logger.info("alarm \"ignored\" not notified — the mode does not notify it", {
|
|
3621
|
+
tags: { deviceId },
|
|
3622
|
+
meta: {
|
|
3623
|
+
panelDeviceId: this.id,
|
|
3624
|
+
mode,
|
|
3625
|
+
reason: "ignored-notify-off"
|
|
3626
|
+
}
|
|
3627
|
+
});
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3598
3630
|
if (!this.exclusions.markAnnounced(deviceId)) {
|
|
3599
3631
|
this.ctx.logger.debug("alarm bypass already announced in this arm — repeat not sent", {
|
|
3600
3632
|
tags: { deviceId },
|
|
@@ -3752,7 +3784,7 @@ function ncAlarmPanelPort(panel) {
|
|
|
3752
3784
|
deleteProfile: (id) => panel.deleteProfile(id),
|
|
3753
3785
|
activateProfile: (id) => panel.activateProfile(id),
|
|
3754
3786
|
availableModes: () => NC_ALARM_MODES,
|
|
3755
|
-
|
|
3787
|
+
triggerFromRule: (source) => panel.triggerFromRule(source),
|
|
3756
3788
|
classifyRuleMatch: (source) => panel.classifyRuleMatch(source),
|
|
3757
3789
|
announceLifted: (deviceId, now) => panel.announceLifted(deviceId, now),
|
|
3758
3790
|
excludedDevices: () => panel.excludedDevices(),
|
|
@@ -4658,22 +4690,26 @@ var HUB_API_PORT = 4443;
|
|
|
4658
4690
|
* preferred LAN interface priority `0`, so a public candidate has to sit below
|
|
4659
4691
|
* zero to outrank it.
|
|
4660
4692
|
*/
|
|
4661
|
-
var
|
|
4693
|
+
var PRIORITY_PUBLIC_ENDPOINT = -400;
|
|
4662
4694
|
var PRIORITY_CONNECTED = -200;
|
|
4663
4695
|
var PRIORITY_EXTERNAL_LIST = -100;
|
|
4664
4696
|
/**
|
|
4665
|
-
* Build the ranked candidate list
|
|
4666
|
-
*
|
|
4667
|
-
* then
|
|
4668
|
-
*
|
|
4697
|
+
* Build the ranked candidate list for every link a notification carries
|
|
4698
|
+
* (buttons, attachments / media, admin links): the hub's PUBLIC ENDPOINT
|
|
4699
|
+
* (D734), then — only reached when that is unknown — the connected external
|
|
4700
|
+
* ingress (tunnels), then the LAN addresses. Every source is best-effort — one
|
|
4701
|
+
* that throws contributes nothing rather than costing the whole list.
|
|
4702
|
+
*
|
|
4703
|
+
* The notification-taps address is deliberately NOT an input: it decides only
|
|
4704
|
+
* where a tap opens, and its default (the app link) is no hub address.
|
|
4669
4705
|
*/
|
|
4670
4706
|
async function collectArtifactEndpoints(sources) {
|
|
4671
4707
|
const out = [];
|
|
4672
|
-
const
|
|
4673
|
-
if (
|
|
4674
|
-
baseUrl:
|
|
4708
|
+
const publicEndpoint = sources.publicEndpoint;
|
|
4709
|
+
if (publicEndpoint !== void 0 && publicEndpoint !== "" && !isAppLinkBase(publicEndpoint)) out.push({
|
|
4710
|
+
baseUrl: publicEndpoint,
|
|
4675
4711
|
kind: "public",
|
|
4676
|
-
priority:
|
|
4712
|
+
priority: PRIORITY_PUBLIC_ENDPOINT
|
|
4677
4713
|
});
|
|
4678
4714
|
try {
|
|
4679
4715
|
const connected = await sources.getConnected();
|
|
@@ -4908,6 +4944,49 @@ var NcArtifactPlane = class {
|
|
|
4908
4944
|
}
|
|
4909
4945
|
};
|
|
4910
4946
|
//#endregion
|
|
4947
|
+
//#region src/notification-center/link-endpoints.ts
|
|
4948
|
+
async function publicEndpointAddress(api, logger) {
|
|
4949
|
+
try {
|
|
4950
|
+
const { roles } = await api.localNetwork.getAddresses.query({});
|
|
4951
|
+
return roles.publicEndpoint.resolved ?? void 0;
|
|
4952
|
+
} catch (err) {
|
|
4953
|
+
logger.warn("nc links: public endpoint unreadable — using the ingress ladder", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
4954
|
+
return;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
/** The ranked base-URL candidates for a notification's non-tap links. */
|
|
4958
|
+
async function notificationLinkEndpoints(api, logger) {
|
|
4959
|
+
return collectArtifactEndpoints({
|
|
4960
|
+
publicEndpoint: await publicEndpointAddress(api, logger),
|
|
4961
|
+
getConnected: async () => {
|
|
4962
|
+
const status = await api.networkAccess.getStatus.query();
|
|
4963
|
+
logger.debug("artifact base-url: connected ingress", { meta: {
|
|
4964
|
+
connected: status.connected,
|
|
4965
|
+
url: status.endpoint?.url ?? null,
|
|
4966
|
+
protocol: status.endpoint?.protocol ?? null
|
|
4967
|
+
} });
|
|
4968
|
+
return status.connected && status.endpoint !== null ? {
|
|
4969
|
+
url: status.endpoint.url,
|
|
4970
|
+
protocol: status.endpoint.protocol
|
|
4971
|
+
} : null;
|
|
4972
|
+
},
|
|
4973
|
+
listExternal: async () => {
|
|
4974
|
+
return (await api.networkAccess.listEndpoints.query()).map((e) => ({
|
|
4975
|
+
url: e.url,
|
|
4976
|
+
protocol: e.protocol
|
|
4977
|
+
}));
|
|
4978
|
+
},
|
|
4979
|
+
listLan: async (port) => {
|
|
4980
|
+
return (await api.localNetwork.getConnectionEndpoints.query({ port })).endpoints.flatMap((e) => e.kind === "app-link" || e.kind === "custom" ? [] : [{
|
|
4981
|
+
baseUrl: e.baseUrl,
|
|
4982
|
+
kind: e.kind,
|
|
4983
|
+
priority: e.priority
|
|
4984
|
+
}]);
|
|
4985
|
+
},
|
|
4986
|
+
logger
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
//#endregion
|
|
4911
4990
|
//#region src/notification-center/artifact-read-plane.ts
|
|
4912
4991
|
/**
|
|
4913
4992
|
* Parse a single-range `Range` header against a known length.
|
|
@@ -5364,158 +5443,6 @@ function mimeFromExtension(file) {
|
|
|
5364
5443
|
return "application/octet-stream";
|
|
5365
5444
|
}
|
|
5366
5445
|
//#endregion
|
|
5367
|
-
//#region src/notification-center/system-event-tap-url.ts
|
|
5368
|
-
/** The admin UI's own routes, as `packages/addon-admin-ui/src/App.tsx` declares them. */
|
|
5369
|
-
var ADMIN_ADDONS_PATH = "/system/addons";
|
|
5370
|
-
var ADMIN_CLUSTER_PATH = "/system/cluster";
|
|
5371
|
-
var ADMIN_DEVICES_PATH = "/devices";
|
|
5372
|
-
/**
|
|
5373
|
-
* The query parameter naming a node.
|
|
5374
|
-
*
|
|
5375
|
-
* One spelling for both admin pages, mirrored by `NODE_PARAM` in
|
|
5376
|
-
* `packages/addon-admin-ui/src/lib/node-url-selection.ts` and pinned across
|
|
5377
|
-
* the two packages by `scripts/check-notification-tap-targets.ts`.
|
|
5378
|
-
*/
|
|
5379
|
-
var NODE_PARAM = "node";
|
|
5380
|
-
/** The device page's top-level tab parameter, read by `DeviceDetail`. */
|
|
5381
|
-
var TAB_PARAM = "tab";
|
|
5382
|
-
/** The Stream Broker tab id — a `WELL_KNOWN_TABS` member, hoisted `top-tab`. */
|
|
5383
|
-
var STREAM_BROKER_TAB = "stream-broker";
|
|
5384
|
-
/** `DeviceType.Camera`. Spelled here rather than imported so this stays a pure
|
|
5385
|
-
* string map with no device-model dependency; the guard pins it. */
|
|
5386
|
-
var CAMERA_DEVICE_TYPE = "camera";
|
|
5387
|
-
/** Kinds whose action is "go and apply an ADDON update". */
|
|
5388
|
-
var ADDON_UPDATE_KINDS = new Set(["addon-update-available"]);
|
|
5389
|
-
/** Kinds whose action is "go and apply a NODE's own (`@camstack/server`) update". */
|
|
5390
|
-
var NODE_UPDATE_KINDS = new Set(["server-update-available", "wrapper-update-available"]);
|
|
5391
|
-
/**
|
|
5392
|
-
* Kinds whose action is "go and look at this device's stream".
|
|
5393
|
-
*
|
|
5394
|
-
* BOTH DIRECTIONS. A recovery lands on the same page as the failure it undoes,
|
|
5395
|
-
* because "did it really come back, and what did it do while it was gone" is
|
|
5396
|
-
* an action and the page that answers it is the one the down-notification
|
|
5397
|
-
* already opened. On 2026-08-30 the operator's phone showed "Il dispositivo
|
|
5398
|
-
* Videocamera sala feste è online" with `tapAction.value: null` — the same
|
|
5399
|
-
* camera's `device-offline` an hour earlier opened the Stream Broker tab, and
|
|
5400
|
-
* the recovery opened nothing, purely because `device-online` had no entry
|
|
5401
|
-
* here.
|
|
5402
|
-
*
|
|
5403
|
-
* The legacy `camera-*` pair is here for the same reason both are still in the
|
|
5404
|
-
* enum: outbox and history rows enqueued before the rename carry them, and an
|
|
5405
|
-
* old row is exactly the one whose action has to keep working.
|
|
5406
|
-
*
|
|
5407
|
-
* Kinds NOT here are kinds whose DOWN direction has no action either
|
|
5408
|
-
* (`node-online`/`node-offline`, `device-enabled`/`device-disabled`) — a
|
|
5409
|
-
* recovery does not earn a destination its failure was never given.
|
|
5410
|
-
*/
|
|
5411
|
-
var DEVICE_STREAM_KINDS = new Set([
|
|
5412
|
-
"device-offline",
|
|
5413
|
-
"device-online",
|
|
5414
|
-
"stream-offline",
|
|
5415
|
-
"stream-online",
|
|
5416
|
-
"camera-offline",
|
|
5417
|
-
"camera-online"
|
|
5418
|
-
]);
|
|
5419
|
-
/**
|
|
5420
|
-
* Kinds whose subject is a camera BY CONSTRUCTION, whatever the row says.
|
|
5421
|
-
*
|
|
5422
|
-
* A stream belongs to a camera and nothing else. The legacy `camera-*` pair is
|
|
5423
|
-
* too — that name is precisely the claim it made, and it is why the kind was
|
|
5424
|
-
* split into `device-*` + a `deviceTypes` condition.
|
|
5425
|
-
*/
|
|
5426
|
-
var CAMERA_BY_CONSTRUCTION_KINDS = new Set([
|
|
5427
|
-
"stream-offline",
|
|
5428
|
-
"stream-online",
|
|
5429
|
-
"camera-offline",
|
|
5430
|
-
"camera-online"
|
|
5431
|
-
]);
|
|
5432
|
-
/**
|
|
5433
|
-
* Where a per-section STATE SUMMARY row lands (D708). The row spans nodes, so
|
|
5434
|
-
* no `?node=`: the unscoped page is the honest one. A section absent here taps
|
|
5435
|
-
* through its kind, like any other row — a device section carries the system
|
|
5436
|
-
* placeholder device and gets no link, and the crash-looped addons have none
|
|
5437
|
-
* (their instant kind has none either).
|
|
5438
|
-
*
|
|
5439
|
-
* `nodes` is the one destination no kind gives: a node going offline has no
|
|
5440
|
-
* instant action, but "which nodes are in trouble" is answered by the
|
|
5441
|
-
* Cluster page, and the summary row is about all of them at once.
|
|
5442
|
-
*/
|
|
5443
|
-
var SUMMARY_SECTION_PATHS = {
|
|
5444
|
-
nodes: ADMIN_CLUSTER_PATH,
|
|
5445
|
-
"addon-updates": ADMIN_ADDONS_PATH,
|
|
5446
|
-
updates: ADMIN_CLUSTER_PATH
|
|
5447
|
-
};
|
|
5448
|
-
function withParam(path, key, value) {
|
|
5449
|
-
if (value === void 0 || value.length === 0) return path;
|
|
5450
|
-
return `${path}?${key}=${encodeURIComponent(value)}`;
|
|
5451
|
-
}
|
|
5452
|
-
/**
|
|
5453
|
-
* The admin path (with its query) a system event taps through to, or `null`
|
|
5454
|
-
* when the kind has no action.
|
|
5455
|
-
*
|
|
5456
|
-
* Exported for the cross-package guard and for tests that want the path
|
|
5457
|
-
* without a base; the dispatcher goes through {@link systemEventTapUrl}.
|
|
5458
|
-
*/
|
|
5459
|
-
function systemEventTapPath(subject) {
|
|
5460
|
-
const sectionPath = subject.summarySection !== void 0 ? SUMMARY_SECTION_PATHS[subject.summarySection] : void 0;
|
|
5461
|
-
if (sectionPath !== void 0) return sectionPath;
|
|
5462
|
-
if (ADDON_UPDATE_KINDS.has(subject.kind)) return withParam(ADMIN_ADDONS_PATH, NODE_PARAM, subject.nodeId);
|
|
5463
|
-
if (NODE_UPDATE_KINDS.has(subject.kind)) return withParam(ADMIN_CLUSTER_PATH, NODE_PARAM, subject.nodeId);
|
|
5464
|
-
if (DEVICE_STREAM_KINDS.has(subject.kind)) {
|
|
5465
|
-
const deviceId = subject.deviceId;
|
|
5466
|
-
if (deviceId === void 0 || !Number.isInteger(deviceId) || deviceId <= 0) return null;
|
|
5467
|
-
const devicePath = `${ADMIN_DEVICES_PATH}/${String(deviceId)}`;
|
|
5468
|
-
return CAMERA_BY_CONSTRUCTION_KINDS.has(subject.kind) || subject.deviceType === CAMERA_DEVICE_TYPE ? withParam(devicePath, TAB_PARAM, STREAM_BROKER_TAB) : devicePath;
|
|
5469
|
-
}
|
|
5470
|
-
return null;
|
|
5471
|
-
}
|
|
5472
|
-
/** Full URL, or `null` when the kind has no action or no base is reachable. */
|
|
5473
|
-
function systemEventTapUrl(subject, baseUrl) {
|
|
5474
|
-
if (baseUrl === null || baseUrl.length === 0) return null;
|
|
5475
|
-
const path = systemEventTapPath(subject);
|
|
5476
|
-
if (path === null) return null;
|
|
5477
|
-
return `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
5478
|
-
}
|
|
5479
|
-
//#endregion
|
|
5480
|
-
//#region src/notification-center/default-tap-url.ts
|
|
5481
|
-
/**
|
|
5482
|
-
* The app-link choice from the flagged notification address — present when
|
|
5483
|
-
* the effective address is the app link. Reads the answer through
|
|
5484
|
-
* `viewerLinkBaseOf`, the same reading the Network page's preview uses.
|
|
5485
|
-
*/
|
|
5486
|
-
function ncAppLinkChoiceOf(endpoint) {
|
|
5487
|
-
const base = viewerLinkBaseOf(endpoint);
|
|
5488
|
-
return base.form === "app" ? { hubBaseUrl: base.hubBaseUrl } : null;
|
|
5489
|
-
}
|
|
5490
|
-
/**
|
|
5491
|
-
* The tap URL, or null. In the app form a camera link is minted even with no
|
|
5492
|
-
* `hub=` — the central page reaches the hub through the app's (or the
|
|
5493
|
-
* browser's) own stored hub (central-link design §5). The hub form and every
|
|
5494
|
-
* admin destination still need a base.
|
|
5495
|
-
*/
|
|
5496
|
-
function defaultTapUrl(input) {
|
|
5497
|
-
const baseUrl = input.baseUrl === null || input.baseUrl.length === 0 ? null : input.baseUrl;
|
|
5498
|
-
if (input.systemEvent !== void 0) return baseUrl === null ? null : systemEventTapUrl(input.systemEvent, baseUrl);
|
|
5499
|
-
const destination = {
|
|
5500
|
-
kind: "camera",
|
|
5501
|
-
deviceId: input.deviceId,
|
|
5502
|
-
atMs: input.timestampMs
|
|
5503
|
-
};
|
|
5504
|
-
return input.appLink !== void 0 ? buildViewerLink({
|
|
5505
|
-
base: {
|
|
5506
|
-
form: "app",
|
|
5507
|
-
hubBaseUrl: input.appLink.hubBaseUrl
|
|
5508
|
-
},
|
|
5509
|
-
destination
|
|
5510
|
-
}) : buildViewerLink({
|
|
5511
|
-
base: {
|
|
5512
|
-
form: "hub",
|
|
5513
|
-
hubBaseUrl: baseUrl
|
|
5514
|
-
},
|
|
5515
|
-
destination
|
|
5516
|
-
});
|
|
5517
|
-
}
|
|
5518
|
-
//#endregion
|
|
5519
5446
|
//#region src/notification-center/prune-windows.ts
|
|
5520
5447
|
/**
|
|
5521
5448
|
* The operator-configurable prune windows, in days. One module, because the
|
|
@@ -14933,7 +14860,8 @@ var NcGroupMemberFateSchema = union([_enum([
|
|
|
14933
14860
|
"already-existed",
|
|
14934
14861
|
"close-failed",
|
|
14935
14862
|
"alarm-entry-delay-started",
|
|
14936
|
-
"alarm-entry-delay"
|
|
14863
|
+
"alarm-entry-delay",
|
|
14864
|
+
"alarm-sensor-excluded"
|
|
14937
14865
|
]), templateLiteral(["failed-condition:", string()])]);
|
|
14938
14866
|
/** Read cap, so a client cannot ask for the whole ring in one call. */
|
|
14939
14867
|
var MAX_QUERY_LIMIT = 200;
|
|
@@ -23037,6 +22965,143 @@ function withSummaryBody(rule, section) {
|
|
|
23037
22965
|
};
|
|
23038
22966
|
}
|
|
23039
22967
|
//#endregion
|
|
22968
|
+
//#region src/notification-center/system-event-tap-url.ts
|
|
22969
|
+
/** The admin UI's own routes, as `packages/addon-admin-ui/src/App.tsx` declares them. */
|
|
22970
|
+
var ADMIN_ADDONS_PATH = "/system/addons";
|
|
22971
|
+
var ADMIN_CLUSTER_PATH = "/system/cluster";
|
|
22972
|
+
var ADMIN_DEVICES_PATH = "/devices";
|
|
22973
|
+
/**
|
|
22974
|
+
* The query parameter naming a node.
|
|
22975
|
+
*
|
|
22976
|
+
* One spelling for both admin pages, mirrored by `NODE_PARAM` in
|
|
22977
|
+
* `packages/addon-admin-ui/src/lib/node-url-selection.ts` and pinned across
|
|
22978
|
+
* the two packages by `scripts/check-notification-tap-targets.ts`.
|
|
22979
|
+
*/
|
|
22980
|
+
var NODE_PARAM = "node";
|
|
22981
|
+
/** The device page's top-level tab parameter, read by `DeviceDetail`. */
|
|
22982
|
+
var TAB_PARAM = "tab";
|
|
22983
|
+
/** The Stream Broker tab id — a `WELL_KNOWN_TABS` member, hoisted `top-tab`. */
|
|
22984
|
+
var STREAM_BROKER_TAB = "stream-broker";
|
|
22985
|
+
/** `DeviceType.Camera`. Spelled here rather than imported so this stays a pure
|
|
22986
|
+
* string map with no device-model dependency; the guard pins it. */
|
|
22987
|
+
var CAMERA_DEVICE_TYPE = "camera";
|
|
22988
|
+
/** Kinds whose action is "go and apply an ADDON update". */
|
|
22989
|
+
var ADDON_UPDATE_KINDS = new Set(["addon-update-available"]);
|
|
22990
|
+
/** Kinds whose action is "go and apply a NODE's own (`@camstack/server`) update". */
|
|
22991
|
+
var NODE_UPDATE_KINDS = new Set(["server-update-available", "wrapper-update-available"]);
|
|
22992
|
+
/**
|
|
22993
|
+
* Kinds whose action is "go and look at this device's stream".
|
|
22994
|
+
*
|
|
22995
|
+
* BOTH DIRECTIONS. A recovery lands on the same page as the failure it undoes,
|
|
22996
|
+
* because "did it really come back, and what did it do while it was gone" is
|
|
22997
|
+
* an action and the page that answers it is the one the down-notification
|
|
22998
|
+
* already opened. On 2026-08-30 the operator's phone showed "Il dispositivo
|
|
22999
|
+
* Videocamera sala feste è online" with `tapAction.value: null` — the same
|
|
23000
|
+
* camera's `device-offline` an hour earlier opened the Stream Broker tab, and
|
|
23001
|
+
* the recovery opened nothing, purely because `device-online` had no entry
|
|
23002
|
+
* here.
|
|
23003
|
+
*
|
|
23004
|
+
* The legacy `camera-*` pair is here for the same reason both are still in the
|
|
23005
|
+
* enum: outbox and history rows enqueued before the rename carry them, and an
|
|
23006
|
+
* old row is exactly the one whose action has to keep working.
|
|
23007
|
+
*
|
|
23008
|
+
* Kinds NOT here are kinds whose DOWN direction has no action either
|
|
23009
|
+
* (`node-online`/`node-offline`, `device-enabled`/`device-disabled`) — a
|
|
23010
|
+
* recovery does not earn a destination its failure was never given.
|
|
23011
|
+
*/
|
|
23012
|
+
var DEVICE_STREAM_KINDS = new Set([
|
|
23013
|
+
"device-offline",
|
|
23014
|
+
"device-online",
|
|
23015
|
+
"stream-offline",
|
|
23016
|
+
"stream-online",
|
|
23017
|
+
"camera-offline",
|
|
23018
|
+
"camera-online"
|
|
23019
|
+
]);
|
|
23020
|
+
/**
|
|
23021
|
+
* Kinds whose subject is a camera BY CONSTRUCTION, whatever the row says.
|
|
23022
|
+
*
|
|
23023
|
+
* A stream belongs to a camera and nothing else. The legacy `camera-*` pair is
|
|
23024
|
+
* too — that name is precisely the claim it made, and it is why the kind was
|
|
23025
|
+
* split into `device-*` + a `deviceTypes` condition.
|
|
23026
|
+
*/
|
|
23027
|
+
var CAMERA_BY_CONSTRUCTION_KINDS = new Set([
|
|
23028
|
+
"stream-offline",
|
|
23029
|
+
"stream-online",
|
|
23030
|
+
"camera-offline",
|
|
23031
|
+
"camera-online"
|
|
23032
|
+
]);
|
|
23033
|
+
/**
|
|
23034
|
+
* Where a per-section STATE SUMMARY row lands (D708). The row spans nodes, so
|
|
23035
|
+
* no `?node=`: the unscoped page is the honest one. A section absent here taps
|
|
23036
|
+
* through its kind, like any other row — a device section carries the system
|
|
23037
|
+
* placeholder device and gets no link, and the crash-looped addons have none
|
|
23038
|
+
* (their instant kind has none either).
|
|
23039
|
+
*
|
|
23040
|
+
* `nodes` is the one destination no kind gives: a node going offline has no
|
|
23041
|
+
* instant action, but "which nodes are in trouble" is answered by the
|
|
23042
|
+
* Cluster page, and the summary row is about all of them at once.
|
|
23043
|
+
*/
|
|
23044
|
+
var SUMMARY_SECTION_PATHS = {
|
|
23045
|
+
nodes: ADMIN_CLUSTER_PATH,
|
|
23046
|
+
"addon-updates": ADMIN_ADDONS_PATH,
|
|
23047
|
+
updates: ADMIN_CLUSTER_PATH
|
|
23048
|
+
};
|
|
23049
|
+
function withParam(path, key, value) {
|
|
23050
|
+
if (value === void 0 || value.length === 0) return path;
|
|
23051
|
+
return `${path}?${key}=${encodeURIComponent(value)}`;
|
|
23052
|
+
}
|
|
23053
|
+
/**
|
|
23054
|
+
* The admin path (with its query) a system event taps through to, or `null`
|
|
23055
|
+
* when the kind has no action.
|
|
23056
|
+
*
|
|
23057
|
+
* Exported for the cross-package guard and for tests that want the path
|
|
23058
|
+
* without a base; the dispatcher goes through {@link systemEventTapUrl}.
|
|
23059
|
+
*/
|
|
23060
|
+
function systemEventTapPath(subject) {
|
|
23061
|
+
const sectionPath = subject.summarySection !== void 0 ? SUMMARY_SECTION_PATHS[subject.summarySection] : void 0;
|
|
23062
|
+
if (sectionPath !== void 0) return sectionPath;
|
|
23063
|
+
if (ADDON_UPDATE_KINDS.has(subject.kind)) return withParam(ADMIN_ADDONS_PATH, NODE_PARAM, subject.nodeId);
|
|
23064
|
+
if (NODE_UPDATE_KINDS.has(subject.kind)) return withParam(ADMIN_CLUSTER_PATH, NODE_PARAM, subject.nodeId);
|
|
23065
|
+
if (DEVICE_STREAM_KINDS.has(subject.kind)) {
|
|
23066
|
+
const deviceId = subject.deviceId;
|
|
23067
|
+
if (deviceId === void 0 || !Number.isInteger(deviceId) || deviceId <= 0) return null;
|
|
23068
|
+
const devicePath = `${ADMIN_DEVICES_PATH}/${String(deviceId)}`;
|
|
23069
|
+
return CAMERA_BY_CONSTRUCTION_KINDS.has(subject.kind) || subject.deviceType === CAMERA_DEVICE_TYPE ? withParam(devicePath, TAB_PARAM, STREAM_BROKER_TAB) : devicePath;
|
|
23070
|
+
}
|
|
23071
|
+
return null;
|
|
23072
|
+
}
|
|
23073
|
+
/** Full URL, or `null` when the kind has no action or no base is reachable. */
|
|
23074
|
+
function systemEventTapUrl(subject, baseUrl) {
|
|
23075
|
+
if (baseUrl === null || baseUrl.length === 0) return null;
|
|
23076
|
+
const path = systemEventTapPath(subject);
|
|
23077
|
+
if (path === null) return null;
|
|
23078
|
+
return `${baseUrl.replace(/\/+$/, "")}${path}`;
|
|
23079
|
+
}
|
|
23080
|
+
//#endregion
|
|
23081
|
+
//#region src/notification-center/default-tap-url.ts
|
|
23082
|
+
/**
|
|
23083
|
+
* The tap URL, or null. In the app form a camera link is minted even with no
|
|
23084
|
+
* `hub=` — the central page reaches the hub through the app's (or the
|
|
23085
|
+
* browser's) own stored hub (central-link design §5). The hub form and every
|
|
23086
|
+
* admin destination still need a base.
|
|
23087
|
+
*/
|
|
23088
|
+
function defaultTapUrl(input) {
|
|
23089
|
+
const publicBaseUrl = input.publicBaseUrl === null || input.publicBaseUrl.length === 0 ? null : input.publicBaseUrl;
|
|
23090
|
+
if (input.systemEvent !== void 0) return publicBaseUrl === null ? null : systemEventTapUrl(input.systemEvent, publicBaseUrl);
|
|
23091
|
+
const destination = {
|
|
23092
|
+
kind: "camera",
|
|
23093
|
+
deviceId: input.deviceId,
|
|
23094
|
+
atMs: input.timestampMs
|
|
23095
|
+
};
|
|
23096
|
+
return buildViewerLink({
|
|
23097
|
+
base: input.tap ?? {
|
|
23098
|
+
form: "hub",
|
|
23099
|
+
hubBaseUrl: publicBaseUrl
|
|
23100
|
+
},
|
|
23101
|
+
destination
|
|
23102
|
+
});
|
|
23103
|
+
}
|
|
23104
|
+
//#endregion
|
|
23040
23105
|
//#region src/notification-center/footage-source.ts
|
|
23041
23106
|
/**
|
|
23042
23107
|
* Decide where this window must be cut from. Pure and total.
|
|
@@ -25534,20 +25599,19 @@ var NcDispatcher = class {
|
|
|
25534
25599
|
* repo keeps paying for.
|
|
25535
25600
|
*/
|
|
25536
25601
|
/**
|
|
25537
|
-
* The
|
|
25538
|
-
*
|
|
25539
|
-
*
|
|
25540
|
-
*
|
|
25602
|
+
* The `notificationTaps` base of a camera tap (D734). A failing read is
|
|
25603
|
+
* `null` — the tap then opens the hub viewer on the public endpoint — and is
|
|
25604
|
+
* logged, so a tap that lands somewhere other than the flagged address has a
|
|
25605
|
+
* line saying why.
|
|
25541
25606
|
*/
|
|
25542
|
-
async
|
|
25543
|
-
const read = this.deps.
|
|
25544
|
-
if (read === void 0) return
|
|
25607
|
+
async resolveTapBase() {
|
|
25608
|
+
const read = this.deps.tapBase;
|
|
25609
|
+
if (read === void 0) return null;
|
|
25545
25610
|
try {
|
|
25546
|
-
|
|
25547
|
-
return choice === null ? {} : { appLink: choice };
|
|
25611
|
+
return await read();
|
|
25548
25612
|
} catch (err) {
|
|
25549
|
-
this.deps.logger.warn("notification
|
|
25550
|
-
return
|
|
25613
|
+
this.deps.logger.warn("notification tap address unreadable — tap uses the public endpoint", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
25614
|
+
return null;
|
|
25551
25615
|
}
|
|
25552
25616
|
}
|
|
25553
25617
|
async mintActions(entry, target) {
|
|
@@ -25641,11 +25705,12 @@ var NcDispatcher = class {
|
|
|
25641
25705
|
const resolvedMedia = await this.withArtifactUrls(entry, resolved.attachments);
|
|
25642
25706
|
const attachments = [...resolvedMedia.attachments, ...frozenArtifactAttachments(entry)];
|
|
25643
25707
|
const params = pickParams(entry.payload.params);
|
|
25644
|
-
const
|
|
25708
|
+
const publicUrlBase = this.deps.publicUrlBase;
|
|
25709
|
+
const tapUrl = params.clickUrl === void 0 && publicUrlBase !== void 0 ? defaultTapUrl({
|
|
25645
25710
|
deviceId: systemEvent !== void 0 ? 0 : subject.deviceId,
|
|
25646
25711
|
timestampMs: subject.timestamp,
|
|
25647
|
-
|
|
25648
|
-
|
|
25712
|
+
publicBaseUrl: await publicUrlBase().catch(() => null),
|
|
25713
|
+
tap: systemEvent === void 0 ? await this.resolveTapBase() : null,
|
|
25649
25714
|
...systemEvent !== void 0 ? { systemEvent: {
|
|
25650
25715
|
kind: systemEvent.kind,
|
|
25651
25716
|
...systemEvent.deviceId !== void 0 ? { deviceId: systemEvent.deviceId } : {},
|
|
@@ -39808,7 +39873,8 @@ var NotificationCenter = class {
|
|
|
39808
39873
|
ruleAiStats: () => ruleAiAnalyst.stats()
|
|
39809
39874
|
} : {},
|
|
39810
39875
|
releaseCooldown: (input) => this.releaseCooldown(input),
|
|
39811
|
-
...deps.dispatcher.
|
|
39876
|
+
...deps.dispatcher.publicUrlBase !== void 0 ? { publicUrlBase: deps.dispatcher.publicUrlBase } : {},
|
|
39877
|
+
...deps.dispatcher.tapBase !== void 0 ? { tapBase: deps.dispatcher.tapBase } : {}
|
|
39812
39878
|
});
|
|
39813
39879
|
this.outbox = new NcOutbox({
|
|
39814
39880
|
store: deps.store,
|
|
@@ -40806,7 +40872,11 @@ var NotificationCenter = class {
|
|
|
40806
40872
|
if (panel === null) return "unchanged";
|
|
40807
40873
|
const opening = alarmOpeningFromSensorEvent(event.kind, event.value);
|
|
40808
40874
|
if (opening.kind !== "reading") return "unchanged";
|
|
40809
|
-
|
|
40875
|
+
const changedAt = event.value?.["lastChangedAt"];
|
|
40876
|
+
return panel.observeOpening(event.sourceDeviceId, opening.reading, event.timestamp, {
|
|
40877
|
+
deferLiftAnnouncement: true,
|
|
40878
|
+
...typeof changedAt === "number" ? { changedAt } : {}
|
|
40879
|
+
});
|
|
40810
40880
|
}
|
|
40811
40881
|
/**
|
|
40812
40882
|
* "Watched again", once the event that lifted the sensor has been EVALUATED (D706).
|
|
@@ -42293,23 +42363,50 @@ var NotificationCenter = class {
|
|
|
42293
42363
|
});
|
|
42294
42364
|
}
|
|
42295
42365
|
/**
|
|
42296
|
-
*
|
|
42297
|
-
*
|
|
42366
|
+
* The one actuation seam of this centre — a rule's sequence step, a granted button.
|
|
42367
|
+
*
|
|
42368
|
+
* A press of THIS node's panel `trigger` never reaches the actuator: with a match
|
|
42369
|
+
* (`origin`) it is {@link NcAlarmPanelPort.triggerFromRule}, synchronously, the source
|
|
42370
|
+
* in the call. The join is HERE rather than in the runner: the runner actuates devices
|
|
42371
|
+
* generically and must not learn what an alarm is, while this class holds the panel.
|
|
42298
42372
|
*
|
|
42299
|
-
*
|
|
42300
|
-
*
|
|
42301
|
-
*
|
|
42302
|
-
*
|
|
42303
|
-
*
|
|
42373
|
+
* Without a match, the press has no sensor to check against the arm's exclusions, so
|
|
42374
|
+
* while the arm EXCLUDES anything it is REFUSED (fail-closed, D735): an anonymous press
|
|
42375
|
+
* from this seam is exactly what sounded the siren off an excluded contact on
|
|
42376
|
+
* 2026-10-07. A person's manual trigger (the panel's cap, from the UI) does not come
|
|
42377
|
+
* through here and is unaffected.
|
|
42304
42378
|
*/
|
|
42305
42379
|
async actuateWithAttribution(input) {
|
|
42380
|
+
const panel = this.alarmPanel;
|
|
42381
|
+
if (panel !== null && input.deviceId === panel.deviceId && input.cap === ALARM_PANEL_CAP && input.method === "trigger" && this.pressLocalPanel(panel, input.origin)) return;
|
|
42306
42382
|
const actuate = this.deps.actuate;
|
|
42307
42383
|
if (actuate === void 0) throw new Error("no actuator is wired on this node");
|
|
42308
|
-
const panel = this.alarmPanel;
|
|
42309
|
-
if (panel !== null && input.origin !== void 0 && input.deviceId === panel.deviceId && input.cap === ALARM_PANEL_CAP && input.method === "trigger") panel.noteRuleTrigger(input.origin);
|
|
42310
42384
|
await actuate(input);
|
|
42311
42385
|
}
|
|
42312
42386
|
/**
|
|
42387
|
+
* A press of this node's panel from the actuation seam; true when it was handled here.
|
|
42388
|
+
* With a source: the panel's rule press. Without one: refused while the arm excludes any
|
|
42389
|
+
* sensor — an ERROR line and a throw, so the sequence step reports failed instead of
|
|
42390
|
+
* looking done. Without one and nothing excluded: false, and the press takes the cap
|
|
42391
|
+
* like any manual press (there is no sensor whose exclusion it could skip).
|
|
42392
|
+
*/
|
|
42393
|
+
pressLocalPanel(panel, origin) {
|
|
42394
|
+
if (origin !== void 0) {
|
|
42395
|
+
panel.triggerFromRule(origin);
|
|
42396
|
+
return true;
|
|
42397
|
+
}
|
|
42398
|
+
const excluded = panel.excludedDevices();
|
|
42399
|
+
if (excluded.length === 0) return false;
|
|
42400
|
+
this.logger.error("alarm trigger REFUSED — a rule-action press carried no source while the arm excludes sensors", {
|
|
42401
|
+
tags: { deviceId: panel.deviceId },
|
|
42402
|
+
meta: {
|
|
42403
|
+
excluded: excluded.join(","),
|
|
42404
|
+
state: panel.currentState()
|
|
42405
|
+
}
|
|
42406
|
+
});
|
|
42407
|
+
throw new Error("alarm trigger refused: no source, and the current arm excludes sensors");
|
|
42408
|
+
}
|
|
42409
|
+
/**
|
|
42313
42410
|
* Device names from the in-process directory — no I/O, so the arm sentence
|
|
42314
42411
|
* can be composed on the publish path.
|
|
42315
42412
|
*
|
|
@@ -43197,13 +43294,9 @@ var NotificationCenter = class {
|
|
|
43197
43294
|
}
|
|
43198
43295
|
const origin = fireRefFor(rule, subject, kind);
|
|
43199
43296
|
const phase = panel.classifyRuleMatch(origin);
|
|
43200
|
-
|
|
43201
|
-
|
|
43202
|
-
|
|
43203
|
-
method: "trigger",
|
|
43204
|
-
args: {},
|
|
43205
|
-
origin
|
|
43206
|
-
}).catch((err) => {
|
|
43297
|
+
try {
|
|
43298
|
+
panel.triggerFromRule(origin);
|
|
43299
|
+
} catch (err) {
|
|
43207
43300
|
this.logger.warn("alarm panel auto-trigger failed", {
|
|
43208
43301
|
tags: { deviceId: subject.deviceId },
|
|
43209
43302
|
meta: {
|
|
@@ -43213,7 +43306,7 @@ var NotificationCenter = class {
|
|
|
43213
43306
|
error: err instanceof Error ? err.message : String(err)
|
|
43214
43307
|
}
|
|
43215
43308
|
});
|
|
43216
|
-
}
|
|
43309
|
+
}
|
|
43217
43310
|
return phase;
|
|
43218
43311
|
}
|
|
43219
43312
|
/**
|
|
@@ -101503,9 +101596,15 @@ async function projectSensorMarkers(deps, data, timestamp) {
|
|
|
101503
101596
|
*
|
|
101504
101597
|
* - The RECOVERY family. The Zigbee entity flaps `off → unavailable → off`
|
|
101505
101598
|
* (03:22:37→03:23:28, 03:56:51→03:58:19, 04:40:05→04:45:45). The HA
|
|
101506
|
-
* provider
|
|
101507
|
-
* `entryOpen`
|
|
101508
|
-
* `last_changed`. The mirror
|
|
101599
|
+
* provider dropped the `unavailable` push and wrote the recovery, whose
|
|
101600
|
+
* `entryOpen` was still `false` and whose `lastChangedAt` was HA's fresh
|
|
101601
|
+
* `last_changed`. The mirror diffed, and emitted. Since D735 the provider
|
|
101602
|
+
* keeps the change time of a value that did not change
|
|
101603
|
+
* (`addon-provider-homeassistant` `ha-change-stamp.ts`), because this gate
|
|
101604
|
+
* cannot fix the ACTIVE half: with its memory empty (a hub restart), a
|
|
101605
|
+
* recovery `on` with a fresh stamp is indistinguishable here from an
|
|
101606
|
+
* opening — that is how four excluded contacts pressed the armed panel on
|
|
101607
|
+
* 2026-10-07 02:32Z.
|
|
101509
101608
|
* - The COLD-SLICE family. `HaBinarySensorDevice`'s constructor writes
|
|
101510
101609
|
* `{ entryOpen: false, lastChangedAt: 0 }` on every device rebuild; the
|
|
101511
101610
|
* real state lands seconds later with a `lastChangedAt` hours old. Two
|
|
@@ -106595,51 +106694,17 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
106595
106694
|
};
|
|
106596
106695
|
}
|
|
106597
106696
|
}
|
|
106598
|
-
/** The operator's marked notification endpoint, or null (AUTO / unavailable). */
|
|
106599
|
-
async markedNotificationEndpoint() {
|
|
106600
|
-
try {
|
|
106601
|
-
return (await this.ctx.api.localNetwork.getNotificationEndpoint.query()).baseUrl ?? void 0;
|
|
106602
|
-
} catch {
|
|
106603
|
-
return;
|
|
106604
|
-
}
|
|
106605
|
-
}
|
|
106606
106697
|
/**
|
|
106607
|
-
* The ranked base-URL candidates a notification LINK may use
|
|
106698
|
+
* The ranked base-URL candidates a notification LINK may use — every link
|
|
106699
|
+
* except the tap: the hub's public endpoint (D734, `link-endpoints.ts`).
|
|
106608
106700
|
*
|
|
106609
|
-
* Shared by the artifact plane (media)
|
|
106610
|
-
*
|
|
106611
|
-
* minting links on different hosts
|
|
106701
|
+
* Shared by the artifact plane (media), the action plane (buttons) and the
|
|
106702
|
+
* admin page a system event taps into — one resolver, because a second copy
|
|
106703
|
+
* would drift and the planes would start minting links on different hosts
|
|
106704
|
+
* for the same notification.
|
|
106612
106705
|
*/
|
|
106613
106706
|
async ncEndpoints() {
|
|
106614
|
-
return
|
|
106615
|
-
markedBaseUrl: await this.markedNotificationEndpoint(),
|
|
106616
|
-
getConnected: async () => {
|
|
106617
|
-
const status = await this.ctx.api.networkAccess.getStatus.query();
|
|
106618
|
-
this.ctx.logger.debug("artifact base-url: connected ingress", { meta: {
|
|
106619
|
-
connected: status.connected,
|
|
106620
|
-
url: status.endpoint?.url ?? null,
|
|
106621
|
-
protocol: status.endpoint?.protocol ?? null
|
|
106622
|
-
} });
|
|
106623
|
-
return status.connected && status.endpoint !== null ? {
|
|
106624
|
-
url: status.endpoint.url,
|
|
106625
|
-
protocol: status.endpoint.protocol
|
|
106626
|
-
} : null;
|
|
106627
|
-
},
|
|
106628
|
-
listExternal: async () => {
|
|
106629
|
-
return (await this.ctx.api.networkAccess.listEndpoints.query()).map((e) => ({
|
|
106630
|
-
url: e.url,
|
|
106631
|
-
protocol: e.protocol
|
|
106632
|
-
}));
|
|
106633
|
-
},
|
|
106634
|
-
listLan: async (port) => {
|
|
106635
|
-
return (await this.ctx.api.localNetwork.getConnectionEndpoints.query({ port })).endpoints.flatMap((e) => e.kind === "app-link" ? [] : [{
|
|
106636
|
-
baseUrl: e.baseUrl,
|
|
106637
|
-
kind: e.kind,
|
|
106638
|
-
priority: e.priority
|
|
106639
|
-
}]);
|
|
106640
|
-
},
|
|
106641
|
-
logger: this.ctx.logger
|
|
106642
|
-
});
|
|
106707
|
+
return notificationLinkEndpoints(this.ctx.api, this.ctx.logger);
|
|
106643
106708
|
}
|
|
106644
106709
|
/**
|
|
106645
106710
|
* Serve the NC ACTION plane: the public route a notification button lands on.
|
|
@@ -109225,8 +109290,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
109225
109290
|
resolveFfmpeg: () => this.ctx.deps.ensureFfmpeg()
|
|
109226
109291
|
}), req),
|
|
109227
109292
|
publishArtifact: async (bytes, mime, deviceId) => await this.ncArtifactPlane?.publish(bytes, mime, deviceId) ?? null,
|
|
109228
|
-
|
|
109229
|
-
|
|
109293
|
+
publicUrlBase: async () => pickArtifactBaseUrl(await this.ncEndpoints()),
|
|
109294
|
+
tapBase: async () => viewerLinkBaseOf(await this.ctx.api.localNetwork.getNotificationEndpoint.query()),
|
|
109230
109295
|
indexArtifact: async (bytes, mime, meta) => await this.ncArtifactPlane?.publishIndexed(bytes, mime, meta) ?? {
|
|
109231
109296
|
url: null,
|
|
109232
109297
|
artifactId: null
|