iobroker.javascript 9.2.2 → 9.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +10 -25
  2. package/admin/assets/{AiChatPanel-Bbux7LP7.js → AiChatPanel-B4vPXgLz.js} +1 -1
  3. package/admin/assets/{ScriptEditor-DKljeYQN.js → ScriptEditor-CmEeMfC2.js} +1 -1
  4. package/admin/assets/ScriptEditorVanillaMonaco-C22Gpet7.js +3 -0
  5. package/admin/assets/cronHoverProvider-Bg0Vz-1E.js +2 -0
  6. package/admin/assets/index-45IwpC7_.js +1 -0
  7. package/admin/assets/{index-DgE6XhUM.js → index-7mrTpzQy.js} +1 -1
  8. package/admin/assets/{index-DSvAO584.js → index-BTQOdA7v.js} +83 -83
  9. package/admin/assets/{index-_BUuZlI0.js → index-Dggem6AX.js} +1 -1
  10. package/admin/assets/{index-CexrwGGC.js → index-qoSy77Bg.js} +41 -37
  11. package/admin/assets/{localSharedImportMap-BCwqnmn4.js → localSharedImportMap-CVlAGiIu.js} +1 -1
  12. package/admin/assets/stateHoverProvider-DxTNxyYN.js +9 -0
  13. package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-DNrEsMrg.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-CeYV4czE.js} +2 -2
  14. package/admin/custom/assets/Components-CGiJE5Zh.js +1 -0
  15. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_react__loadShare__.mjs-sg07E3QA.js +1 -0
  16. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_styled__loadShare__.mjs-CwjI4A-J.js +1 -0
  17. package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs-0SjJESw9.js → ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs-BW9Qbxx5.js} +1 -1
  18. package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs_commonjs-proxy-CInLgMWW.js → ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs_commonjs-proxy-2W2BYT2K.js} +1 -1
  19. package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CorOEig8.js → ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-D8zcZsJo.js} +1 -1
  20. package/admin/custom/assets/DefaultPropsProvider-CfKHPa-G.js +1 -0
  21. package/admin/custom/assets/{TextField-DNzklBSl.js → TextField-CxPfV5Sr.js} +12 -12
  22. package/admin/custom/assets/{ThemeProvider-B4G3jidj.js → ThemeProvider-DSTCckLJ.js} +4 -4
  23. package/admin/custom/assets/{createSvgIcon-ZdrQ8zSU.js → createSvgIcon-b2B2NI3g.js} +1 -1
  24. package/admin/custom/assets/createTheme-MsdB8zt7.js +8 -0
  25. package/admin/custom/assets/defaultTheme-CmVP-QNF.js +1 -0
  26. package/admin/custom/assets/emotion-cache.browser.esm-BgJfNZJN.js +1 -0
  27. package/admin/custom/assets/emotion-react.browser.esm-chZAeh_-.js +1 -0
  28. package/admin/custom/assets/emotion-serialize.esm-BUa21YfQ.js +1 -0
  29. package/admin/custom/assets/emotion-styled.browser.esm-Yi13plpH.js +1 -0
  30. package/admin/custom/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-CEPeBbh9.js +1 -0
  31. package/admin/custom/assets/extends-CF3RwP-h.js +1 -0
  32. package/admin/custom/assets/index-17vbGv00.js +1 -0
  33. package/admin/custom/assets/{index-iRHaQfi-.js → index-B5DqCD_a.js} +1 -1
  34. package/admin/custom/assets/{index-DNKzDki_.js → index-B6NrrORQ.js} +159 -159
  35. package/admin/custom/assets/index-CT5PwRfY.js +964 -0
  36. package/admin/custom/assets/{index-YBvFYuP6.js → index-CzRngQHl.js} +1 -1
  37. package/admin/custom/assets/index-DLNtxshy.js +1 -0
  38. package/admin/custom/assets/{index-NItS-TWp.js → index-DTbSiEk2.js} +1 -1
  39. package/admin/custom/assets/{index-Dp7A3XHd.js → index-DV76KW3-.js} +4 -4
  40. package/admin/custom/assets/{index-D0iTiVVG.js → index-DlQbqHom.js} +1 -1
  41. package/admin/custom/assets/{index-BptSiKBK.js → index-FTmk0JJx.js} +5 -5
  42. package/admin/custom/assets/{jsx-runtime-DfVU3AZG.js → jsx-runtime-BplTyRFk.js} +1 -1
  43. package/admin/custom/assets/localSharedImportMap-ChjFDKsq.js +1 -0
  44. package/admin/custom/assets/useTheme-BV4xOYIL.js +1 -0
  45. package/admin/custom/assets/useTheme-ldICnlmf.js +1 -0
  46. package/admin/custom/assets/{virtualExposes-jSvr-kK3.js → virtualExposes-D402kVGY.js} +1 -1
  47. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-Br3IlnqO.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-ClQrb2n1.js} +2 -2
  48. package/admin/custom/customComponents.js +1 -1
  49. package/admin/google-blockly/own/field_oid.js +151 -2
  50. package/admin/i18n/es.json +77 -77
  51. package/admin/mf-manifest.json +1 -1
  52. package/admin/remoteEntry.js +1 -1
  53. package/admin/tab.html +10 -8
  54. package/build/lib/debugger.js +1 -1
  55. package/build/lib/debugger.js.map +1 -1
  56. package/build/lib/eventObj.js +31 -14
  57. package/build/lib/eventObj.js.map +1 -1
  58. package/build/lib/inspect.js +3 -3
  59. package/build/lib/inspect.js.map +1 -1
  60. package/build/lib/sandbox.js +336 -119
  61. package/build/lib/sandbox.js.map +1 -1
  62. package/build/lib/scheduler.js +18 -13
  63. package/build/lib/scheduler.js.map +1 -1
  64. package/build/main.js +282 -149
  65. package/build/main.js.map +1 -1
  66. package/build/types.d.ts +18 -6
  67. package/io-package.json +27 -14
  68. package/package.json +11 -11
  69. package/admin/assets/ScriptEditorVanillaMonaco-CaFyKuJ2.js +0 -3
  70. package/admin/assets/index-SHs7_wDh.js +0 -1
  71. package/admin/assets/stateHoverProvider-CGChzV5W.js +0 -3
  72. package/admin/custom/assets/Components-B9ZyjeRI.js +0 -1
  73. package/admin/custom/assets/DefaultPropsProvider-CGb21NGY.js +0 -1
  74. package/admin/custom/assets/createTheme-Wd4QxQE0.js +0 -8
  75. package/admin/custom/assets/defaultTheme-F2iJF2G2.js +0 -1
  76. package/admin/custom/assets/index-AZB1OHDH.js +0 -1
  77. package/admin/custom/assets/index-C0RmLaAj.js +0 -1
  78. package/admin/custom/assets/index-sozs10Ij.js +0 -960
  79. package/admin/custom/assets/localSharedImportMap-BxT0VwTO.js +0 -1
  80. package/admin/custom/assets/useTheme-BfhDdtAr.js +0 -1
  81. package/admin/custom/assets/useTheme-DWZ6jNyq.js +0 -1
  82. /package/admin/custom/assets/{index-huvZQhZw.js → index-D_5kZmfw.js} +0 -0
  83. /package/admin/custom/assets/{index-CmJ8HzSc.js → index-DpJ18x6R.js} +0 -0
  84. /package/admin/custom/assets/{leaflet-src-k2rCV-Ix.js → leaflet-src-B472P7Y1.js} +0 -0
