smart-home-engine 1.17.1 → 1.18.1

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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-CvkGKRP_.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-D8qyUbwO.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -172,7 +172,7 @@
172
172
  }
173
173
  })();
174
174
  </script>
175
- <script type="module" crossorigin src="/assets/index-CvkGKRP_.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-D8qyUbwO.js"></script>
176
176
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-Dt8a9eeo.js">
177
177
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
178
178
  <link rel="stylesheet" crossorigin href="/assets/index-BvKmwc2a.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.17.1",
3
+ "version": "1.18.1",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -192,6 +192,18 @@ const scriptTimers = new Map(); // scriptFile → Set<timer id>
192
192
 
193
193
  const _global = {};
194
194
 
195
+ // ── Event-loop heartbeat ─────────────────────────────────────────────────────
196
+ let _blockingScript = null;
197
+
198
+ /** Wrap a user callback dispatch so the heartbeat can identify the active script. */
199
+ function _dispatch(label, fn) {
200
+ _blockingScript = label;
201
+ fn();
202
+ setImmediate(() => {
203
+ _blockingScript = null;
204
+ });
205
+ }
206
+
195
207
  // Sun scheduling
196
208
 
197
209
  const SUNCALC_EVENTS = new Set([
@@ -269,10 +281,10 @@ function sunScheduleEvent(obj, shift) {
269
281
  // Event is less than 1s in the future or already in the past
270
282
  // (options.random may have shifted us further to the past)
271
283
  // call the callback immediately!
272
- obj.domain.bind(obj.callback)();
284
+ _dispatch(obj._script, obj.domain.bind(obj.callback));
273
285
  } else {
274
286
  // Schedule the event and track the job so it can be cancelled on script unload
275
- obj._job = scheduler.scheduleJob(event, obj.domain.bind(obj.callback));
287
+ obj._job = scheduler.scheduleJob(event, () => _dispatch(obj._script, obj.domain.bind(obj.callback)));
276
288
  }
277
289
  }
278
290
  }
@@ -400,7 +412,7 @@ if (config.url) {
400
412
  log.debug('mqtt subscribe #');
401
413
  mqtt.subscribe('#');
402
414
  mqtt.subscribe('$SYS/#');
403
- mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => c.callback());
415
+ mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => _dispatch(c._script, () => c.callback()));
404
416
 
405
417
  if (!_started) {
406
418
  // Cancel the “broker not connecting” startup timeout — we’re connected.
@@ -435,7 +447,7 @@ if (config.url) {
435
447
  if (connected) {
436
448
  connected = false;
437
449
  log.info('mqtt closed ' + config.url);
438
- mqttEventCallbacks.filter((c) => c.event === 'disconnect').forEach((c) => c.callback());
450
+ mqttEventCallbacks.filter((c) => c.event === 'disconnect').forEach((c) => _dispatch(c._script, () => c.callback()));
439
451
  }
440
452
  broadcast({ type: 'mqtt:status', ready: _started, connected: false });
441
453
  });
@@ -604,7 +616,7 @@ function stateChange(topic, state, oldState, msg) {
604
616
  * @param {object} objPrev - previous state - the whole state object
605
617
  * @param {object} msg - the mqtt message as received from MQTT.js
606
618
  */
607
- subs.callback(topic, state.val, state, oldState, msg);
619
+ _dispatch(subs._script, () => subs.callback(topic, state.val, state, oldState, msg));
608
620
  }, delay);
609
621
  }
610
622
  });
@@ -844,12 +856,12 @@ function runScript(script, name, _origin) {
844
856
  scheduler.scheduleJob(pattern, () => {
845
857
  // Track the random-delay timer so it is cancelled on unload
846
858
  // if the job fires in the same tick as the script is reloaded.
847
- const id = setTimeout(scriptDomain.bind(callback), (parseFloat(options.random) || 0) * 1000 * Math.random());
859
+ const id = setTimeout(() => _dispatch(name, scriptDomain.bind(callback)), (parseFloat(options.random) || 0) * 1000 * Math.random());
848
860
  _myTimers.add(id);
849
861
  }),
850
862
  );
851
863
  } else {
852
- _myJobs.push(scheduler.scheduleJob(pattern, scriptDomain.bind(callback)));
864
+ _myJobs.push(scheduler.scheduleJob(pattern, () => _dispatch(name, scriptDomain.bind(callback))));
853
865
  }
854
866
  },
855
867
 
@@ -969,7 +981,7 @@ function runScript(script, name, _origin) {
969
981
  const varStoreKey = 'var::' + key.slice(5);
970
982
  const boundCb = scriptDomain.bind(callback);
971
983
  const varHandler = (changedKey, val, obj, prevObj) => {
972
- if (changedKey === varStoreKey) boundCb(val, obj, prevObj);
984
+ if (changedKey === varStoreKey) _dispatch(name, () => boundCb(val, obj, prevObj));
973
985
  };
974
986
  store.on('change', varHandler);
975
987
  varSubscriptions.push({ key: varStoreKey, handler: varHandler, _script: name });
@@ -1465,6 +1477,26 @@ function loadDir(dir) {
1465
1477
  }
1466
1478
  }
1467
1479
  function start() {
1480
+ if (config.heartbeat?.enabled) {
1481
+ const _hbInterval = typeof config.heartbeat.interval === 'number' ? config.heartbeat.interval : 50;
1482
+ const _hbThreshold = typeof config.heartbeat.threshold === 'number' ? config.heartbeat.threshold : 300;
1483
+ let _lastBeat = Date.now();
1484
+ const _hbTimer = setInterval(() => {
1485
+ const now = Date.now();
1486
+ const lag = now - _lastBeat - _hbInterval;
1487
+ _lastBeat = now;
1488
+ if (lag > _hbThreshold) {
1489
+ const label = _blockingScript ?? 'unknown';
1490
+ if (lag > 2000) {
1491
+ log.warn(`event loop blocked ${lag}ms — script: ${label}`);
1492
+ } else {
1493
+ log.warn(`event loop lag ${lag}ms — last active script: ${label}`);
1494
+ }
1495
+ }
1496
+ }, _hbInterval);
1497
+ _hbTimer.unref();
1498
+ }
1499
+
1468
1500
  if (config.file) {
1469
1501
  if (typeof config.file === 'string') {
1470
1502
  loadScript(config.file);