iobroker.javascript 9.2.4 → 9.3.1

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 (37) hide show
  1. package/README.md +6 -15
  2. package/admin/assets/AiChatPanel-C3bIw_D5.js +317 -0
  3. package/admin/assets/{ScriptEditor-CmEeMfC2.js → ScriptEditor-8UU88tT5.js} +1 -1
  4. package/admin/assets/{ScriptEditorVanillaMonaco-C22Gpet7.js → ScriptEditorVanillaMonaco-Bn8xJ3LV.js} +2 -2
  5. package/admin/assets/index-C0ZnUCQf.js +990 -0
  6. package/admin/assets/{index-BTQOdA7v.js → index-CO9RA-Wq.js} +4 -4
  7. package/admin/assets/{index-7mrTpzQy.js → index-CRGWWpGb.js} +1 -1
  8. package/admin/assets/{index-Dggem6AX.js → index-CsyuIWlD.js} +1 -1
  9. package/admin/assets/localSharedImportMap-Db5DxDmY.js +1 -0
  10. package/admin/assets/{stateHoverProvider-DxTNxyYN.js → stateHoverProvider-C82xsI7Q.js} +1 -1
  11. package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-CeYV4czE.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-Bw22wl4W.js} +2 -2
  12. package/admin/custom/assets/index-CMsuyEZj.js +964 -0
  13. package/admin/custom/assets/{index-B6NrrORQ.js → index-CuAhFQ4L.js} +147 -147
  14. package/admin/custom/assets/{index-DTbSiEk2.js → index-mkhHeJ3Q.js} +1 -1
  15. package/admin/custom/assets/localSharedImportMap-BsMINYMr.js +1 -0
  16. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-ClQrb2n1.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-BzLNCazi.js} +2 -2
  17. package/admin/custom/customComponents.js +1 -1
  18. package/admin/i18n/de.json +8 -0
  19. package/admin/i18n/en.json +8 -0
  20. package/admin/jsonConfig.json +70 -9
  21. package/admin/mf-manifest.json +1 -1
  22. package/admin/remoteEntry.js +1 -1
  23. package/admin/tab.html +2 -2
  24. package/build/lib/aiProviderResolver.js +32 -5
  25. package/build/lib/aiProviderResolver.js.map +1 -1
  26. package/build/lib/sandbox.js +20 -12
  27. package/build/lib/sandbox.js.map +1 -1
  28. package/build/main.js +336 -41
  29. package/build/main.js.map +1 -1
  30. package/build/types.d.ts +10 -4
  31. package/io-package.json +34 -28
  32. package/package.json +9 -9
  33. package/admin/assets/AiChatPanel-B4vPXgLz.js +0 -315
  34. package/admin/assets/index-qoSy77Bg.js +0 -990
  35. package/admin/assets/localSharedImportMap-CVlAGiIu.js +0 -1
  36. package/admin/custom/assets/index-CT5PwRfY.js +0 -964
  37. package/admin/custom/assets/localSharedImportMap-ChjFDKsq.js +0 -1
@@ -10,7 +10,8 @@
10
10
  * These functions are extracted so they can be unit-tested in isolation.
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.PROVIDER_KEY_FIELD = void 0;
13
+ exports.PROVIDER_CREDENTIAL_ID_FIELD = exports.PROVIDER_KEY_FIELD = void 0;
14
+ exports.getProviderCredentialId = getProviderCredentialId;
14
15
  exports.resolveProviderCredentials = resolveProviderCredentials;
15
16
  exports.resolveTestCredentials = resolveTestCredentials;
16
17
  exports.listAvailableProviders = listAvailableProviders;
@@ -22,6 +23,26 @@ exports.PROVIDER_KEY_FIELD = {
22
23
  deepseek: 'deepseekKey',
23
24
  custom: 'gptBaseUrlKey',
24
25
  };
