/* jshint -W097 */ /* jshint -W083 */ /* jshint strict:false */ /* jslint node: true */ /* jshint shadow:true */ 'use strict'; let NodeVM; let VMScript; let vm; if (true || parseInt(process.versions.node.split('.')[0]) < 6) { vm = require('vm'); } else { try { const VM2 = require('vm2'); NodeVM = VM2.NodeVM; VMScript = VM2.VMScript; } catch (e) { vm = require('vm'); } } const nodeFS = require('fs'); const coffeeCompiler = require('coffee-compiler'); const tsc = require('virtual-tsc'); const typescript = require('typescript'); const nodeSchedule = require('node-schedule'); const mods = { fs: {}, dgram: require('dgram'), crypto: require('crypto'), dns: require('dns'), events: require('events'), http: require('http'), https: require('https'), net: require('net'), os: require('os'), path: require('path'), util: require('util'), child_process: require('child_process'), suncalc: require('suncalc'), request: require('request'), wake_on_lan: require('wake_on_lan') }; const utils = require('./lib/utils'); // Get common adapter utils const words = require('./lib/words'); const sandBox = require('./lib/sandbox'); const eventObj = require('./lib/eventObj'); // for node version <= 0.12 if (''.startsWith === undefined) { String.prototype.startsWith = function (s) { return this.indexOf(s) === 0; }; } if (''.endsWith === undefined) { String.prototype.endsWith = function (s) { return this.slice(0 - s.length) === s; }; } /// let webstormDebug; if (process.argv) { for (let a = 1; a < process.argv.length; a++) { if (process.argv[a].startsWith('--webstorm')) { webstormDebug = process.argv[a].replace(/^(.*?=\s*)/, ''); break; } } } /** @type {typescript.CompilerOptions} */ const tsCompilerOptions = { // don't compile faulty scripts noEmitOnError: true, // emit declarations for global scripts declaration: true, // In order to run scripts as a NodeJS vm.Script, // we need to target ES5, otherwise the compiled // scripts may include `import` keywords, which are not // supported by vm.Script. target: typescript.ScriptTarget.ES5, // NodeJS 4+ supports the features of ES2015 // When upgrading the minimum supported version to NodeJS 8 or higher, // consider changing this, so we get to support the newest features too lib: ['lib.es2015.d.ts'], }; const jsDeclarationCompilerOptions = Object.assign( {}, tsCompilerOptions, { // we only care about the declarations emitDeclarationOnly: true, // allow errors noEmitOnError: false, noImplicitAny: false, strict: false, } ); // ambient declarations for typescript /** @type {Record} */ let tsAmbient; /** @type {tsc.Server} */ let tsServer; /** @type {tsc.Server} */ let jsDeclarationServer; /** * @param {string} scriptID - The current script the declarations were generated from * @param {string} declarations */ function provideDeclarationsForGlobalScript(scriptID, declarations) { // Remember which declarations this global script had access to // we need this so the editor doesn't show a duplicate identifier error if (globalDeclarations != null && globalDeclarations !== '') { knownGlobalDeclarationsByScript[scriptID] = globalDeclarations; } // and concatenate the global declarations for the next scripts globalDeclarations += declarations + '\n'; // remember all previously generated global declarations, // so global scripts can reference each other const globalDeclarationPath = 'global.d.ts'; tsAmbient[globalDeclarationPath] = globalDeclarations; // make sure the next script compilation has access to the updated declarations tsServer.provideAmbientDeclarations({ [globalDeclarationPath]: globalDeclarations }); jsDeclarationServer.provideAmbientDeclarations({ [globalDeclarationPath]: globalDeclarations }); } /** * Translates a script ID to a filename for the compiler * @param {string} scriptID The ID of the script */ function scriptIdToTSFilename(scriptID) { return scriptID.replace(/^script.js./, '').replace(/\./g, '/') + '.ts'; } const context = { mods, objects: {}, states: {}, stateIds: [], errorLogFunction: null, subscriptions: [], adapterSubs: {}, subscribedPatterns: {}, cacheObjectEnums: {}, isEnums: false, // If some subscription wants enum channels: null, devices: null, logWithLineInfo: null, timers: {}, enums: [], timerId: 0, names: {}, scripts: {} }; const regExEnum = /^enum\./; const regExGlobalOld = /_global$/; const regExGlobalNew = /script\.js\.global\./; function checkIsGlobal(obj) { return regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id); } const adapter = new utils.Adapter({ name: 'javascript', useFormatDate: true, // load float formatting objectChange: (id, obj) => { if (regExEnum.test(id)) { // clear cache context.cacheObjectEnums = {}; } if (!obj) { // object deleted if (!context.objects[id]) return; // Script deleted => remove it if (context.objects[id].type === 'script' && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) { stop(id); const idActive = 'scriptEnabled.' + id.substring('script.js.'.length); adapter.delObject(idActive); adapter.delState(idActive); } removeFromNames(id); delete context.objects[id]; } else if (!context.objects[id]) { // New object context.objects[id] = obj; addToNames(obj); if (obj.type === 'script' && obj.common.engine === 'system.adapter.' + adapter.namespace) { // create states for scripts createActiveObject(id, obj.common.enabled); if (obj.common.enabled) { if (checkIsGlobal(obj)) { // restart adapter adapter.getForeignObject('system.adapter.' + adapter.namespace, (err, _obj) => { if (_obj) adapter.setForeignObject('system.adapter.' + adapter.namespace, _obj); }); return; } // Start script load(id); } } // added new script to this engine } else if (context.objects[id].common) { const n = getName(id); if (n !== context.objects[id].common.name) { if (n) removeFromNames(id); if (context.objects[id].common.name) addToNames(obj); } // Object just changed if (obj.type !== 'script') { context.objects[id] = obj; if (id === 'system.config') { // set langugae for debug messages if (context.objects['system.config'].common.language) words.setLanguage(context.objects['system.config'].common.language); } return; } if (checkIsGlobal(context.objects[id])) { // restart adapter adapter.getForeignObject('system.adapter.' + adapter.namespace, function (err, obj) { if (obj) { adapter.setForeignObject('system.adapter.' + adapter.namespace, obj); } }); return; } if (obj.common && obj.common.engine === 'system.adapter.' + adapter.namespace) { // create states for scripts createActiveObject(id, obj.common.enabled); } if ((context.objects[id].common.enabled && !obj.common.enabled) || (context.objects[id].common.engine === 'system.adapter.' + adapter.namespace && obj.common.engine !== 'system.adapter.' + adapter.namespace)) { // Script disabled if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) { // Remove it from executing context.objects[id] = obj; stop(id); } else { context.objects[id] = obj; } } else if ((!context.objects[id].common.enabled && obj.common.enabled) || (context.objects[id].common.engine !== 'system.adapter.' + adapter.namespace && obj.common.engine === 'system.adapter.' + adapter.namespace)) { // Script enabled context.objects[id] = obj; if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) { // Start script load(id); } } else { //if (obj.common.source !== context.objects[id].common.source) { context.objects[id] = obj; // Source changed => restart it stop(id, function (res, _id) { load(_id); }); } /*else { // Something changed or not for us objects[id] = obj; }*/ } }, stateChange: (id, state) => { if (!id || id.startsWith('messagebox.') || id.startsWith('log.')) return; const oldState = context.states[id]; if (state) { if (oldState) { // enable or disable script if (!state.ack && id.startsWith(activeStr) && context.objects[id] && context.objects[id].native && context.objects[id].native.script) { adapter.extendForeignObject(context.objects[id].native.script, {common: {enabled: state.val}}); } // monitor if adapter is alive and send all subscriptions once more, after adapter goes online if (/*oldState && */oldState.val === false && state.val && id.endsWith('.alive')) { if (context.adapterSubs[id]) { const parts = id.split('.'); const a = parts[2] + '.' + parts[3]; for (let t = 0; t < context.adapterSubs[id].length; t++) { adapter.log.info('Detected coming adapter "' + a + '". Send subscribe: ' + context.adapterSubs[id][t]); adapter.sendTo(a, 'subscribe', context.adapterSubs[id][t]); } } } } else { if (/*!oldState && */context.stateIds.indexOf(id) === -1) { context.stateIds.push(id); context.stateIds.sort(); } } context.states[id] = state; } else { if (oldState) delete context.states[id]; state = {}; const pos = context.stateIds.indexOf(id); if (pos !== -1) { context.stateIds.splice(pos, 1); } } const _eventObj = eventObj.createEventObject(context, id, state, oldState); // if this state matches any subscriptions for (let i = 0, l = context.subscriptions.length; i < l; i++) { const sub = context.subscriptions[i]; if (sub && patternMatching(_eventObj, sub.patternCompareFunctions)) { sub.callback(_eventObj); } } }, unload: callback => callback(), ready: function () { // todo context.errorLogFunction = webstormDebug ? console : adapter.log; activeStr = adapter.namespace + '.scriptEnabled.'; // try to read TS declarations try { tsAmbient = { 'javascript.d.ts': nodeFS.readFileSync(mods.path.join(__dirname, 'lib/javascript.d.ts'), 'utf8') }; tsServer.provideAmbientDeclarations(tsAmbient); jsDeclarationServer.provideAmbientDeclarations(tsAmbient); } catch (e) { adapter.log.warn('Could not read TypeScript ambient declarations: ' + e); } context.logWithLineInfo = function (level, msg) { if (msg === undefined) { return context.logWithLineInfo ('info', msg); } context.errorLogFunction && context.errorLogFunction[level](msg); const stack = (new Error().stack).split('\n'); for (let i = 3; i < stack.length; i++) { if (!stack[i]) continue; if (stack[i].match(/runInContext|runInNewContext|javascript\.js:/)) break; context.errorLogFunction && context.errorLogFunction[level](fixLineNo(stack[i])); } }; context.logWithLineInfo.warn = context.logWithLineInfo.bind(1, 'warn'); context.logWithLineInfo.error = context.logWithLineInfo.bind(1, 'error'); context.logWithLineInfo.info = context.logWithLineInfo.bind(1, 'info'); installLibraries(() => { getData(() => { adapter.subscribeForeignObjects('*'); if (!adapter.config.subscribe) { adapter.subscribeForeignStates('*'); } adapter.objects.getObjectView('script', 'javascript', {}, (err, doc) => { globalScript = ''; globalDeclarations = ''; knownGlobalDeclarationsByScript = {}; let count = 0; if (doc && doc.rows && doc.rows.length) { // assemble global script for (let g = 0; g < doc.rows.length; g++) { if (checkIsGlobal(doc.rows[g].value)) { const obj = doc.rows[g].value; if (obj && obj.common.enabled) { if (obj.common.engineType.match(/^[cC]offee/)) { count++; coffeeCompiler.fromSource(obj.common.source, { sourceMap: false, bare: true }, function (err, js) { if (err) { adapter.log.error('coffee compile ' + err); return; } globalScript += js + '\n'; if (!--count) { globalScriptLines = globalScript.split(/\r\n|\n|\r/g).length; // load all scripts for (let i = 0; i < doc.rows.length; i++) { if (!checkIsGlobal(doc.rows[i].value)) { load(doc.rows[i].value._id); } } } }); } else if (obj.common.engineType.match(/^[tT]ype[sS]cript/)) { // compile the current global script const filename = scriptIdToTSFilename(obj._id); const tsCompiled = tsServer.compile(filename, obj.common.source); const errors = tsCompiled.diagnostics.map(diag => diag.annotatedSource + '\n').join('\n'); if (tsCompiled.success) { if (errors.length > 0) { adapter.log.warn('TypeScript compilation completed with errors: \n' + errors); } else { adapter.log.info('TypeScript compilation successful'); } globalScript += tsCompiled.result + '\n'; // if declarations were generated, remember them if (tsCompiled.declarations != null) { provideDeclarationsForGlobalScript(obj._id, tsCompiled.declarations); } } else { adapter.log.error('TypeScript compilation failed: \n' + errors); } } else { // javascript const sourceCode = obj.common.source; globalScript += sourceCode + '\n'; // try to compile the declarations so TypeScripts can use // functions defined in global JavaScripts const filename = scriptIdToTSFilename(obj._id); const tsCompiled = jsDeclarationServer.compile(filename, sourceCode); // if declarations were generated, remember them if (tsCompiled.success && tsCompiled.declarations != null) { provideDeclarationsForGlobalScript(obj._id, tsCompiled.declarations); } } } } } } if (!count) { globalScript = globalScript.replace(/\r\n/g, '\n'); globalScriptLines = globalScript.split(/\n/g).length - 1; if (doc && doc.rows && doc.rows.length) { // load all scripts for (let i = 0; i < doc.rows.length; i++) { if (!checkIsGlobal(doc.rows[i].value)) { load(doc.rows[i].value); } } } } }); }); }); }, message: (obj) => { if (obj) { switch (obj.command) { case 'loadTypings': { // Load typings for the editor const typings = {}; // try to load TypeScript lib files from disk const libFiles = [ // This is lib.es2015.d.ts: 'lib.es5.d.ts', 'lib.es2015.core.d.ts', 'lib.es2015.collection.d.ts', 'lib.es2015.generator.d.ts', 'lib.es2015.promise.d.ts', 'lib.es2015.iterable.d.ts', 'lib.es2015.proxy.d.ts', 'lib.es2015.reflect.d.ts', 'lib.es2015.symbol.d.ts', 'lib.es2015.symbol.wellknown.d.ts' ]; for (const libFile of libFiles) { try { const libPath = require.resolve(`typescript/lib/${libFile}`); typings[libFile] = nodeFS.readFileSync(libPath, 'utf8'); } catch (e) { /* ok, no lib then */ } } // try to load node.js typings from disk try { const nodeTypingsPath = require.resolve('@types/node/index.d.ts'); typings['node_modules/@types/node/index.d.ts'] = nodeFS.readFileSync(nodeTypingsPath, 'utf8'); } catch (e) { /* ok, no typings then */ } // provide the already-loaded yunkong2 typings and global script declarations Object.assign(typings, tsAmbient); // also provide the known global declarations for each global script for (const globalScriptPaths of Object.keys(knownGlobalDeclarationsByScript)) { typings[globalScriptPaths + '.d.ts'] = knownGlobalDeclarationsByScript[globalScriptPaths]; } if (obj.callback) { adapter.sendTo(obj.from, obj.command, { typings }, obj.callback); } break; } } } } }); function checkObjectsJson(file) { if (mods.path.normalize(file).replace(/\\/g, '/').indexOf('-data/objects.json') !== -1) { if (adapter) { adapter.log.error('May not read ' + file); } else { console.error('May not read ' + file); } throw new Error('Permission denied'); } } mods.fs.readFile = function () { checkObjectsJson(arguments[0]); return nodeFS.readFile.apply(this, arguments); }; mods.fs.readFileSync = function () { checkObjectsJson(arguments[0]); return nodeFS.readFileSync.apply(this, arguments); }; mods.fs.writeFile = function () { checkObjectsJson(arguments[0]); return nodeFS.writeFile.apply(this, arguments); }; mods.fs.writeFileSync = function () { checkObjectsJson(arguments[0]); return nodeFS.writeFileSync.apply(this, arguments); }; mods.fs.unlink = function () { checkObjectsJson(arguments[0]); return nodeFS.unlink.apply(this, arguments); }; mods.fs.unlinkSync = function () { checkObjectsJson(arguments[0]); return nodeFS.unlinkSync.apply(this, arguments); }; mods.fs.appendFile = function () { checkObjectsJson(arguments[0]); return nodeFS.appendFile.apply(this, arguments); }; mods.fs.appendFileSync = function () { checkObjectsJson(arguments[0]); return nodeFS.appendFileSync.apply(this, arguments); }; mods.fs.chmod = function () { checkObjectsJson(arguments[0]); return nodeFS.chmod.apply(this, arguments); }; mods.fs.chmodSync = function () { checkObjectsJson(arguments[0]); return nodeFS.chmodSync.apply(this, arguments); }; mods.fs.chown = function () { checkObjectsJson(arguments[0]); return nodeFS.chmodSync.apply(this, arguments); }; mods.fs.chownSync = function () { checkObjectsJson(arguments[0]); return nodeFS.chownSync.apply(this, arguments); }; mods.fs.copyFile = function () { checkObjectsJson(arguments[0]); checkObjectsJson(arguments[1]); if (nodeFS.copyFile) { return nodeFS.copyFile.apply(this, arguments); } else { const cb = arguments[2]; return nodeFS.readFile(arguments[0], (err, data) => { if (err) { cb && cb(err); } else { nodeFS.writeFile(arguments[1], data, err => cb && cb(err)); } }); } }; mods.fs.copyFileSync = function () { checkObjectsJson(arguments[0]); checkObjectsJson(arguments[1]); if (nodeFS.copyFileSync) { return nodeFS.copyFileSync.apply(this, arguments); } else { return nodeFS.writeFileSync(arguments[1], nodeFS.readFileSync(arguments[0])); } }; mods.fs.open = function () { checkObjectsJson(arguments[0]); return nodeFS.open.apply(this, arguments); }; mods.fs.openSync = function () { checkObjectsJson(arguments[0]); return nodeFS.openSync.apply(this, arguments); }; mods.fs.rename = function () { checkObjectsJson(arguments[0]); checkObjectsJson(arguments[1]); return nodeFS.rename.apply(this, arguments); }; mods.fs.renameSync = function () { checkObjectsJson(arguments[0]); checkObjectsJson(arguments[1]); return nodeFS.renameSync.apply(this, arguments); }; mods.fs.truncate = function () { checkObjectsJson(arguments[0]); return nodeFS.truncate.apply(this, arguments); }; mods.fs.truncateSync = function () { checkObjectsJson(arguments[0]); return nodeFS.truncateSync.apply(this, arguments); }; context.adapter = adapter; let attempts = {}; let globalScript = ''; /** Generated declarations for global TypeScripts */ let globalDeclarations = ''; // Remember which definitions the global scripts // have access to, because it depends on the compile order let knownGlobalDeclarationsByScript = {}; let globalScriptLines = 0; // let activeRegEx = null; let activeStr = ''; /** * Redirects the virtual-tsc log output to the yunkong2 log * @param {string} msg message * @param {string} sev severity (info, silly, debug, warn, error) */ function tsLog(msg, sev) { // shift the severities around, we don't care about the small details if (sev == null || sev === 'info') sev = 'debug'; else if (sev === 'debug') sev = 'silly'; if (adapter && adapter.log) { adapter.log[sev](msg); } else { console.log(`[${sev.toUpperCase()}] ${msg}`); } } // compiler instance for typescript tsServer = new tsc.Server(tsCompilerOptions, tsLog); // compiler instance for global JS declarations jsDeclarationServer = new tsc.Server(jsDeclarationCompilerOptions); function addGetProperty(object) { Object.defineProperty(object, 'get', { value: function (id) { return this[id] || this[adapter.namespace + '.' + id]; }, enumerable: false }); } function fixLineNo(line) { if (line.indexOf('javascript.js:') >= 0) return line; if (!/script[s]?\.js[.\\\/]/.test(line)) return line; if (/:([\d]+):/.test(line)) { line = line.replace(/:([\d]+):/, function ($0, $1) { return ':' + ($1 > globalScriptLines ? $1 - globalScriptLines : $1) + ':'; }); } else { line = line.replace(/:([\d]+)$/, function ($0, $1) { return ':' + ($1 > globalScriptLines ? $1 - globalScriptLines : $1); }); } return line; } context.logError = function (msg, e, offs) { const stack = e.stack.split('\n'); if (msg.indexOf('\n') < 0) { msg = msg.replace(/[: ]*$/, ': '); } //errorLogFunction.error(msg + stack[0]); context.errorLogFunction.error(msg + fixLineNo(stack[0])); for (let i = offs || 1; i < stack.length; i++) { if (!stack[i]) continue; if (stack[i].match(/runInNewContext|javascript\.js:/)) break; //adapter.log.error(fixLineNo(stack[i])); context.errorLogFunction.error(fixLineNo(stack[i])); } }; function createActiveObject(id, enabled, cb) { const idActive = adapter.namespace + '.scriptEnabled.' + id.substring('script.js.'.length); if (!context.objects[idActive]) { context.objects[idActive] = { _id: idActive, common: { name: 'scriptEnabled.' + id.substring('script.js.'.length), desc: 'controls script activity', type: 'boolean', role: 'switch.active', expert: true }, native: { script: id }, type: 'state' }; adapter.setForeignObject(idActive, context.objects[idActive], err => { if (!err) { adapter.setForeignState(idActive, enabled, true, cb); } else if (cb) { cb(); } }); } else { adapter.getForeignState(idActive, (err, state) => { if (state && state.val !== enabled) { adapter.setForeignState(idActive, enabled, true, cb); } else if (cb) { cb(); } }); } } function addToNames(obj) { const id = obj._id; if (obj.common && obj.common.name) { const name = obj.common.name; if (typeof name !== 'string') return; if (!context.names[name]) { context.names[name] = id; } else { if (typeof context.names[name] === 'string') { context.names[name] = [context.names[name]]; } context.names[name].push(id); } } } function removeFromNames(id) { const n = getName(id); if (n) { let pos; if (context.names[n] === 'object') { pos = context.names[n].indexOf(id); if (pos !== -1) { context.names[n].splice(pos, 1); if (context.names[n].length) { context.names[n] = context.names[n][0]; } } } else { delete context.names[n]; } } } function getName(id) { let pos; for (const n in context.names) { if (context.names.hasOwnProperty(n)) { if (context.names[n] && typeof context.names[n] === 'object') { pos = context.names[n].indexOf(id); if (pos !== -1) return n; } else if (context.names[n] === id) { return n; } } } return null; } function installNpm(npmLib, callback) { const path = __dirname; if (typeof npmLib === 'function') { callback = npmLib; npmLib = undefined; } const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '"'; adapter.log.info(cmd + ' (System call)'); // Install node modules as system call // System call used for update of js-controller itself, // because during installation npm packet will be deleted too, but some files must be loaded even during the install process. const child = mods['child_process'].exec(cmd); child.stdout.on('data', function (buf) { adapter.log.info(buf.toString('utf8')); }); child.stderr.on('data', function (buf) { adapter.log.error(buf.toString('utf8')); }); child.on('exit', function (code /* , signal */) { if (code) { adapter.log.error('Cannot install ' + npmLib + ': ' + code); } // command succeeded if (typeof callback === 'function') callback(npmLib); }); } function installLibraries(callback) { let allInstalled = true; if (adapter.config && adapter.config.libraries) { const libraries = adapter.config.libraries.split(/[,;\s]+/); for (let lib = 0; lib < libraries.length; lib++) { if (libraries[lib] && libraries[lib].trim()) { libraries[lib] = libraries[lib].trim(); if (!nodeFS.existsSync(__dirname + '/node_modules/' + libraries[lib] + '/package.json')) { if (!attempts[libraries[lib]]) { attempts[libraries[lib]] = 1; } else { attempts[libraries[lib]]++; } if (attempts[libraries[lib]] > 3) { adapter.log.error('Cannot install npm packet: ' + libraries[lib]); continue; } installNpm(libraries[lib], function () { installLibraries(callback); }); allInstalled = false; break; } } } } if (allInstalled) callback(); } function compile(source, name) { source += "\n;\nlog('registered ' + __engine.__subscriptions + ' subscription' + (__engine.__subscriptions === 1 ? '' : 's' ) + ' and ' + __engine.__schedules + ' schedule' + (__engine.__schedules === 1 ? '' : 's' ));\n"; try { if (VMScript) { return { script: new VMScript(source, name) }; } else { const options = { filename: name, displayErrors: true //lineOffset: globalScriptLines }; return { script: vm.createScript(source, options) }; } } catch (e) { context.logError(name + ' compile failed:\r\nat ', e); return false; } } function execute(script, name, verbose, debug) { script.intervals = []; script.timeouts = []; script.schedules = []; script.name = name; script._id = Math.floor(Math.random() * 0xFFFFFFFF); script.subscribes = {}; const sandbox = sandBox(script, name, verbose, debug, context); if (NodeVM) { const vm = new NodeVM({ sandbox, require: { external: true, builtin: ['*'], root: '', mock: mods } }); try { vm.run(script.script, name); } catch (e) { context.logError(name, e); } } else { try { script.script.runInNewContext(sandbox, { filename: name, displayErrors: true //lineOffset: globalScriptLines }); } catch (e) { context.logError(name, e); } } } function unsubscribe(id) { if (!id) { adapter.log.warn('unsubscribe: empty name'); return; } if (typeof id === 'object' && id && id.constructor && id.constructor.name === 'RegExp') { //adapter.log.warn('unsubscribe: todo - process regexp'); return; } if (typeof id !== 'string') { adapter.log.error('unsubscribe: invalid type of id - ' + typeof id); return; } const parts = id.split('.'); const _adapter = 'system.adapter.' + parts[0] + '.' + parts[1]; if (context.objects[_adapter] && context.objects[_adapter].common && context.objects[_adapter].common.subscribable) { const a = parts[0] + '.' + parts[1]; const alive = 'system.adapter.' + a + '.alive'; if (context.adapterSubs[alive]) { const pos = context.adapterSubs[alive].indexOf(id); if (pos !== -1) context.adapterSubs[alive].splice(pos, 1); if (!context.adapterSubs[alive].length) delete context.adapterSubs[alive]; } adapter.sendTo(a, 'unsubscribe', id); } } function stop(name, callback) { adapter.log.info('Stop script ' + name); adapter.setState('scriptEnabled.' + name.substring('script.js.'.length), false, true); if (context.scripts[name]) { // Remove from subscriptions context.isEnums = false; if (adapter.config.subscribe) { // check all subscribed IDs for (const id in context.scripts[name].subscribes) { if (!context.scripts[name].subscribes.hasOwnProperty(id)) continue; if (context.subscribedPatterns[id]) { context.subscribedPatterns[id] -= context.scripts[name].subscribes[id]; if (context.subscribedPatterns[id] <= 0) { adapter.unsubscribeForeignStates(id); delete context.subscribedPatterns[id]; if (context.states[id]) delete context.states[id]; } } } } for (let i = context.subscriptions.length - 1; i >= 0; i--) { if (context.subscriptions[i].name === name) { const sub = context.subscriptions.splice(i, 1)[0]; if (sub) { unsubscribe(sub.pattern.id); } } else { if (!context.isEnums && context.subscriptions[i].pattern.enumName || context.subscriptions[i].pattern.enumId) context.isEnums = true; } } // Stop all timeouts for (let i = 0; i < context.scripts[name].timeouts.length; i++) { clearTimeout(context.scripts[name].timeouts[i]); } // Stop all intervals for (let i = 0; i < context.scripts[name].intervals.length; i++) { clearInterval(context.scripts[name].intervals[i]); } // Stop all scheduled jobs for (let i = 0; i < context.scripts[name].schedules.length; i++) { if (context.scripts[name].schedules[i]) { const _name = context.scripts[name].schedules[i].name; if (!nodeSchedule.cancelJob(context.scripts[name].schedules[i])) { adapter.log.error('Error by canceling scheduled job "' + _name + '"'); } } } // if callback for on stop if (typeof context.scripts[name].onStopCb === 'function') { context.scripts[name].onStopTimeout = parseInt(context.scripts[name].onStopTimeout, 10) || 1000; let timeout = setTimeout(function () { if (timeout) { timeout = null; delete context.scripts[name]; if (typeof callback === 'function') callback(true, name); } }, context.scripts[name].onStopTimeout); try { context.scripts[name].onStopCb(function () { if (timeout) { clearTimeout(timeout); timeout = null; delete context.scripts[name]; if (typeof callback === 'function') callback(true, name); } }); } catch (e) { adapter.log.error('error in onStop callback: ' + e); } } else { delete context.scripts[name]; if (typeof callback === 'function') callback(true, name); } } else { if (typeof callback === 'function') callback(false, name); } } function prepareScript(obj, callback) { if (obj && obj.common.enabled && obj.common.engine === 'system.adapter.' + adapter.namespace && obj.common.source) { const name = obj._id; adapter.setState('scriptEnabled.' + name.substring('script.js.'.length), true, true); if ((obj.common.engineType.match(/^[jJ]ava[sS]cript/) || obj.common.engineType === 'Blockly')) { // Javascript adapter.log.info('Start javascript ' + name); let sourceFn = name; if (webstormDebug) { const fn = name.replace(/^script.js./, '').replace(/\./g, '/'); sourceFn = mods.path.join(webstormDebug, fn + '.js'); } context.scripts[name] = compile(globalScript + obj.common.source, sourceFn); if (context.scripts[name]) execute(context.scripts[name], sourceFn, obj.common.verbose, obj.common.debug); if (typeof callback === 'function') callback(true, name); } else if (obj.common.engineType.match(/^[cC]offee/)) { // CoffeeScript coffeeCompiler.fromSource(obj.common.source, { sourceMap: false, bare: true }, function (err, js) { if (err) { adapter.log.error(name + ' coffee compile ' + err); if (typeof callback === 'function') callback(false, name); return; } adapter.log.info('Start coffescript ' + name); context.scripts[name] = compile(globalScript + '\n' + js, name); if (context.scripts[name]) execute(context.scripts[name], name, obj.common.verbose, obj.common.debug); if (typeof callback === 'function') callback(true, name); }); } else if (obj.common.engineType.match(/^[tT]ype[sS]cript/)) { // TypeScript adapter.log.info(name + ': compiling TypeScript source...'); const filename = scriptIdToTSFilename(name); const tsCompiled = tsServer.compile(filename, obj.common.source); const errors = tsCompiled.diagnostics.map(function (diag) { return diag.annotatedSource + '\n'; }).join('\n'); if (tsCompiled.success) { if (errors.length > 0) { adapter.log.warn(name + ': TypeScript compilation had errors: \n' + errors); } else { adapter.log.info(name + ': TypeScript compilation successful'); } context.scripts[name] = compile(globalScript + '\n' + tsCompiled.result, name); if (context.scripts[name]) execute(context.scripts[name], name, obj.common.verbose, obj.common.debug); if (typeof callback === 'function') callback(true, name); } else { adapter.log.error(name + ': TypeScript compilation failed: \n' + errors); } } } else { let _name; if (obj && obj._id) { _name = obj._id; adapter.setState('scriptEnabled.' + _name.substring('script.js.'.length), false, true); } if (!obj) adapter.log.error('Invalid script'); if (typeof callback === 'function') callback(false, _name); } } function load(nameOrObject, callback) { if (typeof nameOrObject === 'object') { // create states for scripts createActiveObject(nameOrObject._id, nameOrObject && nameOrObject.common && nameOrObject.common.enabled, () => { return prepareScript(nameOrObject, callback); }); } else { adapter.getForeignObject(nameOrObject, function (err, obj) { if (!obj || err) { if (err) adapter.log.error('Invalid script "' + nameOrObject + '": ' + err); if (typeof callback === 'function') callback(false, nameOrObject); } else { return load(obj, callback); } }); } } function patternMatching(event, patternFunctions) { let matched = false; for (let i = 0, len = patternFunctions.length; i < len; i++) { if (patternFunctions[i](event)) { if (patternFunctions.logic === 'or') return true; matched = true; } else { if (patternFunctions.logic === 'and') return false; } } return matched; } function getData(callback) { let statesReady; let objectsReady; adapter.log.info('requesting all states'); adapter.getForeignStates('*', function (err, res) { if (!adapter.config.subscribe) { context.states = res; } addGetProperty(context.states); // remember all IDs for (const id in res) { if (res.hasOwnProperty(id)) { context.stateIds.push(id); } } statesReady = true; adapter.log.info('received all states'); if (objectsReady && typeof callback === 'function') callback(); }); adapter.log.info('requesting all objects'); adapter.objects.getObjectList({include_docs: true}, (err, res) => { res = res.rows; context.objects = {}; for (let i = 0; i < res.length; i++) { context.objects[res[i].doc._id] = res[i].doc; if (res[i].doc.type === 'enum') context.enums.push(res[i].doc._id); // Collect all names addToNames(context.objects[res[i].doc._id]); } addGetProperty(context.objects); // set language for debug messages if (context.objects['system.config'] && context.objects['system.config'].common.language) words.setLanguage(context.objects['system.config'].common.language); // try to use system coordinates if (adapter.config.useSystemGPS && context.objects['system.config'] && context.objects['system.config'].common.latitude) { adapter.config.latitude = context.objects['system.config'].common.latitude; adapter.config.longitude = context.objects['system.config'].common.longitude; } adapter.config.latitude = parseFloat(adapter.config.latitude); adapter.config.longitude = parseFloat(adapter.config.longitude); objectsReady = true; adapter.log.info('received all objects'); if (statesReady && typeof callback === 'function') callback(); }); }