package/build/main.js CHANGED
@@ -227,18 +227,32 @@ const jsDeclarationServer = new virtual_tsc_1.Server(typescriptSettings_1.jsDecl
227
227
  * Stores the IDs of script objects whose change should be ignored because
228
228
  * the compiled source was just updated
229
229
  */
230
+ const HTTP_STATUS_TEXTS = {
231
+ 400: 'Bad Request',
232
+ 401: 'Unauthorized',
233
+ 403: 'Forbidden',
234
+ 404: 'Not Found',
235
+ 429: 'Too Many Requests / Rate Limit',
236
+ 500: 'Internal Server Error',
237
+ 502: 'Bad Gateway',
238
+ 503: 'Service Unavailable',
239
+ };
230
240
  function httpStatusText(code) {
231
- const texts = {
232
- 400: 'Bad Request',
233
- 401: 'Unauthorized',
234
- 403: 'Forbidden',
235
- 404: 'Not Found',
236
- 429: 'Too Many Requests / Rate Limit',
237
- 500: 'Internal Server Error',
238
- 502: 'Bad Gateway',
239
- 503: 'Service Unavailable',
240
- };
241
- return texts[code] || `Error ${code}`;
241
+ return HTTP_STATUS_TEXTS[code] ?? `Error ${code}`;
242
+ }
243
+ /**
244
+ * Resolves the correct http/https module based on the URL string.
245
+ * Returns null if the URL is invalid.
246
+ */
247
+ function resolveRequestModule(url) {
248
+ try {
249
+ const { protocol } = new URL(url);
250
+ const isHttps = protocol === 'https:';
251
+ return { module: isHttps ? https : http, isHttps };
252
+ }
253
+ catch {
254
+ return null;
255
+ }
242
256
  }
243
257
  class JavaScript extends adapter_core_1.Adapter {
244
258
  context;
@@ -256,15 +270,36 @@ class JavaScript extends adapter_core_1.Adapter {
256
270
  states = {};
257
271
  interimStateValues = {};
258
272
  stateIds = [];
273
+ /** Fast O(1) lookup set – always kept in sync with stateIds */
274
+ stateIdSet = new Set();
259
275
  subscriptions = [];
276
+ /**
277
+ * O(1) dispatch map for subscriptions with exact (non-wildcard) string IDs.
278
+ * Always kept in sync with `subscriptions`.
279
+ */
280
+ subscriptionsMap = new Map();
281
+ /**
282
+ * Subscriptions whose pattern.id is a RegExp, contains wildcards (*,?), or is undefined.
283
+ * These must still be checked linearly on every state change.
284
+ */
285
+ subscriptionsWildcard = [];
260
286
  subscriptionsFile = [];
261
287
  subscriptionsObject = [];
288
+ /** O(1) dispatch map for subscriptionsObject – pattern → subscribers */
289
+ subscriptionsObjectMap = new Map();
290
+ /** IO-9: Cache for sendTo broadcast – adapterName → instance list, invalidated on object change */
291
+ sendToInstanceCache = new Map();
262
292
  subscribedPatterns = {};
263
293
  subscribedPatternsFile = {};
264
294
  adapterSubs = {};
265
295
  timers = {};
266
- _enums = [];
296
+ /** Reverse-index: scriptName → Set of stateIds that have timers for this script – O(1) cleanup */
297
+ timersByScript = new Map();
298
+ /** O(1) Set for enum-id lookups – replaces sorted string[] array */
299
+ _enums = new Set();
267
300
  names = {}; // name: id
301
+ /** Reverse map: id → name for O(1) getName() lookups */
302
+ nameById = new Map();
268
303
  scripts = {};
269
304
  password = '';
270
305
  messageBusHandlers = {};
@@ -393,13 +428,18 @@ class JavaScript extends adapter_core_1.Adapter {
393
428
  stateIds: this.stateIds,
394
429
  errorLogFunction: this.errorLogFunction,
395
430
  subscriptions: this.subscriptions,
431
+ subscriptionsMap: this.subscriptionsMap,
432
+ subscriptionsWildcard: this.subscriptionsWildcard,
396
433
  subscriptionsFile: this.subscriptionsFile,
397
434
  subscriptionsObject: this.subscriptionsObject,
435
+ subscriptionsObjectMap: this.subscriptionsObjectMap,
436
+ sendToInstanceCache: this.sendToInstanceCache,
398
437
  subscribedPatterns: this.subscribedPatterns,
399
438
  subscribedPatternsFile: this.subscribedPatternsFile,
400
439
  adapterSubs: this.adapterSubs,
401
440
  cacheObjectEnums: {},
402
441
  timers: this.timers,
442
+ timersByScript: this.timersByScript,
403
443
  enums: this._enums,
404
444
  names: this.names,
405
445
  scripts: this.scripts,
@@ -446,20 +486,20 @@ class JavaScript extends adapter_core_1.Adapter {
446
486
  if (id.startsWith('enum.')) {
447
487
  // clear cache
448
488
  this.context.cacheObjectEnums = {};
449
- // update this._enums array
489
+ // update this._enums Set
450
490
  if (obj) {
451
- // If new
452
- if (!this._enums.includes(id)) {
453
- this._enums.push(id);
454
- this._enums.sort();
455
- }
491
+ this._enums.add(id);
456
492
  }
457
493
  else {
458
- const pos = this._enums.indexOf(id);
459
- // if deleted
460
- if (pos !== -1) {
461
- this._enums.splice(pos, 1);
462
- }
494
+ this._enums.delete(id);
495
+ }
496
+ }
497
+ // IO-9: Invalidate sendTo instance-cache when adapter instances change
498
+ if (id.startsWith('system.adapter.')) {
499
+ const parts = id.split('.');
500
+ if (parts.length >= 3) {
501
+ const adapterName = parts[2]; // e.g. "zigbee" from "system.adapter.zigbee.0"
502
+ this.sendToInstanceCache.delete(adapterName);
463
503
  }
464
504
  }
465
505
  if (id === 'system.config' && obj?.common?.language) {
@@ -494,9 +534,10 @@ class JavaScript extends adapter_core_1.Adapter {
494
534
  /* ignore */
495
535
  });
496
536
  }
497
- this.subscriptionsObject.forEach(sub => {
498
- // ToDo: implement comparing with id.0.* too
499
- if (sub.pattern === id) {
537
+ // O(1) dispatch via pattern map instead of O(n) forEach
538
+ const objSubs = this.subscriptionsObjectMap.get(id);
539
+ if (objSubs) {
540
+ for (const sub of objSubs) {
500
541
  try {
501
542
  sub.callback(id, obj);
502
543
  }
@@ -504,7 +545,7 @@ class JavaScript extends adapter_core_1.Adapter {
504
545
  this.log.error(`Error in callback: ${err.toString()}`);
505
546
  }
506
547
  }
507
- });
548
+ }
508
549
  // handle Script object updates
509
550
  if (!obj && formerObj?.type === 'script') {
510
551
  // Object Deleted just now
@@ -631,16 +672,16 @@ class JavaScript extends adapter_core_1.Adapter {
631
672
  if (this.adapterSubs[id]) {
632
673
  const parts = id.split('.');
633
674
  const a = `${parts[2]}.${parts[3]}`;
634
- for (let t = 0; t < this.adapterSubs[id].length; t++) {
635
- this.log.info(`Detected coming adapter "${a}". Send subscribe: ${this.adapterSubs[id][t]}`);
636
- this.sendTo(a, 'subscribe', this.adapterSubs[id][t]);
675
+ for (const sub of this.adapterSubs[id]) {
676
+ this.log.info(`Detected coming adapter "${a}". Send subscribe: ${sub}`);
677
+ this.sendTo(a, 'subscribe', sub);
637
678
  }
638
679
  }
639
680
  }
640
681
  }
641
- else if ( /*!oldState && */!this.stateIds.includes(id)) {
642
- this.stateIds.push(id);
643
- this.stateIds.sort();
682
+ else if ( /*!oldState && */!this.stateIdSet.has(id)) {
683
+ this.sortedInsert(id);
684
+ this.stateIdSet.add(id);
644
685
  }
645
686
  this.states[id] = state;
646
687
  }
@@ -649,18 +690,48 @@ class JavaScript extends adapter_core_1.Adapter {
649
690
  delete this.states[id];
650
691
  }
651
692
  state = {};
652
- const pos = this.stateIds.indexOf(id);
693
+ const pos = this.binaryIndexOf(this.stateIds, id);
653
694
  if (pos !== -1) {
654
695
  this.stateIds.splice(pos, 1);
696
+ this.stateIdSet.delete(id);
697
+ }
698
+ }
699
+ // Collect matching subscriptions:
700
+ // 1. O(1) exact-id map lookup – only buckets for this specific state id
701
+ // 2. Linear scan over wildcard/regex subscriptions (unavoidable)
702
+ // EventObj is created lazily – only when at least one subscription must be dispatched.
703
+ const exactSubs = this.subscriptionsMap.get(id);
704
+ const wildcardSubs = this.subscriptionsWildcard;
705
+ const hasWork = (exactSubs && exactSubs.length > 0) || wildcardSubs.length > 0;
706
+ if (!hasWork) {
707
+ return;
708
+ }
709
+ let _eventObj = null;
710
+ const getEvent = () => {
711
+ if (!_eventObj) {
712
+ _eventObj = (0, eventObj_1.createEventObject)(this.context, id, this.convertBackStringifiedValues(id, state), this.convertBackStringifiedValues(id, oldState));
713
+ }
714
+ return _eventObj;
715
+ };
716
+ if (exactSubs) {
717
+ for (let i = 0, l = exactSubs.length; i < l; i++) {
718
+ const sub = exactSubs[i];
719
+ if (sub?.patternCompareFunctions && patternMatching(getEvent(), sub.patternCompareFunctions)) {
720
+ try {
721
+ sub.callback(getEvent());
722
+ }
723
+ catch (err) {
724
+ this.log.error(`Error in callback: ${err.toString()}`);
725
+ }
726
+ }
655
727
  }
656
728
  }
657
- const _eventObj = (0, eventObj_1.createEventObject)(this.context, id, this.convertBackStringifiedValues(id, state), this.convertBackStringifiedValues(id, oldState));
658
729
  // if this state matches any subscriptions
659
- for (let i = 0, l = this.subscriptions.length; i < l; i++) {
660
- const sub = this.subscriptions[i];
661
- if (sub?.patternCompareFunctions && patternMatching(_eventObj, sub.patternCompareFunctions)) {
730
+ for (let i = 0, l = wildcardSubs.length; i < l; i++) {
731
+ const sub = wildcardSubs[i];
732
+ if (sub?.patternCompareFunctions && patternMatching(getEvent(), sub.patternCompareFunctions)) {
662
733
  try {
663
- sub.callback(_eventObj);
734
+ sub.callback(getEvent());
664
735
  }
665
736
  catch (err) {
666
737
  this.log.error(`Error in callback: ${err.toString()}`);
@@ -683,15 +754,22 @@ class JavaScript extends adapter_core_1.Adapter {
683
754
  }
684
755
  }
685
756
  async onUnload(callback) {
686
- await this.debugStop();
687
- this.stopTimeSchedules();
688
- if (this.setStateCountCheckInterval) {
689
- clearInterval(this.setStateCountCheckInterval);
690
- this.setStateCountCheckInterval = null;
757
+ try {
758
+ await this.debugStop();
759
+ this.stopTimeSchedules();
760
+ if (this.setStateCountCheckInterval) {
761
+ clearInterval(this.setStateCountCheckInterval);
762
+ this.setStateCountCheckInterval = null;
763
+ }
764
+ await this.stopAllScripts();
691
765
  }
692
- await this.stopAllScripts();
693
- if (typeof callback === 'function') {
694
- callback();
766
+ catch (err) {
767
+ this.log.error(`Error during unload: ${err.message}`);
768
+ }
769
+ finally {
770
+ if (typeof callback === 'function') {
771
+ callback();
772
+ }
695
773
  }
696
774
  }
697
775
  async onReady() {
@@ -989,16 +1067,12 @@ class JavaScript extends adapter_core_1.Adapter {
989
1067
  const body = JSON.stringify(bodyObj);
990
1068
  const bodyBuffer = Buffer.from(body, 'utf8');
991
1069
  chatHeaders['Content-Length'] = bodyBuffer.length;
992
- let urlObj;
993
- try {
994
- urlObj = new URL(url);
995
- }
996
- catch {
1070
+ const resolved = resolveRequestModule(url);
1071
+ if (!resolved) {
997
1072
  this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
998
1073
  break;
999
1074
  }
1000
- const isHttps = urlObj.protocol === 'https:';
1001
- const requestModule = isHttps ? https : http;
1075
+ const { module: requestModule, isHttps } = resolved;
1002
1076
  try {
1003
1077
  const req = requestModule.request(url, {
1004
1078
  method: 'POST',
@@ -1110,16 +1184,12 @@ class JavaScript extends adapter_core_1.Adapter {
1110
1184
  testHeaders.Authorization = `Bearer ${apiKey}`;
1111
1185
  }
1112
1186
  }
1113
- let urlObj;
1114
- try {
1115
- urlObj = new URL(url);
1116
- }
1117
- catch {
1187
+ const resolved = resolveRequestModule(url);
1188
+ if (!resolved) {
1118
1189
  this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
1119
1190
  break;
1120
1191
  }
1121
- const isHttps = urlObj.protocol === 'https:';
1122
- const requestModule = isHttps ? https : http;
1192
+ const { module: requestModule, isHttps } = resolved;
1123
1193
  try {
1124
1194
  const req = requestModule.request(url, {
1125
1195
  method: 'GET',
@@ -1234,14 +1304,16 @@ class JavaScript extends adapter_core_1.Adapter {
1234
1304
  }
1235
1305
  }
1236
1306
  onLog(msg) {
1237
- Object.keys(this.logSubscriptions).forEach((name) => this.logSubscriptions[name].forEach(handler => {
1238
- if (typeof handler.cb === 'function' &&
1239
- (handler.severity === '*' || handler.severity === msg.severity)) {
1240
- handler.sandbox.logHandler = handler.severity || '*';
1241
- handler.cb.call(handler.sandbox, msg);
1242
- handler.sandbox.logHandler = undefined;
1243
- }
1244
- }));
1307
+ for (const name of Object.keys(this.logSubscriptions)) {
1308
+ for (const handler of this.logSubscriptions[name]) {
1309
+ if (typeof handler.cb === 'function' &&
1310
+ (handler.severity === '*' || handler.severity === msg.severity)) {
1311
+ handler.sandbox.logHandler = handler.severity || '*';
1312
+ handler.cb.call(handler.sandbox, msg);
1313
+ handler.sandbox.logHandler = undefined;
1314
+ }
1315
+ }
1316
+ }
1245
1317
  }
1246
1318
  logError(scriptName, msg, e, offs) {
1247
1319
  const stack = e.stack ? e.stack.toString().split('\n') : e ? e.toString() : '';
@@ -1493,11 +1565,11 @@ class JavaScript extends adapter_core_1.Adapter {
1493
1565
  }
1494
1566
  }
1495
1567
  }
1496
- // CHeck setState counter per minute and stop a script if too high
1568
+ // Check setState counter per minute and stop a script if too high
1497
1569
  this.setStateCountCheckInterval = setInterval(() => {
1498
- Object.keys(this.scripts).forEach(id => {
1570
+ for (const id of Object.keys(this.scripts)) {
1499
1571
  if (!this.scripts[id]) {
1500
- return;
1572
+ continue;
1501
1573
  }
1502
1574
  const currentSetStatePerMinuteCounter = this.scripts[id].setStatePerMinuteCounter;
1503
1575
  this.scripts[id].setStatePerMinuteCounter = 0;
@@ -1515,8 +1587,8 @@ class JavaScript extends adapter_core_1.Adapter {
1515
1587
  this.scripts[id].setStatePerMinuteProblemCounter--;
1516
1588
  this.log.debug(`Script ${id} has NOT reached the maximum of ${this.config.maxSetStatePerMinute} setState calls per minute. Decrease problem counter to ${this.scripts[id].setStatePerMinuteProblemCounter}`);
1517
1589
  }
1518
- });
1519
- }, 60000);
1590
+ }
1591
+ }, 60_000).unref();
1520
1592
  }
1521
1593
  loadTypeScriptDeclarations() {
1522
1594
  // try to load the typings on disk for all 3rd party modules
@@ -1534,10 +1606,15 @@ class JavaScript extends adapter_core_1.Adapter {
1534
1606
  .split(/[,;\s]+/)
1535
1607
  .map(s => s.trim())
1536
1608
  .filter(s => !!s);
1609
+ // O(1) lookups – avoids O(n²) Array.includes inside loops
1610
+ const installedSet = new Set(installedLibs);
1611
+ const wantsSet = new Set(wantsTypings);
1612
+ const packagesSet = new Set(packages);
1537
1613
  // Add all installed libraries the user has requested typings for to the list of packages
1538
1614
  for (const lib of installedLibs) {
1539
- if (wantsTypings.includes(lib) && !packages.includes(lib)) {
1615
+ if (wantsSet.has(lib) && !packagesSet.has(lib)) {
1540
1616
  packages.push(lib);
1617
+ packagesSet.add(lib);
1541
1618
  }
1542
1619
  }
1543
1620
  // Some packages have submodules (e.g., rxjs/operators) that are not exposed through the main entry point
@@ -1548,8 +1625,9 @@ class JavaScript extends adapter_core_1.Adapter {
1548
1625
  continue;
1549
1626
  }
1550
1627
  const pkgName = lib.substring(0, lib.indexOf('/'));
1551
- if (installedLibs.includes(pkgName) && !packages.includes(lib)) {
1628
+ if (installedSet.has(pkgName) && !packagesSet.has(lib)) {
1552
1629
  packages.push(lib);
1630
+ packagesSet.add(lib);
1553
1631
  }
1554
1632
  }
1555
1633
  }
@@ -1583,43 +1661,38 @@ class JavaScript extends adapter_core_1.Adapter {
1583
1661
  if (obj) {
1584
1662
  // add state to state ID's list
1585
1663
  if (obj.type === 'state') {
1586
- if (!this.stateIds.includes(id)) {
1587
- this.stateIds.push(id);
1588
- this.stateIds.sort();
1664
+ if (!this.stateIdSet.has(id)) {
1665
+ this.sortedInsert(id);
1666
+ this.stateIdSet.add(id);
1589
1667
  }
1590
1668
  if (this.context.devices && this.context.channels) {
1591
1669
  const parts = id.split('.');
1592
1670
  parts.pop();
1593
1671
  const chn = parts.join('.');
1594
- this.context.channels[chn] ||= [];
1595
- this.context.channels[chn].push(id);
1672
+ this.context.channels[chn] ||= new Set();
1673
+ this.context.channels[chn].add(id);
1596
1674
  parts.pop();
1597
1675
  const dev = parts.join('.');
1598
- this.context.devices[dev] ||= [];
1599
- this.context.devices[dev].push(id);
1676
+ this.context.devices[dev] ||= new Set();
1677
+ this.context.devices[dev].add(id);
1600
1678
  }
1601
1679
  }
1602
1680
  }
1603
1681
  else {
1604
1682
  // delete object from state ID's list
1605
- const pos = this.stateIds.indexOf(id);
1683
+ const pos = this.binaryIndexOf(this.stateIds, id);
1606
1684
  if (pos !== -1) {
1607
1685
  this.stateIds.splice(pos, 1);
1686
+ this.stateIdSet.delete(id);
1608
1687
  }
1609
1688
  if (this.context.devices && this.context.channels) {
1610
1689
  const parts = id.split('.');
1611
1690
  parts.pop();
1612
1691
  const chn = parts.join('.');
1613
- if (this.context.channels[chn]) {
1614
- const posChn = this.context.channels[chn].indexOf(id);
1615
- posChn !== -1 && this.context.channels[chn].splice(posChn, 1);
1616
- }
1692
+ this.context.channels[chn]?.delete(id);
1617
1693
  parts.pop();
1618
1694
  const dev = parts.join('.');
1619
- if (this.context.devices[dev]) {
1620
- const posDev = this.context.devices[dev].indexOf(id);
1621
- posDev !== -1 && this.context.devices[dev].splice(posDev, 1);
1622
- }
1695
+ this.context.devices[dev]?.delete(id);
1623
1696
  }
1624
1697
  delete this.folderCreationVerifiedObjects[id];
1625
1698
  }
@@ -1744,11 +1817,12 @@ class JavaScript extends adapter_core_1.Adapter {
1744
1817
  this.context.states = this.states;
1745
1818
  this.addGetProperty(this.states);
1746
1819
  }
1747
- // remember all IDs
1748
- for (const id in res) {
1749
- if (Object.prototype.hasOwnProperty.call(res, id)) {
1750
- this.stateIds.push(id);
1751
- }
1820
+ // remember all IDs – sort once to guarantee the sorted invariant
1821
+ // required by binaryIndexOf() / sortedInsert() used later
1822
+ const keys = Object.keys(res).sort();
1823
+ for (const id of keys) {
1824
+ this.stateIds.push(id);
1825
+ this.stateIdSet.add(id);
1752
1826
  }
1753
1827
  this.statesInitDone = true;
1754
1828
  this.log.info('received all states');
@@ -1768,17 +1842,18 @@ class JavaScript extends adapter_core_1.Adapter {
1768
1842
  this.objects = {};
1769
1843
  this.context.objects = this.objects;
1770
1844
  for (let i = 0; i < res.rows.length; i++) {
1771
- if (!res.rows[i].doc) {
1845
+ const doc = res.rows[i]?.doc;
1846
+ if (!doc) {
1772
1847
  this.log.debug(`Got empty object for index ${i} (${res.rows[i].id})`);
1773
1848
  continue;
1774
1849
  }
1775
- if (this.objects[res.rows[i].doc._id] === undefined) {
1850
+ if (this.objects[doc._id] === undefined) {
1776
1851
  // If was already there, ignore
1777
- this.objects[res.rows[i].doc._id] = res.rows[i].doc;
1852
+ this.objects[doc._id] = doc;
1778
1853
  }
1779
- this.objects[res.rows[i].doc._id].type === 'enum' && this._enums.push(res.rows[i].doc._id);
1854
+ doc.type === 'enum' && this._enums.add(doc._id);
1780
1855
  // Collect all names
1781
- this.addToNames(this.objects[res.rows[i].doc._id]);
1856
+ this.addToNames(this.objects[doc._id]);
1782
1857
  }
1783
1858
  this.addGetProperty(this.objects);
1784
1859
  const systemConfig = this.objects['system.config'];
@@ -1933,37 +2008,32 @@ class JavaScript extends adapter_core_1.Adapter {
1933
2008
  }
1934
2009
  this.names[name].push(id);
1935
2010
  }
2011
+ // keep reverse-map up to date for O(1) getName()
2012
+ this.nameById.set(id, name);
1936
2013
  }
1937
2014
  }
1938
2015
  removeFromNames(id) {
1939
2016
  const n = this.getName(id);
1940
2017
  if (n) {
1941
2018
  if (Array.isArray(this.names[n])) {
1942
- const pos = this.names[n].indexOf(id);
2019
+ const arr = this.names[n];
2020
+ const pos = arr.indexOf(id);
1943
2021
  if (pos > -1) {
1944
- this.names[n].splice(pos, 1);
1945
- if (this.names[n].length === 1) {
1946
- this.names[n] = this.names[n][0];
2022
+ arr.splice(pos, 1);
2023
+ if (arr.length === 1) {
2024
+ this.names[n] = arr[0];
1947
2025
  }
1948
2026
  }
1949
2027
  }
1950
2028
  else {
1951
2029
  delete this.names[n];
1952
2030
  }
2031
+ // keep reverse-map up to date for O(1) getName()
2032
+ this.nameById.delete(id);
1953
2033
  }
1954
2034
  }
1955
2035
  getName(id) {
1956
- for (const n in this.names) {
1957
- if (this.names[n] && Array.isArray(this.names[n])) {
1958
- if (this.names[n].includes(id)) {
1959
- return n;
1960
- }
1961
- }
1962
- else if (this.names[n] === id) {
1963
- return n;
1964
- }
1965
- }
1966
- return null;
2036
+ return this.nameById.get(id) ?? null;
1967
2037
  }
1968
2038
  async installNpm(npmLib) {
1969
2039
  return new Promise((resolve, reject) => {
@@ -1977,6 +2047,7 @@ class JavaScript extends adapter_core_1.Adapter {
1977
2047
  const child = this.mods.child_process.exec(cmd, {
1978
2048
  windowsHide: true,
1979
2049
  cwd: path,
2050
+ timeout: 120_000, // 2 minutes max – prevents infinite blocking
1980
2051
  });
1981
2052
  child.stdout?.on('data', buf => this.log.info(buf.toString('utf8')));
1982
2053
  child.stderr?.on('data', buf => this.log.error(buf.toString('utf8')));
@@ -2124,8 +2195,8 @@ class JavaScript extends adapter_core_1.Adapter {
2124
2195
  }
2125
2196
  }
2126
2197
  execute(script, name, engineType, verbose, debug) {
2127
- script.intervals = [];
2128
- script.timeouts = [];
2198
+ script.intervals = new Set();
2199
+ script.timeouts = new Set();
2129
2200
  script.schedules = [];
2130
2201
  script.wizards = [];
2131
2202
  script.name = name;
@@ -2157,13 +2228,54 @@ class JavaScript extends adapter_core_1.Adapter {
2157
2228
  this.logError(name, 'Error by run:', err);
2158
2229
  }
2159
2230
  }
2231
+ /**
2232
+ * Finds the index of `id` in a sorted array using binary search – O(log n).
2233
+ * Returns -1 if not found. Used instead of Array.indexOf on stateIds.
2234
+ */
2235
+ binaryIndexOf(arr, id) {
2236
+ let lo = 0;
2237
+ let hi = arr.length - 1;
2238
+ while (lo <= hi) {
2239
+ const mid = (lo + hi) >>> 1;
2240
+ if (arr[mid] === id) {
2241
+ return mid;
2242
+ }
2243
+ else if (arr[mid] < id) {
2244
+ lo = mid + 1;
2245
+ }
2246
+ else {
2247
+ hi = mid - 1;
2248
+ }
2249
+ }
2250
+ return -1;
2251
+ }
2252
+ /**
2253
+ * Inserts `id` into the sorted `stateIds` array using binary search – O(log n).
2254
+ * Much faster than push() + sort() which is O(n log n) on every insertion.
2255
+ */
2256
+ sortedInsert(id) {
2257
+ let lo = 0;
2258
+ let hi = this.stateIds.length;
2259
+ while (lo < hi) {
2260
+ const mid = (lo + hi) >>> 1;
2261
+ if (this.stateIds[mid] < id) {
2262
+ lo = mid + 1;
2263
+ }
2264
+ else {
2265
+ hi = mid;
2266
+ }
2267
+ }
2268
+ if (this.stateIds[lo] !== id) {
2269
+ this.stateIds.splice(lo, 0, id);
2270
+ }
2271
+ }
2160
2272
  unsubscribe(id) {
2161
2273
  if (!id) {
2162
2274
  this.log.warn('unsubscribe: empty name');
2163
2275
  return;
2164
2276
  }
2165
2277
  if (Array.isArray(id)) {
2166
- id.forEach(sub => unsubscribe(sub));
2278
+ id.forEach(sub => this.unsubscribe(sub));
2167
2279
  return;
2168
2280
  }
2169
2281
  if (id.constructor && id.constructor.name === 'RegExp') {
@@ -2180,11 +2292,8 @@ class JavaScript extends adapter_core_1.Adapter {
2180
2292
  const a = `${parts[0]}.${parts[1]}`;
2181
2293
  const alive = `system.adapter.${a}.alive`;
2182
2294
  if (this.adapterSubs[alive]) {
2183
- const pos = this.adapterSubs[alive].indexOf(id);
2184
- if (pos !== -1) {
2185
- this.adapterSubs[alive].splice(pos, 1);
2186
- }
2187
- if (!this.adapterSubs[alive].length) {
2295
+ this.adapterSubs[alive].delete(id);
2296
+ if (!this.adapterSubs[alive].size) {
2188
2297
  delete this.adapterSubs[alive];
2189
2298
  }
2190
2299
  }
@@ -2260,6 +2369,11 @@ class JavaScript extends adapter_core_1.Adapter {
2260
2369
  for (let i = this.subscriptions.length - 1; i >= 0; i--) {
2261
2370
  if (this.subscriptions[i].name === name) {
2262
2371
  const sub = this.subscriptions.splice(i, 1)[0];
2372
+ // Also remove from the O(1) dispatch structures – shared helper to keep the
2373
+ // exact-id classification identical to the subscribe side in sandbox.ts
2374
+ if (sub) {
2375
+ (0, sandbox_1.removeFromDispatchIndex)(this.context, sub);
2376
+ }
2263
2377
  if (sub?.pattern.id) {
2264
2378
  this.unsubscribe(sub.pattern.id);
2265
2379
  }
@@ -2291,31 +2405,46 @@ class JavaScript extends adapter_core_1.Adapter {
2291
2405
  if (this.subscriptionsObject[i].name === name) {
2292
2406
  const sub = this.subscriptionsObject.splice(i, 1)[0];
2293
2407
  if (sub) {
2408
+ // Remove from O(1) dispatch map
2409
+ const mapSubs = this.subscriptionsObjectMap.get(sub.pattern);
2410
+ if (mapSubs) {
2411
+ const pos = mapSubs.indexOf(sub);
2412
+ if (pos !== -1) {
2413
+ mapSubs.splice(pos, 1);
2414
+ }
2415
+ if (!mapSubs.length) {
2416
+ this.subscriptionsObjectMap.delete(sub.pattern);
2417
+ }
2418
+ }
2294
2419
  this.unsubscribeForeignObjects(sub.pattern);
2295
2420
  }
2296
2421
  }
2297
2422
  }
2298
2423
  // Stop all timeouts
2299
- for (let i = 0; i < this.scripts[name].timeouts.length; i++) {
2300
- clearTimeout(this.scripts[name].timeouts[i]);
2424
+ for (const t of this.scripts[name].timeouts) {
2425
+ clearTimeout(t);
2301
2426
  }
2302
2427
  // Stop all intervals
2303
- for (let i = 0; i < this.scripts[name].intervals.length; i++) {
2304
- clearInterval(this.scripts[name].intervals[i]);
2305
- }
2306
- // Stop all delayed states (setStateDelayed timers)
2307
- for (const stateId of Object.keys(this.timers)) {
2308
- if (this.timers[stateId]) {
2309
- for (let i = this.timers[stateId].length - 1; i >= 0; i--) {
2310
- if (this.timers[stateId][i].scriptName === name) {
2311
- clearTimeout(this.timers[stateId][i].t);
2312
- this.timers[stateId].splice(i, 1);
2428
+ for (const t of this.scripts[name].intervals) {
2429
+ clearInterval(t);
2430
+ }
2431
+ // Stop all delayed states (setStateDelayed timers) – O(1) via reverse-index
2432
+ const scriptStateIds = this.timersByScript.get(name);
2433
+ if (scriptStateIds) {
2434
+ for (const stateId of scriptStateIds) {
2435
+ if (this.timers[stateId]) {
2436
+ for (let i = this.timers[stateId].length - 1; i >= 0; i--) {
2437
+ if (this.timers[stateId][i].scriptName === name) {
2438
+ clearTimeout(this.timers[stateId][i].t);
2439
+ this.timers[stateId].splice(i, 1);
2440
+ }
2441
+ }
2442
+ if (!this.timers[stateId].length) {
2443
+ delete this.timers[stateId];
2313
2444
  }
2314
- }
2315
- if (!this.timers[stateId].length) {
2316
- delete this.timers[stateId];
2317
2445
  }
2318
2446
  }
2447
+ this.timersByScript.delete(name);
2319
2448
  }
2320
2449
  // Stop all scheduled jobs
2321
2450
  for (let i = 0; i < this.scripts[name].schedules.length; i++) {
@@ -2562,6 +2691,11 @@ class JavaScript extends adapter_core_1.Adapter {
2562
2691
  this.timeScheduleTimer = setTimeout(() => this.timeSchedule(), interval);
2563
2692
  }
2564
2693
  async dayTimeSchedules() {
2694
+ // Always clear any existing timer to prevent memory leaks on rapid re-scheduling
2695
+ if (this.dayScheduleTimer) {
2696
+ clearTimeout(this.dayScheduleTimer);
2697
+ this.dayScheduleTimer = null;
2698
+ }
2565
2699
  // get astrological event
2566
2700
  if (this.config.latitude === undefined ||
2567
2701
  this.config.longitude === undefined ||
@@ -2957,19 +3091,18 @@ class JavaScript extends adapter_core_1.Adapter {
2957
3091
  }
2958
3092
  }
2959
3093
  function patternMatching(event, patternFunctions) {
2960
- let matched = false;
3094
+ const logic = patternFunctions.logic ?? 'and';
2961
3095
  for (let i = 0, len = patternFunctions.length; i < len; i++) {
2962
- if (patternFunctions[i](event)) {
2963
- if (patternFunctions.logic === 'or') {
2964
- return true;
2965
- }
2966
- matched = true;
3096
+ const result = patternFunctions[i](event);
3097
+ if (logic === 'and' && !result) {
3098
+ return false; // short-circuit AND – one false is enough
2967
3099
  }
2968
- else if (patternFunctions.logic === 'and') {
2969
- return false;
3100
+ if (logic === 'or' && result) {
3101
+ return true; // short-circuit OR – one true is enough
2970
3102
  }
2971
3103
  }
2972
- return matched;
3104
+ // AND: all passed → true; OR: none matched → false
3105
+ return logic === 'and';
2973
3106
  }
2974
3107
  // If started as allInOne mode => return function to create an instance
2975
3108
  if (require.main !== module) {