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.
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/index.d.ts +2344 -0
- package/index.js +131 -0
- package/lib/controller/device.js +1317 -0
- package/lib/controller/features/alarm-feature.js +89 -0
- package/lib/controller/features/child-lock-feature.js +61 -0
- package/lib/controller/features/config-feature.js +54 -0
- package/lib/controller/features/consumption-feature.js +210 -0
- package/lib/controller/features/control-feature.js +62 -0
- package/lib/controller/features/diffuser-feature.js +411 -0
- package/lib/controller/features/digest-timer-feature.js +22 -0
- package/lib/controller/features/digest-trigger-feature.js +22 -0
- package/lib/controller/features/dnd-feature.js +79 -0
- package/lib/controller/features/electricity-feature.js +144 -0
- package/lib/controller/features/encryption-feature.js +259 -0
- package/lib/controller/features/garage-feature.js +337 -0
- package/lib/controller/features/hub-feature.js +687 -0
- package/lib/controller/features/light-feature.js +408 -0
- package/lib/controller/features/presence-sensor-feature.js +297 -0
- package/lib/controller/features/roller-shutter-feature.js +456 -0
- package/lib/controller/features/runtime-feature.js +74 -0
- package/lib/controller/features/screen-feature.js +67 -0
- package/lib/controller/features/sensor-history-feature.js +47 -0
- package/lib/controller/features/smoke-config-feature.js +50 -0
- package/lib/controller/features/spray-feature.js +166 -0
- package/lib/controller/features/system-feature.js +269 -0
- package/lib/controller/features/temp-unit-feature.js +55 -0
- package/lib/controller/features/thermostat-feature.js +804 -0
- package/lib/controller/features/timer-feature.js +507 -0
- package/lib/controller/features/toggle-feature.js +223 -0
- package/lib/controller/features/trigger-feature.js +333 -0
- package/lib/controller/hub-device.js +185 -0
- package/lib/controller/subdevice.js +1537 -0
- package/lib/device-factory.js +463 -0
- package/lib/error-budget.js +138 -0
- package/lib/http-api.js +766 -0
- package/lib/manager.js +1609 -0
- package/lib/model/channel-info.js +79 -0
- package/lib/model/constants.js +119 -0
- package/lib/model/enums.js +819 -0
- package/lib/model/exception.js +363 -0
- package/lib/model/http/device.js +215 -0
- package/lib/model/http/error-codes.js +121 -0
- package/lib/model/http/exception.js +151 -0
- package/lib/model/http/subdevice.js +133 -0
- package/lib/model/push/alarm.js +112 -0
- package/lib/model/push/bind.js +97 -0
- package/lib/model/push/common.js +282 -0
- package/lib/model/push/diffuser-light.js +100 -0
- package/lib/model/push/diffuser-spray.js +83 -0
- package/lib/model/push/factory.js +229 -0
- package/lib/model/push/generic.js +115 -0
- package/lib/model/push/hub-battery.js +59 -0
- package/lib/model/push/hub-mts100-all.js +64 -0
- package/lib/model/push/hub-mts100-mode.js +59 -0
- package/lib/model/push/hub-mts100-temperature.js +62 -0
- package/lib/model/push/hub-online.js +59 -0
- package/lib/model/push/hub-sensor-alert.js +61 -0
- package/lib/model/push/hub-sensor-all.js +59 -0
- package/lib/model/push/hub-sensor-smoke.js +110 -0
- package/lib/model/push/hub-sensor-temphum.js +62 -0
- package/lib/model/push/hub-subdevicelist.js +50 -0
- package/lib/model/push/hub-togglex.js +60 -0
- package/lib/model/push/index.js +81 -0
- package/lib/model/push/online.js +53 -0
- package/lib/model/push/presence-study.js +61 -0
- package/lib/model/push/sensor-latestx.js +106 -0
- package/lib/model/push/timerx.js +63 -0
- package/lib/model/push/togglex.js +78 -0
- package/lib/model/push/triggerx.js +62 -0
- package/lib/model/push/unbind.js +34 -0
- package/lib/model/push/water-leak.js +107 -0
- package/lib/model/states/diffuser-light-state.js +119 -0
- package/lib/model/states/diffuser-spray-state.js +58 -0
- package/lib/model/states/garage-door-state.js +71 -0
- package/lib/model/states/index.js +38 -0
- package/lib/model/states/light-state.js +134 -0
- package/lib/model/states/presence-sensor-state.js +239 -0
- package/lib/model/states/roller-shutter-state.js +82 -0
- package/lib/model/states/spray-state.js +58 -0
- package/lib/model/states/thermostat-state.js +297 -0
- package/lib/model/states/timer-state.js +192 -0
- package/lib/model/states/toggle-state.js +105 -0
- package/lib/model/states/trigger-state.js +155 -0
- package/lib/subscription.js +587 -0
- package/lib/utilities/conversion.js +62 -0
- package/lib/utilities/debug.js +165 -0
- package/lib/utilities/mqtt.js +152 -0
- package/lib/utilities/network.js +53 -0
- package/lib/utilities/options.js +64 -0
- package/lib/utilities/request-queue.js +161 -0
- package/lib/utilities/ssid.js +37 -0
- package/lib/utilities/state-changes.js +66 -0
- package/lib/utilities/stats.js +687 -0
- package/lib/utilities/timer.js +310 -0
- package/lib/utilities/trigger.js +286 -0
- 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
|
+
|