UNPKG

@iobroker/socket-classes

Version:
1,063 lines (1,062 loc) 75.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); exports.SocketCommandsAdmin = void 0; const axios_1 = __importDefault(require("axios")); const node_path_1 = require("node:path"); const node_fs_1 = require("node:fs"); const socketCommands_1 = require("./socketCommands"); const ACL_READ = 4; // const ACL_WRITE = 2; class SocketCommandsAdmin extends socketCommands_1.SocketCommands { static ALLOW_CACHE = [ 'getRepository', 'getInstalled', 'getInstalledAdapter', 'getVersion', 'getDiagData', 'getLocationOnDisk', 'getDevList', 'getLogs', 'getHostInfo', ]; states; objects; thresholdInterval = null; cmdSessions = {}; eventsThreshold; cache = {}; cacheGB = null; // cache garbage collector onThresholdChanged = null; secret = ''; constructor(adapter, updateSession, context, objects, states) { super(adapter, updateSession, context); this.objects = objects; this.states = states; this.eventsThreshold = { count: 0, timeActivated: 0, active: false, accidents: 0, repeatSeconds: 3, // how many seconds continuously must be number of events > value value: parseInt(adapter.config.thresholdValue, 10) || 200, // how many events allowed in one check interval checkInterval: 1000, // duration of one check interval }; // do not send too many state updates } start(onThresholdChanged) { // detect event bursts this.thresholdInterval = setInterval(() => { if (!this.eventsThreshold.active) { if (this.eventsThreshold.count > this.eventsThreshold.value) { this.eventsThreshold.accidents++; if (this.eventsThreshold.accidents >= this.eventsThreshold.repeatSeconds) { this.#enableEventThreshold(); } } else { this.eventsThreshold.accidents = 0; } this.eventsThreshold.count = 0; } else if (Date.now() - this.eventsThreshold.timeActivated > 60000) { this.disableEventThreshold(); } }, this.eventsThreshold.checkInterval); this.onThresholdChanged = onThresholdChanged; } /** * Read a file with ratings from server * * @param uuid Unique ioBroker system identification * @param _isAutoUpdate not implemented */ async updateRatings(uuid, _isAutoUpdate) { let _uuid; if (!uuid) { const obj = await this.adapter.getForeignObjectAsync('system.meta.uuid'); _uuid = obj?.native?.uuid || ''; } else { _uuid = uuid; } try { const response = await axios_1.default.get(`https://rating.iobroker.net/rating?uuid=${uuid}`, { timeout: 15000, validateStatus: status => status < 400, }); this.context.ratings = response.data; if (!this.context.ratings || typeof this.context.ratings !== 'object' || Array.isArray(this.context.ratings)) { // @ts-expect-error exception this.context.ratings = { uuid: _uuid }; } else { this.context.ratings.uuid = _uuid; } // auto update only in admin if (this.adapter.name === 'admin') { if (this.context.ratingTimeout) { clearTimeout(this.context.ratingTimeout); } this.context.ratingTimeout = setTimeout(() => { this.context.ratingTimeout = null; void this.updateRatings(uuid).then(() => this.adapter.log.info('Adapter rating updated')); }, 24 * 3600000); } return this.context.ratings; } catch (error) { this.adapter.log.warn(`Cannot update rating: ${error.response ? error.response.data : error.message || error.code}`); return null; } } async #readInstanceConfig(id, user, isTab, configs) { let obj; try { obj = await this.adapter.getForeignObjectAsync(`system.adapter.${id}`, { user, }); } catch { // ignore } if (obj?.common) { const instance = id.split('.').pop(); const config = { id, title: obj.common.titleLang || obj.common.title, desc: obj.common.desc, color: obj.common.color, url: '', icon: obj.common.icon, materialize: obj.common.materialize, // @ts-expect-error it is deprecated jsonConfig: obj.common.jsonConfig, version: obj.common.version, }; if (obj.common.adminUI?.config === 'materialize') { config.materialize = true; } else if (obj.common.adminUI?.config === 'json') { config.jsonConfig = true; } config.url = `/adapter/${obj.common.name}/${isTab ? 'tab' : 'index'}${!isTab && config.materialize ? '_m' : ''}.html${instance ? `?${instance}` : ''}`; if (isTab) { config.tab = true; } else { config.config = true; } configs.push(config); } } _sendToHost = (host, command, message, callback) => { const hash = `${host}_${command}`; if (!message && _a.ALLOW_CACHE.includes(command) && this.cache[hash]) { if (Date.now() - this.cache[hash].ts < 500) { if (typeof callback === 'function') { setImmediate(data => callback(data), JSON.parse(this.cache[hash].res)); } return; } delete this.cache[hash]; } try { this.adapter.sendToHost(host, command, message, res => { if (!message && _a.ALLOW_CACHE.includes(command)) { this.cache[hash] = { ts: Date.now(), res: JSON.stringify(res) }; this.cacheGB = this.cacheGB || setInterval(() => { const commands = Object.keys(this.cache); commands.forEach(cmd => { if (Date.now() - this.cache[cmd].ts > 500) { delete this.cache[cmd]; } }); if (!commands.length && this.cacheGB) { clearInterval(this.cacheGB); this.cacheGB = null; } }, 2000); } if (typeof callback === 'function') { setImmediate(() => callback(res)); } }); } catch (error) { this.adapter.log.error(`[sendToHost] ERROR: ${error.toString()}`); if (typeof callback === 'function') { setImmediate(() => callback({ error })); } } }; // remove this function when js.controller 4.x are mainstream async #readLicenses(login, password) { const config = { headers: { Authorization: `Basic ${Buffer.from(`${login}:${password}`).toString('base64')}` }, timeout: 4000, validateStatus: (status) => status < 400, }; try { const response = await axios_1.default.get(`https://iobroker.net:3001/api/v1/licenses`, config); if (response?.data?.length) { const now = Date.now(); response.data = response.data.filter((license) => !license.validTill || license.validTill === '0000-00-00 00:00:00' || new Date(license.validTill).getTime() > now); } return response.data; } catch (error) { if (error.response) { throw new Error(error.response.data?.error || error.response.data || error.response.status); } if (error.request) { throw new Error('no response'); } throw error; } } // remove this function when js.controller 4.x is mainstream async #updateLicenses(login, password, options) { // if login and password provided in the message, just try to read without saving it in system.licenses if (login && password) { return this.#readLicenses(login, password); } // get actual object const systemLicenses = await this.adapter.getForeignObjectAsync('system.licenses', options); // If password and login exist if (systemLicenses?.native?.password && systemLicenses.native.login) { // get the secret to decode the password if (!this.secret) { const systemConfig = await this.adapter.getForeignObjectAsync('system.config', options); if (systemConfig?.native?.secret) { this.secret = systemConfig.native.secret; } } // decode the password let password = ''; try { password = this.adapter.decrypt(this.secret, systemLicenses.native.password); } catch { throw new Error('Cannot decode password'); } try { const licenses = await this.#readLicenses(systemLicenses.native.login, password); // save licenses to system.licenses and remember the time // merge the information together const oldLicenses = systemLicenses.native.licenses || []; systemLicenses.native.licenses = licenses; oldLicenses.forEach(oldLicense => { if (oldLicense.usedBy) { const newLicense = licenses.find(item => item.json === oldLicense.json); if (newLicense) { newLicense.usedBy = oldLicense.usedBy; } } }); systemLicenses.native.readTime = new Date().toISOString(); // save only if an object changed await this.adapter.setForeignObjectAsync('system.licenses', systemLicenses, options); return licenses; } catch (error) { // if password is invalid if (error.message.includes('Authentication required') || error.message.includes('Cannot decode password')) { // clear existing licenses if exist if (systemLicenses?.native?.licenses?.length) { systemLicenses.native.licenses = []; systemLicenses.native.readTime = new Date().toISOString(); return this.adapter .setForeignObjectAsync('system.licenses', systemLicenses, options) .then(() => { throw error; }); } throw error; } else { throw error; } } } else { // if password or login are empty => clear existing licenses if exist if (systemLicenses?.native?.licenses?.length) { systemLicenses.native.licenses = []; systemLicenses.native.readTime = new Date().toISOString(); return this.adapter.setForeignObjectAsync('system.licenses', systemLicenses, options).then(() => { throw new Error('No password or login'); }); } throw new Error('No password or login'); } } disableEventThreshold() { if (this.eventsThreshold.active) { this.eventsThreshold.accidents = 0; this.eventsThreshold.count = 0; this.eventsThreshold.active = false; this.eventsThreshold.timeActivated = 0; this.adapter.log.info('Subscribe to all states again'); setTimeout(async () => { this.onThresholdChanged?.(false); try { await this.adapter.unsubscribeForeignStatesAsync('system.adapter.*'); } catch (e) { this.adapter.log.error(`Cannot unsubscribe "system.adapter.*": ${e.message}`); } for (const pattern of Object.keys(this.subscribes.stateChange)) { try { await this.adapter.subscribeForeignStatesAsync(pattern); } catch (e) { this.adapter.log.error(`Cannot subscribe "${pattern}": ${e.message}`); } } }, 50); } } #enableEventThreshold() { if (!this.eventsThreshold.active) { this.eventsThreshold.active = true; setTimeout(async () => { this.adapter.log.info(`Unsubscribe from all states, except system's, because over ${this.eventsThreshold.repeatSeconds} seconds the number of events is over ${this.eventsThreshold.value} (in last second ${this.eventsThreshold.count})`); this.eventsThreshold.timeActivated = Date.now(); this.onThresholdChanged?.(true); for (const pattern of Object.keys(this.subscribes.stateChange)) { try { await this.adapter.unsubscribeForeignStatesAsync(pattern); } catch (e) { this.adapter.log.error(`Cannot unsubscribe "${pattern}": ${e.message}`); } } try { await this.adapter.subscribeForeignStatesAsync('system.adapter.*'); } catch (e) { this.adapter.log.error(`Cannot subscribe "system.adapter.*": ${e.message}`); } }, 100); } } #addUser(user, password, options, callback) { if (typeof options === 'function') { callback = options; options = null; } if (!user.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&# ]+$/)) { return socketCommands_1.SocketCommands._fixCallback(callback, 'Invalid characters in the name. Only following special characters are allowed: -@+$§=?!&# and letters'); } try { void this.adapter .getForeignObjectAsync(`system.user.${user}`, options) .then(async (obj) => { if (obj) { socketCommands_1.SocketCommands._fixCallback(callback, 'User yet exists'); } else { try { await this.adapter.setForeignObject(`system.user.${user}`, { type: 'user', common: { name: user, enabled: true, password: '', }, native: {}, }, options); try { await this.adapter.setPassword(user, password, options || {}, callback); } catch (error) { this.adapter.log.error(`[#addUser] cannot set password: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } catch (error) { this.adapter.log.error(`[#addUser] cannot save user: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }); } catch (error) { this.adapter.log.error(`[#addUser] cannot read user: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } #delUser(user, options, callback) { try { void this.adapter.getForeignObject(`system.user.${user}`, options, (error, obj) => { if (error || !obj) { socketCommands_1.SocketCommands._fixCallback(callback, 'User does not exist'); } else { if (obj.common.dontDelete) { socketCommands_1.SocketCommands._fixCallback(callback, 'Cannot delete user, while is system user'); } else { try { this.adapter.delForeignObject(`system.user.${user}`, options || {}, error => // Remove this user from all groups in the web client socketCommands_1.SocketCommands._fixCallback(callback, error)); } catch (error) { this.adapter.log.error(`[#delUser] cannot delete user: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } } }); } catch (error) { this.adapter.log.error(`[#delUser] cannot read user: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } #addGroup(group, desc, acl, options, callback) { let name = group; if (name && name.substring(0, 1) !== name.substring(0, 1).toUpperCase()) { name = name.substring(0, 1).toUpperCase() + name.substring(1); } group = group.substring(0, 1).toLowerCase() + group.substring(1); if (!group.match(/^[-.A-Za-züäößÖÄÜа-яА-Я@+$§0-9=?!&#_ ]+$/)) { return socketCommands_1.SocketCommands._fixCallback(callback, 'Invalid characters in the group name. Only following special characters are allowed: -@+$§=?!&# and letters'); } try { void this.adapter.getForeignObject(`system.group.${group}`, options, (_error, obj) => { if (obj) { socketCommands_1.SocketCommands._fixCallback(callback, 'Group yet exists'); } else { obj = { _id: `system.group.${group}`, type: 'group', common: { name, desc: desc || undefined, members: [], acl: acl || { object: { list: false, read: false, write: false, create: false, delete: false, }, state: { list: false, read: false, write: false, create: false, delete: false, }, users: { list: false, read: false, write: false, create: false, delete: false, }, other: { execute: false, http: false, sendto: false, }, file: { list: false, read: false, write: false, create: false, delete: false, }, }, }, native: {}, }; try { void this.adapter.setForeignObject(`system.group.${group}`, obj, options, error => socketCommands_1.SocketCommands._fixCallback(callback, error, obj)); } catch (error) { this.adapter.log.error(`[#addGroup] cannot write group: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }); } catch (error) { this.adapter.log.error(`[#addGroup] cannot read group: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } #delGroup(group, options, callback) { try { void this.adapter.getForeignObject(`system.group.${group}`, options, (error, obj) => { if (error || !obj) { socketCommands_1.SocketCommands._fixCallback(callback, 'Group does not exist'); } else { if (obj.common.dontDelete) { socketCommands_1.SocketCommands._fixCallback(callback, 'Cannot delete group, while is system group'); } else { try { this.adapter.delForeignObject(`system.group.${group}`, options, error => // Remove this group from all users in the web client socketCommands_1.SocketCommands._fixCallback(callback, error)); } catch (error) { this.adapter.log.error(`[#delGroup] cannot delete group: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } } }); } catch (error) { this.adapter.log.error(`[#delGroup] cannot read group: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } static #checkObject(obj, options, flag) { // read the rights of the object if (!obj?.common || !obj.acl || flag === 'list') { return true; } if (options.user !== 'system.user.admin' && !options.groups.includes('system.group.administrator')) { if (obj.acl.owner !== options.user) { // Check if the user is in the group if (options.groups.includes(obj.acl.ownerGroup)) { // Check group rights if (!(obj.acl.object & (flag << 4))) { return false; } } else { // everybody if (!(obj.acl.object & flag)) { return false; } } } else { // Check group rights if (!(obj.acl.object & (flag << 8))) { return false; } } } return true; } #getAllObjects(socket, callback) { if (typeof callback !== 'function') { return this.adapter.log.warn('[#getAllObjects] Invalid callback'); } if (this._checkPermissions(socket, 'getObjects', callback)) { if (this.objects) { if (socket._acl && socket._acl?.user !== 'system.user.admin' && !socket._acl.groups.includes('system.group.administrator')) { const result = {}; for (const id in this.objects) { if (Object.prototype.hasOwnProperty.call(this.objects, id) && _a.#checkObject(this.objects[id], socket._acl, ACL_READ /* 'read' */)) { result[id] = this.objects[id]; } } callback(null, result); } else { callback(null, this.objects); } } else { try { this.adapter.getObjectList({ include_docs: true }, { user: socket._acl?.user }, (_error, res) => { this.adapter.log.info('received all objects'); const rows = res?.rows || []; const objects = {}; if (socket._acl && socket._acl?.user !== 'system.user.admin' && !socket._acl.groups.includes('system.group.administrator')) { for (let i = 0; i < rows.length; i++) { if (_a.#checkObject(rows[i].doc, socket._acl, ACL_READ)) { objects[rows[i].doc._id] = rows[i].doc; } } callback(null, objects); } else { for (let j = 0; j < rows.length; j++) { objects[rows[j].doc._id] = rows[j].doc; } callback(null, objects); } }); } catch (error) { this.adapter.log.error(`[#getAllObjects] ERROR: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } } } _initCommandsUser() { /** * #DOCUMENTATION users * Add a new user. * * @param socket - WebSocket client instance * @param user - User name, e.g., `benjamin` * @param pass - User password * @param callback - Callback function `(error: string | null) => void` */ this.commands.addUser = (socket, user, pass, callback) => { if (this._checkPermissions(socket, 'addUser', callback, user)) { this.#addUser(user, pass, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } }; /** * #DOCUMENTATION users * Delete an existing user. Admin cannot be deleted. * * @param socket - WebSocket client instance * @param user - User name, e.g., `benjamin` * @param callback - Callback function `(error: string | null) => void` */ this.commands.delUser = (socket, user, callback) => { if (this._checkPermissions(socket, 'delUser', callback, user)) { this.#delUser(user, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } }; /** * #DOCUMENTATION users * Add a new group. * * @param socket - WebSocket client instance * @param group - Group name, e.g., `users` * @param desc - Optional description * @param acl - Optional access control list object, e.g., `{"object":{"list":true,"read":true,"write":false,"delete":false},"state":{"list":true,"read":true,"write":true,"create":true,"delete":false},"users":{"list":true,"read":true,"write":false,"create":false,"delete":false},"other":{"execute":false,"http":true,"sendto":false},"file":{"list":true,"read":true,"write":false,"create":false,"delete":false}}` * @param callback - Callback function `(error: string | null) => void` */ this.commands.addGroup = (socket, group, desc, acl, callback) => { if (this._checkPermissions(socket, 'addGroup', callback, group)) { this.#addGroup(group, desc, acl, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } }; /** * #DOCUMENTATION users * Delete an existing group. Administrator group cannot be deleted. * * @param socket - WebSocket client instance * @param group - Group name, e.g., `users` * @param callback - Callback function `(error: string | null) => void` */ this.commands.delGroup = (socket, group, callback) => { if (this._checkPermissions(socket, 'delGroup', callback, group)) { this.#delGroup(group, { user: socket._acl?.user || '' }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } }; /** * #DOCUMENTATION users * Change user password. * * @param socket - WebSocket client instance * @param user - User name, e.g., `benjamin` * @param pass - New password * @param callback - Callback function `(error: string | null) => void` */ this.commands.changePassword = (socket, user, pass, callback) => { if (user === socket._acl?.user || this._checkPermissions(socket, 'changePassword', callback, user)) { try { void this.adapter.setPassword(user, pass, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } catch (error) { this.adapter.log.error(`[_changePassword] ERROR: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }; } _initCommandsAdmin() { /** * #DOCUMENTATION admin * Read the host object by IP address. * * @param socket - WebSocket client instance * @param ip - IP address, e.g., `192.168.1.1`. IPv4 or IPv6 * @param callback - Callback function `(ip: string, obj: ioBroker.HostObject | null) => void` */ this.commands.getHostByIp = (socket, ip, callback) => { if (typeof callback !== 'function') { return this.adapter.log.warn('[getHostByIp] Invalid callback'); } if (this._checkPermissions(socket, 'getHostByIp', callback, ip)) { try { this.adapter.getObjectView('system', 'host', {}, { user: socket._acl?.user }, (error, data) => { if (data?.rows?.length) { for (let i = 0; i < data.rows.length; i++) { const obj = data.rows[i].value; // if we requested specific name if (obj.common.hostname === ip) { return callback(ip, obj); } if (obj.native.hardware?.networkInterfaces) { // try to find this IP in the list const net = obj.native.hardware.networkInterfaces; for (const eth in net) { if (!Object.prototype.hasOwnProperty.call(net, eth) || !net[eth]) { continue; } for (let j = 0; j < net[eth].length; j++) { if (net[eth][j].address === ip) { return callback(ip, obj); } } } } } } callback(ip, null); }); } catch (error) { this.adapter.log.error(`[_changePassword] ERROR: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }; /** * #DOCUMENTATION admin * Activate or deactivate logging events. Events will be sent to the socket as `log` event. Adapter must have `common.logTransporter = true`. * * @param socket - WebSocket client instance * @param isEnabled - Is logging enabled * @param callback - Callback function `(error: string | null) => void` */ this.commands.requireLog = (socket, isEnabled, callback) => { if (this._checkPermissions(socket, 'setObject', callback)) { if (isEnabled) { this.subscribe(socket, 'log', 'dummy'); } else { this.unsubscribe(socket, 'log', 'dummy'); } if (this.adapter.log.level === 'debug') { this._showSubscribes(socket, 'log'); } if (typeof callback === 'function') { setImmediate(callback, null); } } }; /** * #DOCUMENTATION admin * Get the log files from the given host. * * @param socket - WebSocket client instance * @param host - Host ID, e.g., `system.host.raspberrypi` * @param callback - Callback function `(error: string | null, list?: { fileName: string; size: number }[]) => void` */ this.commands.readLogs = (socket, host, callback) => { if (this._checkPermissions(socket, 'readLogs', callback)) { let timeout = setTimeout(() => { if (timeout) { let result = { list: [] }; // deliver the file list try { const config = this.adapter.systemConfig; // detect file log if (config?.log?.transport) { for (const transport in config.log.transport) { if (Object.prototype.hasOwnProperty.call(config.log.transport, transport) && config.log.transport[transport].type === 'file') { let fileName = config.log.transport[transport].filename || 'log/'; const parts = fileName.replace(/\\/g, '/').split('/'); parts.pop(); fileName = parts.join('/'); if (fileName[0] !== '/' && !fileName.match(/^\W:/)) { const _filename = (0, node_path_1.normalize)(`${__dirname}/../../../`) + fileName; if (!(0, node_fs_1.existsSync)(_filename)) { fileName = (0, node_path_1.normalize)(`${__dirname}/../../`) + fileName; } else { fileName = _filename; } } if ((0, node_fs_1.existsSync)(fileName)) { const files = (0, node_fs_1.readdirSync)(fileName); for (let f = 0; f < files.length; f++) { try { if (!files[f].endsWith('-audit.json')) { const stat = (0, node_fs_1.lstatSync)(`${fileName}/${files[f]}`); if (!stat.isDirectory()) { result.list?.push({ fileName: `log/${transport}/${files[f]}`, size: stat.size, }); } } } catch { // push unchecked // result.list.push('log/' + transport + '/' + files[f]); this.adapter.log.error(`Cannot check file: ${fileName}/${files[f]}`); } } } } } } else { result = { error: 'no file loggers' }; } } catch (error) { this.adapter.log.error(`Cannot read logs: ${error}`); result = { error }; } socketCommands_1.SocketCommands._fixCallback(callback, result.error, result.list); } }, 500); this._sendToHost(host, 'getLogFiles', null, (result) => { if (timeout) { clearTimeout(timeout); timeout = null; } socketCommands_1.SocketCommands._fixCallback(callback, result.error, result.list); }); } }; /** * #DOCUMENTATION states * Delete a state. The corresponding object will be deleted too. * * @param socket - WebSocket client instance * @param id - State ID * @param callback - Callback function `(error: string | null) => void` */ this.commands.delState = (socket, id, callback) => { if (this._checkPermissions(socket, 'delState', callback, id)) { // clear cache if (this.states?.[id]) { delete this.states[id]; } try { this.adapter.delForeignState(id, { user: socket._acl?.user }, (error, ...args) => socketCommands_1.SocketCommands._fixCallback(callback, error, ...args)); } catch (error) { this.adapter.log.error(`[delState] ERROR: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }; /** * #DOCUMENTATION admin * Execute the shell command on host/controller. * Following response commands are expected: `cmdStdout`, `cmdStderr`, `cmdExit`. * * @param socket - WebSocket client instance * @param host - Host name, e.g., `system.host.raspberrypi` * @param id - Session ID, e.g., `Date.now()`. This session ID will come in events `cmdStdout`, `cmdStderr`, `cmdExit` * @param cmd - Command to execute * @param callback - Callback function `(error: string | null) => void` */ this.commands.cmdExec = (socket, host, id, cmd, callback) => { if (id === undefined) { this.adapter.log.error(`cmdExec no session ID for "${cmd}"`); socketCommands_1.SocketCommands._fixCallback(callback, 'no session ID'); } else if (this._checkPermissions(socket, 'cmdExec', callback, cmd)) { this.adapter.log.debug(`cmdExec on ${host}(${id}): ${cmd}`); // remember socket for this ID. this.cmdSessions[id] = { socket }; try { this.adapter.sendToHost(host, 'cmdExec', { data: cmd, id }); socketCommands_1.SocketCommands._fixCallback(callback, null); } catch (error) { this.adapter.log.error(`[cmdExec] ERROR: ${error.toString()}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }; /** * #DOCUMENTATION admin * Enable or disable the event threshold. Used only for admin to limit the number of events to the front-end. * * @param _socket - WebSocket client instance * @param isActive - If true, then events will be limited */ this.commands.eventsThreshold = (_socket, isActive) => { if (!isActive) { this.disableEventThreshold(); } else { this.#enableEventThreshold(); } }; /** * #DOCUMENTATION admin * Get the ratings of adapters. * * @param _socket - WebSocket client instance * @param update - If true, the ratings will be read from the central server, if false from the local cache * @param callback - Callback function `(error: string | null, ratings?: Ratings) => void` */ this.commands.getRatings = (_socket, update, callback) => { if (typeof update === 'function') { callback = update; update = false; } if (update || !this.context.ratings) { void this.updateRatings().then(() => socketCommands_1.SocketCommands._fixCallback(callback, null, this.context.ratings)); } else { socketCommands_1.SocketCommands._fixCallback(callback, null, this.context.ratings); } }; /** * #DOCUMENTATION admin * Get the current instance name, like "admin.0" * * @param _socket - WebSocket client instance * @param callback - Callback function `(error: string | null, namespace?: string) => void` */ this.commands.getCurrentInstance = (_socket, callback) => { socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.namespace); }; /** * #DOCUMENTATION admin * Decrypts text with the system secret key. * * @param socket - WebSocket client instance * @param encryptedText - Encrypted text * @param callback - Callback function `(error: string | null, decryptedText?: string) => void` */ this.commands.decrypt = (socket, encryptedText, callback) => { if (this.secret) { socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.decrypt(this.secret, encryptedText)); } else { try { void this.adapter.getForeignObject('system.config', { user: socket._acl?.user }, (error, obj) => { if (obj?.native?.secret) { this.secret = obj.native.secret; socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.decrypt(this.secret, encryptedText)); } else { this.adapter.log.error(`No system.config found: ${error}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } }); } catch (error) { this.adapter.log.error(`Cannot decrypt: ${error}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } }; /** * #DOCUMENTATION admin * Encrypts text with the system secret key. * * @param socket - WebSocket client instance * @param plainText - Plain text to encrypt * @param callback - Callback function `(error: string | null, encryptedText?: string) => void` */ this.commands.encrypt = (socket, plainText, callback) => { if (this.secret) { socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.encrypt(this.secret, plainText)); } else { void this.adapter.getForeignObject('system.config', { user: socket._acl?.user }, (error, obj) => { if (obj?.native?.secret) { this.secret = obj.native.secret; try { const encrypted = this.adapter.encrypt(this.secret, plainText); socketCommands_1.SocketCommands._fixCallback(callback, null, encrypted); } catch (error) { this.adapter.log.error(`Cannot encrypt: ${error}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } } else { this.adapter.log.error(`No system.config found: ${error}`); socketCommands_1.SocketCommands._fixCallback(callback, error); } }); } }; /** * #DOCUMENTATION admin * Get if the admin has easy mode enabled. * * @param _socket - WebSocket client instance * @param callback - Callback function `(error: string | null, isEasyModeStrict?: boolean) => void` */ this.commands.getIsEasyModeStrict = (_socket, callback) => { socketCommands_1.SocketCommands._fixCallback(callback, null, this.adapter.config.accessLimit); }; /** * #DOCUMENTATION admin * Get easy mode configuration. * * @param socket - WebSocket client instance * @param callback - Callback function `(error: string | null, easyModeConfig?: { strict: boolean; configs: InstanceConfig[] }) => void` */ this.commands.getEasyMode = (socket, callback) => { if (this._checkPermissions(socket, 'getObject', callback)) { let user; if (this.adapter.config.auth) { user = socket._acl?.user || ''; } else { user = this.adapter.config.defaultUser || socket._acl?.user || ''; } if (!user.startsWith('system.user.')) { user = `system.user.${user}`; } if (this.adapter.config.accessLimit) { const configs = []; const promises = []; this.adapter.config.accessAllowedConfigs?.forEach(id => promises.push(this.#readInstanceConfig(id, user, false, configs))); this.adapter.config.accessAllowedTabs?.forEach(id => promises.push(this.#readInstanceConfig(id, user, true, configs))); void Promise.all(promises).then(() => { socketCommands_1.SocketCommands._fixCallback(callback, null, { strict: true, configs, }); }); } else { this.adapter.getObjectView('system', 'instance', { startkey: 'system.adapter.', endkey: 'system.adapter.\u9999' }, { user }, (error, doc) => { const configs = []; const promises = []; if (!error && doc?.rows?.length) { for (let i = 0; i < doc.rows.length; i++) { const obj = doc.rows[i].value; if (obj.common.noConfig && !obj.common.adminTab) { continue; } if (!obj.common.enabled) { continue; } if (!obj.common.noConfig) { promises.push(this.#readInstanceConfig(obj._id.substring('system.adapter.'.length), user, false, configs)); } } }