smart-home-engine 0.26.3 → 0.27.1
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-6dbanXAb.css +1 -0
- package/dist/web/assets/{index-DtmmxR--.js → index-ZrhJ-PgI.js} +80 -80
- package/dist/web/assets/{tsMode-COmSRXCz.js → tsMode-CPNv8SNw.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/index.js +47 -2
- package/src/web/git-api.js +132 -0
- package/dist/web/assets/index-CND95LL-.css +0 -1
|
@@ -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-ZrhJ-PgI.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
|
@@ -155,10 +155,10 @@
|
|
|
155
155
|
}
|
|
156
156
|
})();
|
|
157
157
|
</script>
|
|
158
|
-
<script type="module" crossorigin src="/assets/index-
|
|
158
|
+
<script type="module" crossorigin src="/assets/index-ZrhJ-PgI.js"></script>
|
|
159
159
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-BW2J83t5.js">
|
|
160
160
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
161
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
161
|
+
<link rel="stylesheet" crossorigin href="/assets/index-6dbanXAb.css">
|
|
162
162
|
</head>
|
|
163
163
|
<body>
|
|
164
164
|
<div id="app"></div>
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -241,6 +241,28 @@ function sunScheduleEvent(obj, shift) {
|
|
|
241
241
|
let mqtt = null;
|
|
242
242
|
let connected = false;
|
|
243
243
|
|
|
244
|
+
// Deferred start — wait for retained MQTT state before running scripts
|
|
245
|
+
let _started = false;
|
|
246
|
+
let _startupTimeout = null;
|
|
247
|
+
let _quietTimer = null;
|
|
248
|
+
const _RETAIN_QUIET_MS = 300; // ms of silence after last retained msg → start
|
|
249
|
+
const _STARTUP_TIMEOUT_MS = 10000; // ms to wait for broker before starting anyway
|
|
250
|
+
|
|
251
|
+
function startOnce(reason) {
|
|
252
|
+
if (_started) return;
|
|
253
|
+
_started = true;
|
|
254
|
+
if (_startupTimeout) {
|
|
255
|
+
clearTimeout(_startupTimeout);
|
|
256
|
+
_startupTimeout = null;
|
|
257
|
+
}
|
|
258
|
+
if (_quietTimer) {
|
|
259
|
+
clearTimeout(_quietTimer);
|
|
260
|
+
_quietTimer = null;
|
|
261
|
+
}
|
|
262
|
+
if (reason) log.info(reason);
|
|
263
|
+
start();
|
|
264
|
+
}
|
|
265
|
+
|
|
244
266
|
// Wire up the MQTT API: pass the state store and a getter for the live MQTT client.
|
|
245
267
|
// The getter always returns the current value of `mqtt` (null until connected).
|
|
246
268
|
require('./web/mqtt-api').init(store, () => mqtt);
|
|
@@ -323,6 +345,12 @@ if (config.url) {
|
|
|
323
345
|
log.debug('mqtt subscribe #');
|
|
324
346
|
mqtt.subscribe('#');
|
|
325
347
|
mqttEventCallbacks.filter((c) => c.event === 'connect').forEach((c) => c.callback());
|
|
348
|
+
|
|
349
|
+
// Arm the quiet-period timer (only needed on first connect, before scripts start)
|
|
350
|
+
if (!_started) {
|
|
351
|
+
if (_quietTimer) clearTimeout(_quietTimer);
|
|
352
|
+
_quietTimer = setTimeout(() => startOnce('mqtt: retained state ready, starting scripts'), _RETAIN_QUIET_MS);
|
|
353
|
+
}
|
|
326
354
|
});
|
|
327
355
|
|
|
328
356
|
mqtt.on('close', () => {
|
|
@@ -339,6 +367,13 @@ if (config.url) {
|
|
|
339
367
|
|
|
340
368
|
mqtt.on('message', (topic, payload, msg) => {
|
|
341
369
|
_mqttMsgCount++;
|
|
370
|
+
|
|
371
|
+
// Reset the quiet-period timer while the initial retained-message burst is in flight
|
|
372
|
+
if (!_started && msg.retain && _quietTimer) {
|
|
373
|
+
clearTimeout(_quietTimer);
|
|
374
|
+
_quietTimer = setTimeout(() => startOnce('mqtt: retained state ready, starting scripts'), _RETAIN_QUIET_MS);
|
|
375
|
+
}
|
|
376
|
+
|
|
342
377
|
if (shedb.handleMqttMessage(topic, payload)) return;
|
|
343
378
|
|
|
344
379
|
const state = require('./lib/parse-payload')(payload);
|
|
@@ -428,8 +463,18 @@ if (config.matterStorage) {
|
|
|
428
463
|
log.warn('matter controller disabled — set matterStorage in config.json to enable');
|
|
429
464
|
}
|
|
430
465
|
|
|
431
|
-
//
|
|
432
|
-
|
|
466
|
+
// If no broker is configured, start scripts immediately.
|
|
467
|
+
// If a broker is configured, startOnce() fires from the quiet-period timer inside
|
|
468
|
+
// mqtt.on('connect') once the retained-message burst settles, or from the
|
|
469
|
+
// startup-timeout fallback if the broker is unreachable.
|
|
470
|
+
if (config.url) {
|
|
471
|
+
_startupTimeout = setTimeout(() => {
|
|
472
|
+
log.warn('mqtt startup timeout — starting scripts without retained state');
|
|
473
|
+
startOnce();
|
|
474
|
+
}, _STARTUP_TIMEOUT_MS);
|
|
475
|
+
} else {
|
|
476
|
+
start();
|
|
477
|
+
}
|
|
433
478
|
|
|
434
479
|
function stateChange(topic, state, oldState, msg) {
|
|
435
480
|
subscriptions.forEach((subs) => {
|
package/src/web/git-api.js
CHANGED
|
@@ -198,4 +198,136 @@ router.post('/push', async (req, res) => {
|
|
|
198
198
|
}
|
|
199
199
|
});
|
|
200
200
|
|
|
201
|
+
// GET /she/git/log?path=<relPath>&limit=<n>
|
|
202
|
+
// Returns commits touching the given file (relative to scriptDir), up to limit.
|
|
203
|
+
router.get('/log', async (req, res) => {
|
|
204
|
+
const scriptDir = getRoot(req);
|
|
205
|
+
if (!scriptDir) return res.status(500).json({ error: 'scriptDir not configured' });
|
|
206
|
+
|
|
207
|
+
const relPath = String(req.query.path ?? '').trim();
|
|
208
|
+
if (!relPath) return res.status(400).json({ error: 'Missing path parameter' });
|
|
209
|
+
|
|
210
|
+
const limit = Math.min(parseInt(String(req.query.limit ?? '30'), 10) || 30, 200);
|
|
211
|
+
|
|
212
|
+
const gitRoot = await getGitRoot(scriptDir);
|
|
213
|
+
if (!gitRoot) return res.status(404).json({ error: 'Not a git repository' });
|
|
214
|
+
|
|
215
|
+
const abs = safePath(scriptDir, relPath);
|
|
216
|
+
if (!abs) return res.status(400).json({ error: 'Invalid path' });
|
|
217
|
+
|
|
218
|
+
const relToRoot = path.relative(gitRoot, abs).replace(/\\/g, '/');
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const { stdout } = await git(
|
|
222
|
+
['log', '--follow', '--format=%H%x1f%s%x1f%an%x1f%ai', `-n`, String(limit), '--', relToRoot],
|
|
223
|
+
gitRoot,
|
|
224
|
+
);
|
|
225
|
+
const commits = stdout
|
|
226
|
+
.split('\n')
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.map((line) => {
|
|
229
|
+
const [hash, subject, author, date] = line.split('\x1f');
|
|
230
|
+
return { hash, subject, author, date };
|
|
231
|
+
});
|
|
232
|
+
res.json(commits);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
res.status(500).json({ error: e.message });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// GET /she/git/show?hash=<hash>&path=<relPath>
|
|
239
|
+
// Returns { content: string, binary: false } or { content: null, binary: true }.
|
|
240
|
+
router.get('/show', async (req, res) => {
|
|
241
|
+
const scriptDir = getRoot(req);
|
|
242
|
+
if (!scriptDir) return res.status(500).json({ error: 'scriptDir not configured' });
|
|
243
|
+
|
|
244
|
+
const hash = String(req.query.hash ?? '').trim();
|
|
245
|
+
if (!/^[0-9a-f]{4,64}$/i.test(hash)) return res.status(400).json({ error: 'Invalid hash' });
|
|
246
|
+
|
|
247
|
+
const relPath = String(req.query.path ?? '').trim();
|
|
248
|
+
if (!relPath) return res.status(400).json({ error: 'Missing path parameter' });
|
|
249
|
+
|
|
250
|
+
const gitRoot = await getGitRoot(scriptDir);
|
|
251
|
+
if (!gitRoot) return res.status(404).json({ error: 'Not a git repository' });
|
|
252
|
+
|
|
253
|
+
const abs = safePath(scriptDir, relPath);
|
|
254
|
+
if (!abs) return res.status(400).json({ error: 'Invalid path' });
|
|
255
|
+
|
|
256
|
+
const relToRoot = path.relative(gitRoot, abs).replace(/\\/g, '/');
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const { stdout } = await git(['show', `${hash}:${relToRoot}`], gitRoot);
|
|
260
|
+
const binary = stdout.includes('\0');
|
|
261
|
+
res.json({ content: binary ? null : stdout, binary });
|
|
262
|
+
} catch (e) {
|
|
263
|
+
res.status(500).json({ error: e.message });
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// GET /she/git/log?path=<relPath>&limit=<n>
|
|
268
|
+
// Returns commits touching the given file (relative to scriptDir), up to limit.
|
|
269
|
+
router.get('/log', async (req, res) => {
|
|
270
|
+
const scriptDir = getRoot(req);
|
|
271
|
+
if (!scriptDir) return res.status(500).json({ error: 'scriptDir not configured' });
|
|
272
|
+
|
|
273
|
+
const relPath = String(req.query.path ?? '').trim();
|
|
274
|
+
if (!relPath) return res.status(400).json({ error: 'Missing path parameter' });
|
|
275
|
+
|
|
276
|
+
const limit = Math.min(parseInt(String(req.query.limit ?? '30'), 10) || 30, 200);
|
|
277
|
+
|
|
278
|
+
const gitRoot = await getGitRoot(scriptDir);
|
|
279
|
+
if (!gitRoot) return res.status(404).json({ error: 'Not a git repository' });
|
|
280
|
+
|
|
281
|
+
const abs = safePath(scriptDir, relPath);
|
|
282
|
+
if (!abs) return res.status(400).json({ error: 'Invalid path' });
|
|
283
|
+
|
|
284
|
+
const relToRoot = path.relative(gitRoot, abs).replace(/\\/g, '/');
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const { stdout } = await git(
|
|
288
|
+
['log', '--follow', '--format=%H%x1f%s%x1f%an%x1f%ai', `-n`, String(limit), '--', relToRoot],
|
|
289
|
+
gitRoot,
|
|
290
|
+
);
|
|
291
|
+
const commits = stdout
|
|
292
|
+
.split('\n')
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
.map((line) => {
|
|
295
|
+
const [hash, subject, author, date] = line.split('\x1f');
|
|
296
|
+
return { hash, subject, author, date };
|
|
297
|
+
});
|
|
298
|
+
res.json(commits);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
res.status(500).json({ error: e.message });
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// GET /she/git/show?hash=<hash>&path=<relPath>
|
|
305
|
+
// Returns { content: string, binary: false } or { content: null, binary: true }.
|
|
306
|
+
router.get('/show', async (req, res) => {
|
|
307
|
+
const scriptDir = getRoot(req);
|
|
308
|
+
if (!scriptDir) return res.status(500).json({ error: 'scriptDir not configured' });
|
|
309
|
+
|
|
310
|
+
const hash = String(req.query.hash ?? '').trim();
|
|
311
|
+
if (!/^[0-9a-f]{4,64}$/i.test(hash)) return res.status(400).json({ error: 'Invalid hash' });
|
|
312
|
+
|
|
313
|
+
const relPath = String(req.query.path ?? '').trim();
|
|
314
|
+
if (!relPath) return res.status(400).json({ error: 'Missing path parameter' });
|
|
315
|
+
|
|
316
|
+
const gitRoot = await getGitRoot(scriptDir);
|
|
317
|
+
if (!gitRoot) return res.status(404).json({ error: 'Not a git repository' });
|
|
318
|
+
|
|
319
|
+
const abs = safePath(scriptDir, relPath);
|
|
320
|
+
if (!abs) return res.status(400).json({ error: 'Invalid path' });
|
|
321
|
+
|
|
322
|
+
const relToRoot = path.relative(gitRoot, abs).replace(/\\/g, '/');
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const { stdout } = await git(['show', `${hash}:${relToRoot}`], gitRoot);
|
|
326
|
+
const binary = stdout.includes('\0');
|
|
327
|
+
res.json({ content: binary ? null : stdout, binary });
|
|
328
|
+
} catch (e) {
|
|
329
|
+
res.status(500).json({ error: e.message });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
201
333
|
module.exports = { router, git, getGitRoot };
|