smart-home-engine 1.35.0 → 1.35.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-DfNXDuwc.js → index-DxSKkp8l.js} +123 -123
- package/dist/web/assets/{index-DwaZ1Sfz.css → index-ji2CpixA.css} +1 -1
- package/dist/web/assets/{tsMode-DS17PuxG.js → tsMode-CLFEQmTc.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/lib/services-catalog.js +74 -28
- package/src/web/services-api.js +28 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-DKJI_o9J.js";import{t as I}from"./index-DxSKkp8l.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,10 +172,10 @@
|
|
|
172
172
|
}
|
|
173
173
|
})();
|
|
174
174
|
</script>
|
|
175
|
-
<script type="module" crossorigin src="/assets/index-
|
|
175
|
+
<script type="module" crossorigin src="/assets/index-DxSKkp8l.js"></script>
|
|
176
176
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-DKJI_o9J.js">
|
|
177
177
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
178
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
178
|
+
<link rel="stylesheet" crossorigin href="/assets/index-ji2CpixA.css">
|
|
179
179
|
</head>
|
|
180
180
|
<body>
|
|
181
181
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
* package when `versions[dist-tags.latest].dependencies` has mqtt-interfaces-core
|
|
11
11
|
* 3. full packument for the members only → description, homepage, repository, `mqttInterfaces`
|
|
12
12
|
*
|
|
13
|
-
* Cached for 24 h
|
|
13
|
+
* Cached for 24 h in memory and on disk (the data dir), answered from the cache at once and
|
|
14
|
+
* refreshed in the background — see catalog(); the last good result is kept on failure.
|
|
14
15
|
* Best effort throughout: a publisher that fails is reported in `errors`, the rest still lists.
|
|
15
16
|
*/
|
|
16
17
|
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
17
21
|
const CORE = 'mqtt-interfaces-core';
|
|
18
22
|
const REGISTRY = 'https://registry.npmjs.org';
|
|
19
23
|
const TTL = 24 * 60 * 60 * 1000;
|
|
@@ -23,12 +27,35 @@ function setFetch(fn) {
|
|
|
23
27
|
_fetch = fn;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
/** @type {{key: string, ts: number, result: object, promise?: Promise<object>} | null} */
|
|
30
|
+
/** @type {{key: string, ts: number, result: object|null, promise?: Promise<object>} | null} */
|
|
27
31
|
let _cache = null;
|
|
32
|
+
let _file = null;
|
|
28
33
|
function clearCache() {
|
|
29
34
|
_cache = null;
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
/** Persist the catalog under the data dir so a restarted daemon answers at once (best effort). */
|
|
38
|
+
function init({ file } = {}) {
|
|
39
|
+
_file = file || null;
|
|
40
|
+
_cache = null;
|
|
41
|
+
if (!_file) return;
|
|
42
|
+
try {
|
|
43
|
+
const saved = JSON.parse(fs.readFileSync(_file, 'utf8'));
|
|
44
|
+
if (saved && typeof saved.key === 'string' && saved.result && Array.isArray(saved.result.packages)) _cache = { key: saved.key, ts: saved.ts || 0, result: saved.result };
|
|
45
|
+
} catch {
|
|
46
|
+
/* no cache yet */
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function persist() {
|
|
50
|
+
if (!_file || !_cache || !_cache.result) return;
|
|
51
|
+
try {
|
|
52
|
+
fs.mkdirSync(path.dirname(_file), { recursive: true });
|
|
53
|
+
fs.writeFileSync(_file, JSON.stringify({ key: _cache.key, ts: _cache.ts, result: _cache.result }), 'utf8');
|
|
54
|
+
} catch {
|
|
55
|
+
/* read-only data dir — the in-memory copy still works */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
32
59
|
function validPublisher(p) {
|
|
33
60
|
return typeof p === 'string' && /^[a-z0-9][a-z0-9._-]{0,63}$/i.test(p);
|
|
34
61
|
}
|
|
@@ -107,38 +134,57 @@ async function sweep(publishers, now) {
|
|
|
107
134
|
return { packages, publishers, errors, fetchedAt: now };
|
|
108
135
|
}
|
|
109
136
|
|
|
137
|
+
/** A sweep in the background; the last good list survives a failed one. */
|
|
138
|
+
function startSweep(list, key, now) {
|
|
139
|
+
const previous = _cache && _cache.key === key ? _cache.result : null;
|
|
140
|
+
const promise = sweep(list, now)
|
|
141
|
+
.then((r) => {
|
|
142
|
+
// keep the last good list when the whole sweep produced nothing but errors
|
|
143
|
+
if (r.packages.length === 0 && r.errors.length > 0 && previous && previous.packages.length > 0) return { ...previous, errors: r.errors, stale: true };
|
|
144
|
+
return r;
|
|
145
|
+
})
|
|
146
|
+
.then((result) => {
|
|
147
|
+
_cache = { key, ts: now, result };
|
|
148
|
+
persist();
|
|
149
|
+
return result;
|
|
150
|
+
})
|
|
151
|
+
.catch((err) => {
|
|
152
|
+
_cache = previous ? { key, ts: now, result: previous } : null;
|
|
153
|
+
if (previous) return { ...previous, errors: [{ error: err.message }], stale: true };
|
|
154
|
+
throw err;
|
|
155
|
+
});
|
|
156
|
+
_cache = { key, ts: now, result: previous, promise };
|
|
157
|
+
promise.finally(() => {
|
|
158
|
+
if (_cache && _cache.promise === promise) delete _cache.promise;
|
|
159
|
+
});
|
|
160
|
+
return promise;
|
|
161
|
+
}
|
|
162
|
+
|
|
110
163
|
/**
|
|
111
|
-
* The catalog for the given trusted publishers (
|
|
164
|
+
* The catalog for the given trusted publishers. Answers from the cache (memory, or the file from a
|
|
165
|
+
* previous run) at once and refreshes in the background when the cache is older than a day or
|
|
166
|
+
* `force` is set — `refreshing: true` tells the caller to ask again. Only the very first sweep (no
|
|
167
|
+
* cache at all) or `wait: true` makes the call wait for the registry.
|
|
112
168
|
* @param {string[]} publishers
|
|
113
|
-
* @param {{force?: boolean, now?: number}} [opts]
|
|
169
|
+
* @param {{force?: boolean, wait?: boolean, now?: number}} [opts]
|
|
114
170
|
*/
|
|
115
171
|
async function catalog(publishers, opts = {}) {
|
|
116
172
|
const list = [...new Set((publishers || []).filter(validPublisher).map((p) => p.toLowerCase()))];
|
|
117
173
|
const now = opts.now ?? Date.now();
|
|
118
174
|
const key = list.join(',');
|
|
119
|
-
if (list.length === 0) return { packages: [], publishers: [], errors: [], fetchedAt: now, cached: false };
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
_cache = { key, ts: now, result: previous, promise };
|
|
133
|
-
try {
|
|
134
|
-
const result = await promise;
|
|
135
|
-
_cache = { key, ts: now, result };
|
|
136
|
-
return { ...result, cached: false };
|
|
137
|
-
} catch (err) {
|
|
138
|
-
_cache = previous ? { key, ts: now, result: previous } : null;
|
|
139
|
-
if (previous) return { ...previous, errors: [{ error: err.message }], stale: true, cached: true };
|
|
140
|
-
throw err;
|
|
141
|
-
}
|
|
175
|
+
if (list.length === 0) return { packages: [], publishers: [], errors: [], fetchedAt: now, cached: false, refreshing: false };
|
|
176
|
+
const entry = _cache && _cache.key === key ? _cache : null;
|
|
177
|
+
const fresh = entry && entry.result && now - entry.ts < TTL;
|
|
178
|
+
if ((!fresh || opts.force) && !(entry && entry.promise)) startSweep(list, key, now);
|
|
179
|
+
const cur = _cache;
|
|
180
|
+
if (cur.result && !opts.wait) return { ...cur.result, cached: true, refreshing: Boolean(cur.promise) };
|
|
181
|
+
const result = await (cur.promise || Promise.resolve(cur.result));
|
|
182
|
+
return { ...result, cached: !cur.promise, refreshing: false };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Whether a background sweep is running. */
|
|
186
|
+
function refreshing() {
|
|
187
|
+
return Boolean(_cache && _cache.promise);
|
|
142
188
|
}
|
|
143
189
|
|
|
144
|
-
module.exports = { catalog, sweep, packagesOf, coreDependency, details, validPublisher, setFetch, clearCache, CORE, REGISTRY };
|
|
190
|
+
module.exports = { catalog, sweep, packagesOf, coreDependency, details, validPublisher, setFetch, clearCache, init, refreshing, CORE, REGISTRY, TTL };
|
package/src/web/services-api.js
CHANGED
|
@@ -1278,6 +1278,33 @@ router.post('/hosts/:host/units/:adapter/:instance/file/create', async (req, res
|
|
|
1278
1278
|
});
|
|
1279
1279
|
|
|
1280
1280
|
// ── I7: adapter catalog ───────────────────────────────────────────────────────
|
|
1281
|
+
// persisted under the data dir; a daily timer keeps it fresh so the page never waits for npm
|
|
1282
|
+
try {
|
|
1283
|
+
catalog.init({ file: path.join(require('../lib/storage').getStoragePath('services'), 'catalog.json') });
|
|
1284
|
+
} catch {
|
|
1285
|
+
/* memory only */
|
|
1286
|
+
}
|
|
1287
|
+
let _catalogConfigPath = null;
|
|
1288
|
+
router.use((req, res, next) => {
|
|
1289
|
+
if (req.app && req.app.locals && req.app.locals.configPath) _catalogConfigPath = req.app.locals.configPath;
|
|
1290
|
+
next();
|
|
1291
|
+
});
|
|
1292
|
+
const _catalogTimer = setInterval(
|
|
1293
|
+
() => {
|
|
1294
|
+
if (!_catalogConfigPath) return;
|
|
1295
|
+
try {
|
|
1296
|
+
const cfg = JSON.parse(fs.readFileSync(_catalogConfigPath, 'utf8'));
|
|
1297
|
+
const svc = cfg.services || {};
|
|
1298
|
+
if (svc.enabled !== true) return;
|
|
1299
|
+
const list = Array.isArray(svc.trustedPublishers) ? svc.trustedPublishers : ['hobbyquaker'];
|
|
1300
|
+
catalog.catalog(list.filter(catalog.validPublisher)).catch(() => {}); // refreshes only when older than a day
|
|
1301
|
+
} catch {
|
|
1302
|
+
/* unreadable config */
|
|
1303
|
+
}
|
|
1304
|
+
},
|
|
1305
|
+
60 * 60 * 1000,
|
|
1306
|
+
);
|
|
1307
|
+
_catalogTimer.unref();
|
|
1281
1308
|
|
|
1282
1309
|
const DEFAULT_PUBLISHERS = ['hobbyquaker'];
|
|
1283
1310
|
function trustedPublishers(req) {
|
|
@@ -1289,6 +1316,7 @@ function trustedPublishers(req) {
|
|
|
1289
1316
|
// GET /she/services/catalog[?refresh=1]
|
|
1290
1317
|
router.get('/catalog', async (req, res) => {
|
|
1291
1318
|
try {
|
|
1319
|
+
// answers from the cache at once; a refresh (older than a day, or ?refresh=1) runs in the background and the client asks again while `refreshing`
|
|
1292
1320
|
const r = await catalog.catalog(trustedPublishers(req), { force: req.query.refresh === '1' });
|
|
1293
1321
|
res.json(r);
|
|
1294
1322
|
} catch (err) {
|