smart-home-engine 1.1.6 → 1.1.9
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/dist/web/assets/{index-DRKYxwDj.js → index-CZNc_m6c.js} +3 -3
- package/dist/web/assets/{tsMode-CY_XZkxX.js → tsMode-Cnr_wxoV.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/src/index.js +2 -0
- package/src/lib/dynsec.js +14 -6
- package/src/web/broker-api.js +44 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-CZNc_m6c.js";/*!-----------------------------------------------------------------------------
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
|
|
4
4
|
* Released under the MIT license
|
package/dist/web/index.html
CHANGED
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
}
|
|
173
173
|
})();
|
|
174
174
|
</script>
|
|
175
|
-
<script type="module" crossorigin src="/assets/index-
|
|
175
|
+
<script type="module" crossorigin src="/assets/index-CZNc_m6c.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
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.
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
129
|
-
|
|
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
|
|
package/src/web/broker-api.js
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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,14 +97,22 @@ 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
|
+
const backups = mosquittoConf.listBackups(fp).map((b) => path.basename(b));
|
|
107
|
+
if (bc.ssh && bc.ssh.host) {
|
|
108
|
+
_log?.debug(`broker: reading remote config from ${bc.ssh.host}:${fp}`);
|
|
109
|
+
const raw = await sshDeploy.readRemoteFile(bc.ssh, fp);
|
|
110
|
+
const parsed = mosquittoConf.parseText(raw);
|
|
111
|
+
const cs = crypto.createHash('sha256').update(raw).digest('hex');
|
|
112
|
+
return res.json({ ...parsed, checksum: cs, backups });
|
|
113
|
+
}
|
|
97
114
|
const parsed = mosquittoConf.parse(fp);
|
|
98
115
|
const cs = mosquittoConf.checksum(fp);
|
|
99
|
-
const backups = mosquittoConf.listBackups(fp).map((b) => path.basename(b));
|
|
100
116
|
res.json({ ...parsed, checksum: cs, backups });
|
|
101
117
|
} catch (err) {
|
|
102
118
|
handleError(res, err);
|
|
@@ -107,17 +123,25 @@ 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, the file is also deployed to the broker host via SCP.
|
|
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 });
|
|
117
|
-
const
|
|
134
|
+
const remote = bc.ssh && bc.ssh.host;
|
|
135
|
+
_log?.info(`broker: writing config to ${remote ? `remote ${bc.ssh.host}:${fp}` : `local ${fp}`}`);
|
|
136
|
+
// In remote mode skip local conflict check — remote is the source of truth
|
|
137
|
+
const result = mosquittoConf.write(fp, content, remote ? null : (clientChecksum ?? null));
|
|
118
138
|
if (!result.ok) {
|
|
119
139
|
return res.status(409).json({ error: 'external_modify', message: 'mosquitto.conf was modified externally since last read' });
|
|
120
140
|
}
|
|
141
|
+
if (remote) {
|
|
142
|
+
_log?.debug(`broker: uploading config to ${bc.ssh.host}:${fp}`);
|
|
143
|
+
await sshDeploy.uploadContent(bc.ssh, content, fp);
|
|
144
|
+
}
|
|
121
145
|
_lastWriteChecksum.set(fp, mosquittoConf.checksum(fp));
|
|
122
146
|
res.json({ ok: true, backupPath: result.backupPath ? path.basename(result.backupPath) : null });
|
|
123
147
|
} catch (err) {
|
|
@@ -128,8 +152,9 @@ router.put('/config', (req, res) => {
|
|
|
128
152
|
/**
|
|
129
153
|
* PUT /she/broker/config/raw
|
|
130
154
|
* Write raw mosquitto.conf text directly (used by the Advanced editor).
|
|
155
|
+
* In remote mode, the file is also deployed to the broker host via SCP.
|
|
131
156
|
*/
|
|
132
|
-
router.put('/config/raw', (req, res) => {
|
|
157
|
+
router.put('/config/raw', async (req, res) => {
|
|
133
158
|
try {
|
|
134
159
|
const bc = getBrokerConfig(req);
|
|
135
160
|
const fp = confPath(bc);
|
|
@@ -137,10 +162,17 @@ router.put('/config/raw', (req, res) => {
|
|
|
137
162
|
if (typeof content !== 'string') {
|
|
138
163
|
return res.status(400).json({ error: 'content must be a string' });
|
|
139
164
|
}
|
|
140
|
-
const
|
|
165
|
+
const remote = bc.ssh && bc.ssh.host;
|
|
166
|
+
_log?.info(`broker: writing raw config to ${remote ? `remote ${bc.ssh.host}:${fp}` : `local ${fp}`}`);
|
|
167
|
+
// In remote mode skip local conflict check — remote is the source of truth
|
|
168
|
+
const result = mosquittoConf.write(fp, content, remote ? null : (clientChecksum ?? null));
|
|
141
169
|
if (!result.ok) {
|
|
142
170
|
return res.status(409).json({ error: 'external_modify', message: 'mosquitto.conf was modified externally since last read' });
|
|
143
171
|
}
|
|
172
|
+
if (remote) {
|
|
173
|
+
_log?.debug(`broker: uploading raw config to ${bc.ssh.host}:${fp}`);
|
|
174
|
+
await sshDeploy.uploadContent(bc.ssh, content, fp);
|
|
175
|
+
}
|
|
144
176
|
_lastWriteChecksum.set(fp, mosquittoConf.checksum(fp));
|
|
145
177
|
res.json({ ok: true, backupPath: result.backupPath ? path.basename(result.backupPath) : null });
|
|
146
178
|
} catch (err) {
|
|
@@ -673,7 +705,7 @@ router.delete('/ca/trusted/:fingerprint', async (req, res) => {
|
|
|
673
705
|
}
|
|
674
706
|
});
|
|
675
707
|
|
|
676
|
-
module.exports = { router, setLogger };
|
|
708
|
+
module.exports = { router, setLogger, setStore };
|
|
677
709
|
|
|
678
710
|
// ── SSH routes ─────────────────────────────────────────────────────────────────
|
|
679
711
|
// Note: these routes are mounted on the same router but defined after module.exports
|