iobroker.javascript
Version:
Rules Engine for ioBroker
1,099 lines • 207 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;
};
})();
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