@iobroker/js-controller-common 7.0.0 → 7.0.2-alpha.0-20241026-8055a2557

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.
@@ -9,13 +9,20 @@ import type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';
9
9
  import type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';
10
10
  import type Winston from 'winston';
11
11
  type MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;
12
+ /** Settings to configure an instance of NotificationHandler */
12
13
  export interface NotificationHandlerSettings {
14
+ /** Name of the host (not id - hence, without `system.host.` prefix) */
13
15
  host: string;
16
+ /** States DB instance */
14
17
  states: StatesInRedisClient;
18
+ /** Objects DB instance */
15
19
  objects: ObjectsInRedisClient;
20
+ /** Logger instance */
16
21
  log: Winston.Logger | Console;
22
+ /** Prefix for the log messages */
17
23
  logPrefix: string;
18
24
  }
25
+ /** A notification config entry represents the top level of the notification structure */
19
26
  export interface NotificationsConfigEntry {
20
27
  /** e.g. system */
21
28
  scope: string;
@@ -23,16 +30,23 @@ export interface NotificationsConfigEntry {
23
30
  name: MultilingualObject;
24
31
  /** multilingual description */
25
32
  description: MultilingualObject;
33
+ /** The notification categories inside this scope */
26
34
  categories: CategoryConfigEntry[];
27
35
  }
28
36
  export type Severity = 'info' | 'notify' | 'alert';
37
+ /** A notification category entry represents one of the categories of a scope */
29
38
  export interface CategoryConfigEntry {
39
+ /** Id of the category */
30
40
  category: string;
41
+ /** Name of the category */
31
42
  name: MultilingualObject;
32
43
  /** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */
33
44
  severity: Severity;
45
+ /** Description of the category */
34
46
  description: MultilingualObject;
47
+ /** Regex where `console.error` output is parsed against to check if a notification of this category should be generated */
35
48
  regex: string[];
49
+ /** Restrict the maximum number of messages for this category */
36
50
  limit: number;
37
51
  }
38
52
  interface NotificationMessageObject {
@@ -40,24 +54,27 @@ interface NotificationMessageObject {
40
54
  ts: number;
41
55
  contextData?: ioBroker.NotificationContextData;
42
56
  }
43
- export interface FilteredNotificationInformation {
44
- [scope: string]: {
45
- description: MultilingualObject;
46
- name: MultilingualObject;
47
- categories: {
48
- [category: string]: {
49
- description: MultilingualObject;
50
- name: MultilingualObject;
51
- severity: Severity;
52
- instances: {
53
- [instance: string]: {
54
- messages: NotificationMessageObject[];
55
- };
56
- };
57
- };
57
+ interface FilteredNotificationCategory {
58
+ description: MultilingualObject;
59
+ name: MultilingualObject;
60
+ severity: Severity;
61
+ instances: {
62
+ [instance: string]: {
63
+ messages: NotificationMessageObject[];
58
64
  };
59
65
  };
60
66
  }
67
+ interface FilteredNotificationScope {
68
+ description: MultilingualObject;
69
+ name: MultilingualObject;
70
+ categories: {
71
+ [category: string]: FilteredNotificationCategory;
72
+ };
73
+ }
74
+ /** The filtered information as a response for `getFilteredInformation` */
75
+ export interface FilteredNotificationInformation {
76
+ [scope: string]: FilteredNotificationScope;
77
+ }
61
78
  interface AddMessageOptions {
62
79
  /** Scope of the message */
63
80
  scope: string;
@@ -70,6 +87,9 @@ interface AddMessageOptions {
70
87
  /** Additional context for the notification which can be used by notification processing adapters */
71
88
  contextData?: ioBroker.NotificationContextData;
72
89
  }
90
+ /**
91
+ * The Notification handler class provides an interface to manage the ioBroker notification system
92
+ */
73
93
  export declare class NotificationHandler {
74
94
  private states;
75
95
  private objects;
@@ -79,6 +99,11 @@ export declare class NotificationHandler {
79
99
  private readonly setup;
80
100
  private readonly logPrefix;
81
101
  private readonly host;
102
+ /**
103
+ * Creates a new instance of the notification handler class
104
+ *
105
+ * @param settings settings to configure the handler instance
106
+ */
82
107
  constructor(settings: NotificationHandlerSettings);
83
108
  /**
84
109
  * Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init
@@ -95,9 +95,9 @@ class NotificationHandler {
95
95
  } catch (e) {
96
96
  throw new Error(`Could not get notifications setup from instances: ${e.message}`);
97
97
  }
98
- for (const scope of Object.keys(this.currentNotifications)) {
99
- for (const category of Object.keys(this.currentNotifications[scope])) {
100
- for (const instance of Object.keys(this.currentNotifications[scope][category])) {
98
+ for (const notificationsCategory of Object.values(this.currentNotifications)) {
99
+ for (const instancesInfo of Object.values(notificationsCategory)) {
100
+ for (const instance of Object.keys(instancesInfo)) {
101
101
  if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {
102
102
  this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);
103
103
  await this.clearNotifications(null, null, instance);
@@ -134,13 +134,21 @@ class NotificationHandler {
134
134
  }
135
135
  }
136
136
  if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {
137
- this.setup[scopeObj.scope] = this.setup[scopeObj.scope] || {
137
+ const setupScope = this.setup[scopeObj.scope] || {
138
138
  name: scopeObj.name,
139
139
  description: scopeObj.description,
140
140
  categories: {}
141
141
  };
142
+ this.setup[scopeObj.scope] = setupScope;
142
143
  for (const categoryObj of scopeObj.categories) {
143
- this.setup[scopeObj.scope].categories[categoryObj.category] = this.setup[scopeObj.scope].categories[categoryObj.category] || {};
144
+ const setupCategory = setupScope.categories[categoryObj.category] || {
145
+ regex: [],
146
+ limit: categoryObj.limit,
147
+ name: categoryObj.name,
148
+ severity: categoryObj.severity,
149
+ description: categoryObj.description
150
+ };
151
+ setupScope.categories[categoryObj.category] = setupCategory;
144
152
  try {
145
153
  let regex = [];
146
154
  if (Array.isArray(categoryObj.regex)) {
@@ -150,15 +158,9 @@ class NotificationHandler {
150
158
  } else if (typeof categoryObj.regex === "string") {
151
159
  regex = [new RegExp(categoryObj.regex)];
152
160
  }
153
- this.setup[scopeObj.scope].categories[categoryObj.category] = {
154
- regex,
155
- limit: categoryObj.limit,
156
- name: categoryObj.name,
157
- severity: categoryObj.severity,
158
- description: categoryObj.description
159
- };
161
+ setupCategory.regex = regex;
160
162
  } catch (e) {
161
- this.log.error(`${this.logPrefix} Cannot store ${JSON.stringify(categoryObj.regex)} for scope "${scopeObj.scope}", category "${categoryObj.category}": ${e.message}`);
163
+ this.log.error(`${this.logPrefix} Cannot store regex "${JSON.stringify(categoryObj.regex)}" for scope "${scopeObj.scope}", category "${categoryObj.category}": ${e.message}`);
162
164
  }
163
165
  }
164
166
  }
@@ -186,25 +188,30 @@ class NotificationHandler {
186
188
  if (!category) {
187
189
  categories = this._parseText(scope, message);
188
190
  }
191
+ const currentScopeObj = this.currentNotifications[scope] || {};
192
+ this.currentNotifications[scope] = currentScopeObj;
189
193
  for (const _category of categories) {
190
- if (_category) {
191
- this.currentNotifications[scope] = this.currentNotifications[scope] || {};
192
- this.currentNotifications[scope][_category] = this.currentNotifications[scope][_category] || {};
193
- this.currentNotifications[scope][_category][instance] = this.currentNotifications[scope][_category][instance] || [];
194
- if (!this.setup[scope]?.categories[_category]) {
195
- this.log.warn(`${this.logPrefix} No configuration found for scope "${scope}" and category "${_category}"`);
196
- continue;
197
- }
198
- while (this.setup[scope].categories[_category].limit < this.currentNotifications[scope][_category][instance].length + 1) {
199
- this.currentNotifications[scope][_category][instance].pop();
200
- }
201
- this.currentNotifications[scope][_category][instance].unshift({ message, ts: Date.now(), contextData });
194
+ if (!_category) {
195
+ continue;
196
+ }
197
+ const currentCategoryObj = currentScopeObj[_category] || {};
198
+ currentScopeObj[_category] = currentCategoryObj;
199
+ const currentInstances = currentCategoryObj[instance] || [];
200
+ currentCategoryObj[instance] = currentInstances;
201
+ const setupCategory = this.setup[scope]?.categories[_category];
202
+ if (!setupCategory) {
203
+ this.log.warn(`${this.logPrefix} No configuration found for scope "${scope}" and category "${_category}"`);
204
+ continue;
205
+ }
206
+ while (setupCategory.limit < currentInstances.length + 1) {
207
+ currentInstances.pop();
202
208
  }
209
+ currentInstances.unshift({ message, ts: Date.now(), contextData });
203
210
  }
204
211
  if (import_js_controller_common_db.tools.isObject(this.currentNotifications[scope])) {
205
- for (const _category of Object.keys(this.currentNotifications[scope])) {
206
- const categoryCounter = Object.keys(this.currentNotifications[scope][_category]).length;
207
- stateVal[_category] = { count: categoryCounter };
212
+ for (const [categoryName, category2] of Object.entries(currentScopeObj)) {
213
+ const categoryCounter = Object.keys(category2).length;
214
+ stateVal[categoryName] = { count: categoryCounter };
208
215
  }
209
216
  }
210
217
  try {
@@ -217,14 +224,14 @@ class NotificationHandler {
217
224
  }
218
225
  }
219
226
  async _updateScopeStates() {
220
- for (const scope of Object.keys(this.currentNotifications)) {
227
+ for (const [scope, scopeObj] of Object.entries(this.currentNotifications)) {
221
228
  const stateVal = {};
222
- for (const category of Object.keys(this.currentNotifications[scope])) {
223
- const catCounter = Object.keys(this.currentNotifications[scope][category]).length;
229
+ for (const [category, categoryObj] of Object.entries(scopeObj)) {
230
+ const catCounter = Object.keys(categoryObj).length;
224
231
  stateVal[category] = { count: catCounter };
225
232
  }
226
233
  try {
227
- await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {
234
+ await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {
228
235
  val: JSON.stringify(stateVal),
229
236
  ack: true
230
237
  });
@@ -235,13 +242,15 @@ class NotificationHandler {
235
242
  }
236
243
  _parseText(scope, message) {
237
244
  const categories = [];
238
- if (this.setup[scope]) {
239
- for (const [categoryId, categoryObj] of Object.entries(this.setup[scope].categories)) {
240
- for (const regex of categoryObj.regex) {
241
- if (regex.test(message)) {
242
- categories.push(categoryId);
243
- break;
244
- }
245
+ const scopeObj = this.setup[scope];
246
+ if (!scopeObj) {
247
+ return categories;
248
+ }
249
+ for (const [categoryId, categoryObj] of Object.entries(scopeObj.categories)) {
250
+ for (const regex of categoryObj.regex) {
251
+ if (regex.test(message)) {
252
+ categories.push(categoryId);
253
+ break;
245
254
  }
246
255
  }
247
256
  }
@@ -263,38 +272,41 @@ class NotificationHandler {
263
272
  }
264
273
  getFilteredInformation(scopeFilter, categoryFilter, instanceFilter) {
265
274
  const res = {};
266
- for (const scope of Object.keys(this.currentNotifications)) {
275
+ for (const [scope, currentNotification] of Object.entries(this.currentNotifications)) {
267
276
  if (scopeFilter && scopeFilter !== scope) {
268
277
  continue;
269
278
  }
270
- if (!this.setup[scope]) {
279
+ const scopeObj = this.setup[scope];
280
+ if (!scopeObj) {
271
281
  continue;
272
282
  }
273
- res[scope] = {
283
+ const scopeResult = {
274
284
  categories: {},
275
- description: this.setup[scope].description,
276
- name: this.setup[scope].name
285
+ description: scopeObj.description,
286
+ name: scopeObj.name
277
287
  };
278
- for (const category of Object.keys(this.currentNotifications[scope])) {
288
+ res[scope] = scopeResult;
289
+ for (const [category, currentCategoryObj] of Object.entries(currentNotification)) {
279
290
  if (categoryFilter && categoryFilter !== category) {
280
291
  continue;
281
292
  }
282
- const categoryObj = this.setup[scope].categories[category];
293
+ const categoryObj = scopeObj.categories[category];
283
294
  if (!categoryObj) {
284
295
  continue;
285
296
  }
286
- res[scope].categories[category] = {
297
+ const resultCategories = {
287
298
  instances: {},
288
299
  description: categoryObj.description,
289
300
  name: categoryObj.name,
290
301
  severity: categoryObj.severity
291
302
  };
292
- for (const instance of Object.keys(this.currentNotifications[scope][category])) {
303
+ scopeResult.categories[category] = resultCategories;
304
+ for (const [instance, instanceObj] of Object.entries(currentCategoryObj)) {
293
305
  if (instanceFilter && instanceFilter !== instance) {
294
306
  continue;
295
307
  }
296
- res[scope].categories[category].instances[instance] = {
297
- messages: this.currentNotifications[scope][category][instance]
308
+ resultCategories.instances[instance] = {
309
+ messages: instanceObj
298
310
  };
299
311
  }
300
312
  }
@@ -303,7 +315,8 @@ class NotificationHandler {
303
315
  }
304
316
  async clearNotifications(scopeFilter, categoryFilter, instanceFilter) {
305
317
  for (const scope of Object.keys(this.currentNotifications)) {
306
- if (!this.currentNotifications[scope]) {
318
+ const currentNotificationScope = this.currentNotifications[scope];
319
+ if (!currentNotificationScope) {
307
320
  delete this.currentNotifications[scope];
308
321
  continue;
309
322
  }
@@ -317,29 +330,29 @@ class NotificationHandler {
317
330
  } catch (e) {
318
331
  this.log.error(`${this.logPrefix} Could not get state for scope "${scope}": ${e.message}`);
319
332
  }
320
- for (const category of Object.keys(this.currentNotifications[scope])) {
333
+ for (const [category, categoryObj] of Object.entries(currentNotificationScope)) {
321
334
  if (categoryFilter && categoryFilter !== category) {
322
335
  continue;
323
336
  }
324
337
  let categoryCounter = 0;
325
- for (const instance of Object.keys(this.currentNotifications[scope][category])) {
338
+ for (const instance of Object.keys(categoryObj)) {
326
339
  if (instanceFilter && instanceFilter !== instance) {
327
340
  categoryCounter += 1;
328
341
  continue;
329
342
  }
330
- delete this.currentNotifications[scope][category][instance];
343
+ delete categoryObj[instance];
331
344
  }
332
345
  stateVal[category] = { count: categoryCounter };
333
- if (!Object.keys(this.currentNotifications[scope][category]).length) {
334
- delete this.currentNotifications[scope][category];
346
+ if (!Object.keys(categoryObj).length) {
347
+ delete currentNotificationScope[category];
335
348
  delete stateVal[category];
336
349
  }
337
350
  }
338
- if (!Object.keys(this.currentNotifications[scope]).length) {
351
+ if (!Object.keys(currentNotificationScope).length) {
339
352
  delete this.currentNotifications[scope];
340
353
  }
341
354
  try {
342
- await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {
355
+ await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {
343
356
  val: JSON.stringify(stateVal),
344
357
  ack: true
345
358
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/lib/common/notificationHandler.ts"],
4
- "sourcesContent": ["/**\n * Notification handler class\n *\n * 2021-2024 foxriver76 <moritz.heusinger@gmail.com>\n */\n\nimport fs from 'fs-extra';\nimport { tools } from '@iobroker/js-controller-common-db';\nimport path from 'node:path';\nimport type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport type Winston from 'winston';\n\ntype MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;\n\nexport interface NotificationHandlerSettings {\n host: string;\n states: StatesInRedisClient;\n objects: ObjectsInRedisClient;\n log: Winston.Logger | Console;\n logPrefix: string;\n}\n\nexport interface NotificationsConfigEntry {\n /** e.g. system */\n scope: string;\n /** multilingual name */\n name: MultilingualObject;\n /** multilingual description */\n description: MultilingualObject;\n categories: CategoryConfigEntry[];\n}\n\nexport type Severity = 'info' | 'notify' | 'alert';\n\nexport interface CategoryConfigEntry {\n category: string;\n name: MultilingualObject;\n /** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */\n severity: Severity;\n description: MultilingualObject;\n regex: string[];\n limit: number;\n}\n\ninterface NotificationMessageObject {\n message: string;\n ts: number;\n contextData?: ioBroker.NotificationContextData;\n}\n\ninterface NotificationsObject {\n [scope: string]: {\n [category: string]: {\n [instance: string]: NotificationMessageObject[];\n };\n };\n}\n\nexport interface FilteredNotificationInformation {\n [scope: string]: {\n description: MultilingualObject;\n name: MultilingualObject;\n categories: {\n [category: string]: {\n description: MultilingualObject;\n name: MultilingualObject;\n severity: Severity;\n instances: {\n [instance: string]: {\n messages: NotificationMessageObject[];\n };\n };\n };\n };\n };\n}\n\ninterface NotificationSetupCategory {\n regex: RegExp[];\n limit: number;\n name: MultilingualObject;\n severity: Severity;\n description: MultilingualObject;\n}\n\ninterface NotificationSetupObject {\n [scope: string]: {\n name: MultilingualObject;\n description: MultilingualObject;\n categories: {\n [category: string]: NotificationSetupCategory;\n };\n };\n}\n\ninterface ScopeStateValue {\n [category: string]: {\n count: number;\n };\n}\n\ninterface AddMessageOptions {\n /** Scope of the message */\n scope: string;\n /** Category of the message, if non we check against regex of scope */\n category?: string | null;\n /** Message to add */\n message: string;\n /** Instance e.g., hm-rpc.1 or hostname, if hostname it needs to be prefixed like system.host.rpi */\n instance: string;\n /** Additional context for the notification which can be used by notification processing adapters */\n contextData?: ioBroker.NotificationContextData;\n}\n\nexport class NotificationHandler {\n private states: StatesInRedisClient;\n private objects: ObjectsInRedisClient;\n private log: Winston.Logger | Console;\n private currentNotifications: NotificationsObject = {};\n // default data dir is relative to controllerDir\n private readonly dataDir = path.join(tools.getControllerDir(), tools.getDefaultDataDir());\n private readonly setup: NotificationSetupObject = {};\n private readonly logPrefix: string;\n private readonly host: string;\n\n constructor(settings: NotificationHandlerSettings) {\n this.states = settings.states;\n this.objects = settings.objects;\n this.log = settings.log;\n this.logPrefix = settings.logPrefix;\n this.host = settings.host;\n\n // load notifications file\n this._loadNotifications();\n }\n\n /**\n * Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init\n */\n async getSetupOfAllAdaptersFromHost(): Promise<void> {\n // create the initial notifications object\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications`, {\n type: 'folder',\n common: {\n name: {\n en: 'Notifications',\n de: 'Benachrichtigungen',\n ru: '\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F',\n pt: 'Notifica\u00E7\u00F5es',\n nl: 'Meldingen',\n fr: 'Notifications',\n it: 'Notifiche',\n es: 'Notificaciones',\n pl: 'Powiadomienia',\n uk: '\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F',\n 'zh-cn': '\u901A\u77E5\u4E8B\u9879',\n },\n },\n native: {},\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not create notifications object: ${e.message}`);\n }\n }\n\n const instancesOnHost = [];\n // get all instances\n try {\n const res = await this.objects.getObjectViewAsync('system', 'instance', {\n startkey: 'system.adapter.',\n endkey: 'system.adapter.\\u9999',\n });\n\n for (const entry of res.rows) {\n // check that instance has notification settings\n if (entry.value.notifications) {\n await this.addConfig(entry.value.notifications);\n }\n\n if (entry.value.common && entry.value.common.host === this.host) {\n // if it's on our current host\n instancesOnHost.push(entry.id);\n }\n }\n } catch (e) {\n throw new Error(`Could not get notifications setup from instances: ${e.message}`);\n }\n\n // now clear up all notifications that do not belong to our host anymore\n for (const scope of Object.keys(this.currentNotifications)) {\n for (const category of Object.keys(this.currentNotifications[scope])) {\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {\n // instance no longer on host\n this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);\n await this.clearNotifications(null, null, instance);\n }\n }\n }\n }\n\n // now sync RAM with state\n await this._updateScopeStates();\n }\n\n /**\n * Add a new category to the given scope with a provided optional list of regex\n *\n * @param notifications - Array with notifications\n */\n async addConfig(notifications: NotificationsConfigEntry[]): Promise<void> {\n // if valid attributes, store it\n if (Array.isArray(notifications)) {\n for (const scopeObj of notifications) {\n // create the state object for each scope if non-existing\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications.${scopeObj.scope}`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications.${scopeObj.scope}`, {\n type: 'state',\n common: {\n type: 'object',\n role: 'value',\n read: true,\n write: false,\n name: scopeObj.name,\n desc: scopeObj.description,\n },\n native: {},\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not create notifications object for scope \"${scopeObj.scope}\": ${e.message}`,\n );\n }\n }\n\n if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {\n // only if scope has at least one category\n this.setup[scopeObj.scope] = this.setup[scopeObj.scope] || {\n name: scopeObj.name,\n description: scopeObj.description,\n categories: {},\n };\n\n for (const categoryObj of scopeObj.categories) {\n this.setup[scopeObj.scope].categories[categoryObj.category] =\n this.setup[scopeObj.scope].categories[categoryObj.category] || {};\n try {\n let regex: RegExp[] = [];\n if (Array.isArray(categoryObj.regex)) {\n for (const regexString of categoryObj.regex) {\n regex.push(new RegExp(regexString));\n }\n } else if (typeof categoryObj.regex === 'string') {\n // if someone passes a string, convert to a single entry array\n regex = [new RegExp(categoryObj.regex)];\n }\n\n // we overwrite config, maybe it would also make sense to only add the new regex to existing ones\n this.setup[scopeObj.scope].categories[categoryObj.category] = {\n regex,\n limit: categoryObj.limit,\n name: categoryObj.name,\n severity: categoryObj.severity,\n description: categoryObj.description,\n };\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Cannot store ${JSON.stringify(categoryObj.regex)} for scope \"${\n scopeObj.scope\n }\", category \"${categoryObj.category}\": ${e.message}`,\n );\n }\n }\n }\n }\n }\n }\n\n /**\n * Add a message to the scope and category\n *\n * @param options The scope, category, message, instance and contextData information\n */\n async addMessage(options: AddMessageOptions): Promise<void> {\n const { message, scope, category, contextData } = options;\n let { instance } = options;\n\n if (typeof instance !== 'string') {\n this.log.error(\n `${this.logPrefix} [addMessage] Instance has to be of type \"string\", got \"${typeof instance}\"`,\n );\n return;\n }\n\n if (!instance.startsWith('system.adapter.') && !instance.startsWith('system.host.')) {\n instance = `system.adapter.${instance}`;\n }\n\n // get state of the scope\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state?.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n let categories = [category];\n\n if (!category) {\n // check regex - in parse text we can have multiple matches\n categories = this._parseText(scope, message);\n }\n\n for (const _category of categories) {\n if (_category) {\n this.currentNotifications[scope] = this.currentNotifications[scope] || {};\n this.currentNotifications[scope][_category] = this.currentNotifications[scope][_category] || {};\n // array of all messages for instance/category\n this.currentNotifications[scope][_category][instance] =\n this.currentNotifications[scope][_category][instance] || [];\n\n if (!this.setup[scope]?.categories[_category]) {\n // no setup for this instance/category combination found - so we have nothing to add\n this.log.warn(\n `${this.logPrefix} No configuration found for scope \"${scope}\" and category \"${_category}\"`,\n );\n continue;\n }\n\n // if limit exceeded, remove last element - use while if it somehow grew too big\n while (\n this.setup[scope].categories[_category].limit <\n this.currentNotifications[scope][_category][instance].length + 1\n ) {\n this.currentNotifications[scope][_category][instance].pop();\n }\n\n // add a new element at the beginning\n this.currentNotifications[scope][_category][instance].unshift({ message, ts: Date.now(), contextData });\n }\n }\n\n // now count all messages of this scope - if nothing matched it can be undefined, and we can skip\n if (tools.isObject(this.currentNotifications[scope])) {\n for (const _category of Object.keys(this.currentNotifications[scope])) {\n const categoryCounter = Object.keys(this.currentNotifications[scope][_category]).length;\n\n stateVal[_category] = { count: categoryCounter };\n }\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`);\n }\n }\n\n /**\n * Updates all scope states by current notifications in RAM\n */\n private async _updateScopeStates(): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n const stateVal: ScopeStateValue = {};\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n // count the number of instances with this error\n const catCounter = Object.keys(this.currentNotifications[scope][category]).length;\n stateVal[category] = { count: catCounter };\n }\n\n // set updated scope state\n try {\n await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check the given message against all regular expressions of the given scope\n *\n * @param scope - scope of the message\n * @param message - message to check\n */\n private _parseText(scope: string, message: string): string[] {\n const categories = [];\n if (this.setup[scope]) {\n for (const [categoryId, categoryObj] of Object.entries(this.setup[scope].categories)) {\n // check all regular expressions for this category\n for (const regex of categoryObj.regex) {\n if (regex.test(message)) {\n // matching category\n categories.push(categoryId);\n // no further testing needed for this category\n break;\n }\n }\n }\n }\n\n return categories;\n }\n\n /**\n * Load notifications from a file\n */\n private _loadNotifications(): void {\n try {\n this.currentNotifications = fs.readJSONSync(path.join(this.dataDir, 'notifications.json'));\n } catch (e) {\n // at first start its normal, that we cannot read notifications, so just info\n this.log.debug(`${this.logPrefix} Could not read notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Save current notifications to file\n */\n storeNotifications(): void {\n try {\n fs.writeJSONSync(path.join(this.dataDir, 'notifications.json'), this.currentNotifications);\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not write notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Returns the stored notifications matching the filters with description and name\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n getFilteredInformation(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): FilteredNotificationInformation {\n const res: FilteredNotificationInformation = {};\n for (const scope of Object.keys(this.currentNotifications)) {\n if (scopeFilter && scopeFilter !== scope) {\n // scope filtered out\n continue;\n }\n\n if (!this.setup[scope]) {\n // no scope set up\n continue;\n }\n\n res[scope] = {\n categories: {},\n description: this.setup[scope].description,\n name: this.setup[scope].name,\n };\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n const categoryObj = this.setup[scope].categories[category];\n\n if (!categoryObj) {\n // no category set up\n continue;\n }\n\n res[scope].categories[category] = {\n instances: {},\n description: categoryObj.description,\n name: categoryObj.name,\n severity: categoryObj.severity,\n };\n\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out\n continue;\n }\n\n res[scope].categories[category].instances[instance] = {\n messages: this.currentNotifications[scope][category][instance],\n };\n }\n }\n }\n return res;\n }\n\n /**\n * Clears the stored notifications matching the filters\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n async clearNotifications(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n if (!this.currentNotifications[scope]) {\n delete this.currentNotifications[scope];\n continue;\n }\n if (scopeFilter && scopeFilter !== scope) {\n // skip this, because not desired to be deleted\n continue;\n }\n\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state && state.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n for (const category of Object.keys(this.currentNotifications[scope])) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n let categoryCounter = 0;\n for (const instance of Object.keys(this.currentNotifications[scope][category])) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out - message counter remains\n categoryCounter += 1;\n continue;\n }\n\n // not filtered out -> remove\n delete this.currentNotifications[scope][category][instance];\n }\n\n stateVal[category] = { count: categoryCounter };\n\n // check if all instances of this category deleted\n if (!Object.keys(this.currentNotifications[scope][category]).length) {\n delete this.currentNotifications[scope][category];\n delete stateVal[category];\n }\n }\n\n // check if all categories of this scope deleted\n if (!Object.keys(this.currentNotifications[scope]).length) {\n delete this.currentNotifications[scope];\n }\n\n // set updated state\n try {\n await this.states.setStateAsync(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check if given scope exists in config\n *\n * @param scope - scope to be checked for\n */\n scopeExists(scope: string): boolean {\n return !!this.setup[scope];\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["path", "fs"]
4
+ "sourcesContent": ["/**\n * Notification handler class\n *\n * 2021-2024 foxriver76 <moritz.heusinger@gmail.com>\n */\n\nimport fs from 'fs-extra';\nimport { tools } from '@iobroker/js-controller-common-db';\nimport path from 'node:path';\nimport type { Client as StatesInRedisClient } from '@iobroker/db-states-redis';\nimport type { Client as ObjectsInRedisClient } from '@iobroker/db-objects-redis';\nimport type Winston from 'winston';\n\ntype MultilingualObject = Exclude<ioBroker.StringOrTranslated, string>;\n\n/** Settings to configure an instance of NotificationHandler */\nexport interface NotificationHandlerSettings {\n /** Name of the host (not id - hence, without `system.host.` prefix) */\n host: string;\n /** States DB instance */\n states: StatesInRedisClient;\n /** Objects DB instance */\n objects: ObjectsInRedisClient;\n /** Logger instance */\n log: Winston.Logger | Console;\n /** Prefix for the log messages */\n logPrefix: string;\n}\n\n/** A notification config entry represents the top level of the notification structure */\nexport interface NotificationsConfigEntry {\n /** e.g. system */\n scope: string;\n /** multilingual name */\n name: MultilingualObject;\n /** multilingual description */\n description: MultilingualObject;\n /** The notification categories inside this scope */\n categories: CategoryConfigEntry[];\n}\n\nexport type Severity = 'info' | 'notify' | 'alert';\n\n/** A notification category entry represents one of the categories of a scope */\nexport interface CategoryConfigEntry {\n /** Id of the category */\n category: string;\n /** Name of the category */\n name: MultilingualObject;\n /** Allows defining the severity of the notification with `info` being the lowest, `notify` representing middle priority, `alert` representing high priority and often containing critical information */\n severity: Severity;\n /** Description of the category */\n description: MultilingualObject;\n /** Regex where `console.error` output is parsed against to check if a notification of this category should be generated */\n regex: string[];\n /** Restrict the maximum number of messages for this category */\n limit: number;\n}\n\ninterface NotificationMessageObject {\n message: string;\n ts: number;\n contextData?: ioBroker.NotificationContextData;\n}\n\ninterface NotificationsCategory {\n [category: string]: {\n [instance: string]: NotificationMessageObject[];\n };\n}\n\ninterface NotificationsObject {\n [scope: string]: NotificationsCategory;\n}\n\ninterface FilteredNotificationCategory {\n description: MultilingualObject;\n name: MultilingualObject;\n severity: Severity;\n instances: {\n [instance: string]: {\n messages: NotificationMessageObject[];\n };\n };\n}\n\ninterface FilteredNotificationScope {\n description: MultilingualObject;\n name: MultilingualObject;\n categories: {\n [category: string]: FilteredNotificationCategory;\n };\n}\n\n/** The filtered information as a response for `getFilteredInformation` */\nexport interface FilteredNotificationInformation {\n [scope: string]: FilteredNotificationScope;\n}\n\ninterface NotificationSetupCategory {\n regex: RegExp[];\n limit: number;\n name: MultilingualObject;\n severity: Severity;\n description: MultilingualObject;\n}\n\ninterface NotificationSetupObject {\n [scope: string]: {\n name: MultilingualObject;\n description: MultilingualObject;\n categories: {\n [category: string]: NotificationSetupCategory;\n };\n };\n}\n\ninterface ScopeStateValue {\n [category: string]: {\n count: number;\n };\n}\n\ninterface AddMessageOptions {\n /** Scope of the message */\n scope: string;\n /** Category of the message, if non we check against regex of scope */\n category?: string | null;\n /** Message to add */\n message: string;\n /** Instance e.g., hm-rpc.1 or hostname, if hostname it needs to be prefixed like system.host.rpi */\n instance: string;\n /** Additional context for the notification which can be used by notification processing adapters */\n contextData?: ioBroker.NotificationContextData;\n}\n\n/**\n * The Notification handler class provides an interface to manage the ioBroker notification system\n */\nexport class NotificationHandler {\n private states: StatesInRedisClient;\n private objects: ObjectsInRedisClient;\n private log: Winston.Logger | Console;\n private currentNotifications: NotificationsObject = {};\n // default data dir is relative to controllerDir\n private readonly dataDir = path.join(tools.getControllerDir(), tools.getDefaultDataDir());\n private readonly setup: NotificationSetupObject = {};\n private readonly logPrefix: string;\n private readonly host: string;\n\n /**\n * Creates a new instance of the notification handler class\n *\n * @param settings settings to configure the handler instance\n */\n constructor(settings: NotificationHandlerSettings) {\n this.states = settings.states;\n this.objects = settings.objects;\n this.log = settings.log;\n this.logPrefix = settings.logPrefix;\n this.host = settings.host;\n\n // load notifications file\n this._loadNotifications();\n }\n\n /**\n * Get all adapter instances on this host and store their notifications config - and clears up removed instances notifications - should be called once after init\n */\n async getSetupOfAllAdaptersFromHost(): Promise<void> {\n // create the initial notifications object\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications`, {\n type: 'folder',\n common: {\n name: {\n en: 'Notifications',\n de: 'Benachrichtigungen',\n ru: '\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F',\n pt: 'Notifica\u00E7\u00F5es',\n nl: 'Meldingen',\n fr: 'Notifications',\n it: 'Notifiche',\n es: 'Notificaciones',\n pl: 'Powiadomienia',\n uk: '\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F',\n 'zh-cn': '\u901A\u77E5\u4E8B\u9879',\n },\n },\n native: {},\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not create notifications object: ${e.message}`);\n }\n }\n\n const instancesOnHost = [];\n // get all instances\n try {\n const res = await this.objects.getObjectViewAsync('system', 'instance', {\n startkey: 'system.adapter.',\n endkey: 'system.adapter.\\u9999',\n });\n\n for (const entry of res.rows) {\n // check that instance has notification settings\n if (entry.value.notifications) {\n await this.addConfig(entry.value.notifications);\n }\n\n if (entry.value.common && entry.value.common.host === this.host) {\n // if it's on our current host\n instancesOnHost.push(entry.id);\n }\n }\n } catch (e) {\n throw new Error(`Could not get notifications setup from instances: ${e.message}`);\n }\n\n // now clear up all notifications that do not belong to our host anymore\n for (const notificationsCategory of Object.values(this.currentNotifications)) {\n for (const instancesInfo of Object.values(notificationsCategory)) {\n for (const instance of Object.keys(instancesInfo)) {\n if (!instancesOnHost.includes(instance) && instance !== `system.host.${this.host}`) {\n // instance no longer on host\n this.log.info(`${this.logPrefix} Instance ${instance} removed from host, clear notifications`);\n await this.clearNotifications(null, null, instance);\n }\n }\n }\n }\n\n // now sync RAM with state\n await this._updateScopeStates();\n }\n\n /**\n * Add a new category to the given scope with a provided optional list of regex\n *\n * @param notifications - Array with notifications\n */\n async addConfig(notifications: NotificationsConfigEntry[]): Promise<void> {\n // if valid attributes, store it\n if (Array.isArray(notifications)) {\n for (const scopeObj of notifications) {\n // create the state object for each scope if non-existing\n let obj;\n try {\n obj = await this.objects.getObject(`system.host.${this.host}.notifications.${scopeObj.scope}`);\n } catch {\n // ignore\n }\n\n if (!obj) {\n try {\n await this.objects.setObject(`system.host.${this.host}.notifications.${scopeObj.scope}`, {\n type: 'state',\n common: {\n type: 'object',\n role: 'value',\n read: true,\n write: false,\n name: scopeObj.name,\n desc: scopeObj.description,\n },\n native: {},\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not create notifications object for scope \"${scopeObj.scope}\": ${e.message}`,\n );\n }\n }\n\n if (Array.isArray(scopeObj.categories) && scopeObj.categories.length) {\n const setupScope = this.setup[scopeObj.scope] || {\n name: scopeObj.name,\n description: scopeObj.description,\n categories: {},\n };\n\n // only if scope has at least one category\n this.setup[scopeObj.scope] = setupScope;\n\n for (const categoryObj of scopeObj.categories) {\n const setupCategory = setupScope.categories[categoryObj.category] || {\n regex: [],\n limit: categoryObj.limit,\n name: categoryObj.name,\n severity: categoryObj.severity,\n description: categoryObj.description,\n };\n setupScope.categories[categoryObj.category] = setupCategory;\n\n try {\n let regex: RegExp[] = [];\n if (Array.isArray(categoryObj.regex)) {\n for (const regexString of categoryObj.regex) {\n regex.push(new RegExp(regexString));\n }\n } else if (typeof categoryObj.regex === 'string') {\n // if someone passes a string, convert to a single entry array\n regex = [new RegExp(categoryObj.regex)];\n }\n\n setupCategory.regex = regex;\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Cannot store regex \"${JSON.stringify(categoryObj.regex)}\" for scope \"${\n scopeObj.scope\n }\", category \"${categoryObj.category}\": ${e.message}`,\n );\n }\n }\n }\n }\n }\n }\n\n /**\n * Add a message to the scope and category\n *\n * @param options The scope, category, message, instance and contextData information\n */\n async addMessage(options: AddMessageOptions): Promise<void> {\n const { message, scope, category, contextData } = options;\n let { instance } = options;\n\n if (typeof instance !== 'string') {\n this.log.error(\n `${this.logPrefix} [addMessage] Instance has to be of type \"string\", got \"${typeof instance}\"`,\n );\n return;\n }\n\n if (!instance.startsWith('system.adapter.') && !instance.startsWith('system.host.')) {\n instance = `system.adapter.${instance}`;\n }\n\n // get state of the scope\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state?.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n let categories = [category];\n\n if (!category) {\n // check regex - in parse text we can have multiple matches\n categories = this._parseText(scope, message);\n }\n\n const currentScopeObj = this.currentNotifications[scope] || {};\n this.currentNotifications[scope] = currentScopeObj;\n\n for (const _category of categories) {\n if (!_category) {\n continue;\n }\n\n const currentCategoryObj = currentScopeObj[_category] || {};\n currentScopeObj[_category] = currentCategoryObj;\n\n // array of all messages for instance/category\n const currentInstances = currentCategoryObj[instance] || [];\n currentCategoryObj[instance] = currentInstances;\n\n const setupCategory = this.setup[scope]?.categories[_category];\n\n if (!setupCategory) {\n // no setup for this instance/category combination found - so we have nothing to add\n this.log.warn(\n `${this.logPrefix} No configuration found for scope \"${scope}\" and category \"${_category}\"`,\n );\n continue;\n }\n\n // if limit exceeded, remove last element - use while if it somehow grew too big\n while (setupCategory.limit < currentInstances.length + 1) {\n currentInstances.pop();\n }\n\n // add a new element at the beginning\n currentInstances.unshift({ message, ts: Date.now(), contextData });\n }\n\n // now count all messages of this scope - if nothing matched it can be undefined, and we can skip\n if (tools.isObject(this.currentNotifications[scope])) {\n for (const [categoryName, category] of Object.entries(currentScopeObj)) {\n const categoryCounter = Object.keys(category).length;\n\n stateVal[categoryName] = { count: categoryCounter };\n }\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`);\n }\n }\n\n /**\n * Updates all scope states by current notifications in RAM\n */\n private async _updateScopeStates(): Promise<void> {\n for (const [scope, scopeObj] of Object.entries(this.currentNotifications)) {\n const stateVal: ScopeStateValue = {};\n\n for (const [category, categoryObj] of Object.entries(scopeObj)) {\n // count the number of instances with this error\n const catCounter = Object.keys(categoryObj).length;\n stateVal[category] = { count: catCounter };\n }\n\n // set updated scope state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check the given message against all regular expressions of the given scope\n *\n * @param scope - scope of the message\n * @param message - message to check\n */\n private _parseText(scope: string, message: string): string[] {\n const categories: string[] = [];\n\n const scopeObj = this.setup[scope];\n\n if (!scopeObj) {\n return categories;\n }\n\n for (const [categoryId, categoryObj] of Object.entries(scopeObj.categories)) {\n // check all regular expressions for this category\n for (const regex of categoryObj.regex) {\n if (regex.test(message)) {\n // matching category\n categories.push(categoryId);\n // no further testing needed for this category\n break;\n }\n }\n }\n\n return categories;\n }\n\n /**\n * Load notifications from a file\n */\n private _loadNotifications(): void {\n try {\n this.currentNotifications = fs.readJSONSync(path.join(this.dataDir, 'notifications.json'));\n } catch (e) {\n // at first start its normal, that we cannot read notifications, so just info\n this.log.debug(`${this.logPrefix} Could not read notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Save current notifications to file\n */\n storeNotifications(): void {\n try {\n fs.writeJSONSync(path.join(this.dataDir, 'notifications.json'), this.currentNotifications);\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not write notifications.json: ${e.message}`);\n }\n }\n\n /**\n * Returns the stored notifications matching the filters with description and name\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n getFilteredInformation(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): FilteredNotificationInformation {\n const res: FilteredNotificationInformation = {};\n for (const [scope, currentNotification] of Object.entries(this.currentNotifications)) {\n if (scopeFilter && scopeFilter !== scope) {\n // scope filtered out\n continue;\n }\n\n const scopeObj = this.setup[scope];\n\n if (!scopeObj) {\n // no scope set up\n continue;\n }\n\n const scopeResult: FilteredNotificationScope = {\n categories: {},\n description: scopeObj.description,\n name: scopeObj.name,\n };\n\n res[scope] = scopeResult;\n\n for (const [category, currentCategoryObj] of Object.entries(currentNotification)) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n const categoryObj = scopeObj.categories[category];\n\n if (!categoryObj) {\n // no category set up\n continue;\n }\n\n const resultCategories: FilteredNotificationCategory = {\n instances: {},\n description: categoryObj.description,\n name: categoryObj.name,\n severity: categoryObj.severity,\n };\n\n scopeResult.categories[category] = resultCategories;\n\n for (const [instance, instanceObj] of Object.entries(currentCategoryObj)) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out\n continue;\n }\n\n resultCategories.instances[instance] = {\n messages: instanceObj,\n };\n }\n }\n }\n return res;\n }\n\n /**\n * Clears the stored notifications matching the filters\n *\n * @param scopeFilter - scope of notifications\n * @param categoryFilter - category of notifications\n * @param instanceFilter - instance of notifications\n */\n async clearNotifications(\n scopeFilter: string | null | undefined,\n categoryFilter: string | null | undefined,\n instanceFilter: string | null | undefined,\n ): Promise<void> {\n for (const scope of Object.keys(this.currentNotifications)) {\n const currentNotificationScope = this.currentNotifications[scope];\n\n if (!currentNotificationScope) {\n delete this.currentNotifications[scope];\n continue;\n }\n if (scopeFilter && scopeFilter !== scope) {\n // skip this, because not desired to be deleted\n continue;\n }\n\n let stateVal: ScopeStateValue = {};\n try {\n const state = await this.states.getStateAsync(`system.host.${this.host}.notifications.${scope}`);\n stateVal = state && state.val ? JSON.parse(state.val as string) : {};\n } catch (e) {\n this.log.error(`${this.logPrefix} Could not get state for scope \"${scope}\": ${e.message}`);\n }\n\n for (const [category, categoryObj] of Object.entries(currentNotificationScope)) {\n if (categoryFilter && categoryFilter !== category) {\n // category filtered out\n continue;\n }\n\n let categoryCounter = 0;\n for (const instance of Object.keys(categoryObj)) {\n if (instanceFilter && instanceFilter !== instance) {\n // instance filtered out - message counter remains\n categoryCounter += 1;\n continue;\n }\n\n // not filtered out -> remove\n delete categoryObj[instance];\n }\n\n stateVal[category] = { count: categoryCounter };\n\n // check if all instances of this category deleted\n if (!Object.keys(categoryObj).length) {\n delete currentNotificationScope[category];\n delete stateVal[category];\n }\n }\n\n // check if all categories of this scope deleted\n if (!Object.keys(currentNotificationScope).length) {\n delete this.currentNotifications[scope];\n }\n\n // set updated state\n try {\n await this.states.setState(`system.host.${this.host}.notifications.${scope}`, {\n val: JSON.stringify(stateVal),\n ack: true,\n });\n } catch (e) {\n this.log.error(\n `${this.logPrefix} Could not set notifications state for scope \"${scope}\": ${e.message}`,\n );\n }\n }\n }\n\n /**\n * Check if given scope exists in config\n *\n * @param scope - scope to be checked for\n */\n scopeExists(scope: string): boolean {\n return !!this.setup[scope];\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["path", "category", "fs"]
7
7
  }