UNPKG

iobroker.javascript

Version:
1,099 lines 207 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.sandBox = sandBox; const jsonataMod = __importStar(require("jsonata")); const adapter_core_1 = require("@iobroker/adapter-core"); const tools_1 = require("./tools"); const constsMod = __importStar(require("./consts")); const wordsMod = __importStar(require("./words")); const eventObjMod = __importStar(require("./eventObj")); const patternCompareFunctions_1 = require("./patternCompareFunctions"); const SCRIPT_CODE_MARKER = 'script.js.'; const pattern2RegEx = adapter_core_1.commonTools.pattern2RegEx; function sandBox(script, name, verbose, debug, context) { const consts = constsMod; const words = wordsMod; const eventObj = eventObjMod; const patternCompareFunctions = patternCompareFunctions_1.patternCompareFunctions; const jsonata = jsonataMod.default; const adapter = context.adapter; const mods = context.mods; const states = context.states; const objects = context.objects; const timers = context.timers; const enums = context.enums; const debugMode = context.debugMode; // eslint-disable-next-line prefer-const let sandbox; function errorInCallback(e) { void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, { val: true, ack: true, c: 'errorInCallback', }); context.logError(name, 'Error in callback:', e); context.debugMode && console.log(`error$$${name}$$Exception in callback: ${e}`, Date.now()); } function subscribePattern(script, pattern) { if (adapter.config.subscribe) { if (!script.subscribes[pattern]) { script.subscribes[pattern] = 1; } else { script.subscribes[pattern]++; } if (!context.subscribedPatterns[pattern]) { context.subscribedPatterns[pattern] = 1; if (sandbox.verbose) { sandbox.log(`subscribePattern(pattern=${pattern})`, 'info'); } adapter.subscribeForeignStates(pattern); // request current value to deliver old value on change. if (typeof pattern === 'string' && !pattern.includes('*')) { void adapter.getForeignState(pattern, (_err, state) => { if (state) { states[pattern] = state; } }); } else { adapter.getForeignStates(pattern, (_err, _states) => _states && Object.keys(_states).forEach(id => (states[id] = _states[id]))); } } else { context.subscribedPatterns[pattern]++; } } } function unsubscribePattern(script, pattern) { if (adapter.config.subscribe) { if (script.subscribes[pattern]) { script.subscribes[pattern]--; if (!script.subscribes[pattern]) { delete script.subscribes[pattern]; } } if (context.subscribedPatterns[pattern]) { context.subscribedPatterns[pattern]--; if (!context.subscribedPatterns[pattern]) { adapter.unsubscribeForeignStates(pattern); delete context.subscribedPatterns[pattern]; // if the pattern was regex or with * some states will stay in RAM, but it is OK. if (states[pattern]) { delete states[pattern]; } } } } } function subscribeFile(script, id, fileNamePattern) { const key = `${id}$%$${fileNamePattern}`; if (!script.subscribesFile[key]) { script.subscribesFile[key] = 1; } else { script.subscribesFile[key]++; } if (!context.subscribedPatternsFile[key]) { context.subscribedPatternsFile[key] = 1; void adapter.subscribeForeignFiles(id, fileNamePattern); } else { context.subscribedPatternsFile[key]++; } } function unsubscribeFile(script, id, fileNamePattern) { const key = `${id}$%$${fileNamePattern}`; if (script.subscribesFile[key]) { script.subscribesFile[key]--; if (!script.subscribesFile[key]) { delete script.subscribesFile[key]; } } if (context.subscribedPatternsFile[key]) { context.subscribedPatternsFile[key]--; if (!context.subscribedPatternsFile[key]) { void adapter.unsubscribeForeignFiles(id, fileNamePattern); delete context.subscribedPatternsFile[key]; } } } function getPatternCompareFunctions(pattern) { let func; const functions = []; functions.logic = pattern.logic || 'and'; for (const key in pattern) { if (!Object.prototype.hasOwnProperty.call(pattern, key)) { continue; } if (key === 'logic') { continue; } if (key === 'change' && pattern.change === 'any') { continue; } const _func = patternCompareFunctions[key]; if (!_func) { continue; } func = _func(pattern); if (typeof func !== 'function') { continue; } functions.push(func); } return functions; } /** * Splits a selector string into attribute and value * * @param selector The selector string to split */ function splitSelectorString(selector) { const parts = selector.split('=', 2); if (parts[1] && parts[1][0] === '"') { parts[1] = parts[1].substring(1); const len = parts[1].length; if (parts[1] && parts[1][len - 1] === '"') { parts[1] = parts[1].substring(0, len - 1); } } if (parts[1] && parts[1][0] === "'") { parts[1] = parts[1].substring(1); const len = parts[1].length; if (parts[1] && parts[1][len - 1] === "'") { parts[1] = parts[1].substring(0, len - 1); } } if (parts[1]) { parts[1] = parts[1].trim(); } parts[0] = parts[0].trim(); return { attr: parts[0], value: parts[1] }; } /** * Transforms a selector string with wildcards into a regular expression * * @param str The selector string to transform into a regular expression */ function selectorStringToRegExp(str) { const startsWithWildcard = str[0] === '*'; const endsWithWildcard = str[str.length - 1] === '*'; // Sanitize the selector, so it is safe to use in a RegEx // Taken from https://stackoverflow.com/a/3561711/10179833 but modified // since * has a special meaning in our selector and should not be escaped // eslint-disable-next-line no-useless-escape str = str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*'); return new RegExp((startsWithWildcard ? '' : '^') + str + (endsWithWildcard ? '' : '$')); } /** * Adds a regular expression for selectors targeting the state ID * * @param selector The selector to apply the transform to */ function addRegExpToIdAttrSelectors(selector) { if ((selector.attr === 'id' || selector.attr === 'state.id') && !selector.idRegExp && selector.value) { return { attr: selector.attr, value: selector.value, idRegExp: selectorStringToRegExp(selector.value), }; } return selector; } /** * Tests if a value loosely equals (==) the reference string. * In contrast to the equality operator, this treats true == "true" as well * so we can test common and native attributes for boolean values * * @param value The value to compare with the reference * @param reference The reference to compare the value to */ function looselyEqualsString(value, reference) { // For booleans, compare the string representation // For other types do a loose comparison return typeof value === 'boolean' ? (value && reference === 'true') || (!value && reference === 'false') : value == reference; } /** * Returns the `common.type` for a given variable */ function getCommonTypeOf(value) { return (0, tools_1.isArray)(value) ? 'array' : (0, tools_1.isObject)(value) ? 'object' : typeof value; } /** * Returns if an id is in an allowed namespace for automatic object creations * * @param id id to check */ function validIdForAutomaticFolderCreation(id) { return id.startsWith('javascript.') || id.startsWith('0_userdata.0.') || id.startsWith('alias.0.'); } /** * Iterate through object structure to create missing folder objects */ async function ensureObjectStructure(id) { if (!validIdForAutomaticFolderCreation(id)) { return; } if (context.folderCreationVerifiedObjects[id] === true) { return; } const idArr = id.split('.'); idArr.pop(); // the last is created as an object in any way if (idArr.length < 3) { return; // Nothing to do } // We just create sublevel projects let idToCheck = idArr.splice(0, 2).join('.'); context.folderCreationVerifiedObjects[id] = true; for (const part of idArr) { idToCheck += `.${part}`; if (context.folderCreationVerifiedObjects[idToCheck] === true || objects[idToCheck]) { continue; } context.folderCreationVerifiedObjects[idToCheck] = true; let obj; try { obj = await adapter.getForeignObjectAsync(idToCheck); } catch { // ignore } if (!obj?.common) { sandbox.log(`Create folder object for ${idToCheck}`, 'debug'); try { await adapter.setForeignObjectAsync(idToCheck, { _id: idToCheck, type: 'folder', common: { name: part, }, native: { autocreated: 'by automatic ensure logic', }, }); } catch (err) { sandbox.log(`Could not automatically create folder object ${idToCheck}: ${err.message}`, 'info'); } } else { //sandbox.log(` already existing "${idToCheck}": ${JSON.stringify(obj)}`, 'debug'); } } } function setStateHelper(sandbox, isCreate, isChanged, id, state, isAck, callback) { if (typeof isAck === 'function') { callback = isAck; isAck = undefined; } let stateNotNull; if (isAck === true || isAck === false || isAck === 'true' || isAck === 'false') { if (state && typeof state === 'object' && state.val !== undefined) { stateNotNull = state; // we assume that we were given a state object if // state is an object that contains a `val` property if (!Object.prototype.hasOwnProperty.call(state, 'ack')) { stateNotNull.ack = isAck === true || isAck === 'true'; } } else if (state === null) { stateNotNull = { val: null, ack: isAck === true || isAck === 'true' }; } else { // otherwise, assume that the given state is the value to be set stateNotNull = { val: state, ack: isAck === true || isAck === 'true' }; } } else if (state === null) { stateNotNull = { val: null }; } else { stateNotNull = state; } // Check a type of state if (!objects[id] && objects[`${adapter.namespace}.${id}`]) { id = `${adapter.namespace}.${id}`; } if (isCreate) { if (id.match(/^javascript\.\d+\.scriptEnabled/)) { sandbox.log(`Own states (${id}) should not be used in javascript.X.scriptEnabled.*! Please move the states to 0_userdata.0.*`, 'info'); } else if (id.match(/^javascript\.\d+\.scriptProblem/)) { sandbox.log(`Own states (${id}) should not be used in javascript.X.scriptProblem.*! Please move the states to 0_userdata.0.*`, 'info'); } } const common = objects[id] ? objects[id].common : null; if (common?.type && common.type !== 'mixed' && common.type !== 'json') { // Find out which type the value has let actualCommonType; if (typeof stateNotNull === 'object') { if (stateNotNull && stateNotNull.val !== undefined && stateNotNull.val !== null) { actualCommonType = getCommonTypeOf(stateNotNull.val); } } else if (stateNotNull !== null && stateNotNull !== undefined) { actualCommonType = getCommonTypeOf(stateNotNull); } // If this is not the expected one, issue a warning if (actualCommonType && actualCommonType !== common.type) { context.logWithLineInfo(`You are assigning a ${actualCommonType} to the state "${id}" which expects a ${common.type}. ` + `Please fix your code to use a ${common.type} or change the state type to ${actualCommonType}. ` + `This warning might become an error in future versions.`); } if (actualCommonType === 'array' || actualCommonType === 'object') { try { if (typeof stateNotNull === 'object' && typeof stateNotNull.val !== 'undefined') { stateNotNull.val = JSON.stringify(stateNotNull.val); } else { stateNotNull = JSON.stringify(stateNotNull); } } catch (err) { context.logWithLineInfo(`Could not stringify value for type ${actualCommonType} and id ${id}: ${err.message}`); if (typeof callback === 'function') { try { callback.call(sandbox, new Error(`Could not stringify value for type ${actualCommonType} and id ${id}: ${err.message}`)); } catch (err) { errorInCallback(err); } } } } } // Check min and max of value if (typeof stateNotNull === 'object') { if (common && typeof stateNotNull.val === 'number') { const num = stateNotNull.val; if (common.min !== undefined && num < common.min) { stateNotNull.val = common.min; } else if (common.max !== undefined && num > common.max) { stateNotNull.val = common.max; } } } else if (common && typeof stateNotNull === 'number') { const num = stateNotNull; if (common.min !== undefined && num < common.min) { stateNotNull = common.min; } if (common.max !== undefined && num > common.max) { stateNotNull = common.max; } } let stateAsObject; // modify state here, to make it available in callback if (stateNotNull === null || typeof stateNotNull !== 'object' || stateNotNull.val === undefined) { stateAsObject = context.prepareStateObject(id, { val: stateNotNull, ack: isAck === true || isAck === 'true', }); } else { stateAsObject = context.prepareStateObject(id, stateNotNull); } // set as comment: from which script this state was set. stateAsObject.c = sandbox.scriptName; if (objects[id]) { script.setStatePerMinuteCounter++; if (sandbox.verbose) { sandbox.log(`setForeignState(id=${id}, state=${JSON.stringify(stateAsObject)})`, 'info'); } if (debug) { sandbox.log(`setForeignState(id=${id}, state=${JSON.stringify(stateAsObject)}) - ${words._('was not executed, while debug mode is active')}`, 'warn'); if (typeof callback === 'function') { setImmediate(() => { try { callback.call(sandbox); } catch (err) { errorInCallback(err); } }); } } else { if (!adapter.config.subscribe) { // store actual state to make possible to process value in callback // risk that there will be an error on setState is very low, // but we will not store new state if the setStateChanged is called if (!isChanged) { context.interimStateValues[id] = stateAsObject; } } const errHandler = (err, funcId) => { err && sandbox.log(`${funcId}: ${err}`, 'error'); // If adapter holds all states if (err && !adapter.config.subscribe) { delete context.interimStateValues[id]; } if (typeof callback === 'function') { setImmediate(() => { try { callback.call(sandbox); } catch (err) { errorInCallback(err); } }); } }; if (isChanged) { if (!adapter.config.subscribe && context.interimStateValues[id]) { // if the state is changed, we will compare it with interimStateValues const oldState = context.interimStateValues[id]; const attrs = Object.keys(stateAsObject).filter(attr => attr !== 'ts' && stateAsObject[attr] !== undefined); if (!attrs.every(attr => stateAsObject[attr] === oldState[attr])) { // state is changed for sure, and we will call setForeignState // and store new state to interimStateValues context.interimStateValues[id] = stateAsObject; adapter.setForeignState(id, stateAsObject, err => errHandler(err, 'setForeignState')); } else { // otherwise - do nothing as we have cached state, except callback errHandler(null, 'setForeignStateCached'); } } else { // adapter doesn't hold all states, or it has not cached then we will simply call setForeignStateChanged adapter.setForeignStateChanged(id, { ...stateAsObject, ts: undefined }, err => errHandler(err, 'setForeignStateChanged')); } } else { adapter.setForeignState(id, stateAsObject, err => errHandler(err, 'setForeignState')); } } } else { context.logWithLineInfo(`State "${id}" not found`); if (typeof callback === 'function') { setImmediate(() => { try { callback.call(sandbox, new Error(`State "${id}" not found`)); } catch (err) { errorInCallback(err); } }); } } } sandbox = { mods, _id: script._id, // @deprecated use scriptName name, scriptName: name, instance: adapter.instance || 0, defaultDataDir: context.getAbsoluteDefaultDataDir(), verbose, exports: {}, // Polyfill for the export object in TypeScript modules require: function (md) { if (typeof md === 'string' && md.startsWith('node:')) { md = md.replace(/^node:/, ''); } if (md === 'request') { if (!sandbox.__engine.__deprecatedWarnings.includes(md)) { sandbox.log(`request package is deprecated - please use httpGet (or a stable lib like axios) instead!`, 'warn'); sandbox.__engine.__deprecatedWarnings.push(md); } } if (mods[md]) { return mods[md]; } let error; try { mods[md] = require(adapter.getAdapterScopedPackageIdentifier ? adapter.getAdapterScopedPackageIdentifier(md) : md); return mods[md]; } catch (e) { error = e; } try { // the user requires a module which is not specified in the additional node modules // for backward compatibility we check if the module can simply be required directly before we fail (e.g., direct dependencies of JavaScript adapter) adapter.log.debug(`Try direct require of "${md}" as not specified in the additional dependencies`); mods[md] = require(md); return mods[md]; } catch (e) { context.logError(name, `Error by loading module "${md}":`, error || e, 6); void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, { val: true, ack: true, c: 'require', }); } }, Buffer: Buffer, __engine: { __deprecatedWarnings: [], __subscriptionsObject: 0, __subscriptions: 0, __subscriptionsMessage: 0, __subscriptionsFile: 0, __subscriptionsLog: 0, __schedules: 0, }, $: function (selector) { // following is supported // 'type[commonAttr=something]', 'id[commonAttr=something]', id(enumName="something")', id{nativeName="something"} // Type can be state, channel or device // Attr can be any of the common attributes and can have wildcards * // E.g. "state[id='hm-rpc.0.*]" or "hm-rpc.0.*" returns all states of adapter instance hm-rpc.0 // channel(room="Living room") => all states in room "Living room" // channel{TYPE=BLIND}[state.id=*.LEVEL] // Switch all states with .STATE of channels with role "switch" in "Wohnzimmer" to false // $('channel[role=switch][state.id=*.STATE](rooms=Wohnzimmer)').setState(false); // // Following functions are possible, setValue, getValue (only from first), on, each // Todo CACHE!!! const result = {}; let name = ''; const commonStrings = []; const enumStrings = []; const nativeStrings = []; let isInsideName = true; let isInsideCommonString = false; let isInsideEnumString = false; let isInsideNativeString = false; let currentCommonString = ''; let currentNativeString = ''; let currentEnumString = ''; // parse string let selectorHasInvalidType = false; if (typeof selector === 'string') { for (let i = 0; i < selector.length; i++) { if (selector[i] === '{') { isInsideName = false; if (isInsideCommonString || isInsideEnumString || isInsideNativeString) { // Error break; } isInsideNativeString = true; } else if (selector[i] === '}') { isInsideNativeString = false; nativeStrings.push(currentNativeString); currentNativeString = ''; } else if (selector[i] === '[') { isInsideName = false; if (isInsideCommonString || isInsideEnumString || isInsideNativeString) { // Error break; } isInsideCommonString = true; } else if (selector[i] === ']') { isInsideCommonString = false; commonStrings.push(currentCommonString); currentCommonString = ''; } else if (selector[i] === '(') { isInsideName = false; if (isInsideCommonString || isInsideEnumString || isInsideNativeString) { // Error break; } isInsideEnumString = true; } else if (selector[i] === ')') { isInsideEnumString = false; enumStrings.push(currentEnumString); currentEnumString = ''; } else if (isInsideName) { name += selector[i]; } else if (isInsideCommonString) { currentCommonString += selector[i]; } else if (isInsideEnumString) { currentEnumString += selector[i]; } else if (isInsideNativeString) { currentNativeString += selector[i]; } //else { // some error //} } } else { selectorHasInvalidType = true; } // If some error in the selector if (selectorHasInvalidType || isInsideEnumString || isInsideCommonString || isInsideNativeString) { result.length = 0; result.toArray = function () { return []; }; result.each = function () { return this; }; result.getState = function () { return null; }; result.setState = function () { return this; }; result.on = function () { return this; }; } if (isInsideEnumString) { sandbox.log(`Invalid selector: enum close bracket ")" cannot be found in "${selector}"`, 'warn'); result.error = 'Invalid selector: enum close bracket ")" cannot be found'; return result; } else if (isInsideCommonString) { sandbox.log(`Invalid selector: common close bracket "]" cannot be found in "${selector}"`, 'warn'); result.error = 'Invalid selector: common close bracket "]" cannot be found'; return result; } else if (isInsideNativeString) { sandbox.log(`Invalid selector: native close bracket "}" cannot be found in "${selector}"`, 'warn'); result.error = 'Invalid selector: native close bracket "}" cannot be found'; return result; } else if (selectorHasInvalidType) { const message = `Invalid selector: selector must be a string but is of type ${typeof selector}`; sandbox.log(message, 'warn'); result.error = message; return result; } let commonSelectors = commonStrings.map(selector => splitSelectorString(selector)); let nativeSelectors = nativeStrings.map(selector => splitSelectorString(selector)); const enumSelectorObjects = enumStrings.map(_enum => splitSelectorString(_enum)); const allSelectors = commonSelectors.concat(nativeSelectors, enumSelectorObjects); // These selectors match the state or object ID and don't belong in the common/native selectors // Also use RegExp for the ID matching const stateIdSelectors = allSelectors .filter(selector => selector.attr === 'state.id') .map(selector => addRegExpToIdAttrSelectors(selector)); const objectIdSelectors = allSelectors .filter(selector => selector.attr === 'id') .map(selector => addRegExpToIdAttrSelectors(selector)); commonSelectors = commonSelectors.filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id'); nativeSelectors = nativeSelectors.filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id'); const enumSelectors = enumSelectorObjects .filter(selector => selector.attr !== 'state.id' && selector.attr !== 'id') // enums are filtered by their enum id, so transform the selector into that .map(selector => `enum.${selector.attr}.${selector.value}`); name = name.trim(); if (name === 'channel' || name === 'device') { // Fill the channels and devices objects with the IDs of all their states, // so we can loop over them afterward if (!context.channels || !context.devices) { context.channels = {}; context.devices = {}; for (const _id in objects) { if (Object.prototype.hasOwnProperty.call(objects, _id) && objects[_id].type === 'state') { const parts = _id.split('.'); parts.pop(); const chn = parts.join('.'); parts.pop(); const dev = parts.join('.'); context.devices[dev] = context.devices[dev] || []; context.devices[dev].push(_id); context.channels[chn] = context.channels[chn] || []; context.channels[chn].push(_id); } } } } if (name === 'schedule') { if (!context.schedules) { context.schedules = []; for (const _id in objects) { if (Object.prototype.hasOwnProperty.call(objects, _id) && objects[_id].type === 'schedule') { context.schedules.push(_id); } } } } /** * applies all selectors targeting an object or state ID */ function applyIDSelectors(objId, selectors) { // Only keep the ID if it matches every ID selector return selectors.every(selector => !selector.idRegExp || selector.idRegExp.test(objId)); } /** * Applies all selectors targeting the Object common properties * * @param objId - The ID of the object in question */ function applyCommonSelectors(objId) { const obj = objects[objId]; if (!obj?.common) { return false; } const objCommon = obj.common; // make sure this object satisfies all selectors return commonSelectors.every(selector => // ensure a property exists (selector.value === undefined && objCommon[selector.attr] !== undefined) || // or match exact values looselyEqualsString(objCommon[selector.attr], selector.value)); } /** * Applies all selectors targeting the Object native properties * * @param objId - The ID of the object in question */ function applyNativeSelectors(objId) { const obj = objects[objId]; if (!obj || !obj.native) { return false; } const objNative = obj.native; // make sure this object satisfies all selectors return nativeSelectors.every(selector => // ensure a property exists (selector.value === undefined && objNative[selector.attr] !== undefined) || // or match exact values looselyEqualsString(objNative[selector.attr], selector.value)); } /** * Applies all selectors targeting the Objects enums * * @param objId - The ID of the object in question */ function applyEnumSelectors(objId) { const enumIds = []; eventObj.getObjectEnumsSync(context, objId, enumIds); // make sure this object satisfies all selectors return enumSelectors.every(_enum => enumIds.includes(_enum)); } let res; if (name === 'schedule') { res = context.schedules || []; if (objectIdSelectors.length) { res = res.filter(channelId => applyIDSelectors(channelId, objectIdSelectors)); } // filter out those that don't match every common selector if (commonSelectors.length) { res = res.filter(id => applyCommonSelectors(id)); } // filter out those that don't match every native selector if (nativeSelectors.length) { res = res.filter(id => applyNativeSelectors(id)); } // filter out those that don't match every enum selector if (enumSelectors.length) { res = res.filter(channelId => applyEnumSelectors(channelId)); } } else if (name === 'channel') { if (!context.channels) { // TODO: fill the channels and maintain them on all places where context.stateIds will be changed } const channels = context.channels || {}; // go through all channels res = Object.keys(channels); // filter out those that don't match every ID selector for the channel ID if (objectIdSelectors.length) { res = res.filter(channelId => applyIDSelectors(channelId, objectIdSelectors)); } // filter out those that don't match every common selector if (commonSelectors.length) { res = res.filter(channelId => applyCommonSelectors(channelId)); } // filter out those that don't match every native selector if (nativeSelectors.length) { res = res.filter(channelId => applyNativeSelectors(channelId)); } // filter out those that don't match every enum selector if (enumSelectors.length) { res = res.filter(channelId => applyEnumSelectors(channelId)); } // retrieve the state ID collection for all remaining channels res = res .map(id => channels[id]) // and flatten the array to get only the state IDs .reduce((acc, next) => acc.concat(next), []); // now filter out those that don't match every ID selector for the state ID if (stateIdSelectors.length) { res = res.filter(stateId => applyIDSelectors(stateId, stateIdSelectors)); } } else if (name === 'device') { if (!context.devices) { // TODO: fill the devices and maintain them on all places where context.stateIds will be changed } const devices = context.devices || {}; // go through all devices res = Object.keys(devices); // filter out those that don't match every ID selector for the channel ID if (objectIdSelectors.length) { res = res.filter(deviceId => applyIDSelectors(deviceId, objectIdSelectors)); } // filter out those that don't match every common selector if (commonSelectors.length) { res = res.filter(deviceId => applyCommonSelectors(deviceId)); } // filter out those that don't match every native selector if (nativeSelectors.length) { res = res.filter(deviceId => applyNativeSelectors(deviceId)); } // filter out those that don't match every enum selector if (enumSelectors.length) { res = res.filter(deviceId => applyEnumSelectors(deviceId)); } // retrieve the state ID collection for all remaining devices res = res .map(id => devices[id]) // and flatten the array to get only the state IDs .reduce((acc, next) => acc.concat(next), []); // now filter out those that don't match every ID selector for the state ID if (stateIdSelectors.length) { res = res.filter(stateId => applyIDSelectors(stateId, stateIdSelectors)); } } else { // go through all states res = context.stateIds; // if the "name" is not state, then we filter for the ID as well if (name && name !== 'state') { const r = new RegExp(`^${name.replace(/\./g, '\\.').replace(/\*/g, '.*')}$`); res = res.filter(id => r.test(id)); } // filter out those that don't match every ID selector for the object ID or the state ID if (objectIdSelectors.length) { res = res.filter(id => applyIDSelectors(id, objectIdSelectors)); } // filter out those that don't match every ID selector for the state ID if (stateIdSelectors.length) { res = res.filter(id => applyIDSelectors(id, stateIdSelectors)); } // filter out those that don't match every common selector if (commonSelectors.length) { res = res.filter(id => applyCommonSelectors(id)); } // filter out those that don't match every native selector if (nativeSelectors.length) { res = res.filter(id => applyNativeSelectors(id)); } // filter out those that don't match every enum selector if (enumSelectors.length) { res = res.filter(id => applyEnumSelectors(id)); } } const resUnique = []; for (let i = 0; i < res.length; i++) { if (!resUnique.includes(res[i])) { resUnique.push(res[i]); } } for (let i = 0; i < resUnique.length; i++) { result[i] = resUnique[i]; } result.length = resUnique.length; // Implementing the Symbol.iterator contract makes the query result iterable result[Symbol.iterator] = function* () { for (let i = 0; i < result.length; i++) { yield result[i]; } }; result.toArray = function () { return [...resUnique]; }; result.each = function (callback) { if (typeof callback === 'function') { let r; for (let i = 0; i < this.length; i++) { r = callback(result[i], i); if (r === false) { break; } } } return this; }; // @ts-expect-error fix later result.getState = function (callback) { if (adapter.config.subscribe) { if (typeof callback !== 'function') { sandbox.log('You cannot use this function synchronous', 'error'); } else { void adapter.getForeignState(this[0], (err, state) => { void callback(err, context.convertBackStringifiedValues(this[0], state)); }); } } else { if (!this[0]) { return null; } if (context.interimStateValues[this[0]] !== undefined) { return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]); } return context.convertBackStringifiedValues(this[0], states[this[0]]); } }; result.getStateAsync = async function () { if (adapter.config.subscribe) { const state = await adapter.getForeignStateAsync(this[0]); return context.convertBackStringifiedValues(this[0], state); } if (!this[0]) { return null; } if (context.interimStateValues[this[0]] !== undefined) { return context.convertBackStringifiedValues(this[0], context.interimStateValues[this[0]]); } return context.convertBackStringifiedValues(this[0], states[this[0]]); }; result.setState = function (state, isAck, callback) { if (typeof isAck === 'function') { callback = isAck; isAck = undefined; } void result .setStateAsync(state, isAck) .then(() => typeof callback === 'function' && callback()); return this; }; result.setStateAsync = async function (state, isAck) { for (let i = 0; i < this.length; i++) { await sandbox.setStateAsync(this[i], state, isAck); } }; result.setStateChanged = function (state, isAck, callback) { if (typeof isAck === 'function') { callback = isAck; isAck = undefined; } void result.setStateChangedAsync(state, isAck).then(() => typeof callback === 'function' && callback()); return this; }; result.setStateChangedAsync = async function (state, isAck) { for (let i = 0; i < this.length; i++) { await sandbox.setStateChangedAsync(this[i], state, isAck); } }; result.setStateDelayed = function (state, isAck, delay, clearRunning, callback) { if (typeof isAck !== 'boolean') { callback = clearRunning; clearRunning = delay; delay = isAck; isAck = undefined; } if (typeof delay !== 'number') { callback = clearRunning; clearRunning = delay; delay = 0; } if (typeof clearRunning !== 'boolean') { callback = clearRunning; clearRunning = true; } let count = this.length; for (let i = 0; i < this.length; i++) { sandbox.setStateDelayed(this[i], state, isAck, delay, clearRunning, () => { if (!--count && typeof callback === 'function') { callback(); } }); } return this; }; result.on = function (callbackOrId, value) { for (let i = 0; i < this.length; i++) { sandbox.subscribe(this[i], callbackOrId, value); } return this; }; return result; }, log: function (msg, severity) { severity = severity || 'info'; // disable log in log handler (prevent endless loops) if (sandbox.logHandler && (sandbox.logHandler === severity || sandbox.logHandler === '*')) { return; } if (!adapter.log[severity]) { msg = `Unknown severity level "${severity}" by log of [${msg}]`; severity = 'warn'; } if (msg && typeof msg !== 'string') { msg = mods.util.format(msg); } if (debugMode) { console.log(`${severity}$$${name}$$${msg}`, Date.now()); } else { adapter.log[severity](`${name}: ${msg}`); } }, onLog: function (severity, callback) { if (!['info', 'error', 'debug', 'silly', 'warn', '*'].includes(severity)) { sandbox.log(`Unknown severity "${severity}"`, 'warn'); return 0; } if (typeof callback !== 'function') { sandbox.log(`Invalid callback for onLog`, 'warn'); return 0; } const handler = { id: Date.now() + Math.floor(Math.random() * 10000), cb: callback, sandbox, severity }; context.logSubscriptions[sandbox.scriptName] = context.logSubscriptions[sandbox.scriptName] || []; context.logSubscriptions[sandbox.scriptName].push(handler); context.updateLogSubscriptions(); sandbox.__engine.__subscriptionsLog += 1; sandbox.verbose && sandbox.log(`onLog(severity=${severity}, id=${handler.id}) - logSubscriptions=${sandbox.__engine.__subscriptionsLog}`, 'info'); if (sandbox.__engine.__subscriptionsLog % adapter.config.maxTriggersPerScript === 0) { sandbox.log(`More than ${sandbox.__engine.__subscriptionsLog} log subscriptions registered. Check your script!`, 'warn'); } return handler.id; }, onLogUnregister: function (idOrCallbackOrSeverity) { let found = false; if (context.logSubscriptions?.[sandbox.scriptName]) { sandbox.verbose && sandbox.log(`onLogUnregi