iobroker.javascript 9.2.3 → 9.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +6 -14
  2. package/admin/assets/{AiChatPanel-C4zUsNmN.js → AiChatPanel-B4vPXgLz.js} +1 -1
  3. package/admin/assets/{ScriptEditor-C6W68ya0.js → ScriptEditor-CmEeMfC2.js} +1 -1
  4. package/admin/assets/{ScriptEditorVanillaMonaco-DYTcDqp_.js → ScriptEditorVanillaMonaco-C22Gpet7.js} +2 -2
  5. package/admin/assets/{index-crBQrXKG.js → index-7mrTpzQy.js} +1 -1
  6. package/admin/assets/{index-CUau9-il.js → index-BTQOdA7v.js} +28 -28
  7. package/admin/assets/{index-BXfqctma.js → index-Dggem6AX.js} +1 -1
  8. package/admin/assets/{stateHoverProvider-BYBqzJuX.js → stateHoverProvider-DxTNxyYN.js} +1 -1
  9. package/admin/custom/assets/{Components-jfkoGyES.js → Components-CGiJE5Zh.js} +1 -1
  10. package/admin/custom/assets/{index-CS_GBN2m.js → index-B6NrrORQ.js} +1 -1
  11. package/admin/custom/assets/index-CT5PwRfY.js +964 -0
  12. package/admin/custom/assets/{index-ChQotUCl.js → index-DTbSiEk2.js} +1 -1
  13. package/admin/custom/assets/{localSharedImportMap-BeObsVNc.js → localSharedImportMap-ChjFDKsq.js} +1 -1
  14. package/admin/custom/assets/{virtualExposes-wYe_Np-4.js → virtualExposes-D402kVGY.js} +1 -1
  15. package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-BcRVut8U.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-ClQrb2n1.js} +2 -2
  16. package/admin/custom/customComponents.js +1 -1
  17. package/admin/i18n/es.json +77 -77
  18. package/admin/tab.html +1 -1
  19. package/build/lib/debugger.js +1 -1
  20. package/build/lib/debugger.js.map +1 -1
  21. package/build/lib/eventObj.js +10 -2
  22. package/build/lib/eventObj.js.map +1 -1
  23. package/build/lib/inspect.js +3 -3
  24. package/build/lib/inspect.js.map +1 -1
  25. package/build/lib/sandbox.js +169 -62
  26. package/build/lib/sandbox.js.map +1 -1
  27. package/build/main.js +63 -19
  28. package/build/main.js.map +1 -1
  29. package/build/types.d.ts +4 -0
  30. package/io-package.json +14 -14
  31. package/package.json +11 -11
  32. package/admin/custom/assets/index-qs6J40Tp.js +0 -964
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isExactId = isExactId;
37
+ exports.removeFromDispatchIndex = removeFromDispatchIndex;
36
38
  exports.sandBox = sandBox;
37
39
  const jsonataMod = __importStar(require("jsonata"));
38
40
  const adapter_core_1 = require("@iobroker/adapter-core");
@@ -43,6 +45,61 @@ const eventObjMod = __importStar(require("./eventObj"));
43
45
  const patternCompareFunctions_1 = require("./patternCompareFunctions");
44
46
  const SCRIPT_CODE_MARKER = 'script.js.';
45
47
  const pattern2RegEx = adapter_core_1.commonTools.pattern2RegEx;
