iobroker.javascript 9.2.1 → 9.2.3

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 -16
  2. package/admin/assets/{AiChatPanel-Cc1KscBJ.js → AiChatPanel-C4zUsNmN.js} +15 -15
  3. package/admin/assets/{ScriptEditor-6qrtvwPL.js → ScriptEditor-C6W68ya0.js} +1 -1
  4. package/admin/assets/ScriptEditorVanillaMonaco-DYTcDqp_.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-BZxCaQhO.js → index-BXfqctma.js} +1 -1
  8. package/admin/assets/{index-CYbVtccs.js → index-CUau9-il.js} +83 -83
  9. package/admin/assets/{index-DsW7vBcg.js → index-crBQrXKG.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-BYBqzJuX.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-jfkoGyES.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-CS_GBN2m.js} +159 -159
  35. package/admin/custom/assets/{index-NItS-TWp.js → index-ChQotUCl.js} +1 -1
  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-Dp7A3XHd.js → index-DV76KW3-.js} +4 -4
  39. package/admin/custom/assets/{index-D0iTiVVG.js → index-DlQbqHom.js} +1 -1
  40. package/admin/custom/assets/{index-BptSiKBK.js → index-FTmk0JJx.js} +5 -5
  41. package/admin/custom/assets/index-qs6J40Tp.js +964 -0
  42. package/admin/custom/assets/{jsx-runtime-DfVU3AZG.js → jsx-runtime-BplTyRFk.js} +1 -1
  43. package/admin/custom/assets/localSharedImportMap-BeObsVNc.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-wYe_Np-4.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-BcRVut8U.js} +2 -2
  48. package/admin/custom/customComponents.js +1 -1
  49. package/admin/google-blockly/own/blocks_system.js +34 -0
  50. package/admin/google-blockly/own/blocks_words.js +4 -0
  51. package/admin/google-blockly/own/field_oid.js +151 -2
  52. package/admin/mf-manifest.json +1 -1
  53. package/admin/remoteEntry.js +1 -1
  54. package/admin/tab.html +10 -8
  55. package/build/lib/eventObj.js +21 -12
  56. package/build/lib/eventObj.js.map +1 -1
  57. package/build/lib/sandbox.js +168 -58
  58. package/build/lib/sandbox.js.map +1 -1
  59. package/build/lib/scheduler.js +18 -13
  60. package/build/lib/scheduler.js.map +1 -1
  61. package/build/main.js +224 -135
  62. package/build/main.js.map +1 -1
  63. package/build/types.d.ts +14 -6
  64. package/docs/de/README.md +2 -0
  65. package/docs/de/blockly.md +669 -8
  66. package/docs/en/blockly.md +33 -0
  67. package/io-package.json +27 -1
  68. package/package.json +1 -1
  69. package/admin/assets/ScriptEditorVanillaMonaco-bJua4sm1.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,26 @@ 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 = [];
260
276
  subscriptionsFile = [];
261
277
  subscriptionsObject = [];
278
+ /** O(1) dispatch map for subscriptionsObject – pattern → subscribers */
279
+ subscriptionsObjectMap = new Map();
280
+ /** IO-9: Cache for sendTo broadcast – adapterName → instance list, invalidated on object change */
281
+ sendToInstanceCache = new Map();
262
282
  subscribedPatterns = {};
263
283
  subscribedPatternsFile = {};
264
284
  adapterSubs = {};
265
285
  timers = {};
266
- _enums = [];
286
+ /** Reverse-index: scriptName → Set of stateIds that have timers for this script – O(1) cleanup */
287
+ timersByScript = new Map();
288
+ /** O(1) Set for enum-id lookups – replaces sorted string[] array */
289
+ _enums = new Set();
267
290
  names = {}; // name: id
291
+ /** Reverse map: id → name for O(1) getName() lookups */
292
+ nameById = new Map();
268
293
  scripts = {};
269
294
  password = '';
270
295
  messageBusHandlers = {};
