@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.
@@ -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
- backend;
6
- constructor(backend) {
7
- this.backend = backend;
8
+ api;
9
+ constructor(api) {
10
+ this.api = api;
8
11
  }
9
12
  async getRules() {
10
- const raw = await this.backend.get(COLLECTION, KEY);
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.backend.set(COLLECTION, KEY, [...rules]);
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 deviceId = data["deviceId"] ?? event.source.id;
48
- if (!conditions.deviceIds.includes(deviceId)) return false;
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}` + (notification.deviceId ? ` (device=${notification.deviceId})` : "")
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
- backend;
132
+ api;
128
133
  maxEntries;
129
- constructor(backend, maxEntries = 1e3) {
130
- this.backend = backend;
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.backend.set(COLLECTION2, LOG_KEY, trimmed);
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["deviceId"] === deviceId);
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.backend.get(COLLECTION2, LOG_KEY);
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 = ["detection.raw", "detection.result", "detection.camera-native"];
171
- var AdvancedNotifierAddon = class {
172
- manifest = {
173
- id: "advanced-notifier",
174
- name: "Advanced Notifier",
175
- version: "0.1.0",
176
- capabilities: [
177
- { name: "advanced-notifier", mode: "singleton" },
178
- { name: "notification-output", mode: "collection" }
179
- ]
180
- };
181
- context;
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.context.logger.error("Failed to upsert rule", { error: String(err) });
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.context.logger.error("Failed to delete rule", { error: String(err) });
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.context.eventBus.getRecent(
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 initialize(context) {
245
- this.context = context;
252
+ async onInitialize() {
246
253
  this.ruleEngine = new RuleEngine();
247
254
  this.cooldown = new CooldownTracker();
248
- this.consoleOutput = new ConsoleOutput(context.logger.child("console-output"));
255
+ this.consoleOutput = new ConsoleOutput(this.ctx.logger.child("console-output"));
249
256
  this.outputs = [this.consoleOutput];
250
- if (context.settingsBackend) {
251
- this.ruleStore = new RuleStore(context.settingsBackend);
252
- this.auditLog = new AuditLog(context.settingsBackend);
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.context.logger.info(`Loaded ${this.cachedRules.length} notification rules`);
262
+ this.ctx.logger.info("Loaded notification rules", { meta: { count: this.cachedRules.length } });
255
263
  } else {
256
- this.context.logger.warn("No settingsBackend \u2014 rules will not be persisted");
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 = context.eventBus.subscribe(
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.context.logger.info("AdvancedNotifierAddon initialized");
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 shutdown() {
281
+ async onShutdown() {
270
282
  for (const unsub of this.unsubscribers) {
271
283
  unsub();
272
284
  }
273
285
  this.unsubscribers = [];
274
- this.context.logger.info("AdvancedNotifierAddon shut down");
286
+ this.ctx.logger.info("AdvancedNotifierAddon shut down");
275
287
  }
276
- getCapabilityProvider(name) {
277
- if (name === "advanced-notifier") {
278
- return this.notifier;
279
- }
280
- if (name === "notification-output") {
281
- return this.consoleOutput;
282
- }
283
- return null;
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 ?? 0;
349
+ const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds;
289
350
  if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {
290
- this.context.logger.debug(`Rule ${rule.id} skipped \u2014 in cooldown`);
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: event.data["deviceId"] ?? event.source.id,
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.context.logger.error(`Output ${output.id} failed`, { error: dispatchError });
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.context.logger.error("Failed to record audit log entry", { error: String(err) });
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-HYUFV4O5.mjs.map
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, ISettingsBackend, INotificationOutput, Notification, IScopedLogger, NotificationHistoryEntry, NotificationHistoryFilter } from '@camstack/types';
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 backend;
31
- constructor(backend: ISettingsBackend);
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 backend;
89
+ private readonly api;
90
90
  private readonly maxEntries;
91
- constructor(backend: ISettingsBackend, maxEntries?: number);
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, ISettingsBackend, INotificationOutput, Notification, IScopedLogger, NotificationHistoryEntry, NotificationHistoryFilter } from '@camstack/types';
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 backend;
31
- constructor(backend: ISettingsBackend);
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 backend;
89
+ private readonly api;
90
90
  private readonly maxEntries;
91
- constructor(backend: ISettingsBackend, maxEntries?: number);
91
+ constructor(api: AddonApi, maxEntries?: number);
92
92
  record(entry: NotificationHistoryEntry): Promise<void>;
93
93
  getHistory(filter?: NotificationHistoryFilter): Promise<NotificationHistoryEntry[]>;
94
94
  private getAll;