26
+ /**
27
+ * Maps each provider to the adapter-config field holding the ID of its credential
28
+ * in the central credential store (used in `manager` mode).
29
+ */
30
+ exports.PROVIDER_CREDENTIAL_ID_FIELD = {
31
+ openai: 'credentialIdGptKey',
32
+ anthropic: 'credentialIdClaudeKey',
33
+ gemini: 'credentialIdGeminiKey',
34
+ deepseek: 'credentialIdDeepseekKey',
35
+ custom: 'credentialIdGptBaseUrlKey',
36
+ };
37
+ /**
38
+ * Returns the configured credential ID (e.g. `system.credentials.anthropic`) for a provider
39
+ * in `manager` mode, or an empty string if none/unknown provider.
40
+ */
41
+ function getProviderCredentialId(config, provider) {
42
+ const cfg = config || {};
43
+ const field = exports.PROVIDER_CREDENTIAL_ID_FIELD[provider];
44
+ return field ? (cfg[field] || '').toString().trim() : '';
45
+ }
25
46
  /**
26
47
  * Resolve API key and base URL for a provider from adapter config.
27
48
  * Optional `messageBaseUrl` takes precedence over the stored `gptBaseUrl`
@@ -60,18 +81,24 @@ function resolveTestCredentials(config, provider, messageApiKey, messageBaseUrl)
60
81
  function listAvailableProviders(config) {
61
82
  const cfg = config || {};
62
83
  const providers = [];
63
- if ((cfg.gptKey || '').trim()) {
84
+ const manager = cfg.credentialType === 'manager';
85
+ // A key-based provider is "available" if it has a stored key (manual mode)
86
+ // or a selected credential ID (manager mode).
87
+ const has = (provider, key) => manager ? !!getProviderCredentialId(cfg, provider) : !!(key || '').trim();
88
+ if (has('openai', cfg.gptKey)) {
64
89
  providers.push({ provider: 'openai' });
65
90
  }
66
- if ((cfg.claudeKey || '').trim()) {
91
+ if (has('anthropic', cfg.claudeKey)) {
67
92
  providers.push({ provider: 'anthropic' });
68
93
  }
69
- if ((cfg.geminiKey || '').trim()) {
94
+ if (has('gemini', cfg.geminiKey)) {
70
95
  providers.push({ provider: 'gemini' });
71
96
  }
72
- if ((cfg.deepseekKey || '').trim()) {
97
+ if (has('deepseek', cfg.deepseekKey)) {
73
98
  providers.push({ provider: 'deepseek' });
74
99
  }
100
+ // The custom/OpenAI-compatible endpoint is identified by its base URL (the key is optional,
101
+ // e.g. local Ollama), so its availability does not depend on the credential mode.
75
102
  if ((cfg.gptBaseUrl || '').trim()) {
76
103
  providers.push({ provider: 'custom', baseUrl: cfg.gptBaseUrl });
77
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"aiProviderResolver.js","sourceRoot":"","sources":["../../src/lib/aiProviderResolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AA6BH,gEAgBC;AAMD,wDAYC;AAOD,wDAqBC;AA7ED,0EAA0E;AAC7D,QAAA,kBAAkB,GAA4C;IACvE,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,WAAW;IACnB,QAAQ,EAAE,aAAa;IACvB,MAAM,EAAE,eAAe;CAC1B,CAAC;AAEF;;;;;GAKG;AACH,SAAgB,0BAA0B,CACtC,MAAwC,EACxC,QAAgB,EAChB,cAAuB;IAEvB,MAAM,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,0BAAkB,CAAC,QAAsB,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,2FAA2F;IAC3F,kGAAkG;IAClG,4FAA4F;IAC5F,MAAM,OAAO,GACT,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,QAAQ;QAC1C,CAAC,CAAC,CAAC,cAAc,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE;QAC5D,CAAC,CAAC,EAAE,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAClC,MAAwC,EACxC,QAAgB,EAChB,aAAsB,EACtB,cAAuB;IAEvB,MAAM,QAAQ,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC9E,MAAM,WAAW,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO;QACH,MAAM,EAAE,WAAW,IAAI,QAAQ,CAAC,MAAM;QACtC,OAAO,EAAE,QAAQ,CAAC,OAAO;KAC5B,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAClC,MAAwC;IAExC,MAAM,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC;IACzB,MAAM,SAAS,GAAiD,EAAE,CAAC;IACnE,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACjC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC","sourcesContent":["/**\n * Helpers for resolving AI provider credentials in sendTo handlers.\n *\n * Keys are stored as `encryptedNative` + `protectedNative` in io-package.json —\n * they are never sent from the frontend. The backend looks them up in `this.config`\n * based on the `provider` name sent along with each `chatCompletion` or\n * `testApiConnection` request.\n *\n * These functions are extracted so they can be unit-tested in isolation.\n */\n\n/** Configuration subset that carries AI credentials. */\nexport interface AiConfigSlice {\n gptKey?: string;\n gptBaseUrl?: string;\n gptBaseUrlKey?: string;\n claudeKey?: string;\n geminiKey?: string;\n deepseekKey?: string;\n}\n\nexport type AiProvider = 'openai' | 'anthropic' | 'gemini' | 'deepseek' | 'custom';\n\n/** Maps each provider to the adapter-config field holding its API key. */\nexport const PROVIDER_KEY_FIELD: Record<AiProvider, keyof AiConfigSlice> = {\n openai: 'gptKey',\n anthropic: 'claudeKey',\n gemini: 'geminiKey',\n deepseek: 'deepseekKey',\n custom: 'gptBaseUrlKey',\n};\n\n/**\n * Resolve API key and base URL for a provider from adapter config.\n * Optional `messageBaseUrl` takes precedence over the stored `gptBaseUrl`\n * (used by the settings-dialog Test button where the user's form value\n * should win over the persisted value).\n */\nexport function resolveProviderCredentials(\n config: AiConfigSlice | undefined | null,\n provider: string,\n messageBaseUrl?: string,\n): { apiKey: string; baseUrl: string } {\n const cfg = config || {};\n const keyField = PROVIDER_KEY_FIELD[provider as AiProvider];\n const apiKey = keyField ? (cfg[keyField] || '').toString().trim() : '';\n // baseUrl only applies to openai-compatible providers (custom / openai endpoint override).\n // An empty/whitespace messageBaseUrl counts as \"not provided\" and falls back to the stored value,\n // so the frontend can safely send `baseUrl: ''` without overriding a configured custom URL.\n const baseUrl =\n provider === 'custom' || provider === 'openai'\n ? (messageBaseUrl || cfg.gptBaseUrl || '').toString().trim()\n : '';\n return { apiKey, baseUrl };\n}\n\n/**\n * For the testApiConnection sendTo command: if the caller supplied an apiKey\n * (settings-dialog form value), use it; otherwise fall back to the stored key.\n */\nexport function resolveTestCredentials(\n config: AiConfigSlice | undefined | null,\n provider: string,\n messageApiKey?: string,\n messageBaseUrl?: string,\n): { apiKey: string; baseUrl: string } {\n const fallback = resolveProviderCredentials(config, provider, messageBaseUrl);\n const explicitKey = (messageApiKey || '').toString().trim();\n return {\n apiKey: explicitKey || fallback.apiKey,\n baseUrl: fallback.baseUrl,\n };\n}\n\n/**\n * List of providers that have credentials configured in this.config.\n * Used by the `getAvailableAiProviders` sendTo command so the frontend\n * knows which provider icons/models to show without ever seeing a key.\n */\nexport function listAvailableProviders(\n config: AiConfigSlice | undefined | null,\n): { provider: AiProvider; baseUrl?: string }[] {\n const cfg = config || {};\n const providers: { provider: AiProvider; baseUrl?: string }[] = [];\n if ((cfg.gptKey || '').trim()) {\n providers.push({ provider: 'openai' });\n }\n if ((cfg.claudeKey || '').trim()) {\n providers.push({ provider: 'anthropic' });\n }\n if ((cfg.geminiKey || '').trim()) {\n providers.push({ provider: 'gemini' });\n }\n if ((cfg.deepseekKey || '').trim()) {\n providers.push({ provider: 'deepseek' });\n }\n if ((cfg.gptBaseUrl || '').trim()) {\n providers.push({ provider: 'custom', baseUrl: cfg.gptBaseUrl });\n }\n return providers;\n}\n"]}
1
+ {"version":3,"file":"aiProviderResolver.js","sourceRoot":"","sources":["../../src/lib/aiProviderResolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Helpers for resolving AI provider credentials in sendTo handlers.\n *\n * Keys are stored as `encryptedNative` + `protectedNative` in io-package.json —\n * they are never sent from the frontend. The backend looks them up in `this.config`\n * based on the `provider` name sent along with each `chatCompletion` or\n * `testApiConnection` request.\n *\n * These functions are extracted so they can be unit-tested in isolation.\n */\n\n/** Configuration subset that carries AI credentials. */\nexport interface AiConfigSlice {\n gptKey?: string;\n gptBaseUrl?: string;\n gptBaseUrlKey?: string;\n claudeKey?: string;\n geminiKey?: string;\n deepseekKey?: string;\n /**\n * Where the API keys come from:\n * - `manual`: keys are stored directly in the adapter config (encryptedNative)\n * - `manager`: the config only stores the ID of a credential in the central\n * ioBroker credential store (`system.credentials.*`), resolved at runtime\n */\n credentialType?: 'manual' | 'manager';\n credentialIdGptKey?: string;\n credentialIdClaudeKey?: string;\n credentialIdGeminiKey?: string;\n credentialIdDeepseekKey?: string;\n credentialIdGptBaseUrlKey?: string;\n}\n\nexport type AiProvider = 'openai' | 'anthropic' | 'gemini' | 'deepseek' | 'custom';\n\n/** Maps each provider to the adapter-config field holding its API key. */\nexport const PROVIDER_KEY_FIELD: Record<AiProvider, keyof AiConfigSlice> = {\n openai: 'gptKey',\n anthropic: 'claudeKey',\n gemini: 'geminiKey',\n deepseek: 'deepseekKey',\n custom: 'gptBaseUrlKey',\n};\n\n/**\n * Maps each provider to the adapter-config field holding the ID of its credential\n * in the central credential store (used in `manager` mode).\n */\nexport const PROVIDER_CREDENTIAL_ID_FIELD: Record<AiProvider, keyof AiConfigSlice> = {\n openai: 'credentialIdGptKey',\n anthropic: 'credentialIdClaudeKey',\n gemini: 'credentialIdGeminiKey',\n deepseek: 'credentialIdDeepseekKey',\n custom: 'credentialIdGptBaseUrlKey',\n};\n\n/**\n * Returns the configured credential ID (e.g. `system.credentials.anthropic`) for a provider\n * in `manager` mode, or an empty string if none/unknown provider.\n */\nexport function getProviderCredentialId(config: AiConfigSlice | undefined | null, provider: string): string {\n const cfg = config || {};\n const field = PROVIDER_CREDENTIAL_ID_FIELD[provider as AiProvider];\n return field ? (cfg[field] || '').toString().trim() : '';\n}\n\n/**\n * Resolve API key and base URL for a provider from adapter config.\n * Optional `messageBaseUrl` takes precedence over the stored `gptBaseUrl`\n * (used by the settings-dialog Test button where the user's form value\n * should win over the persisted value).\n */\nexport function resolveProviderCredentials(\n config: AiConfigSlice | undefined | null,\n provider: string,\n messageBaseUrl?: string,\n): { apiKey: string; baseUrl: string } {\n const cfg = config || {};\n const keyField = PROVIDER_KEY_FIELD[provider as AiProvider];\n const apiKey = keyField ? (cfg[keyField] || '').toString().trim() : '';\n // baseUrl only applies to openai-compatible providers (custom / openai endpoint override).\n // An empty/whitespace messageBaseUrl counts as \"not provided\" and falls back to the stored value,\n // so the frontend can safely send `baseUrl: ''` without overriding a configured custom URL.\n const baseUrl =\n provider === 'custom' || provider === 'openai'\n ? (messageBaseUrl || cfg.gptBaseUrl || '').toString().trim()\n : '';\n return { apiKey, baseUrl };\n}\n\n/**\n * For the testApiConnection sendTo command: if the caller supplied an apiKey\n * (settings-dialog form value), use it; otherwise fall back to the stored key.\n */\nexport function resolveTestCredentials(\n config: AiConfigSlice | undefined | null,\n provider: string,\n messageApiKey?: string,\n messageBaseUrl?: string,\n): { apiKey: string; baseUrl: string } {\n const fallback = resolveProviderCredentials(config, provider, messageBaseUrl);\n const explicitKey = (messageApiKey || '').toString().trim();\n return {\n apiKey: explicitKey || fallback.apiKey,\n baseUrl: fallback.baseUrl,\n };\n}\n\n/**\n * List of providers that have credentials configured in this.config.\n * Used by the `getAvailableAiProviders` sendTo command so the frontend\n * knows which provider icons/models to show without ever seeing a key.\n */\nexport function listAvailableProviders(\n config: AiConfigSlice | undefined | null,\n): { provider: AiProvider; baseUrl?: string }[] {\n const cfg = config || {};\n const providers: { provider: AiProvider; baseUrl?: string }[] = [];\n const manager = cfg.credentialType === 'manager';\n // A key-based provider is \"available\" if it has a stored key (manual mode)\n // or a selected credential ID (manager mode).\n const has = (provider: AiProvider, key: string | undefined | null): boolean =>\n manager ? !!getProviderCredentialId(cfg, provider) : !!(key || '').trim();\n if (has('openai', cfg.gptKey)) {\n providers.push({ provider: 'openai' });\n }\n if (has('anthropic', cfg.claudeKey)) {\n providers.push({ provider: 'anthropic' });\n }\n if (has('gemini', cfg.geminiKey)) {\n providers.push({ provider: 'gemini' });\n }\n if (has('deepseek', cfg.deepseekKey)) {\n providers.push({ provider: 'deepseek' });\n }\n // The custom/OpenAI-compatible endpoint is identified by its base URL (the key is optional,\n // e.g. local Ollama), so its availability does not depend on the credential mode.\n if ((cfg.gptBaseUrl || '').trim()) {\n providers.push({ provider: 'custom', baseUrl: cfg.gptBaseUrl });\n }\n return providers;\n}\n"]}
@@ -100,7 +100,7 @@ function removeFromDispatchIndex(ctx, sub) {
100
100
  }
101
101
  }
