@camstack/addon-advanced-notifier 0.1.14 → 0.1.16
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 +42 -77
- package/dist/addon.js.map +1 -1
- package/dist/addon.mjs +410 -6
- package/dist/addon.mjs.map +1 -1
- package/dist/index.js +19 -477
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
- package/dist/addon.d.mts +0 -29
- package/dist/addon.d.ts +0 -29
- package/dist/chunk-MPVMHQWM.mjs +0 -430
- package/dist/chunk-MPVMHQWM.mjs.map +0 -1
- package/dist/index.d.mts +0 -97
- package/dist/index.d.ts +0 -97
package/dist/index.js
CHANGED
|
@@ -1,458 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
AdvancedNotifierAddon: () => AdvancedNotifierAddon,
|
|
24
|
-
AuditLog: () => AuditLog,
|
|
25
|
-
ConsoleOutput: () => ConsoleOutput,
|
|
26
|
-
CooldownTracker: () => CooldownTracker,
|
|
27
|
-
HomeAssistantOutput: () => HomeAssistantOutput,
|
|
28
|
-
NotificationBatcher: () => NotificationBatcher,
|
|
29
|
-
RuleEngine: () => RuleEngine,
|
|
30
|
-
RuleStore: () => RuleStore,
|
|
31
|
-
WebhookOutput: () => WebhookOutput,
|
|
32
|
-
renderTemplate: () => renderTemplate
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(index_exports);
|
|
35
|
-
|
|
36
|
-
// src/addon.ts
|
|
37
|
-
var import_types = require("@camstack/types");
|
|
38
|
-
|
|
39
|
-
// src/rules/rule-store.ts
|
|
40
|
-
var COLLECTION = "addon-settings";
|
|
41
|
-
var KEY = "advanced-notifier:rules";
|
|
42
|
-
var RuleStore = class {
|
|
43
|
-
api;
|
|
44
|
-
constructor(api) {
|
|
45
|
-
this.api = api;
|
|
46
|
-
}
|
|
47
|
-
async getRules() {
|
|
48
|
-
const raw = await this.api.settingsStore.get.query({ collection: COLLECTION, key: KEY });
|
|
49
|
-
if (!raw || !Array.isArray(raw)) return [];
|
|
50
|
-
return raw;
|
|
51
|
-
}
|
|
52
|
-
async saveRules(rules) {
|
|
53
|
-
await this.api.settingsStore.set.mutate({ collection: COLLECTION, key: KEY, value: [...rules] });
|
|
54
|
-
}
|
|
55
|
-
async upsertRule(rule) {
|
|
56
|
-
const rules = await this.getRules();
|
|
57
|
-
const idx = rules.findIndex((r) => r.id === rule.id);
|
|
58
|
-
if (idx >= 0) {
|
|
59
|
-
rules[idx] = rule;
|
|
60
|
-
} else {
|
|
61
|
-
rules.push(rule);
|
|
62
|
-
}
|
|
63
|
-
await this.saveRules(rules);
|
|
64
|
-
}
|
|
65
|
-
async deleteRule(ruleId) {
|
|
66
|
-
const rules = await this.getRules();
|
|
67
|
-
await this.saveRules(rules.filter((r) => r.id !== ruleId));
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// src/rules/rule-engine.ts
|
|
72
|
-
var RuleEngine = class {
|
|
73
|
-
evaluate(event, rules) {
|
|
74
|
-
return rules.filter((rule) => this.matchesRule(event, rule));
|
|
75
|
-
}
|
|
76
|
-
matchesRule(event, rule) {
|
|
77
|
-
if (!rule.enabled) return false;
|
|
78
|
-
if (!rule.eventTypes.includes(event.category)) return false;
|
|
79
|
-
return this.evaluateConditions(event, rule);
|
|
80
|
-
}
|
|
81
|
-
evaluateConditions(event, rule) {
|
|
82
|
-
const { conditions } = rule;
|
|
83
|
-
const data = event.data;
|
|
84
|
-
if (conditions.deviceIds?.length) {
|
|
85
|
-
const raw = data["deviceId"] ?? event.source.id;
|
|
86
|
-
const deviceId = typeof raw === "number" ? raw : Number(raw);
|
|
87
|
-
if (!Number.isFinite(deviceId) || !conditions.deviceIds.includes(deviceId)) return false;
|
|
88
|
-
}
|
|
89
|
-
if (conditions.classNames?.length) {
|
|
90
|
-
const className = data["className"];
|
|
91
|
-
if (!className || !conditions.classNames.includes(className)) return false;
|
|
92
|
-
}
|
|
93
|
-
if (conditions.zoneIds?.length) {
|
|
94
|
-
const zoneId = data["zoneId"];
|
|
95
|
-
if (!zoneId || !conditions.zoneIds.includes(zoneId)) return false;
|
|
96
|
-
}
|
|
97
|
-
if (conditions.minConfidence !== void 0) {
|
|
98
|
-
const confidence = data["confidence"];
|
|
99
|
-
if (confidence === void 0 || confidence < conditions.minConfidence) return false;
|
|
100
|
-
}
|
|
101
|
-
if (conditions.schedule) {
|
|
102
|
-
const now = /* @__PURE__ */ new Date();
|
|
103
|
-
const { days, startHour, endHour } = conditions.schedule;
|
|
104
|
-
if (!days.includes(now.getDay())) return false;
|
|
105
|
-
const hour = now.getHours();
|
|
106
|
-
if (hour < startHour || hour >= endHour) return false;
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// src/rules/cooldown.ts
|
|
113
|
-
var CooldownTracker = class {
|
|
114
|
-
lastFired = /* @__PURE__ */ new Map();
|
|
115
|
-
canFire(ruleId, cooldownSeconds) {
|
|
116
|
-
const last = this.lastFired.get(ruleId);
|
|
117
|
-
if (last === void 0) return true;
|
|
118
|
-
return (Date.now() - last) / 1e3 >= cooldownSeconds;
|
|
119
|
-
}
|
|
120
|
-
recordFire(ruleId) {
|
|
121
|
-
this.lastFired.set(ruleId, Date.now());
|
|
122
|
-
}
|
|
123
|
-
reset() {
|
|
124
|
-
this.lastFired.clear();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
// src/template/renderer.ts
|
|
129
|
-
function renderTemplate(template, variables) {
|
|
130
|
-
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
131
|
-
const value = variables[key];
|
|
132
|
-
return value !== void 0 ? value : match;
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/outputs/console.ts
|
|
137
|
-
var ConsoleOutput = class {
|
|
138
|
-
id = "console";
|
|
139
|
-
name = "Console Log";
|
|
140
|
-
icon = "terminal";
|
|
141
|
-
logger;
|
|
142
|
-
constructor(logger) {
|
|
143
|
-
this.logger = logger;
|
|
144
|
-
}
|
|
145
|
-
async send(notification) {
|
|
146
|
-
this.logger.info(
|
|
147
|
-
`[${notification.severity}] ${notification.title}: ${notification.message}`,
|
|
148
|
-
notification.deviceId !== void 0 ? { tags: { deviceId: notification.deviceId } } : void 0
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
async sendTest() {
|
|
152
|
-
await this.send({
|
|
153
|
-
title: "Test",
|
|
154
|
-
message: "Console output working",
|
|
155
|
-
severity: "info",
|
|
156
|
-
category: "test",
|
|
157
|
-
timestamp: Date.now()
|
|
158
|
-
});
|
|
159
|
-
return { success: true };
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// src/history/audit-log.ts
|
|
164
|
-
var COLLECTION2 = "addon-settings";
|
|
165
|
-
var LOG_KEY = "notifier-history:log";
|
|
166
|
-
var AuditLog = class {
|
|
167
|
-
api;
|
|
168
|
-
maxEntries;
|
|
169
|
-
constructor(api, maxEntries = 1e3) {
|
|
170
|
-
this.api = api;
|
|
171
|
-
this.maxEntries = maxEntries;
|
|
172
|
-
}
|
|
173
|
-
async record(entry) {
|
|
174
|
-
const entries = await this.getAll();
|
|
175
|
-
const updated = [...entries, entry];
|
|
176
|
-
const trimmed = updated.length > this.maxEntries ? updated.slice(updated.length - this.maxEntries) : updated;
|
|
177
|
-
await this.api.settingsStore.set.mutate({ collection: COLLECTION2, key: LOG_KEY, value: trimmed });
|
|
178
|
-
}
|
|
179
|
-
async getHistory(filter) {
|
|
180
|
-
let entries = await this.getAll();
|
|
181
|
-
if (filter?.ruleId) {
|
|
182
|
-
entries = entries.filter((e) => e.ruleId === filter.ruleId);
|
|
183
|
-
}
|
|
184
|
-
if (filter?.deviceId) {
|
|
185
|
-
const deviceId = filter.deviceId;
|
|
186
|
-
entries = entries.filter((e) => e.deviceId === deviceId);
|
|
187
|
-
}
|
|
188
|
-
if (filter?.from !== void 0) {
|
|
189
|
-
const from = filter.from;
|
|
190
|
-
entries = entries.filter((e) => e.timestamp >= from);
|
|
191
|
-
}
|
|
192
|
-
if (filter?.to !== void 0) {
|
|
193
|
-
const to = filter.to;
|
|
194
|
-
entries = entries.filter((e) => e.timestamp <= to);
|
|
195
|
-
}
|
|
196
|
-
if (filter?.limit) {
|
|
197
|
-
entries = entries.slice(-filter.limit);
|
|
198
|
-
}
|
|
199
|
-
return entries;
|
|
200
|
-
}
|
|
201
|
-
async getAll() {
|
|
202
|
-
const raw = await this.api.settingsStore.get.query({ collection: COLLECTION2, key: LOG_KEY });
|
|
203
|
-
if (!raw || !Array.isArray(raw)) return [];
|
|
204
|
-
return raw;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
// src/addon.ts
|
|
209
|
-
var import_node_crypto = require("crypto");
|
|
210
|
-
var EVENT_CATEGORIES = [
|
|
211
|
-
import_types.EventCategory.DetectionRaw,
|
|
212
|
-
import_types.EventCategory.DetectionResult,
|
|
213
|
-
import_types.EventCategory.DetectionCameraNative
|
|
214
|
-
];
|
|
215
|
-
var DEFAULT_NOTIFIER_CONFIG = {
|
|
216
|
-
defaultCooldownSeconds: 60,
|
|
217
|
-
maxHistoryEntries: 1e4,
|
|
218
|
-
auditLogRetentionDays: 30,
|
|
219
|
-
consoleOutputEnabled: true
|
|
220
|
-
};
|
|
221
|
-
var AdvancedNotifierAddon = class extends import_types.BaseAddon {
|
|
222
|
-
constructor() {
|
|
223
|
-
super({ ...DEFAULT_NOTIFIER_CONFIG });
|
|
224
|
-
}
|
|
225
|
-
// Rule management
|
|
226
|
-
ruleStore;
|
|
227
|
-
ruleEngine;
|
|
228
|
-
cooldown;
|
|
229
|
-
cachedRules = [];
|
|
230
|
-
// Outputs
|
|
231
|
-
consoleOutput;
|
|
232
|
-
outputs = [];
|
|
233
|
-
// Audit log
|
|
234
|
-
auditLog;
|
|
235
|
-
// Event bus unsubscribe handles
|
|
236
|
-
unsubscribers = [];
|
|
237
|
-
// IAdvancedNotifier implementation (exposed as capability)
|
|
238
|
-
notifier = {
|
|
239
|
-
getRules: () => this.cachedRules,
|
|
240
|
-
upsertRule: (rule) => {
|
|
241
|
-
this.ruleStore.upsertRule(rule).then(() => {
|
|
242
|
-
const idx = this.cachedRules.findIndex((r) => r.id === rule.id);
|
|
243
|
-
if (idx >= 0) {
|
|
244
|
-
this.cachedRules = [
|
|
245
|
-
...this.cachedRules.slice(0, idx),
|
|
246
|
-
rule,
|
|
247
|
-
...this.cachedRules.slice(idx + 1)
|
|
248
|
-
];
|
|
249
|
-
} else {
|
|
250
|
-
this.cachedRules = [...this.cachedRules, rule];
|
|
251
|
-
}
|
|
252
|
-
}).catch((err) => {
|
|
253
|
-
this.ctx.logger.error("Failed to upsert rule", { meta: { error: String(err) } });
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
deleteRule: (ruleId) => {
|
|
257
|
-
this.ruleStore.deleteRule(ruleId).then(() => {
|
|
258
|
-
this.cachedRules = this.cachedRules.filter((r) => r.id !== ruleId);
|
|
259
|
-
}).catch((err) => {
|
|
260
|
-
this.ctx.logger.error("Failed to delete rule", { tags: { ruleId }, meta: { error: String(err) } });
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
testRule: async (ruleId, lookbackMinutes) => {
|
|
264
|
-
const rule = this.cachedRules.find((r) => r.id === ruleId);
|
|
265
|
-
if (!rule) return [];
|
|
266
|
-
const since = new Date(Date.now() - lookbackMinutes * 60 * 1e3);
|
|
267
|
-
const recentEvents = this.ctx.eventBus.getRecent(
|
|
268
|
-
{ category: void 0 },
|
|
269
|
-
1e3
|
|
270
|
-
).filter((e) => e.timestamp >= since);
|
|
271
|
-
return recentEvents.map((event) => {
|
|
272
|
-
const matched = this.ruleEngine.evaluate(event, [rule]);
|
|
273
|
-
return {
|
|
274
|
-
ruleId,
|
|
275
|
-
eventId: event.id,
|
|
276
|
-
timestamp: event.timestamp.getTime(),
|
|
277
|
-
wouldFire: matched.length > 0,
|
|
278
|
-
reason: matched.length === 0 ? "Conditions not met" : void 0
|
|
279
|
-
};
|
|
280
|
-
});
|
|
281
|
-
},
|
|
282
|
-
getHistory: async (filter) => {
|
|
283
|
-
if (!this.auditLog) return [];
|
|
284
|
-
return this.auditLog.getHistory(filter);
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
async onInitialize() {
|
|
288
|
-
this.ruleEngine = new RuleEngine();
|
|
289
|
-
this.cooldown = new CooldownTracker();
|
|
290
|
-
this.consoleOutput = new ConsoleOutput(this.ctx.logger.child("console-output"));
|
|
291
|
-
this.outputs = [this.consoleOutput];
|
|
292
|
-
const api = this.ctx.api;
|
|
293
|
-
if (api) {
|
|
294
|
-
this.ruleStore = new RuleStore(api);
|
|
295
|
-
this.auditLog = new AuditLog(api);
|
|
296
|
-
this.cachedRules = await this.ruleStore.getRules();
|
|
297
|
-
this.ctx.logger.info("Loaded notification rules", { meta: { count: this.cachedRules.length } });
|
|
298
|
-
} else {
|
|
299
|
-
this.ctx.logger.warn("No ctx.api \u2014 rules will not be persisted");
|
|
300
|
-
}
|
|
301
|
-
for (const category of EVENT_CATEGORIES) {
|
|
302
|
-
const unsubscribe = this.ctx.eventBus.subscribe(
|
|
303
|
-
{ category },
|
|
304
|
-
(event) => {
|
|
305
|
-
void this.handleEvent(event);
|
|
306
|
-
}
|
|
307
|
-
);
|
|
308
|
-
this.unsubscribers.push(unsubscribe);
|
|
309
|
-
}
|
|
310
|
-
this.ctx.logger.info("AdvancedNotifierAddon initialized");
|
|
311
|
-
return [
|
|
312
|
-
{ capability: import_types.advancedNotifierCapability, provider: this.notifier },
|
|
313
|
-
{ capability: import_types.notificationOutputCapability, provider: this.consoleOutput }
|
|
314
|
-
];
|
|
315
|
-
}
|
|
316
|
-
async onShutdown() {
|
|
317
|
-
for (const unsub of this.unsubscribers) {
|
|
318
|
-
unsub();
|
|
319
|
-
}
|
|
320
|
-
this.unsubscribers = [];
|
|
321
|
-
this.ctx.logger.info("AdvancedNotifierAddon shut down");
|
|
322
|
-
}
|
|
323
|
-
// ── Config management (session 5 Sprint B7) ─────────────────────────
|
|
324
|
-
//
|
|
325
|
-
// Rules themselves are opaque JSON documents managed through the
|
|
326
|
-
// `upsertRule`/`deleteRule` cap methods — they do NOT belong in the
|
|
327
|
-
// schema-backed settings API. The schema below exposes addon-level
|
|
328
|
-
// tuning knobs only. All fields are global-scope because advanced-
|
|
329
|
-
// notifier is a system singleton with no per-device state.
|
|
330
|
-
// ── Three-level settings API (Phase 3) ──────────────────────────────
|
|
331
|
-
globalSettingsSchema() {
|
|
332
|
-
return this.schema({
|
|
333
|
-
sections: [{
|
|
334
|
-
id: "advanced-notifier-settings",
|
|
335
|
-
title: "Advanced Notifier",
|
|
336
|
-
columns: 2,
|
|
337
|
-
fields: [
|
|
338
|
-
this.field({
|
|
339
|
-
type: "number",
|
|
340
|
-
key: "defaultCooldownSeconds",
|
|
341
|
-
label: "Default Cooldown",
|
|
342
|
-
description: "Default cooldown between repeated notifications for the same rule.",
|
|
343
|
-
min: 0,
|
|
344
|
-
max: 3600,
|
|
345
|
-
step: 1,
|
|
346
|
-
default: 60,
|
|
347
|
-
unit: "s"
|
|
348
|
-
}),
|
|
349
|
-
this.field({
|
|
350
|
-
type: "number",
|
|
351
|
-
key: "maxHistoryEntries",
|
|
352
|
-
label: "Max History Entries",
|
|
353
|
-
description: "Maximum number of notification history entries to keep.",
|
|
354
|
-
min: 100,
|
|
355
|
-
max: 1e5,
|
|
356
|
-
step: 100,
|
|
357
|
-
default: 1e4
|
|
358
|
-
}),
|
|
359
|
-
this.field({
|
|
360
|
-
type: "number",
|
|
361
|
-
key: "auditLogRetentionDays",
|
|
362
|
-
label: "Audit Log Retention",
|
|
363
|
-
description: "How long to retain notification history entries.",
|
|
364
|
-
min: 1,
|
|
365
|
-
max: 365,
|
|
366
|
-
step: 1,
|
|
367
|
-
default: 30,
|
|
368
|
-
unit: "days"
|
|
369
|
-
}),
|
|
370
|
-
this.field({
|
|
371
|
-
type: "boolean",
|
|
372
|
-
key: "consoleOutputEnabled",
|
|
373
|
-
label: "Console Output Enabled",
|
|
374
|
-
description: "Emit matched rules to the server console.",
|
|
375
|
-
default: true
|
|
376
|
-
})
|
|
377
|
-
]
|
|
378
|
-
}]
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
async handleEvent(event) {
|
|
382
|
-
const matchedRules = this.ruleEngine.evaluate(event, this.cachedRules);
|
|
383
|
-
for (const rule of matchedRules) {
|
|
384
|
-
const cooldownSeconds = rule.conditions.cooldownSeconds ?? this.config.defaultCooldownSeconds;
|
|
385
|
-
if (!this.cooldown.canFire(rule.id, cooldownSeconds)) {
|
|
386
|
-
this.ctx.logger.debug("Rule skipped \u2014 in cooldown", { meta: { ruleId: rule.id } });
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
389
|
-
this.cooldown.recordFire(rule.id);
|
|
390
|
-
const variables = this.buildTemplateVariables(event);
|
|
391
|
-
const title = rule.template ? renderTemplate(rule.template.title, variables) : `CamStack Alert: ${rule.name}`;
|
|
392
|
-
const message = rule.template ? renderTemplate(rule.template.body, variables) : `Event ${event.category} from ${event.source.id}`;
|
|
393
|
-
const rawDeviceId = event.data["deviceId"] ?? event.source.id;
|
|
394
|
-
const deviceIdNum = typeof rawDeviceId === "number" ? rawDeviceId : Number(rawDeviceId);
|
|
395
|
-
const deviceId = Number.isFinite(deviceIdNum) ? deviceIdNum : void 0;
|
|
396
|
-
const notification = {
|
|
397
|
-
title,
|
|
398
|
-
message,
|
|
399
|
-
severity: this.priorityToSeverity(rule.priority),
|
|
400
|
-
category: event.category,
|
|
401
|
-
deviceId,
|
|
402
|
-
data: event.data,
|
|
403
|
-
timestamp: event.timestamp.getTime()
|
|
404
|
-
};
|
|
405
|
-
let success = true;
|
|
406
|
-
let dispatchError;
|
|
407
|
-
for (const output of this.outputs) {
|
|
408
|
-
try {
|
|
409
|
-
await output.send(notification);
|
|
410
|
-
} catch (err) {
|
|
411
|
-
success = false;
|
|
412
|
-
dispatchError = String(err);
|
|
413
|
-
this.ctx.logger.error("Output failed", { meta: { outputId: output.id, error: dispatchError } });
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
if (this.auditLog) {
|
|
417
|
-
const entry = {
|
|
418
|
-
id: (0, import_node_crypto.randomUUID)(),
|
|
419
|
-
ruleId: rule.id,
|
|
420
|
-
ruleName: rule.name,
|
|
421
|
-
eventId: event.id,
|
|
422
|
-
timestamp: event.timestamp.getTime(),
|
|
423
|
-
outputs: this.outputs.map((o) => o.id),
|
|
424
|
-
success,
|
|
425
|
-
error: dispatchError,
|
|
426
|
-
deviceId
|
|
427
|
-
};
|
|
428
|
-
await this.auditLog.record(entry).catch((err) => {
|
|
429
|
-
this.ctx.logger.error("Failed to record audit log entry", { meta: { error: String(err) } });
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
buildTemplateVariables(event) {
|
|
435
|
-
const data = event.data;
|
|
436
|
-
return {
|
|
437
|
-
deviceId: data["deviceId"] ?? String(event.source.id),
|
|
438
|
-
deviceName: data["deviceName"] ?? String(event.source.id),
|
|
439
|
-
className: data["className"] ?? "",
|
|
440
|
-
confidence: String(data["confidence"] ?? ""),
|
|
441
|
-
zoneId: data["zoneId"] ?? "",
|
|
442
|
-
zoneName: data["zoneName"] ?? "",
|
|
443
|
-
category: event.category,
|
|
444
|
-
timestamp: event.timestamp.toISOString()
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
priorityToSeverity(priority) {
|
|
448
|
-
if (priority === "critical") return "critical";
|
|
449
|
-
if (priority === "high") return "warning";
|
|
450
|
-
return "info";
|
|
451
|
-
}
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
// src/outputs/webhook.ts
|
|
455
|
-
var WebhookOutput = class {
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const addon = require("./addon.js");
|
|
4
|
+
class WebhookOutput {
|
|
456
5
|
id = "webhook";
|
|
457
6
|
name = "Webhook";
|
|
458
7
|
icon = "webhook";
|
|
@@ -481,10 +30,8 @@ var WebhookOutput = class {
|
|
|
481
30
|
return { success: false, error: String(err) };
|
|
482
31
|
}
|
|
483
32
|
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// src/outputs/home-assistant.ts
|
|
487
|
-
var HomeAssistantOutput = class {
|
|
33
|
+
}
|
|
34
|
+
class HomeAssistantOutput {
|
|
488
35
|
id = "home-assistant";
|
|
489
36
|
name = "Home Assistant";
|
|
490
37
|
icon = "home";
|
|
@@ -525,10 +72,8 @@ var HomeAssistantOutput = class {
|
|
|
525
72
|
return { success: false, error: String(err) };
|
|
526
73
|
}
|
|
527
74
|
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// src/batching/batcher.ts
|
|
531
|
-
var NotificationBatcher = class {
|
|
75
|
+
}
|
|
76
|
+
class NotificationBatcher {
|
|
532
77
|
windowMs;
|
|
533
78
|
handler;
|
|
534
79
|
batches = /* @__PURE__ */ new Map();
|
|
@@ -560,18 +105,15 @@ var NotificationBatcher = class {
|
|
|
560
105
|
}
|
|
561
106
|
this.batches.clear();
|
|
562
107
|
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
renderTemplate
|
|
576
|
-
});
|
|
577
|
-
//# sourceMappingURL=index.js.map
|
|
108
|
+
}
|
|
109
|
+
exports.AdvancedNotifierAddon = addon.AdvancedNotifierAddon;
|
|
110
|
+
exports.AuditLog = addon.AuditLog;
|
|
111
|
+
exports.ConsoleOutput = addon.ConsoleOutput;
|
|
112
|
+
exports.CooldownTracker = addon.CooldownTracker;
|
|
113
|
+
exports.RuleEngine = addon.RuleEngine;
|
|
114
|
+
exports.RuleStore = addon.RuleStore;
|
|
115
|
+
exports.renderTemplate = addon.renderTemplate;
|
|
116
|
+
exports.HomeAssistantOutput = HomeAssistantOutput;
|
|
117
|
+
exports.NotificationBatcher = NotificationBatcher;
|
|
118
|
+
exports.WebhookOutput = WebhookOutput;
|
|
119
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../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","../src/outputs/webhook.ts","../src/outputs/home-assistant.ts","../src/batching/batcher.ts"],"sourcesContent":["export { AdvancedNotifierAddon } from './addon.js'\nexport { renderTemplate } from './template/renderer.js'\nexport { CooldownTracker } from './rules/cooldown.js'\nexport { RuleEngine } from './rules/rule-engine.js'\nexport { RuleStore } from './rules/rule-store.js'\nexport { WebhookOutput } from './outputs/webhook.js'\nexport { HomeAssistantOutput } from './outputs/home-assistant.js'\nexport { ConsoleOutput } from './outputs/console.js'\nexport { NotificationBatcher } from './batching/batcher.js'\nexport { AuditLog } from './history/audit-log.js'\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","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 { INotificationOutput, Notification } from '@camstack/types'\n\nexport class WebhookOutput implements INotificationOutput {\n readonly id = 'webhook'\n readonly name = 'Webhook'\n readonly icon = 'webhook'\n\n private readonly url: string\n\n constructor(url: string) {\n this.url = url\n }\n\n async send(notification: Notification): Promise<void> {\n await fetch(this.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(notification),\n })\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.send({\n title: 'Test Notification',\n message: 'This is a test from CamStack Advanced Notifier',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n } catch (err) {\n return { success: false, error: String(err) }\n }\n }\n}\n","import type { INotificationOutput, Notification } from '@camstack/types'\n\nexport class HomeAssistantOutput implements INotificationOutput {\n readonly id = 'home-assistant'\n readonly name = 'Home Assistant'\n readonly icon = 'home'\n\n private readonly url: string\n private readonly token: string\n\n constructor(url: string, token: string) {\n this.url = url.replace(/\\/$/, '')\n this.token = token\n }\n\n async send(notification: Notification): Promise<void> {\n await fetch(`${this.url}/api/events/camstack_notification`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify({\n title: notification.title,\n message: notification.message,\n severity: notification.severity,\n category: notification.category,\n device_id: notification.deviceId,\n data: notification.data,\n }),\n })\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.send({\n title: 'CamStack Test',\n message: 'Connection verified',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n } catch (err) {\n return { success: false, error: String(err) }\n }\n }\n}\n","export class NotificationBatcher {\n private readonly windowMs: number\n private readonly handler: (frameKey: string, events: unknown[]) => void\n private readonly batches = new Map<\n string,\n { events: unknown[]; timer: ReturnType<typeof setTimeout> }\n >()\n\n constructor(windowMs: number, handler: (frameKey: string, events: unknown[]) => void) {\n this.windowMs = windowMs\n this.handler = handler\n }\n\n add(frameKey: string, event: unknown): void {\n let batch = this.batches.get(frameKey)\n if (!batch) {\n batch = {\n events: [],\n timer: setTimeout(() => this.flush(frameKey), this.windowMs),\n }\n this.batches.set(frameKey, batch)\n }\n batch.events.push(event)\n }\n\n private flush(frameKey: string): void {\n const batch = this.batches.get(frameKey)\n if (!batch) return\n this.batches.delete(frameKey)\n this.handler(frameKey, batch.events)\n }\n\n shutdown(): void {\n for (const [key, batch] of this.batches) {\n clearTimeout(batch.timer)\n this.handler(key, batch.events)\n }\n this.batches.clear()\n }\n}\n"],"mappings":"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","names":["COLLECTION"]}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/outputs/webhook.ts","../src/outputs/home-assistant.ts","../src/batching/batcher.ts"],"sourcesContent":["import type { INotificationOutput, Notification } from '@camstack/types'\n\nexport class WebhookOutput implements INotificationOutput {\n readonly id = 'webhook'\n readonly name = 'Webhook'\n readonly icon = 'webhook'\n\n private readonly url: string\n\n constructor(url: string) {\n this.url = url\n }\n\n async send(notification: Notification): Promise<void> {\n await fetch(this.url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(notification),\n })\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.send({\n title: 'Test Notification',\n message: 'This is a test from CamStack Advanced Notifier',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n } catch (err) {\n return { success: false, error: String(err) }\n }\n }\n}\n","import type { INotificationOutput, Notification } from '@camstack/types'\n\nexport class HomeAssistantOutput implements INotificationOutput {\n readonly id = 'home-assistant'\n readonly name = 'Home Assistant'\n readonly icon = 'home'\n\n private readonly url: string\n private readonly token: string\n\n constructor(url: string, token: string) {\n this.url = url.replace(/\\/$/, '')\n this.token = token\n }\n\n async send(notification: Notification): Promise<void> {\n await fetch(`${this.url}/api/events/camstack_notification`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify({\n title: notification.title,\n message: notification.message,\n severity: notification.severity,\n category: notification.category,\n device_id: notification.deviceId,\n data: notification.data,\n }),\n })\n }\n\n async sendTest(): Promise<{ success: boolean; error?: string }> {\n try {\n await this.send({\n title: 'CamStack Test',\n message: 'Connection verified',\n severity: 'info',\n category: 'test',\n timestamp: Date.now(),\n })\n return { success: true }\n } catch (err) {\n return { success: false, error: String(err) }\n }\n }\n}\n","export class NotificationBatcher {\n private readonly windowMs: number\n private readonly handler: (frameKey: string, events: unknown[]) => void\n private readonly batches = new Map<\n string,\n { events: unknown[]; timer: ReturnType<typeof setTimeout> }\n >()\n\n constructor(windowMs: number, handler: (frameKey: string, events: unknown[]) => void) {\n this.windowMs = windowMs\n this.handler = handler\n }\n\n add(frameKey: string, event: unknown): void {\n let batch = this.batches.get(frameKey)\n if (!batch) {\n batch = {\n events: [],\n timer: setTimeout(() => this.flush(frameKey), this.windowMs),\n }\n this.batches.set(frameKey, batch)\n }\n batch.events.push(event)\n }\n\n private flush(frameKey: string): void {\n const batch = this.batches.get(frameKey)\n if (!batch) return\n this.batches.delete(frameKey)\n this.handler(frameKey, batch.events)\n }\n\n shutdown(): void {\n for (const [key, batch] of this.batches) {\n clearTimeout(batch.timer)\n this.handler(key, batch.events)\n }\n this.batches.clear()\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
AuditLog,
|
|
4
|
-
ConsoleOutput,
|
|
5
|
-
CooldownTracker,
|
|
6
|
-
RuleEngine,
|
|
7
|
-
RuleStore,
|
|
8
|
-
renderTemplate
|
|
9
|
-
} from "./chunk-MPVMHQWM.mjs";
|
|
10
|
-
|
|
11
|
-
// src/outputs/webhook.ts
|
|
12
|
-
var WebhookOutput = class {
|
|
1
|
+
import { AdvancedNotifierAddon, A, C, a, R, b, r } from "./addon.mjs";
|
|
2
|
+
class WebhookOutput {
|
|
13
3
|
id = "webhook";
|
|
14
4
|
name = "Webhook";
|
|
15
5
|
icon = "webhook";
|
|
@@ -38,10 +28,8 @@ var WebhookOutput = class {
|
|
|
38
28
|
return { success: false, error: String(err) };
|
|
39
29
|
}
|
|
40
30
|
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/outputs/home-assistant.ts
|
|
44
|
-
var HomeAssistantOutput = class {
|
|
31
|
+
}
|
|
32
|
+
class HomeAssistantOutput {
|
|
45
33
|
id = "home-assistant";
|
|
46
34
|
name = "Home Assistant";
|
|
47
35
|
icon = "home";
|
|
@@ -82,10 +70,8 @@ var HomeAssistantOutput = class {
|
|
|
82
70
|
return { success: false, error: String(err) };
|
|
83
71
|
}
|
|
84
72
|
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/batching/batcher.ts
|
|
88
|
-
var NotificationBatcher = class {
|
|
73
|
+
}
|
|
74
|
+
class NotificationBatcher {
|
|
89
75
|
windowMs;
|
|
90
76
|
handler;
|
|
91
77
|
batches = /* @__PURE__ */ new Map();
|
|
@@ -117,17 +103,17 @@ var NotificationBatcher = class {
|
|
|
117
103
|
}
|
|
118
104
|
this.batches.clear();
|
|
119
105
|
}
|
|
120
|
-
}
|
|
106
|
+
}
|
|
121
107
|
export {
|
|
122
108
|
AdvancedNotifierAddon,
|
|
123
|
-
AuditLog,
|
|
124
|
-
ConsoleOutput,
|
|
125
|
-
CooldownTracker,
|
|
109
|
+
A as AuditLog,
|
|
110
|
+
C as ConsoleOutput,
|
|
111
|
+
a as CooldownTracker,
|
|
126
112
|
HomeAssistantOutput,
|
|
127
113
|
NotificationBatcher,
|
|
128
|
-
RuleEngine,
|
|
129
|
-
RuleStore,
|
|
114
|
+
R as RuleEngine,
|
|
115
|
+
b as RuleStore,
|
|
130
116
|
WebhookOutput,
|
|
131
|
-
renderTemplate
|
|
117
|
+
r as renderTemplate
|
|
132
118
|
};
|
|
133
|
-
//# sourceMappingURL=index.mjs.map
|
|
119
|
+
//# sourceMappingURL=index.mjs.map
|