smart-home-engine 1.17.1 → 1.18.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.
@@ -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-CEmLX_uw.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-CEmLX_uw.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.0",
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
  }
@@ -372,6 +384,26 @@ require('./web/server').setStatsProvider(() => {
372
384
  // Push current script:running state so the UI green dots survive a browser reload.
373
385
  setWelcomeProvider(() => Object.keys(scripts).map((f) => ({ type: 'script:running', path: makeLabel(f).slice(0, -1), running: true })));
374
386
 
387
+ if (config.heartbeat?.enabled) {
388
+ const _hbInterval = typeof config.heartbeat.interval === 'number' ? config.heartbeat.interval : 50;
389
+ const _hbThreshold = typeof config.heartbeat.threshold === 'number' ? config.heartbeat.threshold : 300;
390
+ let _lastBeat = Date.now();
391
+ const _hbTimer = setInterval(() => {
392
+ const now = Date.now();
393
+ const lag = now - _lastBeat - _hbInterval;
394
+ _lastBeat = now;
395
+ if (lag > _hbThreshold) {
396
+ const label = _blockingScript ?? 'unknown';
397
+ if (lag > 2000) {
398
+ log.warn(`event loop blocked ${lag}ms — script: ${label}`);
399
+ } else {
400
+ log.warn(`event loop lag ${lag}ms — last active script: ${label}`);
401
+ }
402
+ }
403
+ }, _hbInterval);
404
+ _hbTimer.unref();
405
+ }
406
+
375
407
  if (!config.url) {
376
408
  log.warn('no MQTT broker URL configured — set "url" in ' + path.join(require('os').homedir(), '.she', 'config.json'));
377
409
  }
@@ -400,7 +432,7 @@ if (config.url) {
400
432
  log.debug('mqtt subscribe #');
401
433
  mqtt.subscribe('#');
402
434
  mqtt.subscribe('$SYS/#');
403
- mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => c.callback());
435
+ mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => _dispatch(c._script, () => c.callback()));
404
436
 
405
437
  if (!_started) {
406
438
  // Cancel the “broker not connecting” startup timeout — we’re connected.
@@ -435,7 +467,7 @@ if (config.url) {
435
467
  if (connected) {
436
468
  connected = false;
437
469
  log.info('mqtt closed ' + config.url);
438
- mqttEventCallbacks.filter((c) => c.event === 'disconnect').forEach((c) => c.callback());
470
+ mqttEventCallbacks.filter((c) => c.event === 'disconnect').forEach((c) => _dispatch(c._script, () => c.callback()));
439
471
  }
440
472
  broadcast({ type: 'mqtt:status', ready: _started, connected: false });
441
473
  });
@@ -604,7 +636,7 @@ function stateChange(topic, state, oldState, msg) {
604
636
  * @param {object} objPrev - previous state - the whole state object
605
637
  * @param {object} msg - the mqtt message as received from MQTT.js
606
638
  */
607
- subs.callback(topic, state.val, state, oldState, msg);
639
+ _dispatch(subs._script, () => subs.callback(topic, state.val, state, oldState, msg));
608
640
  }, delay);
609
641
  }
610
642
  });
@@ -844,12 +876,12 @@ function runScript(script, name, _origin) {
844
876
  scheduler.scheduleJob(pattern, () => {
845
877
  // Track the random-delay timer so it is cancelled on unload
846
878
  // 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());
879
+ const id = setTimeout(() => _dispatch(name, scriptDomain.bind(callback)), (parseFloat(options.random) || 0) * 1000 * Math.random());
848
880
  _myTimers.add(id);
849
881
  }),
850
882
  );
851
883
  } else {
852
- _myJobs.push(scheduler.scheduleJob(pattern, scriptDomain.bind(callback)));
884
+ _myJobs.push(scheduler.scheduleJob(pattern, () => _dispatch(name, scriptDomain.bind(callback))));
853
885
  }
854
886
  },
855
887
 
@@ -969,7 +1001,7 @@ function runScript(script, name, _origin) {
969
1001
  const varStoreKey = 'var::' + key.slice(5);
970
1002
  const boundCb = scriptDomain.bind(callback);
971
1003
  const varHandler = (changedKey, val, obj, prevObj) => {
972
- if (changedKey === varStoreKey) boundCb(val, obj, prevObj);
1004
+ if (changedKey === varStoreKey) _dispatch(name, () => boundCb(val, obj, prevObj));
973
1005
  };
974
1006
  store.on('change', varHandler);
975
1007
  varSubscriptions.push({ key: varStoreKey, handler: varHandler, _script: name });