@camstack/addon-advanced-notifier 1.1.13 → 1.1.15
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 +387 -133
- package/dist/addon.mjs +388 -134
- package/dist/index.js +20 -12
- package/dist/index.mjs +21 -13
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -4628,7 +4628,7 @@ function _instanceof(cls, params = {}) {
|
|
|
4628
4628
|
return inst;
|
|
4629
4629
|
}
|
|
4630
4630
|
//#endregion
|
|
4631
|
-
//#region ../types/dist/sleep-
|
|
4631
|
+
//#region ../types/dist/sleep-MHm--th-.mjs
|
|
4632
4632
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
4633
4633
|
EventCategory["SystemBoot"] = "system.boot";
|
|
4634
4634
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -5890,6 +5890,7 @@ var CamStreamKindSchema = _enum([
|
|
|
5890
5890
|
"pull-rtsp",
|
|
5891
5891
|
"pull-rtmp",
|
|
5892
5892
|
"pull-http",
|
|
5893
|
+
"pull-flv",
|
|
5893
5894
|
"pull-rfc4571",
|
|
5894
5895
|
"push-annexb",
|
|
5895
5896
|
"derived"
|
|
@@ -13213,7 +13214,7 @@ var AddBrokerInputSchema = object({
|
|
|
13213
13214
|
});
|
|
13214
13215
|
var AddBrokerResultSchema = object({ id: string() });
|
|
13215
13216
|
var IdInputSchema = object({ id: string() });
|
|
13216
|
-
var TestResultSchema = discriminatedUnion("ok", [object({
|
|
13217
|
+
var TestResultSchema$1 = discriminatedUnion("ok", [object({
|
|
13217
13218
|
ok: literal(true),
|
|
13218
13219
|
latencyMs: number()
|
|
13219
13220
|
}), object({
|
|
@@ -13236,7 +13237,7 @@ var StatusSchema = object({
|
|
|
13236
13237
|
brokerCount: number(),
|
|
13237
13238
|
embeddedRunning: boolean()
|
|
13238
13239
|
});
|
|
13239
|
-
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
13240
|
+
method(_void(), array(BrokerInfoSchema)), method(IdInputSchema, BrokerConnectionDetailsSchema), method(AddBrokerInputSchema, AddBrokerResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(IdInputSchema, TestResultSchema$1, { kind: "mutation" }), method(StartEmbeddedInputSchema, StartEmbeddedResultSchema, { kind: "mutation" }), method(IdInputSchema, _void(), { kind: "mutation" }), method(_void(), StatusSchema);
|
|
13240
13241
|
var NetworkEndpointSchema = object({
|
|
13241
13242
|
url: string(),
|
|
13242
13243
|
hostname: string(),
|
|
@@ -13270,29 +13271,210 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
|
|
|
13270
13271
|
sourcePort: number().optional()
|
|
13271
13272
|
});
|
|
13272
13273
|
method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
|
|
13274
|
+
/**
|
|
13275
|
+
* notification-output — canonical, capability-gated notification delivery.
|
|
13276
|
+
*
|
|
13277
|
+
* Apprise-derived model (see
|
|
13278
|
+
* `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
|
|
13279
|
+
* callers emit ONE canonical `Notification`; each provider declares a
|
|
13280
|
+
* per-kind capability descriptor (`TargetKind`), and the pure degrade
|
|
13281
|
+
* engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
|
|
13282
|
+
* message to what the kind supports — callers never special-case a service.
|
|
13283
|
+
*
|
|
13284
|
+
* DESIGN DECISIONS (locked):
|
|
13285
|
+
* - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
|
|
13286
|
+
* `setTargetEnabled`), each provider persisting via the `settings-store`
|
|
13287
|
+
* cap. Rationale: the admin UI needs one uniform surface across the
|
|
13288
|
+
* notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
|
|
13289
|
+
* alternative would fork the UI per addon and cannot host the
|
|
13290
|
+
* discovery→adopt flow.
|
|
13291
|
+
* - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
|
|
13292
|
+
* the generated cap-mount auto-`concatCollection`-fans them across every
|
|
13293
|
+
* registered provider (notifiers addon + HA addon) so one catalog is
|
|
13294
|
+
* routable. `send` / `testTarget` / CRUD route to ONE provider by the
|
|
13295
|
+
* `addonId` the generated collection router extracts from the call input.
|
|
13296
|
+
* - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
|
|
13297
|
+
* transformer) + UDS MsgPack both round-trip typed arrays — already used by
|
|
13298
|
+
* `storage` / `storage-provider` / `recording` caps over the same path. No
|
|
13299
|
+
* base64 fallback needed.
|
|
13300
|
+
*
|
|
13301
|
+
* TODO (deferred, closed-set change — separate decision): add
|
|
13302
|
+
* `providerKind: 'notify'` so notification providers surface on the unified
|
|
13303
|
+
* admin "Integrations" page.
|
|
13304
|
+
*/
|
|
13305
|
+
/**
|
|
13306
|
+
* Zentik-derived typed-media enum — the superset across every kind. Each
|
|
13307
|
+
* adapter picks what it supports and the degrade engine filters the rest.
|
|
13308
|
+
*/
|
|
13309
|
+
var AttachmentMediaTypeSchema = _enum([
|
|
13310
|
+
"image",
|
|
13311
|
+
"video",
|
|
13312
|
+
"gif",
|
|
13313
|
+
"audio",
|
|
13314
|
+
"icon"
|
|
13315
|
+
]);
|
|
13316
|
+
/**
|
|
13317
|
+
* A single attachment. Exactly one of `url` (remote source, most adapters
|
|
13318
|
+
* prefer this) or `bytes` (inline source; required for Pushover-style
|
|
13319
|
+
* bytes-only kinds) MUST be present — the degrade engine expresses a
|
|
13320
|
+
* url→bytes fetch as a `needsFetch` directive the adapter executes.
|
|
13321
|
+
*/
|
|
13322
|
+
var AttachmentSchema = object({
|
|
13323
|
+
mediaType: AttachmentMediaTypeSchema,
|
|
13324
|
+
url: string().optional(),
|
|
13325
|
+
bytes: _instanceof(Uint8Array).optional(),
|
|
13326
|
+
mime: string().optional(),
|
|
13327
|
+
name: string().optional()
|
|
13328
|
+
}).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
|
|
13329
|
+
var NotificationFormatSchema = _enum([
|
|
13330
|
+
"text",
|
|
13331
|
+
"markdown",
|
|
13332
|
+
"html"
|
|
13333
|
+
]);
|
|
13334
|
+
/** A single tap-through action button. */
|
|
13335
|
+
var NotificationActionSchema = object({
|
|
13336
|
+
id: string(),
|
|
13337
|
+
label: string(),
|
|
13338
|
+
url: string().optional()
|
|
13339
|
+
});
|
|
13340
|
+
/**
|
|
13341
|
+
* The canonical notification. `body` is the only hard field (Apprise model).
|
|
13342
|
+
* `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
|
|
13343
|
+
* NOT a fixed severity enum — each kind declares its own `caps.levels` and
|
|
13344
|
+
* the adapter maps this ordinal onto its native level. `level?` is an
|
|
13345
|
+
* optional kind-native level id (`emergency`, `silent`, …) that overrides
|
|
13346
|
+
* `priority` for that one target.
|
|
13347
|
+
*/
|
|
13348
|
+
var NotificationSchema = object({
|
|
13349
|
+
body: string(),
|
|
13350
|
+
title: string().optional(),
|
|
13351
|
+
format: NotificationFormatSchema.default("text"),
|
|
13352
|
+
priority: number().int().min(1).max(5).default(3),
|
|
13353
|
+
level: string().optional(),
|
|
13354
|
+
attachments: array(AttachmentSchema).optional(),
|
|
13355
|
+
clickUrl: string().optional(),
|
|
13356
|
+
actions: array(NotificationActionSchema).optional(),
|
|
13357
|
+
sound: string().optional(),
|
|
13358
|
+
ttl: number().optional(),
|
|
13359
|
+
tag: string().optional(),
|
|
13360
|
+
deviceId: number().optional(),
|
|
13361
|
+
eventId: string().optional(),
|
|
13362
|
+
metadata: record(string(), unknown()).optional()
|
|
13363
|
+
});
|
|
13364
|
+
/** One declared native severity/priority level for a kind. */
|
|
13365
|
+
var TargetKindLevelSchema = object({
|
|
13366
|
+
id: string(),
|
|
13367
|
+
label: string(),
|
|
13368
|
+
/** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
|
|
13369
|
+
ordinal: number().int().min(1).max(5).nullable(),
|
|
13370
|
+
flags: object({
|
|
13371
|
+
critical: boolean().optional(),
|
|
13372
|
+
silent: boolean().optional(),
|
|
13373
|
+
noPush: boolean().optional()
|
|
13374
|
+
}).optional(),
|
|
13375
|
+
/** e.g. Pushover `emergency` requires `retry` / `expire`. */
|
|
13376
|
+
requires: array(string()).optional(),
|
|
13377
|
+
description: string().optional()
|
|
13378
|
+
});
|
|
13379
|
+
/** The full capability block consulted before dispatch. */
|
|
13380
|
+
var TargetKindCapsSchema = object({
|
|
13381
|
+
attachments: object({
|
|
13382
|
+
mediaTypes: array(AttachmentMediaTypeSchema),
|
|
13383
|
+
mode: _enum([
|
|
13384
|
+
"url",
|
|
13385
|
+
"bytes",
|
|
13386
|
+
"both"
|
|
13387
|
+
]),
|
|
13388
|
+
max: number().int().nonnegative(),
|
|
13389
|
+
maxBytes: number().int().positive().optional()
|
|
13390
|
+
}),
|
|
13391
|
+
/** Max action buttons (0 = none). */
|
|
13392
|
+
actions: number().int().nonnegative(),
|
|
13393
|
+
levels: array(TargetKindLevelSchema),
|
|
13394
|
+
format: array(NotificationFormatSchema),
|
|
13395
|
+
clickUrl: boolean(),
|
|
13396
|
+
sound: boolean(),
|
|
13397
|
+
ttl: boolean(),
|
|
13398
|
+
bodyMaxLen: number().int().positive()
|
|
13399
|
+
});
|
|
13400
|
+
/**
|
|
13401
|
+
* `configSchema` is a `ConfigUISchema` tree passed through to the admin
|
|
13402
|
+
* FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
|
|
13403
|
+
* `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
|
|
13404
|
+
* the union is large and not meant for runtime validation here; the exported
|
|
13405
|
+
* `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
|
|
13406
|
+
*/
|
|
13407
|
+
var ConfigSchemaPassthrough = unknown();
|
|
13408
|
+
var TargetKindSchema = object({
|
|
13409
|
+
kind: string(),
|
|
13410
|
+
label: string(),
|
|
13411
|
+
icon: string(),
|
|
13412
|
+
/** Stamped by each provider so the concat-fanned catalog stays routable. */
|
|
13413
|
+
addonId: string(),
|
|
13414
|
+
configSchema: ConfigSchemaPassthrough,
|
|
13415
|
+
supportsDiscovery: boolean(),
|
|
13416
|
+
caps: TargetKindCapsSchema
|
|
13417
|
+
});
|
|
13418
|
+
/**
|
|
13419
|
+
* A persisted target. `config` holds secrets; providers REDACT secret fields
|
|
13420
|
+
* (return a presence marker only) when serving `listTargets` — never
|
|
13421
|
+
* round-trip a stored secret to the UI.
|
|
13422
|
+
*/
|
|
13423
|
+
var TargetSchema = object({
|
|
13424
|
+
id: string(),
|
|
13425
|
+
name: string(),
|
|
13426
|
+
kind: string(),
|
|
13427
|
+
addonId: string(),
|
|
13428
|
+
enabled: boolean(),
|
|
13429
|
+
config: record(string(), unknown())
|
|
13430
|
+
});
|
|
13431
|
+
/** A discovery-surfaced candidate (config is partial + non-secret). */
|
|
13432
|
+
var DiscoveredTargetSchema = object({
|
|
13433
|
+
kind: string(),
|
|
13434
|
+
suggestedName: string(),
|
|
13435
|
+
config: record(string(), unknown())
|
|
13436
|
+
});
|
|
13437
|
+
/** The degrade engine's report — what was resolved / dropped / degraded. */
|
|
13438
|
+
var RenderedAsSchema = object({
|
|
13439
|
+
level: string(),
|
|
13440
|
+
format: NotificationFormatSchema,
|
|
13441
|
+
attachmentsSent: number().int().nonnegative(),
|
|
13442
|
+
actionsSent: number().int().nonnegative(),
|
|
13443
|
+
truncated: boolean(),
|
|
13444
|
+
dropped: array(string())
|
|
13445
|
+
});
|
|
13446
|
+
var SendResultSchema = object({
|
|
13447
|
+
success: boolean(),
|
|
13448
|
+
error: string().optional(),
|
|
13449
|
+
renderedAs: RenderedAsSchema.optional()
|
|
13450
|
+
});
|
|
13451
|
+
/** Same shape as SendResult — kept as a distinct name for the test panel. */
|
|
13452
|
+
var TestResultSchema = SendResultSchema;
|
|
13273
13453
|
var notificationOutputCapability = {
|
|
13274
13454
|
name: "notification-output",
|
|
13275
13455
|
scope: "system",
|
|
13276
13456
|
mode: "collection",
|
|
13277
13457
|
methods: {
|
|
13458
|
+
listTargetKinds: method(object({}), array(TargetKindSchema)),
|
|
13459
|
+
listTargets: method(object({}), array(TargetSchema)),
|
|
13460
|
+
discoverTargets: method(object({
|
|
13461
|
+
kind: string(),
|
|
13462
|
+
config: record(string(), unknown()).optional()
|
|
13463
|
+
}), array(DiscoveredTargetSchema)),
|
|
13278
13464
|
send: method(object({
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13284
|
-
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
}), _void(), { kind: "mutation" })
|
|
13292
|
-
sendTest: method(_void(), object({
|
|
13293
|
-
success: boolean(),
|
|
13294
|
-
error: string().optional()
|
|
13295
|
-
}), { kind: "mutation" })
|
|
13465
|
+
targetId: string(),
|
|
13466
|
+
notification: NotificationSchema
|
|
13467
|
+
}), SendResultSchema, { kind: "mutation" }),
|
|
13468
|
+
testTarget: method(object({
|
|
13469
|
+
targetId: string(),
|
|
13470
|
+
sample: NotificationSchema.optional()
|
|
13471
|
+
}), TestResultSchema, { kind: "mutation" }),
|
|
13472
|
+
upsertTarget: method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }),
|
|
13473
|
+
deleteTarget: method(object({ targetId: string() }), _void(), { kind: "mutation" }),
|
|
13474
|
+
setTargetEnabled: method(object({
|
|
13475
|
+
targetId: string(),
|
|
13476
|
+
enabled: boolean()
|
|
13477
|
+
}), _void(), { kind: "mutation" })
|
|
13296
13478
|
}
|
|
13297
13479
|
};
|
|
13298
13480
|
/**
|
|
@@ -19289,13 +19471,49 @@ Object.freeze({
|
|
|
19289
19471
|
addonId: null,
|
|
19290
19472
|
access: "create"
|
|
19291
19473
|
},
|
|
19474
|
+
"notificationOutput.deleteTarget": {
|
|
19475
|
+
capName: "notification-output",
|
|
19476
|
+
capScope: "system",
|
|
19477
|
+
addonId: null,
|
|
19478
|
+
access: "delete"
|
|
19479
|
+
},
|
|
19480
|
+
"notificationOutput.discoverTargets": {
|
|
19481
|
+
capName: "notification-output",
|
|
19482
|
+
capScope: "system",
|
|
19483
|
+
addonId: null,
|
|
19484
|
+
access: "view"
|
|
19485
|
+
},
|
|
19486
|
+
"notificationOutput.listTargetKinds": {
|
|
19487
|
+
capName: "notification-output",
|
|
19488
|
+
capScope: "system",
|
|
19489
|
+
addonId: null,
|
|
19490
|
+
access: "view"
|
|
19491
|
+
},
|
|
19492
|
+
"notificationOutput.listTargets": {
|
|
19493
|
+
capName: "notification-output",
|
|
19494
|
+
capScope: "system",
|
|
19495
|
+
addonId: null,
|
|
19496
|
+
access: "view"
|
|
19497
|
+
},
|
|
19292
19498
|
"notificationOutput.send": {
|
|
19293
19499
|
capName: "notification-output",
|
|
19294
19500
|
capScope: "system",
|
|
19295
19501
|
addonId: null,
|
|
19296
19502
|
access: "create"
|
|
19297
19503
|
},
|
|
19298
|
-
"notificationOutput.
|
|
19504
|
+
"notificationOutput.setTargetEnabled": {
|
|
19505
|
+
capName: "notification-output",
|
|
19506
|
+
capScope: "system",
|
|
19507
|
+
addonId: null,
|
|
19508
|
+
access: "create"
|
|
19509
|
+
},
|
|
19510
|
+
"notificationOutput.testTarget": {
|
|
19511
|
+
capName: "notification-output",
|
|
19512
|
+
capScope: "system",
|
|
19513
|
+
addonId: null,
|
|
19514
|
+
access: "create"
|
|
19515
|
+
},
|
|
19516
|
+
"notificationOutput.upsertTarget": {
|
|
19299
19517
|
capName: "notification-output",
|
|
19300
19518
|
capScope: "system",
|
|
19301
19519
|
addonId: null,
|
|
@@ -21281,42 +21499,120 @@ object({
|
|
|
21281
21499
|
schemaVersion: literal(1)
|
|
21282
21500
|
});
|
|
21283
21501
|
//#endregion
|
|
21284
|
-
//#region src/
|
|
21502
|
+
//#region src/history/audit-log.ts
|
|
21285
21503
|
var COLLECTION$1 = "addon-settings";
|
|
21286
|
-
var
|
|
21287
|
-
var
|
|
21504
|
+
var LOG_KEY = "notifier-history:log";
|
|
21505
|
+
var AuditLog = class {
|
|
21288
21506
|
api;
|
|
21289
|
-
|
|
21507
|
+
maxEntries;
|
|
21508
|
+
constructor(api, maxEntries = 1e3) {
|
|
21290
21509
|
this.api = api;
|
|
21510
|
+
this.maxEntries = maxEntries;
|
|
21291
21511
|
}
|
|
21292
|
-
async
|
|
21512
|
+
async record(entry) {
|
|
21513
|
+
const updated = [...await this.getAll(), entry];
|
|
21514
|
+
const trimmed = updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated;
|
|
21515
|
+
await this.api.settingsStore.set.mutate({
|
|
21516
|
+
collection: COLLECTION$1,
|
|
21517
|
+
key: LOG_KEY,
|
|
21518
|
+
value: trimmed
|
|
21519
|
+
});
|
|
21520
|
+
}
|
|
21521
|
+
async getHistory(filter) {
|
|
21522
|
+
let entries = await this.getAll();
|
|
21523
|
+
if (filter?.ruleId) entries = entries.filter((e) => e.ruleId === filter.ruleId);
|
|
21524
|
+
if (filter?.deviceId) {
|
|
21525
|
+
const deviceId = filter.deviceId;
|
|
21526
|
+
entries = entries.filter((e) => e.deviceId === deviceId);
|
|
21527
|
+
}
|
|
21528
|
+
if (filter?.from !== void 0) {
|
|
21529
|
+
const from = filter.from;
|
|
21530
|
+
entries = entries.filter((e) => e.timestamp >= from);
|
|
21531
|
+
}
|
|
21532
|
+
if (filter?.to !== void 0) {
|
|
21533
|
+
const to = filter.to;
|
|
21534
|
+
entries = entries.filter((e) => e.timestamp <= to);
|
|
21535
|
+
}
|
|
21536
|
+
if (filter?.limit) entries = entries.slice(-filter.limit);
|
|
21537
|
+
return entries;
|
|
21538
|
+
}
|
|
21539
|
+
async getAll() {
|
|
21293
21540
|
const raw = await this.api.settingsStore.get.query({
|
|
21294
21541
|
collection: COLLECTION$1,
|
|
21295
|
-
key:
|
|
21542
|
+
key: LOG_KEY
|
|
21296
21543
|
});
|
|
21297
21544
|
if (!raw || !Array.isArray(raw)) return [];
|
|
21298
21545
|
return raw;
|
|
21299
21546
|
}
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
|
|
21304
|
-
|
|
21547
|
+
};
|
|
21548
|
+
//#endregion
|
|
21549
|
+
//#region src/outputs/console.ts
|
|
21550
|
+
var ConsoleOutput = class {
|
|
21551
|
+
id = "console";
|
|
21552
|
+
name = "Console Log";
|
|
21553
|
+
icon = "terminal";
|
|
21554
|
+
logger;
|
|
21555
|
+
constructor(logger) {
|
|
21556
|
+
this.logger = logger;
|
|
21557
|
+
}
|
|
21558
|
+
async send(notification) {
|
|
21559
|
+
this.logger.info(`[p${notification.priority}] ${notification.title ?? ""}: ${notification.body}`, notification.deviceId !== void 0 ? { tags: { deviceId: notification.deviceId } } : void 0);
|
|
21560
|
+
}
|
|
21561
|
+
async sendTest() {
|
|
21562
|
+
await this.send({
|
|
21563
|
+
title: "Test",
|
|
21564
|
+
body: "Console output working",
|
|
21565
|
+
format: "text",
|
|
21566
|
+
priority: 3
|
|
21305
21567
|
});
|
|
21568
|
+
return { success: true };
|
|
21306
21569
|
}
|
|
21307
|
-
|
|
21308
|
-
|
|
21309
|
-
|
|
21310
|
-
|
|
21311
|
-
|
|
21312
|
-
|
|
21570
|
+
};
|
|
21571
|
+
//#endregion
|
|
21572
|
+
//#region src/rules/cooldown.ts
|
|
21573
|
+
var CooldownTracker = class {
|
|
21574
|
+
lastFired = /* @__PURE__ */ new Map();
|
|
21575
|
+
canFire(ruleId, cooldownSeconds) {
|
|
21576
|
+
const last = this.lastFired.get(ruleId);
|
|
21577
|
+
if (last === void 0) return true;
|
|
21578
|
+
return (Date.now() - last) / 1e3 >= cooldownSeconds;
|
|
21313
21579
|
}
|
|
21314
|
-
|
|
21315
|
-
|
|
21316
|
-
|
|
21580
|
+
recordFire(ruleId) {
|
|
21581
|
+
this.lastFired.set(ruleId, Date.now());
|
|
21582
|
+
}
|
|
21583
|
+
reset() {
|
|
21584
|
+
this.lastFired.clear();
|
|
21317
21585
|
}
|
|
21318
21586
|
};
|
|
21319
21587
|
//#endregion
|
|
21588
|
+
//#region src/rules/recent-events.ts
|
|
21589
|
+
/**
|
|
21590
|
+
* Rehydrate serialized recent events into `SystemEvent`s and drop any that
|
|
21591
|
+
* fall before `since`. Order is preserved (the hub returns oldest → newest).
|
|
21592
|
+
*/
|
|
21593
|
+
function reconstructRecentEvents(serialized, since) {
|
|
21594
|
+
const sinceMs = since.getTime();
|
|
21595
|
+
const events = [];
|
|
21596
|
+
for (const e of serialized) {
|
|
21597
|
+
const timestamp = new Date(e.timestamp);
|
|
21598
|
+
if (timestamp.getTime() < sinceMs) continue;
|
|
21599
|
+
events.push({
|
|
21600
|
+
id: e.id,
|
|
21601
|
+
timestamp,
|
|
21602
|
+
source: {
|
|
21603
|
+
type: e.source.type,
|
|
21604
|
+
id: e.source.id,
|
|
21605
|
+
...e.source.nodeId !== void 0 ? { nodeId: e.source.nodeId } : {},
|
|
21606
|
+
...e.source.addonId !== void 0 ? { addonId: e.source.addonId } : {},
|
|
21607
|
+
...e.source.deviceId !== void 0 ? { deviceId: e.source.deviceId } : {}
|
|
21608
|
+
},
|
|
21609
|
+
category: e.category,
|
|
21610
|
+
data: e.data
|
|
21611
|
+
});
|
|
21612
|
+
}
|
|
21613
|
+
return events;
|
|
21614
|
+
}
|
|
21615
|
+
//#endregion
|
|
21320
21616
|
//#region src/rules/rule-engine.ts
|
|
21321
21617
|
var RuleEngine = class {
|
|
21322
21618
|
clip;
|
|
@@ -21389,19 +21685,39 @@ var RuleEngine = class {
|
|
|
21389
21685
|
}
|
|
21390
21686
|
};
|
|
21391
21687
|
//#endregion
|
|
21392
|
-
//#region src/rules/
|
|
21393
|
-
var
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21688
|
+
//#region src/rules/rule-store.ts
|
|
21689
|
+
var COLLECTION = "addon-settings";
|
|
21690
|
+
var KEY = "advanced-notifier:rules";
|
|
21691
|
+
var RuleStore = class {
|
|
21692
|
+
api;
|
|
21693
|
+
constructor(api) {
|
|
21694
|
+
this.api = api;
|
|
21399
21695
|
}
|
|
21400
|
-
|
|
21401
|
-
this.
|
|
21696
|
+
async getRules() {
|
|
21697
|
+
const raw = await this.api.settingsStore.get.query({
|
|
21698
|
+
collection: COLLECTION,
|
|
21699
|
+
key: KEY
|
|
21700
|
+
});
|
|
21701
|
+
if (!raw || !Array.isArray(raw)) return [];
|
|
21702
|
+
return raw;
|
|
21402
21703
|
}
|
|
21403
|
-
|
|
21404
|
-
this.
|
|
21704
|
+
async saveRules(rules) {
|
|
21705
|
+
await this.api.settingsStore.set.mutate({
|
|
21706
|
+
collection: COLLECTION,
|
|
21707
|
+
key: KEY,
|
|
21708
|
+
value: [...rules]
|
|
21709
|
+
});
|
|
21710
|
+
}
|
|
21711
|
+
async upsertRule(rule) {
|
|
21712
|
+
const rules = await this.getRules();
|
|
21713
|
+
const idx = rules.findIndex((r) => r.id === rule.id);
|
|
21714
|
+
if (idx >= 0) rules[idx] = rule;
|
|
21715
|
+
else rules.push(rule);
|
|
21716
|
+
await this.saveRules(rules);
|
|
21717
|
+
}
|
|
21718
|
+
async deleteRule(ruleId) {
|
|
21719
|
+
const rules = await this.getRules();
|
|
21720
|
+
await this.saveRules(rules.filter((r) => r.id !== ruleId));
|
|
21405
21721
|
}
|
|
21406
21722
|
};
|
|
21407
21723
|
//#endregion
|
|
@@ -21413,77 +21729,6 @@ function renderTemplate(template, variables) {
|
|
|
21413
21729
|
});
|
|
21414
21730
|
}
|
|
21415
21731
|
//#endregion
|
|
21416
|
-
//#region src/outputs/console.ts
|
|
21417
|
-
var ConsoleOutput = class {
|
|
21418
|
-
id = "console";
|
|
21419
|
-
name = "Console Log";
|
|
21420
|
-
icon = "terminal";
|
|
21421
|
-
logger;
|
|
21422
|
-
constructor(logger) {
|
|
21423
|
-
this.logger = logger;
|
|
21424
|
-
}
|
|
21425
|
-
async send(notification) {
|
|
21426
|
-
this.logger.info(`[${notification.severity}] ${notification.title}: ${notification.message}`, notification.deviceId !== void 0 ? { tags: { deviceId: notification.deviceId } } : void 0);
|
|
21427
|
-
}
|
|
21428
|
-
async sendTest() {
|
|
21429
|
-
await this.send({
|
|
21430
|
-
title: "Test",
|
|
21431
|
-
message: "Console output working",
|
|
21432
|
-
severity: "info",
|
|
21433
|
-
category: "test",
|
|
21434
|
-
timestamp: Date.now()
|
|
21435
|
-
});
|
|
21436
|
-
return { success: true };
|
|
21437
|
-
}
|
|
21438
|
-
};
|
|
21439
|
-
//#endregion
|
|
21440
|
-
//#region src/history/audit-log.ts
|
|
21441
|
-
var COLLECTION = "addon-settings";
|
|
21442
|
-
var LOG_KEY = "notifier-history:log";
|
|
21443
|
-
var AuditLog = class {
|
|
21444
|
-
api;
|
|
21445
|
-
maxEntries;
|
|
21446
|
-
constructor(api, maxEntries = 1e3) {
|
|
21447
|
-
this.api = api;
|
|
21448
|
-
this.maxEntries = maxEntries;
|
|
21449
|
-
}
|
|
21450
|
-
async record(entry) {
|
|
21451
|
-
const updated = [...await this.getAll(), entry];
|
|
21452
|
-
const trimmed = updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated;
|
|
21453
|
-
await this.api.settingsStore.set.mutate({
|
|
21454
|
-
collection: COLLECTION,
|
|
21455
|
-
key: LOG_KEY,
|
|
21456
|
-
value: trimmed
|
|
21457
|
-
});
|
|
21458
|
-
}
|
|
21459
|
-
async getHistory(filter) {
|
|
21460
|
-
let entries = await this.getAll();
|
|
21461
|
-
if (filter?.ruleId) entries = entries.filter((e) => e.ruleId === filter.ruleId);
|
|
21462
|
-
if (filter?.deviceId) {
|
|
21463
|
-
const deviceId = filter.deviceId;
|
|
21464
|
-
entries = entries.filter((e) => e.deviceId === deviceId);
|
|
21465
|
-
}
|
|
21466
|
-
if (filter?.from !== void 0) {
|
|
21467
|
-
const from = filter.from;
|
|
21468
|
-
entries = entries.filter((e) => e.timestamp >= from);
|
|
21469
|
-
}
|
|
21470
|
-
if (filter?.to !== void 0) {
|
|
21471
|
-
const to = filter.to;
|
|
21472
|
-
entries = entries.filter((e) => e.timestamp <= to);
|
|
21473
|
-
}
|
|
21474
|
-
if (filter?.limit) entries = entries.slice(-filter.limit);
|
|
21475
|
-
return entries;
|
|
21476
|
-
}
|
|
21477
|
-
async getAll() {
|
|
21478
|
-
const raw = await this.api.settingsStore.get.query({
|
|
21479
|
-
collection: COLLECTION,
|
|
21480
|
-
key: LOG_KEY
|
|
21481
|
-
});
|
|
21482
|
-
if (!raw || !Array.isArray(raw)) return [];
|
|
21483
|
-
return raw;
|
|
21484
|
-
}
|
|
21485
|
-
};
|
|
21486
|
-
//#endregion
|
|
21487
21732
|
//#region src/addon.ts
|
|
21488
21733
|
var EVENT_CATEGORIES = [
|
|
21489
21734
|
EventCategory.DetectionRaw,
|
|
@@ -21535,7 +21780,9 @@ var AdvancedNotifierAddon = class extends BaseAddon {
|
|
|
21535
21780
|
const rule = this.cachedRules.find((r) => r.id === ruleId);
|
|
21536
21781
|
if (!rule) return { results: [] };
|
|
21537
21782
|
const since = /* @__PURE__ */ new Date(Date.now() - lookbackMinutes * 60 * 1e3);
|
|
21538
|
-
|
|
21783
|
+
const api = this.ctx.api;
|
|
21784
|
+
if (!api) return { results: [] };
|
|
21785
|
+
return { results: reconstructRecentEvents(await api.eventBusProxy.getRecent.query({ limit: 1e3 }), since).map((event) => {
|
|
21539
21786
|
const matched = this.ruleEngine.evaluate(event, [rule]);
|
|
21540
21787
|
return {
|
|
21541
21788
|
ruleId,
|
|
@@ -21699,12 +21946,11 @@ var AdvancedNotifierAddon = class extends BaseAddon {
|
|
|
21699
21946
|
const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : void 0;
|
|
21700
21947
|
const notification = {
|
|
21701
21948
|
title,
|
|
21702
|
-
message,
|
|
21703
|
-
|
|
21704
|
-
|
|
21949
|
+
body: message,
|
|
21950
|
+
format: "text",
|
|
21951
|
+
priority: this.priorityToOrdinal(rule.priority),
|
|
21705
21952
|
deviceId,
|
|
21706
|
-
|
|
21707
|
-
timestamp: event.timestamp.getTime()
|
|
21953
|
+
metadata: event.data
|
|
21708
21954
|
};
|
|
21709
21955
|
let success = true;
|
|
21710
21956
|
let dispatchError;
|
|
@@ -21749,11 +21995,19 @@ var AdvancedNotifierAddon = class extends BaseAddon {
|
|
|
21749
21995
|
timestamp: event.timestamp.toISOString()
|
|
21750
21996
|
};
|
|
21751
21997
|
}
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21998
|
+
/**
|
|
21999
|
+
* Map a rule's qualitative priority onto the canonical 1..5 ordinal.
|
|
22000
|
+
* Callers now pass ordinal priority; each `notification-output` provider
|
|
22001
|
+
* maps the ordinal onto its declared native level.
|
|
22002
|
+
*/
|
|
22003
|
+
priorityToOrdinal(priority) {
|
|
22004
|
+
switch (priority) {
|
|
22005
|
+
case "critical": return 5;
|
|
22006
|
+
case "high": return 4;
|
|
22007
|
+
case "low": return 2;
|
|
22008
|
+
default: return 3;
|
|
22009
|
+
}
|
|
21756
22010
|
}
|
|
21757
22011
|
};
|
|
21758
22012
|
//#endregion
|
|
21759
|
-
export { AdvancedNotifierAddon, AdvancedNotifierAddon as default,
|
|
22013
|
+
export { AdvancedNotifierAddon, AdvancedNotifierAddon as default, ConsoleOutput as a, CooldownTracker as i, RuleStore as n, AuditLog as o, RuleEngine as r, renderTemplate as t };
|