iobroker.javascript 9.3.1 → 10.0.0

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 (220) hide show
  1. package/README.md +5 -5
  2. package/admin/assets/AiChatPanel-p0KAg6Cs.js +237 -0
  3. package/admin/assets/AiDatapointProvider-DzFpUKkT.js +2 -0
  4. package/admin/assets/AiDiffView-BPTLnNx1.js +1 -0
  5. package/admin/assets/AiInlineProvider-CaHd2G2Q.js +5 -0
  6. package/admin/assets/AiScriptAnalyzer--BHML2rN.js +9 -0
  7. package/admin/assets/BlocklyEditor-CAjVMhYT.js +81 -0
  8. package/admin/assets/ContextWrapper-BVc8wTK_.css +1 -0
  9. package/admin/assets/ContextWrapper-D9CrnNKs.js +127 -0
  10. package/admin/assets/Debugger-SjROokFI.js +15 -0
  11. package/admin/assets/Error-DDfrxAt2.js +1 -0
  12. package/admin/assets/Import-DpTg1Jow.js +1 -0
  13. package/admin/assets/RulesEditor-BSRFWpEm.css +1 -0
  14. package/admin/assets/RulesEditor-D0hgrcBc.js +1 -0
  15. package/admin/assets/ScriptEditor-BGIuoNei.js +2 -0
  16. package/admin/assets/ScriptEditorVanillaMonaco-DyMQtoTQ.js +3 -0
  17. package/admin/assets/_virtual_mf-localSharedImportMap___mfe_internal__iobroker_javascript__mf_owner__1-DWJotR1B.js +1 -0
  18. package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-CxICLr5Z.js +1243 -0
  19. package/admin/assets/_virtual_mf___mfe_internal__iobroker_javascript__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BBAXJaqI.js +9 -0
  20. package/admin/assets/aiCodeLensProvider-hyAmut0e.js +4 -0
  21. package/admin/assets/aiPromptBuilder-Bs27qOHT.js +7 -0
  22. package/admin/assets/applyCodeEdit-D3nb7erO.js +1 -0
  23. package/admin/assets/blocklyDarkTheme-DEiAX5DT.js +1 -0
  24. package/admin/assets/cronHoverProvider-pKJkSevo.js +2 -0
  25. package/admin/assets/dist-uw5orDgd.js +4 -0
  26. package/admin/assets/{docs-compact-CKNGiYh2.js → docs-compact-D7rpMc7r.js} +2 -2
  27. package/admin/assets/hostInit-BFg5u9hY.js +1 -0
  28. package/admin/assets/index-B1gICjdq.js +192 -0
  29. package/admin/assets/index-BHhhebRG.css +1 -0
  30. package/admin/assets/inlineChatWidget-ZaisTOLh.js +2 -0
  31. package/admin/assets/inlineDiffController-hTwHHqcc.js +16 -0
  32. package/admin/assets/mf-entry-bootstrap-0-d6052b31.js +36 -0
  33. package/admin/assets/mjs-DWqivHEk.js +90 -0
  34. package/admin/assets/rolldown-runtime-C0FnF6B9.js +1 -0
  35. package/admin/assets/stateHoverProvider-C7DawIs-.js +9 -0
  36. package/admin/assets/virtualExposes-Ed0lGq7k.js +1 -0
  37. package/admin/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__iobroker_javascript__remoteEntry_js-BYs71IQF.js +1 -0
  38. package/admin/assets/virtual_mf-exposes___mfe_internal__iobroker_javascript__remoteEntry_js-DnnXQRkG.js +1 -0
  39. package/admin/assets/vite-preload-helper-B7qeedMF.js +1 -0
  40. package/admin/custom/assets/Components-BrDyDHXh.js +1 -0
  41. package/admin/custom/assets/Components-DftM8alF.css +1 -0
  42. package/admin/custom/assets/_virtual_mf-localSharedImportMap___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1-CyYJM2-v.js +1 -0
  43. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare___mf_0_emotion_mf_1_react__loadShare__.js-BdzCssLn.js +1 -0
  44. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare___mf_0_emotion_mf_1_styled__loadShare__.js-CGb5Cofy.js +1 -0
  45. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_gui_mf_2_components__loadShare__.js-BVCIAgbo.js +1243 -0
  46. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.js-BkB0anQX.js +1494 -0
  47. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare__leaflet__loadShare__.js-h-rCw3iz.js +1 -0
  48. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BotqDuw-.js +9 -0
  49. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__prebuild__date_mf_2_fns__prebuild__-Cb5TR7FB.js +1 -0
  50. package/admin/custom/assets/_virtual_mf___mfe_internal__ConfigCustomJavascriptSet__mf_owner__1__prebuild__leaflet__prebuild__-DARUoA03.js +1 -0
  51. package/admin/custom/assets/hostInit-DJx2uXXy.js +1 -0
  52. package/admin/custom/assets/index-uEKAoVT7.js +1 -0
  53. package/admin/custom/assets/lib-Yj8cfN0A.js +3 -0
  54. package/admin/custom/assets/mf-entry-bootstrap-0-77793feb.js +36 -0
  55. package/admin/custom/assets/rolldown-runtime-BcKkbAw3.js +1 -0
  56. package/admin/custom/assets/setYear-DJ9FbdkO.js +1 -0
  57. package/admin/custom/assets/virtualExposes-CxDjLoYu.js +1 -0
  58. package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__ConfigCustomJavascriptSet__customComponents_js-BU-ugy_7.js +1 -0
  59. package/admin/custom/assets/virtual_mf-exposes-ssr___mfe_internal__ConfigCustomJavascriptSet__customComponents_js-BEfZbrf7.js +2 -0
  60. package/admin/custom/assets/virtual_mf-exposes___mfe_internal__ConfigCustomJavascriptSet__customComponents_js-CTeZ0Jpm.js +2 -0
  61. package/admin/custom/assets/vite-preload-helper-B7qeedMF.js +1 -0
  62. package/admin/custom/customComponents.js +1 -1
  63. package/admin/jsonConfig.json +1 -1
  64. package/admin/mf-manifest.json +1 -1
  65. package/admin/mf-stats.json +1 -0
  66. package/admin/remoteEntry.js +1 -1
  67. package/admin/tab.html +12 -17
  68. package/build/lib/debugger.js +1 -1
  69. package/build/lib/debugger.js.map +1 -1
  70. package/build/lib/eventObj.js.map +1 -1
  71. package/build/lib/inspect.js.map +1 -1
  72. package/build/lib/javascript.d.ts +18 -19
  73. package/build/lib/nodeModulesManagement.js +3 -1
  74. package/build/lib/nodeModulesManagement.js.map +1 -1
  75. package/build/lib/protectFs.js.map +1 -1
  76. package/build/lib/sandbox.js +84 -31
  77. package/build/lib/sandbox.js.map +1 -1
  78. package/build/lib/scheduler.js.map +1 -1
  79. package/build/lib/tools.js +1 -1
  80. package/build/lib/tools.js.map +1 -1
  81. package/build/lib/typescriptSettings.js +16 -10
  82. package/build/lib/typescriptSettings.js.map +1 -1
  83. package/build/lib/typescriptTools.js +2 -2
  84. package/build/lib/typescriptTools.js.map +1 -1
  85. package/build/main.js +7 -4
  86. package/build/main.js.map +1 -1
  87. package/build/src/lib/aiProviderResolver.js +107 -0
  88. package/build/src/lib/aiProviderResolver.js.map +1 -0
  89. package/build/src/lib/anthropicAdapter.js +175 -0
  90. package/build/src/lib/anthropicAdapter.js.map +1 -0
  91. package/build/src/lib/consts.js +346 -0
  92. package/build/src/lib/consts.js.map +1 -0
  93. package/build/src/lib/convert.js +121 -0
  94. package/build/src/lib/convert.js.map +1 -0
  95. package/build/src/lib/crypto.js +32 -0
  96. package/build/src/lib/crypto.js.map +1 -0
  97. package/build/src/lib/debug.js +163 -0
  98. package/build/src/lib/debug.js.map +1 -0
  99. package/build/src/lib/debugger.js +1135 -0
  100. package/build/src/lib/debugger.js.map +1 -0
  101. package/build/src/lib/eventObj.js +194 -0
  102. package/build/src/lib/eventObj.js.map +1 -0
  103. package/build/src/lib/inspect.js +728 -0
  104. package/build/src/lib/inspect.js.map +1 -0
  105. package/build/src/lib/mirror.js +634 -0
  106. package/build/src/lib/mirror.js.map +1 -0
  107. package/build/src/lib/nodeModulesManagement.js +26 -0
  108. package/build/src/lib/nodeModulesManagement.js.map +1 -0
  109. package/build/src/lib/patternCompareFunctions.js +293 -0
  110. package/build/src/lib/patternCompareFunctions.js.map +1 -0
  111. package/build/src/lib/protectFs.js +554 -0
  112. package/build/src/lib/protectFs.js.map +1 -0
  113. package/build/src/lib/sandbox.js +4897 -0
  114. package/build/src/lib/sandbox.js.map +1 -0
  115. package/build/src/lib/scheduler.js +555 -0
  116. package/build/src/lib/scheduler.js.map +1 -0
  117. package/build/src/lib/tools.js +188 -0
  118. package/build/src/lib/tools.js.map +1 -0
  119. package/build/src/lib/typescriptSettings.js +39 -0
  120. package/build/src/lib/typescriptSettings.js.map +1 -0
  121. package/build/src/lib/typescriptTools.js +435 -0
  122. package/build/src/lib/typescriptTools.js.map +1 -0
  123. package/build/src/lib/utils.js +66 -0
  124. package/build/src/lib/utils.js.map +1 -0
  125. package/build/src/lib/words.js +76 -0
  126. package/build/src/lib/words.js.map +1 -0
  127. package/build/src/main.js +3414 -0
  128. package/build/src/main.js.map +1 -0
  129. package/build/types.d.ts +2 -10
  130. package/io-package.json +15 -15
  131. package/package.json +15 -16
  132. package/admin/assets/AiChatPanel-C3bIw_D5.js +0 -317
  133. package/admin/assets/AiDatapointProvider-B0i3I5Fm.js +0 -2
  134. package/admin/assets/AiDiffView-RPozjjHf.js +0 -1
  135. package/admin/assets/AiInlineProvider-DkgjVSFQ.js +0 -7
  136. package/admin/assets/Save-CkuNSiWL.js +0 -1
  137. package/admin/assets/ScriptEditor-8UU88tT5.js +0 -2
  138. package/admin/assets/ScriptEditorVanillaMonaco-Bn8xJ3LV.js +0 -3
  139. package/admin/assets/applyCodeEdit-BdRnbz2g.js +0 -1
  140. package/admin/assets/createSvgIcon-DYs_tNrE.js +0 -8
  141. package/admin/assets/cronHoverProvider-Bg0Vz-1E.js +0 -2
  142. package/admin/assets/hostInit-LzITLWDa.js +0 -1
  143. package/admin/assets/index-45IwpC7_.js +0 -1
  144. package/admin/assets/index-BCK6q-N6.css +0 -1
  145. package/admin/assets/index-BTsjunTt.css +0 -1
  146. package/admin/assets/index-BkyLyaYT.js +0 -8
  147. package/admin/assets/index-C0ZnUCQf.js +0 -990
  148. package/admin/assets/index-CO9RA-Wq.js +0 -1267
  149. package/admin/assets/index-CRGWWpGb.js +0 -1
  150. package/admin/assets/index-CVZSzCXy.js +0 -1
  151. package/admin/assets/index-CsyuIWlD.js +0 -16
  152. package/admin/assets/index-Dsh1HfIY.js +0 -1
  153. package/admin/assets/index-dN73sbyQ.js +0 -193
  154. package/admin/assets/index-wHNMOf-c.js +0 -5
  155. package/admin/assets/inheritsLoose-CIXm_zPk.js +0 -29
  156. package/admin/assets/inlineChatWidget-Cu6rb4Ar.js +0 -2
  157. package/admin/assets/inlineDiffController-DBLpPOe2.js +0 -16
  158. package/admin/assets/iobroker_javascript__loadShare___mf_0_iobroker_mf_1_adapter_mf_2_react_mf_2_v5__loadShare__.mjs-DkBjPSMw.js +0 -1
  159. package/admin/assets/iobroker_javascript__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-CV1_X-Lf.js +0 -1
  160. package/admin/assets/iobroker_javascript__loadShare__prop_mf_2_types__loadShare__.mjs-BCtSFofY.js +0 -1
  161. package/admin/assets/iobroker_javascript__loadShare__react__loadShare__.mjs-CTZOFJS2.js +0 -1
  162. package/admin/assets/iobroker_javascript__loadShare__react__loadShare__.mjs_commonjs-proxy-CE9lvhW8.js +0 -1
  163. package/admin/assets/iobroker_javascript__loadShare__react_mf_2_dom__loadShare__.mjs-DeVAS-oa.js +0 -1
  164. package/admin/assets/iobroker_javascript__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-RQRRZMX5.js +0 -1
  165. package/admin/assets/localSharedImportMap-Db5DxDmY.js +0 -1
  166. package/admin/assets/preload-helper-BDBacUwf.js +0 -1
  167. package/admin/assets/stateHoverProvider-C82xsI7Q.js +0 -9
  168. package/admin/assets/useTheme-DuTJsNQ-.js +0 -1
  169. package/admin/assets/virtualExposes-BPsCufin.js +0 -1
  170. package/admin/assets/virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-Bw22wl4W.js +0 -8
  171. package/admin/custom/assets/Components-BOms95_F.css +0 -1
  172. package/admin/custom/assets/Components-CGiJE5Zh.js +0 -1
  173. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_react__loadShare__.mjs-sg07E3QA.js +0 -1
  174. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_styled__loadShare__.mjs-CwjI4A-J.js +0 -1
  175. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_iobroker_mf_1_adapter_mf_2_react_mf_2_v5__loadShare__.mjs-B7D2xOvV.js +0 -1
  176. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_iobroker_mf_1_json_mf_2_config__loadShare__.mjs-ClsgYnEX.js +0 -1
  177. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_mui_mf_1_icons_mf_2_material__loadShare__.mjs-B551AMx-.js +0 -1
  178. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_mui_mf_1_material__loadShare__.mjs-KsT5vKjV.js +0 -1
  179. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_mui_mf_1_x_mf_2_date_mf_2_pickers__loadShare__.mjs-DgHZUNxn.js +0 -1
  180. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare__leaflet__loadShare__.mjs-BYONkEgA.js +0 -1
  181. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs-BW9Qbxx5.js +0 -1
  182. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs_commonjs-proxy-2W2BYT2K.js +0 -1
  183. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs-CL--xmjC.js +0 -1
  184. package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-D8zcZsJo.js +0 -1
  185. package/admin/custom/assets/DefaultPropsProvider-CfKHPa-G.js +0 -1
  186. package/admin/custom/assets/TextField-CxPfV5Sr.js +0 -139
  187. package/admin/custom/assets/ThemeProvider-DSTCckLJ.js +0 -29
  188. package/admin/custom/assets/blue-CagzI6CQ.js +0 -1
  189. package/admin/custom/assets/createSvgIcon-b2B2NI3g.js +0 -1
  190. package/admin/custom/assets/createTheme-MsdB8zt7.js +0 -8
  191. package/admin/custom/assets/defaultTheme-CmVP-QNF.js +0 -1
  192. package/admin/custom/assets/emotion-cache.browser.esm-BgJfNZJN.js +0 -1
  193. package/admin/custom/assets/emotion-react.browser.esm-chZAeh_-.js +0 -1
  194. package/admin/custom/assets/emotion-serialize.esm-BUa21YfQ.js +0 -1
  195. package/admin/custom/assets/emotion-styled.browser.esm-Yi13plpH.js +0 -1
  196. package/admin/custom/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-CEPeBbh9.js +0 -1
  197. package/admin/custom/assets/extends-CF3RwP-h.js +0 -1
  198. package/admin/custom/assets/hostInit-iUL-wyj1.js +0 -1
  199. package/admin/custom/assets/index-17vbGv00.js +0 -1
  200. package/admin/custom/assets/index-B5DqCD_a.js +0 -1
  201. package/admin/custom/assets/index-Bvwv5H7k.js +0 -1
  202. package/admin/custom/assets/index-CMsuyEZj.js +0 -964
  203. package/admin/custom/assets/index-CuAhFQ4L.js +0 -1523
  204. package/admin/custom/assets/index-CzRngQHl.js +0 -31
  205. package/admin/custom/assets/index-DLNtxshy.js +0 -1
  206. package/admin/custom/assets/index-DV76KW3-.js +0 -7
  207. package/admin/custom/assets/index-D_5kZmfw.js +0 -1
  208. package/admin/custom/assets/index-DlQbqHom.js +0 -8
  209. package/admin/custom/assets/index-DpJ18x6R.js +0 -1
  210. package/admin/custom/assets/index-FTmk0JJx.js +0 -55
  211. package/admin/custom/assets/index-mkhHeJ3Q.js +0 -1
  212. package/admin/custom/assets/isSameWeek-DDsSIa_t.js +0 -1
  213. package/admin/custom/assets/jsx-runtime-BplTyRFk.js +0 -1
  214. package/admin/custom/assets/leaflet-src-B472P7Y1.js +0 -1
  215. package/admin/custom/assets/localSharedImportMap-BsMINYMr.js +0 -1
  216. package/admin/custom/assets/preload-helper-BDBacUwf.js +0 -1
  217. package/admin/custom/assets/useTheme-BV4xOYIL.js +0 -1
  218. package/admin/custom/assets/useTheme-ldICnlmf.js +0 -1
  219. package/admin/custom/assets/virtualExposes-D402kVGY.js +0 -2
  220. package/admin/custom/assets/virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-BzLNCazi.js +0 -8
