UNPKG

iobroker.vis-2

Version:

Next generation graphical user interface for ioBroker.

1,114 lines 46.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); /** * * iobroker vis-2 Adapter * * Copyright (c) 2021-2025, bluefox * * CC-NC-BY 4.0 License * */ const adapter_core_1 = require("@iobroker/adapter-core"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_https_1 = __importDefault(require("node:https")); const jsonwebtoken_1 = require("jsonwebtoken"); const install_1 = require("./lib/install"); const console = __importStar(require("node:console")); const ioPack = JSON.parse((0, node_fs_1.readFileSync)(`${__dirname}/../io-package.json`).toString()); const cert = (0, node_fs_1.readFileSync)(`${__dirname}/lib/cloudCert.crt`); const POSSIBLE_WIDGET_SETS_LOCATIONS = [ (0, node_path_1.normalize)(`${__dirname}/../../`), (0, node_path_1.normalize)(`${__dirname}/../node_modules/`), (0, node_path_1.normalize)(`${__dirname}/../../../`), (0, node_path_1.normalize)(`${__dirname}/../../../../`), (0, node_path_1.normalize)(`${__dirname}/../../../../node_modules/`), ]; const wwwDir = (0, node_fs_1.existsSync)(`${__dirname}/../www`) ? `${__dirname}/../www` : `${__dirname}/www`; class VisAdapter extends adapter_core_1.Adapter { widgetInstances = {}; stoppingPromise = null; isLicenseError = false; lastProgressUpdate = 0; synchronizing = false; synchronizingQueued = null; vendorPrefix = ''; constructor(options = {}) { options = { ...options, name: 'vis-2', message: obj => this.processMessage(obj), unload: callback => { if (this.synchronizing) { void new Promise((resolve) => { this.stoppingPromise = resolve; }).then(() => callback?.()); } else { callback?.(); } }, ready: () => void this.main(), }; super({ ...options, name: 'vis-2', }); this.visConfig = this.config; } async objectChange(id, obj) { // if it is an instance object if (id.startsWith('system.adapter.') && id.match(/\d+$/) && id !== 'system.adapter.vis.0' && id !== 'system.adapter.vis-2.0') { if (obj && obj.type !== 'instance') { return; } id = id.substring('system.adapter.'.length).replace(/\.\d+$/, ''); if (!obj?.common?.version) { if (this.widgetInstances[id]) { delete this.widgetInstances[id]; await this.buildHtmlPages(false); } } else if (POSSIBLE_WIDGET_SETS_LOCATIONS.find(dir => (0, node_fs_1.existsSync)(`${dir}/iobroker.${id}/widgets/`))) { // Check if the widgets folder exists // still exists if (!this.widgetInstances[id] || this.widgetInstances[id] !== obj.common.version) { this.widgetInstances[id] = obj.common.version; await this.buildHtmlPages(false); } } else if (this.widgetInstances[id]) { delete this.widgetInstances[id]; await this.buildHtmlPages(false); } } } async processMessage(msg) { if (msg?.command === 'checkLicense' && msg.message && msg.callback) { const obj = await this.getForeignObjectAsync(`system.adapter.${msg.message}.0`); if (!obj?.native || (!obj.native.license && !obj.native.useLicenseManager)) { console.log(`[${msg.message}] License not found`); this.sendTo(msg.from, msg.command, { error: 'License not found' }, msg.callback); } else { const result = await this.checkL(obj.native.license, obj.native.useLicenseManager, msg.message); this.sendTo(msg.from, msg.command, { result }, msg.callback); } } else if (msg?.command === 'rebuild' && msg.callback) { if (!this.synchronizing) { this.sendTo(msg.from, msg.command, { result: 'done' }, msg.callback); await this.buildHtmlPages(true); this.log.warn('Force build done!'); } else { this.sendTo(msg.from, msg.command, { error: 'already running' }, msg.callback); } } } static collectWidgetSets(dir, sets) { sets = sets || []; if (!(0, node_fs_1.existsSync)(dir)) { return sets; } const dirs = (0, node_fs_1.readdirSync)(dir); dir = dir.replace(/\\/g, '/'); if (!dir.endsWith('/')) { dir += '/'; } for (let d = 0; d < dirs.length; d++) { const name = dirs[d].toLowerCase(); const widgetPath = `${dir}${dirs[d]}/widgets/`; if (name.startsWith('iobroker.') && !sets.find(s => s.name === name) && (0, node_fs_1.existsSync)(widgetPath)) { let pack; try { pack = JSON.parse((0, node_fs_1.readFileSync)(`${dir}${dirs[d]}/io-package.json`).toString()); } catch (e) { pack = null; console.warn(`Cannot parse "${dir}${dirs[d]}/io-package.json": ${e}`); } sets.push({ path: dir + dirs[d], name, pack }); } } return sets; } async readAdapterList() { const res = await this.getObjectViewAsync('system', 'instance', {}); const instances = []; res.rows.forEach(item => { const obj = item.value; // ignore widgets for V1 only if (obj?.common?.visWidgets && Object.values(obj?.common?.visWidgets).find(w => w?.ignoreInVersions?.includes(2))) { return; } const name = obj && obj._id && obj._id.replace('system.adapter.', '').replace(/\.\d+$/, ''); if (name && !instances.includes(name)) { instances.push(name); } }); instances.sort(); let sets = []; POSSIBLE_WIDGET_SETS_LOCATIONS.forEach(dir => VisAdapter.collectWidgetSets(dir, sets)); sets = sets.filter(s => instances.includes(s.name.substring('iobroker.'.length))); return sets; } async buildHtmlPages(forceBuild) { if (this.synchronizing) { this.synchronizingQueued = { forceBuild }; return; } this.synchronizing = true; const enabledList = await this.readAdapterList(); const configChanged = await this.generateConfigPage(forceBuild, enabledList); this.widgetInstances = {}; enabledList.forEach(instance => (this.widgetInstances[this.name.substring('iobroker.'.length)] = instance.pack?.common?.version)); const { widgetSets, filesChanged } = (0, install_1.syncWidgetSets)(enabledList, forceBuild); const widgetsChanged = await this.generateWidgetsHtml(widgetSets, forceBuild); let uploadedIndexHtml; let indexHtml = ''; if ((0, node_fs_1.existsSync)(`${wwwDir}/index.html`)) { indexHtml = (0, node_fs_1.readFileSync)(`${wwwDir}/index.html`).toString('utf8'); try { const file = await this.readFileAsync('vis-2', 'index.html'); if (typeof file === 'object') { uploadedIndexHtml = file.file.toString('utf8'); } else { uploadedIndexHtml = file.toString(); } } catch { // ignore uploadedIndexHtml = ''; } } else { uploadedIndexHtml = ''; } let uploadedEditHtml; let editHtml = ''; if ((0, node_fs_1.existsSync)(`${wwwDir}/edit.html`)) { editHtml = (0, node_fs_1.readFileSync)(`${wwwDir}/edit.html`).toString('utf8'); try { const file = await this.readFileAsync('vis-2', 'edit.html'); if (typeof file === 'object') { uploadedEditHtml = file.file.toString('utf8'); } else { uploadedEditHtml = file.toString(); } } catch { // ignore uploadedEditHtml = ''; } } else { uploadedEditHtml = ''; } if (configChanged || widgetsChanged || filesChanged || uploadedIndexHtml !== indexHtml || uploadedEditHtml !== editHtml || forceBuild) { try { await this.uploadAdapter(); } catch (e) { this.log.error(`Could not upload adapter: ${e.message}`); } // terminate promise if (this.stoppingPromise) { if (typeof this.stoppingPromise === 'function') { this.stoppingPromise(); this.stoppingPromise = null; } this.synchronizing = false; return; } await this.setState('info.uploaded', Date.now(), true); } else { const state = await this.getStateAsync('info.uploaded'); if (!state?.val) { await this.setState('info.uploaded', Date.now(), true); } } this.synchronizing = false; if (typeof this.stoppingPromise === 'function') { this.stoppingPromise(); this.stoppingPromise = null; } else if (this.synchronizingQueued && !this.visConfig.forceBuild) { const forceBuild = this.synchronizingQueued.forceBuild; this.synchronizingQueued = null; setImmediate(() => void this.buildHtmlPages(forceBuild)); } } async generateWidgetsHtml(widgetSets, forceBuild) { let text = ''; for (let w = 0; w < widgetSets.length; w++) { const widgetSet = widgetSets[w]; let file; const name = `${widgetSet.name}.html`; // ignore the HTML file if adapter has widgets for vis-1 and vis-2. Vis-2 will be loaded from js file and nor from html if (widgetSet.v2) { continue; } try { file = (0, node_fs_1.readFileSync)(`${wwwDir}/widgets/${name}`); // extract all css and js // mark all scripts with data-widgetset attribute file = file.toString().replace(/<script/g, `<script data-widgetset="${name.replace('.html', '')}"`); text += `<!-- --------------${name}--- START -->\n${file.toString()}\n<!-- --------------${name}--- END -->\n`; } catch { this.log.warn(`Cannot read file www/widgets/${name}`); } } let data; try { data = await this.readFileAsync('vis-2', 'widgets.html'); } catch { // ignore } if (typeof data === 'object') { data = data.file; } if (data && (data !== text || forceBuild)) { try { (0, node_fs_1.writeFileSync)(`${wwwDir}/widgets.html`, text); // upload a file to DB await this.writeFileAsync('vis-2', 'www/widgets.html', text); } catch (e) { this.log.error(`Cannot write file www/widgets.html: ${e}`); } return true; } else if (!(0, node_fs_1.existsSync)(`${wwwDir}/widgets.html`) || (0, node_fs_1.readFileSync)(`${wwwDir}/widgets.html`).toString() !== text) { try { (0, node_fs_1.writeFileSync)(`${wwwDir}/widgets.html`, text); } catch (e) { this.log.error(`Cannot write file www/widgets.html: ${e}`); } } return false; } async generateConfigPage(forceBuild, enabledList) { let changed = forceBuild || false; // for back compatibility with vis.1 on cloud const widgetSets = ['basic', 'jqplot', 'jqui', 'swipe', 'tabs']; // collect vis-1 widgets enabledList.forEach(obj => { if (!obj.pack.common.visWidgets) { // find folder in widgets let widgetsPath = `${__dirname}/../node_modules/${obj.name}/widgets`; if (!(0, node_fs_1.existsSync)(widgetsPath)) { widgetsPath = `${__dirname}/../${obj.name}/widgets`; if (!(0, node_fs_1.existsSync)(widgetsPath)) { widgetsPath = ''; } } if (widgetsPath) { (0, node_fs_1.readdirSync)(widgetsPath).forEach(file => { if (file.match(/\.html$/)) { const folderName = file.replace('.html', ''); !widgetSets.includes(folderName) && widgetSets.push(folderName); } }); } } }); const configJs = `window.isLicenseError = ${this.isLicenseError}; // inject the adapter instance window.visAdapterInstance = ${this.instance}; window.vendorPrefix = '${this.vendorPrefix}'; window.disableDataReporting = ${this.common?.disableDataReporting ? 'true' : 'false'}; window.loadingBackgroundColor = '${this.visConfig.loadingBackgroundColor || ''}'; window.loadingBackgroundImage = '${this.visConfig.loadingBackgroundImage ? `files/${this.namespace}/loading-bg.png` : ''}'; window.loadingHideLogo = ${this.visConfig.loadingHideLogo ? 'true' : 'false'}; // for back compatibility with vis.1 on cloud window.visConfig = { "widgetSets": ${JSON.stringify(widgetSets)} }; if (typeof exports !== 'undefined') { exports.config = visConfig; } else { window.visConfig.language = window.navigator.userLanguage || window.navigator.language; } `; // upload config.js let currentConfigJs; try { const file = await this.readFileAsync('vis-2', 'config.js'); currentConfigJs = file.file.toString('utf8'); } catch { // ignore currentConfigJs = ''; } if (!currentConfigJs || currentConfigJs !== configJs || forceBuild) { changed = true; this.log.info('config.js changed. Upload.'); await this.writeFileAsync('vis-2', 'config.js', configJs); try { (0, node_fs_1.writeFileSync)(`${wwwDir}/config.js`, configJs); if (!(0, node_fs_1.existsSync)(`${wwwDir}/js`)) { (0, node_fs_1.mkdirSync)(`${wwwDir}/js`); } (0, node_fs_1.writeFileSync)(`${wwwDir}/js/config.js`, configJs); // backwards compatibility with cloud } catch (e) { this.log.error(`Cannot write file www/config.js: ${e}`); } } else if (!(0, node_fs_1.existsSync)(`${wwwDir}/config.js`) || (0, node_fs_1.readFileSync)(`${wwwDir}/config.js`).toString() !== configJs) { try { (0, node_fs_1.writeFileSync)(`${wwwDir}/config.js`, configJs); if (!(0, node_fs_1.existsSync)(`${wwwDir}/js`)) { (0, node_fs_1.mkdirSync)(`${wwwDir}/js`); } (0, node_fs_1.writeFileSync)(`${wwwDir}/js/config.js`, configJs); // backwards compatibility with cloud } catch (e) { this.log.error(`Cannot write file www/config.js: ${e}`); } } if (!(0, node_fs_1.existsSync)(`${wwwDir}/js/config.js`) || (0, node_fs_1.readFileSync)(`${wwwDir}/js/config.js`).toString() !== configJs) { try { !(0, node_fs_1.existsSync)(`${wwwDir}/js`) && (0, node_fs_1.mkdirSync)(`${wwwDir}/js`); (0, node_fs_1.writeFileSync)(`${wwwDir}/js/config.js`, configJs); // backwards compatibility with cloud } catch (e) { this.log.error(`Cannot write file www/config.js: ${e}`); } } // Create a common user CSS file let data; try { const file = await this.readFileAsync(this.namespace, 'vis-common-user.css'); if (file?.file) { data = file.file.toString(); } else { data = null; } } catch { data = null; } if (data === null) { await this.writeFileAsync(this.namespace, 'vis-common-user.css', ''); } return changed; } // delete this function as js.controller 4.0 will be mainstream async getSuitableLicensesEx(all, name) { // activate it again as js-controller 5.0.19 will be mainstream // return this.getSuitableLicenses(all, name); const licenses = []; try { const obj = await this.getForeignObjectAsync('system.licenses'); const uuidObj = await this.getForeignObjectAsync('system.meta.uuid'); if (!uuidObj?.native?.uuid) { this.log.error('No UUID found!'); return licenses; } const uuid = uuidObj.native.uuid; if (obj?.native?.licenses?.length) { const now = Date.now(); const _obj = name ? await this.getForeignObjectAsync(`system.adapter.${name === 'vis' ? 'vis-2' : name}`) : null; let version; if (_obj?.common?.version) { version = _obj.common.version.split('.')[0]; } else { version = this.pack?.version.split('.')[0] || ''; } obj.native.licenses.forEach(license => { try { const decoded = (0, jsonwebtoken_1.verify)(license.json, cert); if (decoded.name && (!decoded.valid_till || decoded.valid_till === '0000-00-00 00:00:00' || new Date(decoded.valid_till).getTime() > now)) { if (decoded.name.startsWith(`iobroker.${name || 'vis-2'}`) && (all || !license.usedBy || license.usedBy === this.namespace)) { // Licenses for version ranges 0.x and 1.x are handled identically and are valid for both version ranges. // // If license is for adapter with version 0 or 1 if (decoded.version === '&lt;2' || decoded.version === '<2' || decoded.version === '<1' || decoded.version === '<=1') { // check the current adapter major version if (version !== '0' && version !== '1') { // exception if vis-1 has UUID, so it is valid for vis-2 const exception = decoded.name === 'iobroker.vis' && version === '2' && decoded.uuid; if (!exception) { return; } } } else if (decoded.version && decoded.version !== version) { // Licenses for adapter versions >=2 need to match to the adapter major version, // which means that a new major version requires new licenses if it would be "included" // in the last purchase // decoded.version could be only '<2' or direct version, like "2", "3" and so on return; } if (decoded.uuid && decoded.uuid !== uuid) { // License is not for this server return; } // remove free license if commercial license found if (decoded.invoice !== 'free') { const pos = licenses.findIndex(item => item.invoice === 'free'); if (pos !== -1) { licenses.splice(pos, 1); } } license.decoded = decoded; licenses.push(license); } } } catch (err) { this.log.error(`Cannot decode license "${license.name}": ${err.message}`); } }); } } catch { // ignore } licenses.sort((a, b) => { const aInvoice = a.decoded.invoice !== 'free'; const bInvoice = b.decoded.invoice !== 'free'; if (aInvoice === bInvoice) { return 0; } if (aInvoice) { return -1; } if (bInvoice) { return 1; } return 0; }); return licenses; } async checkLicense(license, uuid, originalError, adapterName) { const _obj = adapterName ? await this.getForeignObjectAsync(`system.adapter.${adapterName === 'vis' ? 'vis-2' : adapterName}`) : null; let version; if (_obj?.common?.version) { version = _obj.common.version.split('.')[0]; } else { version = this.version?.split('.')[0]; } license.name = license.name.replace(/\.action$/, ''); license.name = license.name.replace(/\.offline$/, ''); if (license && license.expires * 1000 < new Date().getTime()) { this.log.error(`License error: Expired on ${new Date(license.expires * 1000).toString()}`); return true; } if (!license) { this.log.error(`License error: License is empty${originalError ? ` and ${originalError}` : ''}`); return true; } if (uuid.length !== 36 && license.invoice === 'free' && !uuid.startsWith('IO')) { this.log.error('Cannot use free license with commercial device!'); return true; } if (license.name !== adapterName && license.name !== `iobroker.${adapterName}`) { this.log.error(`License is for other adapter "${license.name}". Expected "iobroker.${adapterName}"`); return true; } if ((license.type !== 'commercial' && version !== '1' && version !== license.version) || (version === '1' && license.version !== '&lt;2' && license.version !== '<2' && license.version !== '<1' && license.version !== '<=1')) { this.log.error(`License is for other adapter version "${license.name}@${license.version}". Expected "iobroker.${adapterName}@${version}"`); return true; } const code = []; for (let i = 0; i < license.type.length; i++) { code.push(`\\u00${license.type.charCodeAt(i).toString(16)}`); } if (license.uuid && uuid !== license.uuid) { this.log.error(`License is for other device with UUID "${license.uuid}". This device has UUID "${uuid}"`); return true; } const t = '\u0063\u006f\u006d\u006d\u0065\u0072\u0063\u0069\u0061\u006c'; if (t.length !== code.length) { if (originalError) { this.log.error(`Cannot check license: ${originalError}`); } return true; } for (let s = 0; s < code.length; s++) { if (code[s] !== `\\u00${t.charCodeAt(s).toString(16)}`) { if (originalError) { this.log.error(`Cannot check license: ${originalError}`); } return true; } } return false; } async check(license, uuid, originalError, name) { try { const decoded = (0, jsonwebtoken_1.verify)(license, (0, node_fs_1.readFileSync)(`${__dirname}/lib/cloudCert.crt`)); return await this.checkLicense(decoded, uuid, originalError, name); } catch (err) { this.log.error(`Cannot check license: ${originalError || err}`); return true; } } async doLicense(license, uuid, adapterName) { let version = this.version?.split('.')[0] || '2'; // take the version of checked adapter if (adapterName !== 'vis' && adapterName !== 'vis-2') { const obj = await this.getForeignObjectAsync(`system.adapter.${adapterName}.0`); if (obj?.common?.version) { version = obj.common.version.split('.')[0]; } else { version = '1'; } } return new Promise((resolve, reject) => { const data = JSON.stringify({ json: license, uuid, version, }); // An object of options to indicate where to post to const postOptions = { host: 'iobroker.net', path: '/api/v1/public/cert', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data), }, }; // Set up the request const postReq = node_https_1.default .request(postOptions, res => { res.setEncoding('utf8'); let result = ''; res.on('data', chunk => (result += chunk)); res.on('end', () => { try { const data = JSON.parse(result); if (data.result === 'OK') { data.name = data.name.replace(/\.action$/, '').replace(/\.offline$/, ''); if (data.name !== `iobroker.${adapterName}` && data.name !== adapterName) { this.log.error(`License is for other adapter "${data.name}". Expected "iobroker.${adapterName}"`); resolve(true); } else if (uuid.length !== 36 && uuid.substring(0, 2) !== 'IO') { try { const decoded = (0, jsonwebtoken_1.verify)(license, (0, node_fs_1.readFileSync)(`${__dirname}/lib/cloudCert.crt`)); if (!decoded || decoded.invoice === 'free') { this.log.error('Cannot use free license with commercial device!'); resolve(true); } else { resolve(false); } } catch (err) { this.log.error(`Cannot check license: ${err}`); resolve(true); } } else { this.log.info('vis-2 license is OK.'); resolve(false); } } else { this.log.error(`License is invalid! Nothing updated. Error: ${data ? data.error || data.result : 'unknown'}`); resolve(true); } } catch (err) { this.log.warn(`License is invalid 2. Error: ${err}`); reject(new Error(err)); } }); res.on('error', err => { this.log.warn(`License is invalid. Error: ${err}`); reject(err); }); }) .on('error', err => { this.log.warn(`License is invalid 1. Error: ${err}`); reject(err); }); postReq.write(data); postReq.end(); }); } /** * Collect Files of an adapter-specific directory from the iobroker storage * * @param adapterPath path in the adapter-specific storage space * @param _result result as string */ async collectExistingFilesToDelete(adapterPath, _result) { _result ||= []; let files; if (this.stoppingPromise) { return _result; } try { this.log.debug(`Scanning ${adapterPath}`); files = await this.readDirAsync('vis-2', adapterPath); } catch { // ignore err files = []; } if (files?.length) { for (const file of files) { if (file.file === '.' || file.file === '..') { continue; } const newPath = adapterPath + file.file; if (file.isDir) { try { const filesToDelete = await this.collectExistingFilesToDelete(`${newPath}/`); _result = _result.concat(filesToDelete); } catch (err) { this.log.warn(`Cannot delete folder "${adapterPath}${newPath}/": ${err.message}`); } } else if (!_result.includes(newPath)) { _result.push(newPath); } } } return _result; } async eraseFiles(files) { if (files?.length) { const uploadID = 'system.adapter.vis-2.upload'; await this.setForeignStateAsync(uploadID, 1, true); for (let f = 0; f < files.length; f++) { const file = files[f]; if (file === '/index.html' || file === '/edit.html') { continue; } if (this.stoppingPromise) { return; } const now = Date.now(); if (!this.lastProgressUpdate || now - this.lastProgressUpdate > 1000) { this.lastProgressUpdate = now; await this.setForeignStateAsync(uploadID, // upload starts from 0% and runs to 50% and round to 10th Math.round((100 * (10 / 2) * f) / files.length) / 10, true); } try { await this.unlinkAsync('vis-2', file); } catch (err) { this.log.error(`Cannot delete file "${file}": ${err}`); } } await this.setForeignStateAsync(uploadID, 50, true); } } async upload(files) { const uploadID = 'system.adapter.vis-2.upload'; if (files.length) { await this.setForeignStateAsync(uploadID, 50, true); } const wwwLen = `${wwwDir}/`.length; for (let f = 0; f < files.length; f++) { const file = files[f]; if (this.stoppingPromise) { return; } const attName = file.substring(wwwLen).replace(/\\/g, '/'); if (attName === 'index.html' || attName === 'edit.html') { continue; } // write upload status into log if (files.length - f > 100) { (!f || !((files.length - f - 1) % 50)) && this.log.debug(`upload [${files.length - f - 1}] ${file.substring(wwwLen)} ${attName}`); } else if (files.length - f - 1 > 20) { (!f || !((files.length - f - 1) % 10)) && this.log.debug(`upload [${files.length - f - 1}] ${file.substring(wwwLen)} ${attName}`); } else { this.log.debug(`upload [${files.length - f - 1}] ${file.substring(wwwLen)} ${attName}`); } // Update upload indicator const now = Date.now(); if (!this.lastProgressUpdate || now - this.lastProgressUpdate > 2000) { this.lastProgressUpdate = now; await this.setForeignStateAsync(uploadID, // upload starts from 50% and runs to 100% 50 + Math.round((100 * (10 / 2) * f) / files.length) / 10, true); } try { const data = (0, node_fs_1.readFileSync)(file); await this.writeFileAsync('vis-2', attName, data); } catch (e) { this.log.error(`Error: Cannot upload ${file}: ${e.message}`); } } // Set upload progress to 0; if (files.length) { await this.setForeignStateAsync(uploadID, 0, true); } } // Read synchronous all files recursively from local directory walk(dir, _results) { const results = _results || []; if (this.stoppingPromise) { return results; } try { if ((0, node_fs_1.existsSync)(dir)) { const list = (0, node_fs_1.readdirSync)(dir); list.map(file => { const stat = (0, node_fs_1.statSync)(`${dir}/${file}`); if (stat.isDirectory()) { this.walk(`${dir}/${file}`, results); } else { if (!file.endsWith('.npmignore') && !file.endsWith('.gitignore') && !file.endsWith('.DS_Store') && !file.endsWith('_socket/info.js')) { results.push(`${dir}/${file}`); } } }); } } catch (err) { console.error(err); } return results; } /** * Upload given adapter */ async uploadAdapter() { if (!(0, node_fs_1.existsSync)(wwwDir)) { return; } // Create "upload progress" object if not exists let obj; const _id = 'system.adapter.vis-2.upload'; try { obj = await this.getForeignObjectAsync(_id); } catch { // ignore } if (!obj) { await this.setForeignObject(_id, { _id, type: 'state', common: { name: 'vis-2.upload', type: 'number', role: 'indicator.state', unit: '%', min: 0, max: 100, def: 0, desc: 'Upload process indicator', read: true, write: false, }, native: {}, }); } await this.setForeignStateAsync(`system.adapter.vis-2.upload`, 0, true); let result; try { result = await this.getForeignObjectAsync('vis-2'); } catch { // ignore } // Read all names with subtrees from the local directory const files = this.walk(wwwDir); if (!result) { await this.setForeignObject('vis-2', { _id: 'vis-2', type: 'meta', common: { name: 'vis-2', type: 'meta.folder', }, native: {}, }); } const filesToDelete = await this.collectExistingFilesToDelete('/'); this.log.debug(`Erasing files: ${filesToDelete.length}`); if (this.stoppingPromise) { return; } // write temp index.html and edit.html await this.writeFileAsync('vis-2', 'index.html', (0, node_fs_1.readFileSync)(`${__dirname}/lib/updating.html`).toString('utf8')); await this.writeFileAsync('vis-2', 'edit.html', (0, node_fs_1.readFileSync)(`${__dirname}/lib/updating.html`).toString('utf8')); // delete old files, before upload of new await this.eraseFiles(filesToDelete); await this.upload(files); if (this.stoppingPromise) { return; } // restore normal files await this.writeFileAsync('vis-2', 'index.html', (0, node_fs_1.readFileSync)(`${wwwDir}/index.html`).toString('utf8')); await this.writeFileAsync('vis-2', 'edit.html', (0, node_fs_1.readFileSync)(`${wwwDir}/edit.html`).toString('utf8')); } async copyFolder(sourceId, sourcePath, targetId, targetPath) { let files; try { files = await this.readDirAsync(sourceId, sourcePath); } catch { return; } for (let f = 0; f < files.length; f++) { if (files[f].isDir) { await this.copyFolder(sourceId, `${sourcePath}/${files[f].file}`, targetId, `${targetPath}/${files[f].file}`); } else { const data = await this.readFileAsync(sourceId, `${sourcePath}/${files[f].file}`); await this.writeFileAsync(targetId, `${targetPath}/${files[f].file}`, data.file); } } } async checkL(license, useLicenseManager, name) { if (name === 'vis-2') { name = 'vis'; } const uuidObj = await this.getForeignObjectAsync('system.meta.uuid'); if (!uuidObj || !uuidObj.native || !uuidObj.native.uuid) { this.log.error('UUID not found!'); return false; } if (useLicenseManager) { const result = await this.getSuitableLicensesEx(true, name); license = result[0]?.json; } if (!license) { this.log.error(`No license found for ${name}. Please get one on https://iobroker.net !`); return false; } try { return !(await this.doLicense(license, uuidObj.native.uuid, name)); } catch (err) { return !(await this.check(license, uuidObj.native.uuid, err || null, name)); } } async exportFormOlderVersions() { // Check if first start of vis-2 let files; try { files = await this.readDirAsync('vis-2.0', ''); } catch { // ignore } // if no files found, try to copy from vis-2-beta.0 if (!files?.length) { // if vis-2-beta installed, copy files from vis-2-beta to vis-2 try { files = await this.readDirAsync('vis-2-beta.0', ''); } catch { // ignore } if (files?.length) { // copy recursive all await this.copyFolder('vis-2-beta.0', '', 'vis-2.0', ''); } else { // try to copy from vis.0 try { files = await this.readDirAsync('vis.0', ''); } catch { // ignore } if (files?.length) { // copy recursive all await this.copyFolder('vis.0', '', 'vis-2.0', ''); } } } } async main() { this.visConfig = this.config; const visObj = await this.getForeignObjectAsync('vis-2'); await this.setForeignStateAsync('system.adapter.vis-2.upload', 0, true); if (!(0, node_fs_1.existsSync)(wwwDir)) { this.log.error('Cannot find www folder. Looks like adapter was installed from github! Please install it from npm!'); return; } // create a vis "meta" object if not exists if (visObj?.type !== 'meta') { await this.setForeignObject('vis-2', { type: 'meta', common: { name: 'vis-2 core files', type: 'meta.user', }, native: {}, }); } // create a vis-2.0 "meta" object, if not exists const visObjNS = await this.getForeignObjectAsync(this.namespace); if (visObjNS?.type !== 'meta') { await this.setForeignObject(this.namespace, { type: 'meta', common: { name: 'user files and images for vis-2', type: 'meta.user', }, native: {}, }); } // repair chart view const systemView = await this.getForeignObjectAsync('_design/system'); if (systemView?.views && !systemView.views.chart) { systemView.views.chart = { map: "function(doc) { if (doc.type === 'chart') emit(doc._id, doc) }", }; await this.setForeignObject(systemView._id, systemView); } // Change running mode to daemon, enable messagebox and correct the local links const instanceObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); if (instanceObj?.common && (instanceObj.common.mode !== 'daemon' || // mode must be "daemon" !instanceObj.common.messagebox || // messagebox must be enabled JSON.stringify(instanceObj.common.localLinks) !== JSON.stringify(ioPack.common.localLinks))) { instanceObj.common.mode = 'daemon'; instanceObj.common.messagebox = true; instanceObj.common.localLinks = ioPack.common.localLinks; await this.setForeignObject(instanceObj._id, instanceObj); // controller will do restart return; } let systemConfig; try { systemConfig = await this.getForeignObjectAsync('system.config'); } catch (e) { this.log.warn(`Cannot read systemConfig: ${e}`); } if (!systemConfig) { this.log.error('Cannot find object system.config'); } let uuid = null; try { uuid = await this.getForeignObjectAsync('system.meta.uuid'); } catch (e) { this.log.warn(`Cannot read UUID: ${e}`); } this.vendorPrefix = systemConfig?.native?.vendor?.uuidPrefix || (uuid?.native?.uuid?.length > 36 ? uuid?.native.uuid.substring(0, 2) : ''); // first check license if (!this.visConfig.useLicenseManager && (!this.visConfig.license || typeof this.visConfig.license !== 'string')) { this.isLicenseError = true; this.log.error('No license found for vis-2. Please get one on https://iobroker.net !'); } else { this.isLicenseError = !(await this.checkL(this.visConfig.license, this.visConfig.useLicenseManager, 'vis')); } await this.exportFormOlderVersions(); await this.buildHtmlPages(this.visConfig.forceBuild); if (this.visConfig.forceBuild) { this.log.warn('Force build done! Restarting...'); await this.extendForeignObjectAsync(`system.adapter.${this.namespace}`, { native: { forceBuild: false }, }); } else { this.subscribeForeignObjects('system.adapter.*'); } } } if (require.main !== module) { // Export the constructor in compact mode module.exports = (options) => new VisAdapter(options); } else { // otherwise start the instance directly (() => new VisAdapter())(); } //# sourceMappingURL=main.js.map