iobroker.vis-2
Version:
Next generation graphical user interface for ioBroker.
1,114 lines • 46.4 kB
JavaScript
"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-2026, 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 === '<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 !== '<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