smart-home-engine 1.1.19 → 1.2.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-sjamTHx3.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-CY1IzFyN.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-sjamTHx3.js"></script>
175
+ <script type="module" crossorigin src="/assets/index-CY1IzFyN.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-b3gdLMp3.css">
178
+ <link rel="stylesheet" crossorigin href="/assets/index-CiR8cZ_Y.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.1.19",
3
+ "version": "1.2.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
@@ -34,6 +34,10 @@ const _queue = [];
34
34
  let _inflight = false;
35
35
  let _inflightResolve = null;
36
36
 
37
+ // $SYS topic cache — populated by the she-admin MQTT connection which has
38
+ // the admin role and therefore $SYS/# subscribe + publishClientReceive ACLs.
39
+ const _sysData = {};
40
+
37
41
  function _drain() {
38
42
  if (_inflight || _queue.length === 0 || !_connected) return;
39
43
 
@@ -145,6 +149,12 @@ function init(config, log) {
145
149
  });
146
150
  }
147
151
  });
152
+ // The admin role has subscribePattern + publishClientReceive for $SYS/#.
153
+ // Subscribe here so the broker status endpoint always has fresh $SYS data
154
+ // even when the main MQTT client lacks the necessary ACL permissions.
155
+ _client.subscribe('$SYS/#', (err) => {
156
+ if (err) _log.debug('dynsec: $SYS/# subscription failed (not critical):', err.message);
157
+ });
148
158
  });
149
159
 
150
160
  _client.on('close', () => {
@@ -160,6 +170,13 @@ function init(config, log) {
160
170
  });
161
171
 
162
172
  _client.on('message', (topic, payload) => {
173
+ if (topic.startsWith('$SYS/')) {
174
+ const val = payload.toString();
175
+ const now = Date.now();
176
+ const prev = _sysData[topic];
177
+ _sysData[topic] = { val, ts: now, lc: prev && prev.val !== val ? now : (prev ? prev.lc : now) };
178
+ return;
179
+ }
163
180
  if (topic !== RESPONSE_TOPIC) return;
164
181
  let msg;
165
182
  try {
@@ -192,6 +209,8 @@ function stop() {
192
209
  _configured = false;
193
210
  _connected = false;
194
211
  _dynsecReady = false;
212
+ // Clear $SYS cache
213
+ Object.keys(_sysData).forEach((k) => delete _sysData[k]);
195
214
  // Reject any in-flight / queued requests
196
215
  if (_inflight && _inflightResolve) {
197
216
  _inflightResolve = null;
@@ -223,11 +242,11 @@ function setClientPassword(username, password) {
223
242
  }
224
243
 
225
244
  function listClients(verbose = false) {
226
- return _request('listClients', { verbose }).then((r) => r.clients || []);
245
+ return _request('listClients', { verbose }).then((r) => r.data?.clients ?? r.clients ?? []);
227
246
  }
228
247
 
229
248
  function getClient(username) {
230
- return _request('getClient', { username }).then((r) => r.client);
249
+ return _request('getClient', { username }).then((r) => r.data?.client ?? r.client);
231
250
  }
232
251
 
233
252
  // ── Role management ────────────────────────────────────────────────────────────
@@ -241,11 +260,11 @@ function deleteRole(rolename) {
241
260
  }
242
261
 
243
262
  function listRoles(verbose = false) {
244
- return _request('listRoles', { verbose }).then((r) => r.roles || []);
263
+ return _request('listRoles', { verbose }).then((r) => r.data?.roles ?? r.roles ?? []);
245
264
  }
246
265
 
247
266
  function getRole(rolename) {
248
- return _request('getRole', { rolename }).then((r) => r.role);
267
+ return _request('getRole', { rolename }).then((r) => r.data?.role ?? r.role);
249
268
  }
250
269
 
251
270
  /**
@@ -286,11 +305,11 @@ function deleteGroup(groupname) {
286
305
  }
287
306
 
288
307
  function listGroups(verbose = false) {
289
- return _request('listGroups', { verbose }).then((r) => r.groups || []);
308
+ return _request('listGroups', { verbose }).then((r) => r.data?.groups ?? r.groups ?? []);
290
309
  }
291
310
 
292
311
  function getGroup(groupname) {
293
- return _request('getGroup', { groupname }).then((r) => r.group);
312
+ return _request('getGroup', { groupname }).then((r) => r.data?.group ?? r.group);
294
313
  }
295
314
 
296
315
  function addGroupClient(groupname, username, priority = -1) {
@@ -312,7 +331,12 @@ function removeGroupRole(groupname, rolename) {
312
331
  // ── Default ACL access ─────────────────────────────────────────────────────────
313
332
 
314
333
  function getDefaultACLAccess() {
315
- return _request('getDefaultACLAccess').then((r) => r.acls || []);
334
+ return _request('getDefaultACLAccess').then((r) => r.data?.acls ?? r.acls ?? []);
335
+ }
336
+
337
+ /** Returns the $SYS topic cache collected by the she-admin MQTT client. */
338
+ function getSysData() {
339
+ return { ..._sysData };
316
340
  }
317
341
 
318
342
  function setDefaultACLAccess(acls) {
@@ -322,6 +346,7 @@ function setDefaultACLAccess(acls) {
322
346
  module.exports = {
323
347
  init,
324
348
  getStatus,
349
+ getSysData,
325
350
  stop,
326
351
  // Users
327
352
  createClient,
@@ -79,11 +79,16 @@ function handleError(res, err) {
79
79
  router.get('/status', (req, res) => {
80
80
  const ds = dynsec.getStatus();
81
81
 
82
- const sys = {};
82
+ // Prefer $SYS data from the she-admin MQTT client: it has admin role ACLs
83
+ // that explicitly allow $SYS/# even when the main client is denied by
84
+ // default-deny subscribe ACLs.
85
+ const sys = dynsec.getSysData();
86
+ // Fall back to the main MQTT client's state store for any topics not yet
87
+ // received by the dynsec client (e.g. when dynsec is not configured).
83
88
  if (_store) {
84
89
  const sysPrefixes = ['$SYS/broker/version', '$SYS/broker/uptime', '$SYS/broker/clients/', '$SYS/broker/messages/'];
85
90
  for (const [topic, entry] of _store.mqttEntries()) {
86
- if (sysPrefixes.some((p) => topic.startsWith(p))) {
91
+ if (!sys[topic] && sysPrefixes.some((p) => topic.startsWith(p))) {
87
92
  sys[topic] = entry;
88
93
  }
89
94
  }
@@ -473,6 +478,18 @@ router.delete('/groups/:group/roles/:role', async (req, res) => {
473
478
  }
474
479
  });
475
480
 
481
+ // ── dynsec: Default ACL access ────────────────────────────────────────────────
482
+
483
+ /** GET /she/broker/acl-defaults */
484
+ router.get('/acl-defaults', async (req, res) => {
485
+ try {
486
+ const acls = await dynsec.getDefaultACLAccess();
487
+ res.json({ acls });
488
+ } catch (err) {
489
+ handleError(res, err);
490
+ }
491
+ });
492
+
476
493
  // ── CA routes ─────────────────────────────────────────────────────────────────
477
494
 
478
495
  /** GET /she/broker/ca — CA cert info */