102
102
  }
103
- function sandBox(script, name, verbose, debug, context) {
103
+ function sandBox(script, name, verbose, debug, context, logCollector) {
104
104
  const consts = constsMod;
105
105
  const words = wordsMod;
106
106
  const eventObj = eventObjMod;
@@ -1126,7 +1126,7 @@ function sandBox(script, name, verbose, debug, context) {
1126
1126
  return result;
1127
1127
  },
1128
1128
  log: function (msg, severity) {
1129
- severity = severity || 'info';
1129
+ severity ||= 'info';
1130
1130
  // disable log in log handler (prevent endless loops)
1131
1131
  if (sandbox.logHandler && (sandbox.logHandler === severity || sandbox.logHandler === '*')) {
1132
1132
  return;
@@ -1138,7 +1138,10 @@ function sandBox(script, name, verbose, debug, context) {
1138
1138
  if (msg && typeof msg !== 'string') {
1139
1139
  msg = mods.util.format(msg);
1140
1140
  }
1141
- if (debugMode) {
1141
+ if (logCollector) {
1142
+ logCollector(severity, msg);
1143
+ }
1144
+ else if (debugMode) {
1142
1145
  console.log(`${severity}$$${name}$$${msg}`, Date.now());
1143
1146
  }
1144
1147
  else {
@@ -1159,8 +1162,9 @@ function sandBox(script, name, verbose, debug, context) {
1159
1162
  context.logSubscriptions[sandbox.scriptName].push(handler);
1160
1163
  context.updateLogSubscriptions();
1161
1164
  sandbox.__engine.__subscriptionsLog += 1;
1162
- sandbox.verbose &&
1165
+ if (sandbox.verbose) {
1163
1166
  sandbox.log(`onLog(severity=${severity}, id=${handler.id}) - logSubscriptions=${sandbox.__engine.__subscriptionsLog}`, 'info');
1167
+ }
1164
1168
  if (sandbox.__engine.__subscriptionsLog %
1165
1169
  adapter.config.maxTriggersPerScript ===
1166
1170
  0) {
@@ -1171,14 +1175,16 @@ function sandBox(script, name, verbose, debug, context) {
1171
1175
  onLogUnregister: function (idOrCallbackOrSeverity) {
1172
1176
  let found = false;
1173
1177
  if (context.logSubscriptions?.[sandbox.scriptName]) {
1174
- sandbox.verbose &&
1178
+ if (sandbox.verbose) {
1175
1179
  sandbox.log(`onLogUnregister(idOrCallbackOrSeverity=${JSON.stringify(idOrCallbackOrSeverity)}) - logSubscriptions=${sandbox.__engine.__subscriptionsLog}`, 'info');
1180
+ }
1176
1181
  for (let i = 0; i < context.logSubscriptions[sandbox.scriptName].length; i++) {
1177
1182
  if (context.logSubscriptions[sandbox.scriptName][i].cb === idOrCallbackOrSeverity ||
1178
1183
  context.logSubscriptions[sandbox.scriptName][i].id === idOrCallbackOrSeverity ||
1179
1184
  context.logSubscriptions[sandbox.scriptName][i].severity === idOrCallbackOrSeverity) {
1180
- sandbox.verbose &&
1185
+ if (sandbox.verbose) {
1181
1186
  sandbox.log(`onLogUnregister(idOrCallbackOrSeverity=${JSON.stringify(idOrCallbackOrSeverity)}, removing id=${context.logSubscriptions[sandbox.scriptName][i].id})`, 'info');
1187
+ }
1182
1188
  context.logSubscriptions[sandbox.scriptName].splice(i, 1);
1183
1189
  i--;
1184
1190
  sandbox.__engine.__subscriptionsLog--;
@@ -1188,9 +1194,8 @@ function sandBox(script, name, verbose, debug, context) {
1188
1194
  break;
1189
1195
  }
1190
1196
  }
1191
- else {
1192
- sandbox.verbose &&
1193
- sandbox.log(`onLogUnregister(idOrCallbackOrSeverity=${JSON.stringify(idOrCallbackOrSeverity)}) NOT = ${JSON.stringify(context.logSubscriptions[sandbox.scriptName][i])}`, 'info');
1197
+ else if (sandbox.verbose) {
1198
+ sandbox.log(`onLogUnregister(idOrCallbackOrSeverity=${JSON.stringify(idOrCallbackOrSeverity)}) NOT = ${JSON.stringify(context.logSubscriptions[sandbox.scriptName][i])}`, 'info');
1194
1199
  }
1195
1200
  }
1196
1201
  }
@@ -3303,8 +3308,9 @@ function sandBox(script, name, verbose, debug, context) {
3303
3308
  }
3304
3309
  // If specific instance
3305
3310
  if (_adapter.match(/\.[0-9]+$/)) {
3306
- sandbox.verbose &&
3311
+ if (sandbox.verbose) {
3307
3312
  sandbox.log(`sendTo(instance=${_adapter}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'})`, 'info');
3313
+ }
3308
3314
  adapter.sendTo(_adapter, cmd, msg, cbFunc, options);
3309
3315
  }
3310
3316
  else {
@@ -3312,8 +3318,9 @@ function sandBox(script, name, verbose, debug, context) {
3312
3318
  const cached = context.sendToInstanceCache.get(_adapter);
3313
3319
  if (cached) {
3314
3320
  cached.forEach(instance => {
3315
- sandbox.verbose &&
3321
+ if (sandbox.verbose) {
3316
3322
  sandbox.log(`sendTo(instance=${instance}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'}) [cached]`, 'info');
3323
+ }
3317
3324
  adapter.sendTo(instance, cmd, msg, cbFunc, options);
3318
3325
  });
3319
3326
  }
@@ -3335,8 +3342,9 @@ function sandBox(script, name, verbose, debug, context) {
3335
3342
  }
3336
3343
  context.sendToInstanceCache.set(_adapter, instances);
3337
3344
  instances.forEach(instance => {
3338
- sandbox.verbose &&
3345
+ if (sandbox.verbose) {
3339
3346
  sandbox.log(`sendTo(instance=${instance}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'})`, 'info');
3347
+ }
3340
3348
  adapter.sendTo(instance, cmd, msg, cbFunc, options);
3341
3349
  });
3342
3350
  });