smart-home-engine 1.6.1 → 1.8.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-BW2J83t5.js";import{t as I}from"./index-DBAKaUFL.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-DLsurhm_.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,10 +172,10 @@
172
172
  }
173
173
  })();
174
174
  </script>
175
- <script type="module" crossorigin src="/assets/index-DBAKaUFL.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-DLsurhm_.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
- <link rel="stylesheet" crossorigin href="/assets/index-D-Rifjr_.css">
178
+ <link rel="stylesheet" crossorigin href="/assets/index-CmpNIegY.css">
179
179
  </head>
180
180
  <body>
181
181
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "1.6.1",
3
+ "version": "1.8.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": {
@@ -29,7 +29,19 @@ const execFileAsync = promisify(execFile);
29
29
  // NOTE: 'plugin_opt_dynsec_config_file' is kept here only for migration —
30
30
  // when read from an old conf it is normalised to 'plugin_opt_config_file' on
31
31
  // the same line that recognises it (see parseText below).
32
- const MANAGED_SINGLE_KEYS = new Set(['per_listener_settings', 'allow_anonymous', 'persistence', 'persistence_location', 'log_dest', 'log_type', 'plugin', 'plugin_opt_config_file', 'plugin_opt_dynsec_config_file']);
32
+ const MANAGED_SINGLE_KEYS = new Set([
33
+ 'per_listener_settings', 'allow_anonymous',
34
+ 'persistence', 'persistence_location',
35
+ 'log_dest', 'log_type',
36
+ 'plugin', 'plugin_opt_config_file', 'plugin_opt_dynsec_config_file',
37
+ // Connection limits
38
+ 'max_connections', 'max_inflight_messages', 'max_queued_messages',
39
+ 'max_packet_size', 'message_size_limit', 'max_keepalive',
40
+ // Sessions
41
+ 'persistent_client_expiration', 'retain_available',
42
+ // Performance / misc
43
+ 'set_tcp_nodelay', 'connection_messages',
44
+ ]);
33
45
 
34
46
  /**
35
47
  * Parse mosquitto.conf text into a structured object.
@@ -112,6 +124,9 @@ function isListenerSubkey(key) {
112
124
  return [
113
125
  'protocol',
114
126
  'socket_domain',
127
+ 'mount_point',
128
+ 'max_connections',
129
+ 'max_qos',
115
130
  'certfile',
116
131
  'keyfile',
117
132
  'cafile',
@@ -119,9 +134,12 @@ function isListenerSubkey(key) {
119
134
  'crlfile',
120
135
  'require_certificate',
121
136
  'use_identity_as_username',
137
+ 'use_subject_as_username',
122
138
  'tls_version',
123
139
  'websockets_log_level',
124
140
  'allow_anonymous',
141
+ 'password_file',
142
+ 'acl_file',
125
143
  ].includes(key);
126
144
  }
127
145
 
@@ -130,6 +148,15 @@ function applyListenerKey(listener, key, value) {
130
148
  case 'protocol':
131
149
  listener.protocol = value;
132
150
  break;
151
+ case 'mount_point':
152
+ listener.mount_point = value;
153
+ break;
154
+ case 'max_connections':
155
+ listener.max_connections = parseInt(value, 10);
156
+ break;
157
+ case 'max_qos':
158
+ listener.max_qos = parseInt(value, 10);
159
+ break;
133
160
  case 'certfile':
134
161
  case 'keyfile':
135
162
  case 'cafile':
@@ -144,9 +171,18 @@ function applyListenerKey(listener, key, value) {
144
171
  case 'use_identity_as_username':
145
172
  listener.tls.use_identity_as_username = value === 'true';
146
173
  break;
174
+ case 'use_subject_as_username':
175
+ listener.tls.use_subject_as_username = value === 'true';
176
+ break;
147
177
  case 'allow_anonymous':
148
178
  listener.allow_anonymous = value === 'true';
149
179
  break;
180
+ case 'password_file':
181
+ listener.password_file = value;
182
+ break;
183
+ case 'acl_file':
184
+ listener.acl_file = value;
185
+ break;
150
186
  default:
151
187
  break;
152
188
  }
@@ -164,7 +200,16 @@ function serialise(conf) {
164
200
  // Managed single-key entries first
165
201
  const { managed = {}, listeners = [], passthrough = [] } = conf;
166
202
 
167
- const keyOrder = ['per_listener_settings', 'allow_anonymous', 'persistence', 'persistence_location', 'log_dest', 'log_type', 'plugin', 'plugin_opt_config_file'];
203
+ const keyOrder = [
204
+ 'per_listener_settings', 'allow_anonymous',
205
+ 'persistence', 'persistence_location',
206
+ 'max_connections', 'max_inflight_messages', 'max_queued_messages',
207
+ 'max_packet_size', 'message_size_limit', 'max_keepalive',
208
+ 'persistent_client_expiration', 'retain_available',
209
+ 'set_tcp_nodelay', 'connection_messages',
210
+ 'log_dest', 'log_type',
211
+ 'plugin', 'plugin_opt_config_file',
212
+ ];
168
213
  for (const key of keyOrder) {
169
214
  if (managed[key] === undefined) continue;
170
215
  const val = managed[key];
@@ -182,6 +227,9 @@ function serialise(conf) {
182
227
  const addr = l.bindAddress ? ` ${l.bindAddress}` : '';
183
228
  lines.push(`listener ${l.port}${addr}`);
184
229
  if (l.protocol && l.protocol !== 'mqtt') lines.push(`protocol ${l.protocol}`);
230
+ if (l.mount_point) lines.push(`mount_point ${l.mount_point}`);
231
+ if (l.max_connections !== undefined && l.max_connections !== null) lines.push(`max_connections ${l.max_connections}`);
232
+ if (l.max_qos !== undefined && l.max_qos !== null) lines.push(`max_qos ${l.max_qos}`);
185
233
  const tls = l.tls || {};
186
234
  for (const tlsKey of ['certfile', 'keyfile', 'cafile', 'capath', 'crlfile', 'tls_version']) {
187
235
  if (tls[tlsKey]) lines.push(`${tlsKey} ${tls[tlsKey]}`);
@@ -192,9 +240,14 @@ function serialise(conf) {
192
240
  if (tls.use_identity_as_username !== undefined) {
193
241
  lines.push(`use_identity_as_username ${tls.use_identity_as_username ? 'true' : 'false'}`);
194
242
  }
243
+ if (tls.use_subject_as_username !== undefined) {
244
+ lines.push(`use_subject_as_username ${tls.use_subject_as_username ? 'true' : 'false'}`);
245
+ }
195
246
  if (l.allow_anonymous !== undefined) {
196
247
  lines.push(`allow_anonymous ${l.allow_anonymous ? 'true' : 'false'}`);
197
248
  }
249
+ if (l.password_file) lines.push(`password_file ${l.password_file}`);
250
+ if (l.acl_file) lines.push(`acl_file ${l.acl_file}`);
198
251
  lines.push('');
199
252
  }
200
253
 
@@ -17,6 +17,9 @@ const express = require('express');
17
17
  const path = require('path');
18
18
  const fs = require('fs');
19
19
  const crypto = require('crypto');
20
+ const { execFile, spawn } = require('child_process');
21
+ const { promisify } = require('util');
22
+ const execFileAsync = promisify(execFile);
20
23
  const dynsec = require('../lib/dynsec');
21
24
  const mosquittoConf = require('../lib/mosquitto-conf');
22
25
  const ca = require('../lib/ca');
@@ -947,7 +950,7 @@ module.exports = { router, setLogger, setStore };
947
950
  router.get('/local/check', async (req, res) => {
948
951
  function probe(cmd) {
949
952
  return new Promise((resolve) => {
950
- const cp = require('child_process').spawn(cmd, ['--help'], { stdio: 'ignore' });
953
+ const cp = spawn(cmd, ['--help'], { stdio: 'ignore' });
951
954
  cp.on('error', (e) => resolve(e.code !== 'ENOENT'));
952
955
  cp.on('close', () => resolve(true));
953
956
  });
@@ -960,6 +963,39 @@ router.get('/local/check', async (req, res) => {
960
963
  }
961
964
  });
962
965
 
966
+ // ── IP address listing ────────────────────────────────────────────────────────
967
+
968
+ /**
969
+ * GET /she/broker/ip-addresses
970
+ * List host IP addresses for bind-address autocomplete.
971
+ * Runs `ip a` locally or via SSH depending on broker.ssh configuration.
972
+ */
973
+ router.get('/ip-addresses', async (req, res) => {
974
+ const bc = getBrokerConfig(req);
975
+ try {
976
+ let stdout = '';
977
+ if (bc.ssh && bc.ssh.host) {
978
+ const result = await sshDeploy.runCommand(bc.ssh, 'ip a 2>/dev/null || ip addr 2>/dev/null');
979
+ stdout = result.stdout;
980
+ } else {
981
+ const result = await execFileAsync('ip', ['a'], { timeout: 5000 }).catch(() =>
982
+ execFileAsync('ip', ['addr'], { timeout: 5000 }),
983
+ );
984
+ stdout = result.stdout;
985
+ }
986
+ // Extract IPv4 and IPv6 addresses; skip loopback
987
+ const addresses = [];
988
+ for (const m of stdout.matchAll(/inet6?\s+([\da-f.:]+)(?:\/\d+)?/gi)) {
989
+ const addr = m[1];
990
+ if (addr === '127.0.0.1' || addr === '::1') continue;
991
+ addresses.push(addr);
992
+ }
993
+ res.json({ addresses: [...new Set(addresses)] });
994
+ } catch {
995
+ res.json({ addresses: [] });
996
+ }
997
+ });
998
+
963
999
  // ── SSH routes ─────────────────────────────────────────────────────────────────
964
1000
  // Note: these routes are mounted on the same router but defined after module.exports
965
1001
  // because they add to `router` (which is already exported by reference).