@@ -0,0 +1,3414 @@
1
+ "use strict";
2
+ /*
3
+ * Javascript adapter
4
+ *
5
+ * The MIT License (MIT)
6
+ *
7
+ * Copyright (c) 2014-2024 bluefox <dogafox@gmail.com>,
8
+ *
9
+ * Copyright (c) 2014 hobbyquaker
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ const node_vm_1 = require("node:vm");
49
+ const node_fs_1 = require("node:fs");
50
+ const node_path_1 = require("node:path");
51
+ const node_child_process_1 = require("node:child_process");
52
+ const virtual_tsc_1 = require("virtual-tsc");
53
+ const node_util_1 = require("node:util");
54
+ const prettier_1 = __importDefault(require("prettier"));
55
+ const dgram = __importStar(require("node:dgram"));
56
+ const crypto = __importStar(require("node:crypto"));
57
+ const dns = __importStar(require("node:dns"));
58
+ const events = __importStar(require("node:events"));
59
+ const http = __importStar(require("node:http"));
60
+ const https = __importStar(require("node:https"));
61
+ const http2 = __importStar(require("node:http2"));
62
+ const net = __importStar(require("node:net"));
63
+ const os = __importStar(require("node:os"));
64
+ const path = __importStar(require("node:path"));
65
+ const util = __importStar(require("node:util"));
66
+ const child_process = __importStar(require("node:child_process"));
67
+ const stream = __importStar(require("node:stream"));
68
+ const zlib = __importStar(require("node:zlib"));
69
+ // @ts-expect-error no types available
70
+ const suncalc = __importStar(require("suncalc2"));
71
+ const axios = __importStar(require("axios"));
72
+ // @ts-expect-error no types available
73
+ const wake_on_lan = __importStar(require("wake_on_lan"));
74
+ const nodeSchedule = __importStar(require("node-schedule"));
75
+ const adapter_core_1 = require("@iobroker/adapter-core");
76
+ const mirror_1 = require("./lib/mirror");
77
+ const protectFs_1 = __importDefault(require("./lib/protectFs"));
78
+ const words_1 = require("./lib/words");
79
+ const sandbox_1 = require("./lib/sandbox");
80
+ const nodeModulesManagement_1 = require("./lib/nodeModulesManagement");
81
+ const aiProviderResolver_1 = require("./lib/aiProviderResolver");
82
+ const anthropicAdapter_1 = require("./lib/anthropicAdapter");
83
+ const eventObj_1 = require("./lib/eventObj");
84
+ const scheduler_1 = require("./lib/scheduler");
85
+ const typescriptSettings_1 = require("./lib/typescriptSettings");
86
+ const tools_1 = require("./lib/tools");
87
+ const typescriptTools_1 = require("./lib/typescriptTools");
88
+ const crypto_1 = require("./lib/crypto");
89
+ /**
90
+ * List of forbidden Locations for a mirror directory
91
+ * relative to the default data directory
92
+ * ATTENTION: the same list is also located in index_m.html!!
93
+ */
94
+ const forbiddenMirrorLocations = [
95
+ 'backup-objects',
96
+ 'files',
97
+ 'backitup',
98
+ '../backups',
99
+ '../node_modules',
100
+ '../log',
101
+ ];
102
+ const packageJson = JSON.parse((0, node_fs_1.readFileSync)(`${__dirname}/../package.json`).toString());
103
+ const SCRIPT_CODE_MARKER = 'script.js.';
104
+ let webstormDebug;
105
+ const isCI = !!process.env.CI;
106
+ // ambient declarations for typescript
107
+ let tsAmbient;
108
+ // TypeScript's scripts are only recompiled if their source hash changes.
109
+ // If an adapter update fixes the compilation bugs, a user won't notice until the changes and re-save the script.
110
+ // To avoid that, we also include the
111
+ // adapter version and TypeScript version in the hash
112
+ const tsSourceHashBase = `versions:adapter=${packageJson.version},typescript=${packageJson.dependencies.typescript}`;
113
+ // taken from here: https://stackoverflow.com/questions/11887934/how-to-check-if-dst-daylight-saving-time-is-in-effect-and-if-so-the-offset
114
+ function dstOffsetAtDate(dateInput) {
115
+ const fullYear = dateInput.getFullYear() | 0;
116
+ // "Leap Years are any year that can be exactly divided by 4 (2012, 2016, etc.)
117
+ // except if it can be exactly divided by 100, then it isn't (2100, 2200, etc.)
118
+ // except if it can be exactly divided by 400, then it is (2000, 2400)"
119
+ // (https://www.mathsisfun.com/leap-years.html).
120
+ const isLeapYear = ((fullYear & 3) | ((fullYear / 100) & 3)) === 0 ? 1 : 0;
121
+ // (fullYear & 3) = (fullYear % 4), but faster
122
+ //Alternative:var isLeapYear=(new Date(currentYear,1,29,12)).getDate()===29?1:0
123
+ const fullMonth = dateInput.getMonth() | 0;
124
+ return (
125
+ // 1. We know what the time since the Epoch really is
126
+ +dateInput - // same as the dateInput.getTime() method
127
+ // 2. We know what the time since the Epoch at the start of the year is
128
+ +new Date(fullYear, 0) - // day defaults to 1 if not explicitly zeroed
129
+ // 3. Now, subtract what we would expect the time to be if daylight savings
130
+ // did not exist. This yields the time-offset due to daylight savings.
131
+ // Calculate the day of the year in the Gregorian calendar
132
+ // The code below works based upon the facts of signed right shifts
133
+ // • (x) >> n: shifts n and fills in the n highest bits with 0s
134
+ // • (-x) >> n: shifts n and fills in the n highest bits with 1s
135
+ // (This assumes that x is a positive integer)
136
+ ((((-1 + // the first day in the year is day 1
137
+ (31 & (-fullMonth >> 4)) + // January // (-11)>>4 = -1
138
+ ((28 + isLeapYear) & ((1 - fullMonth) >> 4)) + // February
139
+ (31 & ((2 - fullMonth) >> 4)) + // March
140
+ (30 & ((3 - fullMonth) >> 4)) + // April
141
+ (31 & ((4 - fullMonth) >> 4)) + // May
142
+ (30 & ((5 - fullMonth) >> 4)) + // June
143
+ (31 & ((6 - fullMonth) >> 4)) + // July
144
+ (31 & ((7 - fullMonth) >> 4)) + // August
145
+ (30 & ((8 - fullMonth) >> 4)) + // September
146
+ (31 & ((9 - fullMonth) >> 4)) + // October
147
+ (30 & ((10 - fullMonth) >> 4)) + // November
148
+ // There are no months past December: the year rolls into the next.
149
+ // Thus, "fullMonth" is 0-based, so it will never be 12 in JavaScript
150
+ (dateInput.getDate() | 0)) & // get day of the month
151
+ 0xffff) *
152
+ 24 *
153
+ 60 + // 24 hours in a day, 60 minutes in an hour
154
+ (dateInput.getHours() & 0xff) * 60 + // 60 minutes in an hour
155
+ (dateInput.getMinutes() & 0xff)) |
156
+ 0) *
157
+ 60 *
158
+ 1000 - // 60 seconds in a minute * 1000 milliseconds in a second
159
+ (dateInput.getSeconds() & 0xff) * 1000 - // 1000 milliseconds in a second
160
+ dateInput.getMilliseconds());
161
+ }
162
+ const regExGlobalOld = /_global$/;
163
+ const regExGlobalNew = /script\.js\.global\./;
164
+ function checkIsGlobal(obj) {
165
+ return obj?.common && (regExGlobalOld.test(obj.common.name) || regExGlobalNew.test(obj._id));
166
+ }
167
+ function fileMatching(sub, id, fileName) {
168
+ if (sub.idRegEx) {
169
+ if (!sub.idRegEx.test(id)) {
170
+ return false;
171
+ }
172
+ }
173
+ else {
174
+ if (sub.id !== id) {
175
+ return false;
176
+ }
177
+ }
178
+ if (sub.fileRegEx) {
179
+ if (!sub.fileRegEx.test(fileName)) {
180
+ return false;
181
+ }
182
+ }
183
+ else {
184
+ if (sub.fileNamePattern !== fileName) {
185
+ return false;
186
+ }
187
+ }
188
+ return true;
189
+ }
190
+ function getNextTimeEvent(time, useNextDay) {
191
+ const now = getAstroStartOfDay();
192
+ const [timeHours, timeMinutes] = time.split(':');
193
+ const nTimeHours = parseInt(timeHours, 10);
194
+ const nTimeMinutes = parseInt(timeMinutes, 10);
195
+ if (useNextDay &&
196
+ (now.getHours() > nTimeHours || (now.getHours() === nTimeHours && now.getMinutes() > nTimeMinutes))) {
197
+ now.setDate(now.getDate() + 1);
198
+ }
199
+ now.setHours(nTimeHours);
200
+ now.setMinutes(nTimeMinutes);
201
+ return now;
202
+ }
203
+ function getAstroStartOfDay() {
204
+ const d = new Date();
205
+ d.setMinutes(0);
206
+ d.setSeconds(0);
207
+ d.setMilliseconds(0);
208
+ d.setTime(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
209
+ d.setUTCHours(0);
210
+ return d;
211
+ }
212
+ function formatHoursMinutesSeconds(date) {
213
+ const h = String(date.getHours());
214
+ const m = String(date.getMinutes());
215
+ const s = String(date.getSeconds());
216
+ return `${h.padStart(2, '0')}:${m.padStart(2, '0')}:${s.padStart(2, '0')}`;
217
+ }
218
+ // Due to a npm bug, virtual-tsc may be hoisted to the top level node_modules, but
219
+ // TypeScript may still be in the adapter level (https://npm.community/t/packages-with-peerdependencies-are-incorrectly-hoisted/4794),
220
+ // so we need to tell virtual-tsc where TypeScript is
221
+ (0, virtual_tsc_1.setTypeScriptResolveOptions)({
222
+ paths: [require.resolve('typescript')],
223
+ });
224
+ // compiler instance for global JS declarations
225
+ const jsDeclarationServer = new virtual_tsc_1.Server(typescriptSettings_1.jsDeclarationCompilerOptions, isCI ? false : undefined);
226
+ /**
227
+ * Stores the IDs of script objects whose change should be ignored because
228
+ * the compiled source was just updated
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
+ };
240
+ function httpStatusText(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
+ }
256
+ }
257
+ class JavaScript extends adapter_core_1.Adapter {
258
+ context;
259
+ errorLogFunction = {
260
+ error: (msg) => console.error(msg),
261
+ warn: (msg) => console.warn(msg),
262
+ info: (msg) => console.log(msg),
263
+ debug: (msg) => console.debug(msg),
264
+ silly: (msg) => console.debug(msg),
265
+ };
266
+ mods;
267
+ objectsInitDone = false;
268
+ statesInitDone = false;
269
+ objects = {};
270
+ states = {};
271
+ interimStateValues = {};
272
+ stateIds = [];
273
+ /** Fast O(1) lookup set – always kept in sync with stateIds */
274
+ stateIdSet = new Set();
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 = [];
286
+ subscriptionsFile = [];
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();
292
+ subscribedPatterns = {};
293
+ subscribedPatternsFile = {};
294
+ adapterSubs = {};
295
+ timers = {};
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();
300
+ names = {}; // name: id
301
+ /** Reverse map: id → name for O(1) getName() lookups */
302
+ nameById = new Map();
303
+ scripts = {};
304
+ password = '';
305
+ messageBusHandlers = {};
306
+ logSubscriptions = {};
307
+ tempDirectories = {}; // name: path
308
+ folderCreationVerifiedObjects = {};
309
+ /** if logs are subscribed or not */
310
+ logSubscribed = false;
311
+ timeSettings = { format12: false, leadingZeros: true };
312
+ dayScheduleTimer = null; // schedule for astrological day
313
+ sunScheduleTimer = null; // schedule for sun moment times
314
+ timeScheduleTimer = null; // schedule for astrological day
315
+ activeStr = ''; // enabled state prefix
316
+ mirror;
317
+ stopCounters = {};
318
+ setStateCountCheckInterval = null;
319
+ /**
320
+ * Decrypted AI API keys cached from the central credential store (manager mode),
321
+ * keyed by credential ID (e.g. `system.credentials.anthropic`). Kept fresh by the
322
+ * subscriptions set up in `subscribeAiCredentials`.
323
+ */
324
+ aiCredentialCache = new Map();
325
+ /** Unsubscribe callbacks for the AI credential subscriptions (manager mode). */
326
+ aiCredentialUnsubscribers = [];
327
+ globalScript = '';
328
+ /** Generated declarations for global TypeScripts */
329
+ globalDeclarations = '';
330
+ // Remember which definitions the global scripts
331
+ // have access to, because it depends on the compilation order
332
+ knownGlobalDeclarationsByScript = {};
333
+ globalScriptLines = 0;
334
+ /** Running counter to build unique names for ad-hoc scripts started via the "execute" message */
335
+ executeCounter = 0;
336
+ // compiler instance for typescript
337
+ tsServer;
338
+ logCollectors = [];
339
+ ignoreObjectChange = new Set();
340
+ debugState = {
341
+ scriptName: '',
342
+ child: null,
343
+ promiseOnEnd: null,
344
+ paused: false,
345
+ started: 0,
346
+ running: false,
347
+ };
348
+ constructor(options = {}) {
349
+ options = {
350
+ ...options,
351
+ name: 'javascript', // adapter name
352
+ useFormatDate: true,
353
+ /**
354
+ * If the JS-Controller catches an unhandled error, this will be called,
355
+ * so we have a chance to handle it ourselves.
356
+ */
357
+ error: (err) => {
358
+ // Identify unhandled errors originating from callbacks in scripts
359
+ // These are not caught by wrapping the execution code in try-catch
360
+ if (err && typeof err.stack === 'string') {
361
+ const scriptCodeMarkerIndex = err.stack.indexOf(SCRIPT_CODE_MARKER);
362
+ if (scriptCodeMarkerIndex > -1) {
363
+ // This is a script error
364
+ let scriptName = err.stack.substring(scriptCodeMarkerIndex);
365
+ scriptName = scriptName.substring(0, scriptName.indexOf(':'));
366
+ this.logError(scriptName, 'Error:', err);
367
+ // Leave the script running for now
368
+ // signal to the JS-Controller that we handled the error ourselves
369
+ return true;
370
+ }
371
+ // check if a path contains adaptername but not own node_module
372
+ // this regex matched "iobroker.javascript/" if NOT followed by "node_modules"
373
+ if (!err.stack.match(/iobroker\.javascript[/\\](?!.*node_modules).*/g)) {
374
+ // This is an error without any info on origin (mostly async errors like connection errors)
375
+ // also consider it as being from a script
376
+ this.log.error('An error happened which is most likely from one of your scripts, but the originating script could not be detected.');
377
+ this.log.error(`Error: ${err.message}`);
378
+ this.log.error(err.stack);
379
+ // signal to the JS-Controller that we handled the error ourselves
380
+ return true;
381
+ }
382
+ }
383
+ return false;
384
+ },
385
+ };
386
+ super(options);
387
+ this.on('objectChange', this.onObjectChange.bind(this));
388
+ this.on('stateChange', this.onStateChange.bind(this));
389
+ this.on('ready', this.onReady.bind(this));
390
+ this.on('message', this.onMessage.bind(this));
391
+ this.on('unload', this.onUnload.bind(this));
392
+ this.on('fileChange', this.onFileChange.bind(this));
393
+ this.on('log', this.onLog.bind(this));
394
+ this.mods = {
395
+ fs: {},
396
+ 'fs/promises': {},
397
+ dgram,
398
+ crypto,
399
+ dns,
400
+ events,
401
+ http,
402
+ https,
403
+ http2,
404
+ net,
405
+ os,
406
+ path,
407
+ util,
408
+ child_process,
409
+ stream,
410
+ zlib,
411
+ suncalc,
412
+ axios,
413
+ wake_on_lan,
414
+ nodeSchedule,
415
+ };
416
+ // check the webstorm debug and just debug modes
417
+ let debugMode;
418
+ if (process.argv) {
419
+ for (let a = 1; a < process.argv.length; a++) {
420
+ if (process.argv[a].startsWith('--webstorm')) {
421
+ webstormDebug = process.argv[a].replace(/^(.*?=\s*)/, '');
422
+ }
423
+ if (process.argv[a] === '--debugScript') {
424
+ if (!process.argv[a + 1]) {
425
+ console.log('No script name provided');
426
+ process.exit(300);
427
+ }
428
+ else {
429
+ debugMode = process.argv[a + 1];
430
+ }
431
+ }
432
+ }
433
+ }
434
+ this.context = {
435
+ mods: this.mods,
436
+ objects: this.objects,
437
+ states: this.states,
438
+ interimStateValues: this.interimStateValues,
439
+ stateIds: this.stateIds,
440
+ errorLogFunction: this.errorLogFunction,
441
+ subscriptions: this.subscriptions,
442
+ subscriptionsMap: this.subscriptionsMap,
443
+ subscriptionsWildcard: this.subscriptionsWildcard,
444
+ subscriptionsFile: this.subscriptionsFile,
445
+ subscriptionsObject: this.subscriptionsObject,
446
+ subscriptionsObjectMap: this.subscriptionsObjectMap,
447
+ sendToInstanceCache: this.sendToInstanceCache,
448
+ subscribedPatterns: this.subscribedPatterns,
449
+ subscribedPatternsFile: this.subscribedPatternsFile,
450
+ adapterSubs: this.adapterSubs,
451
+ cacheObjectEnums: {},
452
+ timers: this.timers,
453
+ timersByScript: this.timersByScript,
454
+ enums: this._enums,
455
+ names: this.names,
456
+ scripts: this.scripts,
457
+ messageBusHandlers: this.messageBusHandlers,
458
+ logSubscriptions: this.logSubscriptions,
459
+ tempDirectories: this.tempDirectories,
460
+ folderCreationVerifiedObjects: this.folderCreationVerifiedObjects,
461
+ isEnums: false, // If some subscription wants enum
462
+ channels: null,
463
+ devices: null,
464
+ logWithLineInfo: this.logWithLineInfo.bind(this),
465
+ scheduler: null,
466
+ timerId: 0,
467
+ rulesOpened: null, // opened rules
468
+ language: this.language || 'en',
469
+ updateLogSubscriptions: this.updateLogSubscriptions.bind(this),
470
+ convertBackStringifiedValues: this.convertBackStringifiedValues.bind(this),
471
+ updateObjectContext: this.updateObjectContext.bind(this),
472
+ prepareStateObject: this.prepareStateObject.bind(this),
473
+ debugMode,
474
+ getAbsoluteDefaultDataDir: adapter_core_1.getAbsoluteDefaultDataDir,
475
+ adapter: this,
476
+ logError: this.logError.bind(this),
477
+ allowSelfSignedCerts: false,
478
+ };
479
+ this.tsServer = new virtual_tsc_1.Server(typescriptSettings_1.tsCompilerOptions, this.tsLog);
480
+ }
481
+ async onObjectChange(id, obj) {
482
+ // Check if we should ignore this change (once!) because we just updated the compiled sources
483
+ if (this.ignoreObjectChange.has(id)) {
484
+ // Update the cached script object and do nothing more
485
+ this.objects[id] = obj;
486
+ this.ignoreObjectChange.delete(id);
487
+ return;
488
+ }
489
+ // When still in initializing: already remember current values,
490
+ // but data structures are initialized elsewhere
491
+ if (!this.objectsInitDone) {
492
+ if (obj) {
493
+ this.objects[id] = obj;
494
+ }
495
+ return;
496
+ }
497
+ if (id.startsWith('enum.')) {
498
+ // clear cache
499
+ this.context.cacheObjectEnums = {};
500
+ // update this._enums Set
501
+ if (obj) {
502
+ this._enums.add(id);
503
+ }
504
+ else {
505
+ this._enums.delete(id);
506
+ }
507
+ }
508
+ // IO-9: Invalidate sendTo instance-cache when adapter instances change
509
+ if (id.startsWith('system.adapter.')) {
510
+ const parts = id.split('.');
511
+ if (parts.length >= 3) {
512
+ const adapterName = parts[2]; // e.g. "zigbee" from "system.adapter.zigbee.0"
513
+ this.sendToInstanceCache.delete(adapterName);
514
+ }
515
+ }
516
+ if (id === 'system.config' && obj?.common?.language) {
517
+ // set language for debug messages
518
+ (0, words_1.setLanguage)(obj.common.language);
519
+ this.language = obj.common.language;
520
+ this.context.language = this.language;
521
+ }
522
+ // update stored time format for variables.dayTime
523
+ if (id === `${this.namespace}.variables.dayTime` && obj?.native) {
524
+ this.timeSettings.format12 = obj.native.format12 || false;
525
+ this.timeSettings.leadingZeros = obj.native.leadingZeros === undefined ? true : obj.native.leadingZeros;
526
+ }
527
+ // send changes to the disk mirror
528
+ this.mirror?.onObjectChange(id, obj);
529
+ const formerObj = this.objects[id];
530
+ this.updateObjectContext(id, obj); // Update all Meta object data
531
+ // for the alias object changes on the state objects, we need to manually update the
532
+ // state cache value, because the new value is only published on the next change
533
+ if (obj?.type === 'state' && id.startsWith('alias.0.')) {
534
+ // execute async for speed
535
+ this.getForeignStateAsync(id)
536
+ .then(state => {
537
+ if (state) {
538
+ this.states[id] = state;
539
+ }
540
+ else if (this.states[id] !== undefined) {
541
+ delete this.states[id];
542
+ }
543
+ })
544
+ .catch(() => {
545
+ /* ignore */
546
+ });
547
+ }
548
+ // O(1) dispatch via pattern map instead of O(n) forEach
549
+ const objSubs = this.subscriptionsObjectMap.get(id);
550
+ if (objSubs) {
551
+ for (const sub of objSubs) {
552
+ try {
553
+ sub.callback(id, obj);
554
+ }
555
+ catch (err) {
556
+ this.log.error(`Error in callback: ${err.toString()}`);
557
+ }
558
+ }
559
+ }
560
+ // handle Script object updates
561
+ if (!obj && formerObj?.type === 'script') {
562
+ // Object Deleted just now
563
+ if (checkIsGlobal(formerObj)) {
564
+ // it was a global Script, and it was enabled and is now deleted => restart adapter
565
+ if (formerObj.common.enabled) {
566
+ this.log.info(`Active global Script ${id} deleted. Restart instance.`);
567
+ this.restart();
568
+ }
569
+ }
570
+ else if (formerObj.common?.engine === `system.adapter.${this.namespace}`) {
571
+ // It was a non-global Script and deleted => stop and remove it
572
+ await this.stopScript(id);
573
+ // delete scriptEnabled.blabla variable
574
+ const idActive = `scriptEnabled.${id.substring(SCRIPT_CODE_MARKER.length)}`;
575
+ await this.delStateAsync(idActive);
576
+ await this.delObjectAsync(idActive);
577
+ // delete scriptProblem.blabla variable
578
+ const idProblem = `scriptProblem.${id.substring(SCRIPT_CODE_MARKER.length)}`;
579
+ await this.delStateAsync(idProblem);
580
+ await this.delObjectAsync(idProblem);
581
+ }
582
+ }
583
+ else if (!formerObj && obj?.type === 'script') {
584
+ // New script that does not exist before
585
+ if (checkIsGlobal(obj)) {
586
+ // new global script added => restart adapter
587
+ if (obj.common.enabled) {
588
+ this.log.info(`Active global Script ${id} created. Restart instance.`);
589
+ this.restart();
590
+ }
591
+ }
592
+ else if (obj.common?.engine === `system.adapter.${this.namespace}`) {
593
+ // new non-global script - create states for scripts
594
+ await this.createActiveObject(id, !!obj.common.enabled);
595
+ await this.createProblemObject(id);
596
+ if (obj.common.enabled) {
597
+ // if enabled => Start a script
598
+ await this.loadScriptById(id);
599
+ }
600
+ }
601
+ }
602
+ else if (obj?.type === 'script' && formerObj?.common) {
603
+ // Script changed ...
604
+ if (checkIsGlobal(obj)) {
605
+ if (obj.common.enabled || formerObj.common.enabled) {
606
+ this.log.info(`Global Script ${id} updated. Restart instance.`);
607
+ this.restart();
608
+ }
609
+ }
610
+ else {
611
+ // No global script
612
+ if (obj.common?.engine === `system.adapter.${this.namespace}`) {
613
+ // create states for scripts
614
+ await this.createActiveObject(id, !!obj.common.enabled);
615
+ await this.createProblemObject(id);
616
+ }
617
+ if ((formerObj.common.enabled && !obj.common.enabled) ||
618
+ (formerObj.common.engine === `system.adapter.${this.namespace}` &&
619
+ obj.common.engine !== `system.adapter.${this.namespace}`)) {
620
+ // Script disabled
621
+ if (formerObj.common.enabled && formerObj.common.engine === `system.adapter.${this.namespace}`) {
622
+ // Remove it from executing
623
+ await this.stopScript(id);
624
+ }
625
+ }
626
+ else if ((!formerObj.common.enabled && obj.common.enabled) ||
627
+ (formerObj.common.engine !== `system.adapter.${this.namespace}` &&
628
+ obj.common.engine === `system.adapter.${this.namespace}`)) {
629
+ // Script enabled
630
+ if (obj.common.enabled && obj.common.engine === `system.adapter.${this.namespace}`) {
631
+ // Start script
632
+ await this.loadScriptById(id);
633
+ }
634
+ }
635
+ else if (obj.common.engine === `system.adapter.${this.namespace}` ||
636
+ formerObj.common.engine === `system.adapter.${this.namespace}`) {
637
+ // Source changed => restart the script (only on the relevant instance)
638
+ this.stopCounters[id] = this.stopCounters[id] ? this.stopCounters[id] + 1 : 1;
639
+ void this.stopScript(id).then(() => {
640
+ // only start again after stop when "last" object change to prevent problems on
641
+ // multiple changes in fast frequency
642
+ if (!--this.stopCounters[id]) {
643
+ void this.loadScriptById(id);
644
+ }
645
+ });
646
+ }
647
+ }
648
+ }
649
+ }
650
+ onStateChange(id, state) {
651
+ if (this.interimStateValues[id] !== undefined) {
652
+ // any update invalidates the remembered interim value
653
+ delete this.interimStateValues[id];
654
+ }
655
+ if (!id || id.startsWith('messagebox.') || id.startsWith('log.')) {
656
+ return;
657
+ }
658
+ if (id === `${this.namespace}.debug.to` && state && !state.ack) {
659
+ if (!this.context.debugMode) {
660
+ this.debugSendToInspector(state.val);
661
+ }
662
+ return;
663
+ }
664
+ // When still in initializing: already remember current values,
665
+ // but data structures are initialized elsewhere
666
+ if (!this.statesInitDone) {
667
+ if (state) {
668
+ this.states[id] = state;
669
+ }
670
+ return;
671
+ }
672
+ const oldState = this.states[id];
673
+ if (state) {
674
+ if (oldState) {
675
+ // enable or disable script
676
+ if (!state.ack && id.startsWith(this.activeStr) && this.objects[id]?.native?.script) {
677
+ void this.extendForeignObject(this.objects[id].native.script, {
678
+ common: { enabled: state.val },
679
+ });
680
+ }
681
+ // monitor if the adapter is alive and send all subscriptions once more, after the adapter goes online
682
+ if ( /*oldState && */oldState.val === false && state.val && id.endsWith('.alive')) {
683
+ if (this.adapterSubs[id]) {
684
+ const parts = id.split('.');
685
+ const a = `${parts[2]}.${parts[3]}`;
686
+ for (const sub of this.adapterSubs[id]) {
687
+ this.log.info(`Detected coming adapter "${a}". Send subscribe: ${sub}`);
688
+ this.sendTo(a, 'subscribe', sub);
689
+ }
690
+ }
691
+ }
692
+ }
693
+ else if ( /*!oldState && */!this.stateIdSet.has(id)) {
694
+ this.sortedInsert(id);
695
+ this.stateIdSet.add(id);
696
+ }
697
+ this.states[id] = state;
698
+ }
699
+ else {
700
+ if (oldState) {
701
+ delete this.states[id];
702
+ }
703
+ state = {};
704
+ const pos = this.binaryIndexOf(this.stateIds, id);
705
+ if (pos !== -1) {
706
+ this.stateIds.splice(pos, 1);
707
+ this.stateIdSet.delete(id);
708
+ }
709
+ }
710
+ // Collect matching subscriptions:
711
+ // 1. O(1) exact-id map lookup – only buckets for this specific state id
712
+ // 2. Linear scan over wildcard/regex subscriptions (unavoidable)
713
+ // EventObj is created lazily – only when at least one subscription must be dispatched.
714
+ const exactSubs = this.subscriptionsMap.get(id);
715
+ const wildcardSubs = this.subscriptionsWildcard;
716
+ const hasWork = (exactSubs && exactSubs.length > 0) || wildcardSubs.length > 0;
717
+ if (!hasWork) {
718
+ return;
719
+ }
720
+ let _eventObj = null;
721
+ const getEvent = () => {
722
+ if (!_eventObj) {
723
+ _eventObj = (0, eventObj_1.createEventObject)(this.context, id, this.convertBackStringifiedValues(id, state), this.convertBackStringifiedValues(id, oldState));
724
+ }
725
+ return _eventObj;
726
+ };
727
+ if (exactSubs) {
728
+ for (let i = 0, l = exactSubs.length; i < l; i++) {
729
+ const sub = exactSubs[i];
730
+ if (sub?.patternCompareFunctions && patternMatching(getEvent(), sub.patternCompareFunctions)) {
731
+ try {
732
+ sub.callback(getEvent());
733
+ }
734
+ catch (err) {
735
+ this.log.error(`Error in callback: ${err.toString()}`);
736
+ }
737
+ }
738
+ }
739
+ }
740
+ // if this state matches any subscriptions
741
+ for (let i = 0, l = wildcardSubs.length; i < l; i++) {
742
+ const sub = wildcardSubs[i];
743
+ if (sub?.patternCompareFunctions && patternMatching(getEvent(), sub.patternCompareFunctions)) {
744
+ try {
745
+ sub.callback(getEvent());
746
+ }
747
+ catch (err) {
748
+ this.log.error(`Error in callback: ${err.toString()}`);
749
+ }
750
+ }
751
+ }
752
+ }
753
+ onFileChange(id, fileName, size) {
754
+ // if this file matches any subscriptions
755
+ for (let i = 0, l = this.subscriptionsFile.length; i < l; i++) {
756
+ const sub = this.subscriptionsFile[i];
757
+ if (sub && fileMatching(sub, id, fileName)) {
758
+ try {
759
+ sub.callback(id, fileName, size, sub.withFile);
760
+ }
761
+ catch (err) {
762
+ this.log.error(`Error in callback: ${err.toString()}`);
763
+ }
764
+ }
765
+ }
766
+ }
767
+ async onUnload(callback) {
768
+ try {
769
+ await this.debugStop();
770
+ this.stopTimeSchedules();
771
+ if (this.setStateCountCheckInterval) {
772
+ clearInterval(this.setStateCountCheckInterval);
773
+ this.setStateCountCheckInterval = null;
774
+ }
775
+ await this.unsubscribeAiCredentials();
776
+ await this.stopAllScripts();
777
+ }
778
+ catch (err) {
779
+ this.log.error(`Error during unload: ${err.message}`);
780
+ }
781
+ finally {
782
+ if (typeof callback === 'function') {
783
+ callback();
784
+ }
785
+ }
786
+ }
787
+ async onReady() {
788
+ this.errorLogFunction = this.log;
789
+ this.context.errorLogFunction = this.log;
790
+ this.config.maxSetStatePerMinute = parseInt(this.config.maxSetStatePerMinute, 10) || 1000;
791
+ this.config.maxTriggersPerScript = parseInt(this.config.maxTriggersPerScript, 10) || 100;
792
+ if (this.supportsFeature?.('PLUGINS')) {
793
+ const sentryInstance = this.getPluginInstance('sentry');
794
+ if (sentryInstance) {
795
+ const Sentry = sentryInstance.getSentryObject();
796
+ Sentry?.withScope(scope => {
797
+ scope.addEventProcessor((event, _hint) => {
798
+ if (event.exception?.values?.[0]) {
799
+ const eventData = event.exception.values[0];
800
+ if (eventData.stacktrace?.frames &&
801
+ Array.isArray(eventData.stacktrace.frames) &&
802
+ eventData.stacktrace.frames.length) {
803
+ // Exclude event if script Marker is included
804
+ if (eventData.stacktrace.frames.find(frame => frame.filename?.includes(SCRIPT_CODE_MARKER))) {
805
+ return null;
806
+ }
807
+ // Exclude event if own directory is included but not inside own node_modules
808
+ const ownNodeModulesDir = (0, node_path_1.join)(__dirname, 'node_modules');
809
+ if (!eventData.stacktrace.frames.find(frame => frame.filename?.includes(__dirname) &&
810
+ !frame.filename.includes(ownNodeModulesDir))) {
811
+ return null;
812
+ }
813
+ // We have exception data and did not sort it out, so report it
814
+ return event;
815
+ }
816
+ }
817
+ // No exception in it ... do not report
818
+ return null;
819
+ });
820
+ });
821
+ }
822
+ }
823
+ await this.main();
824
+ }
825
+ /** Read and decrypt a single AI credential's key from the central store; returns '' (and logs) on error. */
826
+ async readAiCredentialKey(id) {
827
+ if (!adapter_core_1.Credentials?.getCredentials) {
828
+ this.log.warn(`Cannot read AI credential "${id}": Credentials API is only with 7.2 js-controller available`);
829
+ return '';
830
+ }
831
+ try {
832
+ const cred = await adapter_core_1.Credentials.getCredentials(this, id);
833
+ return (cred?.values?.key || '').trim();
834
+ }
835
+ catch (e) {
836
+ this.log.warn(`Cannot read AI credential "${id}": ${e instanceof Error ? e.message : String(e)}`);
837
+ return '';
838
+ }
839
+ }
840
+ /**
841
+ * Resolve the API key (and base URL) for an AI provider.
842
+ *
843
+ * In `manual` mode the key comes from the encryptedNative adapter config.
844
+ * In `manager` mode the config only stores the ID of a credential in the central
845
+ * ioBroker credential store (`system.credentials.*`); the actual key is taken from the
846
+ * `aiCredentialCache` (kept fresh by `subscribeAiCredentials`) or, for credentials we are
847
+ * not subscribed to (e.g. a not-yet-saved selection in the settings dialog), read directly.
848
+ *
849
+ * The settings-dialog Test button may pass form values that are not saved yet
850
+ * (`messageApiKey` / `messageCredentialId` / `credentialType`); those win over the stored config.
851
+ */
852
+ async resolveAiCredentials(provider, opts = {}) {
853
+ const mode = opts.credentialType || this.config.credentialType || 'manual';
854
+ if (mode === 'manager') {
855
+ // The base URL is not a secret and is resolved the same way in both modes.
856
+ const { baseUrl } = (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider, opts.messageBaseUrl);
857
+ const id = (opts.messageCredentialId || (0, aiProviderResolver_1.getProviderCredentialId)(this.config, provider)).trim();
858
+ if (!id) {
859
+ return { apiKey: '', baseUrl };
860
+ }
861
+ // Prefer the cached value kept fresh by the credential subscription.
862
+ const cached = this.aiCredentialCache.get(id);
863
+ const apiKey = cached !== undefined ? cached : await this.readAiCredentialKey(id);
864
+ return { apiKey, baseUrl };
865
+ }
866
+ // Manual mode. The Test button sends the current form key (maybe empty) — let it win.
867
+ if (opts.messageApiKey !== undefined) {
868
+ return (0, aiProviderResolver_1.resolveTestCredentials)(this.config, provider, opts.messageApiKey, opts.messageBaseUrl);
869
+ }
870
+ return (0, aiProviderResolver_1.resolveProviderCredentials)(this.config, provider, opts.messageBaseUrl);
871
+ }
872
+ /**
873
+ * In `manager` mode, subscribe to all configured AI credentials so that edits made in the
874
+ * admin credential manager (Settings → Credentials) are picked up live, without restarting
875
+ * the adapter (the `system.credentials.*` objects are global, not part of the instance config).
876
+ * The decrypted keys are cached and kept fresh by the subscription handlers.
877
+ */
878
+ async subscribeAiCredentials() {
879
+ // Always start from a clean state (idempotent — also used to re-subscribe).
880
+ await this.unsubscribeAiCredentials();
881
+ if (this.config.credentialType !== 'manager') {
882
+ return;
883
+ }
884
+ if (!adapter_core_1.Credentials?.subscribeCredentials) {
885
+ this.log.warn(`Cannot subscribe AI credential: Credentials API is only with 7.2 js-controller available`);
886
+ return;
887
+ }
888
+ // Collect the distinct credential IDs configured across all AI providers.
889
+ const ids = new Set();
890
+ for (const provider of ['openai', 'anthropic', 'gemini', 'deepseek', 'custom']) {
891
+ const id = (0, aiProviderResolver_1.getProviderCredentialId)(this.config, provider);
892
+ if (id) {
893
+ ids.add(id);
894
+ }
895
+ }
896
+ for (const id of ids) {
897
+ try {
898
+ const unsubscribe = await adapter_core_1.Credentials.subscribeCredentials(this, id, (changedId, cred) => {
899
+ if (cred) {
900
+ this.aiCredentialCache.set(changedId, (cred.values?.key || '').trim());
901
+ this.log.debug(`AI credential "${changedId}" updated`);
902
+ }
903
+ else {
904
+ // The credential was deleted
905
+ this.aiCredentialCache.delete(changedId);
906
+ this.log.debug(`AI credential "${changedId}" was deleted`);
907
+ }
908
+ });
909
+ this.aiCredentialUnsubscribers.push(unsubscribe);
910
+ // Prime the cache with the current value (the handler may only fire on later changes).
911
+ this.aiCredentialCache.set(id, await this.readAiCredentialKey(id));
912
+ }
913
+ catch (e) {
914
+ this.log.warn(`Cannot subscribe to AI credential "${id}": ${e instanceof Error ? e.message : String(e)}`);
915
+ }
916
+ }
917
+ if (this.aiCredentialUnsubscribers.length) {
918
+ this.log.debug(`Subscribed to ${this.aiCredentialUnsubscribers.length} AI credential(s)`);
919
+ }
920
+ }
921
+ /** Tear down all AI credential subscriptions and clear the cache. */
922
+ async unsubscribeAiCredentials() {
923
+ const unsubscribers = this.aiCredentialUnsubscribers;
924
+ this.aiCredentialUnsubscribers = [];
925
+ this.aiCredentialCache.clear();
926
+ for (const unsubscribe of unsubscribers) {
927
+ try {
928
+ await unsubscribe();
929
+ }
930
+ catch (e) {
931
+ this.log.warn(`Cannot unsubscribe from AI credential: ${e instanceof Error ? e.message : String(e)}`);
932
+ }
933
+ }
934
+ }
935
+ onMessage(obj) {
936
+ switch (obj?.command) {
937
+ // process messageTo commands
938
+ case 'toScript':
939
+ case 'jsMessageBus':
940
+ if (obj.message &&
941
+ (obj.message.instance === null ||
942
+ obj.message.instance === undefined ||
943
+ `javascript.${obj.message.instance}` === this.namespace ||
944
+ obj.message.instance === this.namespace)) {
945
+ Object.keys(this.messageBusHandlers).forEach(name => {
946
+ // the script name could be script.js.xxx or only xxx
947
+ if ((!obj.message.script || obj.message.script === name) &&
948
+ this.messageBusHandlers[name][obj.message.message]) {
949
+ this.messageBusHandlers[name][obj.message.message].forEach(handler => {
950
+ const sandbox = handler.sandbox;
951
+ if (sandbox.verbose) {
952
+ sandbox.log(`onMessage: ${JSON.stringify(obj.message)}`, 'info');
953
+ }
954
+ try {
955
+ if (obj.callback) {
956
+ handler.cb.call(sandbox, obj.message.data, (result) => {
957
+ if (sandbox.verbose) {
958
+ sandbox.log(`onMessage result: ${JSON.stringify(result)}`, 'info');
959
+ }
960
+ this.sendTo(obj.from, obj.command, result, obj.callback);
961
+ });
962
+ }
963
+ else {
964
+ handler.cb.call(sandbox, obj.message.data, (result) => {
965
+ if (sandbox.verbose) {
966
+ sandbox.log(`onMessage result: ${JSON.stringify(result)}`, 'info');
967
+ }
968
+ });
969
+ }
970
+ }
971
+ catch (err) {
972
+ void this.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, true, true);
973
+ this.logError(name, 'Error in callback:', err);
974
+ }
975
+ });
976
+ }
977
+ });
978
+ }
979
+ break;
980
+ case 'loadTypings': {
981
+ // Load typings for the editor
982
+ const typings = {};
983
+ // try to load TypeScript lib files from disk
984
+ try {
985
+ const typescriptLibs = (0, typescriptTools_1.resolveTypescriptLibs)(typescriptSettings_1.targetTsLib);
986
+ Object.assign(typings, typescriptLibs);
987
+ }
988
+ catch {
989
+ /* ok, no lib then */
990
+ }
991
+ // provide the already-loaded ioBroker typings and global script declarations
992
+ Object.assign(typings, tsAmbient);
993
+ // also provide the known global declarations for each global script
994
+ for (const globalScriptPaths of Object.keys(this.knownGlobalDeclarationsByScript)) {
995
+ typings[`${globalScriptPaths}.d.ts`] = this.knownGlobalDeclarationsByScript[globalScriptPaths];
996
+ }
997
+ if (obj.callback) {
998
+ this.sendTo(obj.from, obj.command, { typings }, obj.callback);
999
+ }
1000
+ break;
1001
+ }
1002
+ case 'calcAstroAll': {
1003
+ if (obj.message) {
1004
+ const sunriseOffset = parseInt(obj.message.sunriseOffset === undefined
1005
+ ? this.config.sunriseOffset
1006
+ : obj.message.sunriseOffset, 10) || 0;
1007
+ const sunsetOffset = parseInt(obj.message.sunsetOffset === undefined
1008
+ ? this.config.sunsetOffset
1009
+ : obj.message.sunsetOffset, 10) || 0;
1010
+ const longitude = parseFloat(obj.message.longitude === undefined ? this.config.longitude : obj.message.longitude) || 0;
1011
+ const latitude = parseFloat(obj.message.latitude === undefined ? this.config.latitude : obj.message.latitude) ||
1012
+ 0;
1013
+ const today = getAstroStartOfDay();
1014
+ let astroEvents = {};
1015
+ try {
1016
+ astroEvents = this.mods.suncalc.getTimes(today, latitude, longitude);
1017
+ }
1018
+ catch (err) {
1019
+ this.log.error(`Cannot calculate astro data: ${err}`);
1020
+ }
1021
+ if (astroEvents) {
1022
+ try {
1023
+ astroEvents.nextSunrise = this.getAstroEvent(today, obj.message.sunriseEvent || this.config.sunriseEvent, obj.message.sunriseLimitStart || this.config.sunriseLimitStart, obj.message.sunriseLimitEnd || this.config.sunriseLimitEnd, sunriseOffset, false, latitude, longitude, true);
1024
+ astroEvents.nextSunset = this.getAstroEvent(today, obj.message.sunsetEvent || this.config.sunsetEvent, obj.message.sunsetLimitStart || this.config.sunsetLimitStart, obj.message.sunsetLimitEnd || this.config.sunsetLimitEnd, sunsetOffset, true, latitude, longitude, true);
1025
+ }
1026
+ catch (err) {
1027
+ this.log.error(`Cannot calculate astro data: ${err}`);
1028
+ }
1029
+ }
1030
+ const result = {};
1031
+ const keys = Object.keys(astroEvents).sort((a, b) => astroEvents[a] -
1032
+ astroEvents[b]);
1033
+ keys.forEach(key => {
1034
+ const validDate = astroEvents[key] !== null &&
1035
+ !isNaN(astroEvents[key].getTime());
1036
+ result[key] = {
1037
+ isValidDate: validDate,
1038
+ serverTime: validDate
1039
+ ? formatHoursMinutesSeconds(astroEvents[key])
1040
+ : 'n/a',
1041
+ date: validDate
1042
+ ? astroEvents[key].toISOString()
1043
+ : 'n/a',
1044
+ };
1045
+ });
1046
+ if (obj.callback) {
1047
+ this.sendTo(obj.from, obj.command, result, obj.callback);
1048
+ }
1049
+ }
1050
+ break;
1051
+ }
1052
+ case 'calcAstro': {
1053
+ if (obj.message) {
1054
+ const longitude = parseFloat(obj.message.longitude === undefined ? this.config.longitude : obj.message.longitude) || 0;
1055
+ const latitude = parseFloat(obj.message.latitude === undefined ? this.config.latitude : obj.message.latitude) ||
1056
+ 0;
1057
+ const today = getAstroStartOfDay();
1058
+ const sunriseEvent = obj.message?.sunriseEvent || this.config.sunriseEvent;
1059
+ const sunriseLimitStart = obj.message?.sunriseLimitStart || this.config.sunriseLimitStart;
1060
+ const sunriseLimitEnd = obj.message?.sunriseLimitEnd || this.config.sunriseLimitEnd;
1061
+ const sunriseOffset = parseInt(obj.message.sunriseOffset === undefined
1062
+ ? this.config.sunriseOffset
1063
+ : obj.message.sunriseOffset, 10) || 0;
1064
+ const nextSunrise = this.getAstroEvent(today, sunriseEvent, sunriseLimitStart, sunriseLimitEnd, sunriseOffset, false, latitude, longitude, true);
1065
+ const sunsetEvent = obj.message?.sunsetEvent || this.config.sunsetEvent;
1066
+ const sunsetLimitStart = obj.message?.sunsetLimitStart || this.config.sunsetLimitStart;
1067
+ const sunsetLimitEnd = obj.message?.sunsetLimitEnd || this.config.sunsetLimitEnd;
1068
+ const sunsetOffset = parseInt(obj.message.sunsetOffset === undefined
1069
+ ? this.config.sunsetOffset
1070
+ : obj.message.sunsetOffset, 10) || 0;
1071
+ const nextSunset = this.getAstroEvent(today, sunsetEvent, sunsetLimitStart, sunsetLimitEnd, sunsetOffset, true, latitude, longitude, true);
1072
+ const validDateSunrise = nextSunrise !== null && !isNaN(nextSunrise.getTime());
1073
+ const validDateSunset = nextSunset !== null && !isNaN(nextSunset.getTime());
1074
+ this.log.debug(`calcAstro sunrise: ${sunriseEvent} -> start ${sunriseLimitStart}, end: ${sunriseLimitEnd}, offset: ${sunriseOffset} - ${validDateSunrise ? nextSunrise.toISOString() : 'n/a'}`);
1075
+ this.log.debug(`calcAstro sunset: ${sunsetEvent} -> start ${sunsetLimitStart}, end: ${sunsetLimitEnd}, offset: ${sunsetOffset} - ${validDateSunset ? nextSunset.toISOString() : 'n/a'}`);
1076
+ if (obj.callback) {
1077
+ this.sendTo(obj.from, obj.command, {
1078
+ nextSunrise: {
1079
+ isValidDate: validDateSunrise,
1080
+ serverTime: validDateSunrise ? formatHoursMinutesSeconds(nextSunrise) : 'n/a',
1081
+ date: nextSunrise.toISOString(),
1082
+ },
1083
+ nextSunset: {
1084
+ isValidDate: validDateSunset,
1085
+ serverTime: validDateSunset ? formatHoursMinutesSeconds(nextSunset) : 'n/a',
1086
+ date: nextSunset.toISOString(),
1087
+ },
1088
+ }, obj.callback);
1089
+ }
1090
+ }
1091
+ break;
1092
+ }
1093
+ case 'debug': {
1094
+ if (!this.context.debugMode) {
1095
+ this.debugStart(obj.message);
1096
+ }
1097
+ break;
1098
+ }
1099
+ case 'debugStop': {
1100
+ if (!this.context.debugMode) {
1101
+ void this.debugStop().then(() => console.log('stopped'));
1102
+ }
1103
+ break;
1104
+ }
1105
+ case 'rulesOn': {
1106
+ this.context.rulesOpened = obj.message;
1107
+ console.log(`Enable messaging for ${this.context.rulesOpened}`);
1108
+ break;
1109
+ }
1110
+ case 'rulesOff': {
1111
+ // maybe if (context.rulesOpened === obj.message)
1112
+ console.log(`Disable messaging for ${this.context.rulesOpened}`);
1113
+ this.context.rulesOpened = null;
1114
+ break;
1115
+ }
1116
+ case 'getIoBrokerDataDir': {
1117
+ if (obj.callback) {
1118
+ this.sendTo(obj.from, obj.command, {
1119
+ dataDir: (0, adapter_core_1.getAbsoluteDefaultDataDir)(),
1120
+ sep: node_path_1.sep,
1121
+ }, obj.callback);
1122
+ }
1123
+ break;
1124
+ }
1125
+ case 'chatCompletion': {
1126
+ // Proxy chat completion requests to an OpenAI-compatible API endpoint.
1127
+ // API keys are resolved server-side from the encryptedNative config or the central
1128
+ // credentials manager — they never leave the adapter (frontend only sends `provider`).
1129
+ void (async () => {
1130
+ if (!obj.callback) {
1131
+ return;
1132
+ }
1133
+ const chatModel = (obj.message?.model || '').trim();
1134
+ const messages = obj.message?.messages;
1135
+ const tools = obj.message?.tools;
1136
+ const provider = (obj.message?.provider || 'openai').trim();
1137
+ const { apiKey, baseUrl } = await this.resolveAiCredentials(provider, {
1138
+ messageBaseUrl: obj.message?.baseUrl,
1139
+ });
1140
+ // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
1141
+ if (!apiKey &&
1142
+ (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
1143
+ this.sendTo(obj.from, obj.command, { error: 'No API key provided' }, obj.callback);
1144
+ return;
1145
+ }
1146
+ if (!chatModel || !messages) {
1147
+ this.sendTo(obj.from, obj.command, { error: 'Model and messages are required' }, obj.callback);
1148
+ return;
1149
+ }
1150
+ let url;
1151
+ const chatHeaders = {
1152
+ 'Content-Type': 'application/json',
1153
+ };
1154
+ let bodyObj;
1155
+ if (provider === 'anthropic') {
1156
+ url = 'https://api.anthropic.com/v1/messages';
1157
+ chatHeaders['x-api-key'] = apiKey;
1158
+ chatHeaders['anthropic-version'] = '2023-06-01';
1159
+ // Translate OpenAI-format messages/tools into Anthropic's content-block format.
1160
+ const { system: systemText, messages: anthropicMessages } = (0, anthropicAdapter_1.translateMessagesToAnthropic)(messages);
1161
+ const anthropicTools = tools?.length ? (0, anthropicAdapter_1.translateToolsToAnthropic)(tools) : [];
1162
+ bodyObj = {
1163
+ model: chatModel,
1164
+ max_tokens: 8192,
1165
+ stream: false,
1166
+ ...(systemText ? { system: systemText } : {}),
1167
+ messages: anthropicMessages,
1168
+ ...(anthropicTools.length ? { tools: anthropicTools } : {}),
1169
+ };
1170
+ }
1171
+ else if (provider === 'gemini') {
1172
+ url = 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions';
1173
+ if (apiKey) {
1174
+ chatHeaders.Authorization = `Bearer ${apiKey}`;
1175
+ }
1176
+ bodyObj = { model: chatModel, messages, stream: false, ...(tools?.length ? { tools } : {}) };
1177
+ }
1178
+ else if (provider === 'deepseek') {
1179
+ url = 'https://api.deepseek.com/chat/completions';
1180
+ chatHeaders.Authorization = `Bearer ${apiKey}`;
1181
+ bodyObj = { model: chatModel, messages, stream: false, ...(tools?.length ? { tools } : {}) };
1182
+ }
1183
+ else {
1184
+ url = `${baseUrl || 'https://api.openai.com/v1'}/chat/completions`;
1185
+ if (apiKey) {
1186
+ chatHeaders.Authorization = `Bearer ${apiKey}`;
1187
+ }
1188
+ bodyObj = {
1189
+ model: chatModel,
1190
+ messages,
1191
+ stream: false,
1192
+ ...(tools?.length ? { tools } : {}),
1193
+ // Disable thinking/reasoning for local models to save context and speed
1194
+ ...(baseUrl ? { reasoning_effort: 'none' } : {}),
1195
+ };
1196
+ }
1197
+ const body = JSON.stringify(bodyObj);
1198
+ const bodyBuffer = Buffer.from(body, 'utf8');
1199
+ chatHeaders['Content-Length'] = bodyBuffer.length;
1200
+ const resolved = resolveRequestModule(url);
1201
+ if (!resolved) {
1202
+ this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
1203
+ return;
1204
+ }
1205
+ const { module: requestModule, isHttps } = resolved;
1206
+ try {
1207
+ const req = requestModule.request(url, {
1208
+ method: 'POST',
1209
+ headers: chatHeaders,
1210
+ timeout: 600000,
1211
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1212
+ }, res => {
1213
+ let data = '';
1214
+ res.on('data', (chunk) => {
1215
+ data += chunk.toString();
1216
+ });
1217
+ res.on('end', () => {
1218
+ if (res.statusCode === 200) {
1219
+ try {
1220
+ const parsed = JSON.parse(data);
1221
+ let content;
1222
+ let tool_calls;
1223
+ if (provider === 'anthropic') {
1224
+ const translated = (0, anthropicAdapter_1.translateAnthropicResponseToOpenAI)(parsed);
1225
+ content = translated.content;
1226
+ tool_calls = translated.tool_calls;
1227
+ }
1228
+ else {
1229
+ const message = parsed.choices?.[0]?.message;
1230
+ content = message?.content || '';
1231
+ tool_calls = message?.tool_calls;
1232
+ }
1233
+ if (!content && !tool_calls?.length) {
1234
+ this.sendTo(obj.from, obj.command, { error: 'Empty response from API' }, obj.callback);
1235
+ }
1236
+ else {
1237
+ this.sendTo(obj.from, obj.command, {
1238
+ success: true,
1239
+ content,
1240
+ ...(tool_calls ? { tool_calls } : {}),
1241
+ }, obj.callback);
1242
+ }
1243
+ }
1244
+ catch {
1245
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1246
+ }
1247
+ }
1248
+ else {
1249
+ let detail = '';
1250
+ try {
1251
+ const errParsed = JSON.parse(data);
1252
+ detail = errParsed.error?.message || data.substring(0, 200);
1253
+ }
1254
+ catch {
1255
+ detail = data.substring(0, 200);
1256
+ }
1257
+ this.sendTo(obj.from, obj.command, {
1258
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1259
+ }, obj.callback);
1260
+ }
1261
+ });
1262
+ });
1263
+ req.on('error', (err) => {
1264
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1265
+ });
1266
+ req.on('timeout', () => {
1267
+ req.destroy();
1268
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (600s)' }, obj.callback);
1269
+ });
1270
+ req.write(bodyBuffer);
1271
+ req.end();
1272
+ }
1273
+ catch (error) {
1274
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1275
+ }
1276
+ })();
1277
+ break;
1278
+ }
1279
+ case 'testApiConnection': {
1280
+ // Test connection to an OpenAI-compatible API endpoint.
1281
+ // The settings-dialog Test button sends the current form value as `apiKey`
1282
+ // (so users can test before saving); otherwise we fall back to the stored key.
1283
+ void (async () => {
1284
+ if (!obj.callback) {
1285
+ return;
1286
+ }
1287
+ const provider = (obj.message?.provider || 'openai').trim();
1288
+ const { apiKey, baseUrl } = await this.resolveAiCredentials(provider, {
1289
+ messageApiKey: obj.message?.apiKey,
1290
+ messageBaseUrl: obj.message?.baseUrl,
1291
+ messageCredentialId: obj.message?.credentialId,
1292
+ credentialType: obj.message?.credentialType,
1293
+ });
1294
+ // Anthropic, Gemini, and DeepSeek always require an API key; OpenAI-compatible allows empty key with custom base URL
1295
+ if (!apiKey &&
1296
+ (provider === 'anthropic' || provider === 'gemini' || provider === 'deepseek' || !baseUrl)) {
1297
+ this.sendTo(obj.from, obj.command, { error: 'No API key provided' }, obj.callback);
1298
+ return;
1299
+ }
1300
+ let url;
1301
+ const testHeaders = {
1302
+ 'Content-Type': 'application/json',
1303
+ };
1304
+ if (provider === 'anthropic') {
1305
+ url = 'https://api.anthropic.com/v1/models';
1306
+ testHeaders['x-api-key'] = apiKey;
1307
+ testHeaders['anthropic-version'] = '2023-06-01';
1308
+ }
1309
+ else if (provider === 'gemini') {
1310
+ url = 'https://generativelanguage.googleapis.com/v1beta/openai/models';
1311
+ if (apiKey) {
1312
+ testHeaders.Authorization = `Bearer ${apiKey}`;
1313
+ }
1314
+ }
1315
+ else if (provider === 'deepseek') {
1316
+ url = 'https://api.deepseek.com/models';
1317
+ testHeaders.Authorization = `Bearer ${apiKey}`;
1318
+ }
1319
+ else {
1320
+ url = `${baseUrl || 'https://api.openai.com/v1'}/models`;
1321
+ if (apiKey) {
1322
+ testHeaders.Authorization = `Bearer ${apiKey}`;
1323
+ }
1324
+ }
1325
+ const resolved = resolveRequestModule(url);
1326
+ if (!resolved) {
1327
+ this.sendTo(obj.from, obj.command, { error: `Invalid API URL: ${url}` }, obj.callback);
1328
+ return;
1329
+ }
1330
+ const { module: requestModule, isHttps } = resolved;
1331
+ try {
1332
+ const req = requestModule.request(url, {
1333
+ method: 'GET',
1334
+ headers: testHeaders,
1335
+ timeout: 10000,
1336
+ ...(isHttps && this.config.allowSelfSignedCerts ? { rejectUnauthorized: false } : {}),
1337
+ }, res => {
1338
+ let data = '';
1339
+ res.on('data', (chunk) => {
1340
+ data += chunk.toString();
1341
+ });
1342
+ res.on('end', () => {
1343
+ if (res.statusCode === 200) {
1344
+ try {
1345
+ const parsed = JSON.parse(data);
1346
+ const models = (parsed.data || [])
1347
+ .map((m) => m.id.startsWith('models/') ? m.id.substring(7) : m.id)
1348
+ .sort();
1349
+ this.sendTo(obj.from, obj.command, { success: true, models, count: models.length }, obj.callback);
1350
+ }
1351
+ catch {
1352
+ this.sendTo(obj.from, obj.command, { error: 'Invalid JSON response from API' }, obj.callback);
1353
+ }
1354
+ }
1355
+ else if (res.statusCode === 401) {
1356
+ this.sendTo(obj.from, obj.command, { error: 'Invalid API key (401)' }, obj.callback);
1357
+ }
1358
+ else if (res.statusCode === 403) {
1359
+ this.sendTo(obj.from, obj.command, { error: 'Access denied (403)' }, obj.callback);
1360
+ }
1361
+ else {
1362
+ // Include response body for debugging
1363
+ let detail = '';
1364
+ try {
1365
+ const errParsed = JSON.parse(data);
1366
+ detail = errParsed.error?.message || data.substring(0, 200);
1367
+ }
1368
+ catch {
1369
+ detail = data.substring(0, 200);
1370
+ }
1371
+ this.sendTo(obj.from, obj.command, {
1372
+ error: `${detail || httpStatusText(res.statusCode || 0)} (${res.statusCode})`,
1373
+ }, obj.callback);
1374
+ }
1375
+ });
1376
+ });
1377
+ req.on('error', (err) => {
1378
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${err.message}` }, obj.callback);
1379
+ });
1380
+ req.on('timeout', () => {
1381
+ req.destroy();
1382
+ this.sendTo(obj.from, obj.command, { error: 'Connection timeout (10s)' }, obj.callback);
1383
+ });
1384
+ req.end();
1385
+ }
1386
+ catch (error) {
1387
+ this.sendTo(obj.from, obj.command, { error: `Connection failed: ${error.toString()}` }, obj.callback);
1388
+ }
1389
+ })();
1390
+ break;
1391
+ }
1392
+ case 'getAvailableAiProviders': {
1393
+ // Reports which AI providers have stored credentials (keys never leave the backend).
1394
+ if (obj.callback) {
1395
+ const providers = (0, aiProviderResolver_1.listAvailableProviders)(this.config);
1396
+ this.sendTo(obj.from, obj.command, { providers }, obj.callback);
1397
+ }
1398
+ break;
1399
+ }
1400
+ case 'prettier': {
1401
+ // Format the code with Prettier
1402
+ if (obj.message && typeof obj.message.code === 'string') {
1403
+ try {
1404
+ prettier_1.default
1405
+ .format(obj.message.code, {
1406
+ parser: obj.message.type === 'typescript' ? 'babel-ts' : 'babel',
1407
+ printWidth: 120,
1408
+ semi: true,
1409
+ tabWidth: 4,
1410
+ useTabs: false,
1411
+ trailingComma: 'all',
1412
+ singleQuote: true,
1413
+ singleAttributePerLine: true,
1414
+ endOfLine: 'lf',
1415
+ bracketSpacing: true,
1416
+ arrowParens: 'avoid',
1417
+ quoteProps: 'as-needed',
1418
+ })
1419
+ .then(formattedCode => {
1420
+ if (obj.callback) {
1421
+ this.sendTo(obj.from, obj.command, { code: formattedCode }, obj.callback);
1422
+ }
1423
+ else {
1424
+ this.logWithLineInfo(`Formatted code:\n${formattedCode}`);
1425
+ }
1426
+ })
1427
+ .catch(e => {
1428
+ this.logError('Prettier', 'Error formatting code:', e);
1429
+ this.sendTo(obj.from, obj.command, { error: e.toString() }, obj.callback);
1430
+ });
1431
+ }
1432
+ catch (e) {
1433
+ this.logError('Prettier', 'Error formatting code:', e);
1434
+ this.sendTo(obj.from, obj.command, { error: e.toString() }, obj.callback);
1435
+ }
1436
+ }
1437
+ else {
1438
+ this.sendTo(obj.from, obj.command, { error: 'No code provided' }, obj.callback);
1439
+ }
1440
+ break;
1441
+ }
1442
+ case 'execute': {
1443
+ if (obj.callback) {
1444
+ void this.executeScript(obj.message)
1445
+ .then(result => this.sendTo(obj.from, obj.command, result, obj.callback))
1446
+ .catch(err => this.sendTo(obj.from, obj.command, {
1447
+ ok: false,
1448
+ error: `Internal error: ${err}`,
1449
+ logs: [],
1450
+ output: '',
1451
+ }, obj.callback));
1452
+ }
1453
+ break;
1454
+ }
1455
+ }
1456
+ }
1457
+ onLog(msg) {
1458
+ for (const name of Object.keys(this.logSubscriptions)) {
1459
+ for (const handler of this.logSubscriptions[name]) {
1460
+ if (typeof handler.cb === 'function' &&
1461
+ (handler.severity === '*' || handler.severity === msg.severity)) {
1462
+ handler.sandbox.logHandler = handler.severity || '*';
1463
+ handler.cb.call(handler.sandbox, msg);
1464
+ handler.sandbox.logHandler = undefined;
1465
+ }
1466
+ }
1467
+ }
1468
+ // Special case if some script is executed now with "execute" command, and we see "script.js.__execute_X:" at the beginning
1469
+ if (this.logCollectors.length) {
1470
+ for (const logCollector of this.logCollectors) {
1471
+ if (msg.message.includes(`${logCollector.name}:`)) {
1472
+ logCollector.collector(msg.severity, msg.message);
1473
+ }
1474
+ }
1475
+ }
1476
+ }
1477
+ logError(scriptName, msg, e, offs) {
1478
+ const stack = e.stack ? e.stack.toString().split('\n') : e ? e.toString() : '';
1479
+ if (!msg.includes('\n')) {
1480
+ msg = msg.replace(/[: ]*$/, ': ');
1481
+ }
1482
+ if (!msg.endsWith(' ')) {
1483
+ msg += ':';
1484
+ }
1485
+ if (!scriptName.startsWith(SCRIPT_CODE_MARKER)) {
1486
+ scriptName = SCRIPT_CODE_MARKER + scriptName;
1487
+ }
1488
+ this.errorLogFunction.error(`${scriptName}: ${msg}${this.fixLineNo(stack[0])}`);
1489
+ for (let i = offs || 1; i < stack.length; i++) {
1490
+ if (!stack[i]) {
1491
+ continue;
1492
+ }
1493
+ if (stack[i].match(/runInNewContext|javascript\.js:/)) {
1494
+ break;
1495
+ }
1496
+ this.errorLogFunction.error(`${scriptName}: ${this.fixLineNo(stack[i])}`);
1497
+ }
1498
+ }
1499
+ logWithLineInfo(msg) {
1500
+ this.errorLogFunction.warn(msg);
1501
+ // get current error stack
1502
+ const stack = new Error().stack?.split('\n');
1503
+ if (stack) {
1504
+ for (let i = 3; i < stack.length; i++) {
1505
+ if (!stack[i]) {
1506
+ continue;
1507
+ }
1508
+ if (stack[i].match(/runInContext|runInNewContext|javascript\.js:/)) {
1509
+ break;
1510
+ }
1511
+ this.errorLogFunction.warn(this.fixLineNo(stack[i]));
1512
+ }
1513
+ }
1514
+ }
1515
+ async main() {
1516
+ // Patch the font as it sometimes is wrong
1517
+ if (!this.context.debugMode) {
1518
+ if (await this.patchFont()) {
1519
+ this.log.debug('Font patched');
1520
+ }
1521
+ }
1522
+ this.log.debug(`config.subscribe (Do not subscribe all states on start): ${this.config.subscribe}`);
1523
+ // correct jsonConfig for admin
1524
+ const instObj = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`);
1525
+ if (instObj?.common) {
1526
+ if (instObj.common.adminUI?.config !== 'json') {
1527
+ if (instObj.common.adminUI) {
1528
+ instObj.common.adminUI.config = 'json';
1529
+ }
1530
+ else {
1531
+ instObj.common.adminUI = { config: 'json' };
1532
+ }
1533
+ void this.setForeignObject(instObj._id, instObj);
1534
+ }
1535
+ }
1536
+ if (webstormDebug) {
1537
+ this.errorLogFunction = {
1538
+ error: console.error,
1539
+ warn: console.warn,
1540
+ info: console.info,
1541
+ debug: console.log,
1542
+ silly: console.log,
1543
+ };
1544
+ this.context.errorLogFunction = this.errorLogFunction;
1545
+ }
1546
+ this.activeStr = `${this.namespace}.scriptEnabled.`;
1547
+ this.mods.fs = new protectFs_1.default(this.log, (0, adapter_core_1.getAbsoluteDefaultDataDir)());
1548
+ this.mods['fs/promises'] = this.mods.fs.promises; // to avoid require('fs/promises');
1549
+ // try to read TS declarations
1550
+ try {
1551
+ tsAmbient = {
1552
+ 'javascript.d.ts': (0, node_fs_1.readFileSync)(this.mods.path.join(__dirname, 'lib/javascript.d.ts'), 'utf8'),
1553
+ };
1554
+ this.tsServer.provideAmbientDeclarations(tsAmbient);
1555
+ jsDeclarationServer.provideAmbientDeclarations(tsAmbient);
1556
+ }
1557
+ catch (err) {
1558
+ this.log.warn(`Could not read TypeScript ambient declarations: ${err}`);
1559
+ // This should not happen, so send an error report to Sentry
1560
+ if (this.supportsFeature && this.supportsFeature('PLUGINS')) {
1561
+ const sentryInstance = this.getPluginInstance('sentry');
1562
+ if (sentryInstance) {
1563
+ const sentryObject = sentryInstance.getSentryObject();
1564
+ sentryObject?.captureException(err);
1565
+ }
1566
+ }
1567
+ // Keep the adapter from crashing when the included typings cannot be read
1568
+ tsAmbient = {};
1569
+ }
1570
+ await this.installLibraries();
1571
+ // Load the TS declarations for Node.js and all 3rd party modules
1572
+ this.loadTypeScriptDeclarations();
1573
+ await this.getData();
1574
+ this.context.scheduler = new scheduler_1.Scheduler(this.log, Date, this.mods.suncalc, this.config.latitude, this.config.longitude);
1575
+ await this.dayTimeSchedules();
1576
+ await this.sunTimeSchedules();
1577
+ await this.timeSchedule();
1578
+ // Store allowSelfSignedCerts on the context, so sandbox HTTP functions can use it
1579
+ // without setting the global process.env.NODE_TLS_REJECT_UNAUTHORIZED (which affects all adapters in compact mode)
1580
+ this.context.allowSelfSignedCerts = this.config.allowSelfSignedCerts;
1581
+ // In `manager` credential mode, subscribe to the configured AI credentials so changes in the
1582
+ // central credential store are picked up live (the keys are cached for the AI sendTo handlers).
1583
+ await this.subscribeAiCredentials();
1584
+ const doc = await this.getObjectViewAsync('script', 'javascript', {});
1585
+ if (doc?.rows?.length) {
1586
+ // sort global scripts if configured
1587
+ if (this.config.sortGlobalScriptsAlphabetically) {
1588
+ doc.rows.sort((a, b) => a.value._id.localeCompare(b.value._id));
1589
+ }
1590
+ // assemble global script
1591
+ for (let g = 0; g < doc.rows.length; g++) {
1592
+ const obj = doc.rows[g].value;
1593
+ if (checkIsGlobal(obj)) {
1594
+ if (obj && obj.common) {
1595
+ const engineType = (obj.common.engineType || '').toLowerCase();
1596
+ if (obj.common.enabled) {
1597
+ if (engineType.startsWith('typescript')) {
1598
+ // TypeScript
1599
+ this.log.info(`${obj._id}: compiling TypeScript source...`);
1600
+ // In order to compile global TypeScript, we need to do some transformations
1601
+ // 1. For top-level-await, some statements must be wrapped in an immediately-invoked async function
1602
+ // 2. If any global script uses `import`, the declarations are no longer visible if they are not exported with `declare global`
1603
+ const transformedSource = (0, typescriptTools_1.transformScriptBeforeCompilation)(obj.common.source, true);
1604
+ // The source code must be transformed in order to support top level await
1605
+ // Global scripts must not be treated as a module, otherwise their methods
1606
+ // cannot be found by the normal scripts
1607
+ // We need to hash both global declarations that are known until now
1608
+ // AND the script source, because changing either can change the compilation output
1609
+ const sourceHash = (0, tools_1.hashSource)(tsSourceHashBase + this.globalDeclarations + transformedSource);
1610
+ let compiled;
1611
+ let declarations;
1612
+ // If we already stored the compiled source code and the original source hash,
1613
+ // use the hash to check whether we can rely on the compiled source code or
1614
+ // if we need to compile it again
1615
+ if (typeof obj.common.compiled === 'string' &&
1616
+ typeof obj.common.sourceHash === 'string' &&
1617
+ sourceHash === obj.common.sourceHash) {
1618
+ // We can reuse the stored source
1619
+ compiled = obj.common.compiled;
1620
+ declarations = obj.common.declarations;
1621
+ this.log.info(`${obj._id}: source code did not change, using cached compilation result...`);
1622
+ }
1623
+ else {
1624
+ // We don't have a hashed source code, or the original source changed, compile it
1625
+ const filename = (0, typescriptTools_1.scriptIdToTSFilename)(obj._id);
1626
+ let tsCompiled;
1627
+ try {
1628
+ tsCompiled = this.tsServer.compile(filename, transformedSource);
1629
+ }
1630
+ catch (err) {
1631
+ this.log.error(`${obj._id}: TypeScript compilation failed:\n${err}`);
1632
+ continue;
1633
+ }
1634
+ const errors = tsCompiled.diagnostics
1635
+ .map(diag => `${diag.annotatedSource}\n`)
1636
+ .join('\n');
1637
+ if (tsCompiled.success) {
1638
+ if (errors.length > 0) {
1639
+ this.log.warn(`${obj._id}: TypeScript compilation completed with errors:\n${errors}`);
1640
+ }
1641
+ else {
1642
+ this.log.info(`${obj._id}: TypeScript compilation successful`);
1643
+ }
1644
+ compiled = tsCompiled.result;
1645
+ // Global scripts that have been transformed to support `import` need to have their declarations transformed aswell
1646
+ declarations = (0, typescriptTools_1.transformGlobalDeclarations)(tsCompiled.declarations || '');
1647
+ const newCommon = {
1648
+ sourceHash,
1649
+ compiled,
1650
+ };
1651
+ if (declarations) {
1652
+ newCommon.declarations = declarations;
1653
+ }
1654
+ // Store the compiled source and the original source hash, so we don't need to do the work again next time
1655
+ this.ignoreObjectChange.add(obj._id); // ignore the next change and don't restart scripts
1656
+ void this.extendForeignObject(obj._id, {
1657
+ common: newCommon,
1658
+ });
1659
+ }
1660
+ else {
1661
+ this.log.error(`${obj._id}: TypeScript compilation failed:\n${errors}`);
1662
+ continue;
1663
+ }
1664
+ }
1665
+ this.globalScript += `${compiled}\n`;
1666
+ // if declarations were generated, remember them
1667
+ if (declarations != null) {
1668
+ this.provideDeclarationsForGlobalScript(obj._id, declarations);
1669
+ }
1670
+ }
1671
+ else {
1672
+ // javascript
1673
+ const sourceCode = obj.common.source;
1674
+ this.globalScript += `${sourceCode}\n`;
1675
+ // try to compile the declarations so TypeScripts can use
1676
+ // functions defined in global JavaScripts
1677
+ const filename = (0, typescriptTools_1.scriptIdToTSFilename)(obj._id);
1678
+ let tsCompiled;
1679
+ try {
1680
+ tsCompiled = jsDeclarationServer.compile(filename, sourceCode);
1681
+ }
1682
+ catch (err) {
1683
+ this.log.warn(`${obj._id}: Error while generating type declarations, skipping:\n${err}`);
1684
+ continue;
1685
+ }
1686
+ // if declarations were generated, remember them
1687
+ if (tsCompiled.success && tsCompiled.declarations != null) {
1688
+ this.provideDeclarationsForGlobalScript(obj._id, tsCompiled.declarations);
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ }
1696
+ this.globalScript = this.globalScript.replace(/\r\n/g, '\n');
1697
+ this.globalScriptLines = this.globalScript.split(/\n/g).length - 1;
1698
+ if (doc?.rows?.length) {
1699
+ // load all scripts
1700
+ for (let i = 0; i < doc.rows.length; i++) {
1701
+ if (!checkIsGlobal(doc.rows[i].value)) {
1702
+ void this.loadScript(doc.rows[i].value);
1703
+ }
1704
+ }
1705
+ }
1706
+ if (this.config.mirrorPath?.trim()) {
1707
+ this.config.mirrorInstance = parseInt(this.config.mirrorInstance, 10) || 0;
1708
+ if (this.instance === this.config.mirrorInstance) {
1709
+ const ioBDataDir = (0, adapter_core_1.getAbsoluteDefaultDataDir)() + node_path_1.sep;
1710
+ this.config.mirrorPath = (0, node_path_1.normalize)(this.config.mirrorPath);
1711
+ let mirrorForbidden = false;
1712
+ for (let dir of forbiddenMirrorLocations) {
1713
+ dir = (0, node_path_1.join)(ioBDataDir, dir) + node_path_1.sep;
1714
+ if (dir.includes(this.config.mirrorPath) || this.config.mirrorPath.startsWith(dir)) {
1715
+ this.log.error(`The Mirror directory is not allowed to be a central ioBroker directory!`);
1716
+ this.log.error(`Directory ${this.config.mirrorPath} is not allowed to mirror files!`);
1717
+ mirrorForbidden = true;
1718
+ break;
1719
+ }
1720
+ }
1721
+ if (!mirrorForbidden) {
1722
+ this.mirror = new mirror_1.Mirror({
1723
+ adapter: this,
1724
+ log: this.log,
1725
+ diskRoot: this.config.mirrorPath,
1726
+ });
1727
+ }
1728
+ }
1729
+ }
1730
+ // Check setState counter per minute and stop a script if too high
1731
+ this.setStateCountCheckInterval = setInterval(() => {
1732
+ for (const id of Object.keys(this.scripts)) {
1733
+ if (!this.scripts[id]) {
1734
+ continue;
1735
+ }
1736
+ const currentSetStatePerMinuteCounter = this.scripts[id].setStatePerMinuteCounter;
1737
+ this.scripts[id].setStatePerMinuteCounter = 0;
1738
+ if (currentSetStatePerMinuteCounter > this.config.maxSetStatePerMinute) {
1739
+ this.scripts[id].setStatePerMinuteProblemCounter++;
1740
+ this.log.debug(`${id}: Script has reached the maximum of ${this.config.maxSetStatePerMinute} setState calls per minute in ${this.scripts[id].setStatePerMinuteProblemCounter} consecutive minutes`);
1741
+ // Allow "too high counters" for 1 minute for script starts or such and only
1742
+ // stop the script when lasts longer
1743
+ if (this.scripts[id].setStatePerMinuteProblemCounter > 1) {
1744
+ this.log.error(`${id}: Script is calling setState more than ${this.config.maxSetStatePerMinute} times per minute! Stopping Script now! Please check your script!`);
1745
+ void this.stopScript(id);
1746
+ }
1747
+ }
1748
+ else if (this.scripts[id].setStatePerMinuteProblemCounter > 0) {
1749
+ this.scripts[id].setStatePerMinuteProblemCounter--;
1750
+ 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}`);
1751
+ }
1752
+ }
1753
+ }, 60_000).unref();
1754
+ }
1755
+ loadTypeScriptDeclarations() {
1756
+ // try to load the typings on disk for all 3rd party modules
1757
+ const packages = [
1758
+ 'node', // this provides auto-completion for most builtins
1759
+ '@iobroker/types', // this provides auto-completion for most builtins
1760
+ ];
1761
+ // Also include user-selected libraries (but only those that are also installed)
1762
+ if (typeof this.config?.libraries === 'string' && typeof this.config.libraryTypings === 'string') {
1763
+ const installedLibs = this.config.libraries
1764
+ .split(/[,;\s]+/)
1765
+ .map(s => s.trim().split('@')[0])
1766
+ .filter(s => !!s);
1767
+ const wantsTypings = this.config.libraryTypings
1768
+ .split(/[,;\s]+/)
1769
+ .map(s => s.trim())
1770
+ .filter(s => !!s);
1771
+ // O(1) lookups – avoids O(n²) Array.includes inside loops
1772
+ const installedSet = new Set(installedLibs);
1773
+ const wantsSet = new Set(wantsTypings);
1774
+ const packagesSet = new Set(packages);
1775
+ // Add all installed libraries the user has requested typings for to the list of packages
1776
+ for (const lib of installedLibs) {
1777
+ if (wantsSet.has(lib) && !packagesSet.has(lib)) {
1778
+ packages.push(lib);
1779
+ packagesSet.add(lib);
1780
+ }
1781
+ }
1782
+ // Some packages have submodules (e.g., rxjs/operators) that are not exposed through the main entry point
1783
+ // If typings are requested for them, also add them if the base module is installed
1784
+ for (const lib of wantsTypings) {
1785
+ // Extract the package name and check if we need to add it
1786
+ if (!lib.includes('/')) {
1787
+ continue;
1788
+ }
1789
+ const pkgName = lib.substring(0, lib.indexOf('/'));
1790
+ if (installedSet.has(pkgName) && !packagesSet.has(lib)) {
1791
+ packages.push(lib);
1792
+ packagesSet.add(lib);
1793
+ }
1794
+ }
1795
+ }
1796
+ for (const pkg of packages) {
1797
+ let pkgTypings = (0, typescriptTools_1.resolveTypings)(pkg, this.getAdapterScopedPackageIdentifier ? this.getAdapterScopedPackageIdentifier(pkg) : pkg,
1798
+ // node needs ambient typings, so we don't wrap it in declare module
1799
+ pkg !== 'node');
1800
+ if (!pkgTypings) {
1801
+ // Create the empty dummy declarations so users don't get the "not found" error
1802
+ // for installed packages
1803
+ if (pkg.includes('/')) {
1804
+ pkgTypings = {
1805
+ [`node_modules/${pkg}/index.d.ts`]: `declare module "${pkg}";`,
1806
+ };
1807
+ }
1808
+ else {
1809
+ pkgTypings = {
1810
+ [`node_modules/@types/${pkg}/index.d.ts`]: `declare module "${pkg}";`,
1811
+ };
1812
+ }
1813
+ }
1814
+ this.log.debug(`Loaded TypeScript definitions for "${pkg}": ${JSON.stringify(Object.keys(pkgTypings))}`);
1815
+ // remember the declarations for the editor
1816
+ Object.assign(tsAmbient, pkgTypings);
1817
+ // and give the language servers access to them
1818
+ this.tsServer.provideAmbientDeclarations(pkgTypings);
1819
+ jsDeclarationServer.provideAmbientDeclarations(pkgTypings);
1820
+ }
1821
+ }
1822
+ updateObjectContext(id, obj) {
1823
+ if (obj) {
1824
+ // add state to state ID's list
1825
+ if (obj.type === 'state') {
1826
+ if (!this.stateIdSet.has(id)) {
1827
+ this.sortedInsert(id);
1828
+ this.stateIdSet.add(id);
1829
+ }
1830
+ if (this.context.devices && this.context.channels) {
1831
+ const parts = id.split('.');
1832
+ parts.pop();
1833
+ const chn = parts.join('.');
1834
+ this.context.channels[chn] ||= new Set();
1835
+ this.context.channels[chn].add(id);
1836
+ parts.pop();
1837
+ const dev = parts.join('.');
1838
+ this.context.devices[dev] ||= new Set();
1839
+ this.context.devices[dev].add(id);
1840
+ }
1841
+ }
1842
+ }
1843
+ else {
1844
+ // delete object from state ID's list
1845
+ const pos = this.binaryIndexOf(this.stateIds, id);
1846
+ if (pos !== -1) {
1847
+ this.stateIds.splice(pos, 1);
1848
+ this.stateIdSet.delete(id);
1849
+ }
1850
+ if (this.context.devices && this.context.channels) {
1851
+ const parts = id.split('.');
1852
+ parts.pop();
1853
+ const chn = parts.join('.');
1854
+ this.context.channels[chn]?.delete(id);
1855
+ parts.pop();
1856
+ const dev = parts.join('.');
1857
+ this.context.devices[dev]?.delete(id);
1858
+ }
1859
+ delete this.folderCreationVerifiedObjects[id];
1860
+ }
1861
+ if (!obj && this.objects[id]) {
1862
+ // objects were deleted
1863
+ this.removeFromNames(id);
1864
+ delete this.objects[id];
1865
+ }
1866
+ else if (obj && !this.objects[id]) {
1867
+ // object was added
1868
+ this.objects[id] = obj;
1869
+ this.addToNames(obj);
1870
+ }
1871
+ else if (obj && this.objects[id].common) {
1872
+ // Object just changed
1873
+ this.objects[id] = obj;
1874
+ const n = this.getName(id);
1875
+ let nn = this.objects[id].common ? this.objects[id].common.name : '';
1876
+ if (nn && typeof nn === 'object') {
1877
+ nn = nn[(0, words_1.getLanguage)()] || nn.en;
1878
+ }
1879
+ if (n !== nn) {
1880
+ if (n) {
1881
+ this.removeFromNames(id);
1882
+ }
1883
+ if (nn) {
1884
+ this.addToNames(obj);
1885
+ }
1886
+ }
1887
+ }
1888
+ }
1889
+ async stopAllScripts() {
1890
+ const scripts = Object.keys(this.scripts);
1891
+ const promises = [];
1892
+ for (let i = 0; i < scripts.length; i++) {
1893
+ promises.push(this.stopScript(scripts[i]));
1894
+ }
1895
+ return Promise.all(promises).then(() => { });
1896
+ }
1897
+ convertBackStringifiedValues(id, state) {
1898
+ if (state &&
1899
+ typeof state.val === 'string' &&
1900
+ this.objects[id]?.common &&
1901
+ (this.objects[id].common.type === 'array' || this.objects[id].common.type === 'object')) {
1902
+ try {
1903
+ state.val = JSON.parse(state.val);
1904
+ }
1905
+ catch (err) {
1906
+ if (id.startsWith('javascript.') || id.startsWith('0_userdata.0')) {
1907
+ this.log.info(`Could not parse value for id "${id}" into ${this.objects[id].common.type}: ${err.toString()}`);
1908
+ }
1909
+ else {
1910
+ this.log.debug(`Could not parse value for id "${id}" into ${this.objects[id].common.type}: ${err.toString()}`);
1911
+ }
1912
+ }
1913
+ }
1914
+ return state;
1915
+ }
1916
+ prepareStateObjectSimple(id, state, isAck) {
1917
+ // otherwise, assume that the given state is the value to be set
1918
+ const oState = { val: state, ack: isAck };
1919
+ return this.prepareStateObject(id, oState);
1920
+ }
1921
+ prepareStateObject(id, state) {
1922
+ let oState;
1923
+ if (state && typeof state === 'object') {
1924
+ oState = state;
1925
+ }
1926
+ else {
1927
+ oState = { val: null };
1928
+ }
1929
+ if (this.config.subscribe) {
1930
+ return oState;
1931
+ }
1932
+ // set other values to have a full state object
1933
+ // mirrors logic from statesInRedis
1934
+ if (oState.ts === undefined) {
1935
+ oState.ts = Date.now();
1936
+ }
1937
+ if (oState.q === undefined) {
1938
+ oState.q = 0;
1939
+ }
1940
+ oState.from =
1941
+ typeof oState.from === 'string' && oState.from !== '' ? oState.from : `system.adapter.${this.namespace}`;
1942
+ if (oState.lc === undefined) {
1943
+ const formerStateValue = this.interimStateValues[id] || this.states[id];
1944
+ if (!formerStateValue) {
1945
+ oState.lc = oState.ts;
1946
+ }
1947
+ else {
1948
+ // isDeepStrictEqual works on objects and primitive values
1949
+ const hasChanged = !(0, node_util_1.isDeepStrictEqual)(formerStateValue.val, oState.val);
1950
+ if (!formerStateValue.lc || hasChanged) {
1951
+ oState.lc = oState.ts;
1952
+ }
1953
+ else {
1954
+ oState.lc = formerStateValue.lc;
1955
+ }
1956
+ }
1957
+ }
1958
+ return oState;
1959
+ }
1960
+ async getData() {
1961
+ await this.subscribeForeignObjectsAsync('*');
1962
+ if (!this.config.subscribe) {
1963
+ await this.subscribeForeignStatesAsync('*');
1964
+ }
1965
+ else {
1966
+ await this.subscribeStatesAsync('debug.to');
1967
+ await this.subscribeStatesAsync('scriptEnabled.*');
1968
+ }
1969
+ this.log.info('requesting all states');
1970
+ const statesPromise = this.getForeignStatesAsync('*')
1971
+ .then(res => {
1972
+ if (!res) {
1973
+ this.log.error(`Could not initialize states: no result`);
1974
+ this.terminate(adapter_core_1.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);
1975
+ return;
1976
+ }
1977
+ if (!this.config.subscribe) {
1978
+ this.states = Object.assign(res, this.states);
1979
+ this.context.states = this.states;
1980
+ this.addGetProperty(this.states);
1981
+ }
1982
+ // remember all IDs – sort once to guarantee the sorted invariant
1983
+ // required by binaryIndexOf() / sortedInsert() used later
1984
+ const keys = Object.keys(res).sort();
1985
+ for (const id of keys) {
1986
+ this.stateIds.push(id);
1987
+ this.stateIdSet.add(id);
1988
+ }
1989
+ this.statesInitDone = true;
1990
+ this.log.info('received all states');
1991
+ })
1992
+ .catch((err) => {
1993
+ this.log.error(`Could not initialize states: ${err?.message || 'no result'}`);
1994
+ this.terminate(adapter_core_1.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);
1995
+ });
1996
+ this.log.info('requesting all objects');
1997
+ const objectsPromise = this.getObjectListAsync({ include_docs: true })
1998
+ .then(res => {
1999
+ if (!res?.rows) {
2000
+ this.log.error(`Could not initialize objects: no result`);
2001
+ this.terminate(adapter_core_1.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);
2002
+ return;
2003
+ }
2004
+ this.objects = {};
2005
+ this.context.objects = this.objects;
2006
+ for (let i = 0; i < res.rows.length; i++) {
2007
+ const doc = res.rows[i]?.doc;
2008
+ if (!doc) {
2009
+ this.log.debug(`Got empty object for index ${i} (${res.rows[i].id})`);
2010
+ continue;
2011
+ }
2012
+ if (this.objects[doc._id] === undefined) {
2013
+ // If was already there, ignore
2014
+ this.objects[doc._id] = doc;
2015
+ }
2016
+ doc.type === 'enum' && this._enums.add(doc._id);
2017
+ // Collect all names
2018
+ this.addToNames(this.objects[doc._id]);
2019
+ }
2020
+ this.addGetProperty(this.objects);
2021
+ const systemConfig = this.objects['system.config'];
2022
+ this.password = systemConfig?.native?.javascriptPassword
2023
+ ? this.decrypt(systemConfig?.native.javascriptPassword)
2024
+ : '';
2025
+ // set language for debug messages
2026
+ if (systemConfig?.common?.language) {
2027
+ (0, words_1.setLanguage)(systemConfig.common.language);
2028
+ this.language = systemConfig.common.language;
2029
+ this.context.language = this.language;
2030
+ }
2031
+ else if (this.language) {
2032
+ (0, words_1.setLanguage)(this.language);
2033
+ this.context.language = this.language;
2034
+ }
2035
+ // try to use system coordinates
2036
+ if (this.config.useSystemGPS) {
2037
+ if (systemConfig?.common?.latitude || systemConfig?.common?.longitude) {
2038
+ this.config.latitude = systemConfig.common.latitude;
2039
+ this.config.longitude = systemConfig.common.longitude;
2040
+ }
2041
+ else if (this.latitude && this.longitude) {
2042
+ this.config.latitude = this.latitude;
2043
+ this.config.longitude = this.longitude;
2044
+ }
2045
+ }
2046
+ this.config.latitude = parseFloat(this.config.latitude);
2047
+ this.config.longitude = parseFloat(this.config.longitude);
2048
+ if (isNaN(this.config.latitude)) {
2049
+ this.log.warn(`Configured latitude is not a number - check (instance/system) configuration`);
2050
+ }
2051
+ else if (this.config.latitude < -90 || this.config.latitude > 90) {
2052
+ this.log.warn(`Configured latitude "${this.config.latitude}" is invalid - check (instance/system) configuration`);
2053
+ }
2054
+ if (isNaN(this.config.longitude)) {
2055
+ this.log.warn(`Configured longitude is not a number - check (instance/system) configuration`);
2056
+ }
2057
+ else if (this.config.longitude < -180 || this.config.longitude > 180) {
2058
+ this.log.warn(`Configured longitude "${this.config.longitude}" is invalid - check (instance/system) configuration`);
2059
+ }
2060
+ this.config.sunriseEvent ||= 'nightEnd';
2061
+ this.config.sunriseOffset ||= 0;
2062
+ this.config.sunriseLimitStart ||= '06:00';
2063
+ this.config.sunriseLimitEnd ||= '12:00';
2064
+ this.config.sunsetEvent ||= 'dusk';
2065
+ this.config.sunsetOffset ||= 0;
2066
+ this.config.sunsetLimitStart ||= '18:00';
2067
+ this.config.sunsetLimitEnd ||= '23:00';
2068
+ this.objectsInitDone = true;
2069
+ this.log.info('received all objects');
2070
+ })
2071
+ .catch((err) => {
2072
+ this.log.error(`Could not initialize objects: ${err?.message || 'no result'}`);
2073
+ this.terminate(adapter_core_1.EXIT_CODES.START_IMMEDIATELY_AFTER_STOP);
2074
+ });
2075
+ return Promise.all([statesPromise, objectsPromise]).then(() => { });
2076
+ }
2077
+ async createActiveObject(id, enabled) {
2078
+ const idActive = `${this.namespace}.scriptEnabled.${id.substring(SCRIPT_CODE_MARKER.length)}`;
2079
+ if (!this.objects[idActive]) {
2080
+ this.objects[idActive] = {
2081
+ _id: idActive,
2082
+ common: {
2083
+ name: `scriptEnabled.${id.substring(SCRIPT_CODE_MARKER.length)}`,
2084
+ desc: 'controls script activity',
2085
+ type: 'boolean',
2086
+ write: true,
2087
+ read: true,
2088
+ role: 'switch.active',
2089
+ },
2090
+ native: {
2091
+ script: id,
2092
+ },
2093
+ type: 'state',
2094
+ };
2095
+ try {
2096
+ await this.setForeignObjectAsync(idActive, this.objects[idActive]);
2097
+ const intermediateStateValue = this.prepareStateObjectSimple(idActive, enabled, true);
2098
+ await this.setForeignStateAsync(idActive, enabled, true);
2099
+ if (enabled && !this.config.subscribe) {
2100
+ this.interimStateValues[idActive] = intermediateStateValue;
2101
+ }
2102
+ }
2103
+ catch {
2104
+ // ignore
2105
+ }
2106
+ }
2107
+ else {
2108
+ const state = await this.getForeignStateAsync(idActive);
2109
+ if (state && state.val !== enabled) {
2110
+ const intermediateStateValue = this.prepareStateObjectSimple(idActive, enabled, true);
2111
+ await this.setForeignStateAsync(idActive, enabled, true);
2112
+ if (enabled && !this.config.subscribe) {
2113
+ this.interimStateValues[idActive] = intermediateStateValue;
2114
+ }
2115
+ }
2116
+ }
2117
+ }
2118
+ async createProblemObject(id) {
2119
+ const idProblem = `${this.namespace}.scriptProblem.${id.substring(SCRIPT_CODE_MARKER.length)}`;
2120
+ if (!this.objects[idProblem]) {
2121
+ this.objects[idProblem] = {
2122
+ _id: idProblem,
2123
+ common: {
2124
+ name: `scriptProblem.${id.substring(SCRIPT_CODE_MARKER.length)}`,
2125
+ desc: 'Script has a problem',
2126
+ type: 'boolean',
2127
+ expert: true,
2128
+ write: false,
2129
+ read: true,
2130
+ role: 'indicator.error',
2131
+ },
2132
+ native: {
2133
+ script: id,
2134
+ },
2135
+ type: 'state',
2136
+ };
2137
+ try {
2138
+ await this.setForeignObjectAsync(idProblem, this.objects[idProblem]);
2139
+ await this.setForeignStateAsync(idProblem, false, true);
2140
+ }
2141
+ catch {
2142
+ // ignore
2143
+ }
2144
+ }
2145
+ else {
2146
+ const state = await this.getForeignStateAsync(idProblem);
2147
+ if (state && state.val !== false) {
2148
+ await this.setForeignStateAsync(idProblem, false, true);
2149
+ }
2150
+ }
2151
+ }
2152
+ addToNames(obj) {
2153
+ const id = obj._id;
2154
+ if (obj.common?.name) {
2155
+ let name = obj.common.name;
2156
+ if (name && typeof name === 'object') {
2157
+ name = name[(0, words_1.getLanguage)()] || name.en;
2158
+ }
2159
+ if (!name || typeof name !== 'string') {
2160
+ // TODO, take name in current language
2161
+ return;
2162
+ }
2163
+ if (!this.names[name]) {
2164
+ this.names[name] = id;
2165
+ }
2166
+ else {
2167
+ // convert to array
2168
+ if (!Array.isArray(this.names[name])) {
2169
+ this.names[name] = [this.names[name]];
2170
+ }
2171
+ this.names[name].push(id);
2172
+ }
2173
+ // keep reverse-map up to date for O(1) getName()
2174
+ this.nameById.set(id, name);
2175
+ }
2176
+ }
2177
+ removeFromNames(id) {
2178
+ const n = this.getName(id);
2179
+ if (n) {
2180
+ if (Array.isArray(this.names[n])) {
2181
+ const arr = this.names[n];
2182
+ const pos = arr.indexOf(id);
2183
+ if (pos > -1) {
2184
+ arr.splice(pos, 1);
2185
+ if (arr.length === 1) {
2186
+ this.names[n] = arr[0];
2187
+ }
2188
+ }
2189
+ }
2190
+ else {
2191
+ delete this.names[n];
2192
+ }
2193
+ // keep reverse-map up to date for O(1) getName()
2194
+ this.nameById.delete(id);
2195
+ }
2196
+ }
2197
+ getName(id) {
2198
+ return this.nameById.get(id) ?? null;
2199
+ }
2200
+ async installNpm(npmLib) {
2201
+ return new Promise((resolve, reject) => {
2202
+ const path = __dirname;
2203
+ // Also, set the working directory (cwd) of the process instead of using --prefix
2204
+ // because that has ugly bugs on Windows
2205
+ const cmd = `npm install ${npmLib} --omit=dev`;
2206
+ this.log.info(`Installing ${npmLib} into ${__dirname} - cmd: ${cmd}`);
2207
+ // System call used for update of js-controller itself,
2208
+ // because during the installation the npm packet will be deleted too, but some files must be loaded even during the installation process.
2209
+ const child = this.mods.child_process.exec(cmd, {
2210
+ windowsHide: true,
2211
+ cwd: path,
2212
+ timeout: 120_000, // 2 minutes max – prevents infinite blocking
2213
+ });
2214
+ child.stdout?.on('data', buf => this.log.info(buf.toString('utf8')));
2215
+ child.stderr?.on('data', buf => this.log.error(buf.toString('utf8')));
2216
+ child.on('err', err => {
2217
+ this.log.error(`Cannot install ${npmLib}: ${err}`);
2218
+ reject(new Error(`Cannot install ${npmLib}: ${err}`));
2219
+ });
2220
+ child.on('error', err => {
2221
+ this.log.error(`Cannot install ${npmLib}: ${err}`);
2222
+ reject(new Error(`Cannot install ${npmLib}: ${err}`));
2223
+ });
2224
+ child.on('exit', (code /* , signal */) => {
2225
+ if (code) {
2226
+ this.log.error(`Cannot install ${npmLib}: ${code}`);
2227
+ reject(new Error(`Cannot install ${npmLib}: ${code}`));
2228
+ }
2229
+ // command succeeded
2230
+ resolve(code);
2231
+ });
2232
+ });
2233
+ }
2234
+ async installLibraries() {
2235
+ if (typeof this.config?.libraries !== 'string') {
2236
+ this.config.libraries = '';
2237
+ }
2238
+ const libraries = this.config.libraries
2239
+ .split(/[,;\s]+/)
2240
+ .map(d => d.trim())
2241
+ .filter(d => d);
2242
+ this.log.debug(`Custom libraries in config: "${this.config.libraries}": ${JSON.stringify(libraries)}`);
2243
+ let installedNodeModules = [];
2244
+ const keepModules = [];
2245
+ // js-controller >= 6.x
2246
+ if (typeof this.listInstalledNodeModules === 'function') {
2247
+ installedNodeModules = await this.listInstalledNodeModules();
2248
+ this.log.debug(`Found installed libraries: ${JSON.stringify(installedNodeModules)}`);
2249
+ }
2250
+ for (const lib of libraries) {
2251
+ let depName = lib;
2252
+ let version = 'latest';
2253
+ if (depName.includes('@') && depName.lastIndexOf('@') > 0) {
2254
+ const parts = depName.split('@');
2255
+ version = parts.pop() ?? 'latest';
2256
+ depName = parts.join('@');
2257
+ }
2258
+ /** The real module name, because the dependency can be a URL too */
2259
+ let moduleName = depName;
2260
+ if (URL.canParse(depName)) {
2261
+ moduleName = await (0, nodeModulesManagement_1.requestModuleNameByUrl)(depName);
2262
+ this.log.debug(`Found custom library in config: "${moduleName}@${version}" (from ${depName})`);
2263
+ }
2264
+ else {
2265
+ this.log.debug(`Found custom library in config: "${moduleName}@${version}"`);
2266
+ }
2267
+ keepModules.push(moduleName);
2268
+ // js-controller >= 6.x
2269
+ if (typeof this.installNodeModule === 'function') {
2270
+ try {
2271
+ const result = await this.installNodeModule(depName, { version });
2272
+ if (result.success) {
2273
+ this.log.debug(`Installed custom library: "${moduleName}@${version}"`);
2274
+ const importedModule = await this.importNodeModule(moduleName);
2275
+ this.mods[moduleName] = importedModule.default ?? importedModule;
2276
+ }
2277
+ else {
2278
+ this.log.warn(`Cannot install custom npm package "${moduleName}@${version}"`);
2279
+ }
2280
+ }
2281
+ catch (err) {
2282
+ this.log.warn(`Cannot install custom npm package "${moduleName}@${version}": ${err}`);
2283
+ }
2284
+ }
2285
+ else if (!(0, node_fs_1.existsSync)(`${__dirname}/node_modules/${depName}/package.json`)) {
2286
+ // js-controller < 6.x
2287
+ this.log.info(`Installing custom library (legacy mode): "${lib}"`);
2288
+ try {
2289
+ await this.installNpm(lib);
2290
+ this.log.info(`Installed custom npm package (legacy mode): "${lib}"`);
2291
+ }
2292
+ catch (err) {
2293
+ this.log.warn(`Cannot install custom npm package "${lib}" (legacy mode): ${err.toString()}`);
2294
+ }
2295
+ }
2296
+ }
2297
+ // js-controller >= 6.x
2298
+ if (typeof this.uninstallNodeModule === 'function') {
2299
+ for (const installedNodeModule of installedNodeModules) {
2300
+ if (!keepModules.includes(installedNodeModule)) {
2301
+ try {
2302
+ await this.uninstallNodeModule(installedNodeModule);
2303
+ this.log.info(`Removed custom npm package: "${installedNodeModule}"`);
2304
+ }
2305
+ catch (err) {
2306
+ this.log.warn(`Cannot remove custom npm package ${installedNodeModule}: ${err.toString()}`);
2307
+ }
2308
+ }
2309
+ }
2310
+ }
2311
+ }
2312
+ createVM(source, name, wrapAsync) {
2313
+ if (this.context.debugMode && name !== this.context.debugMode) {
2314
+ return false;
2315
+ }
2316
+ if (!this.context.debugMode) {
2317
+ const logSubscriptionsText = "\n;\nlog(`registered ${__engine.__subscriptions} subscription${__engine.__subscriptions === 1 ? '' : 's'}," +
2318
+ " ${__engine.__schedules} schedule${__engine.__schedules === 1 ? '' : 's'}," +
2319
+ " ${__engine.__subscriptionsMessage} message${__engine.__subscriptionsMessage === 1 ? '' : 's'}," +
2320
+ " ${__engine.__subscriptionsLog} log${__engine.__subscriptionsLog === 1 ? '' : 's'}" +
2321
+ " and ${__engine.__subscriptionsFile} file subscription${__engine.__subscriptionsFile === 1 ? '' : 's'}`);\n";
2322
+ if (wrapAsync) {
2323
+ source = `(async () => {\n${source}\n${logSubscriptionsText}\n})();`;
2324
+ }
2325
+ else {
2326
+ if (source.endsWith('export {};\n')) {
2327
+ // If the source ends with "export {};" place the log subscriptions before it
2328
+ source = source.slice(0, -11); // remove "export {};\n"
2329
+ source = `${source}\n${logSubscriptionsText}\nexport {};\n`;
2330
+ }
2331
+ else {
2332
+ source = `${source}\n${logSubscriptionsText}`;
2333
+ }
2334
+ }
2335
+ }
2336
+ else {
2337
+ if (wrapAsync) {
2338
+ source = `(async () => {debugger;\n${source}\n})();`;
2339
+ }
2340
+ else {
2341
+ source = `debugger;${source}`;
2342
+ }
2343
+ }
2344
+ try {
2345
+ const options = {
2346
+ filename: name,
2347
+ // displayErrors: true,
2348
+ // lineOffset: this.globalScriptLines
2349
+ };
2350
+ return {
2351
+ script: new node_vm_1.Script(source, options),
2352
+ };
2353
+ }
2354
+ catch (err) {
2355
+ this.logError(name, `compile failed at`, err);
2356
+ return false;
2357
+ }
2358
+ }
2359
+ execute(script, name, engineType, verbose, debug,
2360
+ /**
2361
+ * Optional sink for the "execute" message API. When provided, the script runs in an
2362
+ * ephemeral diagnostic mode: every log line (the script's own `log()`/`console.*` output
2363
+ * AND all verbose internal operations) is forwarded to this collector instead of the
2364
+ * adapter log, and no `scriptProblem` state is written.
2365
+ */
2366
+ logCollector) {
2367
+ script.intervals = new Set();
2368
+ script.timeouts = new Set();
2369
+ script.schedules = [];
2370
+ script.wizards = [];
2371
+ script.name = name;
2372
+ script.engineType = engineType;
2373
+ script._id = Math.floor(Math.random() * 0xffffffff);
2374
+ script.subscribes = {};
2375
+ script.subscribesFile = {};
2376
+ script.setStatePerMinuteCounter = 0;
2377
+ script.setStatePerMinuteProblemCounter = 0;
2378
+ if (!logCollector) {
2379
+ void this.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
2380
+ val: false,
2381
+ ack: true,
2382
+ expire: 1000,
2383
+ });
2384
+ }
2385
+ const sandbox = (0, sandbox_1.sandBox)(script, name, verbose, debug, this.context, logCollector);
2386
+ try {
2387
+ script.script.runInNewContext(sandbox, {
2388
+ filename: name,
2389
+ displayErrors: true,
2390
+ // lineOffset: this.globalScriptLines
2391
+ });
2392
+ }
2393
+ catch (err) {
2394
+ if (logCollector) {
2395
+ const e = err;
2396
+ const stack = (e?.stack ? e.stack.toString() : String(err))
2397
+ .split('\n')
2398
+ .map(line => this.fixLineNo(line))
2399
+ .join('\n');
2400
+ logCollector('error', `Error by run: ${stack}`);
2401
+ }
2402
+ else {
2403
+ void this.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
2404
+ val: true,
2405
+ ack: true,
2406
+ c: 'execute',
2407
+ });
2408
+ this.logError(name, 'Error by run:', err);
2409
+ }
2410
+ }
2411
+ }
2412
+ /**
2413
+ * Run an ad-hoc script sent via the `execute` message and return everything it logged.
2414
+ *
2415
+ * The script is compiled (JavaScript or TypeScript), executed with the same sandbox API as a
2416
+ * regular script (verbose by default, so internal operations like setState/subscribe are logged
2417
+ * too), left running for `timeout` ms to collect asynchronous output, and afterwards stopped and
2418
+ * fully cleaned up (timers, subscriptions, schedules). It is ephemeral: no script object or
2419
+ * states are created.
2420
+ *
2421
+ * Expected `message`:
2422
+ * - `source` / `code` (string, required) – the script source
2423
+ * - `engineType` (string, optional) – `TypeScript/ts` to compile as TypeScript, otherwise JavaScript
2424
+ * - `verbose` (boolean, optional, default `true`) – log internal sandbox operations
2425
+ * - `logLevel` (silly|debug|info|warn|error, optional, default `silly`) – minimum severity to return
2426
+ * - `timeout` (number ms, optional, default 5000, clamped to 0…60000) – collection window
2427
+ * - `maxLogs` (number, optional, default 5000) – cap on returned log lines
2428
+ */
2429
+ async executeScript(message) {
2430
+ const LEVELS = ['silly', 'debug', 'info', 'warn', 'error'];
2431
+ const source = message?.source ?? message?.code;
2432
+ const engineTypeStr = (message?.engineType || '').toString().toLowerCase();
2433
+ const isTypeScript = engineTypeStr.startsWith('typescript') || engineTypeStr === 'ts';
2434
+ const engineType = isTypeScript ? 'TypeScript/ts' : 'Javascript/js';
2435
+ const empty = (error) => ({ ok: false, error, engineType, runtime: 0, truncated: false, logs: [], output: '' });
2436
+ if (typeof source !== 'string' || !source.trim()) {
2437
+ return empty('No source code provided');
2438
+ }
2439
+ if (this.context.debugMode) {
2440
+ return empty('Cannot execute a script while a debug session is active');
2441
+ }
2442
+ let timeout = parseInt(message?.timeout, 10);
2443
+ if (isNaN(timeout)) {
2444
+ timeout = 5000;
2445
+ }
2446
+ timeout = Math.max(0, Math.min(timeout, 60000));
2447
+ const verbose = message?.verbose !== false;
2448
+ const minLevel = message?.logLevel
2449
+ ? LEVELS.includes(message?.logLevel)
2450
+ ? message.logLevel
2451
+ : 'silly'
2452
+ : 'silly';
2453
+ let maxLogs = parseInt(message?.maxLogs, 10);
2454
+ if (isNaN(maxLogs) || maxLogs <= 0) {
2455
+ maxLogs = 5000;
2456
+ }
2457
+ const name = `${SCRIPT_CODE_MARKER}__execute_${++this.executeCounter}`;
2458
+ // Compile the source the same way regular scripts are compiled
2459
+ let createdScript;
2460
+ if (isTypeScript) {
2461
+ const transformedSource = (0, typescriptTools_1.transformScriptBeforeCompilation)(source, false);
2462
+ const filename = (0, typescriptTools_1.scriptIdToTSFilename)(name);
2463
+ let tsCompiled;
2464
+ try {
2465
+ tsCompiled = this.tsServer.compile(filename, transformedSource);
2466
+ }
2467
+ catch (err) {
2468
+ return empty(`TypeScript compilation failed: ${err}`);
2469
+ }
2470
+ if (!tsCompiled.success) {
2471
+ const errors = tsCompiled.diagnostics.map(diag => diag.annotatedSource).join('\n');
2472
+ return empty(`TypeScript compilation failed:\n${errors}`);
2473
+ }
2474
+ createdScript = this.createVM(`${this.globalScript}\n${tsCompiled.result || ''}`, name, false);
2475
+ }
2476
+ else {
2477
+ createdScript = this.createVM(`${this.globalScript}\n${source}`, name, true);
2478
+ }
2479
+ if (!createdScript) {
2480
+ return empty('Compilation failed');
2481
+ }
2482
+ const logs = [];
2483
+ let truncated = false;
2484
+ const collector = (severity, msg) => {
2485
+ if (logs.length >= maxLogs) {
2486
+ truncated = true;
2487
+ return;
2488
+ }
2489
+ logs.push({ ts: Date.now(), severity, message: msg });
2490
+ };
2491
+ this.logCollectors.push({ name, collector });
2492
+ this.updateLogSubscriptions();
2493
+ this.scripts[name] = createdScript;
2494
+ this.execute(createdScript, name, engineType, verbose, false, collector);
2495
+ // Let asynchronous output (timeouts, awaited code, triggered subscriptions) accumulate
2496
+ if (timeout) {
2497
+ await new Promise(resolve => setTimeout(resolve, timeout));
2498
+ }
2499
+ // Stop and clean up the ephemeral script (timers, subscriptions, schedules, …)
2500
+ await this.stopScript(name, true);
2501
+ const pos = this.logCollectors.findIndex(it => it.name === name);
2502
+ if (pos !== -1) {
2503
+ this.logCollectors.splice(pos, 1);
2504
+ }
2505
+ this.updateLogSubscriptions();
2506
+ const minIdx = LEVELS.indexOf(minLevel);
2507
+ const filtered = logs.filter(entry => {
2508
+ const idx = LEVELS.indexOf(entry.severity);
2509
+ return idx < 0 || idx >= minIdx;
2510
+ });
2511
+ return {
2512
+ ok: true,
2513
+ engineType,
2514
+ runtime: timeout,
2515
+ truncated,
2516
+ logs: filtered,
2517
+ output: filtered.map(entry => `[${entry.severity}] ${entry.message}`).join('\n'),
2518
+ };
2519
+ }
2520
+ /**
2521
+ * Finds the index of `id` in a sorted array using binary search – O(log n).
2522
+ * Returns -1 if not found. Used instead of Array.indexOf on stateIds.
2523
+ */
2524
+ binaryIndexOf(arr, id) {
2525
+ let lo = 0;
2526
+ let hi = arr.length - 1;
2527
+ while (lo <= hi) {
2528
+ const mid = (lo + hi) >>> 1;
2529
+ if (arr[mid] === id) {
2530
+ return mid;
2531
+ }
2532
+ else if (arr[mid] < id) {
2533
+ lo = mid + 1;
2534
+ }
2535
+ else {
2536
+ hi = mid - 1;
2537
+ }
2538
+ }
2539
+ return -1;
2540
+ }
2541
+ /**
2542
+ * Inserts `id` into the sorted `stateIds` array using binary search – O(log n).
2543
+ * Much faster than push() + sort() which is O(n log n) on every insertion.
2544
+ */
2545
+ sortedInsert(id) {
2546
+ let lo = 0;
2547
+ let hi = this.stateIds.length;
2548
+ while (lo < hi) {
2549
+ const mid = (lo + hi) >>> 1;
2550
+ if (this.stateIds[mid] < id) {
2551
+ lo = mid + 1;
2552
+ }
2553
+ else {
2554
+ hi = mid;
2555
+ }
2556
+ }
2557
+ if (this.stateIds[lo] !== id) {
2558
+ this.stateIds.splice(lo, 0, id);
2559
+ }
2560
+ }
2561
+ unsubscribe(id) {
2562
+ if (!id) {
2563
+ this.log.warn('unsubscribe: empty name');
2564
+ return;
2565
+ }
2566
+ if (Array.isArray(id)) {
2567
+ id.forEach(sub => this.unsubscribe(sub));
2568
+ return;
2569
+ }
2570
+ if (id.constructor && id.constructor.name === 'RegExp') {
2571
+ // adapter.log.warn('unsubscribe: todo - process regexp');
2572
+ return;
2573
+ }
2574
+ if (typeof id !== 'string') {
2575
+ this.log.error(`unsubscribe: invalid type of id - ${typeof id}`);
2576
+ return;
2577
+ }
2578
+ const parts = id.split('.');
2579
+ const _adapter = `system.adapter.${parts[0]}.${parts[1]}`;
2580
+ if (this.objects[_adapter]?.common?.subscribable) {
2581
+ const a = `${parts[0]}.${parts[1]}`;
2582
+ const alive = `system.adapter.${a}.alive`;
2583
+ if (this.adapterSubs[alive]) {
2584
+ this.adapterSubs[alive].delete(id);
2585
+ if (!this.adapterSubs[alive].size) {
2586
+ delete this.adapterSubs[alive];
2587
+ }
2588
+ }
2589
+ this.sendTo(a, 'unsubscribe', id);
2590
+ }
2591
+ }
2592
+ // Analyze if logs are still required or not
2593
+ updateLogSubscriptions() {
2594
+ let found = '';
2595
+ if (this.logCollectors.length) {
2596
+ found = this.logCollectors[0].name;
2597
+ }
2598
+ else {
2599
+ // go through all scripts and check if some script still requires logs
2600
+ Object.keys(this.logSubscriptions).forEach(scriptName => {
2601
+ if (!this.logSubscriptions?.[scriptName] || !this.logSubscriptions[scriptName].length) {
2602
+ delete this.logSubscriptions[scriptName];
2603
+ }
2604
+ else {
2605
+ found = scriptName;
2606
+ }
2607
+ });
2608
+ }
2609
+ if (this.requireLog) {
2610
+ if (found && !this.logSubscribed) {
2611
+ this.logSubscribed = true;
2612
+ void this.requireLog(this.logSubscribed);
2613
+ this.log.info(`Subscribed to log messages (at least because of ${found})`);
2614
+ }
2615
+ else if (!found && this.logSubscribed) {
2616
+ this.logSubscribed = false;
2617
+ void this.requireLog(this.logSubscribed);
2618
+ this.log.info(`Unsubscribed from log messages (not found any subscribers)`);
2619
+ }
2620
+ }
2621
+ }
2622
+ async stopScript(name, silent) {
2623
+ if (!this.scripts[name]) {
2624
+ return false;
2625
+ }
2626
+ // `silent` is used for ephemeral scripts started via the "execute" message – they have no
2627
+ // `scriptEnabled` state and should not appear in the adapter log.
2628
+ if (!silent) {
2629
+ this.log.info(`${name}: Stopping script`);
2630
+ await this.setState(`scriptEnabled.${name.substring(SCRIPT_CODE_MARKER.length)}`, false, true);
2631
+ }
2632
+ if (this.messageBusHandlers[name]) {
2633
+ delete this.messageBusHandlers[name];
2634
+ }
2635
+ if (this.tempDirectories[name]) {
2636
+ try {
2637
+ this.mods.fs.rmSync(this.tempDirectories[name], { recursive: true });
2638
+ this.log.debug(`${name}: Removed temp directory: ${this.tempDirectories[name]}`);
2639
+ }
2640
+ catch {
2641
+ this.log.warn(`${name}: Unable to remove temp directory: ${this.tempDirectories[name]}`);
2642
+ }
2643
+ delete this.tempDirectories[name];
2644
+ }
2645
+ if (this.logSubscriptions[name]) {
2646
+ delete this.logSubscriptions[name];
2647
+ this.updateLogSubscriptions();
2648
+ }
2649
+ if (this.scripts[name]) {
2650
+ // Remove from subscriptions
2651
+ this.context.isEnums = false;
2652
+ if (this.config.subscribe) {
2653
+ // check all subscribed IDs
2654
+ Object.keys(this.scripts[name].subscribes).forEach(id => {
2655
+ if (this.subscribedPatterns[id]) {
2656
+ this.subscribedPatterns[id] -= this.scripts[name].subscribes[id];
2657
+ if (this.subscribedPatterns[id] <= 0) {
2658
+ this.unsubscribeForeignStates(id);
2659
+ delete this.subscribedPatterns[id];
2660
+ if (this.states[id]) {
2661
+ delete this.states[id];
2662
+ }
2663
+ }
2664
+ }
2665
+ });
2666
+ }
2667
+ for (let i = this.subscriptions.length - 1; i >= 0; i--) {
2668
+ if (this.subscriptions[i].name === name) {
2669
+ const sub = this.subscriptions.splice(i, 1)[0];
2670
+ // Also remove from the O(1) dispatch structures – shared helper to keep the
2671
+ // exact-id classification identical to the subscribe side in sandbox.ts
2672
+ if (sub) {
2673
+ (0, sandbox_1.removeFromDispatchIndex)(this.context, sub);
2674
+ }
2675
+ if (sub?.pattern.id) {
2676
+ this.unsubscribe(sub.pattern.id);
2677
+ }
2678
+ }
2679
+ else {
2680
+ if ((!this.context.isEnums && this.subscriptions[i].pattern.enumName) ||
2681
+ this.subscriptions[i].pattern.enumId) {
2682
+ this.context.isEnums = true;
2683
+ }
2684
+ }
2685
+ }
2686
+ // check all subscribed files
2687
+ Object.keys(this.scripts[name].subscribesFile).forEach(key => {
2688
+ if (this.subscribedPatternsFile[key]) {
2689
+ this.subscribedPatternsFile[key] -= this.scripts[name].subscribesFile[key];
2690
+ if (this.subscribedPatternsFile[key] <= 0) {
2691
+ const [id, file] = key.split('$%$');
2692
+ void this.unsubscribeForeignFiles(id, file);
2693
+ delete this.subscribedPatternsFile[key];
2694
+ }
2695
+ }
2696
+ });
2697
+ for (let i = this.subscriptionsFile.length - 1; i >= 0; i--) {
2698
+ if (this.subscriptionsFile[i].name === name) {
2699
+ this.subscriptionsFile.splice(i, 1);
2700
+ }
2701
+ }
2702
+ for (let i = this.subscriptionsObject.length - 1; i >= 0; i--) {
2703
+ if (this.subscriptionsObject[i].name === name) {
2704
+ const sub = this.subscriptionsObject.splice(i, 1)[0];
2705
+ if (sub) {
2706
+ // Remove from O(1) dispatch map
2707
+ const mapSubs = this.subscriptionsObjectMap.get(sub.pattern);
2708
+ if (mapSubs) {
2709
+ const pos = mapSubs.indexOf(sub);
2710
+ if (pos !== -1) {
2711
+ mapSubs.splice(pos, 1);
2712
+ }
2713
+ if (!mapSubs.length) {
2714
+ this.subscriptionsObjectMap.delete(sub.pattern);
2715
+ }
2716
+ }
2717
+ this.unsubscribeForeignObjects(sub.pattern);
2718
+ }
2719
+ }
2720
+ }
2721
+ // Stop all timeouts
2722
+ for (const t of this.scripts[name].timeouts) {
2723
+ clearTimeout(t);
2724
+ }
2725
+ // Stop all intervals
2726
+ for (const t of this.scripts[name].intervals) {
2727
+ clearInterval(t);
2728
+ }
2729
+ // Stop all delayed states (setStateDelayed timers) – O(1) via reverse-index
2730
+ const scriptStateIds = this.timersByScript.get(name);
2731
+ if (scriptStateIds) {
2732
+ for (const stateId of scriptStateIds) {
2733
+ if (this.timers[stateId]) {
2734
+ for (let i = this.timers[stateId].length - 1; i >= 0; i--) {
2735
+ if (this.timers[stateId][i].scriptName === name) {
2736
+ clearTimeout(this.timers[stateId][i].t);
2737
+ this.timers[stateId].splice(i, 1);
2738
+ }
2739
+ }
2740
+ if (!this.timers[stateId].length) {
2741
+ delete this.timers[stateId];
2742
+ }
2743
+ }
2744
+ }
2745
+ this.timersByScript.delete(name);
2746
+ }
2747
+ // Stop all scheduled jobs
2748
+ for (let i = 0; i < this.scripts[name].schedules.length; i++) {
2749
+ if (this.scripts[name].schedules[i]) {
2750
+ const _name = this.scripts[name].schedules[i].name;
2751
+ if (!this.mods.nodeSchedule.cancelJob(this.scripts[name].schedules[i])) {
2752
+ this.log.error(`${name}: Error by canceling scheduled job "${_name}"`);
2753
+ }
2754
+ }
2755
+ }
2756
+ // Stop all time wizards jobs
2757
+ if (this.context.scheduler) {
2758
+ for (let i = 0; i < this.scripts[name].wizards.length; i++) {
2759
+ if (this.scripts[name].wizards[i]) {
2760
+ this.context.scheduler.remove(this.scripts[name].wizards[i]);
2761
+ }
2762
+ }
2763
+ }
2764
+ // if callback for on stop
2765
+ if (typeof this.scripts[name].onStopCb === 'function') {
2766
+ this.scripts[name].onStopTimeout =
2767
+ parseInt(this.scripts[name].onStopTimeout, 10) || 1000;
2768
+ await new Promise(resolve => {
2769
+ let timeout = setTimeout(() => {
2770
+ if (timeout) {
2771
+ timeout = null;
2772
+ resolve(true);
2773
+ }
2774
+ }, this.scripts[name].onStopTimeout);
2775
+ try {
2776
+ this.scripts[name].onStopCb(() => {
2777
+ if (timeout) {
2778
+ clearTimeout(timeout);
2779
+ timeout = null;
2780
+ resolve(true);
2781
+ }
2782
+ });
2783
+ }
2784
+ catch (err) {
2785
+ this.log.error(`${name}: error in onStop callback: ${err}`);
2786
+ }
2787
+ });
2788
+ }
2789
+ delete this.scripts[name];
2790
+ return true;
2791
+ }
2792
+ return false;
2793
+ }
2794
+ async prepareScript(obj) {
2795
+ if (obj?.common?.enabled && this.debugState.scriptName === obj._id) {
2796
+ const id = obj._id;
2797
+ await this.debugStop();
2798
+ this.log.info(`${id}: Debugging was stopped, because started in normal mode`);
2799
+ return this.prepareScript(obj);
2800
+ }
2801
+ if (obj?.common?.source &&
2802
+ (obj.common.enabled || this.context.debugMode === obj._id) &&
2803
+ obj.common.engine === `system.adapter.${this.namespace}`) {
2804
+ const name = obj._id;
2805
+ if (this.password && obj.native?.protected) {
2806
+ obj.common.source = (0, crypto_1.decryptText)(this.password, obj.common.source);
2807
+ }
2808
+ const nameId = name.substring(SCRIPT_CODE_MARKER.length);
2809
+ if (!nameId.length || nameId.endsWith('.')) {
2810
+ this.log.error(`${name}: Script name "${name}" is invalid!`);
2811
+ return false;
2812
+ }
2813
+ const idActive = `scriptEnabled.${nameId}`;
2814
+ if (!this.config.subscribe) {
2815
+ this.interimStateValues[idActive] = this.prepareStateObjectSimple(`${this.namespace}.${idActive}`, true, true);
2816
+ }
2817
+ await this.setState(idActive, true, true);
2818
+ obj.common.engineType ||= '';
2819
+ if (obj.common.engineType.toLowerCase().startsWith('javascript') ||
2820
+ obj.common.engineType === 'Blockly' ||
2821
+ obj.common.engineType === 'Rules') {
2822
+ // Javascript
2823
+ this.log.info(`${name}: start JavaScript (${obj.common.engineType})`);
2824
+ let sourceFn = name;
2825
+ if (webstormDebug) {
2826
+ const fn = name.replace(/^script\.js\./, '').replace(/\./g, '/');
2827
+ sourceFn = this.mods.path.join(webstormDebug, `${fn}.js`);
2828
+ }
2829
+ const createdScript = this.createVM(`${this.globalScript}\n${obj.common.source}`, sourceFn, true);
2830
+ if (!createdScript) {
2831
+ return false;
2832
+ }
2833
+ this.scripts[name] = createdScript;
2834
+ this.execute(this.scripts[name], sourceFn, obj.common.engineType, obj.common.verbose, obj.common.debug);
2835
+ return true;
2836
+ }
2837
+ if (obj.common.engineType.toLowerCase().startsWith('typescript')) {
2838
+ // TypeScript
2839
+ this.log.info(`${name}: Compiling TypeScript source`);
2840
+ // The source code must be transformed in order to support top level await
2841
+ // and to force TypeScript to compile the code as a module
2842
+ const transformedSource = (0, typescriptTools_1.transformScriptBeforeCompilation)(obj.common.source, false);
2843
+ // We need to hash both global declarations that are known until now
2844
+ // AND the script source, because changing either can change the compilation output
2845
+ const sourceHash = (0, tools_1.hashSource)(tsSourceHashBase + this.globalDeclarations + transformedSource);
2846
+ let compiled;
2847
+ // If we already stored the compiled source code and the original source hash,
2848
+ // use the hash to check whether we can rely on the compiled source code or
2849
+ // if we need to compile it again
2850
+ if (typeof obj.common.compiled === 'string' &&
2851
+ typeof obj.common.sourceHash === 'string' &&
2852
+ sourceHash === obj.common.sourceHash) {
2853
+ // We can reuse the stored source
2854
+ compiled = obj.common.compiled;
2855
+ this.log.info(`${name}: source code did not change, using cached compilation result...`);
2856
+ }
2857
+ else {
2858
+ // We don't have a hashed source code, or the original source changed, compile it
2859
+ const filename = (0, typescriptTools_1.scriptIdToTSFilename)(name);
2860
+ let tsCompiled;
2861
+ try {
2862
+ tsCompiled = this.tsServer.compile(filename, transformedSource);
2863
+ }
2864
+ catch (err) {
2865
+ this.log.error(`${obj._id}: TypeScript compilation failed:\n${err}`);
2866
+ return false;
2867
+ }
2868
+ const errors = tsCompiled.diagnostics.map(diag => `${diag.annotatedSource}\n`).join('\n');
2869
+ if (tsCompiled.success) {
2870
+ if (errors.length > 0) {
2871
+ this.log.warn(`${name}: TypeScript compilation had errors:\n${errors}`);
2872
+ }
2873
+ else {
2874
+ this.log.info(`${name}: TypeScript compilation successful`);
2875
+ }
2876
+ compiled = tsCompiled.result || '';
2877
+ // Store the compiled source and the original source hash, so we don't need to do the work again next time
2878
+ this.ignoreObjectChange.add(name); // ignore the next change and don't restart scripts
2879
+ await this.extendForeignObjectAsync(name, {
2880
+ common: {
2881
+ sourceHash,
2882
+ compiled,
2883
+ },
2884
+ });
2885
+ }
2886
+ else {
2887
+ this.log.error(`${name}: TypeScript compilation failed:\n${errors}`);
2888
+ return false;
2889
+ }
2890
+ }
2891
+ const createdScript = this.createVM(`${this.globalScript}\n${compiled}`, name, false);
2892
+ if (!createdScript) {
2893
+ return false;
2894
+ }
2895
+ this.scripts[name] = createdScript;
2896
+ this.execute(this.scripts[name], name, obj.common.engineType, obj.common.verbose, obj.common.debug);
2897
+ return true;
2898
+ }
2899
+ this.log.warn(`${obj._id}: Unknown engine type: ${obj.common.engineType}`);
2900
+ return false;
2901
+ }
2902
+ let _name;
2903
+ if (obj?._id) {
2904
+ _name = obj._id;
2905
+ const scriptIdName = _name.substring(SCRIPT_CODE_MARKER.length);
2906
+ if (!scriptIdName.length || scriptIdName.endsWith('.')) {
2907
+ this.log.error(`${obj._id}: Script name "${_name}" is invalid!`);
2908
+ return false;
2909
+ }
2910
+ await this.setState(`scriptEnabled.${scriptIdName}`, false, true);
2911
+ }
2912
+ if (!obj) {
2913
+ this.log.error('Invalid script');
2914
+ }
2915
+ return false;
2916
+ }
2917
+ async loadScriptById(id) {
2918
+ let obj;
2919
+ try {
2920
+ obj = (await this.getForeignObjectAsync(id));
2921
+ }
2922
+ catch (err) {
2923
+ this.log.error(`${id}: Invalid script: ${err}`);
2924
+ }
2925
+ if (!obj) {
2926
+ return false;
2927
+ }
2928
+ return this.loadScript(obj);
2929
+ }
2930
+ async loadScript(nameOrObject) {
2931
+ // create states for scripts
2932
+ await this.createActiveObject(nameOrObject._id, !!nameOrObject?.common?.enabled);
2933
+ await this.createProblemObject(nameOrObject._id);
2934
+ return this.prepareScript(nameOrObject);
2935
+ }
2936
+ getAstroEvent(date, astroEvent, start, end, offsetMinutes, isDayEnd, latitude, longitude, useNextDay) {
2937
+ let ts = this.mods.suncalc.getTimes(date, latitude, longitude)[astroEvent];
2938
+ if (!ts || ts.getTime().toString() === 'NaN') {
2939
+ ts = isDayEnd ? getNextTimeEvent(end, useNextDay) : getNextTimeEvent(start, useNextDay);
2940
+ }
2941
+ ts.setMilliseconds(0);
2942
+ ts.setMinutes(ts.getMinutes() + (parseInt(offsetMinutes, 10) || 0));
2943
+ const [timeHoursStart, timeMinutesStart] = start.split(':');
2944
+ const nTimeHoursStart = parseInt(timeHoursStart, 10);
2945
+ const nTimeMinutesStart = parseInt(timeMinutesStart, 10) || 0;
2946
+ if (ts.getHours() < nTimeHoursStart ||
2947
+ (ts.getHours() === nTimeHoursStart && ts.getMinutes() < nTimeMinutesStart)) {
2948
+ ts = getNextTimeEvent(start, useNextDay);
2949
+ ts.setSeconds(0);
2950
+ }
2951
+ const [timeHoursEnd, timeMinutesEnd] = end.split(':');
2952
+ const nTimeHoursEnd = parseInt(timeHoursEnd, 10);
2953
+ const nTimeMinutesEnd = parseInt(timeMinutesEnd, 10) || 0;
2954
+ if (ts.getHours() > nTimeHoursEnd || (ts.getHours() === nTimeHoursEnd && ts.getMinutes() > nTimeMinutesEnd)) {
2955
+ ts = getNextTimeEvent(end, useNextDay);
2956
+ ts.setSeconds(0);
2957
+ }
2958
+ // if event in the past
2959
+ if (date > ts && useNextDay) {
2960
+ // take the next day
2961
+ ts.setDate(ts.getDate() + 1);
2962
+ }
2963
+ return ts;
2964
+ }
2965
+ async timeSchedule() {
2966
+ const now = new Date();
2967
+ let hours = now.getHours();
2968
+ const minutes = now.getMinutes();
2969
+ if (this.timeSettings.format12) {
2970
+ if (hours > 12) {
2971
+ hours -= 12;
2972
+ }
2973
+ }
2974
+ let sHours;
2975
+ if (this.timeSettings.leadingZeros) {
2976
+ sHours = hours.toString().padStart(2, '0');
2977
+ }
2978
+ else {
2979
+ sHours = hours.toString();
2980
+ }
2981
+ await this.setState('variables.dayTime', {
2982
+ val: `${sHours}:${minutes.toString().padStart(2, '0')}`,
2983
+ ack: true,
2984
+ });
2985
+ now.setMinutes(now.getMinutes() + 1);
2986
+ now.setSeconds(0);
2987
+ now.setMilliseconds(0);
2988
+ const interval = now.getTime() - Date.now();
2989
+ this.timeScheduleTimer = setTimeout(() => this.timeSchedule(), interval);
2990
+ }
2991
+ async dayTimeSchedules() {
2992
+ // Always clear any existing timer to prevent memory leaks on rapid re-scheduling
2993
+ if (this.dayScheduleTimer) {
2994
+ clearTimeout(this.dayScheduleTimer);
2995
+ this.dayScheduleTimer = null;
2996
+ }
2997
+ // get astrological event
2998
+ if (this.config.latitude === undefined ||
2999
+ this.config.longitude === undefined ||
3000
+ this.config.latitude === '' ||
3001
+ this.config.longitude === '' ||
3002
+ this.config.latitude === null ||
3003
+ this.config.longitude === null) {
3004
+ this.log.error('Longitude or latitude does not set. Cannot use astro.');
3005
+ return;
3006
+ }
3007
+ // Calculate the next event today
3008
+ const todayDate = getAstroStartOfDay();
3009
+ const nowDate = new Date();
3010
+ const todaySunrise = this.getAstroEvent(todayDate, this.config.sunriseEvent, this.config.sunriseLimitStart, this.config.sunriseLimitEnd, this.config.sunriseOffset, false, this.config.latitude, this.config.longitude);
3011
+ const todaySunset = this.getAstroEvent(todayDate, this.config.sunsetEvent, this.config.sunsetLimitStart, this.config.sunsetLimitEnd, this.config.sunsetOffset, true, this.config.latitude, this.config.longitude);
3012
+ // Sunrise
3013
+ let sunriseTimeout = todaySunrise.getTime() - nowDate.getTime();
3014
+ if (sunriseTimeout < 0 || sunriseTimeout > 3600000) {
3015
+ sunriseTimeout = 3600000;
3016
+ }
3017
+ // Sunset
3018
+ let sunsetTimeout = todaySunset.getTime() - nowDate.getTime();
3019
+ if (sunsetTimeout < 0 || sunsetTimeout > 3600000) {
3020
+ sunsetTimeout = 3600000;
3021
+ }
3022
+ const isDayTime = await this.getStateAsync('variables.isDayTime');
3023
+ let isDay;
3024
+ if (sunriseTimeout < 5000) {
3025
+ isDay = true;
3026
+ }
3027
+ else if (sunsetTimeout < 5000) {
3028
+ isDay = false;
3029
+ }
3030
+ else {
3031
+ // check if in between
3032
+ isDay = nowDate.getTime() > todaySunrise.getTime() - 60000 && nowDate <= todaySunset;
3033
+ }
3034
+ const valDayTime = isDayTime ? !!isDayTime.val : false;
3035
+ if (valDayTime !== isDay || isDayTime === null) {
3036
+ await this.setState('variables.isDayTime', isDay, true);
3037
+ }
3038
+ const dayLightSaving = await this.getStateAsync('variables.isDaylightSaving');
3039
+ const isDayLightSaving = dstOffsetAtDate(nowDate) !== 0;
3040
+ const val = dayLightSaving ? !!dayLightSaving.val : false;
3041
+ if (val !== isDayLightSaving || dayLightSaving === null) {
3042
+ await this.setState('variables.isDaylightSaving', isDayLightSaving, true);
3043
+ }
3044
+ let nextTimeout = sunriseTimeout;
3045
+ if (sunriseTimeout > sunsetTimeout) {
3046
+ nextTimeout = sunsetTimeout;
3047
+ }
3048
+ nextTimeout = nextTimeout - 3000;
3049
+ if (nextTimeout < 3000) {
3050
+ nextTimeout = 3000;
3051
+ }
3052
+ this.dayScheduleTimer = setTimeout(() => this.dayTimeSchedules(), nextTimeout);
3053
+ }
3054
+ stopTimeSchedules() {
3055
+ if (this.dayScheduleTimer) {
3056
+ clearTimeout(this.dayScheduleTimer);
3057
+ this.dayScheduleTimer = null;
3058
+ }
3059
+ if (this.sunScheduleTimer) {
3060
+ clearTimeout(this.sunScheduleTimer);
3061
+ this.sunScheduleTimer = null;
3062
+ }
3063
+ if (this.timeScheduleTimer) {
3064
+ clearTimeout(this.timeScheduleTimer);
3065
+ this.timeScheduleTimer = null;
3066
+ }
3067
+ }
3068
+ async patchFont() {
3069
+ let stat;
3070
+ let dbFile;
3071
+ let fileName = `${__dirname}/../admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`;
3072
+ let jsonFileName = `${__dirname}/../admin/vsFont/codicon.json`;
3073
+ try {
3074
+ if ((0, node_fs_1.existsSync)(fileName)) {
3075
+ stat = (0, node_fs_1.statSync)(fileName);
3076
+ }
3077
+ if (!stat) {
3078
+ jsonFileName = `${__dirname}/admin/vsFont/codicon.json`;
3079
+ fileName = `${__dirname}/admin/vs/base/browser/ui/codicons/codicon/codicon.ttf`;
3080
+ if ((0, node_fs_1.existsSync)(fileName)) {
3081
+ stat = (0, node_fs_1.statSync)(fileName);
3082
+ }
3083
+ }
3084
+ const _dbFile = await this.readFileAsync('javascript.admin', `vs/base/browser/ui/codicons/codicon/codicon.ttf`);
3085
+ if (_dbFile?.file) {
3086
+ dbFile = _dbFile.file;
3087
+ }
3088
+ }
3089
+ catch {
3090
+ // ignore
3091
+ }
3092
+ if (stat?.size !== 73452 || dbFile?.byteLength !== 73452) {
3093
+ try {
3094
+ const buffer = Buffer.from(JSON.parse((0, node_fs_1.readFileSync)(jsonFileName).toString()), 'base64');
3095
+ const jszip = await import('jszip');
3096
+ const zip = await jszip.loadAsync(buffer);
3097
+ let data;
3098
+ if (zip) {
3099
+ data = await zip.file('codicon.ttf')?.async('arraybuffer');
3100
+ if (data?.byteLength !== 73452) {
3101
+ this.log.error(`Cannot patch font: invalid font file!`);
3102
+ return false;
3103
+ }
3104
+ }
3105
+ else {
3106
+ this.log.error(`Cannot patch font: invalid font file!`);
3107
+ return false;
3108
+ }
3109
+ (0, node_fs_1.writeFileSync)(fileName, Buffer.from(data));
3110
+ // upload this file
3111
+ await this.writeFileAsync('javascript.admin', 'vs/base/browser/ui/codicons/codicon/codicon.ttf', Buffer.from(data));
3112
+ return true;
3113
+ }
3114
+ catch (err) {
3115
+ this.log.error(`Cannot patch font: ${err}`);
3116
+ return false;
3117
+ }
3118
+ }
3119
+ return false;
3120
+ }
3121
+ async sunTimeSchedules() {
3122
+ if (this.config.createAstroStates) {
3123
+ if (!isNaN(this.config.longitude) && !isNaN(this.config.longitude)) {
3124
+ const calcDate = getAstroStartOfDay();
3125
+ const times = this.mods.suncalc.getTimes(calcDate, this.config.latitude, this.config.longitude);
3126
+ this.log.debug(`[sunTimeSchedules] Times: ${JSON.stringify(times)}`);
3127
+ for (const t in times) {
3128
+ try {
3129
+ const objId = `variables.astro.${t}`;
3130
+ await this.setObjectNotExistsAsync(objId, {
3131
+ type: 'state',
3132
+ common: {
3133
+ name: `Astro ${t}`,
3134
+ type: 'string',
3135
+ role: 'value',
3136
+ read: true,
3137
+ write: false,
3138
+ },
3139
+ native: {},
3140
+ });
3141
+ if (times[t] !== null && !isNaN(times[t].getTime())) {
3142
+ const timeFormatted = formatHoursMinutesSeconds(times[t]);
3143
+ await this.setState(objId, {
3144
+ val: timeFormatted,
3145
+ c: times[t].toISOString(),
3146
+ ack: true,
3147
+ });
3148
+ }
3149
+ else {
3150
+ await this.setState(objId, { val: null, c: 'n/a', ack: true, q: 0x01 });
3151
+ }
3152
+ }
3153
+ catch (err) {
3154
+ this.log.error(`[sunTimeSchedules] Unable to set state for astro time "${t}" (${times[t].getTime()}): ${err}`);
3155
+ }
3156
+ }
3157
+ const todayDate = new Date();
3158
+ todayDate.setHours(0);
3159
+ todayDate.setMinutes(0);
3160
+ todayDate.setSeconds(1);
3161
+ todayDate.setMilliseconds(0);
3162
+ todayDate.setDate(todayDate.getDate() + 1);
3163
+ this.log.debug(`[sunTimeSchedules] Next: ${todayDate.toISOString()}`);
3164
+ this.sunScheduleTimer = setTimeout(() => this.sunTimeSchedules(), todayDate.getTime() - Date.now());
3165
+ }
3166
+ }
3167
+ else {
3168
+ // remove astro states if disabled
3169
+ this.delObject('variables.astro', { recursive: true });
3170
+ }
3171
+ }
3172
+ /**
3173
+ * Redirects the virtual-tsc log output to the ioBroker log
3174
+ */
3175
+ tsLog = (message, severity) => {
3176
+ // shift the severities around, we don't care about the small details
3177
+ if (!severity || severity === 'info') {
3178
+ severity = 'debug';
3179
+ }
3180
+ else if (severity === 'debug') {
3181
+ // Don't spam build logs on Travis
3182
+ if (isCI) {
3183
+ return;
3184
+ }
3185
+ severity = 'silly';
3186
+ }
3187
+ if (this?.log) {
3188
+ this.log[severity](message);
3189
+ }
3190
+ else {
3191
+ console.log(`[${severity.toUpperCase()}] ${message}`);
3192
+ }
3193
+ };
3194
+ addGetProperty(object) {
3195
+ try {
3196
+ Object.defineProperty(object, 'get', {
3197
+ value: function (id) {
3198
+ return this[id] || this[`${this.namespace}.${id}`];
3199
+ },
3200
+ enumerable: false,
3201
+ });
3202
+ }
3203
+ catch {
3204
+ console.error('Cannot install get property');
3205
+ }
3206
+ }
3207
+ /**
3208
+ * Add declarations for global scripts
3209
+ *
3210
+ * @param scriptID - The current script the declarations were generated from
3211
+ * @param declarations - Declarations from a script
3212
+ */
3213
+ provideDeclarationsForGlobalScript(scriptID, declarations) {
3214
+ // Remember which declarations this global script had access to;
3215
+ // we need this so the editor doesn't show a duplicate identifier error
3216
+ if (this.globalDeclarations != null && this.globalDeclarations !== '') {
3217
+ this.knownGlobalDeclarationsByScript[scriptID] = this.globalDeclarations;
3218
+ }
3219
+ // and concatenate the global declarations for the next scripts
3220
+ this.globalDeclarations += `${declarations}\n`;
3221
+ // remember all previously generated global declarations,
3222
+ // so global scripts can reference each other
3223
+ const globalDeclarationPath = 'global.d.ts';
3224
+ tsAmbient[globalDeclarationPath] = this.globalDeclarations;
3225
+ // make sure the next script compilation has access to the updated declarations
3226
+ this.tsServer.provideAmbientDeclarations({
3227
+ [globalDeclarationPath]: this.globalDeclarations,
3228
+ });
3229
+ jsDeclarationServer.provideAmbientDeclarations({
3230
+ [globalDeclarationPath]: this.globalDeclarations,
3231
+ });
3232
+ }
3233
+ fixLineNo(line) {
3234
+ if (line.includes('javascript.js:')) {
3235
+ return line;
3236
+ }
3237
+ if (!/scripts?\.js[.\\/]/.test(line)) {
3238
+ return line;
3239
+ }
3240
+ if (/:(\d+):/.test(line)) {
3241
+ line = line.replace(/:(\d+):/, (_$0, $1) => `:${$1 > this.globalScriptLines + 1 ? $1 - this.globalScriptLines - 1 : $1}:`); // one line for 'async function ()'
3242
+ }
3243
+ else {
3244
+ line = line.replace(/:(\d+)$/, (_$0, $1) => `:${$1 > this.globalScriptLines + 1 ? $1 - this.globalScriptLines - 1 : $1}`); // one line for 'async function ()'
3245
+ }
3246
+ return line;
3247
+ }
3248
+ async debugStop() {
3249
+ if (this.debugState.child) {
3250
+ this.debugSendToInspector({ cmd: 'end' });
3251
+ this.debugState.endTimeout = setTimeout(() => {
3252
+ this.debugState.endTimeout = null;
3253
+ this.debugState.child?.kill('SIGTERM');
3254
+ }, 500);
3255
+ this.debugState.promiseOnEnd ||= Promise.resolve(0);
3256
+ }
3257
+ else {
3258
+ this.debugState.promiseOnEnd = Promise.resolve(0);
3259
+ }
3260
+ await this.debugState.promiseOnEnd;
3261
+ this.debugState.child = null;
3262
+ this.debugState.running = false;
3263
+ this.debugState.scriptName = '';
3264
+ if (this.debugState.endTimeout) {
3265
+ clearTimeout(this.debugState.endTimeout);
3266
+ this.debugState.endTimeout = null;
3267
+ }
3268
+ }
3269
+ async debugDisableScript(id) {
3270
+ if (id) {
3271
+ const obj = this.objects[id];
3272
+ if (obj?.common?.enabled) {
3273
+ await this.extendForeignObjectAsync(obj._id, { common: { enabled: false } });
3274
+ }
3275
+ }
3276
+ }
3277
+ debugSendToInspector(message) {
3278
+ if (this.debugState.child) {
3279
+ try {
3280
+ this.log.info(`send to debugger: ${message}`);
3281
+ this.debugState.child.send(message);
3282
+ }
3283
+ catch {
3284
+ void this.debugStop().then(() => this.log.info(`${this.debugState.scriptName}: Debugging was stopped, because started in normal mode`));
3285
+ }
3286
+ }
3287
+ else {
3288
+ this.log.error(`${this.debugState.scriptName}: Cannot send command to terminated inspector`);
3289
+ void this.setState('debug.from', JSON.stringify({ cmd: 'error', error: `Cannot send command to terminated inspector`, id: 1 }), true);
3290
+ }
3291
+ }
3292
+ debugStart(data) {
3293
+ if (Date.now() - this.debugState.started < 1000) {
3294
+ console.log('Start ignored');
3295
+ return;
3296
+ }
3297
+ this.debugState.started = Date.now();
3298
+ // stop the script if it's running
3299
+ void this.debugDisableScript(data.scriptName)
3300
+ .then(() => this.debugStop())
3301
+ .then(() => {
3302
+ if (data.adapter) {
3303
+ this.debugState.adapterInstance = data.adapter;
3304
+ this.debugState.scriptName = '';
3305
+ }
3306
+ else {
3307
+ this.debugState.adapterInstance = '';
3308
+ this.debugState.scriptName = data.scriptName;
3309
+ }
3310
+ this.debugState.breakOnStart = data.breakOnStart;
3311
+ this.debugState.promiseOnEnd = new Promise(resolve => {
3312
+ const options = {
3313
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
3314
+ //stdio: ['pipe', 'pipe', 'pipe', 'ipc']
3315
+ };
3316
+ const args = [];
3317
+ if (this.debugState.adapterInstance) {
3318
+ args.push('--breakOnStart');
3319
+ }
3320
+ this.debugState.child = (0, node_child_process_1.fork)(`${__dirname}/lib/inspect.js`, args, options);
3321
+ /*debugState.child.stdout.setEncoding('utf8');
3322
+ debugState.child.stderr.setEncoding('utf8');
3323
+ debugState.child.stdout.on('data', childPrint);
3324
+ debugState.child.stderr.on('data', childPrint);*/
3325
+ this.debugState.child?.on('message', (message) => {
3326
+ let oMessage;
3327
+ if (typeof message === 'string') {
3328
+ try {
3329
+ oMessage = JSON.parse(message);
3330
+ }
3331
+ catch {
3332
+ return this.log.error(`Cannot parse message from inspector: ${message}`);
3333
+ }
3334
+ }
3335
+ else {
3336
+ oMessage = message;
3337
+ }
3338
+ if (oMessage.cmd !== 'ready') {
3339
+ void this.setState('debug.from', JSON.stringify(oMessage), true);
3340
+ }
3341
+ switch (oMessage.cmd) {
3342
+ case 'ready': {
3343
+ this.debugSendToInspector({
3344
+ cmd: 'start',
3345
+ scriptName: this.debugState.scriptName,
3346
+ adapterInstance: this.debugState.adapterInstance,
3347
+ instance: this.instance,
3348
+ });
3349
+ break;
3350
+ }
3351
+ case 'watched': {
3352
+ //console.log(`WATCHED: ${JSON.stringify(oMessage)}`);
3353
+ break;
3354
+ }
3355
+ case 'paused': {
3356
+ this.debugState.paused = true;
3357
+ console.log(`host: PAUSED`);
3358
+ break;
3359
+ }
3360
+ case 'resumed': {
3361
+ this.debugState.paused = false;
3362
+ //console.log(`STARTED`);
3363
+ break;
3364
+ }
3365
+ case 'log': {
3366
+ console.log(`[${oMessage.severity}] ${oMessage.text}`);
3367
+ break;
3368
+ }
3369
+ case 'readyToDebug': {
3370
+ console.log(`host: readyToDebug (set breakpoints): [${oMessage.scriptId}] ${oMessage.script}`);
3371
+ break;
3372
+ }
3373
+ }
3374
+ });
3375
+ this.debugState.child?.on('error', error => {
3376
+ this.log.error(`Cannot start inspector: ${error}`);
3377
+ void this.setState('debug.from', JSON.stringify({ cmd: 'error', error }), true);
3378
+ });
3379
+ this.debugState.child?.on('exit', async (code) => {
3380
+ if (code) {
3381
+ await this.setState('debug.from', JSON.stringify({ cmd: 'error', error: `invalid response code: ${code}` }), true);
3382
+ }
3383
+ await this.setState('debug.from', JSON.stringify({ cmd: 'debugStopped', code }), true);
3384
+ this.debugState.child = null;
3385
+ resolve(code);
3386
+ });
3387
+ });
3388
+ });
3389
+ }
3390
+ }
3391
+ function patternMatching(event, patternFunctions) {
3392
+ const logic = patternFunctions.logic ?? 'and';
3393
+ for (let i = 0, len = patternFunctions.length; i < len; i++) {
3394
+ const result = patternFunctions[i](event);
3395
+ if (logic === 'and' && !result) {
3396
+ return false; // short-circuit AND – one false is enough
3397
+ }
3398
+ if (logic === 'or' && result) {
3399
+ return true; // short-circuit OR – one true is enough
3400
+ }
3401
+ }
3402
+ // AND: all passed → true; OR: none matched → false
3403
+ return logic === 'and';
3404
+ }
3405
+ // If started as allInOne mode => return function to create an instance
3406
+ if (require.main !== module) {
3407
+ // Export the constructor in compact mode
3408
+ module.exports = (options) => new JavaScript(options);
3409
+ }
3410
+ else {
3411
+ // otherwise start the instance directly
3412
+ (() => new JavaScript())();
3413
+ }
3414
+ //# sourceMappingURL=main.js.map