iobroker.cometvisu 0.0.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/LICENSE +21 -0
- package/README.md +87 -0
- package/admin/cometvisu.png +0 -0
- package/admin/custom/assets/Components-CLNZhuIY.js +1 -0
- package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomCometVisu__mf_owner__1-C7tyeGL_.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomCometVisu__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-CceB7pYp.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomCometVisu__mf_owner__1__loadShare___mf_0_mui_mf_1_icons_mf_2_material__loadShare__.js-CwI5TYpV.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomCometVisu__mf_owner__1__loadShare___mf_0_mui_mf_1_material__loadShare__.js-DMgVXGjz.js +1 -0
- package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomCometVisu__mf_owner__1__loadShare__react__loadShare__.js-Dmq5ji6n.js +1 -0
- package/admin/custom/assets/dist-DPGjdSM2.js +4 -0
- package/admin/custom/assets/hostInit-DOpe5t6n.js +1 -0
- package/admin/custom/assets/index-CnKGi9b5.js +1 -0
- package/admin/custom/assets/mf-entry-bootstrap-0-d9e9941a.js +41 -0
- package/admin/custom/assets/virtualExposes-B2dZqxIp.js +1 -0
- package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__ConfigCustomCometVisu__customComponents_js-Ck-YWfGN.js +1 -0
- package/admin/custom/assets/virtual_mf-exposes___mfe_internal__ConfigCustomCometVisu__customComponents_js-CDpFBOxm.js +2 -0
- package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
- package/admin/custom/customComponents.js +12 -0
- package/admin/i18n/de/translations.json +8 -0
- package/admin/i18n/en/translations.json +8 -0
- package/admin/jsonConfig.json +39 -0
- package/build/lib/releases.js +271 -0
- package/build/lib/releases.js.map +7 -0
- package/build/lib/web.js +230 -0
- package/build/lib/web.js.map +7 -0
- package/build/lib/webSocket.js +42 -0
- package/build/lib/webSocket.js.map +7 -0
- package/build/main.js +309 -0
- package/build/main.js.map +7 -0
- package/io-package.json +128 -0
- package/package.json +77 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var releases_exports = {};
|
|
30
|
+
__export(releases_exports, {
|
|
31
|
+
CUSTOM_FILE_PREFIX: () => CUSTOM_FILE_PREFIX,
|
|
32
|
+
CUSTOM_PREFIX: () => CUSTOM_PREFIX,
|
|
33
|
+
CUSTOM_VERSION: () => CUSTOM_VERSION,
|
|
34
|
+
OFFICIAL_FILE_PREFIX: () => OFFICIAL_FILE_PREFIX,
|
|
35
|
+
customBuildDir: () => customBuildDir,
|
|
36
|
+
ensureRelease: () => ensureRelease,
|
|
37
|
+
findHtmlRoot: () => findHtmlRoot,
|
|
38
|
+
listCustomBuilds: () => listCustomBuilds,
|
|
39
|
+
pruneReleaseBuilds: () => pruneReleaseBuilds,
|
|
40
|
+
readCustomBuild: () => readCustomBuild,
|
|
41
|
+
removeCustomBuild: () => removeCustomBuild,
|
|
42
|
+
removeLegacyCustomBuild: () => removeLegacyCustomBuild,
|
|
43
|
+
resolveVersionSelection: () => resolveVersionSelection,
|
|
44
|
+
unpackUploadedTarball: () => unpackUploadedTarball
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(releases_exports);
|
|
47
|
+
var import_axios = __toESM(require("axios"));
|
|
48
|
+
var import_crypto = require("crypto");
|
|
49
|
+
var fs = __toESM(require("fs"));
|
|
50
|
+
var path = __toESM(require("path"));
|
|
51
|
+
var tar = __toESM(require("tar"));
|
|
52
|
+
const REPO = "CometVisu/CometVisu";
|
|
53
|
+
const RELEASES_URL = `https://api.github.com/repos/${REPO}/releases?per_page=100`;
|
|
54
|
+
const CUSTOM_VERSION = "__custom__";
|
|
55
|
+
const CUSTOM_PREFIX = `${CUSTOM_VERSION}:`;
|
|
56
|
+
const CUSTOM_FILE_PREFIX = "[Custom] ";
|
|
57
|
+
const OFFICIAL_FILE_PREFIX = "[Official] ";
|
|
58
|
+
const LEGACY_CUSTOM_PREFIX = "Custom: ";
|
|
59
|
+
const LEGACY_OFFICIAL_PREFIX = "Official: ";
|
|
60
|
+
const LEGACY_LATEST_LABEL = "latest release";
|
|
61
|
+
function resolveVersionSelection(value, legacyBuildUpload) {
|
|
62
|
+
if (value.startsWith(CUSTOM_FILE_PREFIX)) {
|
|
63
|
+
return { kind: "custom", file: value.slice(CUSTOM_FILE_PREFIX.length) };
|
|
64
|
+
}
|
|
65
|
+
if (value.startsWith(OFFICIAL_FILE_PREFIX)) {
|
|
66
|
+
return { kind: "release", tag: value.slice(OFFICIAL_FILE_PREFIX.length) };
|
|
67
|
+
}
|
|
68
|
+
if (value.startsWith(LEGACY_CUSTOM_PREFIX)) {
|
|
69
|
+
return { kind: "custom", file: value };
|
|
70
|
+
}
|
|
71
|
+
if (value.startsWith(LEGACY_OFFICIAL_PREFIX)) {
|
|
72
|
+
const tag = value.slice(LEGACY_OFFICIAL_PREFIX.length);
|
|
73
|
+
return { kind: "release", tag: tag === LEGACY_LATEST_LABEL ? "" : tag };
|
|
74
|
+
}
|
|
75
|
+
if (value.startsWith(CUSTOM_PREFIX)) {
|
|
76
|
+
return { kind: "custom", file: value.slice(CUSTOM_PREFIX.length) };
|
|
77
|
+
}
|
|
78
|
+
if (value === CUSTOM_VERSION) {
|
|
79
|
+
return { kind: "custom", file: legacyBuildUpload || "" };
|
|
80
|
+
}
|
|
81
|
+
return { kind: "release", tag: value };
|
|
82
|
+
}
|
|
83
|
+
function githubHeaders() {
|
|
84
|
+
return {
|
|
85
|
+
Accept: "application/vnd.github+json",
|
|
86
|
+
"User-Agent": "iobroker.cometvisu"
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function buildAsset(release) {
|
|
90
|
+
const exact = `cometvisu-${release.tag_name.toLowerCase()}.tar.gz`;
|
|
91
|
+
return release.assets.find((a) => a.name.toLowerCase() === exact) || release.assets.find((a) => /^CometVisu-.*\.tar\.gz$/i.test(a.name));
|
|
92
|
+
}
|
|
93
|
+
async function fetchReleases() {
|
|
94
|
+
const res = await import_axios.default.get(RELEASES_URL, { headers: githubHeaders(), timeout: 2e4 });
|
|
95
|
+
return res.data.filter((r) => !!buildAsset(r));
|
|
96
|
+
}
|
|
97
|
+
async function resolveRelease(version) {
|
|
98
|
+
const releases = await fetchReleases();
|
|
99
|
+
if (!releases.length) {
|
|
100
|
+
throw new Error("no CometVisu release with a build archive was found");
|
|
101
|
+
}
|
|
102
|
+
if (!version) {
|
|
103
|
+
return releases.find((r) => !r.prerelease) || releases[0];
|
|
104
|
+
}
|
|
105
|
+
const release = releases.find((r) => r.tag_name === version);
|
|
106
|
+
if (!release) {
|
|
107
|
+
throw new Error(`CometVisu release "${version}" not found`);
|
|
108
|
+
}
|
|
109
|
+
return release;
|
|
110
|
+
}
|
|
111
|
+
function findHtmlRoot(dir) {
|
|
112
|
+
let level = [dir];
|
|
113
|
+
for (let depth = 0; depth < 5 && level.length; depth++) {
|
|
114
|
+
const next = [];
|
|
115
|
+
for (const current of level) {
|
|
116
|
+
if (fs.existsSync(path.join(current, "index.html"))) {
|
|
117
|
+
return current;
|
|
118
|
+
}
|
|
119
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
next.push(path.join(current, entry.name));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
level = next;
|
|
126
|
+
}
|
|
127
|
+
return dir;
|
|
128
|
+
}
|
|
129
|
+
async function ensureRelease(dataDir, version, log) {
|
|
130
|
+
const release = await resolveRelease(version);
|
|
131
|
+
const targetDir = path.join(dataDir, "cometvisu", release.tag_name);
|
|
132
|
+
const marker = path.join(targetDir, ".complete");
|
|
133
|
+
if (fs.existsSync(marker)) {
|
|
134
|
+
log.debug(`CometVisu ${release.tag_name} is already present`);
|
|
135
|
+
return { tag: release.tag_name, htmlRoot: findHtmlRoot(targetDir) };
|
|
136
|
+
}
|
|
137
|
+
const asset = buildAsset(release);
|
|
138
|
+
if (!asset) {
|
|
139
|
+
throw new Error(`release ${release.tag_name} has no CometVisu build archive`);
|
|
140
|
+
}
|
|
141
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
142
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
143
|
+
log.info(`downloading CometVisu ${release.tag_name} (${asset.name}, ${Math.round(asset.size / 1048576)} MB)`);
|
|
144
|
+
const res = await import_axios.default.get(asset.browser_download_url, {
|
|
145
|
+
headers: githubHeaders(),
|
|
146
|
+
responseType: "stream",
|
|
147
|
+
timeout: 12e4
|
|
148
|
+
});
|
|
149
|
+
await extractTarball(res.data, targetDir);
|
|
150
|
+
fs.writeFileSync(marker, (/* @__PURE__ */ new Date()).toISOString());
|
|
151
|
+
log.info(`CometVisu ${release.tag_name} unpacked to ${targetDir}`);
|
|
152
|
+
return { tag: release.tag_name, htmlRoot: findHtmlRoot(targetDir) };
|
|
153
|
+
}
|
|
154
|
+
function extractTarball(source, targetDir) {
|
|
155
|
+
return new Promise((resolve, reject) => {
|
|
156
|
+
const extract = tar.x({ cwd: targetDir });
|
|
157
|
+
const input = typeof source === "string" ? fs.createReadStream(source) : source;
|
|
158
|
+
input.on("error", reject);
|
|
159
|
+
extract.on("error", reject);
|
|
160
|
+
extract.on("finish", () => resolve());
|
|
161
|
+
input.pipe(extract);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
function customRootDir(dataDir) {
|
|
165
|
+
return path.join(dataDir, "cometvisu", "custom");
|
|
166
|
+
}
|
|
167
|
+
function customBuildDir(dataDir, file) {
|
|
168
|
+
const readable = file.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 64);
|
|
169
|
+
const hash = (0, import_crypto.createHash)("sha1").update(file).digest("hex").slice(0, 8);
|
|
170
|
+
return path.join(customRootDir(dataDir), `${readable}__${hash}`);
|
|
171
|
+
}
|
|
172
|
+
async function unpackUploadedTarball(tgzPath, dataDir, log, source) {
|
|
173
|
+
if (!fs.existsSync(tgzPath)) {
|
|
174
|
+
throw new Error(`uploaded build archive not found at ${tgzPath}`);
|
|
175
|
+
}
|
|
176
|
+
const targetDir = customBuildDir(dataDir, source.file);
|
|
177
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
178
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
179
|
+
log.info(`unpacking uploaded CometVisu build "${source.file}"`);
|
|
180
|
+
await extractTarball(tgzPath, targetDir);
|
|
181
|
+
const htmlRoot = findHtmlRoot(targetDir);
|
|
182
|
+
if (htmlRoot === targetDir && !fs.existsSync(path.join(targetDir, "index.html"))) {
|
|
183
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
184
|
+
throw new Error("the uploaded archive does not contain a CometVisu build (no index.html found)");
|
|
185
|
+
}
|
|
186
|
+
fs.writeFileSync(path.join(targetDir, ".complete"), (/* @__PURE__ */ new Date()).toISOString());
|
|
187
|
+
fs.writeFileSync(path.join(targetDir, ".source"), JSON.stringify(source));
|
|
188
|
+
log.info(`uploaded CometVisu build unpacked to ${targetDir}`);
|
|
189
|
+
return { tag: source.file, htmlRoot };
|
|
190
|
+
}
|
|
191
|
+
function readSource(targetDir) {
|
|
192
|
+
const sourceFile = path.join(targetDir, ".source");
|
|
193
|
+
if (!fs.existsSync(sourceFile)) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const content = fs.readFileSync(sourceFile, "utf8").trim();
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(content);
|
|
199
|
+
return typeof (parsed == null ? void 0 : parsed.file) === "string" ? parsed : null;
|
|
200
|
+
} catch {
|
|
201
|
+
return content ? { file: content } : null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function readCustomBuild(dataDir, file) {
|
|
205
|
+
const targetDir = customBuildDir(dataDir, file);
|
|
206
|
+
if (!fs.existsSync(path.join(targetDir, ".complete"))) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return { htmlRoot: findHtmlRoot(targetDir), source: readSource(targetDir) };
|
|
210
|
+
}
|
|
211
|
+
function listCustomBuilds(dataDir) {
|
|
212
|
+
const root = customRootDir(dataDir);
|
|
213
|
+
if (!fs.existsSync(root)) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
return fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => {
|
|
217
|
+
var _a, _b;
|
|
218
|
+
const dir = path.join(root, entry.name);
|
|
219
|
+
return { dir, file: (_b = (_a = readSource(dir)) == null ? void 0 : _a.file) != null ? _b : null };
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function removeCustomBuild(dataDir, file) {
|
|
223
|
+
const targetDir = customBuildDir(dataDir, file);
|
|
224
|
+
if (!fs.existsSync(targetDir)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function removeLegacyCustomBuild(dataDir) {
|
|
231
|
+
const legacyDir = path.join(dataDir, "cometvisu", CUSTOM_VERSION);
|
|
232
|
+
if (!fs.existsSync(legacyDir)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
function pruneReleaseBuilds(dataDir, keepTag) {
|
|
239
|
+
const root = path.join(dataDir, "cometvisu");
|
|
240
|
+
if (!fs.existsSync(root)) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
const keep = /* @__PURE__ */ new Set([CUSTOM_VERSION, "custom", keepTag]);
|
|
244
|
+
const removed = [];
|
|
245
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
246
|
+
if (!entry.isDirectory() || keep.has(entry.name)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
fs.rmSync(path.join(root, entry.name), { recursive: true, force: true });
|
|
250
|
+
removed.push(entry.name);
|
|
251
|
+
}
|
|
252
|
+
return removed;
|
|
253
|
+
}
|
|
254
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
255
|
+
0 && (module.exports = {
|
|
256
|
+
CUSTOM_FILE_PREFIX,
|
|
257
|
+
CUSTOM_PREFIX,
|
|
258
|
+
CUSTOM_VERSION,
|
|
259
|
+
OFFICIAL_FILE_PREFIX,
|
|
260
|
+
customBuildDir,
|
|
261
|
+
ensureRelease,
|
|
262
|
+
findHtmlRoot,
|
|
263
|
+
listCustomBuilds,
|
|
264
|
+
pruneReleaseBuilds,
|
|
265
|
+
readCustomBuild,
|
|
266
|
+
removeCustomBuild,
|
|
267
|
+
removeLegacyCustomBuild,
|
|
268
|
+
resolveVersionSelection,
|
|
269
|
+
unpackUploadedTarball
|
|
270
|
+
});
|
|
271
|
+
//# sourceMappingURL=releases.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/releases.ts"],
|
|
4
|
+
"sourcesContent": ["// Lists CometVisu GitHub releases and downloads/unpacks the selected one into the instance\n// data directory, so the webserver can serve it.\n\nimport axios from 'axios';\nimport { createHash } from 'crypto';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as tar from 'tar';\n\nconst REPO = 'CometVisu/CometVisu';\nconst RELEASES_URL = `https://api.github.com/repos/${REPO}/releases?per_page=100`;\n\n/** Directory (and legacy version value) for a build the user uploaded instead of a GitHub release. */\nexport const CUSTOM_VERSION = '__custom__';\n/** Legacy version values for uploaded builds were \"__custom__:<file name>\". */\nexport const CUSTOM_PREFIX = `${CUSTOM_VERSION}:`;\n\n// The admin shows one list: the archives the user uploaded (they are the only files in the\n// instance's \"files\" meta object) and the GitHub releases, which the admin component fetches\n// itself. The configured value carries the prefix of the entry that was picked.\n/** Value prefix for an archive the user uploaded; the rest is the file name as stored. */\nexport const CUSTOM_FILE_PREFIX = '[Custom] ';\n/** Value prefix for a GitHub release; the rest is the tag. */\nexport const OFFICIAL_FILE_PREFIX = '[Official] ';\n\n// values written by earlier adapter versions\nconst LEGACY_CUSTOM_PREFIX = 'Custom: ';\nconst LEGACY_OFFICIAL_PREFIX = 'Official: ';\nconst LEGACY_LATEST_LABEL = 'latest release';\n\n/** What a configured version value refers to. */\nexport type VersionSelection = { kind: 'custom'; file: string } | { kind: 'release'; tag: string };\n\n/**\n * Resolve the configured version to either an uploaded archive or a GitHub release tag. The values\n * written by earlier adapter versions are still accepted, so an existing instance keeps working.\n *\n * @param value the configured version value\n * @param legacyBuildUpload file name from the former separate upload field, used by the bare\n * \"__custom__\" legacy value\n */\nexport function resolveVersionSelection(value: string, legacyBuildUpload?: string): VersionSelection {\n if (value.startsWith(CUSTOM_FILE_PREFIX)) {\n return { kind: 'custom', file: value.slice(CUSTOM_FILE_PREFIX.length) };\n }\n if (value.startsWith(OFFICIAL_FILE_PREFIX)) {\n return { kind: 'release', tag: value.slice(OFFICIAL_FILE_PREFIX.length) };\n }\n // legacy values\n if (value.startsWith(LEGACY_CUSTOM_PREFIX)) {\n // those uploads were stored including the prefix\n return { kind: 'custom', file: value };\n }\n if (value.startsWith(LEGACY_OFFICIAL_PREFIX)) {\n const tag = value.slice(LEGACY_OFFICIAL_PREFIX.length);\n return { kind: 'release', tag: tag === LEGACY_LATEST_LABEL ? '' : tag };\n }\n if (value.startsWith(CUSTOM_PREFIX)) {\n return { kind: 'custom', file: value.slice(CUSTOM_PREFIX.length) };\n }\n if (value === CUSTOM_VERSION) {\n return { kind: 'custom', file: legacyBuildUpload || '' };\n }\n return { kind: 'release', tag: value };\n}\n\ninterface Asset {\n name: string;\n browser_download_url: string;\n size: number;\n}\ninterface Release {\n tag_name: string;\n name: string;\n prerelease: boolean;\n assets: Asset[];\n}\n\nfunction githubHeaders(): Record<string, string> {\n return {\n Accept: 'application/vnd.github+json',\n 'User-Agent': 'iobroker.cometvisu',\n };\n}\n\n/**\n * The CometVisu build archive of a release, e.g. \"CometVisu-v0.12.6.tar.gz\".\n *\n * @param release the GitHub release to inspect\n */\nfunction buildAsset(release: Release): Asset | undefined {\n const exact = `cometvisu-${release.tag_name.toLowerCase()}.tar.gz`;\n return (\n release.assets.find(a => a.name.toLowerCase() === exact) ||\n release.assets.find(a => /^CometVisu-.*\\.tar\\.gz$/i.test(a.name))\n );\n}\n\nasync function fetchReleases(): Promise<Release[]> {\n const res = await axios.get<Release[]>(RELEASES_URL, { headers: githubHeaders(), timeout: 20000 });\n // only releases that actually ship a built archive can be served\n return res.data.filter(r => !!buildAsset(r));\n}\n\nasync function resolveRelease(version: string): Promise<Release> {\n const releases = await fetchReleases();\n if (!releases.length) {\n throw new Error('no CometVisu release with a build archive was found');\n }\n if (!version) {\n // latest: newest stable, or newest overall if there is no stable release\n return releases.find(r => !r.prerelease) || releases[0];\n }\n const release = releases.find(r => r.tag_name === version);\n if (!release) {\n throw new Error(`CometVisu release \"${version}\" not found`);\n }\n return release;\n}\n\n/**\n * Finds the directory to serve inside the extracted archive. CometVisu ships its app under a\n * \"cometvisu/release/\" sub path, so this returns the shallowest directory that holds index.html.\n *\n * @param dir the directory the archive was extracted into\n */\nexport function findHtmlRoot(dir: string): string {\n // breadth-first search so the app root (\"release/\") wins over deeper index.html files\n let level = [dir];\n for (let depth = 0; depth < 5 && level.length; depth++) {\n const next: string[] = [];\n for (const current of level) {\n if (fs.existsSync(path.join(current, 'index.html'))) {\n return current;\n }\n for (const entry of fs.readdirSync(current, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n next.push(path.join(current, entry.name));\n }\n }\n }\n level = next;\n }\n return dir;\n}\n\n/**\n * Make sure the requested version is available locally and return the directory to serve.\n * Downloads and unpacks the release once, then reuses the cached copy.\n *\n * @param dataDir the instance data directory to unpack into\n * @param version the release tag to serve, '' for the latest release\n * @param log the adapter logger\n */\nexport async function ensureRelease(\n dataDir: string,\n version: string,\n log: ioBroker.Logger,\n): Promise<{ tag: string; htmlRoot: string }> {\n const release = await resolveRelease(version);\n const targetDir = path.join(dataDir, 'cometvisu', release.tag_name);\n const marker = path.join(targetDir, '.complete');\n\n if (fs.existsSync(marker)) {\n log.debug(`CometVisu ${release.tag_name} is already present`);\n return { tag: release.tag_name, htmlRoot: findHtmlRoot(targetDir) };\n }\n\n const asset = buildAsset(release);\n if (!asset) {\n throw new Error(`release ${release.tag_name} has no CometVisu build archive`);\n }\n\n // a partial previous download must not be served\n fs.rmSync(targetDir, { recursive: true, force: true });\n fs.mkdirSync(targetDir, { recursive: true });\n\n log.info(`downloading CometVisu ${release.tag_name} (${asset.name}, ${Math.round(asset.size / 1048576)} MB)`);\n const res = await axios.get<NodeJS.ReadableStream>(asset.browser_download_url, {\n headers: githubHeaders(),\n responseType: 'stream',\n timeout: 120000,\n });\n\n await extractTarball(res.data, targetDir);\n\n fs.writeFileSync(marker, new Date().toISOString());\n log.info(`CometVisu ${release.tag_name} unpacked to ${targetDir}`);\n return { tag: release.tag_name, htmlRoot: findHtmlRoot(targetDir) };\n}\n\n/**\n * Extract a .tar.gz into targetDir. node-tar auto-detects the gzip, so both a download stream and a\n * path to a local .tgz can be handed in.\n *\n * @param source a readable .tar.gz stream or the path to a local .tar.gz file\n * @param targetDir the directory to extract into (must already exist)\n */\nfunction extractTarball(source: NodeJS.ReadableStream | string, targetDir: string): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const extract = tar.x({ cwd: targetDir });\n const input: NodeJS.ReadableStream = typeof source === 'string' ? fs.createReadStream(source) : source;\n input.on('error', reject);\n extract.on('error', reject);\n extract.on('finish', () => resolve());\n input.pipe(extract);\n });\n}\n\n/** What an uploaded archive was when it was unpacked, so a re-upload can be told apart. */\nexport interface CustomBuildSource {\n /** name of the uploaded archive in the instance's file storage */\n file: string;\n /** byte size of that archive, unknown for builds of an older adapter version */\n size?: number;\n /** upload time of that archive, unknown for builds of an older adapter version */\n modifiedAt?: number;\n}\n\n/**\n * Directory that holds one sub directory per uploaded build.\n *\n * @param dataDir the instance data directory\n */\nfunction customRootDir(dataDir: string): string {\n return path.join(dataDir, 'cometvisu', 'custom');\n}\n\n/**\n * The directory an uploaded archive is unpacked into. Every upload gets its own one, so several\n * uploaded builds can exist side by side and switching between them needs no unpacking. The name is\n * derived from the archive's file name, which makes an upload under the same name replace exactly\n * that build. The hash keeps names apart that only the sanitizing would make equal (\"a b.tgz\" and\n * \"a_b.tgz\").\n *\n * @param dataDir the instance data directory\n * @param file name of the uploaded archive\n */\nexport function customBuildDir(dataDir: string, file: string): string {\n const readable = file.replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 64);\n const hash = createHash('sha1').update(file).digest('hex').slice(0, 8);\n return path.join(customRootDir(dataDir), `${readable}__${hash}`);\n}\n\n/**\n * Unpack a CometVisu build the user uploaded through the admin into its own directory and return the\n * directory to serve. Behaves like {@link ensureRelease}, only the source is a local file.\n *\n * @param tgzPath path to the uploaded .tar.gz file\n * @param dataDir the instance data directory to unpack into\n * @param log the adapter logger\n * @param source describes the uploaded archive, stored so a later start can tell whether this build\n * still matches the archive in the file storage\n */\nexport async function unpackUploadedTarball(\n tgzPath: string,\n dataDir: string,\n log: ioBroker.Logger,\n source: CustomBuildSource,\n): Promise<{ tag: string; htmlRoot: string }> {\n if (!fs.existsSync(tgzPath)) {\n throw new Error(`uploaded build archive not found at ${tgzPath}`);\n }\n const targetDir = customBuildDir(dataDir, source.file);\n\n // a previous build of the same archive must be replaced completely\n fs.rmSync(targetDir, { recursive: true, force: true });\n fs.mkdirSync(targetDir, { recursive: true });\n\n log.info(`unpacking uploaded CometVisu build \"${source.file}\"`);\n await extractTarball(tgzPath, targetDir);\n\n const htmlRoot = findHtmlRoot(targetDir);\n if (htmlRoot === targetDir && !fs.existsSync(path.join(targetDir, 'index.html'))) {\n fs.rmSync(targetDir, { recursive: true, force: true });\n throw new Error('the uploaded archive does not contain a CometVisu build (no index.html found)');\n }\n\n fs.writeFileSync(path.join(targetDir, '.complete'), new Date().toISOString());\n fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify(source));\n log.info(`uploaded CometVisu build unpacked to ${targetDir}`);\n return { tag: source.file, htmlRoot };\n}\n\n/**\n * Read the \".source\" marker of an unpacked build. Older adapter versions stored the plain file name,\n * which is still understood - the missing size and time then simply mean \"unknown\".\n *\n * @param targetDir directory of the unpacked build\n */\nfunction readSource(targetDir: string): CustomBuildSource | null {\n const sourceFile = path.join(targetDir, '.source');\n if (!fs.existsSync(sourceFile)) {\n return null;\n }\n const content = fs.readFileSync(sourceFile, 'utf8').trim();\n try {\n const parsed = JSON.parse(content);\n return typeof parsed?.file === 'string' ? (parsed as CustomBuildSource) : null;\n } catch {\n return content ? { file: content } : null;\n }\n}\n\n/**\n * Directory + source info of the build unpacked from a given archive, or null if it is not unpacked.\n *\n * @param dataDir the instance data directory\n * @param file name of the uploaded archive\n */\nexport function readCustomBuild(\n dataDir: string,\n file: string,\n): { htmlRoot: string; source: CustomBuildSource | null } | null {\n const targetDir = customBuildDir(dataDir, file);\n if (!fs.existsSync(path.join(targetDir, '.complete'))) {\n return null;\n }\n return { htmlRoot: findHtmlRoot(targetDir), source: readSource(targetDir) };\n}\n\n/**\n * All unpacked builds with the archive each one was made from, used to drop those whose archive was\n * deleted in the admin.\n *\n * @param dataDir the instance data directory\n */\nexport function listCustomBuilds(dataDir: string): { dir: string; file: string | null }[] {\n const root = customRootDir(dataDir);\n if (!fs.existsSync(root)) {\n return [];\n }\n return fs\n .readdirSync(root, { withFileTypes: true })\n .filter(entry => entry.isDirectory())\n .map(entry => {\n const dir = path.join(root, entry.name);\n return { dir, file: readSource(dir)?.file ?? null };\n });\n}\n\n/**\n * Remove the build that was unpacked from a given archive.\n *\n * @param dataDir the instance data directory\n * @param file name of the uploaded archive\n */\nexport function removeCustomBuild(dataDir: string, file: string): boolean {\n const targetDir = customBuildDir(dataDir, file);\n if (!fs.existsSync(targetDir)) {\n return false;\n }\n fs.rmSync(targetDir, { recursive: true, force: true });\n return true;\n}\n\n/**\n * Remove the single directory all uploads shared before every upload got its own one.\n *\n * @param dataDir the instance data directory\n */\nexport function removeLegacyCustomBuild(dataDir: string): boolean {\n const legacyDir = path.join(dataDir, 'cometvisu', CUSTOM_VERSION);\n if (!fs.existsSync(legacyDir)) {\n return false;\n }\n fs.rmSync(legacyDir, { recursive: true, force: true });\n return true;\n}\n\n/**\n * Drop every unpacked release except the one that is served. A release is not kept as an archive but\n * as its unpacked directory, so trying out versions would pile them up without this.\n *\n * @param dataDir the instance data directory\n * @param keepTag tag of the release to keep, null when an uploaded build is served\n * @returns the tags whose directories were removed\n */\nexport function pruneReleaseBuilds(dataDir: string, keepTag: string | null): string[] {\n const root = path.join(dataDir, 'cometvisu');\n if (!fs.existsSync(root)) {\n return [];\n }\n // the uploaded builds live next to the releases and are kept\n const keep = new Set([CUSTOM_VERSION, 'custom', keepTag]);\n const removed: string[] = [];\n for (const entry of fs.readdirSync(root, { withFileTypes: true })) {\n if (!entry.isDirectory() || keep.has(entry.name)) {\n continue;\n }\n fs.rmSync(path.join(root, entry.name), { recursive: true, force: true });\n removed.push(entry.name);\n }\n return removed;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAAkB;AAClB,oBAA2B;AAC3B,SAAoB;AACpB,WAAsB;AACtB,UAAqB;AAErB,MAAM,OAAO;AACb,MAAM,eAAe,gCAAgC,IAAI;AAGlD,MAAM,iBAAiB;AAEvB,MAAM,gBAAgB,GAAG,cAAc;AAMvC,MAAM,qBAAqB;AAE3B,MAAM,uBAAuB;AAGpC,MAAM,uBAAuB;AAC7B,MAAM,yBAAyB;AAC/B,MAAM,sBAAsB;AAarB,SAAS,wBAAwB,OAAe,mBAA8C;AACjG,MAAI,MAAM,WAAW,kBAAkB,GAAG;AACtC,WAAO,EAAE,MAAM,UAAU,MAAM,MAAM,MAAM,mBAAmB,MAAM,EAAE;AAAA,EAC1E;AACA,MAAI,MAAM,WAAW,oBAAoB,GAAG;AACxC,WAAO,EAAE,MAAM,WAAW,KAAK,MAAM,MAAM,qBAAqB,MAAM,EAAE;AAAA,EAC5E;AAEA,MAAI,MAAM,WAAW,oBAAoB,GAAG;AAExC,WAAO,EAAE,MAAM,UAAU,MAAM,MAAM;AAAA,EACzC;AACA,MAAI,MAAM,WAAW,sBAAsB,GAAG;AAC1C,UAAM,MAAM,MAAM,MAAM,uBAAuB,MAAM;AACrD,WAAO,EAAE,MAAM,WAAW,KAAK,QAAQ,sBAAsB,KAAK,IAAI;AAAA,EAC1E;AACA,MAAI,MAAM,WAAW,aAAa,GAAG;AACjC,WAAO,EAAE,MAAM,UAAU,MAAM,MAAM,MAAM,cAAc,MAAM,EAAE;AAAA,EACrE;AACA,MAAI,UAAU,gBAAgB;AAC1B,WAAO,EAAE,MAAM,UAAU,MAAM,qBAAqB,GAAG;AAAA,EAC3D;AACA,SAAO,EAAE,MAAM,WAAW,KAAK,MAAM;AACzC;AAcA,SAAS,gBAAwC;AAC7C,SAAO;AAAA,IACH,QAAQ;AAAA,IACR,cAAc;AAAA,EAClB;AACJ;AAOA,SAAS,WAAW,SAAqC;AACrD,QAAM,QAAQ,aAAa,QAAQ,SAAS,YAAY,CAAC;AACzD,SACI,QAAQ,OAAO,KAAK,OAAK,EAAE,KAAK,YAAY,MAAM,KAAK,KACvD,QAAQ,OAAO,KAAK,OAAK,2BAA2B,KAAK,EAAE,IAAI,CAAC;AAExE;AAEA,eAAe,gBAAoC;AAC/C,QAAM,MAAM,MAAM,aAAAA,QAAM,IAAe,cAAc,EAAE,SAAS,cAAc,GAAG,SAAS,IAAM,CAAC;AAEjG,SAAO,IAAI,KAAK,OAAO,OAAK,CAAC,CAAC,WAAW,CAAC,CAAC;AAC/C;AAEA,eAAe,eAAe,SAAmC;AAC7D,QAAM,WAAW,MAAM,cAAc;AACrC,MAAI,CAAC,SAAS,QAAQ;AAClB,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACzE;AACA,MAAI,CAAC,SAAS;AAEV,WAAO,SAAS,KAAK,OAAK,CAAC,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EAC1D;AACA,QAAM,UAAU,SAAS,KAAK,OAAK,EAAE,aAAa,OAAO;AACzD,MAAI,CAAC,SAAS;AACV,UAAM,IAAI,MAAM,sBAAsB,OAAO,aAAa;AAAA,EAC9D;AACA,SAAO;AACX;AAQO,SAAS,aAAa,KAAqB;AAE9C,MAAI,QAAQ,CAAC,GAAG;AAChB,WAAS,QAAQ,GAAG,QAAQ,KAAK,MAAM,QAAQ,SAAS;AACpD,UAAM,OAAiB,CAAC;AACxB,eAAW,WAAW,OAAO;AACzB,UAAI,GAAG,WAAW,KAAK,KAAK,SAAS,YAAY,CAAC,GAAG;AACjD,eAAO;AAAA,MACX;AACA,iBAAW,SAAS,GAAG,YAAY,SAAS,EAAE,eAAe,KAAK,CAAC,GAAG;AAClE,YAAI,MAAM,YAAY,GAAG;AACrB,eAAK,KAAK,KAAK,KAAK,SAAS,MAAM,IAAI,CAAC;AAAA,QAC5C;AAAA,MACJ;AAAA,IACJ;AACA,YAAQ;AAAA,EACZ;AACA,SAAO;AACX;AAUA,eAAsB,cAClB,SACA,SACA,KAC0C;AAC1C,QAAM,UAAU,MAAM,eAAe,OAAO;AAC5C,QAAM,YAAY,KAAK,KAAK,SAAS,aAAa,QAAQ,QAAQ;AAClE,QAAM,SAAS,KAAK,KAAK,WAAW,WAAW;AAE/C,MAAI,GAAG,WAAW,MAAM,GAAG;AACvB,QAAI,MAAM,aAAa,QAAQ,QAAQ,qBAAqB;AAC5D,WAAO,EAAE,KAAK,QAAQ,UAAU,UAAU,aAAa,SAAS,EAAE;AAAA,EACtE;AAEA,QAAM,QAAQ,WAAW,OAAO;AAChC,MAAI,CAAC,OAAO;AACR,UAAM,IAAI,MAAM,WAAW,QAAQ,QAAQ,iCAAiC;AAAA,EAChF;AAGA,KAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,KAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAE3C,MAAI,KAAK,yBAAyB,QAAQ,QAAQ,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,MAAM,OAAO,OAAO,CAAC,MAAM;AAC5G,QAAM,MAAM,MAAM,aAAAA,QAAM,IAA2B,MAAM,sBAAsB;AAAA,IAC3E,SAAS,cAAc;AAAA,IACvB,cAAc;AAAA,IACd,SAAS;AAAA,EACb,CAAC;AAED,QAAM,eAAe,IAAI,MAAM,SAAS;AAExC,KAAG,cAAc,SAAQ,oBAAI,KAAK,GAAE,YAAY,CAAC;AACjD,MAAI,KAAK,aAAa,QAAQ,QAAQ,gBAAgB,SAAS,EAAE;AACjE,SAAO,EAAE,KAAK,QAAQ,UAAU,UAAU,aAAa,SAAS,EAAE;AACtE;AASA,SAAS,eAAe,QAAwC,WAAkC;AAC9F,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,UAAM,UAAU,IAAI,EAAE,EAAE,KAAK,UAAU,CAAC;AACxC,UAAM,QAA+B,OAAO,WAAW,WAAW,GAAG,iBAAiB,MAAM,IAAI;AAChG,UAAM,GAAG,SAAS,MAAM;AACxB,YAAQ,GAAG,SAAS,MAAM;AAC1B,YAAQ,GAAG,UAAU,MAAM,QAAQ,CAAC;AACpC,UAAM,KAAK,OAAO;AAAA,EACtB,CAAC;AACL;AAiBA,SAAS,cAAc,SAAyB;AAC5C,SAAO,KAAK,KAAK,SAAS,aAAa,QAAQ;AACnD;AAYO,SAAS,eAAe,SAAiB,MAAsB;AAClE,QAAM,WAAW,KAAK,QAAQ,oBAAoB,GAAG,EAAE,MAAM,GAAG,EAAE;AAClE,QAAM,WAAO,0BAAW,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,CAAC;AACrE,SAAO,KAAK,KAAK,cAAc,OAAO,GAAG,GAAG,QAAQ,KAAK,IAAI,EAAE;AACnE;AAYA,eAAsB,sBAClB,SACA,SACA,KACA,QAC0C;AAC1C,MAAI,CAAC,GAAG,WAAW,OAAO,GAAG;AACzB,UAAM,IAAI,MAAM,uCAAuC,OAAO,EAAE;AAAA,EACpE;AACA,QAAM,YAAY,eAAe,SAAS,OAAO,IAAI;AAGrD,KAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,KAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAE3C,MAAI,KAAK,uCAAuC,OAAO,IAAI,GAAG;AAC9D,QAAM,eAAe,SAAS,SAAS;AAEvC,QAAM,WAAW,aAAa,SAAS;AACvC,MAAI,aAAa,aAAa,CAAC,GAAG,WAAW,KAAK,KAAK,WAAW,YAAY,CAAC,GAAG;AAC9E,OAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,UAAM,IAAI,MAAM,+EAA+E;AAAA,EACnG;AAEA,KAAG,cAAc,KAAK,KAAK,WAAW,WAAW,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC;AAC5E,KAAG,cAAc,KAAK,KAAK,WAAW,SAAS,GAAG,KAAK,UAAU,MAAM,CAAC;AACxE,MAAI,KAAK,wCAAwC,SAAS,EAAE;AAC5D,SAAO,EAAE,KAAK,OAAO,MAAM,SAAS;AACxC;AAQA,SAAS,WAAW,WAA6C;AAC7D,QAAM,aAAa,KAAK,KAAK,WAAW,SAAS;AACjD,MAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC5B,WAAO;AAAA,EACX;AACA,QAAM,UAAU,GAAG,aAAa,YAAY,MAAM,EAAE,KAAK;AACzD,MAAI;AACA,UAAM,SAAS,KAAK,MAAM,OAAO;AACjC,WAAO,QAAO,iCAAQ,UAAS,WAAY,SAA+B;AAAA,EAC9E,QAAQ;AACJ,WAAO,UAAU,EAAE,MAAM,QAAQ,IAAI;AAAA,EACzC;AACJ;AAQO,SAAS,gBACZ,SACA,MAC6D;AAC7D,QAAM,YAAY,eAAe,SAAS,IAAI;AAC9C,MAAI,CAAC,GAAG,WAAW,KAAK,KAAK,WAAW,WAAW,CAAC,GAAG;AACnD,WAAO;AAAA,EACX;AACA,SAAO,EAAE,UAAU,aAAa,SAAS,GAAG,QAAQ,WAAW,SAAS,EAAE;AAC9E;AAQO,SAAS,iBAAiB,SAAyD;AACtF,QAAM,OAAO,cAAc,OAAO;AAClC,MAAI,CAAC,GAAG,WAAW,IAAI,GAAG;AACtB,WAAO,CAAC;AAAA,EACZ;AACA,SAAO,GACF,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC,EACzC,OAAO,WAAS,MAAM,YAAY,CAAC,EACnC,IAAI,WAAS;AA/UtB;AAgVY,UAAM,MAAM,KAAK,KAAK,MAAM,MAAM,IAAI;AACtC,WAAO,EAAE,KAAK,OAAM,sBAAW,GAAG,MAAd,mBAAiB,SAAjB,YAAyB,KAAK;AAAA,EACtD,CAAC;AACT;AAQO,SAAS,kBAAkB,SAAiB,MAAuB;AACtE,QAAM,YAAY,eAAe,SAAS,IAAI;AAC9C,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC3B,WAAO;AAAA,EACX;AACA,KAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,SAAO;AACX;AAOO,SAAS,wBAAwB,SAA0B;AAC9D,QAAM,YAAY,KAAK,KAAK,SAAS,aAAa,cAAc;AAChE,MAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC3B,WAAO;AAAA,EACX;AACA,KAAG,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,SAAO;AACX;AAUO,SAAS,mBAAmB,SAAiB,SAAkC;AAClF,QAAM,OAAO,KAAK,KAAK,SAAS,WAAW;AAC3C,MAAI,CAAC,GAAG,WAAW,IAAI,GAAG;AACtB,WAAO,CAAC;AAAA,EACZ;AAEA,QAAM,OAAO,oBAAI,IAAI,CAAC,gBAAgB,UAAU,OAAO,CAAC;AACxD,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,GAAG,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC,GAAG;AAC/D,QAAI,CAAC,MAAM,YAAY,KAAK,KAAK,IAAI,MAAM,IAAI,GAAG;AAC9C;AAAA,IACJ;AACA,OAAG,OAAO,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACvE,YAAQ,KAAK,MAAM,IAAI;AAAA,EAC3B;AACA,SAAO;AACX;",
|
|
6
|
+
"names": ["axios"]
|
|
7
|
+
}
|
package/build/lib/web.js
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var web_exports = {};
|
|
30
|
+
__export(web_exports, {
|
|
31
|
+
default: () => web_default,
|
|
32
|
+
web: () => web
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(web_exports);
|
|
35
|
+
var utils = __toESM(require("@iobroker/adapter-core"));
|
|
36
|
+
var import_express = __toESM(require("express"));
|
|
37
|
+
var fs = __toESM(require("fs"));
|
|
38
|
+
var path = __toESM(require("path"));
|
|
39
|
+
var import_releases = require("./releases");
|
|
40
|
+
var import_webSocket = require("./webSocket");
|
|
41
|
+
class web {
|
|
42
|
+
log;
|
|
43
|
+
/** namespace of our own instance, e.g. "cometvisu.0" */
|
|
44
|
+
namespace;
|
|
45
|
+
native;
|
|
46
|
+
/** path the visualisation is mounted under */
|
|
47
|
+
mountPath;
|
|
48
|
+
/** serves the current build, null while there is none to serve */
|
|
49
|
+
handler = null;
|
|
50
|
+
/** directory the current handler serves, null while there is nothing to serve */
|
|
51
|
+
servedRoot = null;
|
|
52
|
+
/** set once unload() ran, so a late request does not resurrect the handler */
|
|
53
|
+
unloaded = false;
|
|
54
|
+
/** whether the web instance offers a socket at all */
|
|
55
|
+
hasSocket = false;
|
|
56
|
+
/** port of an external socket adapter, null while the socket is on the port of the web adapter */
|
|
57
|
+
socketPort = null;
|
|
58
|
+
/** whether an external socket is reached over TLS */
|
|
59
|
+
socketSecure = false;
|
|
60
|
+
/**
|
|
61
|
+
* Mounts the visualisation into the web adapter's express app.
|
|
62
|
+
*
|
|
63
|
+
* @param _server the web adapter's http(s) server, unused
|
|
64
|
+
* @param _webSettings settings of the web instance, unused
|
|
65
|
+
* @param adapter the web adapter, used for logging
|
|
66
|
+
* @param instanceSettings our own instance object
|
|
67
|
+
* @param app express app of the web adapter
|
|
68
|
+
*/
|
|
69
|
+
constructor(_server, _webSettings, adapter, instanceSettings, app) {
|
|
70
|
+
this.log = adapter.log;
|
|
71
|
+
this.namespace = instanceSettings._id.substring("system.adapter.".length);
|
|
72
|
+
this.native = instanceSettings.native || {};
|
|
73
|
+
this.mountPath = this.namespace.endsWith(".0") ? "/cometvisu" : `/${this.namespace}`;
|
|
74
|
+
this.resolveSocket(adapter);
|
|
75
|
+
this.refresh();
|
|
76
|
+
app.use(this.mountPath, (req, res, next) => {
|
|
77
|
+
if (this.unloaded) {
|
|
78
|
+
next();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (req.originalUrl === this.mountPath) {
|
|
82
|
+
res.redirect(301, `${this.mountPath}/`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
res.setHeader("X-CometVisu-Backend-Name", "iobroker");
|
|
86
|
+
if (this.hasSocket) {
|
|
87
|
+
res.setHeader("X-CometVisu-Backend-IoBroker-Url", this.socketUrl(req));
|
|
88
|
+
}
|
|
89
|
+
if (!this.servedRoot || !fs.existsSync(this.servedRoot)) {
|
|
90
|
+
this.refresh();
|
|
91
|
+
}
|
|
92
|
+
if (!this.handler) {
|
|
93
|
+
res.status(503).send("No CometVisu build has been prepared yet - please check the adapter instance.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.handler(req, res, next);
|
|
97
|
+
});
|
|
98
|
+
this.log.info(`CometVisu is served by the web adapter under ${this.mountPath}/`);
|
|
99
|
+
}
|
|
100
|
+
// No welcomePage() on purpose: iobroker.web appends what the extensions return there *after* it
|
|
101
|
+
// has de-duplicated the list it built from common.localLinks, so an entry here would simply show
|
|
102
|
+
// up a second time. common.localLinks covers both the welcome screen and the link in the admin.
|
|
103
|
+
/**
|
|
104
|
+
* Pick up a version that was selected in the admin without restarting the web adapter.
|
|
105
|
+
*
|
|
106
|
+
* @param id id of the changed object
|
|
107
|
+
* @param obj the changed object
|
|
108
|
+
*/
|
|
109
|
+
objectChange(id, obj) {
|
|
110
|
+
if (id !== `system.adapter.${this.namespace}` || !obj) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const native = obj.native || {};
|
|
114
|
+
if (native.version === this.native.version && native.buildUpload === this.native.buildUpload) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.native.version = native.version;
|
|
118
|
+
this.native.buildUpload = native.buildUpload;
|
|
119
|
+
this.log.info(`CometVisu version changed to "${native.version || ""}"`);
|
|
120
|
+
this.refresh();
|
|
121
|
+
}
|
|
122
|
+
/** Called by the web adapter when our instance goes away. */
|
|
123
|
+
unload() {
|
|
124
|
+
this.unloaded = true;
|
|
125
|
+
this.handler = null;
|
|
126
|
+
this.servedRoot = null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Directory of the build the instance is configured to serve, or null when it is not unpacked
|
|
130
|
+
* (yet). Uses the same resolution as the adapter itself.
|
|
131
|
+
*/
|
|
132
|
+
resolveHtmlRoot() {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
const dataDir = utils.getAbsoluteInstanceDataDir(this.namespace);
|
|
135
|
+
const selection = (0, import_releases.resolveVersionSelection)(this.native.version || "", this.native.buildUpload);
|
|
136
|
+
if (selection.kind === "custom") {
|
|
137
|
+
return (_b = (_a = (0, import_releases.readCustomBuild)(dataDir, selection.file)) == null ? void 0 : _a.htmlRoot) != null ? _b : null;
|
|
138
|
+
}
|
|
139
|
+
const releaseDir = selection.tag ? path.join(dataDir, "cometvisu", selection.tag) : this.findSingleReleaseDir(dataDir);
|
|
140
|
+
return releaseDir && fs.existsSync(path.join(releaseDir, ".complete")) ? (0, import_releases.findHtmlRoot)(releaseDir) : null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The one unpacked release directory, ignoring the uploaded builds.
|
|
144
|
+
*
|
|
145
|
+
* @param dataDir the instance data directory
|
|
146
|
+
*/
|
|
147
|
+
findSingleReleaseDir(dataDir) {
|
|
148
|
+
const root = path.join(dataDir, "cometvisu");
|
|
149
|
+
if (!fs.existsSync(root)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const dirs = fs.readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "custom" && !entry.name.startsWith("__")).map((entry) => path.join(root, entry.name));
|
|
153
|
+
return dirs.length === 1 ? dirs[0] : null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Work out which socket CometVisu has to connect to. An external socket adapter runs on its own
|
|
157
|
+
* port, which is only known after reading its instance object - until that answer arrives, and
|
|
158
|
+
* if it never does, the origin of the request is used.
|
|
159
|
+
*
|
|
160
|
+
* @param adapter the web adapter we run in
|
|
161
|
+
*/
|
|
162
|
+
resolveSocket(adapter) {
|
|
163
|
+
const setup = (0, import_webSocket.classifyWebSocket)(adapter.config);
|
|
164
|
+
if (setup.kind === "none") {
|
|
165
|
+
this.log.error(
|
|
166
|
+
`${adapter.namespace} serves no socket ("none"), so CometVisu cannot reach ioBroker - configure a socket in that web instance`
|
|
167
|
+
);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.hasSocket = true;
|
|
171
|
+
if (setup.kind !== "external") {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
adapter.getForeignObjectAsync(setup.instance).then(
|
|
175
|
+
(obj) => {
|
|
176
|
+
const native = obj == null ? void 0 : obj.native;
|
|
177
|
+
if (!(native == null ? void 0 : native.port)) {
|
|
178
|
+
this.log.warn(`cannot read the port of ${setup.instance}, using the port of the web adapter`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.socketPort = native.port;
|
|
182
|
+
this.socketSecure = !!native.secure;
|
|
183
|
+
this.log.info(`CometVisu connects to ${setup.instance} on port ${native.port} (${setup.transport})`);
|
|
184
|
+
},
|
|
185
|
+
(e) => this.log.warn(`cannot read ${setup.instance}: ${e.message}`)
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The websocket URL to hand to CometVisu for this request.
|
|
190
|
+
*
|
|
191
|
+
* @param req the request being answered
|
|
192
|
+
*/
|
|
193
|
+
socketUrl(req) {
|
|
194
|
+
if (this.socketPort === null) {
|
|
195
|
+
return `${req.secure ? "wss" : "ws"}://${req.headers.host}/`;
|
|
196
|
+
}
|
|
197
|
+
const host = (req.headers.host || "").replace(/:\d+$/, "");
|
|
198
|
+
return `${this.socketSecure ? "wss" : "ws"}://${host}:${this.socketPort}/`;
|
|
199
|
+
}
|
|
200
|
+
/** Points the handler at the configured build, or drops it when there is none. */
|
|
201
|
+
refresh() {
|
|
202
|
+
const htmlRoot = this.resolveHtmlRoot();
|
|
203
|
+
if (!htmlRoot) {
|
|
204
|
+
if (this.servedRoot) {
|
|
205
|
+
this.log.warn(`the CometVisu build served for ${this.namespace} disappeared`);
|
|
206
|
+
}
|
|
207
|
+
this.servedRoot = null;
|
|
208
|
+
this.handler = null;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (htmlRoot === this.servedRoot && this.handler) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const router = import_express.default.Router();
|
|
215
|
+
const overlayDir = path.join(utils.getAbsoluteInstanceDataDir(this.namespace), "resource");
|
|
216
|
+
if (fs.existsSync(overlayDir)) {
|
|
217
|
+
router.use("/resource", import_express.default.static(overlayDir));
|
|
218
|
+
}
|
|
219
|
+
router.use(import_express.default.static(htmlRoot));
|
|
220
|
+
this.handler = router;
|
|
221
|
+
this.servedRoot = htmlRoot;
|
|
222
|
+
this.log.info(`CometVisu (${this.namespace}) serves ${htmlRoot}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var web_default = web;
|
|
226
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
227
|
+
0 && (module.exports = {
|
|
228
|
+
web
|
|
229
|
+
});
|
|
230
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/web.ts"],
|
|
4
|
+
"sourcesContent": ["// Web extension for iobroker.web: serves the CometVisu build through the web adapter, so its login,\n// session and socket protect and supply the visualisation. This module is loaded by iobroker.web\n// (common.webExtension in io-package.json) and runs inside that adapter's process - the download and\n// unpacking of a build stays in our own adapter process, this one only serves what is on disk.\n\nimport * as utils from '@iobroker/adapter-core';\nimport express from 'express';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { findHtmlRoot, readCustomBuild, resolveVersionSelection } from './releases';\nimport { type WebNative, classifyWebSocket } from './webSocket';\n\n/** The part of our instance object this extension needs. */\ninterface InstanceSettings {\n _id: string;\n native: {\n version?: string;\n buildUpload?: string;\n };\n}\n\n/** Settings iobroker.web hands to every extension. */\ninterface WebSettings {\n secure?: boolean;\n port?: number;\n language?: string;\n defaultUser?: string;\n auth?: boolean;\n}\n\n/**\n * Exported twice on purpose, because iobroker.web changed how it picks the class up: up to 7.0.8 it\n * only unwraps a default export, from 7.0.9 on it also looks for `module[<file name>]`. The class\n * name therefore has to stay `web`, and the default export below keeps the older loader working.\n */\nexport class web {\n private readonly log: ioBroker.Logger;\n /** namespace of our own instance, e.g. \"cometvisu.0\" */\n private readonly namespace: string;\n private readonly native: InstanceSettings['native'];\n /** path the visualisation is mounted under */\n private readonly mountPath: string;\n /** serves the current build, null while there is none to serve */\n private handler: express.RequestHandler | null = null;\n /** directory the current handler serves, null while there is nothing to serve */\n private servedRoot: string | null = null;\n /** set once unload() ran, so a late request does not resurrect the handler */\n private unloaded = false;\n /** whether the web instance offers a socket at all */\n private hasSocket = false;\n /** port of an external socket adapter, null while the socket is on the port of the web adapter */\n private socketPort: number | null = null;\n /** whether an external socket is reached over TLS */\n private socketSecure = false;\n\n /**\n * Mounts the visualisation into the web adapter's express app.\n *\n * @param _server the web adapter's http(s) server, unused\n * @param _webSettings settings of the web instance, unused\n * @param adapter the web adapter, used for logging\n * @param instanceSettings our own instance object\n * @param app express app of the web adapter\n */\n public constructor(\n _server: unknown,\n _webSettings: WebSettings,\n adapter: ioBroker.Adapter,\n instanceSettings: InstanceSettings,\n app: express.Express,\n ) {\n this.log = adapter.log;\n this.namespace = instanceSettings._id.substring('system.adapter.'.length);\n this.native = instanceSettings.native || {};\n // several instances must not fight over the same path\n this.mountPath = this.namespace.endsWith('.0') ? '/cometvisu' : `/${this.namespace}`;\n\n this.resolveSocket(adapter);\n this.refresh();\n // one stable mount point; what is behind it follows the configuration and the disk\n app.use(this.mountPath, (req, res, next) => {\n if (this.unloaded) {\n next();\n return;\n }\n // Without the trailing slash the browser would resolve every relative asset against the\n // parent path, so send it to the directory form first.\n if (req.originalUrl === this.mountPath) {\n res.redirect(301, `${this.mountPath}/`);\n return;\n }\n // Tell CometVisu which backend to connect to. The request reaches us through the web\n // adapter, so its origin - and with it its socket, login and session - is the right\n // one, unless the socket lives in another adapter on its own port. Which protocol that\n // socket speaks does not have to be told: CometVisu loads the matching client library\n // from /socket.io.js of that very server. This is only a default, the visu config wins.\n res.setHeader('X-CometVisu-Backend-Name', 'iobroker');\n if (this.hasSocket) {\n res.setHeader('X-CometVisu-Backend-IoBroker-Url', this.socketUrl(req));\n }\n\n // The adapter unpacks the build in its own process, which may still be running - or may\n // have replaced the build under the same name. Re-resolving whenever nothing is served\n // (or what was served disappeared) picks that up without restarting the web adapter.\n if (!this.servedRoot || !fs.existsSync(this.servedRoot)) {\n this.refresh();\n }\n if (!this.handler) {\n res.status(503).send('No CometVisu build has been prepared yet - please check the adapter instance.');\n return;\n }\n this.handler(req, res, next);\n });\n this.log.info(`CometVisu is served by the web adapter under ${this.mountPath}/`);\n }\n\n // No welcomePage() on purpose: iobroker.web appends what the extensions return there *after* it\n // has de-duplicated the list it built from common.localLinks, so an entry here would simply show\n // up a second time. common.localLinks covers both the welcome screen and the link in the admin.\n\n /**\n * Pick up a version that was selected in the admin without restarting the web adapter.\n *\n * @param id id of the changed object\n * @param obj the changed object\n */\n public objectChange(id: string, obj: ioBroker.Object | null | undefined): void {\n if (id !== `system.adapter.${this.namespace}` || !obj) {\n return;\n }\n const native = (obj as unknown as InstanceSettings).native || {};\n if (native.version === this.native.version && native.buildUpload === this.native.buildUpload) {\n return;\n }\n this.native.version = native.version;\n this.native.buildUpload = native.buildUpload;\n this.log.info(`CometVisu version changed to \"${native.version || ''}\"`);\n this.refresh();\n }\n\n /** Called by the web adapter when our instance goes away. */\n public unload(): void {\n // the mount stays in place, but stops handling anything\n this.unloaded = true;\n this.handler = null;\n this.servedRoot = null;\n }\n\n /**\n * Directory of the build the instance is configured to serve, or null when it is not unpacked\n * (yet). Uses the same resolution as the adapter itself.\n */\n private resolveHtmlRoot(): string | null {\n const dataDir = utils.getAbsoluteInstanceDataDir(this.namespace);\n const selection = resolveVersionSelection(this.native.version || '', this.native.buildUpload);\n\n if (selection.kind === 'custom') {\n return readCustomBuild(dataDir, selection.file)?.htmlRoot ?? null;\n }\n\n // A release is unpacked into a directory named after its tag. An empty tag comes from older\n // configurations (\"latest\"), where only the adapter knows which tag that resolved to - it\n // keeps just the release being served, so the single remaining directory is the right one.\n const releaseDir = selection.tag\n ? path.join(dataDir, 'cometvisu', selection.tag)\n : this.findSingleReleaseDir(dataDir);\n\n return releaseDir && fs.existsSync(path.join(releaseDir, '.complete')) ? findHtmlRoot(releaseDir) : null;\n }\n\n /**\n * The one unpacked release directory, ignoring the uploaded builds.\n *\n * @param dataDir the instance data directory\n */\n private findSingleReleaseDir(dataDir: string): string | null {\n const root = path.join(dataDir, 'cometvisu');\n if (!fs.existsSync(root)) {\n return null;\n }\n const dirs = fs\n .readdirSync(root, { withFileTypes: true })\n .filter(entry => entry.isDirectory() && entry.name !== 'custom' && !entry.name.startsWith('__'))\n .map(entry => path.join(root, entry.name));\n return dirs.length === 1 ? dirs[0] : null;\n }\n\n /**\n * Work out which socket CometVisu has to connect to. An external socket adapter runs on its own\n * port, which is only known after reading its instance object - until that answer arrives, and\n * if it never does, the origin of the request is used.\n *\n * @param adapter the web adapter we run in\n */\n private resolveSocket(adapter: ioBroker.Adapter): void {\n const setup = classifyWebSocket(adapter.config as WebNative);\n if (setup.kind === 'none') {\n this.log.error(\n `${adapter.namespace} serves no socket (\"none\"), so CometVisu cannot reach ioBroker - ` +\n 'configure a socket in that web instance',\n );\n return;\n }\n this.hasSocket = true;\n if (setup.kind !== 'external') {\n return;\n }\n adapter.getForeignObjectAsync(setup.instance).then(\n obj => {\n const native = obj?.native as { port?: number; secure?: boolean } | undefined;\n if (!native?.port) {\n this.log.warn(`cannot read the port of ${setup.instance}, using the port of the web adapter`);\n return;\n }\n this.socketPort = native.port;\n this.socketSecure = !!native.secure;\n this.log.info(`CometVisu connects to ${setup.instance} on port ${native.port} (${setup.transport})`);\n },\n (e: Error) => this.log.warn(`cannot read ${setup.instance}: ${e.message}`),\n );\n }\n\n /**\n * The websocket URL to hand to CometVisu for this request.\n *\n * @param req the request being answered\n */\n private socketUrl(req: express.Request): string {\n if (this.socketPort === null) {\n return `${req.secure ? 'wss' : 'ws'}://${req.headers.host}/`;\n }\n // same host as the request, but the port of the adapter that serves the socket\n const host = (req.headers.host || '').replace(/:\\d+$/, '');\n return `${this.socketSecure ? 'wss' : 'ws'}://${host}:${this.socketPort}/`;\n }\n\n /** Points the handler at the configured build, or drops it when there is none. */\n private refresh(): void {\n const htmlRoot = this.resolveHtmlRoot();\n if (!htmlRoot) {\n if (this.servedRoot) {\n this.log.warn(`the CometVisu build served for ${this.namespace} disappeared`);\n }\n this.servedRoot = null;\n this.handler = null;\n return;\n }\n if (htmlRoot === this.servedRoot && this.handler) {\n return;\n }\n\n const router = express.Router();\n // same overlay as the adapter's own web server: a user provided \"resource\" folder wins over\n // the one shipped with the build\n const overlayDir = path.join(utils.getAbsoluteInstanceDataDir(this.namespace), 'resource');\n if (fs.existsSync(overlayDir)) {\n router.use('/resource', express.static(overlayDir));\n }\n router.use(express.static(htmlRoot));\n\n this.handler = router;\n this.servedRoot = htmlRoot;\n this.log.info(`CometVisu (${this.namespace}) serves ${htmlRoot}`);\n }\n}\n\nexport default web;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["express"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var webSocket_exports = {};
|
|
20
|
+
__export(webSocket_exports, {
|
|
21
|
+
classifyWebSocket: () => classifyWebSocket
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(webSocket_exports);
|
|
24
|
+
function classifyWebSocket(native) {
|
|
25
|
+
const socketio = (native == null ? void 0 : native.socketio) || "";
|
|
26
|
+
if (socketio === "none") {
|
|
27
|
+
return { kind: "none" };
|
|
28
|
+
}
|
|
29
|
+
if (socketio.startsWith("system.adapter.")) {
|
|
30
|
+
return {
|
|
31
|
+
kind: "external",
|
|
32
|
+
transport: socketio.startsWith("system.adapter.ws.") ? "ws" : "socket.io",
|
|
33
|
+
instance: socketio
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { kind: "integrated", transport: (native == null ? void 0 : native.usePureWebSockets) ? "ws" : "socket.io" };
|
|
37
|
+
}
|
|
38
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
39
|
+
0 && (module.exports = {
|
|
40
|
+
classifyWebSocket
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=webSocket.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/webSocket.ts"],
|
|
4
|
+
"sourcesContent": ["// Which socket a web instance offers, and which protocol it speaks. CometVisu can talk both the raw\n// ws framing of @iobroker/ws and socket.io, but it has to be told which one - it cannot find out on its\n// own. The rules below are those of iobroker.web 9.1.4 (main.js, lines 556-575 and 2156-2176).\n\n/** Protocol a socket speaks, in the spelling CometVisu expects in the transport header. */\nexport type SocketTransport = 'ws' | 'socket.io';\n\n/** Where the socket of a web instance lives and what it speaks. */\nexport type WebSocketSetup =\n /** the web instance serves the socket on its own port */\n | { kind: 'integrated'; transport: SocketTransport }\n /** another adapter instance serves the socket on its own port */\n | { kind: 'external'; transport: SocketTransport; instance: string }\n /** the web instance has no socket at all */\n | { kind: 'none' };\n\n/** The part of a web instance's configuration that decides this. */\nexport interface WebNative {\n /** empty for the integrated socket, \"none\", or the id of a socket adapter instance */\n socketio?: string;\n /** whether the integrated socket speaks the raw `@iobroker/ws` framing */\n usePureWebSockets?: boolean;\n}\n\n/**\n * Work out which socket the given web instance offers.\n *\n * Note that `usePureWebSockets` is not part of the defaults of iobroker.web, so a freshly created\n * web instance ends up on socket.io.\n *\n * @param native native configuration of the web instance\n */\nexport function classifyWebSocket(native: WebNative | undefined | null): WebSocketSetup {\n const socketio = native?.socketio || '';\n\n if (socketio === 'none') {\n return { kind: 'none' };\n }\n if (socketio.startsWith('system.adapter.')) {\n // iobroker.ws speaks the raw framing, every other socket adapter speaks socket.io\n return {\n kind: 'external',\n transport: socketio.startsWith('system.adapter.ws.') ? 'ws' : 'socket.io',\n instance: socketio,\n };\n }\n return { kind: 'integrated', transport: native?.usePureWebSockets ? 'ws' : 'socket.io' };\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,SAAS,kBAAkB,QAAsD;AACpF,QAAM,YAAW,iCAAQ,aAAY;AAErC,MAAI,aAAa,QAAQ;AACrB,WAAO,EAAE,MAAM,OAAO;AAAA,EAC1B;AACA,MAAI,SAAS,WAAW,iBAAiB,GAAG;AAExC,WAAO;AAAA,MACH,MAAM;AAAA,MACN,WAAW,SAAS,WAAW,oBAAoB,IAAI,OAAO;AAAA,MAC9D,UAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO,EAAE,MAAM,cAAc,YAAW,iCAAQ,qBAAoB,OAAO,YAAY;AAC3F;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|