smart-home-engine 1.6.0 → 1.7.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--jR1TyQt.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-Ba2WAZRy.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--jR1TyQt.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-Ba2WAZRy.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-CloKwceI.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.0",
3
+ "version": "1.7.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/lib/dynsec.js CHANGED
@@ -64,6 +64,11 @@ function _drain() {
64
64
  clearTimeout(timer);
65
65
  _inflight = false;
66
66
  _inflightResolve = null;
67
+ // Any response from the plugin proves it is active, regardless of the startup probe result.
68
+ if (!_dynsecReady) {
69
+ _dynsecReady = true;
70
+ if (_log) _log.info('dynsec: plugin confirmed active (via response)');
71
+ }
67
72
  const r = responses.find((resp) => resp.command === command);
68
73
  if (r && r.error) {
69
74
  if (_log) _log.debug(`dynsec: ✕ "${command}" error: ${r.error}`);
@@ -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,6 +134,7 @@ 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',
@@ -130,6 +146,15 @@ function applyListenerKey(listener, key, value) {
130
146
  case 'protocol':
131
147
  listener.protocol = value;
132
148
  break;
149
+ case 'mount_point':
150
+ listener.mount_point = value;
151
+ break;
152
+ case 'max_connections':
153
+ listener.max_connections = parseInt(value, 10);
154
+ break;
155
+ case 'max_qos':
156
+ listener.max_qos = parseInt(value, 10);
157
+ break;
133
158
  case 'certfile':
134
159
  case 'keyfile':
135
160
  case 'cafile':
@@ -144,6 +169,9 @@ function applyListenerKey(listener, key, value) {
144
169
  case 'use_identity_as_username':
145
170
  listener.tls.use_identity_as_username = value === 'true';
146
171
  break;
172
+ case 'use_subject_as_username':
173
+ listener.tls.use_subject_as_username = value === 'true';
174
+ break;
147
175
  case 'allow_anonymous':
148
176
  listener.allow_anonymous = value === 'true';
149
177
  break;
@@ -164,7 +192,16 @@ function serialise(conf) {
164
192
  // Managed single-key entries first
165
193
  const { managed = {}, listeners = [], passthrough = [] } = conf;
166
194
 
167
- const keyOrder = ['per_listener_settings', 'allow_anonymous', 'persistence', 'persistence_location', 'log_dest', 'log_type', 'plugin', 'plugin_opt_config_file'];
195
+ const keyOrder = [
196
+ 'per_listener_settings', 'allow_anonymous',
197
+ 'persistence', 'persistence_location',
198
+ 'max_connections', 'max_inflight_messages', 'max_queued_messages',
199
+ 'max_packet_size', 'message_size_limit', 'max_keepalive',
200
+ 'persistent_client_expiration', 'retain_available',
201
+ 'set_tcp_nodelay', 'connection_messages',
202
+ 'log_dest', 'log_type',
203
+ 'plugin', 'plugin_opt_config_file',
204
+ ];
168
205
  for (const key of keyOrder) {
169
206
  if (managed[key] === undefined) continue;
170
207
  const val = managed[key];
@@ -182,6 +219,9 @@ function serialise(conf) {
182
219
  const addr = l.bindAddress ? ` ${l.bindAddress}` : '';
183
220
  lines.push(`listener ${l.port}${addr}`);
184
221
  if (l.protocol && l.protocol !== 'mqtt') lines.push(`protocol ${l.protocol}`);
222
+ if (l.mount_point) lines.push(`mount_point ${l.mount_point}`);
223
+ if (l.max_connections !== undefined && l.max_connections !== null) lines.push(`max_connections ${l.max_connections}`);
224
+ if (l.max_qos !== undefined && l.max_qos !== null) lines.push(`max_qos ${l.max_qos}`);
185
225
  const tls = l.tls || {};
186
226
  for (const tlsKey of ['certfile', 'keyfile', 'cafile', 'capath', 'crlfile', 'tls_version']) {
187
227
  if (tls[tlsKey]) lines.push(`${tlsKey} ${tls[tlsKey]}`);
@@ -192,6 +232,9 @@ function serialise(conf) {
192
232
  if (tls.use_identity_as_username !== undefined) {
193
233
  lines.push(`use_identity_as_username ${tls.use_identity_as_username ? 'true' : 'false'}`);
194
234
  }
235
+ if (tls.use_subject_as_username !== undefined) {
236
+ lines.push(`use_subject_as_username ${tls.use_subject_as_username ? 'true' : 'false'}`);
237
+ }
195
238
  if (l.allow_anonymous !== undefined) {
196
239
  lines.push(`allow_anonymous ${l.allow_anonymous ? 'true' : 'false'}`);
197
240
  }
@@ -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).