@camstack/addon-advanced-notifier 0.1.22 → 0.1.24

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 CHANGED
@@ -186,41 +186,37 @@ class AdvancedNotifierAddon extends types.BaseAddon {
186
186
  auditLog;
187
187
  // Event bus unsubscribe handles
188
188
  unsubscribers = [];
189
- // IAdvancedNotifier implementation (exposed as capability)
189
+ // Canonical-shape provider (every method takes `{input}` and returns Promise<output>).
190
190
  notifier = {
191
- getRules: () => this.cachedRules,
192
- upsertRule: (rule) => {
193
- this.ruleStore.upsertRule(rule).then(() => {
194
- const idx = this.cachedRules.findIndex((r) => r.id === rule.id);
195
- if (idx >= 0) {
196
- this.cachedRules = [
197
- ...this.cachedRules.slice(0, idx),
198
- rule,
199
- ...this.cachedRules.slice(idx + 1)
200
- ];
201
- } else {
202
- this.cachedRules = [...this.cachedRules, rule];
203
- }
204
- }).catch((err) => {
205
- this.ctx.logger.error("Failed to upsert rule", { meta: { error: String(err) } });
206
- });
191
+ getRules: async () => ({ rules: this.cachedRules }),
192
+ upsertRule: async ({ rule }) => {
193
+ await this.ruleStore.upsertRule(rule);
194
+ const idx = this.cachedRules.findIndex((r) => r.id === rule.id);
195
+ if (idx >= 0) {
196
+ this.cachedRules = [
197
+ ...this.cachedRules.slice(0, idx),
198
+ rule,
199
+ ...this.cachedRules.slice(idx + 1)
200
+ ];
201
+ } else {
202
+ this.cachedRules = [...this.cachedRules, rule];
203
+ }
204
+ return { success: true };
207
205
  },
208
- deleteRule: (ruleId) => {
209
- this.ruleStore.deleteRule(ruleId).then(() => {
210
- this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
211
- }).catch((err) => {
212
- this.ctx.logger.error("Failed to delete rule", { tags: { ruleId }, meta: { error: String(err) } });
213
- });
206
+ deleteRule: async ({ ruleId }) => {
207
+ await this.ruleStore.deleteRule(ruleId);
208
+ this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
209
+ return { success: true };
214
210
  },
215
- testRule: async (ruleId, lookbackMinutes) => {
211
+ testRule: async ({ ruleId, lookbackMinutes }) => {
216
212
  const rule = this.cachedRules.find((r) => r.id === ruleId);
217
- if (!rule) return [];
213
+ if (!rule) return { results: [] };
218
214
  const since = new Date(Date.now() - lookbackMinutes * 60 * 1e3);
219
215
  const recentEvents = this.ctx.eventBus.getRecent(
220
216
  { category: void 0 },
221
217
  1e3
222
218
  ).filter((e) => e.timestamp >= since);
223
- return recentEvents.map((event) => {
219
+ const results = recentEvents.map((event) => {
224
220
  const matched = this.ruleEngine.evaluate(event, [rule]);
225
221
  return {
226
222
  ruleId,
@@ -230,10 +226,12 @@ class AdvancedNotifierAddon extends types.BaseAddon {
230
226
  reason: matched.length === 0 ? "Conditions not met" : void 0
231
227
  };
232
228
  });
229
+ return { results };
233
230
  },
234
- getHistory: async (filter) => {
235
- if (!this.auditLog) return [];
236
- return this.auditLog.getHistory(filter);
231
+ getHistory: async ({ filter }) => {
232
+ if (!this.auditLog) return { entries: [] };
233
+ const entries = await this.auditLog.getHistory(filter);
234
+ return { entries };
237
235
  }
238
236
  };
239
237
  async onInitialize() {
package/dist/addon.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"addon.js","sources":["../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","../src/addon.ts"],"sourcesContent":["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","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"],"names":["COLLECTION","EventCategory","BaseAddon","advancedNotifierCapability","notificationOutputCapability","randomUUID"],"mappings":"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"}
1
+ {"version":3,"file":"addon.js","sources":["../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","../src/addon.ts"],"sourcesContent":["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","import type {\n ProviderRegistration,\n IAdvancedNotifierProvider,\n INotificationOutput,\n NotificationRule,\n NotificationHistoryEntry,\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 // Canonical-shape provider (every method takes `{input}` and returns Promise<output>).\n private readonly notifier: IAdvancedNotifierProvider = {\n getRules: async () => ({ rules: this.cachedRules as readonly NotificationRule[] }),\n\n upsertRule: async ({ rule }) => {\n await this.ruleStore.upsertRule(rule)\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 return { success: true }\n },\n\n deleteRule: async ({ ruleId }) => {\n await this.ruleStore.deleteRule(ruleId)\n this.cachedRules = this.cachedRules.filter(r => r.id !== ruleId)\n return { success: true }\n },\n\n testRule: async ({ ruleId, lookbackMinutes }) => {\n const rule = this.cachedRules.find(r => r.id === ruleId)\n if (!rule) return { results: [] }\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 const results = 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 return { results }\n },\n\n getHistory: async ({ filter }) => {\n if (!this.auditLog) return { entries: [] }\n const entries = await this.auditLog.getHistory(filter)\n return { entries }\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"],"names":["COLLECTION","EventCategory","BaseAddon","advancedNotifierCapability","notificationOutputCapability","randomUUID"],"mappings":"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"}
package/dist/addon.mjs CHANGED
@@ -184,41 +184,37 @@ class AdvancedNotifierAddon extends BaseAddon {
184
184
  auditLog;
185
185
  // Event bus unsubscribe handles
186
186
  unsubscribers = [];
187
- // IAdvancedNotifier implementation (exposed as capability)
187
+ // Canonical-shape provider (every method takes `{input}` and returns Promise<output>).
188
188
  notifier = {
189
- getRules: () => this.cachedRules,
190
- upsertRule: (rule) => {
191
- this.ruleStore.upsertRule(rule).then(() => {
192
- const idx = this.cachedRules.findIndex((r) => r.id === rule.id);
193
- if (idx >= 0) {
194
- this.cachedRules = [
195
- ...this.cachedRules.slice(0, idx),
196
- rule,
197
- ...this.cachedRules.slice(idx + 1)
198
- ];
199
- } else {
200
- this.cachedRules = [...this.cachedRules, rule];
201
- }
202
- }).catch((err) => {
203
- this.ctx.logger.error("Failed to upsert rule", { meta: { error: String(err) } });
204
- });
189
+ getRules: async () => ({ rules: this.cachedRules }),
190
+ upsertRule: async ({ rule }) => {
191
+ await this.ruleStore.upsertRule(rule);
192
+ const idx = this.cachedRules.findIndex((r) => r.id === rule.id);
193
+ if (idx >= 0) {
194
+ this.cachedRules = [
195
+ ...this.cachedRules.slice(0, idx),
196
+ rule,
197
+ ...this.cachedRules.slice(idx + 1)
198
+ ];
199
+ } else {
200
+ this.cachedRules = [...this.cachedRules, rule];
201
+ }
202
+ return { success: true };
205
203
  },
206
- deleteRule: (ruleId) => {
207
- this.ruleStore.deleteRule(ruleId).then(() => {
208
- this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
209
- }).catch((err) => {
210
- this.ctx.logger.error("Failed to delete rule", { tags: { ruleId }, meta: { error: String(err) } });
211
- });
204
+ deleteRule: async ({ ruleId }) => {
205
+ await this.ruleStore.deleteRule(ruleId);
206
+ this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
207
+ return { success: true };
212
208
  },
213
- testRule: async (ruleId, lookbackMinutes) => {
209
+ testRule: async ({ ruleId, lookbackMinutes }) => {
214
210
  const rule = this.cachedRules.find((r) => r.id === ruleId);
215
- if (!rule) return [];
211
+ if (!rule) return { results: [] };
216
212
  const since = new Date(Date.now() - lookbackMinutes * 60 * 1e3);
217
213
  const recentEvents = this.ctx.eventBus.getRecent(
218
214
  { category: void 0 },
219
215
  1e3
220
216
  ).filter((e) => e.timestamp >= since);
221
- return recentEvents.map((event) => {
217
+ const results = recentEvents.map((event) => {
222
218
  const matched = this.ruleEngine.evaluate(event, [rule]);
223
219
  return {
224
220
  ruleId,
@@ -228,10 +224,12 @@ class AdvancedNotifierAddon extends BaseAddon {
228
224
  reason: matched.length === 0 ? "Conditions not met" : void 0
229
225
  };
230
226
  });
227
+ return { results };
231
228
  },
232
- getHistory: async (filter) => {
233
- if (!this.auditLog) return [];
234
- return this.auditLog.getHistory(filter);
229
+ getHistory: async ({ filter }) => {
230
+ if (!this.auditLog) return { entries: [] };
231
+ const entries = await this.auditLog.getHistory(filter);
232
+ return { entries };
235
233
  }
236
234
  };
237
235
  async onInitialize() {
@@ -1 +1 @@
1
- {"version":3,"file":"addon.mjs","sources":["../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","../src/addon.ts"],"sourcesContent":["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","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"],"names":["COLLECTION"],"mappings":"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"}
1
+ {"version":3,"file":"addon.mjs","sources":["../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","../src/addon.ts"],"sourcesContent":["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","import type {\n ProviderRegistration,\n IAdvancedNotifierProvider,\n INotificationOutput,\n NotificationRule,\n NotificationHistoryEntry,\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 // Canonical-shape provider (every method takes `{input}` and returns Promise<output>).\n private readonly notifier: IAdvancedNotifierProvider = {\n getRules: async () => ({ rules: this.cachedRules as readonly NotificationRule[] }),\n\n upsertRule: async ({ rule }) => {\n await this.ruleStore.upsertRule(rule)\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 return { success: true }\n },\n\n deleteRule: async ({ ruleId }) => {\n await this.ruleStore.deleteRule(ruleId)\n this.cachedRules = this.cachedRules.filter(r => r.id !== ruleId)\n return { success: true }\n },\n\n testRule: async ({ ruleId, lookbackMinutes }) => {\n const rule = this.cachedRules.find(r => r.id === ruleId)\n if (!rule) return { results: [] }\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 const results = 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 return { results }\n },\n\n getHistory: async ({ filter }) => {\n if (!this.auditLog) return { entries: [] }\n const entries = await this.auditLog.getHistory(filter)\n return { entries }\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"],"names":["COLLECTION"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-advanced-notifier",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Rules-based notification engine for CamStack",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",