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-
|
|
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
|
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-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
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,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
|