@@ -395,11 +420,14 @@ class JavaScript extends adapter_core_1.Adapter {
395
420
  subscriptions: this.subscriptions,
396
421
  subscriptionsFile: this.subscriptionsFile,
397
422
  subscriptionsObject: this.subscriptionsObject,
423
+ subscriptionsObjectMap: this.subscriptionsObjectMap,
424
+ sendToInstanceCache: this.sendToInstanceCache,
398
425
  subscribedPatterns: this.subscribedPatterns,
399
426
  subscribedPatternsFile: this.subscribedPatternsFile,
400
427
  adapterSubs: this.adapterSubs,
401
428
  cacheObjectEnums: {},
402
429
  timers: this.timers,
430
+ timersByScript: this.timersByScript,
403
431
  enums: this._enums,
404
432
  names: this.names,
405
433
  scripts: this.scripts,
@@ -446,20 +474,20 @@ class JavaScript extends adapter_core_1.Adapter {
446
474
  if (id.startsWith('enum.')) {
447
475
  // clear cache
448
476
  this.context.cacheObjectEnums = {};
449
- // update this._enums array
477
+ // update this._enums Set
450
478
  if (obj) {
451
- // If new
452
- if (!this._enums.includes(id)) {
453
- this._enums.push(id);
454
- this._enums.sort();
455
- }
479
+ this._enums.add(id);
456
480
  }
457
481
  else {
458
- const pos = this._enums.indexOf(id);
459
- // if deleted
460
- if (pos !== -1) {
461
- this._enums.splice(pos, 1);
462
- }
482
+ this._enums.delete(id);
483
+ }
484
+ }
485
+ // IO-9: Invalidate sendTo instance-cache when adapter instances change
486
+ if (id.startsWith('system.adapter.')) {
487
+ const parts = id.split('.');
488
+ if (parts.length >= 3) {
489
+ const adapterName = parts[2]; // e.g. "zigbee" from "system.adapter.zigbee.0"
490
+ this.sendToInstanceCache.delete(adapterName);
463
491
  }
464
492
  }
465
493
  if (id === 'system.config' && obj?.common?.language) {
@@ -494,9 +522,10 @@ class JavaScript extends adapter_core_1.Adapter {
494
522
  /* ignore */
495
523
  });
496
524
  }
497
- this.subscriptionsObject.forEach(sub => {
498
- // ToDo: implement comparing with id.0.* too
499
- if (sub.pattern === id) {
525
+ // O(1) dispatch via pattern map instead of O(n) forEach
526
+ const objSubs = this.subscriptionsObjectMap.get(id);
527
+ if (objSubs) {
528
+ for (const sub of objSubs) {
500
529
  try {
501
530
  sub.callback(id, obj);
502
531
  }
@@ -504,7 +533,7 @@ class JavaScript extends adapter_core_1.Adapter {
504
533
  this.log.error(`Error in callback: ${err.toString()}`);
505
534
  }
506
535
  }
507
- });
536
+ }
508
537
  // handle Script object updates
509
538
  if (!obj && formerObj?.type === 'script') {
510
539
  // Object Deleted just now
@@ -631,16 +660,16 @@ class JavaScript extends adapter_core_1.Adapter {
631
660
  if (this.adapterSubs[id]) {
632
661
  const parts = id.split('.');
633
662
  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]);
663
+ for (const sub of this.adapterSubs[id]) {
664
+ this.log.info(`Detected coming adapter "${a}". Send subscribe: ${sub}`);
665
+ this.sendTo(a, 'subscribe', sub);
637
666
  }
638
667
  }
639
668
  }
640
669
  }
641
- else if ( /*!oldState && */!this.stateIds.includes(id)) {
642
- this.stateIds.push(id);
643
- this.stateIds.sort();
670
+ else if ( /*!oldState && */!this.stateIdSet.has(id)) {
671
+ this.sortedInsert(id);
672
+ this.stateIdSet.add(id);
644
673
  }
645
674
  this.states[id] = state;
646
675
  }
@@ -649,9 +678,10 @@ class JavaScript extends adapter_core_1.Adapter {
649
678
  delete this.states[id];
650
679
  }
651
680
  state = {};