48
+ // Pre-compiled RegExp constants for formatTimeDiff – avoids recompiling on every call
49
+ const FTD_TEST_D = /(?<!\\)[DTД]/;
50
+ const FTD_REPL_DD = /(?<!\\)(?:DD|TT|ДД)/g;
51
+ const FTD_REPL_D = /(?<!\\)[DTД]/g;
52
+ const FTD_TEST_H = /(?<!\\)[hSч]/;
53
+ const FTD_REPL_HH = /(?<!\\)(?:hh|SS|чч)/g;
54
+ const FTD_REPL_H = /(?<!\\)[hSч]/g;
55
+ const FTD_TEST_M = /(?<!\\)[mм]/;
56
+ const FTD_REPL_MM = /(?<!\\)(?:mm|мм)/g;
57
+ const FTD_REPL_M = /(?<!\\)[mм]/g;
58
+ const FTD_TEST_S = /(?<!\\)[sс]/;
59
+ const FTD_REPL_SS = /(?<!\\)(?:ss|сс)/g;
60
+ const FTD_REPL_S = /(?<!\\)[sс]/g;
61
+ const FTD_UNESCAPE = [
62
+ [/\\([DTД])/g, '$1'],
63
+ [/\\([hSч])/g, '$1'],
64
+ [/\\([mм])/g, '$1'],
65
+ [/\\([sс])/g, '$1'],
66
+ ];
67
+ /** Maximum number of cached selector RegExps before FIFO eviction kicks in */
68
+ const _SELECTOR_REGEXP_CACHE_MAX = 1000;
69
+ /** Cache for selector-string → RegExp to avoid recompiling identical patterns (FIFO-bounded) */
70
+ const _selectorRegExpCache = new Map();
71
+ /** Monotonically increasing handler-ID counter – avoids Date.now()+random collisions */
72
+ let _handlerIdCounter = 1;
73
+ /** Returns true when patId is a plain exact state-ID (no wildcards, no RegExp notation). */
74
+ function isExactId(patId) {
75
+ return (!!patId && typeof patId === 'string' && !patId.includes('*') && !patId.includes('?') && !patId.startsWith('/'));
76
+ }
77
+ /**
78
+ * Removes a subscription from the O(1) dispatch index (subscriptionsMap / subscriptionsWildcard).
79
+ * Must use the same exact-id classification as the subscribe side (isExactId),
80
+ * otherwise entries leak in the structure that was not searched.
81
+ */
82
+ function removeFromDispatchIndex(ctx, sub) {
83
+ const patId = sub.pattern?.id;
84
+ if (isExactId(patId)) {
85
+ const bucket = ctx.subscriptionsMap.get(patId);
86
+ if (bucket) {
87
+ const pos = bucket.indexOf(sub);
88
+ if (pos !== -1) {
89
+ bucket.splice(pos, 1);
90
+ }
91
+ if (bucket.length === 0) {
92
+ ctx.subscriptionsMap.delete(patId);
93
+ }
94
+ }
95
+ }
96
+ else {
97
+ const wPos = ctx.subscriptionsWildcard.indexOf(sub);
98
+ if (wPos !== -1) {
99
+ ctx.subscriptionsWildcard.splice(wPos, 1);
100
+ }
101
+ }
102
+ }
46
103
  function sandBox(script, name, verbose, debug, context) {
47
104
  const consts = constsMod;
48
105
  const words = wordsMod;
@@ -211,14 +268,24 @@ function sandBox(script, name, verbose, debug, context) {
211
268
  * @param str The selector string to transform into a regular expression
212
269
  */
213
270
  function selectorStringToRegExp(str) {
271
+ const cached = _selectorRegExpCache.get(str);
272
+ if (cached) {
273
+ return cached;
274
+ }
214
275
  const startsWithWildcard = str[0] === '*';
215
276
  const endsWithWildcard = str[str.length - 1] === '*';
216
- // Sanitize the selector, so it is safe to use in a RegEx
217
- // Taken from https://stackoverflow.com/a/3561711/10179833 but modified
218
- // since * has a special meaning in our selector and should not be escaped
219
277
  // eslint-disable-next-line no-useless-escape
220
- str = str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
221
- return new RegExp((startsWithWildcard ? '' : '^') + str + (endsWithWildcard ? '' : '$'));
278
+ const escaped = str.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/\*/g, '.*');
279
+ const re = new RegExp((startsWithWildcard ? '' : '^') + escaped + (endsWithWildcard ? '' : '$'));
280
+ // FIFO eviction: drop the oldest entry once the cache exceeds its limit
281
+ if (_selectorRegExpCache.size >= _SELECTOR_REGEXP_CACHE_MAX) {
282
+ const oldestKey = _selectorRegExpCache.keys().next().value;
283
+ if (oldestKey !== undefined) {
284
+ _selectorRegExpCache.delete(oldestKey);
285
+ }
286
+ }
287
+ _selectorRegExpCache.set(str, re);
288
+ return re;
222
289
  }
223
290
  /**
224
291
  * Adds a regular expression for selectors targeting the state ID
@@ -741,8 +808,8 @@ function sandBox(script, name, verbose, debug, context) {
741
808
  if (!context.channels || !context.devices) {
742
809
  context.channels = {};
743
810
  context.devices = {};
744
- for (const _id in objects) {
745
- if (Object.prototype.hasOwnProperty.call(objects, _id) && objects[_id].type === 'state') {
811
+ for (const _id of Object.keys(objects)) {
812
+ if (objects[_id].type === 'state') {
746
813
  const parts = _id.split('.');
747
814
  parts.pop();
748
815
  const chn = parts.join('.');
@@ -759,8 +826,8 @@ function sandBox(script, name, verbose, debug, context) {
759
826
  if (name === 'schedule') {
760
827
  if (!context.schedules) {
761
828
  context.schedules = [];
762
- for (const _id in objects) {
763
- if (Object.prototype.hasOwnProperty.call(objects, _id) && objects[_id].type === 'schedule') {
829
+ for (const _id of Object.keys(objects)) {
830
+ if (objects[_id].type === 'schedule') {
764
831
  context.schedules.push(_id);
765
832
  }
766
833
  }
@@ -1087,7 +1154,7 @@ function sandBox(script, name, verbose, debug, context) {
1087
1154
  sandbox.log(`Invalid callback for onLog`, 'warn');
1088
1155
  return 0;
1089
1156
  }
1090
- const handler = { id: Date.now() + Math.floor(Math.random() * 10000), cb: callback, sandbox, severity };
1157
+ const handler = { id: _handlerIdCounter++, cb: callback, sandbox, severity };
1091
1158
  context.logSubscriptions[sandbox.scriptName] = context.logSubscriptions[sandbox.scriptName] || [];
1092
1159
  context.logSubscriptions[sandbox.scriptName].push(handler);
1093
1160
  context.updateLogSubscriptions();
@@ -1455,6 +1522,16 @@ function sandBox(script, name, verbose, debug, context) {
1455
1522
  subscribePattern(script, oPattern.id);
1456
1523
  subs.patternCompareFunctions = getPatternCompareFunctions(oPattern);
1457
1524
  context.subscriptions.push(subs);
1525
+ // O(1) dispatch index: exact string IDs go into the map, everything else into the wildcard array
1526
+ if (isExactId(oPattern.id)) {
1527
+ if (!context.subscriptionsMap.has(oPattern.id)) {
1528
+ context.subscriptionsMap.set(oPattern.id, []);
1529
+ }
1530
+ context.subscriptionsMap.get(oPattern.id).push(subs);
1531
+ }
1532
+ else {
1533
+ context.subscriptionsWildcard.push(subs);
1534
+ }
1458
1535
  if (oPattern.enumName || oPattern.enumId) {
1459
1536
  context.isEnums = true;
1460
1537
  }
@@ -1462,13 +1539,13 @@ function sandBox(script, name, verbose, debug, context) {
1462
1539
  },
1463
1540
  getSubscriptions: function () {
1464
1541
  const result = {};
1465
- for (let s = 0; s < context.subscriptions.length; s++) {
1466
- result[context.subscriptions[s].pattern.id] =
1467
- result[context.subscriptions[s].pattern.id] || [];
1468
- result[context.subscriptions[s].pattern.id].push({
1469
- name: context.subscriptions[s].name,
1470
- pattern: context.subscriptions[s].pattern,
1471
- });
1542
+ // Iterate over the O(1) dispatch index instead of the flat array
1543
+ for (const [id, bucket] of context.subscriptionsMap) {
1544
+ result[id] = bucket.map(s => ({ name: s.name, pattern: s.pattern }));
1545
+ }
1546
+ for (const s of context.subscriptionsWildcard) {
1547
+ const key = s.pattern.id;
1548
+ (result[key] ??= []).push({ name: s.name, pattern: s.pattern });
1472
1549
  }
1473
1550
  if (sandbox.verbose) {
1474
1551
  sandbox.log(`getSubscriptions() => ${JSON.stringify(result)}`, 'info');
@@ -1527,8 +1604,11 @@ function sandBox(script, name, verbose, debug, context) {
1527
1604
  if ((0, tools_1.isObject)(idOrObject)) {
1528
1605
  for (let i = context.subscriptions.length - 1; i >= 0; i--) {
1529
1606
  if (context.subscriptions[i] === idOrObject) {
1530
- unsubscribePattern(script, context.subscriptions[i].pattern.id);
1607
+ const sub = context.subscriptions[i];
1608
+ unsubscribePattern(script, sub.pattern.id);
1531
1609
  context.subscriptions.splice(i, 1);
1610
+ // Remove from O(1) dispatch structures
1611
+ removeFromDispatchIndex(context, sub);
1532
1612
  sandbox.__engine.__subscriptions--;
1533
1613
  return true;
1534
1614
  }
@@ -1539,8 +1619,11 @@ function sandBox(script, name, verbose, debug, context) {
1539
1619
  for (let i = context.subscriptions.length - 1; i >= 0; i--) {
1540
1620
  if (context.subscriptions[i].name === name && context.subscriptions[i].pattern.id === idOrObject) {
1541
1621
  deleted++;
1542
- unsubscribePattern(script, context.subscriptions[i].pattern.id);
1622
+ const sub = context.subscriptions[i];
1623
+ unsubscribePattern(script, sub.pattern.id);
1543
1624
  context.subscriptions.splice(i, 1);
1625
+ // Remove from O(1) dispatch structures
1626
+ removeFromDispatchIndex(context, sub);
1544
1627
  sandbox.__engine.__subscriptions--;
1545
1628
  }
1546
1629
  }
@@ -2459,7 +2542,7 @@ function sandBox(script, name, verbose, debug, context) {
2459
2542
  }
2460
2543
  let result;
2461
2544
  try {
2462
- result = JSON.parse(JSON.stringify(objects[id]));
2545
+ result = objects[id] ? structuredClone(objects[id]) : undefined;
2463
2546
  }
2464
2547
  catch (err) {
2465
2548
  void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
@@ -2484,9 +2567,9 @@ function sandBox(script, name, verbose, debug, context) {
2484
2567
  }
2485
2568
  if (enumName) {
2486
2569
  const e = eventObj.getObjectEnumsSync(context, id);
2487
- const obj = JSON.parse(JSON.stringify(objects[id]));
2488
- obj.enumIds = JSON.parse(JSON.stringify(e.enumIds));
2489
- obj.enumNames = JSON.parse(JSON.stringify(e.enumNames));
2570
+ const obj = structuredClone(objects[id]);
2571
+ obj.enumIds = structuredClone(e.enumIds);
2572
+ obj.enumNames = structuredClone(e.enumNames);
2490
2573
  if (typeof enumName === 'string') {
2491
2574
  const r = new RegExp(`^enum\\.${enumName}\\.`);
2492
2575
  for (let i = obj.enumIds.length - 1; i >= 0; i--) {
@@ -2502,7 +2585,7 @@ function sandBox(script, name, verbose, debug, context) {
2502
2585
  }
2503
2586
  let result;
2504
2587
  try {
2505
- result = JSON.parse(JSON.stringify(objects[id]));
2588
+ result = structuredClone(objects[id]);
2506
2589
  }
2507
2590
  catch (err) {
2508
2591
  void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
@@ -2573,7 +2656,7 @@ function sandBox(script, name, verbose, debug, context) {
2573
2656
  if (sandbox.verbose) {
2574
2657
  sandbox.log(`getEnums(enumName=${enumName}) => ${JSON.stringify(result)}`, 'info');
2575
2658
  }
2576
- return JSON.parse(JSON.stringify(result));
2659
+ return structuredClone(result);
2577
2660
  },
2578
2661
  createAlias: function (name, alias, forceCreation, common, native, callback) {
2579
2662
  if (typeof native === 'function') {
@@ -3242,7 +3325,14 @@ function sandBox(script, name, verbose, debug, context) {
3242
3325
  return;
3243
3326
  }
3244
3327
  const instances = res.rows.map(item => item.id.substring('system.adapter.'.length));
3245
- // Store in cache for subsequent calls (invalidated on system.adapter.* object change)
3328
+ // Store in cache for subsequent calls (invalidated on system.adapter.* object change).
3329
+ // LRU eviction: keep the map bounded to avoid unbounded memory growth.
3330
+ if (context.sendToInstanceCache.size >= 200) {
3331
+ const firstKey = context.sendToInstanceCache.keys().next().value;
3332
+ if (firstKey !== undefined) {
3333
+ context.sendToInstanceCache.delete(firstKey);
3334
+ }
3335
+ }
3246
3336
  context.sendToInstanceCache.set(_adapter, instances);
3247
3337
  instances.forEach(instance => {
3248
3338
  sandbox.verbose &&
@@ -3715,50 +3805,45 @@ function sandBox(script, name, verbose, debug, context) {
3715
3805
  const day = 24 * hour;
3716
3806
  const neg = diff < 0;
3717
3807
  diff = Math.abs(diff);
3718
- if (/(?<!\\)(D|T|Д)/.test(text)) {
3808
+ if (FTD_TEST_D.test(text)) {
3719
3809
  const days = Math.floor(diff / day);
3720
- text = text
3721
- .replace(/(?<!\\)(DD|TT|ДД)/g, days.toString().padStart(2, '0'))
3722
- .replace(/(?<!\\)(D|T|Д)/g, days.toString());
3810
+ text = text.replace(FTD_REPL_DD, days.toString().padStart(2, '0')).replace(FTD_REPL_D, days.toString());
3723
3811
  if (sandbox.verbose) {
3724
3812
  sandbox.log(`formatTimeDiff(format=${format}, text=${text}, days=${days})`, 'debug');
3725
3813
  }
3726
3814
  diff -= days * day;
3727
3815
  }
3728
- if (/(?<!\\)(h|S|ч)/.test(text)) {
3816
+ if (FTD_TEST_H.test(text)) {
3729
3817
  const hours = Math.floor(diff / hour);
3730
3818
  text = text
3731
- .replace(/(?<!\\)(hh|SS|чч)/g, hours.toString().padStart(2, '0'))
3732
- .replace(/(?<!\\)(h|S|ч)/g, hours.toString());
3819
+ .replace(FTD_REPL_HH, hours.toString().padStart(2, '0'))
3820
+ .replace(FTD_REPL_H, hours.toString());
3733
3821
  if (sandbox.verbose) {
3734
3822
  sandbox.log(`formatTimeDiff(format=${format}, text=${text}, hours=${hours})`, 'debug');
3735
3823
  }
3736
3824
  diff -= hours * hour;
3737
3825
  }
3738
- if (/(?<!\\)(m|м)/.test(text)) {
3826
+ if (FTD_TEST_M.test(text)) {
3739
3827
  const minutes = Math.floor(diff / minute);
3740
3828
  text = text
3741
- .replace(/(?<!\\)(mm|мм)/g, minutes.toString().padStart(2, '0'))
3742
- .replace(/(?<!\\)(m|м)/g, minutes.toString());
3829
+ .replace(FTD_REPL_MM, minutes.toString().padStart(2, '0'))
3830
+ .replace(FTD_REPL_M, minutes.toString());
3743
3831
  if (sandbox.verbose) {
3744
3832
  sandbox.log(`formatTimeDiff(format=${format}, text=${text}, minutes=${minutes})`, 'debug');
3745
3833
  }
3746
3834
  diff -= minutes * minute;
3747
3835
  }
3748
- if (/(?<!\\)(s|с)/.test(text)) {
3836
+ if (FTD_TEST_S.test(text)) {
3749
3837
  const seconds = Math.floor(diff / second);
3750
3838
  text = text
3751
- .replace(/(?<!\\)(ss|сс)/g, seconds.toString().padStart(2, '0'))
3752
- .replace(/(?<!\\)(s|с)/g, seconds.toString());
3839
+ .replace(FTD_REPL_SS, seconds.toString().padStart(2, '0'))
3840
+ .replace(FTD_REPL_S, seconds.toString());
3753
3841
  sandbox.verbose &&
3754
3842
  sandbox.log(`formatTimeDiff(format=${format}, text=${text}, seconds=${seconds})`, 'debug');
3755
- // diff -= seconds * second; // no milliseconds
3756
3843
  }
3757
- text = text
3758
- .replace(/\\(D|T|Д)/g, '$1')
3759
- .replace(/\\(h|S|ч)/g, '$1')
3760
- .replace(/\\(m|м)/g, '$1')
3761
- .replace(/\\(s|с)/g, '$1');
3844
+ for (const [pattern, replacement] of FTD_UNESCAPE) {
3845
+ text = text.replace(pattern, replacement);
3846
+ }
3762
3847
  if (sandbox.verbose) {
3763
3848
  sandbox.log(`formatTimeDiff(format=${format}, text=${text})`, 'debug');
3764
3849
  }
@@ -4382,7 +4467,7 @@ function sandBox(script, name, verbose, debug, context) {
4382
4467
  context.messageBusHandlers[sandbox.scriptName] = context.messageBusHandlers[sandbox.scriptName] || {};
4383
4468
  context.messageBusHandlers[sandbox.scriptName][messageName] =
4384
4469
  context.messageBusHandlers[sandbox.scriptName][messageName] || [];
4385
- const handler = { id: Date.now() + Math.floor(Math.random() * 10000), cb: callback, sandbox };
4470
+ const handler = { id: _handlerIdCounter++, cb: callback, sandbox };
4386
4471
  context.messageBusHandlers[sandbox.scriptName][messageName].push(handler);
4387
4472
  sandbox.__engine.__subscriptionsMessage += 1;
4388
4473
  if (sandbox.__engine.__subscriptionsMessage %
@@ -4397,23 +4482,18 @@ function sandBox(script, name, verbose, debug, context) {
4397
4482
  let found = false;
4398
4483
  if (ctx) {
4399
4484
  if (typeof idOrName === 'number') {
4400
- for (const messageName in ctx) {
4401
- if (Object.prototype.hasOwnProperty.call(ctx, messageName)) {
4402
- for (let i = 0; i < ctx[messageName].length; i++) {
4403
- if (ctx[messageName][i].id === idOrName) {
4404
- ctx[messageName].splice(i, 1);
4405
- if (!ctx[messageName].length) {
4406
- delete ctx[messageName];
4407
- sandbox.__engine.__subscriptionsMessage--;
4408
- }
4409
- found = true;
4410
- break;
4485
+ outer: for (const messageName of Object.keys(ctx)) {
4486
+ for (let i = 0; i < ctx[messageName].length; i++) {
4487
+ if (ctx[messageName][i].id === idOrName) {
4488
+ ctx[messageName].splice(i, 1);
4489
+ if (!ctx[messageName].length) {
4490
+ delete ctx[messageName];
4491
+ sandbox.__engine.__subscriptionsMessage--;
4411
4492
  }
4493
+ found = true;
4494
+ break outer;
4412
4495
  }
4413
4496
  }
4414
- if (found) {
4415
- break;
4416
- }
4417
4497
  }
4418
4498
  }
4419
4499
  else if (idOrName && ctx[idOrName]) {
@@ -4662,11 +4742,33 @@ function sandBox(script, name, verbose, debug, context) {
4662
4742
  if (sandbox.verbose) {
4663
4743
  sandbox.log(`extendObject(id=${id}, obj=${JSON.stringify(obj)})`, 'info');
4664
4744
  }
4745
+ let objClone;
4746
+ try {
4747
+ objClone = structuredClone(obj);
4748
+ }
4749
+ catch (err) {
4750
+ // e.g. DataCloneError when the object contains functions
4751
+ void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
4752
+ val: true,
4753
+ ack: true,
4754
+ c: 'extendObject',
4755
+ });
4756
+ sandbox.log(`Object "${id}" can't be copied: ${JSON.stringify(err)}`, 'error');
4757
+ if (typeof callback === 'function') {
4758
+ try {
4759
+ callback.call(sandbox, new Error(`Object "${id}" can't be copied`));
4760
+ }
4761
+ catch (err) {
4762
+ errorInCallback(err);
4763
+ }
4764
+ }
4765
+ return;
4766
+ }
4665
4767
  if (callback) {
4666
- adapter.extendForeignObject(id, JSON.parse(JSON.stringify(obj)), callback);
4768
+ adapter.extendForeignObject(id, objClone, callback);
4667
4769
  }
4668
4770
  else {
4669
- void adapter.extendForeignObject(id, JSON.parse(JSON.stringify(obj)));
4771
+ void adapter.extendForeignObject(id, objClone);
4670
4772
  }
4671
4773
  }
4672
4774
  };
@@ -4722,6 +4824,11 @@ function sandBox(script, name, verbose, debug, context) {
4722
4824
  writable: false,
4723
4825
  });
4724
4826
  }
4827
+ // Shallow-freeze the console object exposed to scripts to prevent its methods from being overwritten
4828
+ // Note: __engine must NOT be frozen because its counter properties are mutated at runtime
4829
+ if (sandbox.console && typeof sandbox.console === 'object') {
4830
+ Object.freeze(sandbox.console);
4831
+ }
4725
4832
  return sandbox;
4726
4833
  }
4727
4834
  //# sourceMappingURL=sandbox.js.map