1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | 'use strict';
|
17 |
|
18 | let NodeVM;
|
19 | let VMScript;
|
20 | let vm;
|
21 | if (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 | }
|
32 | const nodeFS = require('fs');
|
33 | const nodePath = require('path');
|
34 | const coffeeCompiler = require('coffee-compiler');
|
35 | const tsc = require('virtual-tsc');
|
36 | const typescript = require('typescript');
|
37 | const nodeSchedule = require('node-schedule');
|
38 | const Mirror = require('./lib/mirror');
|
39 |
|
40 | const 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 |
|
58 | const utils = require('@iobroker/adapter-core');
|
59 | const words = require('./lib/words');
|
60 | const sandBox = require('./lib/sandbox');
|
61 | const eventObj = require('./lib/eventObj');
|
62 | const Scheduler = require('./lib/scheduler');
|
63 | const {
|
64 | resolveTypescriptLibs,
|
65 | resolveTypings,
|
66 | scriptIdToTSFilename
|
67 | } = require('./lib/typescriptTools');
|
68 |
|
69 | const adapterName = require('./package.json').name.split('.').pop();
|
70 | const scriptCodeMarker = 'script.js.';
|
71 |
|
72 |
|
73 | if (''.startsWith === undefined) {
|
74 | String.prototype.startsWith = function (s) {
|
75 | return this.indexOf(s) === 0;
|
76 | };
|
77 | }
|
78 | if (''.endsWith === undefined) {
|
79 | String.prototype.endsWith = function (s) {
|
80 | return this.slice(0 - s.length) === s;
|
81 | };
|
82 | }
|
83 |
|
84 |
|
85 | let webstormDebug;
|
86 | if (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 |
|
95 | const isCI = !!process.env.CI;
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | const targetTsLib = 'es2017';
|
101 |
|
102 |
|
103 | const tsCompilerOptions = {
|
104 |
|
105 | noEmitOnError: true,
|
106 |
|
107 | declaration: true,
|
108 |
|
109 | esModuleInterop: true,
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | target: typescript.ScriptTarget.ES5,
|
115 | lib: [`lib.${targetTsLib}.d.ts`],
|
116 | };
|
117 |
|
118 | const jsDeclarationCompilerOptions = Object.assign(
|
119 | {}, tsCompilerOptions,
|
120 | {
|
121 |
|
122 | emitDeclarationOnly: true,
|
123 |
|
124 | noEmitOnError: false,
|
125 | noImplicitAny: false,
|
126 | strict: false,
|
127 | }
|
128 | );
|
129 |
|
130 |
|
131 |
|
132 | let tsAmbient;
|
133 |
|
134 | let tsServer;
|
135 |
|
136 | let jsDeclarationServer;
|
137 |
|
138 | let mirror;
|
139 |
|
140 |
|
141 | let logSubscribed;
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 | function provideDeclarationsForGlobalScript(scriptID, declarations) {
|
148 |
|
149 |
|
150 | if (globalDeclarations != null && globalDeclarations !== '') {
|
151 | knownGlobalDeclarationsByScript[scriptID] = globalDeclarations;
|
152 | }
|
153 |
|
154 | globalDeclarations += declarations + '\n';
|
155 |
|
156 |
|
157 | const globalDeclarationPath = 'global.d.ts';
|
158 | tsAmbient[globalDeclarationPath] = globalDeclarations;
|
159 |
|
160 | tsServer.provideAmbientDeclarations({
|
161 | [globalDeclarationPath]: globalDeclarations
|
162 | });
|
163 | jsDeclarationServer.provideAmbientDeclarations({
|
164 | [globalDeclarationPath]: globalDeclarations
|
165 | });
|
166 | }
|
167 |
|
168 | function loadTypeScriptDeclarations() {
|
169 |
|
170 | const packages = [
|
171 | 'node',
|
172 | 'request',
|
173 | ];
|
174 |
|
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 |
|
195 | pkg !== 'node'
|
196 | );
|
197 | adapter.log.debug(`Loaded TypeScript definitions for ${pkg}: ${JSON.stringify(Object.keys(pkgTypings))}`);
|
198 |
|
199 | Object.assign(tsAmbient, pkgTypings);
|
200 |
|
201 | tsServer.provideAmbientDeclarations(pkgTypings);
|
202 | jsDeclarationServer.provideAmbientDeclarations(pkgTypings);
|
203 | }
|
204 | }
|
205 |
|
206 |
|
207 | const context = {
|
208 | mods,
|
209 | objects: {},
|
210 | states: {},
|
211 | stateIds: [],
|
212 | errorLogFunction: null,
|
213 | subscriptions: [],
|
214 | adapterSubs: {},
|
215 | subscribedPatterns: {},
|
216 | cacheObjectEnums: {},
|
217 | isEnums: false,
|
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 |
|
236 | const regExGlobalOld = /_global$/;
|
237 | const regExGlobalNew = /script\.js\.global\./;
|
238 |
|
239 | function checkIsGlobal(obj) {
|
240 | return regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id);
|
241 | }
|
242 |
|
243 | let adapter;
|
244 | function startAdapter(options) {
|
245 | options = options || {};
|
246 | Object.assign(options, {
|
247 |
|
248 | name: adapterName,
|
249 |
|
250 | useFormatDate: true,
|
251 |
|
252 | objectChange: (id, obj) => {
|
253 | if (id.startsWith('enum.')) {
|
254 |
|
255 | context.cacheObjectEnums = {};
|
256 |
|
257 |
|
258 | if (obj) {
|
259 |
|
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 |
|
267 | if (pos !== -1) {
|
268 | context.enums.splice(pos, 1);
|
269 | }
|
270 | }
|
271 | }
|
272 |
|
273 |
|
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 |
|
280 | mirror && mirror.onObjectChange(id, obj);
|
281 |
|
282 | if (obj) {
|
283 |
|
284 | if (obj.type === 'state' && !context.stateIds.includes(id)) {
|
285 | context.stateIds.push(id);
|
286 | context.stateIds.sort();
|
287 | }
|
288 | } else {
|
289 |
|
290 | const pos = context.stateIds.indexOf(id);
|
291 | pos !== -1 && context.stateIds.splice(pos, 1);
|
292 | }
|
293 |
|
294 | if (!obj) {
|
295 |
|
296 | if (!context.objects[id]) return;
|
297 |
|
298 |
|
299 | if (context.objects[id].type === 'script' && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
|
300 | stop(id);
|
301 |
|
302 |
|
303 | const idActive = 'scriptEnabled.' + id.substring('script.js.'.length);
|
304 | adapter.delObject(idActive);
|
305 | adapter.delState(idActive);
|
306 |
|
307 |
|
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 |
|
317 | context.objects[id] = obj;
|
318 |
|
319 | addToNames(obj);
|
320 |
|
321 | if (obj.type === 'script' && obj.common.engine === 'system.adapter.' + adapter.namespace) {
|
322 |
|
323 | createActiveObject(id, obj.common.enabled, () => createProblemObject(id));
|
324 |
|
325 | if (obj.common.enabled) {
|
326 | if (checkIsGlobal(obj)) {
|
327 |
|
328 | adapter.getForeignObject('system.adapter.' + adapter.namespace, (err, _obj) =>
|
329 | _obj && adapter.setForeignObject('system.adapter.' + adapter.namespace, _obj));
|
330 | return;
|
331 | }
|
332 |
|
333 |
|
334 | load(id);
|
335 | }
|
336 | }
|
337 |
|
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 |
|
347 | if (obj.type !== 'script') {
|
348 | context.objects[id] = obj;
|
349 |
|
350 | if (id === 'system.config') {
|
351 |
|
352 | if (obj.common && obj.common.language) {
|
353 | words.setLanguage(obj.common.language);
|
354 | }
|
355 | }
|
356 |
|
357 | return;
|
358 | }
|
359 |
|
360 |
|
361 |
|
362 | if (checkIsGlobal(context.objects[id])) {
|
363 |
|
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 |
|
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 |
|
379 | if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
|
380 |
|
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 |
|
389 | context.objects[id] = obj;
|
390 |
|
391 | if (context.objects[id].common.enabled && context.objects[id].common.engine === 'system.adapter.' + adapter.namespace) {
|
392 |
|
393 | load(id);
|
394 | }
|
395 | } else {
|
396 | context.objects[id] = obj;
|
397 |
|
398 |
|
399 | stop(id, (res, _id) =>
|
400 | load(_id));
|
401 | } |
402 |
|
403 |
|
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 |
|
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 |
|
422 | if (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 (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 |
|
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 |
|
475 | if (eventData.stacktrace.frames.find(frame => frame.filename && frame.filename.includes(scriptCodeMarker))) {
|
476 | return null;
|
477 | }
|
478 |
|
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 |
|
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 |
|
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 => { });
|
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': {
|
534 | const typings = {};
|
535 |
|
536 |
|
537 | try {
|
538 | const typescriptLibs = resolveTypescriptLibs(targetTsLib);
|
539 | Object.assign(typings, typescriptLibs);
|
540 | } catch (e) {
|
541 | }
|
542 |
|
543 |
|
544 | Object.assign(typings, tsAmbient);
|
545 |
|
546 |
|
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 |
|
597 |
|
598 |
|
599 |
|
600 | error: (err) => {
|
601 |
|
602 |
|
603 | if (err && typeof err.stack === 'string') {
|
604 | const scriptCodeMarkerIndex = err.stack.indexOf(scriptCodeMarker);
|
605 | if (scriptCodeMarkerIndex > -1) {
|
606 |
|
607 | let scriptName = err.stack.substr(scriptCodeMarkerIndex);
|
608 | scriptName = scriptName.substr(0, scriptName.indexOf(':'));
|
609 | context.logError(scriptName, err);
|
610 |
|
611 |
|
612 |
|
613 | return true;
|
614 | }
|
615 |
|
616 |
|
617 | if (!err.stack.match(/iobroker\.javascript[\/\\](?!.*node_modules).*/g)) {
|
618 |
|
619 |
|
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 |
|
625 | return true;
|
626 | }
|
627 | }
|
628 | }
|
629 | });
|
630 |
|
631 | adapter = new utils.Adapter(options);
|
632 |
|
633 |
|
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 |
|
650 | function main() {
|
651 |
|
652 | context.errorLogFunction = webstormDebug ? console : adapter.log;
|
653 | activeStr = adapter.namespace + '.scriptEnabled.';
|
654 |
|
655 | mods.fs = new require('./lib/protectFs')(adapter.log);
|
656 |
|
657 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 {
|
767 | const sourceCode = obj.common.source;
|
768 | globalScript += sourceCode + '\n';
|
769 |
|
770 |
|
771 |
|
772 | const filename = scriptIdToTSFilename(obj._id);
|
773 | const tsCompiled = jsDeclarationServer.compile(filename, sourceCode);
|
774 |
|
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 |
|
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 |
|
811 | function stopAllScripts(cb) {
|
812 | Object.keys(context.scripts).forEach(id => stop(id));
|
813 | setTimeout(() => cb(), 0);
|
814 | }
|
815 |
|
816 | const attempts = {};
|
817 | let globalScript = '';
|
818 |
|
819 | let globalDeclarations = '';
|
820 |
|
821 |
|
822 | let knownGlobalDeclarationsByScript = {};
|
823 | let globalScriptLines = 0;
|
824 |
|
825 | let activeStr = '';
|
826 | let timeVariableTimer = null;
|
827 | let daySchedule = null;
|
828 |
|
829 | function 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 |
|
844 | function 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 |
|
869 | if (now > ts) {
|
870 |
|
871 | ts.setDate(ts.getDate() + 1);
|
872 | }
|
873 | return ts;
|
874 | }
|
875 |
|
876 | function 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 |
|
899 | function dayTimeSchedules(adapter, context) {
|
900 |
|
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 |
|
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 |
|
915 | let sunriseTimeout = nextSunrise.getTime() - nowDate.getTime();
|
916 | if (sunriseTimeout > 3600000) {
|
917 | sunriseTimeout = 3600000;
|
918 | }
|
919 |
|
920 |
|
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 |
|
934 |
|
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 |
|
964 | function stopTimeSchedules() {
|
965 | daySchedule && clearTimeout(daySchedule);
|
966 | timeVariableTimer && clearTimeout(timeVariableTimer);
|
967 | }
|
968 |
|
969 |
|
970 |
|
971 |
|
972 |
|
973 |
|
974 | function tsLog(msg, sev) {
|
975 |
|
976 | if (sev == null || sev === 'info') {
|
977 | sev = 'debug';
|
978 | } else if (sev === 'debug') {
|
979 |
|
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 |
|
991 | tsServer = new tsc.Server(tsCompilerOptions, tsLog);
|
992 |
|
993 | jsDeclarationServer = new tsc.Server(
|
994 | jsDeclarationCompilerOptions,
|
995 | isCI ? false : undefined
|
996 | );
|
997 |
|
998 | function 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 |
|
1011 | function 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 |
|
1024 | context.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 |
|
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 |
|
1036 | context.errorLogFunction.error(fixLineNo(stack[i]));
|
1037 | }
|
1038 | };
|
1039 |
|
1040 | function 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 |
|
1077 | function 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 |
|
1115 | function 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 |
|
1132 | function 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 |
|
1151 | function 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 |
|
1166 | function 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 |
|
1176 |
|
1177 |
|
1178 |
|
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 ) => {
|
1188 | if (code) {
|
1189 | adapter.log.error('Cannot install ' + npmLib + ': ' + code);
|
1190 | }
|
1191 |
|
1192 | if (typeof callback === 'function') callback(npmLib);
|
1193 | });
|
1194 | }
|
1195 |
|
1196 | function 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 |
|
1228 | function 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 |
|
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 |
|
1251 | function 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 |
|
1286 | });
|
1287 | } catch (e) {
|
1288 | adapter.setState('scriptProblem.' + name.substring('script.js.'.length), true, true);
|
1289 | context.logError(name, e);
|
1290 | }
|
1291 | }
|
1292 | }
|
1293 |
|
1294 | function 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 |
|
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 |
|
1324 | function updateLogSubscriptions() {
|
1325 | let found = false;
|
1326 |
|
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 |
|
1344 | function 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 |
|
1360 | context.isEnums = false;
|
1361 | if (adapter.config.subscribe) {
|
1362 |
|
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 |
|
1388 | for (let i = 0; i < context.scripts[name].timeouts.length; i++) {
|
1389 | clearTimeout(context.scripts[name].timeouts[i]);
|
1390 | }
|
1391 |
|
1392 | for (let i = 0; i < context.scripts[name].intervals.length; i++) {
|
1393 | clearInterval(context.scripts[name].intervals[i]);
|
1394 | }
|
1395 |
|
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 |
|
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 |
|
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 |
|
1446 | function 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 |
|
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 |
|
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 |
|
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 |
|
1513 | function load(nameOrObject, callback) {
|
1514 | if (typeof nameOrObject === 'object') {
|
1515 |
|
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 |
|
1532 | function 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 |
|
1546 | function 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 |
|
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 |
|
1582 | addToNames(context.objects[res[i].doc._id]);
|
1583 | }
|
1584 | addGetProperty(context.objects);
|
1585 |
|
1586 | const systemConfig = context.objects['system.config'];
|
1587 |
|
1588 |
|
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 |
|
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 |
|
1626 | if (module.parent) {
|
1627 | module.exports = startAdapter;
|
1628 | } else {
|
1629 |
|
1630 | startAdapter();
|
1631 | }
|