UNPKG

64.1 kBJavaScriptView Raw
1/*
2 * Javascript adapter
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2014-2020 bluefox <dogafox@gmail.com>,
7 *
8 * Copyright (c) 2014 hobbyquaker
9*/
10
11/* jshint -W097 */
12/* jshint -W083 */
13/* jshint strict: false */
14/* jslint node: true */
15/* jshint shadow: true */
16'use strict';
17
18let NodeVM;
19let VMScript;
20let vm;
21if (true || parseInt(process.versions.node.split('.')[0]) < 6) {
22 vm = require('vm');
23} else {
24 try {
25 const VM2 = require('vm2');
26 NodeVM = VM2.NodeVM;
27 VMScript = VM2.VMScript;
28 } catch (e) {
29 vm = require('vm');
30 }
31}
32const nodeFS = require('fs');
33const nodePath = require('path');
34const coffeeCompiler = require('coffee-compiler');
35const tsc = require('virtual-tsc');
36const typescript = require('typescript');
37const nodeSchedule = require('node-schedule');
38const Mirror = require('./lib/mirror');
39
40const mods = {
41 fs: {},
42 dgram: require('dgram'),
43 crypto: require('crypto'),
44 dns: require('dns'),
45 events: require('events'),
46 http: require('http'),
47 https: require('https'),
48 net: require('net'),
49 os: require('os'),
50 path: require('path'),
51 util: require('util'),
52 child_process: require('child_process'),
53 suncalc: require('suncalc2'),
54 request: require('./lib/request'),
55 wake_on_lan: require('wake_on_lan')
56};
57
58const utils = require('@iobroker/adapter-core'); // Get common adapter utils
59const words = require('./lib/words');
60const sandBox = require('./lib/sandbox');
61const eventObj = require('./lib/eventObj');
62const Scheduler = require('./lib/scheduler');
63const {
64 resolveTypescriptLibs,
65 resolveTypings,
66 scriptIdToTSFilename
67} = require('./lib/typescriptTools');
68
69const adapterName = require('./package.json').name.split('.').pop();
70const scriptCodeMarker = 'script.js.';
71
72// for node version <= 0.12
73if (''.startsWith === undefined) {
74 String.prototype.startsWith = function (s) {
75 return this.indexOf(s) === 0;
76 };
77}
78if (''.endsWith === undefined) {
79 String.prototype.endsWith = function (s) {
80 return this.slice(0 - s.length) === s;
81 };
82}
83///
84
85let webstormDebug;
86if (process.argv) {
87 for (let a = 1; a < process.argv.length; a++) {
88 if (process.argv[a].startsWith('--webstorm')) {
89 webstormDebug = process.argv[a].replace(/^(.*?=\s*)/, '');
90 break;
91 }
92 }
93}
94
95const isCI = !!process.env.CI;
96
97// NodeJS 8+ supports the features of ES2017
98// When upgrading the minimum supported version to NodeJS 10 or higher,
99// consider changing this, so we get to support the newest features too
100const targetTsLib = 'es2017';
101
102/** @type {typescript.CompilerOptions} */
103const tsCompilerOptions = {
104 // don't compile faulty scripts
105 noEmitOnError: true,
106 // emit declarations for global scripts
107 declaration: true,
108 // This enables TS users to `import * as ... from` and `import ... from`
109 esModuleInterop: true,
110 // In order to run scripts as a NodeJS vm.Script,
111 // we need to target ES5, otherwise the compiled
112 // scripts may include `import` keywords, which are not
113 // supported by vm.Script.
114 target: typescript.ScriptTarget.ES5,
115 lib: [`lib.${targetTsLib}.d.ts`],
116};
117
118const jsDeclarationCompilerOptions = Object.assign(
119 {}, tsCompilerOptions,
120 {
121 // we only care about the declarations
122 emitDeclarationOnly: true,
123 // allow errors
124 noEmitOnError: false,
125 noImplicitAny: false,
126 strict: false,
127 }
128);
129
130// ambient declarations for typescript
131/** @type {Record<string, string>} */
132let tsAmbient;
133/** @type {tsc.Server} */
134let tsServer;
135/** @type {tsc.Server} */
136let jsDeclarationServer;
137
138let mirror;
139
140/** @type {boolean} if logs are subscribed or not */
141let logSubscribed;
142
143/**
144 * @param {string} scriptID - The current script the declarations were generated from
145 * @param {string} declarations
146 */
147function provideDeclarationsForGlobalScript(scriptID, declarations) {
148 // Remember which declarations this global script had access to
149 // we need this so the editor doesn't show a duplicate identifier error
150 if (globalDeclarations != null && globalDeclarations !== '') {
151 knownGlobalDeclarationsByScript[scriptID] = globalDeclarations;
152 }
153 // and concatenate the global declarations for the next scripts
154 globalDeclarations += declarations + '\n';
155 // remember all previously generated global declarations,
156 // so global scripts can reference each other
157 const globalDeclarationPath = 'global.d.ts';
158 tsAmbient[globalDeclarationPath] = globalDeclarations;
159 // make sure the next script compilation has access to the updated declarations
160 tsServer.provideAmbientDeclarations({
161 [globalDeclarationPath]: globalDeclarations
162 });
163 jsDeclarationServer.provideAmbientDeclarations({
164 [globalDeclarationPath]: globalDeclarations
165 });
166}
167
168function loadTypeScriptDeclarations() {
169 // try to load the typings on disk for all 3rd party modules
170 const packages = [
171 'node', // this provides auto completion for most builtins
172 'request', // preloaded by the adapter
173 ];
174 // Also include user-selected libraries (but only those that are also installed)
175 if (
176 adapter.config
177 && typeof adapter.config.libraries === 'string'
178 && typeof adapter.config.libraryTypings === 'string'
179 ) {
180 const installedLibs = adapter.config.libraries.split(/[,;\s]+/).map(s => s.trim());
181 const wantsTypings = adapter.config.libraryTypings.split(/[,;\s]+/).map(s => s.trim());
182 for (const lib of installedLibs) {
183 if (
184 wantsTypings.indexOf(lib) > -1
185 && packages.indexOf(lib) === -1
186 ) {
187 packages.push(lib);
188 }
189 }
190 }
191 for (const pkg of packages) {
192 const pkgTypings = resolveTypings(
193 pkg,
194 // node needs ambient typings, so we don't wrap it in declare module
195 pkg !== 'node'
196 );
197 adapter.log.debug(`Loaded TypeScript definitions for ${pkg}: ${JSON.stringify(Object.keys(pkgTypings))}`);
198 // remember the declarations for the editor
199 Object.assign(tsAmbient, pkgTypings);
200 // and give the language servers access to them
201 tsServer.provideAmbientDeclarations(pkgTypings);
202 jsDeclarationServer.provideAmbientDeclarations(pkgTypings);
203 }
204}
205
206
207const context = {
208 mods,
209 objects: {},
210 states: {},
211 stateIds: [],
212 errorLogFunction: null,
213 subscriptions: [],
214 adapterSubs: {},
215 subscribedPatterns: {},
216 cacheObjectEnums: {},
217 isEnums: false, // If some subscription wants enum
218 channels: null,
219 devices: null,
220 logWithLineInfo: null,
221 scheduler: null,
222 timers: {},
223 enums: [],
224 timerId: 0,
225 names: {},
226 scripts: {},
227 messageBusHandlers: {},
228 logSubscriptions: {},
229 updateLogSubscriptions,
230 timeSettings: {
231 format12: false,
232 leadingZeros: true
233 }
234};
235
236const regExGlobalOld = /_global$/;
237const regExGlobalNew = /script\.js\.global\./;
238
239function checkIsGlobal(obj) {
240 return regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id);
241}
242
243let adapter;
244function startAdapter(options) {
245 options = options || {};
246 Object.assign(options, {
247
248 name: adapterName,
249
250 useFormatDate: true, // load float formatting
251
252 objectChange: (id, obj) => {
253 if (id.startsWith('enum.')) {
254 // clear cache
255 context.cacheObjectEnums = {};
256
257 // update context.enums array
258 if (obj) {
259 // If new
260 if (context.enums.indexOf(id) === -1) {
261 context.enums.push(id);
262 context.enums.sort();
263 }
264 } else {
265 const pos = context.enums.indexOf(id);
266 // if deleted
267 if (pos !== -1) {
268 context.enums.splice(pos, 1);
269 }
270 }
271 }
272
273 // update stored time format for variables.dayTime
274 if (id === adapter.namespace + '.variables.dayTime' && obj && obj.native) {
275 context.timeSettings.format12 = obj.native.format12 || false;
276 context.timeSettings.leadingZeros = obj.native.leadingZeros === undefined ? true : obj.native.leadingZeros;
277 }
278
279 // send changes to disk mirror
280 mirror && mirror.onObjectChange(id, obj);
281
282 if (obj) {
283 // add state to state ID's list
284 if (obj.type === 'state' && !context.stateIds.includes(id)) {
285 context.stateIds.push(id);
286 context.stateIds.sort();
287 }
288 } else {
289 // delete object from state ID's list
290 const pos = context.stateIds.indexOf(id);
291 pos !== -1 && context.stateIds.splice(pos, 1);
292 }
293
294 if (!obj) {
295 // object deleted
296 if (!context.objects[id]) return;
297
298 // Script deleted => remove it
299 if (context.objects[id].type === 'script' && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
300 stop(id);
301
302 // delete scriptEnabled.blabla variable
303 const idActive = 'scriptEnabled.' + id.substring('script.js.'.length);
304 adapter.delObject(idActive);
305 adapter.delState(idActive);
306
307 // delete scriptProblem.blabla variable
308 const idProblem = 'scriptProblem.' + id.substring('script.js.'.length);
309 adapter.delObject(idProblem);
310 adapter.delState(idProblem);
311 }
312
313 removeFromNames(id);
314 delete context.objects[id];
315 } else if (!context.objects[id]) {
316 // New object
317 context.objects[id] = obj;
318
319 addToNames(obj);
320
321 if (obj.type === 'script' && obj.common.engine === 'system.adapter.' + adapter.namespace) {
322 // create states for scripts
323 createActiveObject(id, obj.common.enabled, () => createProblemObject(id));
324
325 if (obj.common.enabled) {
326 if (checkIsGlobal(obj)) {
327 // restart adapter
328 adapter.getForeignObject('system.adapter.' + adapter.namespace, (err, _obj) =>
329 _obj && adapter.setForeignObject('system.adapter.' + adapter.namespace, _obj));
330 return;
331 }
332
333 // Start script
334 load(id);
335 }
336 }
337 // added new script to this engine
338 } else if (context.objects[id].common) {
339 const n = getName(id);
340
341 if (n !== context.objects[id].common.name) {
342 if (n) removeFromNames(id);
343 if (context.objects[id].common.name) addToNames(obj);
344 }
345
346 // Object just changed
347 if (obj.type !== 'script') {
348 context.objects[id] = obj;
349
350 if (id === 'system.config') {
351 // set language for debug messages
352 if (obj.common && obj.common.language) {
353 words.setLanguage(obj.common.language);
354 }
355 }
356
357 return;
358 }
359
360 // Analyse type = 'script'
361
362 if (checkIsGlobal(context.objects[id])) {
363 // restart adapter
364 adapter.getForeignObject('system.adapter.' + adapter.namespace, (err, obj) =>
365 obj && adapter.setForeignObject('system.adapter.' + adapter.namespace, obj));
366
367 return;
368 }
369
370 if (obj.common && obj.common.engine === 'system.adapter.' + adapter.namespace) {
371 // create states for scripts
372 createActiveObject(id, obj.common.enabled, () => createProblemObject(id));
373 }
374
375 if ((context.objects[id].common.enabled && !obj.common.enabled) ||
376 (context.objects[id].common.engine === 'system.adapter.' + adapter.namespace && obj.common.engine !== 'system.adapter.' + adapter.namespace)) {
377
378 // Script disabled
379 if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
380 // Remove it from executing
381 context.objects[id] = obj;
382 stop(id);
383 } else {
384 context.objects[id] = obj;
385 }
386 } else if ((!context.objects[id].common.enabled && obj.common.enabled) ||
387 (context.objects[id].common.engine !== 'system.adapter.' + adapter.namespace && obj.common.engine === 'system.adapter.' + adapter.namespace)) {
388 // Script enabled
389 context.objects[id] = obj;
390
391 if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
392 // Start script
393 load(id);
394 }
395 } else { //if (obj.common.source !== context.objects[id].common.source) {
396 context.objects[id] = obj;
397
398 // Source changed => restart it
399 stop(id, (res, _id) =>
400 load(_id));
401 } /*else {
402 // Something changed or not for us
403 objects[id] = obj;
404 }*/
405 }
406 },
407
408 stateChange: (id, state) => {
409 if (!id || id.startsWith('messagebox.') || id.startsWith('log.')) {
410 return;
411 }
412
413 const oldState = context.states[id];
414 if (state) {
415 if (oldState) {
416 // enable or disable script
417 if (!state.ack && id.startsWith(activeStr) && context.objects[id] && context.objects[id].native && context.objects[id].native.script) {
418 adapter.extendForeignObject(context.objects[id].native.script, { common: { enabled: state.val } });
419 }
420
421 // monitor if adapter is alive and send all subscriptions once more, after adapter goes online
422 if (/*oldState && */oldState.val === false && state.val && id.endsWith('.alive')) {
423 if (context.adapterSubs[id]) {
424 const parts = id.split('.');
425 const a = parts[2] + '.' + parts[3];
426 for (let t = 0; t < context.adapterSubs[id].length; t++) {
427 adapter.log.info('Detected coming adapter "' + a + '". Send subscribe: ' + context.adapterSubs[id][t]);
428 adapter.sendTo(a, 'subscribe', context.adapterSubs[id][t]);
429 }
430 }
431 }
432 } else if (/*!oldState && */context.stateIds.indexOf(id) === -1) {
433 context.stateIds.push(id);
434 context.stateIds.sort();
435 }
436 context.states[id] = state;
437 } else {
438 if (oldState) delete context.states[id];
439 state = {};
440 const pos = context.stateIds.indexOf(id);
441 if (pos !== -1) {
442 context.stateIds.splice(pos, 1);
443 }
444 }
445 const _eventObj = eventObj.createEventObject(context, id, state, oldState);
446
447 // if this state matches any subscriptions
448 for (let i = 0, l = context.subscriptions.length; i < l; i++) {
449 const sub = context.subscriptions[i];
450 if (sub && patternMatching(_eventObj, sub.patternCompareFunctions)) {
451 sub.callback(_eventObj);
452 }
453 }
454 },
455
456 unload: callback => {
457 stopTimeSchedules(adapter, context);
458 stopAllScripts(callback)
459 },
460
461 ready: () => {
462 mods.request.setLogger(adapter.log);
463
464 if (adapter.supportsFeature && adapter.supportsFeature('PLUGINS')) {
465 const sentryInstance = adapter.getPluginInstance('sentry');
466 if (sentryInstance) {
467 const Sentry = sentryInstance.getSentryObject();
468 if (Sentry) {
469 Sentry.configureScope(scope => {
470 scope.addEventProcessor((event, _hint) => {
471 if (event.exception && event.exception.values && event.exception.values[0]) {
472 const eventData = event.exception.values[0];
473 if (eventData.stacktrace && eventData.stacktrace.frames && Array.isArray(eventData.stacktrace.frames) && eventData.stacktrace.frames.length) {
474 // Exclude event if script Marker is included
475 if (eventData.stacktrace.frames.find(frame => frame.filename && frame.filename.includes(scriptCodeMarker))) {
476 return null;
477 }
478 //Exclude event if own directory is included but not inside own node_modules
479 const ownNodeModulesDir = nodePath.join(__dirname, 'node_modules');
480 if (!eventData.stacktrace.frames.find(frame => frame.filename && frame.filename.includes(__dirname) && !frame.filename.includes(ownNodeModulesDir))) {
481 return null;
482 }
483 }
484 }
485
486 return event;
487 });
488 main();
489 });
490 } else {
491 main();
492 }
493 } else {
494 main();
495 }
496 } else {
497 main();
498 }
499 },
500
501 message: obj => {
502 if (obj) {
503 switch (obj.command) {
504 // process messageTo commands
505 case 'jsMessageBus':
506 if (obj.message && (
507 obj.message.instance === null ||
508 obj.message.instance === undefined ||
509 ('javascript.' + obj.instance === adapter.namespace) ||
510 (obj.instance === adapter.namespace)
511 )) {
512 Object.keys(context.messageBusHandlers).forEach(name => {
513 // script name could be script.js.xxx or only xxx
514 if ((!obj.message.script || obj.message.script === name) && context.messageBusHandlers[name][obj.message.message]) {
515 context.messageBusHandlers[name][obj.message.message].forEach(handler => {
516 try {
517 if (obj.callback) {
518 handler.cb.call(handler.sandbox, obj.message.data, result =>
519 adapter.sendTo(obj.from, obj.command, result, obj.callback));
520 } else {
521 handler.cb.call(handler.sandbox, obj.message.data, result => {/* nop */ });
522 }
523 } catch (e) {
524 adapter.setState('scriptProblem.' + name.substring('script.js.'.length), true, true);
525 context.logError('Error in callback', e);
526 }
527 });
528 }
529 });
530 }
531 break;
532
533 case 'loadTypings': { // Load typings for the editor
534 const typings = {};
535
536 // try to load TypeScript lib files from disk
537 try {
538 const typescriptLibs = resolveTypescriptLibs(targetTsLib);
539 Object.assign(typings, typescriptLibs);
540 } catch (e) { /* ok, no lib then */
541 }
542
543 // provide the already-loaded ioBroker typings and global script declarations
544 Object.assign(typings, tsAmbient);
545
546 // also provide the known global declarations for each global script
547 for (const globalScriptPaths of Object.keys(knownGlobalDeclarationsByScript)) {
548 typings[globalScriptPaths + '.d.ts'] = knownGlobalDeclarationsByScript[globalScriptPaths];
549 }
550
551 if (obj.callback) {
552 adapter.sendTo(obj.from, obj.command, {typings}, obj.callback);
553 }
554 break;
555 }
556 case 'calcAstro': {
557 if (obj.message) {
558 const sunriseOffset = parseInt(obj.message.sunriseOffset === undefined ? adapter.config.sunriseOffset : obj.message.sunriseOffset, 10) || 0;
559 const sunsetOffset = parseInt(obj.message.sunsetOffset === undefined ? adapter.config.sunsetOffset : obj.message.sunsetOffset, 10) || 0;
560 const longitude = parseFloat(obj.message.longitude === undefined ? adapter.config.longitude : obj.message.longitude) || 0;
561 const latitude = parseFloat(obj.message.latitude === undefined ? adapter.config.latitude : obj.message.latitude) || 0;
562 const now = new Date();
563 const nextSunrise = getAstroEvent(
564 now,
565 obj.message.sunriseEvent || adapter.config.sunriseEvent,
566 obj.message.sunriseLimitStart || adapter.config.sunriseLimitStart,
567 obj.message.sunriseLimitEnd || adapter.config.sunriseLimitEnd,
568 sunriseOffset,
569 false,
570 latitude,
571 longitude
572 );
573 const nextSunset = getAstroEvent(
574 now,
575 obj.message.sunsetEvent || adapter.config.sunsetEvent,
576 obj.message.sunsetLimitStart || adapter.config.sunsetLimitStart,
577 obj.message.sunsetLimitEnd || adapter.config.sunsetLimitEnd,
578 sunsetOffset,
579 true,
580 latitude,
581 longitude
582 );
583
584 obj.callback && adapter.sendTo(obj.from, obj.command, {
585 nextSunrise,
586 nextSunset
587 }, obj.callback);
588 }
589 break;
590 }
591 }
592 }
593 },
594
595 /**
596 * If the JS-Controller catches an unhandled error, this will be called
597 * so we have a chance to handle it ourself.
598 * @param {Error} err
599 */
600 error: (err) => {
601 // Identify unhandled errors originating from callbacks in scripts
602 // These are not caught by wrapping the execution code in try-catch
603 if (err && typeof err.stack === 'string') {
604 const scriptCodeMarkerIndex = err.stack.indexOf(scriptCodeMarker);
605 if (scriptCodeMarkerIndex > -1) {
606 // This is a script error
607 let scriptName = err.stack.substr(scriptCodeMarkerIndex);
608 scriptName = scriptName.substr(0, scriptName.indexOf(':'));
609 context.logError(scriptName, err);
610
611 // Leave the script running for now
612 // signal to the JS-Controller that we handled the error ourselves
613 return true;
614 }
615 // check if a path contains adaptername but not own node_module
616 // this regex matched "iobroker.javascript/" if NOT followed by "node_modules"
617 if (!err.stack.match(/iobroker\.javascript[\/\\](?!.*node_modules).*/g)) {
618 // This is an error without any info on origin (mostly async errors like connection errors)
619 // also consider it as being from a script
620 adapter.log.error('An error happened which is most likely from one of your scripts, but the originating script could not be detected.')
621 adapter.log.error('Error: ' + err.message);
622 adapter.log.error(err.stack);
623
624 // signal to the JS-Controller that we handled the error ourselves
625 return true;
626 }
627 }
628 }
629 });
630
631 adapter = new utils.Adapter(options);
632
633 // handler for logs
634 adapter.on('log', msg =>
635 Object.keys(context.logSubscriptions)
636 .forEach(name =>
637 context.logSubscriptions[name].forEach(handler => {
638 if (typeof handler.cb === 'function' && (handler.severity === '*' || handler.severity === msg.severity)) {
639 handler.sandbox.logHandler = handler.severity || '*';
640 handler.cb.call(handler.sandbox, msg);
641 handler.sandbox.logHandler = null;
642 }
643 })));
644
645 context.adapter = adapter;
646
647 return adapter;
648}
649
650function main() {
651 // todo
652 context.errorLogFunction = webstormDebug ? console : adapter.log;
653 activeStr = adapter.namespace + '.scriptEnabled.';
654
655 mods.fs = new require('./lib/protectFs')(adapter.log);
656
657 // try to read TS declarations
658 try {
659 tsAmbient = {
660 'javascript.d.ts': nodeFS.readFileSync(mods.path.join(__dirname, 'lib/javascript.d.ts'), 'utf8')
661 };
662 tsServer.provideAmbientDeclarations(tsAmbient);
663 jsDeclarationServer.provideAmbientDeclarations(tsAmbient);
664 } catch (e) {
665 adapter.log.warn('Could not read TypeScript ambient declarations: ' + e);
666 }
667
668 context.logWithLineInfo = function (level, msg) {
669 if (msg === undefined) {
670 return context.logWithLineInfo('info', msg);
671 }
672
673 context.errorLogFunction && context.errorLogFunction[level](msg);
674
675 const stack = (new Error().stack).split('\n');
676
677 for (let i = 3; i < stack.length; i++) {
678 if (!stack[i]) continue;
679 if (stack[i].match(/runInContext|runInNewContext|javascript\.js:/)) break;
680 context.errorLogFunction && context.errorLogFunction[level](fixLineNo(stack[i]));
681 }
682 };
683
684 context.logWithLineInfo.warn = context.logWithLineInfo.bind(1, 'warn');
685 context.logWithLineInfo.error = context.logWithLineInfo.bind(1, 'error');
686 context.logWithLineInfo.info = context.logWithLineInfo.bind(1, 'info');
687
688 context.scheduler = new Scheduler(adapter.log, Date, mods.suncalc, adapter.config.latitude, adapter.config.longitude);
689
690 installLibraries(() => {
691
692 // Load the TS declarations for Node.js and all 3rd party modules
693 loadTypeScriptDeclarations();
694
695 getData(() => {
696 dayTimeSchedules(adapter, context);
697 timeSchedule(adapter, context);
698
699 adapter.subscribeForeignObjects('*');
700
701 if (!adapter.config.subscribe) {
702 adapter.subscribeForeignStates('*');
703 }
704
705 // Warning. It could have a side-effect in compact mode, so all adapters will accept self signed certificates
706 if (adapter.config.allowSelfSignedCerts) {
707 process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
708 }
709
710 adapter.getObjectView('script', 'javascript', {}, (err, doc) => {
711 globalScript = '';
712 globalDeclarations = '';
713 knownGlobalDeclarationsByScript = {};
714 let count = 0;
715 if (doc && doc.rows && doc.rows.length) {
716 // assemble global script
717 for (let g = 0; g < doc.rows.length; g++) {
718 if (checkIsGlobal(doc.rows[g].value)) {
719 const obj = doc.rows[g].value;
720
721 if (obj && obj.common.enabled) {
722 const engineType = (obj.common.engineType || '').toLowerCase();
723 if (engineType.startsWith('coffee')) {
724 count++;
725 coffeeCompiler.fromSource(obj.common.source, {
726 sourceMap: false,
727 bare: true
728 }, (err, js) => {
729 if (err) {
730 adapter.log.error('coffee compile ' + err);
731 return;
732 }
733 globalScript += js + '\n';
734 if (!--count) {
735 globalScriptLines = globalScript.split(/\r\n|\n|\r/g).length;
736 // load all scripts
737 for (let i = 0; i < doc.rows.length; i++) {
738 if (!checkIsGlobal(doc.rows[i].value)) {
739 load(doc.rows[i].value._id);
740 }
741 }
742 }
743 });
744 } else if (engineType.startsWith('typescript')) {
745 // compile the current global script
746 const filename = scriptIdToTSFilename(obj._id);
747 const tsCompiled = tsServer.compile(filename, obj.common.source);
748
749 const errors = tsCompiled.diagnostics.map(diag => diag.annotatedSource + '\n').join('\n');
750
751 if (tsCompiled.success) {
752 if (errors.length > 0) {
753 adapter.log.warn('TypeScript compilation completed with errors: \n' + errors);
754 } else {
755 adapter.log.info('TypeScript compilation successful');
756 }
757 globalScript += tsCompiled.result + '\n';
758
759 // if declarations were generated, remember them
760 if (tsCompiled.declarations != null) {
761 provideDeclarationsForGlobalScript(obj._id, tsCompiled.declarations);
762 }
763 } else {
764 adapter.log.error('TypeScript compilation failed: \n' + errors);
765 }
766 } else { // javascript
767 const sourceCode = obj.common.source;
768 globalScript += sourceCode + '\n';
769
770 // try to compile the declarations so TypeScripts can use
771 // functions defined in global JavaScripts
772 const filename = scriptIdToTSFilename(obj._id);
773 const tsCompiled = jsDeclarationServer.compile(filename, sourceCode);
774 // if declarations were generated, remember them
775 if (tsCompiled.success && tsCompiled.declarations != null) {
776 provideDeclarationsForGlobalScript(obj._id, tsCompiled.declarations);
777 }
778 }
779 }
780 }
781 }
782 }
783
784 if (!count) {
785 globalScript = globalScript.replace(/\r\n/g, '\n');
786 globalScriptLines = globalScript.split(/\n/g).length - 1;
787
788 if (doc && doc.rows && doc.rows.length) {
789 // load all scripts
790 for (let i = 0; i < doc.rows.length; i++) {
791 if (!checkIsGlobal(doc.rows[i].value)) {
792 load(doc.rows[i].value);
793 }
794 }
795 }
796 }
797
798 if (adapter.config.mirrorPath) {
799 mirror = new Mirror({
800 adapter,
801 log: adapter.log,
802 diskRoot: adapter.config.mirrorPath
803 });
804 }
805
806 });
807 });
808 });
809}
810
811function stopAllScripts(cb) {
812 Object.keys(context.scripts).forEach(id => stop(id));
813 setTimeout(() => cb(), 0);
814}
815
816const attempts = {};
817let globalScript = '';
818/** Generated declarations for global TypeScripts */
819let globalDeclarations = '';
820// Remember which definitions the global scripts
821// have access to, because it depends on the compile order
822let knownGlobalDeclarationsByScript = {};
823let globalScriptLines = 0;
824// let activeRegEx = null;
825let activeStr = ''; // enabled state prefix
826let timeVariableTimer = null; // timer to update dayTime every minute
827let daySchedule = null; // schedule for astrological day
828
829function getNextTimeEvent(time) {
830 const now = new Date();
831 let [timeHours, timeMinutes] = time.split(':');
832 timeHours = parseInt(timeHours, 10);
833 timeMinutes = parseInt(timeMinutes, 10);
834 if ((now.getHours() > timeHours) ||
835 (now.getHours() === timeHours && now.getMinutes() > timeMinutes)) {
836 now.setDate(now.getDate() + 1);
837 }
838
839 now.setHours(timeHours);
840 now.setMinutes(timeMinutes);
841 return now;
842}
843
844function getAstroEvent(now, astroEvent, start, end, offsetMinutes, isDayEnd, latitude, longitude) {
845 let ts = mods.suncalc.getTimes(now, latitude, longitude)[astroEvent];
846 if (!ts || ts.getTime().toString() === 'NaN') {
847 ts = isDayEnd ? getNextTimeEvent(end) : getNextTimeEvent(start);
848 }
849 ts.setSeconds(0);
850 ts.setMilliseconds(0);
851
852 let [timeHoursStart, timeMinutesStart] = start.split(':');
853 timeHoursStart = parseInt(timeHoursStart, 10);
854 timeMinutesStart = parseInt(timeMinutesStart, 10) || 0;
855
856 if (ts.getHours() < timeHoursStart || (ts.getHours() === timeHoursStart && ts.getMinutes() < timeMinutesStart)) {
857 ts = getNextTimeEvent(start);
858 }
859
860 let [timeHoursEnd, timeMinutesEnd] = end.split(':');
861 timeHoursEnd = parseInt(timeHoursEnd, 10);
862 timeMinutesEnd = parseInt(timeMinutesEnd, 10) || 0;
863
864 if (ts.getHours() > timeHoursEnd || (ts.getHours() === timeHoursEnd && ts.getMinutes() > timeMinutesEnd)) {
865 ts = getNextTimeEvent(end);
866 }
867
868 // if event in the past
869 if (now > ts) {
870 // take next day
871 ts.setDate(ts.getDate() + 1);
872 }
873 return ts;
874}
875
876function timeSchedule(adapter, context) {
877 const now = new Date();
878 let hours = now.getHours();
879 let minutes = now.getMinutes();
880 if (context.timeSettings.format12) {
881 if (hours > 12) {
882 hours -= 12;
883 }
884 }
885 if (context.timeSettings.leadingZeros && hours < 10) {
886 hours = '0' + hours;
887 }
888 if (minutes < 10) {
889 minutes = '0' + minutes;
890 }
891 adapter.setState('variables.dayTime', hours + ':' + minutes, true);
892 now.setMinutes(now.getMinutes() + 1);
893 now.setSeconds(0);
894 now.setMilliseconds(0);
895 const interval = now.getTime() - Date.now();
896 setTimeout(timeSchedule, interval, adapter, context);
897}
898
899function dayTimeSchedules(adapter, context) {
900 // get astrological event
901 if (adapter.config.latitude === undefined || adapter.config.longitude === undefined ||
902 adapter.config.latitude === '' || adapter.config.longitude === '' ||
903 adapter.config.latitude === null || adapter.config.longitude === null) {
904 adapter.log.error('Longitude or latitude does not set. Cannot use astro.');
905 return;
906 }
907
908 // Calculate next event;
909 const nowDate = new Date();
910
911 const nextSunrise = getAstroEvent(nowDate, adapter.config.sunriseEvent, adapter.config.sunriseLimitStart, adapter.config.sunriseLimitEnd, adapter.config.sunriseOffset, false, adapter.config.latitude, adapter.config.longitude);
912 const nextSunset = getAstroEvent(nowDate, adapter.config.sunsetEvent, adapter.config.sunsetLimitStart, adapter.config.sunsetLimitEnd, adapter.config.sunsetOffset, true, adapter.config.latitude, adapter.config.longitude);
913
914 // Sunrise
915 let sunriseTimeout = nextSunrise.getTime() - nowDate.getTime();
916 if (sunriseTimeout > 3600000) {
917 sunriseTimeout = 3600000;
918 }
919
920 // Sunset
921 let sunsetTimeout = nextSunset.getTime() - nowDate.getTime();
922 if (sunsetTimeout > 3600000) {
923 sunsetTimeout = 3600000;
924 }
925
926 let isDay;
927 if (sunriseTimeout < 5000) {
928 isDay = true;
929
930 } else if (sunsetTimeout < 5000) {
931 isDay = false;
932 } else {
933 // check if in between
934 // todo
935 const nowStartDate = new Date();
936 nowStartDate.setHours(0);
937 nowStartDate.setMinutes(0);
938 nowStartDate.setSeconds(0);
939 nowStartDate.setMilliseconds(0);
940 const todaySunrise = getAstroEvent(nowStartDate, adapter.config.sunriseEvent, adapter.config.sunriseLimitStart, adapter.config.sunriseLimitEnd, adapter.config.sunriseOffset, false);
941 const todaySunset = getAstroEvent(nowStartDate, adapter.config.sunsetEvent, adapter.config.sunsetLimitStart, adapter.config.sunsetLimitEnd, adapter.config.sunsetOffset, false);
942 isDay = nowDate > todaySunrise && nowDate <= todaySunset;
943 }
944
945 adapter.getState('variables.isDayTime', (err, state) => {
946 const val = state ? !!state.val : false;
947 if (val !== isDay) {
948 adapter.setState('variables.isDayTime', isDay, true);
949 }
950 });
951
952 let nextTimeout = sunriseTimeout;
953 if (sunriseTimeout > sunsetTimeout) {
954 nextTimeout = sunsetTimeout;
955 }
956 nextTimeout = nextTimeout - 3000;
957 if (nextTimeout < 3000) {
958 nextTimeout = 3000;
959 }
960
961 daySchedule = setTimeout(dayTimeSchedules, nextTimeout, adapter, context);
962}
963
964function stopTimeSchedules() {
965 daySchedule && clearTimeout(daySchedule);
966 timeVariableTimer && clearTimeout(timeVariableTimer);
967}
968
969/**
970 * Redirects the virtual-tsc log output to the ioBroker log
971 * @param {string} msg message
972 * @param {string} sev severity (info, silly, debug, warn, error)
973 */
974function tsLog(msg, sev) {
975 // shift the severities around, we don't care about the small details
976 if (sev == null || sev === 'info') {
977 sev = 'debug';
978 } else if (sev === 'debug') {
979 // Don't spam build logs on Travis
980 if (isCI) return;
981 sev = 'silly';
982 }
983
984 if (adapter && adapter.log) {
985 adapter.log[sev](msg);
986 } else {
987 console.log(`[${sev.toUpperCase()}] ${msg}`);
988 }
989}
990// compiler instance for typescript
991tsServer = new tsc.Server(tsCompilerOptions, tsLog);
992// compiler instance for global JS declarations
993jsDeclarationServer = new tsc.Server(
994 jsDeclarationCompilerOptions,
995 isCI ? false : undefined
996);
997
998function addGetProperty(object) {
999 try {
1000 Object.defineProperty(object, 'get', {
1001 value: function (id) {
1002 return this[id] || this[adapter.namespace + '.' + id];
1003 },
1004 enumerable: false
1005 });
1006 } catch (e) {
1007 console.error('Cannot install get property');
1008 }
1009}
1010
1011function fixLineNo(line) {
1012 if (line.indexOf('javascript.js:') >= 0) return line;
1013 if (!/script[s]?\.js[.\\\/]/.test(line)) return line;
1014 if (/:([\d]+):/.test(line)) {
1015 line = line.replace(/:([\d]+):/, ($0, $1) =>
1016 ':' + ($1 > globalScriptLines ? $1 - globalScriptLines : $1) + ':');
1017 } else {
1018 line = line.replace(/:([\d]+)$/, ($0, $1) =>
1019 ':' + ($1 > globalScriptLines ? $1 - globalScriptLines : $1));
1020 }
1021 return line;
1022}
1023
1024context.logError = function (msg, e, offs) {
1025 const stack = e.stack ? e.stack.split('\n') : (e ? e.toString() : '');
1026 if (msg.indexOf('\n') < 0) {
1027 msg = msg.replace(/[: ]*$/, ': ');
1028 }
1029
1030 //errorLogFunction.error(msg + stack[0]);
1031 context.errorLogFunction.error(msg + fixLineNo(stack[0]));
1032 for (let i = offs || 1; i < stack.length; i++) {
1033 if (!stack[i]) continue;
1034 if (stack[i].match(/runInNewContext|javascript\.js:/)) break;
1035 //adapter.log.error(fixLineNo(stack[i]));
1036 context.errorLogFunction.error(fixLineNo(stack[i]));
1037 }
1038};
1039
1040function createActiveObject(id, enabled, cb) {
1041 const idActive = adapter.namespace + '.scriptEnabled.' + id.substring('script.js.'.length);
1042
1043 if (!context.objects[idActive]) {
1044 context.objects[idActive] = {
1045 _id: idActive,
1046 common: {
1047 name: 'scriptEnabled.' + id.substring('script.js.'.length),
1048 desc: 'controls script activity',
1049 type: 'boolean',
1050 write: true,
1051 read: true,
1052 role: 'switch.active'
1053 },
1054 native: {
1055 script: id
1056 },
1057 type: 'state'
1058 };
1059 adapter.setForeignObject(idActive, context.objects[idActive], err => {
1060 if (!err) {
1061 adapter.setForeignState(idActive, enabled, true, cb);
1062 } else if (cb) {
1063 cb();
1064 }
1065 });
1066 } else {
1067 adapter.getForeignState(idActive, (err, state) => {
1068 if (state && state.val !== enabled) {
1069 adapter.setForeignState(idActive, enabled, true, cb);
1070 } else if (cb) {
1071 cb();
1072 }
1073 });
1074 }
1075}
1076
1077function createProblemObject(id, cb) {
1078 const idProblem = adapter.namespace + '.scriptProblem.' + id.substring('script.js.'.length);
1079
1080 if (!context.objects[idProblem]) {
1081 context.objects[idProblem] = {
1082 _id: idProblem,
1083 common: {
1084 name: 'scriptProblem.' + id.substring('script.js.'.length),
1085 desc: 'is the script has a problem',
1086 type: 'boolean',
1087 expert: true,
1088 write: false,
1089 read: true,
1090 role: 'indicator.error'
1091 },
1092 native: {
1093 script: id
1094 },
1095 type: 'state'
1096 };
1097 adapter.setForeignObject(idProblem, context.objects[idProblem], err => {
1098 if (!err) {
1099 adapter.setForeignState(idProblem, false, true, cb);
1100 } else if (cb) {
1101 cb();
1102 }
1103 });
1104 } else {
1105 adapter.getForeignState(idProblem, (err, state) => {
1106 if (state && state.val !== false) {
1107 adapter.setForeignState(idProblem, false, true, cb);
1108 } else if (cb) {
1109 cb();
1110 }
1111 });
1112 }
1113}
1114
1115function addToNames(obj) {
1116 const id = obj._id;
1117 if (obj.common && obj.common.name) {
1118 const name = obj.common.name;
1119 if (typeof name !== 'string') return;
1120
1121 if (!context.names[name]) {
1122 context.names[name] = id;
1123 } else {
1124 if (typeof context.names[name] === 'string') {
1125 context.names[name] = [context.names[name]];
1126 }
1127 context.names[name].push(id);
1128 }
1129 }
1130}
1131
1132function removeFromNames(id) {
1133 const n = getName(id);
1134
1135 if (n) {
1136 let pos;
1137 if (context.names[n] === 'object') {
1138 pos = context.names[n].indexOf(id);
1139 if (pos !== -1) {
1140 context.names[n].splice(pos, 1);
1141 if (context.names[n].length) {
1142 context.names[n] = context.names[n][0];
1143 }
1144 }
1145 } else {
1146 delete context.names[n];
1147 }
1148 }
1149}
1150
1151function getName(id) {
1152 let pos;
1153 for (const n in context.names) {
1154 if (context.names.hasOwnProperty(n)) {
1155 if (context.names[n] && typeof context.names[n] === 'object') {
1156 pos = context.names[n].indexOf(id);
1157 if (pos !== -1) return n;
1158 } else if (context.names[n] === id) {
1159 return n;
1160 }
1161 }
1162 }
1163 return null;
1164}
1165
1166function installNpm(npmLib, callback) {
1167 const path = __dirname;
1168 if (typeof npmLib === 'function') {
1169 callback = npmLib;
1170 npmLib = undefined;
1171 }
1172
1173 const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '"';
1174 adapter.log.info(cmd + ' (System call)');
1175 // Install node modules as system call
1176
1177 // System call used for update of js-controller itself,
1178 // because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
1179 const child = mods['child_process'].exec(cmd);
1180
1181 child.stdout.on('data', buf =>
1182 adapter.log.info(buf.toString('utf8')));
1183
1184 child.stderr.on('data', buf =>
1185 adapter.log.error(buf.toString('utf8')));
1186
1187 child.on('exit', (code /* , signal */) => {
1188 if (code) {
1189 adapter.log.error('Cannot install ' + npmLib + ': ' + code);
1190 }
1191 // command succeeded
1192 if (typeof callback === 'function') callback(npmLib);
1193 });
1194}
1195
1196function installLibraries(callback) {
1197 let allInstalled = true;
1198 if (adapter.config && adapter.config.libraries) {
1199 const libraries = adapter.config.libraries.split(/[,;\s]+/);
1200
1201 for (let lib = 0; lib < libraries.length; lib++) {
1202 if (libraries[lib] && libraries[lib].trim()) {
1203 libraries[lib] = libraries[lib].trim();
1204 if (!nodeFS.existsSync(__dirname + '/node_modules/' + libraries[lib] + '/package.json')) {
1205
1206 if (!attempts[libraries[lib]]) {
1207 attempts[libraries[lib]] = 1;
1208 } else {
1209 attempts[libraries[lib]]++;
1210 }
1211 if (attempts[libraries[lib]] > 3) {
1212 adapter.log.error('Cannot install npm packet: ' + libraries[lib]);
1213 continue;
1214 }
1215
1216 installNpm(libraries[lib], () =>
1217 installLibraries(callback));
1218
1219 allInstalled = false;
1220 break;
1221 }
1222 }
1223 }
1224 }
1225 if (allInstalled) callback();
1226}
1227
1228function compile(source, name) {
1229 source += "\n;\nlog('registered ' + __engine.__subscriptions + ' subscription' + (__engine.__subscriptions === 1 ? '' : 's' ) + ' and ' + __engine.__schedules + ' schedule' + (__engine.__schedules === 1 ? '' : 's' ));\n";
1230 try {
1231 if (VMScript) {
1232 return {
1233 script: new VMScript(source, name)
1234 };
1235 } else {
1236 const options = {
1237 filename: name,
1238 displayErrors: true
1239 //lineOffset: globalScriptLines
1240 };
1241 return {
1242 script: vm.createScript(source, options)
1243 };
1244 }
1245 } catch (e) {
1246 context.logError(name + ' compile failed:\r\nat ', e);
1247 return false;
1248 }
1249}
1250
1251function execute(script, name, verbose, debug) {
1252 script.intervals = [];
1253 script.timeouts = [];
1254 script.schedules = [];
1255 script.wizards = [];
1256 script.name = name;
1257 script._id = Math.floor(Math.random() * 0xFFFFFFFF);
1258 script.subscribes = {};
1259 adapter.setState('scriptProblem.' + name.substring('script.js.'.length), { val: false, ack: true, expire: 1000 });
1260
1261 const sandbox = sandBox(script, name, verbose, debug, context);
1262
1263 if (NodeVM) {
1264 const vm = new NodeVM({
1265 sandbox,
1266 require: {
1267 external: true,
1268 builtin: ['*'],
1269 root: '',
1270 mock: mods
1271 }
1272 });
1273
1274 try {
1275 vm.run(script.script, name);
1276 } catch (e) {
1277 adapter.setState('scriptProblem.' + name.substring('script.js.'.length), true, true);
1278 context.logError(name, e);
1279 }
1280 } else {
1281 try {
1282 script.script.runInNewContext(sandbox, {
1283 filename: name,
1284 displayErrors: true
1285 //lineOffset: globalScriptLines
1286 });
1287 } catch (e) {
1288 adapter.setState('scriptProblem.' + name.substring('script.js.'.length), true, true);
1289 context.logError(name, e);
1290 }
1291 }
1292}
1293
1294function unsubscribe(id) {
1295 if (!id) {
1296 adapter.log.warn('unsubscribe: empty name');
1297 return;
1298 }
1299
1300 if (id.constructor && id.constructor.name === 'RegExp') {
1301 //adapter.log.warn('unsubscribe: todo - process regexp');
1302 return;
1303 }
1304
1305 if (typeof id !== 'string') {
1306 adapter.log.error('unsubscribe: invalid type of id - ' + typeof id);
1307 return;
1308 }
1309 const parts = id.split('.');
1310 const _adapter = 'system.adapter.' + parts[0] + '.' + parts[1];
1311 if (context.objects[_adapter] && context.objects[_adapter].common && context.objects[_adapter].common.subscribable) {
1312 const a = parts[0] + '.' + parts[1];
1313 const alive = 'system.adapter.' + a + '.alive';
1314 if (context.adapterSubs[alive]) {
1315 const pos = context.adapterSubs[alive].indexOf(id);
1316 if (pos !== -1) context.adapterSubs[alive].splice(pos, 1);
1317 if (!context.adapterSubs[alive].length) delete context.adapterSubs[alive];
1318 }
1319 adapter.sendTo(a, 'unsubscribe', id);
1320 }
1321}
1322
1323// Analyse if logs are still required or not
1324function updateLogSubscriptions() {
1325 let found = false;
1326 // go through all scripts and check if some one script still require logs
1327 Object.keys(context.logSubscriptions).forEach(name => {
1328 if (!context.logSubscriptions[name] || !context.logSubscriptions[name].length) {
1329 delete context.logSubscriptions[name];
1330 } else {
1331 found = true;
1332 }
1333 });
1334
1335 if (found && !logSubscribed) {
1336 logSubscribed = true;
1337 adapter.requireLog(logSubscribed);
1338 } else if (!found && logSubscribed) {
1339 logSubscribed = false;
1340 adapter.requireLog(logSubscribed);
1341 }
1342}
1343
1344function stop(name, callback) {
1345 adapter.log.info('Stop script ' + name);
1346
1347 adapter.setState('scriptEnabled.' + name.substring('script.js.'.length), false, true);
1348
1349 if (context.messageBusHandlers[name]) {
1350 delete context.messageBusHandlers[name];
1351 }
1352
1353 if (context.logSubscriptions[name]) {
1354 delete context.logSubscriptions[name];
1355 updateLogSubscriptions();
1356 }
1357
1358 if (context.scripts[name]) {
1359 // Remove from subscriptions
1360 context.isEnums = false;
1361 if (adapter.config.subscribe) {
1362 // check all subscribed IDs
1363 for (const id in context.scripts[name].subscribes) {
1364 if (!context.scripts[name].subscribes.hasOwnProperty(id)) continue;
1365 if (context.subscribedPatterns[id]) {
1366 context.subscribedPatterns[id] -= context.scripts[name].subscribes[id];
1367 if (context.subscribedPatterns[id] <= 0) {
1368 adapter.unsubscribeForeignStates(id);
1369 delete context.subscribedPatterns[id];
1370 if (context.states[id]) delete context.states[id];
1371 }
1372 }
1373 }
1374 }
1375
1376 for (let i = context.subscriptions.length - 1; i >= 0; i--) {
1377 if (context.subscriptions[i].name === name) {
1378 const sub = context.subscriptions.splice(i, 1)[0];
1379 sub && unsubscribe(sub.pattern.id);
1380 } else {
1381 if (!context.isEnums && context.subscriptions[i].pattern.enumName || context.subscriptions[i].pattern.enumId) {
1382 context.isEnums = true;
1383 }
1384 }
1385 }
1386
1387 // Stop all timeouts
1388 for (let i = 0; i < context.scripts[name].timeouts.length; i++) {
1389 clearTimeout(context.scripts[name].timeouts[i]);
1390 }
1391 // Stop all intervals
1392 for (let i = 0; i < context.scripts[name].intervals.length; i++) {
1393 clearInterval(context.scripts[name].intervals[i]);
1394 }
1395 // Stop all scheduled jobs
1396 for (let i = 0; i < context.scripts[name].schedules.length; i++) {
1397 if (context.scripts[name].schedules[i]) {
1398 const _name = context.scripts[name].schedules[i].name;
1399 if (!nodeSchedule.cancelJob(context.scripts[name].schedules[i])) {
1400 adapter.log.error('Error by canceling scheduled job "' + _name + '"');
1401 }
1402 }
1403 }
1404
1405 // Stop all time wizards jobs
1406 for (let i = 0; i < context.scripts[name].wizards.length; i++) {
1407 if (context.scripts[name].wizards[i]) {
1408 context.scheduler.remove(context.scripts[name].wizards[i]);
1409 }
1410 }
1411
1412 // if callback for on stop
1413 if (typeof context.scripts[name].onStopCb === 'function') {
1414 context.scripts[name].onStopTimeout = parseInt(context.scripts[name].onStopTimeout, 10) || 1000;
1415
1416 let timeout = setTimeout(() => {
1417 if (timeout) {
1418 timeout = null;
1419 delete context.scripts[name];
1420 typeof callback === 'function' && callback(true, name);
1421 }
1422 }, context.scripts[name].onStopTimeout);
1423
1424 try {
1425 context.scripts[name].onStopCb(() => {
1426 if (timeout) {
1427 clearTimeout(timeout);
1428 timeout = null;
1429 delete context.scripts[name];
1430 typeof callback === 'function' && callback(true, name);
1431 }
1432 });
1433 } catch (e) {
1434 adapter.log.error('error in onStop callback: ' + e);
1435 }
1436
1437 } else {
1438 delete context.scripts[name];
1439 typeof callback === 'function' && callback(true, name);
1440 }
1441 } else {
1442 typeof callback === 'function' && callback(false, name);
1443 }
1444}
1445
1446function prepareScript(obj, callback) {
1447 if (obj &&
1448 obj.common.enabled &&
1449 obj.common.engine === 'system.adapter.' + adapter.namespace &&
1450 obj.common.source) {
1451 const name = obj._id;
1452
1453 adapter.setState('scriptEnabled.' + name.substring('script.js.'.length), true, true);
1454 obj.common.engineType = obj.common.engineType || '';
1455
1456 if ((obj.common.engineType.toLowerCase().startsWith('javascript') || obj.common.engineType === 'Blockly')) {
1457 // Javascript
1458 adapter.log.info('Start javascript ' + name);
1459
1460 let sourceFn = name;
1461 if (webstormDebug) {
1462 const fn = name.replace(/^script.js./, '').replace(/\./g, '/');
1463 sourceFn = mods.path.join(webstormDebug, fn + '.js');
1464 }
1465 context.scripts[name] = compile(globalScript + obj.common.source, sourceFn);
1466 context.scripts[name] && execute(context.scripts[name], sourceFn, obj.common.verbose, obj.common.debug);
1467 typeof callback === 'function' && callback(true, name);
1468 } else if (obj.common.engineType.toLowerCase().startsWith('coffee')) {
1469 // CoffeeScript
1470 coffeeCompiler.fromSource(obj.common.source, { sourceMap: false, bare: true }, (err, js) => {
1471 if (err) {
1472 adapter.log.error(name + ' coffee compile ' + err);
1473 typeof callback === 'function' && callback(false, name);
1474 return;
1475 }
1476 adapter.log.info('Start coffescript ' + name);
1477 context.scripts[name] = compile(globalScript + '\n' + js, name);
1478 context.scripts[name] && execute(context.scripts[name], name, obj.common.verbose, obj.common.debug);
1479 typeof callback === 'function' && callback(true, name);
1480 });
1481 } else if (obj.common.engineType.toLowerCase().startsWith('typescript')) {
1482 // TypeScript
1483 adapter.log.info(name + ': compiling TypeScript source...');
1484 const filename = scriptIdToTSFilename(name);
1485 const tsCompiled = tsServer.compile(filename, obj.common.source);
1486
1487 const errors = tsCompiled.diagnostics.map(diag => diag.annotatedSource + '\n').join('\n');
1488
1489 if (tsCompiled.success) {
1490 if (errors.length > 0) {
1491 adapter.log.warn(name + ': TypeScript compilation had errors: \n' + errors);
1492 } else {
1493 adapter.log.info(name + ': TypeScript compilation successful');
1494 }
1495 context.scripts[name] = compile(globalScript + '\n' + tsCompiled.result, name);
1496 context.scripts[name] && execute(context.scripts[name], name, obj.common.verbose, obj.common.debug);
1497 typeof callback === 'function' && callback(true, name);
1498 } else {
1499 adapter.log.error(name + ': TypeScript compilation failed: \n' + errors);
1500 }
1501 }
1502 } else {
1503 let _name;
1504 if (obj && obj._id) {
1505 _name = obj._id;
1506 adapter.setState('scriptEnabled.' + _name.substring('script.js.'.length), false, true);
1507 }
1508 !obj && adapter.log.error('Invalid script');
1509 typeof callback === 'function' && callback(false, _name);
1510 }
1511}
1512
1513function load(nameOrObject, callback) {
1514 if (typeof nameOrObject === 'object') {
1515 // create states for scripts
1516 createActiveObject(nameOrObject._id, nameOrObject && nameOrObject.common && nameOrObject.common.enabled, () =>
1517 createProblemObject(nameOrObject._id, () =>
1518 prepareScript(nameOrObject, callback)));
1519
1520 } else {
1521 adapter.getForeignObject(nameOrObject, (err, obj) => {
1522 if (!obj || err) {
1523 err && adapter.log.error('Invalid script "' + nameOrObject + '": ' + err);
1524 typeof callback === 'function' && callback(false, nameOrObject);
1525 } else {
1526 return load(obj, callback);
1527 }
1528 });
1529 }
1530}
1531
1532function patternMatching(event, patternFunctions) {
1533 let matched = false;
1534 for (let i = 0, len = patternFunctions.length; i < len; i++) {
1535 if (patternFunctions[i](event)) {
1536 if (patternFunctions.logic === 'or') return true;
1537
1538 matched = true;
1539 } else if (patternFunctions.logic === 'and') {
1540 return false;
1541 }
1542 }
1543 return matched;
1544}
1545
1546function getData(callback) {
1547 let statesReady;
1548 let objectsReady;
1549 adapter.log.info('requesting all states');
1550 adapter.getForeignStates('*', (err, res) => {
1551 if (!adapter.config.subscribe) {
1552 context.states = res;
1553 }
1554
1555 addGetProperty(context.states);
1556
1557 // remember all IDs
1558 for (const id in res) {
1559 if (res.hasOwnProperty(id)) {
1560 context.stateIds.push(id);
1561 }
1562 }
1563 statesReady = true;
1564 adapter.log.info('received all states');
1565 objectsReady && typeof callback === 'function' && callback();
1566 });
1567
1568 adapter.log.info('requesting all objects');
1569
1570 adapter.getObjectList({ include_docs: true }, (err, res) => {
1571 res = res.rows;
1572 context.objects = {};
1573 for (let i = 0; i < res.length; i++) {
1574 if (!res[i].doc) {
1575 adapter.log.debug('Got empty object for index ' + i + ' (' + res[i].id + ')');
1576 continue;
1577 }
1578 context.objects[res[i].doc._id] = res[i].doc;
1579 res[i].doc.type === 'enum' && context.enums.push(res[i].doc._id);
1580
1581 // Collect all names
1582 addToNames(context.objects[res[i].doc._id]);
1583 }
1584 addGetProperty(context.objects);
1585
1586 const systemConfig = context.objects['system.config'];
1587
1588 // set language for debug messages
1589 if (systemConfig && systemConfig.common && systemConfig.common.language) {
1590 words.setLanguage(systemConfig.common.language);
1591 } else if (adapter.language) {
1592 words.setLanguage(adapter.language);
1593 }
1594
1595 // try to use system coordinates
1596 if (adapter.config.useSystemGPS) {
1597 if (systemConfig && systemConfig.common && systemConfig.common.latitude) {
1598 adapter.config.latitude = systemConfig.common.latitude;
1599 adapter.config.longitude = systemConfig.common.longitude;
1600 } else if (adapter.latitude) {
1601 adapter.config.latitude = adapter.latitude;
1602 adapter.config.longitude = adapter.longitude;
1603 }
1604 }
1605 adapter.config.latitude = parseFloat(adapter.config.latitude);
1606 adapter.config.longitude = parseFloat(adapter.config.longitude);
1607
1608 adapter.config.sunriseEvent = adapter.config.sunriseEvent || 'nightEnd';
1609 adapter.config.sunriseOffset = adapter.config.sunriseOffset || 0;
1610 adapter.config.sunriseLimitStart = adapter.config.sunriseLimitStart || '06:00';
1611 adapter.config.sunriseLimitEnd = adapter.config.sunriseLimitEnd || '12:00';
1612
1613 adapter.config.sunsetEvent = adapter.config.sunsetEvent || 'dusk';
1614 adapter.config.sunsetOffset = adapter.config.sunsetOffset || 0;
1615 adapter.config.sunsetLimitStart = adapter.config.sunsetLimitStart || '18:00';
1616 adapter.config.sunsetLimitEnd = adapter.config.sunsetLimitEnd || '23:00';
1617
1618
1619 objectsReady = true;
1620 adapter.log.info('received all objects');
1621 statesReady && typeof callback === 'function' && callback();
1622 });
1623}
1624
1625// If started as allInOne mode => return function to create instance
1626if (module.parent) {
1627 module.exports = startAdapter;
1628} else {
1629 // or start the instance directly
1630 startAdapter();
1631}