652
- const pos = this.stateIds.indexOf(id);
681
+ const pos = this.binaryIndexOf(this.stateIds, id);
653
682
  if (pos !== -1) {
654
683
  this.stateIds.splice(pos, 1);
684
+ this.stateIdSet.delete(id);
655
685
  }
656
686
  }
657
687
  const _eventObj = (0, eventObj_1.createEventObject)(this.context, id, this.convertBackStringifiedValues(id, state), this.convertBackStringifiedValues(id, oldState));
@@ -683,15 +713,22 @@ class JavaScript extends adapter_core_1.Adapter {
683
713
  }
684
714
  }
685
715
  async onUnload(callback) {
686
- await this.debugStop();
687
- this.stopTimeSchedules();
688
- if (this.setStateCountCheckInterval) {
689
- clearInterval(this.setStateCountCheckInterval);
690
- this.setStateCountCheckInterval = null;
716
+ try {
717
+ await this.debugStop();
718
+ this.stopTimeSchedules();
719
+ if (this.setStateCountCheckInterval) {
720
+ clearInterval(this.setStateCountCheckInterval);
721
+ this.setStateCountCheckInterval = null;
722
+ }
723
+ await this.stopAllScripts();
691
724
  }
692
- await this.stopAllScripts();
693
- if (typeof callback === 'function') {
694
- callback();
725
+ catch (err) {
726
+ this.log.error(`Error during unload: ${err.message}`);
727
+ }
728
+ finally {
729
+ if (typeof callback === 'function') {
730
+ callback();
731
+ }
695
732
  }
696
733
  }
697
734
  async onReady() {
@@ -989,16 +1026,12 @@ class JavaScript extends adapter_core_1.Adapter {
989
1026
  const body = JSON.stringify(bodyObj);
990
1027
  const bodyBuffer = Buffer.from(body, 'utf8');
991
1028
  chatHeaders['Content-Length'] = bodyBuffer.length;
992
- let urlObj;
993
- try {
994
- urlObj = new URL(url);
995
- }
996
- catch {
1029
+ const resolved = resolveRequestModule(url);
1030
+ if (!resolved) {
997
1031
  this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
998
1032
  break;
999
1033
  }
1000
- const isHttps = urlObj.protocol === 'https:';
1001
- const requestModule = isHttps ? https : http;
1034
+ const { module: requestModule, isHttps } = resolved;
1002
1035
  try {
1003
1036
  const req = requestModule.request(url, {
1004
1037
  method: 'POST',
@@ -1110,16 +1143,12 @@ class JavaScript extends adapter_core_1.Adapter {
1110
1143
  testHeaders.Authorization = `Bearer ${apiKey}`;
1111
1144
  }
1112
1145
  }
1113
- let urlObj;
1114
- try {
1115
- urlObj = new URL(url);
1116
- }
1117
- catch {
1146
+ const resolved = resolveRequestModule(url);
1147
+ if (!resolved) {
1118
1148
  this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
1119
1149
  break;
1120
1150
  }
1121
- const isHttps = urlObj.protocol === 'https:';
1122
- const requestModule = isHttps ? https : http;
1151
+ const { module: requestModule, isHttps } = resolved;
1123
1152
  try {
1124
1153
  const req = requestModule.request(url, {
1125
1154
  method: 'GET',
@@ -1234,14 +1263,16 @@ class JavaScript extends adapter_core_1.Adapter {
1234
1263
  }
1235
1264
  }
1236
1265
  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
- }));
1266
+ for (const name in this.logSubscriptions) {
1267
+ for (const handler of this.logSubscriptions[name]) {
1268
+ if (typeof handler.cb === 'function' &&
1269
+ (handler.severity === '*' || handler.severity === msg.severity)) {
1270
+ handler.sandbox.logHandler = handler.severity || '*';
1271
+ handler.cb.call(handler.sandbox, msg);
1272
+ handler.sandbox.logHandler = undefined;
1273
+ }
1274
+ }
1275
+ }
1245
1276
  }
1246
1277
  logError(scriptName, msg, e, offs) {
1247
1278
  const stack = e.stack ? e.stack.toString().split('\n') : e ? e.toString() : '';
@@ -1495,9 +1526,9 @@ class JavaScript extends adapter_core_1.Adapter {
1495
1526
  }
1496
1527
  // CHeck setState counter per minute and stop a script if too high
1497
1528
  this.setStateCountCheckInterval = setInterval(() => {
1498
- Object.keys(this.scripts).forEach(id => {
1529
+ for (const id in this.scripts) {
1499
1530
  if (!this.scripts[id]) {
1500
- return;
1531
+ continue;
1501
1532
  }
1502
1533
  const currentSetStatePerMinuteCounter = this.scripts[id].setStatePerMinuteCounter;
1503
1534
  this.scripts[id].setStatePerMinuteCounter = 0;
@@ -1515,7 +1546,7 @@ class JavaScript extends adapter_core_1.Adapter {
1515
1546
  this.scripts[id].setStatePerMinuteProblemCounter--;
1516
1547
  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
1548
  }
1518
- });
1549
+ }
1519
1550
  }, 60000);
1520
1551
  }
1521
1552
  loadTypeScriptDeclarations() {
@@ -1534,10 +1565,15 @@ class JavaScript extends adapter_core_1.Adapter {
1534
1565
  .split(/[,;\s]+/)
1535
1566
  .map(s => s.trim())
1536
1567
  .filter(s => !!s);
1568
+ // O(1) lookups – avoids O(n²) Array.includes inside loops
1569
+ const installedSet = new Set(installedLibs);
1570
+ const wantsSet = new Set(wantsTypings);
1571
+ const packagesSet = new Set(packages);
1537
1572
  // Add all installed libraries the user has requested typings for to the list of packages
1538
1573
  for (const lib of installedLibs) {
1539
- if (wantsTypings.includes(lib) && !packages.includes(lib)) {
1574
+ if (wantsSet.has(lib) && !packagesSet.has(lib)) {
1540
1575
  packages.push(lib);
1576
+ packagesSet.add(lib);
1541
1577
  }
1542
1578
  }
1543
1579
  // Some packages have submodules (e.g., rxjs/operators) that are not exposed through the main entry point
@@ -1548,8 +1584,9 @@ class JavaScript extends adapter_core_1.Adapter {
1548
1584
  continue;
1549
1585
  }
1550
1586
  const pkgName = lib.substring(0, lib.indexOf('/'));
1551
- if (installedLibs.includes(pkgName) && !packages.includes(lib)) {
1587
+ if (installedSet.has(pkgName) && !packagesSet.has(lib)) {
1552
1588
  packages.push(lib);
1589
+ packagesSet.add(lib);
1553
1590
  }
1554
1591
  }
1555
1592
  }
@@ -1583,43 +1620,38 @@ class JavaScript extends adapter_core_1.Adapter {
1583
1620
  if (obj) {
1584
1621
  // add state to state ID's list
1585
1622
  if (obj.type === 'state') {
1586
- if (!this.stateIds.includes(id)) {
1587
- this.stateIds.push(id);
1588
- this.stateIds.sort();
1623
+ if (!this.stateIdSet.has(id)) {
1624
+ this.sortedInsert(id);
1625
+ this.stateIdSet.add(id);
1589
1626
  }
1590
1627
  if (this.context.devices && this.context.channels) {
1591
1628
  const parts = id.split('.');
1592
1629
  parts.pop();
1593
1630
  const chn = parts.join('.');
1594
- this.context.channels[chn] ||= [];
1595
- this.context.channels[chn].push(id);
1631
+ this.context.channels[chn] ||= new Set();
1632
+ this.context.channels[chn].add(id);
1596
1633
  parts.pop();
1597
1634
  const dev = parts.join('.');
1598
- this.context.devices[dev] ||= [];
1599
- this.context.devices[dev].push(id);
1635
+ this.context.devices[dev] ||= new Set();
1636
+ this.context.devices[dev].add(id);
1600
1637
  }
1601
1638
  }
1602
1639
  }
1603
1640
  else {
1604
1641
  // delete object from state ID's list
1605
- const pos = this.stateIds.indexOf(id);
1642
+ const pos = this.binaryIndexOf(this.stateIds, id);
1606
1643
  if (pos !== -1) {
1607
1644
  this.stateIds.splice(pos, 1);
1645
+ this.stateIdSet.delete(id);
1608
1646
  }
1609
1647
  if (this.context.devices && this.context.channels) {
1610
1648
  const parts = id.split('.');
1611
1649
  parts.pop();
1612
1650
  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
- }
1651
+ this.context.channels[chn]?.delete(id);
1617
1652
  parts.pop();
1618
1653
  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
- }
1654
+ this.context.devices[dev]?.delete(id);
1623
1655
  }
1624
1656
  delete this.folderCreationVerifiedObjects[id];
1625
1657
  }
@@ -1744,11 +1776,12 @@ class JavaScript extends adapter_core_1.Adapter {
1744
1776
  this.context.states = this.states;
1745
1777
  this.addGetProperty(this.states);
1746
1778
  }
1747
- // remember all IDs
1748
- for (const id in res) {
1749
- if (Object.prototype.hasOwnProperty.call(res, id)) {
1750
- this.stateIds.push(id);
1751
- }
1779
+ // remember all IDs – sort once to guarantee the sorted invariant
1780
+ // required by binaryIndexOf() / sortedInsert() used later
1781
+ const keys = Object.keys(res).sort();
1782
+ for (const id of keys) {
1783
+ this.stateIds.push(id);
1784
+ this.stateIdSet.add(id);
1752
1785
  }
1753
1786
  this.statesInitDone = true;
1754
1787
  this.log.info('received all states');
@@ -1768,17 +1801,18 @@ class JavaScript extends adapter_core_1.Adapter {
1768
1801
  this.objects = {};
1769
1802
  this.context.objects = this.objects;
1770
1803
  for (let i = 0; i < res.rows.length; i++) {
1771
- if (!res.rows[i].doc) {
1804
+ const doc = res.rows[i]?.doc;
1805
+ if (!doc) {
1772
1806
  this.log.debug(`Got empty object for index ${i} (${res.rows[i].id})`);
1773
1807
  continue;
1774
1808
  }
1775
- if (this.objects[res.rows[i].doc._id] === undefined) {
1809
+ if (this.objects[doc._id] === undefined) {
1776
1810
  // If was already there, ignore
1777
- this.objects[res.rows[i].doc._id] = res.rows[i].doc;
1811
+ this.objects[doc._id] = doc;
1778
1812
  }
1779
- this.objects[res.rows[i].doc._id].type === 'enum' && this._enums.push(res.rows[i].doc._id);
1813
+ doc.type === 'enum' && this._enums.add(doc._id);
1780
1814
  // Collect all names
1781
- this.addToNames(this.objects[res.rows[i].doc._id]);
1815
+ this.addToNames(this.objects[doc._id]);
1782
1816
  }
1783
1817
  this.addGetProperty(this.objects);
1784
1818
  const systemConfig = this.objects['system.config'];
@@ -1933,37 +1967,32 @@ class JavaScript extends adapter_core_1.Adapter {
1933
1967
  }
1934
1968
  this.names[name].push(id);
1935
1969
  }
1970
+ // keep reverse-map up to date for O(1) getName()
1971
+ this.nameById.set(id, name);
1936
1972
  }
1937
1973
  }
1938
1974
  removeFromNames(id) {
1939
1975
  const n = this.getName(id);
1940
1976
  if (n) {
1941
1977
  if (Array.isArray(this.names[n])) {
1942
- const pos = this.names[n].indexOf(id);
1978
+ const arr = this.names[n];
1979
+ const pos = arr.indexOf(id);
1943
1980
  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];
1981
+ arr.splice(pos, 1);
1982
+ if (arr.length === 1) {
1983
+ this.names[n] = arr[0];
1947
1984
  }
1948
1985
  }
1949
1986
  }
1950
1987
  else {
1951
1988
  delete this.names[n];
1952
1989
  }
1990
+ // keep reverse-map up to date for O(1) getName()
1991
+ this.nameById.delete(id);
1953
1992
  }
1954
1993
  }
1955
1994
  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;
1995
+ return this.nameById.get(id) ?? null;
1967
1996
  }
1968
1997
  async installNpm(npmLib) {
1969
1998
  return new Promise((resolve, reject) => {
@@ -1977,6 +2006,7 @@ class JavaScript extends adapter_core_1.Adapter {
1977
2006
  const child = this.mods.child_process.exec(cmd, {
1978
2007
  windowsHide: true,
1979
2008
  cwd: path,
2009
+ timeout: 120_000, // 2 minutes max – prevents infinite blocking
1980
2010
  });
1981
2011
  child.stdout?.on('data', buf => this.log.info(buf.toString('utf8')));
1982
2012
  child.stderr?.on('data', buf => this.log.error(buf.toString('utf8')));
@@ -2124,8 +2154,8 @@ class JavaScript extends adapter_core_1.Adapter {
2124
2154
  }
2125
2155
  }
2126
2156
  execute(script, name, engineType, verbose, debug) {
2127
- script.intervals = [];
2128
- script.timeouts = [];
2157
+ script.intervals = new Set();
2158
+ script.timeouts = new Set();
2129
2159
  script.schedules = [];
2130
2160
  script.wizards = [];
2131
2161
  script.name = name;
@@ -2157,13 +2187,54 @@ class JavaScript extends adapter_core_1.Adapter {
2157
2187
  this.logError(name, 'Error by run:', err);
2158
2188
  }
2159
2189
  }
2190
+ /**
2191
+ * Finds the index of `id` in a sorted array using binary search – O(log n).
2192
+ * Returns -1 if not found. Used instead of Array.indexOf on stateIds.
2193
+ */
2194
+ binaryIndexOf(arr, id) {
2195
+ let lo = 0;
2196
+ let hi = arr.length - 1;
2197
+ while (lo <= hi) {
2198
+ const mid = (lo + hi) >>> 1;
2199
+ if (arr[mid] === id) {
2200
+ return mid;
2201
+ }
2202
+ else if (arr[mid] < id) {
2203
+ lo = mid + 1;
2204
+ }
2205
+ else {
2206
+ hi = mid - 1;
2207
+ }
2208
+ }
2209
+ return -1;
2210
+ }
2211
+ /**
2212
+ * Inserts `id` into the sorted `stateIds` array using binary search – O(log n).
2213
+ * Much faster than push() + sort() which is O(n log n) on every insertion.
2214
+ */
2215
+ sortedInsert(id) {
2216
+ let lo = 0;
2217
+ let hi = this.stateIds.length;
2218
+ while (lo < hi) {
2219
+ const mid = (lo + hi) >>> 1;
2220
+ if (this.stateIds[mid] < id) {
2221
+ lo = mid + 1;
2222
+ }
2223
+ else {
2224
+ hi = mid;
2225
+ }
2226
+ }
2227
+ if (this.stateIds[lo] !== id) {
2228
+ this.stateIds.splice(lo, 0, id);
2229
+ }
2230
+ }
2160
2231
  unsubscribe(id) {
2161
2232
  if (!id) {
2162
2233
  this.log.warn('unsubscribe: empty name');
2163
2234
  return;
2164
2235
  }
2165
2236
  if (Array.isArray(id)) {
2166
- id.forEach(sub => unsubscribe(sub));
2237
+ id.forEach(sub => this.unsubscribe(sub));
2167
2238
  return;
2168
2239
  }
2169
2240
  if (id.constructor && id.constructor.name === 'RegExp') {
@@ -2180,11 +2251,8 @@ class JavaScript extends adapter_core_1.Adapter {
2180
2251
  const a = `${parts[0]}.${parts[1]}`;
2181
2252
  const alive = `system.adapter.${a}.alive`;
2182
2253
  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) {
2254
+ this.adapterSubs[alive].delete(id);
2255
+ if (!this.adapterSubs[alive].size) {
2188
2256
  delete this.adapterSubs[alive];
2189
2257
  }
2190
2258
  }
@@ -2291,31 +2359,46 @@ class JavaScript extends adapter_core_1.Adapter {
2291
2359
  if (this.subscriptionsObject[i].name === name) {
2292
2360
  const sub = this.subscriptionsObject.splice(i, 1)[0];
2293
2361
  if (sub) {
2362
+ // Remove from O(1) dispatch map
2363
+ const mapSubs = this.subscriptionsObjectMap.get(sub.pattern);
2364
+ if (mapSubs) {
2365
+ const pos = mapSubs.indexOf(sub);
2366
+ if (pos !== -1) {
2367
+ mapSubs.splice(pos, 1);
2368
+ }
2369
+ if (!mapSubs.length) {
2370
+ this.subscriptionsObjectMap.delete(sub.pattern);
2371
+ }
2372
+ }
2294
2373
  this.unsubscribeForeignObjects(sub.pattern);
2295
2374
  }
2296
2375
  }
2297
2376
  }
2298
2377
  // Stop all timeouts
2299
- for (let i = 0; i < this.scripts[name].timeouts.length; i++) {
2300
- clearTimeout(this.scripts[name].timeouts[i]);
2378
+ for (const t of this.scripts[name].timeouts) {
2379
+ clearTimeout(t);
2301
2380
  }
2302
2381
  // 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);
2382
+ for (const t of this.scripts[name].intervals) {
2383
+ clearInterval(t);
2384
+ }
2385
+ // Stop all delayed states (setStateDelayed timers) – O(1) via reverse-index
2386
+ const scriptStateIds = this.timersByScript.get(name);
2387
+ if (scriptStateIds) {
2388
+ for (const stateId of scriptStateIds) {
2389
+ if (this.timers[stateId]) {
2390
+ for (let i = this.timers[stateId].length - 1; i >= 0; i--) {
2391
+ if (this.timers[stateId][i].scriptName === name) {
2392
+ clearTimeout(this.timers[stateId][i].t);
2393
+ this.timers[stateId].splice(i, 1);
2394
+ }
2395
+ }
2396
+ if (!this.timers[stateId].length) {
2397
+ delete this.timers[stateId];
2313
2398
  }
2314
- }
2315
- if (!this.timers[stateId].length) {
2316
- delete this.timers[stateId];
2317
2399
  }
2318
2400
  }
2401
+ this.timersByScript.delete(name);
2319
2402
  }
2320
2403
  // Stop all scheduled jobs
2321
2404
  for (let i = 0; i < this.scripts[name].schedules.length; i++) {
@@ -2562,6 +2645,11 @@ class JavaScript extends adapter_core_1.Adapter {
2562
2645
  this.timeScheduleTimer = setTimeout(() => this.timeSchedule(), interval);
2563
2646
  }
2564
2647
  async dayTimeSchedules() {
2648
+ // Always clear any existing timer to prevent memory leaks on rapid re-scheduling
2649
+ if (this.dayScheduleTimer) {
2650
+ clearTimeout(this.dayScheduleTimer);
2651
+ this.dayScheduleTimer = null;
2652
+ }
2565
2653
  // get astrological event
2566
2654
  if (this.config.latitude === undefined ||
2567
2655
  this.config.longitude === undefined ||
@@ -2958,14 +3046,15 @@ class JavaScript extends adapter_core_1.Adapter {
2958
3046
  }
2959
3047
  function patternMatching(event, patternFunctions) {
2960
3048
  let matched = false;
3049
+ const logic = patternFunctions.logic;
2961
3050
  for (let i = 0, len = patternFunctions.length; i < len; i++) {
2962
3051
  if (patternFunctions[i](event)) {
2963
- if (patternFunctions.logic === 'or') {
3052
+ if (logic === 'or') {
2964
3053
  return true;
2965
3054
  }
2966
3055
  matched = true;
2967
3056
  }
2968
- else if (patternFunctions.logic === 'and') {
3057
+ else if (logic === 'and') {
2969
3058
  return false;
2970
3059
  }
2971
3060
  }