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.
Files changed (31) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/admin/cometvisu.png +0 -0
  4. package/admin/custom/assets/Components-CLNZhuIY.js +1 -0
  5. package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomCometVisu__mf_owner__1-C7tyeGL_.js +1 -0
  6. 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
  7. 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
  8. 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
  9. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomCometVisu__mf_owner__1__loadShare__react__loadShare__.js-Dmq5ji6n.js +1 -0
  10. package/admin/custom/assets/dist-DPGjdSM2.js +4 -0
  11. package/admin/custom/assets/hostInit-DOpe5t6n.js +1 -0
  12. package/admin/custom/assets/index-CnKGi9b5.js +1 -0
  13. package/admin/custom/assets/mf-entry-bootstrap-0-d9e9941a.js +41 -0
  14. package/admin/custom/assets/virtualExposes-B2dZqxIp.js +1 -0
  15. package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__ConfigCustomCometVisu__customComponents_js-Ck-YWfGN.js +1 -0
  16. package/admin/custom/assets/virtual_mf-exposes___mfe_internal__ConfigCustomCometVisu__customComponents_js-CDpFBOxm.js +2 -0
  17. package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
  18. package/admin/custom/customComponents.js +12 -0
  19. package/admin/i18n/de/translations.json +8 -0
  20. package/admin/i18n/en/translations.json +8 -0
  21. package/admin/jsonConfig.json +39 -0
  22. package/build/lib/releases.js +271 -0
  23. package/build/lib/releases.js.map +7 -0
  24. package/build/lib/web.js +230 -0
  25. package/build/lib/web.js.map +7 -0
  26. package/build/lib/webSocket.js +42 -0
  27. package/build/lib/webSocket.js.map +7 -0
  28. package/build/main.js +309 -0
  29. package/build/main.js.map +7 -0
  30. package/io-package.json +128 -0
  31. package/package.json +77 -0
