@camstack/addon-notifiers 1.2.20 → 1.2.21
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/addon.js +177 -15
- package/dist/addon.mjs +177 -15
- package/package.json +1 -1
package/dist/addon.js
CHANGED
|
@@ -13111,11 +13111,33 @@ var NotificationFormatSchema = _enum([
|
|
|
13111
13111
|
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
13112
13112
|
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
13113
13113
|
* differently.
|
|
13114
|
+
*
|
|
13115
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
13116
|
+
*
|
|
13117
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
13118
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
13119
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
13120
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
13121
|
+
* decides its glyph, which is the only place that decision can be made
|
|
13122
|
+
* honestly.
|
|
13123
|
+
*
|
|
13124
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
13125
|
+
* `disarm` straight through; iOS feeds that string to
|
|
13126
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
13127
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
13128
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
13129
|
+
*
|
|
13130
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
13131
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
13132
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
13133
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
13134
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
13114
13135
|
*/
|
|
13115
13136
|
var NotificationActionIconSchema = _enum([
|
|
13116
13137
|
"acknowledge",
|
|
13117
13138
|
"dismiss",
|
|
13118
13139
|
"silence",
|
|
13140
|
+
"snooze",
|
|
13119
13141
|
"view",
|
|
13120
13142
|
"play",
|
|
13121
13143
|
"open",
|
|
@@ -13123,9 +13145,13 @@ var NotificationActionIconSchema = _enum([
|
|
|
13123
13145
|
"lock",
|
|
13124
13146
|
"unlock",
|
|
13125
13147
|
"arm",
|
|
13148
|
+
"arm-home",
|
|
13149
|
+
"arm-away",
|
|
13150
|
+
"arm-night",
|
|
13126
13151
|
"disarm",
|
|
13127
13152
|
"light",
|
|
13128
|
-
"alert"
|
|
13153
|
+
"alert",
|
|
13154
|
+
"camera"
|
|
13129
13155
|
]);
|
|
13130
13156
|
/** A single tap-through action button. */
|
|
13131
13157
|
var NotificationActionSchema = object({
|
|
@@ -13339,6 +13365,24 @@ var notificationOutputCapability = {
|
|
|
13339
13365
|
}), _void(), { kind: "mutation" })
|
|
13340
13366
|
}
|
|
13341
13367
|
};
|
|
13368
|
+
new Set([
|
|
13369
|
+
{
|
|
13370
|
+
id: "person",
|
|
13371
|
+
name: "Person"
|
|
13372
|
+
},
|
|
13373
|
+
{
|
|
13374
|
+
id: "vehicle",
|
|
13375
|
+
name: "Vehicle"
|
|
13376
|
+
},
|
|
13377
|
+
{
|
|
13378
|
+
id: "animal",
|
|
13379
|
+
name: "Animal"
|
|
13380
|
+
},
|
|
13381
|
+
{
|
|
13382
|
+
id: "package",
|
|
13383
|
+
name: "Package"
|
|
13384
|
+
}
|
|
13385
|
+
].map((l) => l.id));
|
|
13342
13386
|
var COCO_TO_MACRO = {
|
|
13343
13387
|
mapping: {
|
|
13344
13388
|
person: "person",
|
|
@@ -14136,6 +14180,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14136
14180
|
"alarm-triggered",
|
|
14137
14181
|
"alarm-armed",
|
|
14138
14182
|
"alarm-disarmed",
|
|
14183
|
+
"alarm-arming",
|
|
14184
|
+
"alarm-arm-refused",
|
|
14139
14185
|
"camera-online",
|
|
14140
14186
|
"camera-offline",
|
|
14141
14187
|
"camera-disabled",
|
|
@@ -17368,6 +17414,22 @@ var detectionFpsField = {
|
|
|
17368
17414
|
default: 10,
|
|
17369
17415
|
step: 1
|
|
17370
17416
|
};
|
|
17417
|
+
/**
|
|
17418
|
+
* The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
|
|
17419
|
+
*
|
|
17420
|
+
* The recheck is now on by default (a parked car is invisible to occupancy
|
|
17421
|
+
* rules until the stationary registry has been rebuilt by motion, which after a
|
|
17422
|
+
* restart may be never on a quiet camera). Each cycle re-subscribes a detection
|
|
17423
|
+
* session — an RTSP re-dial — so the switch is only affordable at a WIDE
|
|
17424
|
+
* interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
|
|
17425
|
+
* 30 s. A parked car is therefore counted within 5 minutes of a restart.
|
|
17426
|
+
*
|
|
17427
|
+
* Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
|
|
17428
|
+
* the host validates `attachCamera` against ITS copy of this schema, so a
|
|
17429
|
+
* runner asked for 600 would be rejected by the hub until a `@camstack/server`
|
|
17430
|
+
* carrying the wider bound is installed everywhere. 300 is the widest value
|
|
17431
|
+
* that ships with an addon deploy.
|
|
17432
|
+
*/
|
|
17371
17433
|
var occupancyRecheckSecField = {
|
|
17372
17434
|
min: 0,
|
|
17373
17435
|
max: 300,
|
|
@@ -17569,15 +17631,21 @@ var RunnerCameraConfigSchema = object({
|
|
|
17569
17631
|
*/
|
|
17570
17632
|
onboardMotionDrivesAnalyzer: boolean().default(true),
|
|
17571
17633
|
/**
|
|
17572
|
-
* Master toggle for the occupancy re-check. When `false`
|
|
17573
|
-
*
|
|
17574
|
-
* this is off by default because the recheck re-subscribes a detection session
|
|
17575
|
-
* every N seconds while `watching`, a major source of pull-decoder re-dial
|
|
17576
|
-
* churn (each cycle creates+tears a session → RTSP re-dial → latency). The
|
|
17634
|
+
* Master toggle for the occupancy re-check. When `false` the runner never arms
|
|
17635
|
+
* the periodic recheck timer, regardless of `occupancyRecheckSec`; the
|
|
17577
17636
|
* `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
|
|
17578
17637
|
* (and only render) when this is enabled.
|
|
17579
|
-
|
|
17580
|
-
|
|
17638
|
+
*
|
|
17639
|
+
* DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
|
|
17640
|
+
* recheck re-subscribes a detection session every N seconds while `watching`
|
|
17641
|
+
* — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
|
|
17642
|
+
* pull-decoder churn source. What that bought was a blind spot: a STATIONARY
|
|
17643
|
+
* object is counted only while the stationary registry holds it, and the
|
|
17644
|
+
* registry rebuilds from motion, so after a restart a parked car was invisible
|
|
17645
|
+
* to every occupancy rule until something moved in front of it. The churn is
|
|
17646
|
+
* now paid on the interval instead — see `occupancyRecheckSecField`.
|
|
17647
|
+
*/
|
|
17648
|
+
occupancyRecheckEnabled: boolean().default(true),
|
|
17581
17649
|
occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
|
|
17582
17650
|
occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
|
|
17583
17651
|
/**
|
|
@@ -32433,6 +32501,21 @@ var ntfyAdapter = {
|
|
|
32433
32501
|
max: 1
|
|
32434
32502
|
},
|
|
32435
32503
|
actions: MAX_ACTIONS$1,
|
|
32504
|
+
/**
|
|
32505
|
+
* ntfy has NO per-action icon, and this says so out loud.
|
|
32506
|
+
*
|
|
32507
|
+
* Its action object is `{action, label, url, clear, headers, body}` and the
|
|
32508
|
+
* `X-Actions` header form is `action, label, url[, extras]` — there is no
|
|
32509
|
+
* icon field in either, and the `icon` ntfy does have is the NOTIFICATION's
|
|
32510
|
+
* icon (an attachment URL, handled above), not a per-button one.
|
|
32511
|
+
*
|
|
32512
|
+
* Left EXPLICIT rather than absent: absent and `false` behave identically
|
|
32513
|
+
* (the degrade engine reads a missing value as unsupported), but only one
|
|
32514
|
+
* of them proves somebody checked. The degrade engine then strips the icon
|
|
32515
|
+
* centrally and puts `actionIcons` in `renderedAs.dropped`, so the loss is
|
|
32516
|
+
* reported instead of being discovered on a phone.
|
|
32517
|
+
*/
|
|
32518
|
+
actionIcons: false,
|
|
32436
32519
|
levels: [
|
|
32437
32520
|
{
|
|
32438
32521
|
id: "min",
|
|
@@ -33616,6 +33699,82 @@ function userIdsFrom(config) {
|
|
|
33616
33699
|
return [];
|
|
33617
33700
|
}
|
|
33618
33701
|
/**
|
|
33702
|
+
* Canonical icon token → the string Zentik's client actually renders.
|
|
33703
|
+
*
|
|
33704
|
+
* ── EVIDENCE (read from the Zentik sources, not guessed) ────────────
|
|
33705
|
+
* `NotificationActionDto.icon` is `@IsOptional() @IsString()` — the BACKEND
|
|
33706
|
+
* validates nothing, so a wrong value is accepted, stored and delivered. The
|
|
33707
|
+
* rendering happens on the client, and both iOS paths do the same thing
|
|
33708
|
+
* (`ZentikNotificationService/NotificationService.swift`,
|
|
33709
|
+
* `ZentikNotificationContentExtension/NotificationViewController.swift`):
|
|
33710
|
+
*
|
|
33711
|
+
* let actualIconName = iconName.hasPrefix("sfsymbols:")
|
|
33712
|
+
* ? String(iconName.dropFirst("sfsymbols:".count)) : iconName
|
|
33713
|
+
* icon = UNNotificationActionIcon(systemImageName: actualIconName)
|
|
33714
|
+
*
|
|
33715
|
+
* So the value is an **SF Symbol name**, with an optional `sfsymbols:` prefix
|
|
33716
|
+
* that is stripped. Zentik's own UI confirms the convention: its icon field
|
|
33717
|
+
* placeholder is "e.g., house.fill, heart, gear" and its picker writes
|
|
33718
|
+
* `sfsymbols:${iconName}`.
|
|
33719
|
+
*
|
|
33720
|
+
* `UNNotificationActionIcon(systemImageName:)` with a name iOS does not know
|
|
33721
|
+
* renders NOTHING — silently. That is the whole failure: we sent `silence`,
|
|
33722
|
+
* `arm` and `disarm`, none of which are SF Symbols, and every button arrived
|
|
33723
|
+
* blank with nothing anywhere saying why.
|
|
33724
|
+
*
|
|
33725
|
+
* The prefix is emitted explicitly even though bare names work, because the
|
|
33726
|
+
* bare form is indistinguishable from an un-translated intent token in a
|
|
33727
|
+
* captured payload — and telling those two apart is what cost the time.
|
|
33728
|
+
*
|
|
33729
|
+
* A `Record` over the closed enum, NOT a lookup with a fallback: adding a
|
|
33730
|
+
* vocabulary member without deciding its glyph fails the build here.
|
|
33731
|
+
*
|
|
33732
|
+
* Android: Zentik's Android client does not render per-action icons at all
|
|
33733
|
+
* (an Android notification action needs a bundled drawable, not a name), so
|
|
33734
|
+
* this is an iOS/watchOS enrichment and a no-op elsewhere — which is a
|
|
33735
|
+
* cosmetic loss, not a wrong glyph.
|
|
33736
|
+
*/
|
|
33737
|
+
var ZENTIK_ACTION_ICONS = {
|
|
33738
|
+
acknowledge: "checkmark.circle",
|
|
33739
|
+
dismiss: "xmark.circle",
|
|
33740
|
+
silence: "bell.slash",
|
|
33741
|
+
snooze: "zzz",
|
|
33742
|
+
view: "eye",
|
|
33743
|
+
play: "play.fill",
|
|
33744
|
+
open: "door.left.hand.open",
|
|
33745
|
+
close: "door.left.hand.closed",
|
|
33746
|
+
lock: "lock.fill",
|
|
33747
|
+
unlock: "lock.open.fill",
|
|
33748
|
+
arm: "shield.fill",
|
|
33749
|
+
"arm-home": "house.fill",
|
|
33750
|
+
"arm-away": "figure.walk",
|
|
33751
|
+
"arm-night": "moon.fill",
|
|
33752
|
+
disarm: "shield.slash.fill",
|
|
33753
|
+
light: "lightbulb.fill",
|
|
33754
|
+
alert: "exclamationmark.triangle.fill",
|
|
33755
|
+
camera: "video.fill"
|
|
33756
|
+
};
|
|
33757
|
+
/** The `sfsymbols:` prefix Zentik's clients strip before resolving the symbol. */
|
|
33758
|
+
var SFSYMBOLS_PREFIX = "sfsymbols:";
|
|
33759
|
+
/**
|
|
33760
|
+
* The Zentik value for one icon token, or `undefined` when there is none.
|
|
33761
|
+
*
|
|
33762
|
+
* Typed on `string`, not on `NotificationActionIcon`, because that is what this
|
|
33763
|
+
* boundary actually receives: an addon deployed against a hub whose vocabulary
|
|
33764
|
+
* moved on hands over a token this build has never heard of. Omitting it is the
|
|
33765
|
+
* honest answer — forwarding it raw would put a string iOS ignores on the wire
|
|
33766
|
+
* and produce exactly the blank button this map exists to fix.
|
|
33767
|
+
*
|
|
33768
|
+
* `Object.hasOwn` and not a truthiness check: an inherited `toString` must not
|
|
33769
|
+
* read as a mapping.
|
|
33770
|
+
*/
|
|
33771
|
+
function zentikActionIcon(icon) {
|
|
33772
|
+
const table = ZENTIK_ACTION_ICONS;
|
|
33773
|
+
if (!Object.hasOwn(ZENTIK_ACTION_ICONS, icon)) return void 0;
|
|
33774
|
+
const symbol = table[icon];
|
|
33775
|
+
return symbol === void 0 ? void 0 : `${SFSYMBOLS_PREFIX}${symbol}`;
|
|
33776
|
+
}
|
|
33777
|
+
/**
|
|
33619
33778
|
* How a `BACKGROUND_CALL` names its request: `METHOD::url`.
|
|
33620
33779
|
*
|
|
33621
33780
|
* The whole reason the type exists here: a "silence this for 30 minutes"
|
|
@@ -33627,9 +33786,10 @@ var BACKGROUND_CALL_PREFIX = "POST::";
|
|
|
33627
33786
|
function actionsFor(prepared) {
|
|
33628
33787
|
const out = [];
|
|
33629
33788
|
for (const action of prepared.actions.slice(0, MAX_ACTIONS)) {
|
|
33789
|
+
const symbol = action.icon === void 0 ? void 0 : zentikActionIcon(action.icon);
|
|
33630
33790
|
const decoration = {
|
|
33631
33791
|
...action.destructive !== void 0 ? { destructive: action.destructive } : {},
|
|
33632
|
-
...
|
|
33792
|
+
...symbol !== void 0 ? { icon: symbol } : {}
|
|
33633
33793
|
};
|
|
33634
33794
|
if (action.url === void 0 || action.url.length === 0) {
|
|
33635
33795
|
out.push({
|
|
@@ -33821,12 +33981,14 @@ var zentikAdapter = {
|
|
|
33821
33981
|
format: ["text"],
|
|
33822
33982
|
clickUrl: true,
|
|
33823
33983
|
/**
|
|
33824
|
-
*
|
|
33825
|
-
*
|
|
33826
|
-
*
|
|
33827
|
-
*
|
|
33828
|
-
*
|
|
33829
|
-
*
|
|
33984
|
+
* A RENDERING claim, which it was not before.
|
|
33985
|
+
*
|
|
33986
|
+
* This used to say the intent name "travels verbatim … a name the app does
|
|
33987
|
+
* not know renders as no icon rather than as a wrong one" — true, and it
|
|
33988
|
+
* described a feature that never once drew an icon, because NO intent name
|
|
33989
|
+
* is an SF Symbol. The engine handed the icon over and the adapter put it
|
|
33990
|
+
* on the wire untranslated. `ZENTIK_ACTION_ICONS` is what makes this claim
|
|
33991
|
+
* honest; keep it exhaustive or this reverts to a pass-through that lies.
|
|
33830
33992
|
*/
|
|
33831
33993
|
actionIcons: true,
|
|
33832
33994
|
sound: true,
|
package/dist/addon.mjs
CHANGED
|
@@ -13084,11 +13084,33 @@ var NotificationFormatSchema = _enum([
|
|
|
13084
13084
|
* Named by INTENT, never by glyph. "check" would tie the vocabulary to one
|
|
13085
13085
|
* renderer's icon set; "acknowledge" survives an adapter that draws it
|
|
13086
13086
|
* differently.
|
|
13087
|
+
*
|
|
13088
|
+
* ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
|
|
13089
|
+
*
|
|
13090
|
+
* These names are for US. **No adapter may forward one verbatim.** Each maps
|
|
13091
|
+
* the whole set onto its own renderer's vocabulary through a
|
|
13092
|
+
* `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
|
|
13093
|
+
* fallback, so adding a member here fails every adapter's build until someone
|
|
13094
|
+
* decides its glyph, which is the only place that decision can be made
|
|
13095
|
+
* honestly.
|
|
13096
|
+
*
|
|
13097
|
+
* This paragraph is the bug. Zentik declared `actionIcons: true` and passed
|
|
13098
|
+
* `disarm` straight through; iOS feeds that string to
|
|
13099
|
+
* `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
|
|
13100
|
+
* and every snooze and alarm button arrived BLANK. A pass-through is not a
|
|
13101
|
+
* mapping, and "the field is documented" is not "the value renders".
|
|
13102
|
+
*
|
|
13103
|
+
* Adding a member is TRAIN-BOUND. The enum lives in the published
|
|
13104
|
+
* `@camstack/server` closure and the cap seam validates against the HUB's copy,
|
|
13105
|
+
* so an addon that emits a token the running hub does not know does not lose an
|
|
13106
|
+
* icon — its whole `send` fails Zod validation and the notification never
|
|
13107
|
+
* arrives. Never emit a new token from an addon before the train carrying it.
|
|
13087
13108
|
*/
|
|
13088
13109
|
var NotificationActionIconSchema = _enum([
|
|
13089
13110
|
"acknowledge",
|
|
13090
13111
|
"dismiss",
|
|
13091
13112
|
"silence",
|
|
13113
|
+
"snooze",
|
|
13092
13114
|
"view",
|
|
13093
13115
|
"play",
|
|
13094
13116
|
"open",
|
|
@@ -13096,9 +13118,13 @@ var NotificationActionIconSchema = _enum([
|
|
|
13096
13118
|
"lock",
|
|
13097
13119
|
"unlock",
|
|
13098
13120
|
"arm",
|
|
13121
|
+
"arm-home",
|
|
13122
|
+
"arm-away",
|
|
13123
|
+
"arm-night",
|
|
13099
13124
|
"disarm",
|
|
13100
13125
|
"light",
|
|
13101
|
-
"alert"
|
|
13126
|
+
"alert",
|
|
13127
|
+
"camera"
|
|
13102
13128
|
]);
|
|
13103
13129
|
/** A single tap-through action button. */
|
|
13104
13130
|
var NotificationActionSchema = object({
|
|
@@ -13312,6 +13338,24 @@ var notificationOutputCapability = {
|
|
|
13312
13338
|
}), _void(), { kind: "mutation" })
|
|
13313
13339
|
}
|
|
13314
13340
|
};
|
|
13341
|
+
new Set([
|
|
13342
|
+
{
|
|
13343
|
+
id: "person",
|
|
13344
|
+
name: "Person"
|
|
13345
|
+
},
|
|
13346
|
+
{
|
|
13347
|
+
id: "vehicle",
|
|
13348
|
+
name: "Vehicle"
|
|
13349
|
+
},
|
|
13350
|
+
{
|
|
13351
|
+
id: "animal",
|
|
13352
|
+
name: "Animal"
|
|
13353
|
+
},
|
|
13354
|
+
{
|
|
13355
|
+
id: "package",
|
|
13356
|
+
name: "Package"
|
|
13357
|
+
}
|
|
13358
|
+
].map((l) => l.id));
|
|
13315
13359
|
var COCO_TO_MACRO = {
|
|
13316
13360
|
mapping: {
|
|
13317
13361
|
person: "person",
|
|
@@ -14109,6 +14153,8 @@ var NcSystemEventKindSchema = _enum([
|
|
|
14109
14153
|
"alarm-triggered",
|
|
14110
14154
|
"alarm-armed",
|
|
14111
14155
|
"alarm-disarmed",
|
|
14156
|
+
"alarm-arming",
|
|
14157
|
+
"alarm-arm-refused",
|
|
14112
14158
|
"camera-online",
|
|
14113
14159
|
"camera-offline",
|
|
14114
14160
|
"camera-disabled",
|
|
@@ -17341,6 +17387,22 @@ var detectionFpsField = {
|
|
|
17341
17387
|
default: 10,
|
|
17342
17388
|
step: 1
|
|
17343
17389
|
};
|
|
17390
|
+
/**
|
|
17391
|
+
* The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
|
|
17392
|
+
*
|
|
17393
|
+
* The recheck is now on by default (a parked car is invisible to occupancy
|
|
17394
|
+
* rules until the stationary registry has been rebuilt by motion, which after a
|
|
17395
|
+
* restart may be never on a quiet camera). Each cycle re-subscribes a detection
|
|
17396
|
+
* session — an RTSP re-dial — so the switch is only affordable at a WIDE
|
|
17397
|
+
* interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
|
|
17398
|
+
* 30 s. A parked car is therefore counted within 5 minutes of a restart.
|
|
17399
|
+
*
|
|
17400
|
+
* Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
|
|
17401
|
+
* the host validates `attachCamera` against ITS copy of this schema, so a
|
|
17402
|
+
* runner asked for 600 would be rejected by the hub until a `@camstack/server`
|
|
17403
|
+
* carrying the wider bound is installed everywhere. 300 is the widest value
|
|
17404
|
+
* that ships with an addon deploy.
|
|
17405
|
+
*/
|
|
17344
17406
|
var occupancyRecheckSecField = {
|
|
17345
17407
|
min: 0,
|
|
17346
17408
|
max: 300,
|
|
@@ -17542,15 +17604,21 @@ var RunnerCameraConfigSchema = object({
|
|
|
17542
17604
|
*/
|
|
17543
17605
|
onboardMotionDrivesAnalyzer: boolean().default(true),
|
|
17544
17606
|
/**
|
|
17545
|
-
* Master toggle for the occupancy re-check. When `false`
|
|
17546
|
-
*
|
|
17547
|
-
* this is off by default because the recheck re-subscribes a detection session
|
|
17548
|
-
* every N seconds while `watching`, a major source of pull-decoder re-dial
|
|
17549
|
-
* churn (each cycle creates+tears a session → RTSP re-dial → latency). The
|
|
17607
|
+
* Master toggle for the occupancy re-check. When `false` the runner never arms
|
|
17608
|
+
* the periodic recheck timer, regardless of `occupancyRecheckSec`; the
|
|
17550
17609
|
* `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
|
|
17551
17610
|
* (and only render) when this is enabled.
|
|
17552
|
-
|
|
17553
|
-
|
|
17611
|
+
*
|
|
17612
|
+
* DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
|
|
17613
|
+
* recheck re-subscribes a detection session every N seconds while `watching`
|
|
17614
|
+
* — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
|
|
17615
|
+
* pull-decoder churn source. What that bought was a blind spot: a STATIONARY
|
|
17616
|
+
* object is counted only while the stationary registry holds it, and the
|
|
17617
|
+
* registry rebuilds from motion, so after a restart a parked car was invisible
|
|
17618
|
+
* to every occupancy rule until something moved in front of it. The churn is
|
|
17619
|
+
* now paid on the interval instead — see `occupancyRecheckSecField`.
|
|
17620
|
+
*/
|
|
17621
|
+
occupancyRecheckEnabled: boolean().default(true),
|
|
17554
17622
|
occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
|
|
17555
17623
|
occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
|
|
17556
17624
|
/**
|
|
@@ -32406,6 +32474,21 @@ var ntfyAdapter = {
|
|
|
32406
32474
|
max: 1
|
|
32407
32475
|
},
|
|
32408
32476
|
actions: MAX_ACTIONS$1,
|
|
32477
|
+
/**
|
|
32478
|
+
* ntfy has NO per-action icon, and this says so out loud.
|
|
32479
|
+
*
|
|
32480
|
+
* Its action object is `{action, label, url, clear, headers, body}` and the
|
|
32481
|
+
* `X-Actions` header form is `action, label, url[, extras]` — there is no
|
|
32482
|
+
* icon field in either, and the `icon` ntfy does have is the NOTIFICATION's
|
|
32483
|
+
* icon (an attachment URL, handled above), not a per-button one.
|
|
32484
|
+
*
|
|
32485
|
+
* Left EXPLICIT rather than absent: absent and `false` behave identically
|
|
32486
|
+
* (the degrade engine reads a missing value as unsupported), but only one
|
|
32487
|
+
* of them proves somebody checked. The degrade engine then strips the icon
|
|
32488
|
+
* centrally and puts `actionIcons` in `renderedAs.dropped`, so the loss is
|
|
32489
|
+
* reported instead of being discovered on a phone.
|
|
32490
|
+
*/
|
|
32491
|
+
actionIcons: false,
|
|
32409
32492
|
levels: [
|
|
32410
32493
|
{
|
|
32411
32494
|
id: "min",
|
|
@@ -33589,6 +33672,82 @@ function userIdsFrom(config) {
|
|
|
33589
33672
|
return [];
|
|
33590
33673
|
}
|
|
33591
33674
|
/**
|
|
33675
|
+
* Canonical icon token → the string Zentik's client actually renders.
|
|
33676
|
+
*
|
|
33677
|
+
* ── EVIDENCE (read from the Zentik sources, not guessed) ────────────
|
|
33678
|
+
* `NotificationActionDto.icon` is `@IsOptional() @IsString()` — the BACKEND
|
|
33679
|
+
* validates nothing, so a wrong value is accepted, stored and delivered. The
|
|
33680
|
+
* rendering happens on the client, and both iOS paths do the same thing
|
|
33681
|
+
* (`ZentikNotificationService/NotificationService.swift`,
|
|
33682
|
+
* `ZentikNotificationContentExtension/NotificationViewController.swift`):
|
|
33683
|
+
*
|
|
33684
|
+
* let actualIconName = iconName.hasPrefix("sfsymbols:")
|
|
33685
|
+
* ? String(iconName.dropFirst("sfsymbols:".count)) : iconName
|
|
33686
|
+
* icon = UNNotificationActionIcon(systemImageName: actualIconName)
|
|
33687
|
+
*
|
|
33688
|
+
* So the value is an **SF Symbol name**, with an optional `sfsymbols:` prefix
|
|
33689
|
+
* that is stripped. Zentik's own UI confirms the convention: its icon field
|
|
33690
|
+
* placeholder is "e.g., house.fill, heart, gear" and its picker writes
|
|
33691
|
+
* `sfsymbols:${iconName}`.
|
|
33692
|
+
*
|
|
33693
|
+
* `UNNotificationActionIcon(systemImageName:)` with a name iOS does not know
|
|
33694
|
+
* renders NOTHING — silently. That is the whole failure: we sent `silence`,
|
|
33695
|
+
* `arm` and `disarm`, none of which are SF Symbols, and every button arrived
|
|
33696
|
+
* blank with nothing anywhere saying why.
|
|
33697
|
+
*
|
|
33698
|
+
* The prefix is emitted explicitly even though bare names work, because the
|
|
33699
|
+
* bare form is indistinguishable from an un-translated intent token in a
|
|
33700
|
+
* captured payload — and telling those two apart is what cost the time.
|
|
33701
|
+
*
|
|
33702
|
+
* A `Record` over the closed enum, NOT a lookup with a fallback: adding a
|
|
33703
|
+
* vocabulary member without deciding its glyph fails the build here.
|
|
33704
|
+
*
|
|
33705
|
+
* Android: Zentik's Android client does not render per-action icons at all
|
|
33706
|
+
* (an Android notification action needs a bundled drawable, not a name), so
|
|
33707
|
+
* this is an iOS/watchOS enrichment and a no-op elsewhere — which is a
|
|
33708
|
+
* cosmetic loss, not a wrong glyph.
|
|
33709
|
+
*/
|
|
33710
|
+
var ZENTIK_ACTION_ICONS = {
|
|
33711
|
+
acknowledge: "checkmark.circle",
|
|
33712
|
+
dismiss: "xmark.circle",
|
|
33713
|
+
silence: "bell.slash",
|
|
33714
|
+
snooze: "zzz",
|
|
33715
|
+
view: "eye",
|
|
33716
|
+
play: "play.fill",
|
|
33717
|
+
open: "door.left.hand.open",
|
|
33718
|
+
close: "door.left.hand.closed",
|
|
33719
|
+
lock: "lock.fill",
|
|
33720
|
+
unlock: "lock.open.fill",
|
|
33721
|
+
arm: "shield.fill",
|
|
33722
|
+
"arm-home": "house.fill",
|
|
33723
|
+
"arm-away": "figure.walk",
|
|
33724
|
+
"arm-night": "moon.fill",
|
|
33725
|
+
disarm: "shield.slash.fill",
|
|
33726
|
+
light: "lightbulb.fill",
|
|
33727
|
+
alert: "exclamationmark.triangle.fill",
|
|
33728
|
+
camera: "video.fill"
|
|
33729
|
+
};
|
|
33730
|
+
/** The `sfsymbols:` prefix Zentik's clients strip before resolving the symbol. */
|
|
33731
|
+
var SFSYMBOLS_PREFIX = "sfsymbols:";
|
|
33732
|
+
/**
|
|
33733
|
+
* The Zentik value for one icon token, or `undefined` when there is none.
|
|
33734
|
+
*
|
|
33735
|
+
* Typed on `string`, not on `NotificationActionIcon`, because that is what this
|
|
33736
|
+
* boundary actually receives: an addon deployed against a hub whose vocabulary
|
|
33737
|
+
* moved on hands over a token this build has never heard of. Omitting it is the
|
|
33738
|
+
* honest answer — forwarding it raw would put a string iOS ignores on the wire
|
|
33739
|
+
* and produce exactly the blank button this map exists to fix.
|
|
33740
|
+
*
|
|
33741
|
+
* `Object.hasOwn` and not a truthiness check: an inherited `toString` must not
|
|
33742
|
+
* read as a mapping.
|
|
33743
|
+
*/
|
|
33744
|
+
function zentikActionIcon(icon) {
|
|
33745
|
+
const table = ZENTIK_ACTION_ICONS;
|
|
33746
|
+
if (!Object.hasOwn(ZENTIK_ACTION_ICONS, icon)) return void 0;
|
|
33747
|
+
const symbol = table[icon];
|
|
33748
|
+
return symbol === void 0 ? void 0 : `${SFSYMBOLS_PREFIX}${symbol}`;
|
|
33749
|
+
}
|
|
33750
|
+
/**
|
|
33592
33751
|
* How a `BACKGROUND_CALL` names its request: `METHOD::url`.
|
|
33593
33752
|
*
|
|
33594
33753
|
* The whole reason the type exists here: a "silence this for 30 minutes"
|
|
@@ -33600,9 +33759,10 @@ var BACKGROUND_CALL_PREFIX = "POST::";
|
|
|
33600
33759
|
function actionsFor(prepared) {
|
|
33601
33760
|
const out = [];
|
|
33602
33761
|
for (const action of prepared.actions.slice(0, MAX_ACTIONS)) {
|
|
33762
|
+
const symbol = action.icon === void 0 ? void 0 : zentikActionIcon(action.icon);
|
|
33603
33763
|
const decoration = {
|
|
33604
33764
|
...action.destructive !== void 0 ? { destructive: action.destructive } : {},
|
|
33605
|
-
...
|
|
33765
|
+
...symbol !== void 0 ? { icon: symbol } : {}
|
|
33606
33766
|
};
|
|
33607
33767
|
if (action.url === void 0 || action.url.length === 0) {
|
|
33608
33768
|
out.push({
|
|
@@ -33794,12 +33954,14 @@ var zentikAdapter = {
|
|
|
33794
33954
|
format: ["text"],
|
|
33795
33955
|
clickUrl: true,
|
|
33796
33956
|
/**
|
|
33797
|
-
*
|
|
33798
|
-
*
|
|
33799
|
-
*
|
|
33800
|
-
*
|
|
33801
|
-
*
|
|
33802
|
-
*
|
|
33957
|
+
* A RENDERING claim, which it was not before.
|
|
33958
|
+
*
|
|
33959
|
+
* This used to say the intent name "travels verbatim … a name the app does
|
|
33960
|
+
* not know renders as no icon rather than as a wrong one" — true, and it
|
|
33961
|
+
* described a feature that never once drew an icon, because NO intent name
|
|
33962
|
+
* is an SF Symbol. The engine handed the icon over and the adapter put it
|
|
33963
|
+
* on the wire untranslated. `ZENTIK_ACTION_ICONS` is what makes this claim
|
|
33964
|
+
* honest; keep it exhaustive or this reverts to a pass-through that lies.
|
|
33803
33965
|
*/
|
|
33804
33966
|
actionIcons: true,
|
|
33805
33967
|
sound: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-notifiers",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.21",
|
|
4
4
|
"description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|