meross-iot 0.1.0

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +153 -0
  4. package/index.d.ts +2344 -0
  5. package/index.js +131 -0
  6. package/lib/controller/device.js +1317 -0
  7. package/lib/controller/features/alarm-feature.js +89 -0
  8. package/lib/controller/features/child-lock-feature.js +61 -0
  9. package/lib/controller/features/config-feature.js +54 -0
  10. package/lib/controller/features/consumption-feature.js +210 -0
  11. package/lib/controller/features/control-feature.js +62 -0
  12. package/lib/controller/features/diffuser-feature.js +411 -0
  13. package/lib/controller/features/digest-timer-feature.js +22 -0
  14. package/lib/controller/features/digest-trigger-feature.js +22 -0
  15. package/lib/controller/features/dnd-feature.js +79 -0
  16. package/lib/controller/features/electricity-feature.js +144 -0
  17. package/lib/controller/features/encryption-feature.js +259 -0
  18. package/lib/controller/features/garage-feature.js +337 -0
  19. package/lib/controller/features/hub-feature.js +687 -0
  20. package/lib/controller/features/light-feature.js +408 -0
  21. package/lib/controller/features/presence-sensor-feature.js +297 -0
  22. package/lib/controller/features/roller-shutter-feature.js +456 -0
  23. package/lib/controller/features/runtime-feature.js +74 -0
  24. package/lib/controller/features/screen-feature.js +67 -0
  25. package/lib/controller/features/sensor-history-feature.js +47 -0
  26. package/lib/controller/features/smoke-config-feature.js +50 -0
  27. package/lib/controller/features/spray-feature.js +166 -0
  28. package/lib/controller/features/system-feature.js +269 -0
  29. package/lib/controller/features/temp-unit-feature.js +55 -0
  30. package/lib/controller/features/thermostat-feature.js +804 -0
  31. package/lib/controller/features/timer-feature.js +507 -0
  32. package/lib/controller/features/toggle-feature.js +223 -0
  33. package/lib/controller/features/trigger-feature.js +333 -0
  34. package/lib/controller/hub-device.js +185 -0
  35. package/lib/controller/subdevice.js +1537 -0
  36. package/lib/device-factory.js +463 -0
  37. package/lib/error-budget.js +138 -0
  38. package/lib/http-api.js +766 -0
  39. package/lib/manager.js +1609 -0
  40. package/lib/model/channel-info.js +79 -0
  41. package/lib/model/constants.js +119 -0
  42. package/lib/model/enums.js +819 -0
  43. package/lib/model/exception.js +363 -0
  44. package/lib/model/http/device.js +215 -0
  45. package/lib/model/http/error-codes.js +121 -0
  46. package/lib/model/http/exception.js +151 -0
  47. package/lib/model/http/subdevice.js +133 -0
  48. package/lib/model/push/alarm.js +112 -0
  49. package/lib/model/push/bind.js +97 -0
  50. package/lib/model/push/common.js +282 -0
  51. package/lib/model/push/diffuser-light.js +100 -0
  52. package/lib/model/push/diffuser-spray.js +83 -0
  53. package/lib/model/push/factory.js +229 -0
  54. package/lib/model/push/generic.js +115 -0
  55. package/lib/model/push/hub-battery.js +59 -0
  56. package/lib/model/push/hub-mts100-all.js +64 -0
  57. package/lib/model/push/hub-mts100-mode.js +59 -0
  58. package/lib/model/push/hub-mts100-temperature.js +62 -0
  59. package/lib/model/push/hub-online.js +59 -0
  60. package/lib/model/push/hub-sensor-alert.js +61 -0
  61. package/lib/model/push/hub-sensor-all.js +59 -0
  62. package/lib/model/push/hub-sensor-smoke.js +110 -0
  63. package/lib/model/push/hub-sensor-temphum.js +62 -0
  64. package/lib/model/push/hub-subdevicelist.js +50 -0
  65. package/lib/model/push/hub-togglex.js +60 -0
  66. package/lib/model/push/index.js +81 -0
  67. package/lib/model/push/online.js +53 -0
  68. package/lib/model/push/presence-study.js +61 -0
  69. package/lib/model/push/sensor-latestx.js +106 -0
  70. package/lib/model/push/timerx.js +63 -0
  71. package/lib/model/push/togglex.js +78 -0
  72. package/lib/model/push/triggerx.js +62 -0
  73. package/lib/model/push/unbind.js +34 -0
  74. package/lib/model/push/water-leak.js +107 -0
  75. package/lib/model/states/diffuser-light-state.js +119 -0
  76. package/lib/model/states/diffuser-spray-state.js +58 -0
  77. package/lib/model/states/garage-door-state.js +71 -0
  78. package/lib/model/states/index.js +38 -0
  79. package/lib/model/states/light-state.js +134 -0
  80. package/lib/model/states/presence-sensor-state.js +239 -0
  81. package/lib/model/states/roller-shutter-state.js +82 -0
  82. package/lib/model/states/spray-state.js +58 -0
  83. package/lib/model/states/thermostat-state.js +297 -0
  84. package/lib/model/states/timer-state.js +192 -0
  85. package/lib/model/states/toggle-state.js +105 -0
  86. package/lib/model/states/trigger-state.js +155 -0
  87. package/lib/subscription.js +587 -0
  88. package/lib/utilities/conversion.js +62 -0
  89. package/lib/utilities/debug.js +165 -0
  90. package/lib/utilities/mqtt.js +152 -0
  91. package/lib/utilities/network.js +53 -0
  92. package/lib/utilities/options.js +64 -0
  93. package/lib/utilities/request-queue.js +161 -0
  94. package/lib/utilities/ssid.js +37 -0
  95. package/lib/utilities/state-changes.js +66 -0
  96. package/lib/utilities/stats.js +687 -0
  97. package/lib/utilities/timer.js +310 -0
  98. package/lib/utilities/trigger.js +286 -0
  99. package/package.json +73 -0
