@camstack/addon-advanced-notifier 0.1.12 → 0.1.14
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.d.mts +13 -7
- package/dist/addon.d.ts +13 -7
- package/dist/addon.js +120 -57
- package/dist/addon.js.map +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/{chunk-HYUFV4O5.mjs → chunk-MPVMHQWM.mjs} +123 -58
- package/dist/chunk-MPVMHQWM.mjs.map +1 -0
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +123 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +5 -6
- package/dist/chunk-HYUFV4O5.mjs.map +0 -1
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
// src/addon.ts
|
|
2
|
+
import { BaseAddon, EventCategory, advancedNotifierCapability, notificationOutputCapability } from "@camstack/types";
|
|
3
|
+
|
|
1
4
|
// src/rules/rule-store.ts
|
|
2
5
|
var COLLECTION = "addon-settings";
|
|
3
6
|
var KEY = "advanced-notifier:rules";
|
|
4
7
|
var RuleStore = class {
|
|
5
|
-
|
|
6
|
-
constructor(
|
|
7
|
-
this.
|
|
8
|
+
api;
|
|
9
|
+
constructor(api) {
|
|
10
|
+
this.api = api;
|
|
8
11
|
}
|
|
9
12
|
async getRules() {
|
|
10
|
-
const raw = await this.
|
|
13
|
+
const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: KEY });
|
|
11
14
|
if (!raw || !Array.isArray(raw)) return [];
|
|
12
15
|
return raw;
|
|
13
16
|
}
|
|
14
17
|
async saveRules(rules) {
|
|
15
|
-
await this.
|
|
18
|
+
await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: KEY, value: [...rules] });
|
|
16
19
|
}
|
|
17
20
|
async upsertRule(rule) {
|
|
18
21
|
const rules = await this.getRules();
|
|
@@ -44,8 +47,9 @@ var RuleEngine = class {
|
|
|
44
47
|
const { conditions } = rule;
|
|
45
48
|
const data = event.data;
|
|
46
49
|
if (conditions.deviceIds?.length) {
|
|
47
|
-
const
|
|
48
|
-
|
|
50
|
+
const raw = data["deviceId"] ?? event.source.id;
|
|
51
|
+
const deviceId = typeof raw === "number" ? raw : Number(raw);
|
|
52
|
+
if (!Number.isFinite(deviceId) || !conditions.deviceIds.includes(deviceId)) return false;
|
|
49
53
|
}
|
|
50
54
|
if (conditions.classNames?.length) {
|
|
51
55
|
const className = data["className"];
|
|
@@ -105,7 +109,8 @@ var ConsoleOutput = class {
|
|
|
105
109
|
}
|
|
106
110
|
async send(notification) {
|
|
107
111
|
this.logger.info(
|
|
108
|
-
`[${notification.severity}] ${notification.title}: ${notification.message}
|
|
112
|
+
`[${notification.severity}] ${notification.title}: ${notification.message}`,
|
|
113
|
+
notification.deviceId !== void 0 ? { tags: { deviceId: notification.deviceId } } : void 0
|
|
109
114
|
);
|
|
110
115
|
}
|
|
111
116
|
async sendTest() {
|
|
@@ -124,17 +129,17 @@ var ConsoleOutput = class {
|
|
|
124
129
|
var COLLECTION2 = "addon-settings";
|
|
125
130
|
var LOG_KEY = "notifier-history:log";
|
|
126
131
|
var AuditLog = class {
|
|
127
|
-
|
|
132
|
+
api;
|
|
128
133
|
maxEntries;
|
|
129
|
-
constructor(
|
|
130
|
-
this.
|
|
134
|
+
constructor(api, maxEntries = 1e3) {
|
|
135
|
+
this.api = api;
|
|
131
136
|
this.maxEntries = maxEntries;
|
|
132
137
|
}
|
|
133
138
|
async record(entry) {
|
|
134
139
|
const entries = await this.getAll();
|
|
135
140
|
const updated = [...entries, entry];
|
|
136
141
|
const trimmed = updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated;
|
|
137
|
-
await this.
|
|
142
|
+
await this.api.settingsStore.set.mutate({ collection: COLLECTION2, key: LOG_KEY, value: trimmed });
|
|
138
143
|
}
|
|
139
144
|
async getHistory(filter) {
|
|
140
145
|
let entries = await this.getAll();
|
|
@@ -143,7 +148,7 @@ var AuditLog = class {
|
|
|
143
148
|
}
|
|
144
149
|
if (filter?.deviceId) {
|
|
145
150
|
const deviceId = filter.deviceId;
|
|
146
|
-
entries = entries.filter((e) => e
|
|
151
|
+
entries = entries.filter((e) => e.deviceId === deviceId);
|
|
147
152
|
}
|
|
148
153
|
if (filter?.from !== void 0) {
|
|
149
154
|
const from = filter.from;
|
|
@@ -159,7 +164,7 @@ var AuditLog = class {
|
|
|
159
164
|
return entries;
|
|
160
165
|
}
|
|
161
166
|
async getAll() {
|
|
162
|
-
const raw = await this.
|
|
167
|
+
const raw = await this.api.settingsStore.get.query({ collection: COLLECTION2, key: LOG_KEY });
|
|
163
168
|
if (!raw || !Array.isArray(raw)) return [];
|
|
164
169
|
return raw;
|
|
165
170
|
}
|
|
@@ -167,18 +172,21 @@ var AuditLog = class {
|
|
|
167
172
|
|
|
168
173
|
// src/addon.ts
|
|
169
174
|
import { randomUUID } from "crypto";
|
|
170
|
-
var EVENT_CATEGORIES = [
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
var EVENT_CATEGORIES = [
|
|
176
|
+
EventCategory.DetectionRaw,
|
|
177
|
+
EventCategory.DetectionResult,
|
|
178
|
+
EventCategory.DetectionCameraNative
|
|
179
|
+
];
|
|
180
|
+
var DEFAULT_NOTIFIER_CONFIG = {
|
|
181
|
+
defaultCooldownSeconds: 60,
|
|
182
|
+
maxHistoryEntries: 1e4,
|
|
183
|
+
auditLogRetentionDays: 30,
|
|
184
|
+
consoleOutputEnabled: true
|
|
185
|
+
};
|
|
186
|
+
var AdvancedNotifierAddon = class extends BaseAddon {
|
|
187
|
+
constructor() {
|
|
188
|
+
super({ ...DEFAULT_NOTIFIER_CONFIG });
|
|
189
|
+
}
|
|
182
190
|
// Rule management
|
|
183
191
|
ruleStore;
|
|
184
192
|
ruleEngine;
|
|
@@ -207,21 +215,21 @@ var AdvancedNotifierAddon = class {
|
|
|
207
215
|
this.cachedRules = [...this.cachedRules, rule];
|
|
208
216
|
}
|
|
209
217
|
}).catch((err) => {
|
|
210
|
-
this.
|
|
218
|
+
this.ctx.logger.error("Failed to upsert rule", { meta: { error: String(err) } });
|
|
211
219
|
});
|
|
212
220
|
},
|
|
213
221
|
deleteRule: (ruleId) => {
|
|
214
222
|
this.ruleStore.deleteRule(ruleId).then(() => {
|
|
215
223
|
this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
|
|
216
224
|
}).catch((err) => {
|
|
217
|
-
this.
|
|
225
|
+
this.ctx.logger.error("Failed to delete rule", { tags: { ruleId }, meta: { error: String(err) } });
|
|
218
226
|
});
|
|
219
227
|
},
|
|
220
228
|
testRule: async (ruleId, lookbackMinutes) => {
|
|
221
229
|
const rule = this.cachedRules.find((r) => r.id === ruleId);
|
|
222
230
|
if (!rule) return [];
|
|
223
231
|
const since = new Date(Date.now() - lookbackMinutes * 60 * 1e3);
|
|
224
|
-
const recentEvents = this.
|
|
232
|
+
const recentEvents = this.ctx.eventBus.getRecent(
|
|
225
233
|
{ category: void 0 },
|
|
226
234
|
1e3
|
|
227
235
|
).filter((e) => e.timestamp >= since);
|
|
@@ -241,22 +249,22 @@ var AdvancedNotifierAddon = class {
|
|
|
241
249
|
return this.auditLog.getHistory(filter);
|
|
242
250
|
}
|
|
243
251
|
};
|
|
244
|
-
async
|
|
245
|
-
this.context = context;
|
|
252
|
+
async onInitialize() {
|
|
246
253
|
this.ruleEngine = new RuleEngine();
|
|
247
254
|
this.cooldown = new CooldownTracker();
|
|
248
|
-
this.consoleOutput = new ConsoleOutput(
|
|
255
|
+
this.consoleOutput = new ConsoleOutput(this.ctx.logger.child("console-output"));
|
|
249
256
|
this.outputs = [this.consoleOutput];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
this.
|
|
257
|
+
const api = this.ctx.api;
|
|
258
|
+
if (api) {
|
|
259
|
+
this.ruleStore = new RuleStore(api);
|
|
260
|
+
this.auditLog = new AuditLog(api);
|
|
253
261
|
this.cachedRules = await this.ruleStore.getRules();
|
|
254
|
-
this.
|
|
262
|
+
this.ctx.logger.info("Loaded notification rules", { meta: { count: this.cachedRules.length } });
|
|
255
263
|
} else {
|
|
256
|
-
this.
|
|
264
|
+
this.ctx.logger.warn("No ctx.api \u2014 rules will not be persisted");
|
|
257
265
|
}
|
|
258
266
|
for (const category of EVENT_CATEGORIES) {
|
|
259
|
-
const unsubscribe =
|
|
267
|
+
const unsubscribe = this.ctx.eventBus.subscribe(
|
|
260
268
|
{ category },
|
|
261
269
|
(event) => {
|
|
262
270
|
void this.handleEvent(event);
|
|
@@ -264,42 +272,98 @@ var AdvancedNotifierAddon = class {
|
|
|
264
272
|
);
|
|
265
273
|
this.unsubscribers.push(unsubscribe);
|
|
266
274
|
}
|
|
267
|
-
this.
|
|
275
|
+
this.ctx.logger.info("AdvancedNotifierAddon initialized");
|
|
276
|
+
return [
|
|
277
|
+
{ capability: advancedNotifierCapability, provider: this.notifier },
|
|
278
|
+
{ capability: notificationOutputCapability, provider: this.consoleOutput }
|
|
279
|
+
];
|
|
268
280
|
}
|
|
269
|
-
async
|
|
281
|
+
async onShutdown() {
|
|
270
282
|
for (const unsub of this.unsubscribers) {
|
|
271
283
|
unsub();
|
|
272
284
|
}
|
|
273
285
|
this.unsubscribers = [];
|
|
274
|
-
this.
|
|
286
|
+
this.ctx.logger.info("AdvancedNotifierAddon shut down");
|
|
275
287
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
288
|
+
// ── Config management (session 5 Sprint B7) ─────────────────────────
|
|
289
|
+
//
|
|
290
|
+
// Rules themselves are opaque JSON documents managed through the
|
|
291
|
+
// `upsertRule`/`deleteRule` cap methods — they do NOT belong in the
|
|
292
|
+
// schema-backed settings API. The schema below exposes addon-level
|
|
293
|
+
// tuning knobs only. All fields are global-scope because advanced-
|
|
294
|
+
// notifier is a system singleton with no per-device state.
|
|
295
|
+
// ── Three-level settings API (Phase 3) ──────────────────────────────
|
|
296
|
+
globalSettingsSchema() {
|
|
297
|
+
return this.schema({
|
|
298
|
+
sections: [{
|
|
299
|
+
id: "advanced-notifier-settings",
|
|
300
|
+
title: "Advanced Notifier",
|
|
301
|
+
columns: 2,
|
|
302
|
+
fields: [
|
|
303
|
+
this.field({
|
|
304
|
+
type: "number",
|
|
305
|
+
key: "defaultCooldownSeconds",
|
|
306
|
+
label: "Default Cooldown",
|
|
307
|
+
description: "Default cooldown between repeated notifications for the same rule.",
|
|
308
|
+
min: 0,
|
|
309
|
+
max: 3600,
|
|
310
|
+
step: 1,
|
|
311
|
+
default: 60,
|
|
312
|
+
unit: "s"
|
|
313
|
+
}),
|
|
314
|
+
this.field({
|
|
315
|
+
type: "number",
|
|
316
|
+
key: "maxHistoryEntries",
|
|
317
|
+
label: "Max History Entries",
|
|
318
|
+
description: "Maximum number of notification history entries to keep.",
|
|
319
|
+
min: 100,
|
|
320
|
+
max: 1e5,
|
|
321
|
+
step: 100,
|
|
322
|
+
default: 1e4
|
|
323
|
+
}),
|
|
324
|
+
this.field({
|
|
325
|
+
type: "number",
|
|
326
|
+
key: "auditLogRetentionDays",
|
|
327
|
+
label: "Audit Log Retention",
|
|
328
|
+
description: "How long to retain notification history entries.",
|
|
329
|
+
min: 1,
|
|
330
|
+
max: 365,
|
|
331
|
+
step: 1,
|
|
332
|
+
default: 30,
|
|
333
|
+
unit: "days"
|
|
334
|
+
}),
|
|
335
|
+
this.field({
|
|
336
|
+
type: "boolean",
|
|
337
|
+
key: "consoleOutputEnabled",
|
|
338
|
+
label: "Console Output Enabled",
|
|
339
|
+
description: "Emit matched rules to the server console.",
|
|
340
|
+
default: true
|
|
341
|
+
})
|
|
342
|
+
]
|
|
343
|
+
}]
|
|
344
|
+
});
|
|
284
345
|
}
|
|
285
346
|
async handleEvent(event) {
|
|
286
347
|
const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules);
|
|
287
348
|
for (const rule of matchedRules) {
|
|
288
|
-
const cooldownSeconds = rule.conditions.cooldownSeconds ??
|
|
349
|
+
const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds;
|
|
289
350
|
if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {
|
|
290
|
-
this.
|
|
351
|
+
this.ctx.logger.debug("Rule skipped \u2014 in cooldown", { meta: { ruleId: rule.id } });
|
|
291
352
|
continue;
|
|
292
353
|
}
|
|
293
354
|
this.cooldown.recordFire(rule.id);
|
|
294
355
|
const variables = this.buildTemplateVariables(event);
|
|
295
356
|
const title = rule.template ? renderTemplate(rule.template.title, variables) : `CamStack Alert: ${rule.name}`;
|
|
296
357
|
const message = rule.template ? renderTemplate(rule.template.body, variables) : `Event ${event.category} from ${event.source.id}`;
|
|
358
|
+
const rawDeviceId = event.data["deviceId"] ?? event.source.id;
|
|
359
|
+
const deviceIdNum = typeof rawDeviceId === "number" ? rawDeviceId : Number(rawDeviceId);
|
|
360
|
+
const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : void 0;
|
|
297
361
|
const notification = {
|
|
298
362
|
title,
|
|
299
363
|
message,
|
|
300
364
|
severity: this.priorityToSeverity(rule.priority),
|
|
301
365
|
category: event.category,
|
|
302
|
-
deviceId
|
|
366
|
+
deviceId,
|
|
303
367
|
data: event.data,
|
|
304
368
|
timestamp: event.timestamp.getTime()
|
|
305
369
|
};
|
|
@@ -311,7 +375,7 @@ var AdvancedNotifierAddon = class {
|
|
|
311
375
|
} catch (err) {
|
|
312
376
|
success = false;
|
|
313
377
|
dispatchError = String(err);
|
|
314
|
-
this.
|
|
378
|
+
this.ctx.logger.error("Output failed", { meta: { outputId: output.id, error: dispatchError } });
|
|
315
379
|
}
|
|
316
380
|
}
|
|
317
381
|
if (this.auditLog) {
|
|
@@ -323,10 +387,11 @@ var AdvancedNotifierAddon = class {
|
|
|
323
387
|
timestamp: event.timestamp.getTime(),
|
|
324
388
|
outputs: this.outputs.map((o) => o.id),
|
|
325
389
|
success,
|
|
326
|
-
error: dispatchError
|
|
390
|
+
error: dispatchError,
|
|
391
|
+
deviceId
|
|
327
392
|
};
|
|
328
393
|
await this.auditLog.record(entry).catch((err) => {
|
|
329
|
-
this.
|
|
394
|
+
this.ctx.logger.error("Failed to record audit log entry", { meta: { error: String(err) } });
|
|
330
395
|
});
|
|
331
396
|
}
|
|
332
397
|
}
|
|
@@ -334,8 +399,8 @@ var AdvancedNotifierAddon = class {
|
|
|
334
399
|
buildTemplateVariables(event) {
|
|
335
400
|
const data = event.data;
|
|
336
401
|
return {
|
|
337
|
-
deviceId: data["deviceId"] ?? event.source.id,
|
|
338
|
-
deviceName: data["deviceName"] ?? event.source.id,
|
|
402
|
+
deviceId: data["deviceId"] ?? String(event.source.id),
|
|
403
|
+
deviceName: data["deviceName"] ?? String(event.source.id),
|
|
339
404
|
className: data["className"] ?? "",
|
|
340
405
|
confidence: String(data["confidence"] ?? ""),
|
|
341
406
|
zoneId: data["zoneId"] ?? "",
|
|
@@ -362,4 +427,4 @@ export {
|
|
|
362
427
|
AdvancedNotifierAddon,
|
|
363
428
|
addon_default
|
|
364
429
|
};
|
|
365
|
-
//# sourceMappingURL=chunk-
|
|
430
|
+
//# sourceMappingURL=chunk-MPVMHQWM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/addon.ts","../src/rules/rule-store.ts","../src/rules/rule-engine.ts","../src/rules/cooldown.ts","../src/template/renderer.ts","../src/outputs/console.ts","../src/history/audit-log.ts"],"sourcesContent":["import type {\n ProviderRegistration,\n IAdvancedNotifier,\n INotificationOutput,\n NotificationRule,\n NotificationTestResult,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n SystemEvent,\n} from '@camstack/types'\nimport { BaseAddon, EventCategory, advancedNotifierCapability, notificationOutputCapability } from '@camstack/types'\nimport { RuleStore } from './rules/rule-store.js'\nimport { RuleEngine } from './rules/rule-engine.js'\nimport { CooldownTracker } from './rules/cooldown.js'\nimport { renderTemplate } from './template/renderer.js'\nimport { ConsoleOutput } from './outputs/console.js'\nimport { AuditLog } from './history/audit-log.js'\nimport { randomUUID } from 'node:crypto'\n\nconst EVENT_CATEGORIES = [\n EventCategory.DetectionRaw,\n EventCategory.DetectionResult,\n EventCategory.DetectionCameraNative,\n] as const\n\ninterface NotifierConfig {\n readonly defaultCooldownSeconds: number\n readonly maxHistoryEntries: number\n readonly auditLogRetentionDays: number\n readonly consoleOutputEnabled: boolean\n}\n\nconst DEFAULT_NOTIFIER_CONFIG: NotifierConfig = {\n defaultCooldownSeconds: 60,\n maxHistoryEntries: 10000,\n auditLogRetentionDays: 30,\n consoleOutputEnabled: true,\n}\n\nexport class AdvancedNotifierAddon extends BaseAddon<NotifierConfig> {\n constructor() {\n super({ ...DEFAULT_NOTIFIER_CONFIG })\n }\n\n // Rule management\n private ruleStore!: RuleStore\n private ruleEngine!: RuleEngine\n private cooldown!: CooldownTracker\n private cachedRules: NotificationRule[] = []\n\n // Outputs\n private consoleOutput!: ConsoleOutput\n private outputs: INotificationOutput[] = []\n\n // Audit log\n private auditLog!: AuditLog\n\n // Event bus unsubscribe handles\n private unsubscribers: Array<() => void> = []\n\n // IAdvancedNotifier implementation (exposed as capability)\n private readonly notifier: IAdvancedNotifier = {\n getRules: () => this.cachedRules as readonly NotificationRule[],\n\n upsertRule: (rule: NotificationRule): void => {\n // Fire-and-forget with error logging\n this.ruleStore.upsertRule(rule).then(() => {\n const idx = this.cachedRules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n this.cachedRules = [\n ...this.cachedRules.slice(0, idx),\n rule,\n ...this.cachedRules.slice(idx + 1),\n ]\n } else {\n this.cachedRules = [...this.cachedRules, rule]\n }\n }).catch(err => {\n this.ctx.logger.error('Failed to upsert rule', { meta: { error: String(err) } })\n })\n },\n\n deleteRule: (ruleId: string): void => {\n this.ruleStore.deleteRule(ruleId).then(() => {\n this.cachedRules = this.cachedRules.filter(r => r.id !== ruleId)\n }).catch(err => {\n this.ctx.logger.error('Failed to delete rule', { tags: { ruleId }, meta: { error: String(err) } })\n })\n },\n\n testRule: async (ruleId: string, lookbackMinutes: number): Promise<NotificationTestResult[]> => {\n const rule = this.cachedRules.find(r => r.id === ruleId)\n if (!rule) return []\n const since = new Date(Date.now() - lookbackMinutes * 60 * 1000)\n const recentEvents = this.ctx.eventBus.getRecent(\n { category: undefined },\n 1000,\n ).filter(e => e.timestamp >= since)\n\n return recentEvents.map(event => {\n const matched = this.ruleEngine.evaluate(event, [rule])\n return {\n ruleId,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n wouldFire: matched.length > 0,\n reason: matched.length === 0 ? 'Conditions not met' : undefined,\n }\n })\n },\n\n getHistory: async (filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> => {\n if (!this.auditLog) return []\n return this.auditLog.getHistory(filter)\n },\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n this.ruleEngine = new RuleEngine()\n this.cooldown = new CooldownTracker()\n this.consoleOutput = new ConsoleOutput(this.ctx.logger.child('console-output'))\n this.outputs = [this.consoleOutput]\n\n const api = this.ctx.api\n if (api) {\n this.ruleStore = new RuleStore(api)\n this.auditLog = new AuditLog(api)\n this.cachedRules = await this.ruleStore.getRules()\n this.ctx.logger.info('Loaded notification rules', { meta: { count: this.cachedRules.length } })\n } else {\n this.ctx.logger.warn('No ctx.api — rules will not be persisted')\n }\n\n // Subscribe to detection event categories\n for (const category of EVENT_CATEGORIES) {\n const unsubscribe = this.ctx.eventBus.subscribe(\n { category },\n (event: SystemEvent) => { void this.handleEvent(event) },\n )\n this.unsubscribers.push(unsubscribe)\n }\n\n this.ctx.logger.info('AdvancedNotifierAddon initialized')\n\n return [\n { capability: advancedNotifierCapability, provider: this.notifier },\n { capability: notificationOutputCapability, provider: this.consoleOutput },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n for (const unsub of this.unsubscribers) {\n unsub()\n }\n this.unsubscribers = []\n this.ctx.logger.info('AdvancedNotifierAddon shut down')\n }\n\n // ── Config management (session 5 Sprint B7) ─────────────────────────\n //\n // Rules themselves are opaque JSON documents managed through the\n // `upsertRule`/`deleteRule` cap methods — they do NOT belong in the\n // schema-backed settings API. The schema below exposes addon-level\n // tuning knobs only. All fields are global-scope because advanced-\n // notifier is a system singleton with no per-device state.\n\n // ── Three-level settings API (Phase 3) ──────────────────────────────\n protected globalSettingsSchema() {\n return this.schema({\n sections: [{\n id: 'advanced-notifier-settings',\n title: 'Advanced Notifier',\n columns: 2,\n fields: [\n this.field({\n type: 'number', key: 'defaultCooldownSeconds', label: 'Default Cooldown',\n description: 'Default cooldown between repeated notifications for the same rule.',\n min: 0, max: 3600, step: 1, default: 60, unit: 's',\n }),\n this.field({\n type: 'number', key: 'maxHistoryEntries', label: 'Max History Entries',\n description: 'Maximum number of notification history entries to keep.',\n min: 100, max: 100000, step: 100, default: 10000,\n }),\n this.field({\n type: 'number', key: 'auditLogRetentionDays', label: 'Audit Log Retention',\n description: 'How long to retain notification history entries.',\n min: 1, max: 365, step: 1, default: 30, unit: 'days',\n }),\n this.field({\n type: 'boolean', key: 'consoleOutputEnabled', label: 'Console Output Enabled',\n description: 'Emit matched rules to the server console.',\n default: true,\n }),\n ],\n }],\n })\n }\n\n private async handleEvent(event: SystemEvent): Promise<void> {\n const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules)\n\n for (const rule of matchedRules) {\n const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds\n if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {\n this.ctx.logger.debug('Rule skipped — in cooldown', { meta: { ruleId: rule.id } })\n continue\n }\n this.cooldown.recordFire(rule.id)\n\n const variables = this.buildTemplateVariables(event)\n const title = rule.template\n ? renderTemplate(rule.template.title, variables)\n : `CamStack Alert: ${rule.name}`\n const message = rule.template\n ? renderTemplate(rule.template.body, variables)\n : `Event ${event.category} from ${event.source.id}`\n\n const rawDeviceId = event.data['deviceId'] ?? event.source.id\n const deviceIdNum = typeof rawDeviceId === 'number' ? rawDeviceId : Number(rawDeviceId)\n const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : undefined\n const notification = {\n title,\n message,\n severity: this.priorityToSeverity(rule.priority),\n category: event.category,\n deviceId,\n data: event.data,\n timestamp: event.timestamp.getTime(),\n } as const\n\n let success = true\n let dispatchError: string | undefined\n\n for (const output of this.outputs) {\n try {\n await output.send(notification)\n } catch (err) {\n success = false\n dispatchError = String(err)\n this.ctx.logger.error('Output failed', { meta: { outputId: output.id, error: dispatchError } })\n }\n }\n\n if (this.auditLog) {\n const entry: NotificationHistoryEntry = {\n id: randomUUID(),\n ruleId: rule.id,\n ruleName: rule.name,\n eventId: event.id,\n timestamp: event.timestamp.getTime(),\n outputs: this.outputs.map(o => o.id),\n success,\n error: dispatchError,\n deviceId,\n }\n await this.auditLog.record(entry).catch(err => {\n this.ctx.logger.error('Failed to record audit log entry', { meta: { error: String(err) } })\n })\n }\n }\n }\n\n private buildTemplateVariables(event: SystemEvent): Record<string, string> {\n const data = event.data\n return {\n deviceId: (data['deviceId'] as string | undefined) ?? String(event.source.id),\n deviceName: (data['deviceName'] as string | undefined) ?? String(event.source.id),\n className: (data['className'] as string | undefined) ?? '',\n confidence: String(data['confidence'] ?? ''),\n zoneId: (data['zoneId'] as string | undefined) ?? '',\n zoneName: (data['zoneName'] as string | undefined) ?? '',\n category: event.category,\n timestamp: event.timestamp.toISOString(),\n }\n }\n\n private priorityToSeverity(\n priority: NotificationRule['priority'],\n ): 'info' | 'warning' | 'critical' {\n if (priority === 'critical') return 'critical'\n if (priority === 'high') return 'warning'\n return 'info'\n }\n}\n\nexport default AdvancedNotifierAddon\n","import type { AddonApi, NotificationRule } from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst KEY = 'advanced-notifier:rules'\n\nexport class RuleStore {\n private readonly api: AddonApi\n\n constructor(api: AddonApi) {\n this.api = api\n }\n\n async getRules(): Promise<NotificationRule[]> {\n const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: KEY })\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationRule[]\n }\n\n async saveRules(rules: readonly NotificationRule[]): Promise<void> {\n await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: KEY, value: [...rules] })\n }\n\n async upsertRule(rule: NotificationRule): Promise<void> {\n const rules = await this.getRules()\n const idx = rules.findIndex(r => r.id === rule.id)\n if (idx >= 0) {\n rules[idx] = rule\n } else {\n rules.push(rule)\n }\n await this.saveRules(rules)\n }\n\n async deleteRule(ruleId: string): Promise<void> {\n const rules = await this.getRules()\n await this.saveRules(rules.filter(r => r.id !== ruleId))\n }\n}\n","import type { NotificationRule } from '@camstack/types'\n\ninterface EventData {\n readonly id: string\n readonly timestamp: Date\n readonly source: { readonly type: string; readonly id: string | number }\n readonly category: string\n readonly data: Record<string, unknown>\n}\n\nexport class RuleEngine {\n evaluate(event: EventData, rules: readonly NotificationRule[]): NotificationRule[] {\n return rules.filter(rule => this.matchesRule(event, rule))\n }\n\n private matchesRule(event: EventData, rule: NotificationRule): boolean {\n if (!rule.enabled) return false\n if (!rule.eventTypes.includes(event.category)) return false\n return this.evaluateConditions(event, rule)\n }\n\n private evaluateConditions(event: EventData, rule: NotificationRule): boolean {\n const { conditions } = rule\n const data = event.data\n\n if (conditions.deviceIds?.length) {\n const raw = data['deviceId'] ?? event.source.id\n const deviceId = typeof raw === 'number' ? raw : Number(raw)\n if (!Number.isFinite(deviceId) || !conditions.deviceIds.includes(deviceId)) return false\n }\n\n if (conditions.classNames?.length) {\n const className = data['className'] as string | undefined\n if (!className || !conditions.classNames.includes(className)) return false\n }\n\n if (conditions.zoneIds?.length) {\n const zoneId = data['zoneId'] as string | undefined\n if (!zoneId || !conditions.zoneIds.includes(zoneId)) return false\n }\n\n if (conditions.minConfidence !== undefined) {\n const confidence = data['confidence'] as number | undefined\n if (confidence === undefined || confidence < conditions.minConfidence) return false\n }\n\n if (conditions.schedule) {\n const now = new Date()\n const { days, startHour, endHour } = conditions.schedule\n if (!days.includes(now.getDay())) return false\n const hour = now.getHours()\n if (hour < startHour || hour >= endHour) return false\n }\n\n return true\n }\n}\n","export class CooldownTracker {\n private readonly lastFired = new Map<string, number>()\n\n canFire(ruleId: string, cooldownSeconds: number): boolean {\n const last = this.lastFired.get(ruleId)\n if (last === undefined) return true\n return (Date.now() - last) / 1000 >= cooldownSeconds\n }\n\n recordFire(ruleId: string): void {\n this.lastFired.set(ruleId, Date.now())\n }\n\n reset(): void {\n this.lastFired.clear()\n }\n}\n","export function renderTemplate(template: string, variables: Record<string, string>): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value: string | undefined = variables[key]\n return value !== undefined ? value : match\n })\n}\n","import type { INotificationOutput, Notification, IScopedLogger } from '@camstack/types'\n\nexport class ConsoleOutput implements INotificationOutput {\n readonly id = 'console'\n readonly name = 'Console Log'\n readonly icon = 'terminal'\n\n private readonly logger: IScopedLogger\n\n constructor(logger: IScopedLogger) {\n this.logger = logger\n }\n\n async send(notification: Notification): Promise<void> {\n this.logger.info(\n `[${notification.severity}] ${notification.title}: ${notification.message}`,\n notification.deviceId !== undefined\n ? { tags: { deviceId: notification.deviceId } }\n : undefined,\n )\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n await this.send({\n title: 'Test',\n message: 'Console output working',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n }\n}\n","import type {\n AddonApi,\n NotificationHistoryEntry,\n NotificationHistoryFilter,\n} from '@camstack/types'\n\nconst COLLECTION = 'addon-settings' as const\nconst LOG_KEY = 'notifier-history:log'\n\nexport class AuditLog {\n private readonly api: AddonApi\n private readonly maxEntries: number\n\n constructor(api: AddonApi, maxEntries = 1000) {\n this.api = api\n this.maxEntries = maxEntries\n }\n\n async record(entry: NotificationHistoryEntry): Promise<void> {\n const entries = await this.getAll()\n const updated = [...entries, entry]\n const trimmed =\n updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated\n await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: LOG_KEY, value: trimmed })\n }\n\n async getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]> {\n let entries = await this.getAll()\n if (filter?.ruleId) {\n entries = entries.filter(e => e.ruleId === filter.ruleId)\n }\n if (filter?.deviceId) {\n const deviceId = filter.deviceId\n entries = entries.filter(e => e.deviceId === deviceId)\n }\n if (filter?.from !== undefined) {\n const from = filter.from\n entries = entries.filter(e => e.timestamp >= from)\n }\n if (filter?.to !== undefined) {\n const to = filter.to\n entries = entries.filter(e => e.timestamp <= to)\n }\n if (filter?.limit) {\n entries = entries.slice(-filter.limit)\n }\n return entries\n }\n\n private async getAll(): Promise<NotificationHistoryEntry[]> {\n const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: LOG_KEY })\n if (!raw || !Array.isArray(raw)) return []\n return raw as NotificationHistoryEntry[]\n }\n}\n"],"mappings":"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","names":["COLLECTION"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AdvancedNotifierAddon } from './addon.mjs';
|
|
2
|
-
import { NotificationRule,
|
|
2
|
+
import { NotificationRule, AddonApi, INotificationOutput, Notification, IScopedLogger, NotificationHistoryEntry, NotificationHistoryFilter } from '@camstack/types';
|
|
3
3
|
|
|
4
4
|
declare function renderTemplate(template: string, variables: Record<string, string>): string;
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ interface EventData {
|
|
|
15
15
|
readonly timestamp: Date;
|
|
16
16
|
readonly source: {
|
|
17
17
|
readonly type: string;
|
|
18
|
-
readonly id: string;
|
|
18
|
+
readonly id: string | number;
|
|
19
19
|
};
|
|
20
20
|
readonly category: string;
|
|
21
21
|
readonly data: Record<string, unknown>;
|
|
@@ -27,8 +27,8 @@ declare class RuleEngine {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
declare class RuleStore {
|
|
30
|
-
private readonly
|
|
31
|
-
constructor(
|
|
30
|
+
private readonly api;
|
|
31
|
+
constructor(api: AddonApi);
|
|
32
32
|
getRules(): Promise<NotificationRule[]>;
|
|
33
33
|
saveRules(rules: readonly NotificationRule[]): Promise<void>;
|
|
34
34
|
upsertRule(rule: NotificationRule): Promise<void>;
|
|
@@ -86,9 +86,9 @@ declare class NotificationBatcher {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
declare class AuditLog {
|
|
89
|
-
private readonly
|
|
89
|
+
private readonly api;
|
|
90
90
|
private readonly maxEntries;
|
|
91
|
-
constructor(
|
|
91
|
+
constructor(api: AddonApi, maxEntries?: number);
|
|
92
92
|
record(entry: NotificationHistoryEntry): Promise<void>;
|
|
93
93
|
getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]>;
|
|
94
94
|
private getAll;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AdvancedNotifierAddon } from './addon.js';
|
|
2
|
-
import { NotificationRule,
|
|
2
|
+
import { NotificationRule, AddonApi, INotificationOutput, Notification, IScopedLogger, NotificationHistoryEntry, NotificationHistoryFilter } from '@camstack/types';
|
|
3
3
|
|
|
4
4
|
declare function renderTemplate(template: string, variables: Record<string, string>): string;
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ interface EventData {
|
|
|
15
15
|
readonly timestamp: Date;
|
|
16
16
|
readonly source: {
|
|
17
17
|
readonly type: string;
|
|
18
|
-
readonly id: string;
|
|
18
|
+
readonly id: string | number;
|
|
19
19
|
};
|
|
20
20
|
readonly category: string;
|
|
21
21
|
readonly data: Record<string, unknown>;
|
|
@@ -27,8 +27,8 @@ declare class RuleEngine {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
declare class RuleStore {
|
|
30
|
-
private readonly
|
|
31
|
-
constructor(
|
|
30
|
+
private readonly api;
|
|
31
|
+
constructor(api: AddonApi);
|
|
32
32
|
getRules(): Promise<NotificationRule[]>;
|
|
33
33
|
saveRules(rules: readonly NotificationRule[]): Promise<void>;
|
|
34
34
|
upsertRule(rule: NotificationRule): Promise<void>;
|
|
@@ -86,9 +86,9 @@ declare class NotificationBatcher {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
declare class AuditLog {
|
|
89
|
-
private readonly
|
|
89
|
+
private readonly api;
|
|
90
90
|
private readonly maxEntries;
|
|
91
|
-
constructor(
|
|
91
|
+
constructor(api: AddonApi, maxEntries?: number);
|
|
92
92
|
record(entry: NotificationHistoryEntry): Promise<void>;
|
|
93
93
|
getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]>;
|
|
94
94
|
private getAll;
|