package/build/main.js ADDED
@@ -0,0 +1,309 @@
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 __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ var utils = __toESM(require("@iobroker/adapter-core"));
25
+ var fs = __toESM(require("fs"));
26
+ var path = __toESM(require("path"));
27
+ var import_releases = require("./lib/releases");
28
+ var import_webSocket = require("./lib/webSocket");
29
+ const FORMER_OWN_SERVER_SETTINGS = [
30
+ "port",
31
+ "ip",
32
+ "findNextPort",
33
+ "secure",
34
+ "certPublic",
35
+ "certPrivate",
36
+ "certChained",
37
+ "auth",
38
+ "defaultUser",
39
+ "defaultUserPassword",
40
+ "ttl",
41
+ "ownServer"
42
+ ];
43
+ class Cometvisu extends utils.Adapter {
44
+ constructor(options = {}) {
45
+ super({ ...options, name: "cometvisu" });
46
+ this.on("ready", this.onReady.bind(this));
47
+ this.on("message", this.onMessage.bind(this));
48
+ this.on("unload", this.onUnload.bind(this));
49
+ }
50
+ async onReady() {
51
+ await this.setState("info.connection", { val: false, ack: true });
52
+ await this.dropFormerServerSettings();
53
+ const dataDir = utils.getAbsoluteInstanceDataDir(this);
54
+ await this.pruneCustomBuilds(dataDir);
55
+ let htmlRoot;
56
+ let servedTag = null;
57
+ try {
58
+ const selection = await this.resolveVersion();
59
+ if (selection.kind === "custom") {
60
+ htmlRoot = await this.prepareCustomBuild(dataDir, selection.file);
61
+ this.log.info(`uploaded CometVisu build prepared in ${htmlRoot}`);
62
+ } else {
63
+ const result = await (0, import_releases.ensureRelease)(dataDir, selection.tag, this.log);
64
+ htmlRoot = result.htmlRoot;
65
+ servedTag = result.tag;
66
+ this.log.info(`CometVisu ${result.tag} prepared in ${htmlRoot}`);
67
+ }
68
+ } catch (e) {
69
+ this.log.error(`could not prepare the CometVisu build: ${e instanceof Error ? e.message : String(e)}`);
70
+ return;
71
+ }
72
+ const dropped = (0, import_releases.pruneReleaseBuilds)(dataDir, servedTag);
73
+ if (dropped.length) {
74
+ this.log.info(`removed unpacked release(s) no longer in use: ${dropped.join(", ")}`);
75
+ }
76
+ fs.mkdirSync(path.join(dataDir, "resource"), { recursive: true });
77
+ if (!this.config.webInstance) {
78
+ this.log.error(
79
+ "the visualisation is served by the web adapter - please install it and select its instance in the settings"
80
+ );
81
+ return;
82
+ }
83
+ if (!await this.checkWebSocket()) {
84
+ return;
85
+ }
86
+ await this.setState("info.connection", { val: true, ack: true });
87
+ this.log.info(`the CometVisu build is ready, ${this.config.webInstance} serves it under /cometvisu`);
88
+ }
89
+ /**
90
+ * Remove the settings of the web server this adapter used to run itself. They stay in an
91
+ * instance that was created while they still existed, where they are misleading - the admin
92
+ * keeps showing a port that nothing listens on, and an encrypted password no code reads.
93
+ *
94
+ * The object is written back as a whole instead of extending it: whether extendObject deletes a
95
+ * single attribute is not something this could rely on. Changing our own configuration makes the
96
+ * controller restart the instance once; there is nothing left to do on the next start, so this
97
+ * just carries on afterwards.
98
+ */
99
+ async dropFormerServerSettings() {
100
+ const id = `system.adapter.${this.namespace}`;
101
+ let obj;
102
+ try {
103
+ obj = await this.getForeignObjectAsync(id);
104
+ } catch (e) {
105
+ this.log.warn(`cannot read ${id}: ${e instanceof Error ? e.message : String(e)}`);
106
+ return;
107
+ }
108
+ const native = obj == null ? void 0 : obj.native;
109
+ if (!native) {
110
+ return;
111
+ }
112
+ const found = FORMER_OWN_SERVER_SETTINGS.filter((key) => key in native);
113
+ if (!found.length) {
114
+ return;
115
+ }
116
+ for (const key of found) {
117
+ delete native[key];
118
+ }
119
+ try {
120
+ await this.setForeignObjectAsync(id, obj);
121
+ this.log.info(`removed settings of the former own web server: ${found.join(", ")}`);
122
+ } catch (e) {
123
+ this.log.warn(`cannot clean up ${id}: ${e instanceof Error ? e.message : String(e)}`);
124
+ }
125
+ }
126
+ /**
127
+ * Report which socket CometVisu will use, and say so clearly when the web instance offers none.
128
+ * The extension reads the same configuration in the process of the web adapter, but a CometVisu
129
+ * problem is looked for in this log, so the message belongs here as well.
130
+ */
131
+ async checkWebSocket() {
132
+ const instance = `system.adapter.${this.config.webInstance}`;
133
+ let obj;
134
+ try {
135
+ obj = await this.getForeignObjectAsync(instance);
136
+ } catch (e) {
137
+ this.log.warn(`cannot read ${instance}: ${e instanceof Error ? e.message : String(e)}`);
138
+ return true;
139
+ }
140
+ if (!obj) {
141
+ this.log.error(
142
+ `${instance} does not exist - the visualisation is served by the web adapter, please install it and select its instance in the settings`
143
+ );
144
+ return false;
145
+ }
146
+ const setup = (0, import_webSocket.classifyWebSocket)(obj.native);
147
+ if (setup.kind === "none") {
148
+ this.log.error(
149
+ `${this.config.webInstance} serves no socket ("none"), so CometVisu cannot reach ioBroker - configure a socket in that web instance`
150
+ );
151
+ return false;
152
+ }
153
+ const where = setup.kind === "external" ? setup.instance : this.config.webInstance;
154
+ this.log.info(`CometVisu will talk ${setup.transport} to ${where}`);
155
+ return true;
156
+ }
157
+ /**
158
+ * What the configured version points at. The admin stores a prefixed entry of the version list,
159
+ * but a bare value can still show up from an older configuration. Such a value is an uploaded
160
+ * archive if a file by that name exists, and a release tag otherwise.
161
+ */
162
+ async resolveVersion() {
163
+ const value = (this.config.version || "").replace(/^\/+/, "");
164
+ const selection = (0, import_releases.resolveVersionSelection)(value, this.config.buildUpload);
165
+ if (selection.kind === "custom" || !selection.tag) {
166
+ return selection;
167
+ }
168
+ if (value.startsWith(import_releases.OFFICIAL_FILE_PREFIX)) {
169
+ return selection;
170
+ }
171
+ for (const candidate of [value, `${import_releases.CUSTOM_FILE_PREFIX}${value}`]) {
172
+ try {
173
+ if (await this.fileExistsAsync(`${this.namespace}.files`, candidate)) {
174
+ return { kind: "custom", file: candidate };
175
+ }
176
+ } catch {
177
+ }
178
+ }
179
+ return selection;
180
+ }
181
+ /**
182
+ * Return the directory to serve for an uploaded ("custom") build. Every upload has its own
183
+ * directory, so switching back to a build that was unpacked before costs no unpacking at all.
184
+ * Unpacking only happens when that directory is missing, or when the archive was uploaded again
185
+ * under the same name - which the size and time of the stored file reveal.
186
+ *
187
+ * @param dataDir the instance data directory
188
+ * @param file name of the uploaded archive to serve
189
+ */
190
+ async prepareCustomBuild(dataDir, file) {
191
+ if (!file) {
192
+ throw new Error("no CometVisu build selected - please upload one in the adapter settings");
193
+ }
194
+ const source = await this.uploadSource(file);
195
+ const current = (0, import_releases.readCustomBuild)(dataDir, file);
196
+ if (current && this.matchesUpload(current.source, source)) {
197
+ this.log.debug(`uploaded CometVisu build "${file}" is already unpacked`);
198
+ return current.htmlRoot;
199
+ }
200
+ const tmp = path.join(dataDir, "upload.tar.gz");
201
+ try {
202
+ const stored = await this.readFileAsync(`${this.namespace}.files`, file);
203
+ const buffer = Buffer.isBuffer(stored.file) ? stored.file : Buffer.from(stored.file, "binary");
204
+ fs.mkdirSync(dataDir, { recursive: true });
205
+ fs.writeFileSync(tmp, buffer);
206
+ const result = await (0, import_releases.unpackUploadedTarball)(tmp, dataDir, this.log, source);
207
+ return result.htmlRoot;
208
+ } finally {
209
+ fs.rmSync(tmp, { force: true });
210
+ }
211
+ }
212
+ /**
213
+ * Size and modification time of an uploaded archive, used to notice that it was replaced by a
214
+ * new upload of the same name without reading the whole archive.
215
+ *
216
+ * @param file name of the uploaded archive
217
+ */
218
+ async uploadSource(file) {
219
+ var _a;
220
+ try {
221
+ const entries = await this.readDirAsync(`${this.namespace}.files`, "/");
222
+ const entry = entries.find((e) => e.file === file);
223
+ if (entry) {
224
+ return { file, size: (_a = entry.stats) == null ? void 0 : _a.size, modifiedAt: entry.modifiedAt };
225
+ }
226
+ } catch {
227
+ }
228
+ return { file };
229
+ }
230
+ /**
231
+ * Whether an unpacked build was made from exactly the archive that is stored now. A marker
232
+ * without size and time comes from an older adapter version, so that build is unpacked again.
233
+ *
234
+ * @param unpacked the source the build was unpacked from
235
+ * @param stored the archive currently in the file storage
236
+ */
237
+ matchesUpload(unpacked, stored) {
238
+ if (!unpacked || unpacked.file !== stored.file) {
239
+ return false;
240
+ }
241
+ if (unpacked.size === void 0 || unpacked.modifiedAt === void 0) {
242
+ return false;
243
+ }
244
+ return unpacked.size === stored.size && unpacked.modifiedAt === stored.modifiedAt;
245
+ }
246
+ /**
247
+ * Drop the builds whose archive is no longer in the file storage, plus the single directory all
248
+ * uploads shared before every upload got its own one.
249
+ *
250
+ * @param dataDir the instance data directory
251
+ */
252
+ async pruneCustomBuilds(dataDir) {
253
+ var _a;
254
+ if ((0, import_releases.removeLegacyCustomBuild)(dataDir)) {
255
+ this.log.info("removed the CometVisu build directory of the previous adapter version");
256
+ }
257
+ let uploads;
258
+ try {
259
+ const entries = await this.readDirAsync(`${this.namespace}.files`, "/");
260
+ uploads = entries.filter((entry) => !entry.isDir).map((entry) => entry.file);
261
+ } catch {
262
+ return;
263
+ }
264
+ for (const build of (0, import_releases.listCustomBuilds)(dataDir)) {
265
+ if (build.file === null || !uploads.includes(build.file)) {
266
+ fs.rmSync(build.dir, { recursive: true, force: true });
267
+ this.log.info(`removed unpacked build of deleted archive "${(_a = build.file) != null ? _a : build.dir}"`);
268
+ }
269
+ }
270
+ }
271
+ /**
272
+ * The admin component asks to remove the build of an archive it just deleted, so the unpacked
273
+ * files go away right there instead of only at the next start.
274
+ *
275
+ * @param obj the admin message
276
+ */
277
+ onMessage(obj) {
278
+ var _a;
279
+ if (!obj || typeof obj !== "object" || obj.command !== "deleteCustomBuild") {
280
+ return;
281
+ }
282
+ const file = (_a = obj.message) == null ? void 0 : _a.file;
283
+ let removed = false;
284
+ if (file) {
285
+ removed = (0, import_releases.removeCustomBuild)(utils.getAbsoluteInstanceDataDir(this), file);
286
+ if (removed) {
287
+ this.log.info(`removed unpacked build of deleted archive "${file}"`);
288
+ }
289
+ }
290
+ if (obj.callback) {
291
+ this.sendTo(obj.from, obj.command, { removed }, obj.callback);
292
+ }
293
+ }
294
+ onUnload(callback) {
295
+ try {
296
+ void this.setState("info.connection", { val: false, ack: true });
297
+ callback();
298
+ } catch (e) {
299
+ this.log.error(`error during unload: ${e instanceof Error ? e.message : String(e)}`);
300
+ callback();
301
+ }
302
+ }
303
+ }
304
+ if (require.main !== module) {
305
+ module.exports = (options) => new Cometvisu(options);
306
+ } else {
307
+ (() => new Cometvisu())();
308
+ }
309
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["/*\n * Created with @iobroker/create-adapter v3.1.5\n */\n\nimport * as utils from '@iobroker/adapter-core';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport {\n CUSTOM_FILE_PREFIX,\n type CustomBuildSource,\n OFFICIAL_FILE_PREFIX,\n type VersionSelection,\n ensureRelease,\n listCustomBuilds,\n pruneReleaseBuilds,\n readCustomBuild,\n removeCustomBuild,\n removeLegacyCustomBuild,\n resolveVersionSelection,\n unpackUploadedTarball,\n} from './lib/releases';\nimport { type WebNative, classifyWebSocket } from './lib/webSocket';\n\n// The visualisation is served by iobroker.web through the extension in lib/web.ts, which runs in\n// that adapter's process. This adapter only keeps the selected build ready on disk.\n\n// Settings of the web server this adapter used to run itself. They were dropped from io-package.json,\n// but an instance created before that still carries them: the upgrade only adds missing defaults\n// (extendNative() in @iobroker/js-controller-cli), it never removes what is gone.\nconst FORMER_OWN_SERVER_SETTINGS = [\n 'port',\n 'ip',\n 'findNextPort',\n 'secure',\n 'certPublic',\n 'certPrivate',\n 'certChained',\n 'auth',\n 'defaultUser',\n 'defaultUserPassword',\n 'ttl',\n 'ownServer',\n];\n\nclass Cometvisu extends utils.Adapter {\n public constructor(options: Partial<utils.AdapterOptions> = {}) {\n super({ ...options, name: 'cometvisu' });\n this.on('ready', this.onReady.bind(this));\n this.on('message', this.onMessage.bind(this));\n this.on('unload', this.onUnload.bind(this));\n }\n\n private async onReady(): Promise<void> {\n await this.setState('info.connection', { val: false, ack: true });\n await this.dropFormerServerSettings();\n\n // 1) make sure the selected CometVisu build is available locally\n const dataDir = utils.getAbsoluteInstanceDataDir(this);\n await this.pruneCustomBuilds(dataDir);\n let htmlRoot: string;\n // tag of the release being prepared, null while an uploaded build is used\n let servedTag: string | null = null;\n try {\n const selection = await this.resolveVersion();\n if (selection.kind === 'custom') {\n // the build the user uploaded through the admin\n htmlRoot = await this.prepareCustomBuild(dataDir, selection.file);\n this.log.info(`uploaded CometVisu build prepared in ${htmlRoot}`);\n } else {\n const result = await ensureRelease(dataDir, selection.tag, this.log);\n htmlRoot = result.htmlRoot;\n servedTag = result.tag;\n this.log.info(`CometVisu ${result.tag} prepared in ${htmlRoot}`);\n }\n } catch (e) {\n this.log.error(`could not prepare the CometVisu build: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n\n // A release is cached as its unpacked directory, so only the one in use is kept - this\n // runs after the build was prepared, both because \"latest\" only resolves to a tag in there\n // and so a failed preparation leaves the existing cache alone.\n const dropped = pruneReleaseBuilds(dataDir, servedTag);\n if (dropped.length) {\n this.log.info(`removed unpacked release(s) no longer in use: ${dropped.join(', ')}`);\n }\n\n // A user provided \"resource\" folder next to the version folders acts as an overlay for\n // /resource/*, which the extension puts in front of the build's own resource folder. It is\n // created here so there is a place to drop files into even when it was never used.\n fs.mkdirSync(path.join(dataDir, 'resource'), { recursive: true });\n\n if (!this.config.webInstance) {\n this.log.error(\n 'the visualisation is served by the web adapter - please install it and select its ' +\n 'instance in the settings',\n );\n return;\n }\n if (!(await this.checkWebSocket())) {\n return;\n }\n await this.setState('info.connection', { val: true, ack: true });\n this.log.info(`the CometVisu build is ready, ${this.config.webInstance} serves it under /cometvisu`);\n }\n\n /**\n * Remove the settings of the web server this adapter used to run itself. They stay in an\n * instance that was created while they still existed, where they are misleading - the admin\n * keeps showing a port that nothing listens on, and an encrypted password no code reads.\n *\n * The object is written back as a whole instead of extending it: whether extendObject deletes a\n * single attribute is not something this could rely on. Changing our own configuration makes the\n * controller restart the instance once; there is nothing left to do on the next start, so this\n * just carries on afterwards.\n */\n private async dropFormerServerSettings(): Promise<void> {\n const id = `system.adapter.${this.namespace}`;\n let obj: ioBroker.Object | null | undefined;\n try {\n obj = await this.getForeignObjectAsync(id);\n } catch (e) {\n this.log.warn(`cannot read ${id}: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n const native = obj?.native as Record<string, unknown> | undefined;\n if (!native) {\n return;\n }\n\n const found = FORMER_OWN_SERVER_SETTINGS.filter(key => key in native);\n if (!found.length) {\n return;\n }\n for (const key of found) {\n delete native[key];\n }\n\n try {\n await this.setForeignObjectAsync(id, obj!);\n this.log.info(`removed settings of the former own web server: ${found.join(', ')}`);\n } catch (e) {\n this.log.warn(`cannot clean up ${id}: ${e instanceof Error ? e.message : String(e)}`);\n }\n }\n\n /**\n * Report which socket CometVisu will use, and say so clearly when the web instance offers none.\n * The extension reads the same configuration in the process of the web adapter, but a CometVisu\n * problem is looked for in this log, so the message belongs here as well.\n */\n private async checkWebSocket(): Promise<boolean> {\n const instance = `system.adapter.${this.config.webInstance}`;\n let obj: ioBroker.Object | null | undefined;\n try {\n obj = await this.getForeignObjectAsync(instance);\n } catch (e) {\n this.log.warn(`cannot read ${instance}: ${e instanceof Error ? e.message : String(e)}`);\n return true;\n }\n if (!obj) {\n this.log.error(\n `${instance} does not exist - the visualisation is served by the web adapter, please ` +\n 'install it and select its instance in the settings',\n );\n return false;\n }\n\n const setup = classifyWebSocket(obj.native as WebNative);\n if (setup.kind === 'none') {\n this.log.error(\n `${this.config.webInstance} serves no socket (\"none\"), so CometVisu cannot reach ioBroker - ` +\n 'configure a socket in that web instance',\n );\n return false;\n }\n const where = setup.kind === 'external' ? setup.instance : this.config.webInstance;\n this.log.info(`CometVisu will talk ${setup.transport} to ${where}`);\n return true;\n }\n\n /**\n * What the configured version points at. The admin stores a prefixed entry of the version list,\n * but a bare value can still show up from an older configuration. Such a value is an uploaded\n * archive if a file by that name exists, and a release tag otherwise.\n */\n private async resolveVersion(): Promise<VersionSelection> {\n const value = (this.config.version || '').replace(/^\\/+/, '');\n const selection = resolveVersionSelection(value, this.config.buildUpload);\n if (selection.kind === 'custom' || !selection.tag) {\n return selection;\n }\n if (value.startsWith(OFFICIAL_FILE_PREFIX)) {\n return selection;\n }\n // a bare value: an upload if such a file exists, otherwise a release tag\n for (const candidate of [value, `${CUSTOM_FILE_PREFIX}${value}`]) {\n try {\n if (await this.fileExistsAsync(`${this.namespace}.files`, candidate)) {\n return { kind: 'custom', file: candidate };\n }\n } catch {\n // no file storage yet - treat the value as a release tag\n }\n }\n return selection;\n }\n\n /**\n * Return the directory to serve for an uploaded (\"custom\") build. Every upload has its own\n * directory, so switching back to a build that was unpacked before costs no unpacking at all.\n * Unpacking only happens when that directory is missing, or when the archive was uploaded again\n * under the same name - which the size and time of the stored file reveal.\n *\n * @param dataDir the instance data directory\n * @param file name of the uploaded archive to serve\n */\n private async prepareCustomBuild(dataDir: string, file: string): Promise<string> {\n if (!file) {\n throw new Error('no CometVisu build selected - please upload one in the adapter settings');\n }\n const source = await this.uploadSource(file);\n const current = readCustomBuild(dataDir, file);\n if (current && this.matchesUpload(current.source, source)) {\n this.log.debug(`uploaded CometVisu build \"${file}\" is already unpacked`);\n return current.htmlRoot;\n }\n\n const tmp = path.join(dataDir, 'upload.tar.gz');\n try {\n const stored = await this.readFileAsync(`${this.namespace}.files`, file);\n const buffer = Buffer.isBuffer(stored.file) ? stored.file : Buffer.from(stored.file, 'binary');\n\n fs.mkdirSync(dataDir, { recursive: true });\n fs.writeFileSync(tmp, buffer);\n const result = await unpackUploadedTarball(tmp, dataDir, this.log, source);\n return result.htmlRoot;\n } finally {\n fs.rmSync(tmp, { force: true });\n }\n }\n\n /**\n * Size and modification time of an uploaded archive, used to notice that it was replaced by a\n * new upload of the same name without reading the whole archive.\n *\n * @param file name of the uploaded archive\n */\n private async uploadSource(file: string): Promise<CustomBuildSource> {\n try {\n const entries = await this.readDirAsync(`${this.namespace}.files`, '/');\n const entry = entries.find(e => e.file === file);\n if (entry) {\n return { file, size: entry.stats?.size, modifiedAt: entry.modifiedAt };\n }\n } catch {\n // no file storage yet - the read of the archive itself will report the real problem\n }\n return { file };\n }\n\n /**\n * Whether an unpacked build was made from exactly the archive that is stored now. A marker\n * without size and time comes from an older adapter version, so that build is unpacked again.\n *\n * @param unpacked the source the build was unpacked from\n * @param stored the archive currently in the file storage\n */\n private matchesUpload(unpacked: CustomBuildSource | null, stored: CustomBuildSource): boolean {\n if (!unpacked || unpacked.file !== stored.file) {\n return false;\n }\n if (unpacked.size === undefined || unpacked.modifiedAt === undefined) {\n return false;\n }\n return unpacked.size === stored.size && unpacked.modifiedAt === stored.modifiedAt;\n }\n\n /**\n * Drop the builds whose archive is no longer in the file storage, plus the single directory all\n * uploads shared before every upload got its own one.\n *\n * @param dataDir the instance data directory\n */\n private async pruneCustomBuilds(dataDir: string): Promise<void> {\n if (removeLegacyCustomBuild(dataDir)) {\n this.log.info('removed the CometVisu build directory of the previous adapter version');\n }\n let uploads: string[];\n try {\n const entries = await this.readDirAsync(`${this.namespace}.files`, '/');\n uploads = entries.filter(entry => !entry.isDir).map(entry => entry.file);\n } catch {\n // without a file storage there is nothing to compare against, so keep what is there\n return;\n }\n for (const build of listCustomBuilds(dataDir)) {\n if (build.file === null || !uploads.includes(build.file)) {\n fs.rmSync(build.dir, { recursive: true, force: true });\n this.log.info(`removed unpacked build of deleted archive \"${build.file ?? build.dir}\"`);\n }\n }\n }\n\n /**\n * The admin component asks to remove the build of an archive it just deleted, so the unpacked\n * files go away right there instead of only at the next start.\n *\n * @param obj the admin message\n */\n private onMessage(obj: ioBroker.Message): void {\n if (!obj || typeof obj !== 'object' || obj.command !== 'deleteCustomBuild') {\n return;\n }\n const file = (obj.message as { file?: string } | undefined)?.file;\n let removed = false;\n if (file) {\n removed = removeCustomBuild(utils.getAbsoluteInstanceDataDir(this), file);\n if (removed) {\n this.log.info(`removed unpacked build of deleted archive \"${file}\"`);\n }\n }\n if (obj.callback) {\n this.sendTo(obj.from, obj.command, { removed }, obj.callback);\n }\n }\n\n private onUnload(callback: () => void): void {\n try {\n void this.setState('info.connection', { val: false, ack: true });\n callback();\n } catch (e) {\n this.log.error(`error during unload: ${e instanceof Error ? e.message : String(e)}`);\n callback();\n }\n }\n}\n\nif (require.main !== module) {\n // Export the constructor in compact mode\n module.exports = (options: Partial<utils.AdapterOptions> | undefined) => new Cometvisu(options);\n} else {\n // otherwise start the instance directly\n (() => new Cometvisu())();\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,128 @@
1
+ {
2
+ "common": {
3
+ "name": "cometvisu",
4
+ "version": "0.0.1",
5
+ "news": {
6
+ "0.0.1": {
7
+ "en": "initial release",
8
+ "de": "Erstveröffentlichung",
9
+ "ru": "Начальная версия",
10
+ "pt": "lançamento inicial",
11
+ "nl": "Eerste uitgave",
12
+ "fr": "Première version",
13
+ "it": "Versione iniziale",
14
+ "es": "Versión inicial",
15
+ "pl": "Pierwsze wydanie",
16
+ "uk": "Початкова версія",
17
+ "zh-cn": "首次出版"
18
+ }
19
+ },
20
+ "titleLang": {
21
+ "en": "CometVisu",
22
+ "de": "CometVisu",
23
+ "ru": "КометВизу",
24
+ "pt": "CometaVisu",
25
+ "nl": "KomeetVisu",
26
+ "fr": "ComèteVisu",
27
+ "it": "CometaVisu",
28
+ "es": "CometaVisu",
29
+ "pl": "CometVisu",
30
+ "uk": "CometVisu",
31
+ "zh-cn": "彗星维苏"
32
+ },
33
+ "desc": {
34
+ "en": "Serves the CometVisu visualization through the ioBroker web adapter",
35
+ "de": "Liefert die CometVisu-Visualisierung über den ioBroker-web-Adapter aus",
36
+ "ru": "Предоставляет визуализацию CometVisu через веб-адаптер ioBroker",
37
+ "pt": "Fornece a visualização CometVisu através do adaptador web do ioBroker",
38
+ "nl": "Levert de CometVisu-visualisatie via de ioBroker-webadapter",
39
+ "fr": "Fournit la visualisation CometVisu via l'adaptateur web d'ioBroker",
40
+ "it": "Fornisce la visualizzazione CometVisu tramite l'adattatore web di ioBroker",
41
+ "es": "Sirve la visualización CometVisu a través del adaptador web de ioBroker",
42
+ "pl": "Udostępnia wizualizację CometVisu przez adapter web ioBrokera",
43
+ "uk": "Надає візуалізацію CometVisu через вебадаптер ioBroker",
44
+ "zh-cn": "通过 ioBroker web 适配器提供 CometVisu 可视化界面"
45
+ },
46
+ "authors": ["Florian Schirmer <jolt@tuxbox.org>"],
47
+ "keywords": ["cometvisu", "visualization", "websocket", "web"],
48
+ "licenseInformation": {
49
+ "type": "free",
50
+ "license": "MIT"
51
+ },
52
+ "platform": "Javascript/Node.js",
53
+ "icon": "cometvisu.png",
54
+ "enabled": true,
55
+ "extIcon": "https://raw.githubusercontent.com/joltcoke/ioBroker.cometvisu/main/admin/cometvisu.png",
56
+ "readme": "https://github.com/joltcoke/ioBroker.cometvisu/blob/main/README.md",
57
+ "loglevel": "info",
58
+ "tier": 3,
59
+ "mode": "daemon",
60
+ "type": "visualization",
61
+ "compact": true,
62
+ "connectionType": "local",
63
+ "dataSource": "push",
64
+ "adminUI": {
65
+ "config": "json"
66
+ },
67
+ "dependencies": [
68
+ {
69
+ "js-controller": ">=6.0.11"
70
+ },
71
+ {
72
+ "web": ">=7.0.3"
73
+ }
74
+ ],
75
+ "globalDependencies": [
76
+ {
77
+ "admin": ">=7.0.23"
78
+ }
79
+ ],
80
+ "messagebox": true,
81
+ "localLinks": {
82
+ "ui": {
83
+ "link": "%web_protocol%://%ip%:%web_port%/cometvisu/",
84
+ "name": "CometVisu",
85
+ "color": "greenLight",
86
+ "order": 10
87
+ }
88
+ },
89
+ "webExtension": "build/lib/web.js"
90
+ },
91
+ "native": {
92
+ "version": "",
93
+ "webInstance": "web.0"
94
+ },
95
+ "objects": [],
96
+ "instanceObjects": [
97
+ {
98
+ "_id": "files",
99
+ "type": "meta",
100
+ "common": {
101
+ "name": "CometVisu build uploads",
102
+ "type": "meta.user"
103
+ },
104
+ "native": {}
105
+ },
106
+ {
107
+ "_id": "info",
108
+ "type": "channel",
109
+ "common": {
110
+ "name": "Information"
111
+ },
112
+ "native": {}
113
+ },
114
+ {
115
+ "_id": "info.connection",
116
+ "type": "state",
117
+ "common": {
118
+ "role": "indicator.connected",
119
+ "name": "CometVisu build ready",
120
+ "type": "boolean",
121
+ "read": true,
122
+ "write": false,
123
+ "def": false
124
+ },
125
+ "native": {}
126
+ }
127
+ ]
128
+ }
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "iobroker.cometvisu",
3
+ "version": "0.0.1",
4
+ "description": "Serves the CometVisu visualization through the ioBroker web adapter",
5
+ "author": {
6
+ "name": "Florian Schirmer",
7
+ "email": "jolt@tuxbox.org"
8
+ },
9
+ "contributors": [],
10
+ "homepage": "https://github.com/joltcoke/ioBroker.cometvisu",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "cometvisu",
14
+ "visualization",
15
+ "websocket",
16
+ "web"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/joltcoke/ioBroker.cometvisu.git"
21
+ },
22
+ "engines": {
23
+ "node": ">= 20"
24
+ },
25
+ "dependencies": {
26
+ "@iobroker/adapter-core": "^3.4.3",
27
+ "axios": "^1.7.9",
28
+ "express": "^4.21.2",
29
+ "tar": "^7.4.3"
30
+ },
31
+ "devDependencies": {
32
+ "@alcalzone/release-script": "^5.2.1",
33
+ "@alcalzone/release-script-plugin-iobroker": "^5.2.0",
34
+ "@alcalzone/release-script-plugin-license": "^5.2.2",
35
+ "@alcalzone/release-script-plugin-manual-review": "^5.2.0",
36
+ "@iobroker/adapter-dev": "^1.5.0",
37
+ "@iobroker/eslint-config": "^2.3.4",
38
+ "@iobroker/testing": "^5.3.0",
39
+ "@iobroker/types": "^7.2.2",
40
+ "@tsconfig/node20": "^20.1.10",
41
+ "@types/express": "^4.17.21",
42
+ "@types/node": "^20.19.43",
43
+ "rimraf": "^6.1.3",
44
+ "source-map-support": "^0.5.21",
45
+ "ts-node": "^10.9.2",
46
+ "typescript": "~5.9.3"
47
+ },
48
+ "main": "build/main.js",
49
+ "files": [
50
+ "admin{,/!(src)/**}/!(tsconfig|tsconfig.*|.eslintrc).{json,json5}",
51
+ "admin{,/!(src)/**}/*.{html,css,png,svg,jpg,js}",
52
+ "build/",
53
+ "io-package.json",
54
+ "LICENSE"
55
+ ],
56
+ "scripts": {
57
+ "prebuild": "rimraf build",
58
+ "build": "build-adapter ts",
59
+ "watch": "build-adapter ts --watch",
60
+ "prebuild:ts": "rimraf build",
61
+ "build:ts": "build-adapter ts",
62
+ "watch:ts": "build-adapter ts --watch",
63
+ "test:ts": "mocha --config test/mocharc.custom.json \"src/**/*.test.ts\"",
64
+ "test:package": "mocha test/package --exit",
65
+ "test:integration": "mocha test/integration --exit",
66
+ "test": "npm run test:ts && npm run test:package",
67
+ "check": "tsc --noEmit",
68
+ "lint": "eslint -c eslint.config.mjs .",
69
+ "translate": "translate-adapter",
70
+ "release": "release-script",
71
+ "build:admin": "npm --prefix src-admin run build && node tasks/copy-admin-components.mjs"
72
+ },
73
+ "bugs": {
74
+ "url": "https://github.com/joltcoke/ioBroker.cometvisu/issues"
75
+ },
76
+ "readmeFilename": "README.md"
77
+ }