smart-home-engine 1.1.6 → 1.1.10

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-BW2J83t5.js";import{t as I}from"./index-DRKYxwDj.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-F_ZNZQ3N.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-DRKYxwDj.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-F_ZNZQ3N.js"></script>
176
176
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-BW2J83t5.js">
177
177
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
178
178
  <link rel="stylesheet" crossorigin href="/assets/index-iTrR9H4f.css">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.1.6",
3
+ "version": "1.1.10",
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
@@ -146,6 +146,7 @@ const scheduler = modules['node-schedule'];
146
146
  const StateStore = require('./lib/state-store');
147
147
  const sandboxModules = [];
148
148
  const store = new StateStore();
149
+ if (typeof config.port !== 'undefined') require('./web/broker-api').setStore(store);
149
150
  const scripts = {};
150
151
  const scriptOrigins = new Map(); // file → 'builtin' | 'user'
151
152
  const subscriptions = [];
@@ -361,6 +362,7 @@ if (config.url) {
361
362
  log.info('mqtt connected ' + config.url);
362
363
  log.debug('mqtt subscribe #');
363
364
  mqtt.subscribe('#');
365
+ mqtt.subscribe('$SYS/#');
364
366
  mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => c.callback());
365
367
 
366
368
  if (!_started) {
package/src/lib/dynsec.js CHANGED
@@ -46,9 +46,13 @@ function _drain() {
46
46
  const timer = setTimeout(() => {
47
47
  _inflight = false;
48
48
  _inflightResolve = null;
49
- if (_log) _log.debug(`dynsec: timeout waiting for response to "${command}" (${_timeout}ms)`);
49
+ if (_log) _log.warn(`dynsec: timeout waiting for response to "${command}" (${_timeout}ms) — is the dynsec plugin loaded and the admin user configured?`);
50
50
  reject(new Error(`dynsec timeout waiting for response to "${command}"`));
51
- _drain();
51
+ // Fail-fast: reject all remaining queued commands since the broker is not responding
52
+ while (_queue.length > 0) {
53
+ const queued = _queue.shift();
54
+ queued.reject(new Error(`dynsec: aborting "${queued.command}" — previous command timed out`));
55
+ }
52
56
  }, _timeout);
53
57
 
54
58
  _inflightResolve = (responses) => {
@@ -122,11 +126,15 @@ function init(config, log) {
122
126
  _client = mqtt.connect(config.url, opts);
123
127
 
124
128
  _client.on('connect', () => {
125
- _connected = true;
126
- _log.info('dynsec: connected as', dynsecCfg.adminUsername);
129
+ _log.info('dynsec: MQTT connect event, subscribing to response topic');
127
130
  _client.subscribe(RESPONSE_TOPIC, (err) => {
128
- if (err) _log.error('dynsec: failed to subscribe to response topic:', err.message);
129
- else _drain(); // flush any requests queued before connection
131
+ if (err) {
132
+ _log.error('dynsec: failed to subscribe to response topic:', err.message);
133
+ } else {
134
+ _connected = true;
135
+ _log.info('dynsec: ready — subscribed as', dynsecCfg.adminUsername);
136
+ _drain(); // flush any requests queued before connection
137
+ }
130
138
  });
131
139
  });
132
140
 
@@ -16,6 +16,7 @@
16
16
  const express = require('express');
17
17
  const path = require('path');
18
18
  const fs = require('fs');
19
+ const crypto = require('crypto');
19
20
  const dynsec = require('../lib/dynsec');
20
21
  const mosquittoConf = require('../lib/mosquitto-conf');
21
22
  const ca = require('../lib/ca');
@@ -28,12 +29,18 @@ const DEFAULT_SSH_KEY = path.join(sheConfig['data-dir'], 'ssh', 'broker_id_ed255
28
29
  const router = express.Router();
29
30
 
30
31
  let _log = null;
32
+ let _store = null;
31
33
 
32
34
  /** Must be called once from index.js so broker-api can emit debug-level log lines. */
33
35
  function setLogger(log) {
34
36
  _log = log;
35
37
  }
36
38
 
39
+ /** Must be called once from index.js to give broker-api access to the MQTT state store. */
40
+ function setStore(store) {
41
+ _store = store;
42
+ }
43
+
37
44
  // ── Helpers ────────────────────────────────────────────────────────────────────
38
45
 
39
46
  /** Get broker config from live config.json */
@@ -71,13 +78,14 @@ function handleError(res, err) {
71
78
  */
72
79
  router.get('/status', (req, res) => {
73
80
  const ds = dynsec.getStatus();
74
- const mqttState = req.app.locals.mqttState || {};
75
81
 
76
- const sysPrefixes = ['$SYS/broker/version', '$SYS/broker/clients/', '$SYS/broker/uptime'];
77
82
  const sys = {};
78
- for (const [topic, entry] of Object.entries(mqttState)) {
79
- if (sysPrefixes.some((p) => topic.startsWith(p))) {
80
- sys[topic] = entry;
83
+ if (_store) {
84
+ const sysPrefixes = ['$SYS/broker/version', '$SYS/broker/uptime', '$SYS/broker/clients/', '$SYS/broker/messages/'];
85
+ for (const [topic, entry] of _store.mqttEntries()) {
86
+ if (sysPrefixes.some((p) => topic.startsWith(p))) {
87
+ sys[topic] = entry;
88
+ }
81
89
  }
82
90
  }
83
91
 
@@ -89,11 +97,19 @@ router.get('/status', (req, res) => {
89
97
  /**
90
98
  * GET /she/broker/config
91
99
  * Returns parsed config structure + raw text + checksum.
100
+ * In remote mode, reads mosquitto.conf from the broker host via SSH.
92
101
  */
93
- router.get('/config', (req, res) => {
102
+ router.get('/config', async (req, res) => {
94
103
  try {
95
104
  const bc = getBrokerConfig(req);
96
105
  const fp = confPath(bc);
106
+ if (bc.ssh && bc.ssh.host) {
107
+ _log?.debug(`broker: reading remote config from ${bc.ssh.host}:${fp}`);
108
+ const raw = await sshDeploy.readRemoteFile(bc.ssh, fp);
109
+ const parsed = mosquittoConf.parseText(raw);
110
+ const cs = crypto.createHash('sha256').update(raw).digest('hex');
111
+ return res.json({ ...parsed, checksum: cs, backups: [] });
112
+ }
97
113
  const parsed = mosquittoConf.parse(fp);
98
114
  const cs = mosquittoConf.checksum(fp);
99
115
  const backups = mosquittoConf.listBackups(fp).map((b) => path.basename(b));
@@ -107,13 +123,20 @@ router.get('/config', (req, res) => {
107
123
  * PUT /she/broker/config
108
124
  * Write structured config (body: { listeners, managed, passthrough, checksum? }).
109
125
  * body.checksum is the client's last-known checksum for external-modify detection.
126
+ * In remote mode, uploads directly to the broker host via SCP — no local fs access.
110
127
  */
111
- router.put('/config', (req, res) => {
128
+ router.put('/config', async (req, res) => {
112
129
  try {
113
130
  const bc = getBrokerConfig(req);
114
131
  const fp = confPath(bc);
115
132
  const { listeners, managed, passthrough, checksum: clientChecksum } = req.body;
116
133
  const content = mosquittoConf.serialise({ listeners, managed, passthrough });
134
+ if (bc.ssh && bc.ssh.host) {
135
+ _log?.info(`broker: uploading config to remote ${bc.ssh.host}:${fp}`);
136
+ await sshDeploy.uploadContent(bc.ssh, content, fp);
137
+ return res.json({ ok: true, backupPath: null });
138
+ }
139
+ _log?.info(`broker: writing config to local ${fp}`);
117
140
  const result = mosquittoConf.write(fp, content, clientChecksum ?? null);
118
141
  if (!result.ok) {
119
142
  return res.status(409).json({ error: 'external_modify', message: 'mosquitto.conf was modified externally since last read' });
@@ -128,8 +151,9 @@ router.put('/config', (req, res) => {
128
151
  /**
129
152
  * PUT /she/broker/config/raw
130
153
  * Write raw mosquitto.conf text directly (used by the Advanced editor).
154
+ * In remote mode, uploads directly to the broker host via SCP — no local fs access.
131
155
  */
132
- router.put('/config/raw', (req, res) => {
156
+ router.put('/config/raw', async (req, res) => {
133
157
  try {
134
158
  const bc = getBrokerConfig(req);
135
159
  const fp = confPath(bc);
@@ -137,6 +161,12 @@ router.put('/config/raw', (req, res) => {
137
161
  if (typeof content !== 'string') {
138
162
  return res.status(400).json({ error: 'content must be a string' });
139
163
  }
164
+ if (bc.ssh && bc.ssh.host) {
165
+ _log?.info(`broker: uploading raw config to remote ${bc.ssh.host}:${fp}`);
166
+ await sshDeploy.uploadContent(bc.ssh, content, fp);
167
+ return res.json({ ok: true, backupPath: null });
168
+ }
169
+ _log?.info(`broker: writing raw config to local ${fp}`);
140
170
  const result = mosquittoConf.write(fp, content, clientChecksum ?? null);
141
171
  if (!result.ok) {
142
172
  return res.status(409).json({ error: 'external_modify', message: 'mosquitto.conf was modified externally since last read' });
@@ -673,7 +703,7 @@ router.delete('/ca/trusted/:fingerprint', async (req, res) => {
673
703
  }
674
704
  });
675
705
 
676
- module.exports = { router, setLogger };
706
+ module.exports = { router, setLogger, setStore };
677
707
 
678
708
  // ── SSH routes ─────────────────────────────────────────────────────────────────
679
709
  // Note: these routes are mounted on the same router but defined after module.exports