@@ -0,0 +1,687 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Represents a single HTTP request sample captured for statistics tracking.
5
+ *
6
+ * Stores immutable data about a single HTTP request made to the Meross API,
7
+ * including the URL, HTTP method, response codes, and timestamp. Samples are
8
+ * collected over time and aggregated to provide usage statistics.
9
+ *
10
+ * @class HttpRequestSample
11
+ */
12
+ class HttpRequestSample {
13
+ /**
14
+ * Creates a new HTTP request sample.
15
+ *
16
+ * @param {string} url - The URL that was requested (e.g., 'https://iot.meross.com/v1/Profile/login')
17
+ * @param {string} method - HTTP method used (e.g., 'GET', 'POST', 'PUT', 'DELETE')
18
+ * @param {number} httpResponseCode - HTTP response status code (e.g., 200, 404, 500)
19
+ * @param {number|null} apiResponseCode - Meross API response code (0 for success, or error code). Can be null if not applicable.
20
+ * @param {number} [timestamp] - Timestamp in milliseconds when the request was made. Defaults to current time if not provided.
21
+ */
22
+ constructor(url, method, httpResponseCode, apiResponseCode, timestamp) {
23
+ this._url = url;
24
+ this._method = method;
25
+ this._httpResponseCode = httpResponseCode;
26
+ this._apiResponseCode = apiResponseCode;
27
+ this._timestamp = timestamp || Date.now();
28
+ }
29
+
30
+ /**
31
+ * Gets the URL that was requested.
32
+ *
33
+ * @returns {string} The request URL
34
+ */
35
+ get url() {
36
+ return this._url;
37
+ }
38
+
39
+ /**
40
+ * Gets the HTTP method used for the request.
41
+ *
42
+ * @returns {string} The HTTP method
43
+ */
44
+ get method() {
45
+ return this._method;
46
+ }
47
+
48
+ /**
49
+ * Gets the HTTP response status code.
50
+ *
51
+ * @returns {number} The HTTP status code
52
+ */
53
+ get httpResponseCode() {
54
+ return this._httpResponseCode;
55
+ }
56
+
57
+ /**
58
+ * Gets the Meross API response code.
59
+ *
60
+ * @returns {number|null} The API response code (0 for success, or error code). Returns null if not applicable.
61
+ */
62
+ get apiResponseCode() {
63
+ return this._apiResponseCode;
64
+ }
65
+
66
+ /**
67
+ * Gets the timestamp when the request was made.
68
+ *
69
+ * @returns {number} Timestamp in milliseconds since Unix epoch
70
+ */
71
+ get timestamp() {
72
+ return this._timestamp;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Aggregates HTTP statistics for a single URL or globally.
78
+ *
79
+ * This class collects and aggregates multiple HTTP request samples, providing
80
+ * counts by HTTP response codes and Meross API response codes.
81
+ *
82
+ * @class HttpStat
83
+ */
84
+ class HttpStat {
85
+ /**
86
+ * Creates a new HTTP statistics aggregator.
87
+ */
88
+ constructor() {
89
+ this._totalApiCalls = 0;
90
+ this._byHttpResponseCode = {};
91
+ this._byApiResponseCode = {};
92
+ }
93
+
94
+ /**
95
+ * Adds a sample to the statistics aggregation.
96
+ *
97
+ * Aggregates counts by HTTP response code and API response code to enable
98
+ * analysis of error patterns and success rates.
99
+ *
100
+ * @param {HttpRequestSample} sample - The HTTP request sample to add
101
+ */
102
+ add(sample) {
103
+ this._totalApiCalls += 1;
104
+
105
+ if (!this._byHttpResponseCode[sample.httpResponseCode]) {
106
+ this._byHttpResponseCode[sample.httpResponseCode] = 0;
107
+ }
108
+ this._byHttpResponseCode[sample.httpResponseCode] += 1;
109
+
110
+ const apiCode = sample.apiResponseCode !== null && sample.apiResponseCode !== undefined
111
+ ? sample.apiResponseCode
112
+ : 'null';
113
+ if (!this._byApiResponseCode[apiCode]) {
114
+ this._byApiResponseCode[apiCode] = 0;
115
+ }
116
+ this._byApiResponseCode[apiCode] += 1;
117
+ }
118
+
119
+ /**
120
+ * Gets the total number of API calls aggregated.
121
+ *
122
+ * @returns {number} Total number of calls
123
+ */
124
+ get totalCalls() {
125
+ return this._totalApiCalls;
126
+ }
127
+
128
+ /**
129
+ * Gets statistics grouped by HTTP response code.
130
+ *
131
+ * @returns {Array<[string, number]>} Array of [HTTP status code, count] pairs
132
+ */
133
+ byHttpResponseCode() {
134
+ return Object.entries(this._byHttpResponseCode);
135
+ }
136
+
137
+ /**
138
+ * Gets statistics grouped by Meross API response code.
139
+ *
140
+ * @returns {Array<[string, number]>} Array of [API response code, count] pairs
141
+ */
142
+ byApiStatusCode() {
143
+ return Object.entries(this._byApiResponseCode);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Aggregates HTTP statistics results across multiple URLs.
149
+ *
150
+ * This class provides both global statistics (across all URLs) and per-URL statistics,
151
+ * allowing you to analyze HTTP request patterns both globally and by specific endpoint.
152
+ *
153
+ * @class HttpStatsResult
154
+ */
155
+ class HttpStatsResult {
156
+ /**
157
+ * Creates a new HTTP statistics result aggregator.
158
+ */
159
+ constructor() {
160
+ this._global = new HttpStat();
161
+ this._byUrl = {};
162
+ }
163
+
164
+ /**
165
+ * Adds a sample to both global and per-URL statistics.
166
+ *
167
+ * @param {HttpRequestSample} sample - The HTTP request sample to add
168
+ */
169
+ add(sample) {
170
+ this._global.add(sample);
171
+
172
+ const { url } = sample;
173
+ if (!this._byUrl[url]) {
174
+ this._byUrl[url] = new HttpStat();
175
+ }
176
+ this._byUrl[url].add(sample);
177
+ }
178
+
179
+ /**
180
+ * Gets the global statistics aggregated across all URLs.
181
+ *
182
+ * @returns {HttpStat} Global HTTP statistics
183
+ */
184
+ get globalStats() {
185
+ return this._global;
186
+ }
187
+
188
+ /**
189
+ * Gets statistics for a specific URL.
190
+ *
191
+ * @param {string} url - The URL to get statistics for
192
+ * @returns {HttpStat|null} Statistics for the URL, or null if no requests were made to that URL
193
+ */
194
+ statsByUrl(url) {
195
+ return this._byUrl[url] || null;
196
+ }
197
+
198
+ /**
199
+ * Gets all URLs with their associated statistics.
200
+ *
201
+ * @returns {Array<[string, HttpStat]>} Array of [URL, HttpStat] pairs
202
+ */
203
+ deviceStats() {
204
+ return Object.entries(this._byUrl);
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Counter for tracking HTTP request statistics over time.
210
+ *
211
+ * This class maintains a rolling window of HTTP request samples and provides
212
+ * methods to query statistics for specific time windows. Samples are automatically
213
+ * pruned when the maximum sample count is exceeded, keeping only the most recent samples.
214
+ *
215
+ * @class HttpStatsCounter
216
+ */
217
+ class HttpStatsCounter {
218
+ /**
219
+ * Creates a new HTTP statistics counter.
220
+ *
221
+ * @param {number} [maxSamples=1000] - Maximum number of samples to keep in memory.
222
+ * When exceeded, oldest samples are removed.
223
+ * Defaults to 1000 samples.
224
+ */
225
+ constructor(maxSamples = 1000) {
226
+ this._maxSamples = maxSamples;
227
+ this._samples = [];
228
+ }
229
+
230
+ /**
231
+ * Notifies the counter of an HTTP request that was made.
232
+ *
233
+ * Records request details for statistical analysis. Samples are automatically
234
+ * pruned when the maximum count is exceeded to prevent unbounded memory growth.
235
+ *
236
+ * Errors during statistics collection are silently caught to avoid disrupting
237
+ * normal operation.
238
+ *
239
+ * @param {string} requestUrl - The URL that was requested (e.g., 'https://iot.meross.com/v1/Profile/login')
240
+ * @param {string} method - HTTP method used (e.g., 'GET', 'POST', 'PUT', 'DELETE')
241
+ * @param {number} httpResponseCode - HTTP response status code (e.g., 200, 404, 500)
242
+ * @param {number|null} apiResponseCode - Meross API response code (0 for success, or error code).
243
+ * Can be null if not applicable.
244
+ */
245
+ notifyHttpRequest(requestUrl, method, httpResponseCode, apiResponseCode) {
246
+ try {
247
+ const sample = new HttpRequestSample(
248
+ requestUrl,
249
+ method,
250
+ httpResponseCode,
251
+ apiResponseCode,
252
+ Date.now()
253
+ );
254
+
255
+ this._samples.push(sample);
256
+
257
+ if (this._samples.length > this._maxSamples) {
258
+ this._samples.shift();
259
+ }
260
+ } catch (error) {
261
+ // Statistics collection failures should not disrupt normal operation
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Gets aggregated statistics for requests within a specified time window.
267
+ *
268
+ * Filters samples to include only those within the specified time window (from now
269
+ * going back `timeWindowMs` milliseconds) and returns aggregated statistics. The
270
+ * time window is calculated from the current time backwards.
271
+ *
272
+ * Iterates backwards through chronologically ordered samples for efficiency, breaking
273
+ * early when encountering samples outside the time window.
274
+ *
275
+ * @param {number} [timeWindowMs=60000] - Time window in milliseconds. Defaults to 60000 (1 minute).
276
+ * Use 3600000 for 1 hour, 86400000 for 24 hours, etc.
277
+ * @returns {HttpStatsResult} Aggregated statistics for the time window, including:
278
+ * - Global statistics across all URLs
279
+ * - Per-URL statistics
280
+ */
281
+ getStats(timeWindowMs = 60000) {
282
+ const result = new HttpStatsResult();
283
+ const lowerLimit = Date.now() - timeWindowMs;
284
+
285
+ for (let i = this._samples.length - 1; i >= 0; i--) {
286
+ const sample = this._samples[i];
287
+ if (sample.timestamp > lowerLimit) {
288
+ result.add(sample);
289
+ } else {
290
+ break;
291
+ }
292
+ }
293
+
294
+ return result;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Represents a single MQTT API call sample captured for statistics tracking.
300
+ *
301
+ * This class stores immutable data about a single MQTT message sent to a Meross device,
302
+ * including the device UUID, namespace, method, and timestamp.
303
+ *
304
+ * @class ApiCallSample
305
+ */
306
+ class ApiCallSample {
307
+ /**
308
+ * Creates a new MQTT API call sample.
309
+ *
310
+ * @param {string} deviceUuid - The UUID of the device the message was sent to
311
+ * @param {string} namespace - The namespace of the message (e.g., 'Appliance.System.All', 'Appliance.Control.ToggleX')
312
+ * @param {string} method - The method used (e.g., 'GET', 'SET', 'PUSH')
313
+ * @param {number} [timestamp] - Timestamp in milliseconds when the message was sent. Defaults to current time if not provided.
314
+ */
315
+ constructor(deviceUuid, namespace, method, timestamp) {
316
+ this._deviceUuid = deviceUuid;
317
+ this._namespace = namespace;
318
+ this._method = method;
319
+ this._timestamp = timestamp || Date.now();
320
+ }
321
+
322
+ /**
323
+ * Gets the UUID of the device the message was sent to.
324
+ *
325
+ * @returns {string} The device UUID
326
+ */
327
+ get deviceUuid() {
328
+ return this._deviceUuid;
329
+ }
330
+
331
+ /**
332
+ * Gets the namespace of the message.
333
+ *
334
+ * @returns {string} The namespace
335
+ */
336
+ get namespace() {
337
+ return this._namespace;
338
+ }
339
+
340
+ /**
341
+ * Gets the method used for the message.
342
+ *
343
+ * @returns {string} The method
344
+ */
345
+ get method() {
346
+ return this._method;
347
+ }
348
+
349
+ /**
350
+ * Gets the timestamp when the message was sent.
351
+ *
352
+ * @returns {number} Timestamp in milliseconds since Unix epoch
353
+ */
354
+ get timestamp() {
355
+ return this._timestamp;
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Aggregates API statistics for a single device or globally.
361
+ *
362
+ * This class collects and aggregates multiple MQTT API call samples, providing
363
+ * counts by method and namespace combinations.
364
+ *
365
+ * @class ApiStat
366
+ */
367
+ class ApiStat {
368
+ /**
369
+ * Creates a new API statistics aggregator.
370
+ */
371
+ constructor() {
372
+ this._totalApiCalls = 0;
373
+ this._byMethodNamespace = {};
374
+ }
375
+
376
+ /**
377
+ * Adds a sample to the statistics aggregation.
378
+ *
379
+ * Aggregates counts by method and namespace combination to enable analysis
380
+ * of which API operations are most frequently used.
381
+ *
382
+ * @param {ApiCallSample} sample - The API call sample to add
383
+ */
384
+ add(sample) {
385
+ this._totalApiCalls += 1;
386
+
387
+ const methodNs = `${sample.method} ${sample.namespace}`;
388
+ if (!this._byMethodNamespace[methodNs]) {
389
+ this._byMethodNamespace[methodNs] = 0;
390
+ }
391
+ this._byMethodNamespace[methodNs] += 1;
392
+ }
393
+
394
+ /**
395
+ * Gets the total number of API calls aggregated.
396
+ *
397
+ * @returns {number} Total number of calls
398
+ */
399
+ get totalCalls() {
400
+ return this._totalApiCalls;
401
+ }
402
+
403
+ /**
404
+ * Gets statistics grouped by method and namespace combination.
405
+ *
406
+ * @returns {Array<[string, number]>} Array of [method namespace, count] pairs
407
+ */
408
+ byMethodNamespace() {
409
+ return Object.entries(this._byMethodNamespace);
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Aggregates API statistics results across multiple devices.
415
+ *
416
+ * This class provides both global statistics (across all devices) and per-device statistics,
417
+ * allowing you to analyze MQTT message patterns both globally and by specific device.
418
+ *
419
+ * @class ApiStatsResult
420
+ */
421
+ class ApiStatsResult {
422
+ /**
423
+ * Creates a new API statistics result aggregator.
424
+ */
425
+ constructor() {
426
+ this._global = new ApiStat();
427
+ this._byUuid = {};
428
+ }
429
+
430
+ /**
431
+ * Adds a sample to both global and per-device statistics.
432
+ *
433
+ * @param {ApiCallSample} sample - The API call sample to add
434
+ */
435
+ add(sample) {
436
+ this._global.add(sample);
437
+
438
+ const uuid = sample.deviceUuid;
439
+ if (!this._byUuid[uuid]) {
440
+ this._byUuid[uuid] = new ApiStat();
441
+ }
442
+ this._byUuid[uuid].add(sample);
443
+ }
444
+
445
+ /**
446
+ * Gets the global statistics aggregated across all devices.
447
+ *
448
+ * @returns {ApiStat} Global API statistics
449
+ */
450
+ get globalStats() {
451
+ return this._global;
452
+ }
453
+
454
+ /**
455
+ * Gets statistics for a specific device.
456
+ *
457
+ * @param {string} deviceUuid - The device UUID to get statistics for
458
+ * @returns {ApiStat|null} Statistics for the device, or null if no messages were sent to that device
459
+ */
460
+ statsByUuid(deviceUuid) {
461
+ return this._byUuid[deviceUuid] || null;
462
+ }
463
+
464
+ /**
465
+ * Gets all devices with their associated statistics.
466
+ *
467
+ * @returns {Array<[string, ApiStat]>} Array of [device UUID, ApiStat] pairs
468
+ */
469
+ deviceStats() {
470
+ return Object.entries(this._byUuid);
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Counter for tracking MQTT message statistics over time.
476
+ *
477
+ * This class maintains rolling windows of MQTT API call samples and provides
478
+ * methods to query statistics for specific time windows. It tracks three types of calls:
479
+ * - **Sent calls**: API calls that were successfully sent
480
+ * - **Delayed calls**: API calls that were delayed (e.g., due to rate limiting)
481
+ * - **Dropped calls**: API calls that were dropped (e.g., due to queue overflow)
482
+ *
483
+ * Samples are automatically pruned when the maximum sample count is exceeded,
484
+ * keeping only the most recent samples.
485
+ *
486
+ * @class MqttStatsCounter
487
+ */
488
+ class MqttStatsCounter {
489
+ /**
490
+ * Creates a new MQTT statistics counter.
491
+ *
492
+ * @param {number} [maxSamples=1000] - Maximum number of samples to keep in memory per category
493
+ * (sent, delayed, dropped). When exceeded, oldest samples are removed.
494
+ * Defaults to 1000 samples per category.
495
+ */
496
+ constructor(maxSamples = 1000) {
497
+ this._maxSamples = maxSamples;
498
+ this.apiCalls = [];
499
+ this.delayedCalls = [];
500
+ this.droppedCalls = [];
501
+ }
502
+
503
+ /**
504
+ * Notifies the counter of an API call that was successfully sent.
505
+ *
506
+ * Records call details for statistical analysis. Samples are automatically
507
+ * pruned when the maximum count is exceeded to prevent unbounded memory growth.
508
+ *
509
+ * Errors during statistics collection are silently caught to avoid disrupting
510
+ * normal operation.
511
+ *
512
+ * @param {string} deviceUuid - The UUID of the device the message was sent to
513
+ * @param {string} namespace - The namespace of the message (e.g., 'Appliance.System.All', 'Appliance.Control.ToggleX')
514
+ * @param {string} method - The method used (e.g., 'GET', 'SET', 'PUSH')
515
+ */
516
+ notifyApiCall(deviceUuid, namespace, method) {
517
+ try {
518
+ const sample = new ApiCallSample(deviceUuid, namespace, method, Date.now());
519
+ this._addSample(this.apiCalls, sample);
520
+ } catch (error) {
521
+ // Statistics collection failures should not disrupt normal operation
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Notifies the counter of an API call that was delayed.
527
+ *
528
+ * Delayed calls occur when rate limiting or queue management postpones a call
529
+ * rather than sending it immediately. Records call details for analysis of
530
+ * system performance and bottlenecks.
531
+ *
532
+ * Samples are automatically pruned when the maximum count is exceeded to prevent
533
+ * unbounded memory growth. Errors during statistics collection are silently caught
534
+ * to avoid disrupting normal operation.
535
+ *
536
+ * @param {string} deviceUuid - The UUID of the device the message was intended for
537
+ * @param {string} namespace - The namespace of the message (e.g., 'Appliance.System.All', 'Appliance.Control.ToggleX')
538
+ * @param {string} method - The method used (e.g., 'GET', 'SET', 'PUSH')
539
+ */
540
+ notifyDelayedCall(deviceUuid, namespace, method) {
541
+ try {
542
+ const sample = new ApiCallSample(deviceUuid, namespace, method, Date.now());
543
+ this._addSample(this.delayedCalls, sample);
544
+ } catch (error) {
545
+ // Statistics collection failures should not disrupt normal operation
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Notifies the counter of an API call that was dropped.
551
+ *
552
+ * Dropped calls occur when the message queue is full or when a call cannot be
553
+ * processed and must be discarded. Records call details for analysis of system
554
+ * capacity and potential queue overflow issues.
555
+ *
556
+ * Samples are automatically pruned when the maximum count is exceeded to prevent
557
+ * unbounded memory growth. Errors during statistics collection are silently caught
558
+ * to avoid disrupting normal operation.
559
+ *
560
+ * @param {string} deviceUuid - The UUID of the device the message was intended for
561
+ * @param {string} namespace - The namespace of the message (e.g., 'Appliance.System.All', 'Appliance.Control.ToggleX')
562
+ * @param {string} method - The method used (e.g., 'GET', 'SET', 'PUSH')
563
+ */
564
+ notifyDroppedCall(deviceUuid, namespace, method) {
565
+ try {
566
+ const sample = new ApiCallSample(deviceUuid, namespace, method, Date.now());
567
+ this._addSample(this.droppedCalls, sample);
568
+ } catch (error) {
569
+ // Statistics collection failures should not disrupt normal operation
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Internal helper to add a sample to an array while maintaining max samples limit.
575
+ *
576
+ * Removes oldest samples when the limit is exceeded to prevent unbounded memory growth.
577
+ *
578
+ * @private
579
+ * @param {Array<ApiCallSample>} array - The array to add the sample to
580
+ * @param {ApiCallSample} sample - The sample to add
581
+ */
582
+ _addSample(array, sample) {
583
+ array.push(sample);
584
+ if (array.length > this._maxSamples) {
585
+ array.shift();
586
+ }
587
+ }
588
+
589
+ /**
590
+ * Gets aggregated statistics for successfully sent API calls within a specified time window.
591
+ *
592
+ * Filters samples to include only those within the specified time window (from now
593
+ * going back `timeWindowMs` milliseconds) and returns aggregated statistics. The
594
+ * time window is calculated from the current time backwards.
595
+ *
596
+ * Iterates backwards through chronologically ordered samples for efficiency, breaking
597
+ * early when encountering samples outside the time window.
598
+ *
599
+ * @param {number} [timeWindowMs=60000] - Time window in milliseconds. Defaults to 60000 (1 minute).
600
+ * Use 3600000 for 1 hour, 86400000 for 24 hours, etc.
601
+ * @returns {ApiStatsResult} Aggregated statistics for sent calls in the time window, including:
602
+ * - Global statistics across all devices
603
+ * - Per-device statistics
604
+ */
605
+ getApiStats(timeWindowMs = 60000) {
606
+ return this._getStats(this.apiCalls, timeWindowMs);
607
+ }
608
+
609
+ /**
610
+ * Gets aggregated statistics for delayed API calls within a specified time window.
611
+ *
612
+ * Filters samples to include only those within the specified time window (from now
613
+ * going back `timeWindowMs` milliseconds) and returns aggregated statistics. The
614
+ * time window is calculated from the current time backwards.
615
+ *
616
+ * Iterates backwards through chronologically ordered samples for efficiency, breaking
617
+ * early when encountering samples outside the time window.
618
+ *
619
+ * @param {number} [timeWindowMs=60000] - Time window in milliseconds. Defaults to 60000 (1 minute).
620
+ * Use 3600000 for 1 hour, 86400000 for 24 hours, etc.
621
+ * @returns {ApiStatsResult} Aggregated statistics for delayed calls in the time window, including:
622
+ * - Global statistics across all devices
623
+ * - Per-device statistics
624
+ */
625
+ getDelayedApiStats(timeWindowMs = 60000) {
626
+ return this._getStats(this.delayedCalls, timeWindowMs);
627
+ }
628
+
629
+ /**
630
+ * Gets aggregated statistics for dropped API calls within a specified time window.
631
+ *
632
+ * Filters samples to include only those within the specified time window (from now
633
+ * going back `timeWindowMs` milliseconds) and returns aggregated statistics. The
634
+ * time window is calculated from the current time backwards.
635
+ *
636
+ * Iterates backwards through chronologically ordered samples for efficiency, breaking
637
+ * early when encountering samples outside the time window.
638
+ *
639
+ * @param {number} [timeWindowMs=60000] - Time window in milliseconds. Defaults to 60000 (1 minute).
640
+ * Use 3600000 for 1 hour, 86400000 for 24 hours, etc.
641
+ * @returns {ApiStatsResult} Aggregated statistics for dropped calls in the time window, including:
642
+ * - Global statistics across all devices
643
+ * - Per-device statistics
644
+ */
645
+ getDroppedApiStats(timeWindowMs = 60000) {
646
+ return this._getStats(this.droppedCalls, timeWindowMs);
647
+ }
648
+
649
+ /**
650
+ * Internal helper to get statistics for a given array of samples.
651
+ *
652
+ * Filters samples by time window and aggregates them into statistics. Used by
653
+ * the public getter methods to avoid code duplication.
654
+ *
655
+ * @private
656
+ * @param {Array<ApiCallSample>} samples - The array of samples to process
657
+ * @param {number} timeWindowMs - Time window in milliseconds
658
+ * @returns {ApiStatsResult} Aggregated statistics for the time window
659
+ */
660
+ _getStats(samples, timeWindowMs) {
661
+ const result = new ApiStatsResult();
662
+ const lowerLimit = Date.now() - timeWindowMs;
663
+
664
+ for (let i = samples.length - 1; i >= 0; i--) {
665
+ const sample = samples[i];
666
+ if (sample.timestamp > lowerLimit) {
667
+ result.add(sample);
668
+ } else {
669
+ break;
670
+ }
671
+ }
672
+
673
+ return result;
674
+ }
675
+ }
676
+
677
+ module.exports = {
678
+ HttpRequestSample,
679
+ HttpStat,
680
+ HttpStatsResult,
681
+ HttpStatsCounter,
682
+ ApiCallSample,
683
+ ApiStat,
684
+ ApiStatsResult,
685
+ MqttStatsCounter
686
+ };
687
+