iobroker.javascript 9.2.2 → 9.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -25
- package/admin/assets/{AiChatPanel-Bbux7LP7.js → AiChatPanel-B4vPXgLz.js} +1 -1
- package/admin/assets/{ScriptEditor-DKljeYQN.js → ScriptEditor-CmEeMfC2.js} +1 -1
- package/admin/assets/ScriptEditorVanillaMonaco-C22Gpet7.js +3 -0
- package/admin/assets/cronHoverProvider-Bg0Vz-1E.js +2 -0
- package/admin/assets/index-45IwpC7_.js +1 -0
- package/admin/assets/{index-DgE6XhUM.js → index-7mrTpzQy.js} +1 -1
- package/admin/assets/{index-DSvAO584.js → index-BTQOdA7v.js} +83 -83
- package/admin/assets/{index-_BUuZlI0.js → index-Dggem6AX.js} +1 -1
- package/admin/assets/{index-CexrwGGC.js → index-qoSy77Bg.js} +41 -37
- package/admin/assets/{localSharedImportMap-BCwqnmn4.js → localSharedImportMap-CVlAGiIu.js} +1 -1
- package/admin/assets/stateHoverProvider-DxTNxyYN.js +9 -0
- package/admin/assets/{virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-DNrEsMrg.js → virtual_mf-REMOTE_ENTRY_ID_iobroker_javascript__remoteEntry_js-CeYV4czE.js} +2 -2
- package/admin/custom/assets/Components-CGiJE5Zh.js +1 -0
- package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_react__loadShare__.mjs-sg07E3QA.js +1 -0
- package/admin/custom/assets/ConfigCustomJavascriptSet__loadShare___mf_0_emotion_mf_1_styled__loadShare__.mjs-CwjI4A-J.js +1 -0
- package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs-0SjJESw9.js → ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs-BW9Qbxx5.js} +1 -1
- package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs_commonjs-proxy-CInLgMWW.js → ConfigCustomJavascriptSet__loadShare__react__loadShare__.mjs_commonjs-proxy-2W2BYT2K.js} +1 -1
- package/admin/custom/assets/{ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CorOEig8.js → ConfigCustomJavascriptSet__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-D8zcZsJo.js} +1 -1
- package/admin/custom/assets/DefaultPropsProvider-CfKHPa-G.js +1 -0
- package/admin/custom/assets/{TextField-DNzklBSl.js → TextField-CxPfV5Sr.js} +12 -12
- package/admin/custom/assets/{ThemeProvider-B4G3jidj.js → ThemeProvider-DSTCckLJ.js} +4 -4
- package/admin/custom/assets/{createSvgIcon-ZdrQ8zSU.js → createSvgIcon-b2B2NI3g.js} +1 -1
- package/admin/custom/assets/createTheme-MsdB8zt7.js +8 -0
- package/admin/custom/assets/defaultTheme-CmVP-QNF.js +1 -0
- package/admin/custom/assets/emotion-cache.browser.esm-BgJfNZJN.js +1 -0
- package/admin/custom/assets/emotion-react.browser.esm-chZAeh_-.js +1 -0
- package/admin/custom/assets/emotion-serialize.esm-BUa21YfQ.js +1 -0
- package/admin/custom/assets/emotion-styled.browser.esm-Yi13plpH.js +1 -0
- package/admin/custom/assets/emotion-use-insertion-effect-with-fallbacks.browser.esm-CEPeBbh9.js +1 -0
- package/admin/custom/assets/extends-CF3RwP-h.js +1 -0
- package/admin/custom/assets/index-17vbGv00.js +1 -0
- package/admin/custom/assets/{index-iRHaQfi-.js → index-B5DqCD_a.js} +1 -1
- package/admin/custom/assets/{index-DNKzDki_.js → index-B6NrrORQ.js} +159 -159
- package/admin/custom/assets/index-CT5PwRfY.js +964 -0
- package/admin/custom/assets/{index-YBvFYuP6.js → index-CzRngQHl.js} +1 -1
- package/admin/custom/assets/index-DLNtxshy.js +1 -0
- package/admin/custom/assets/{index-NItS-TWp.js → index-DTbSiEk2.js} +1 -1
- package/admin/custom/assets/{index-Dp7A3XHd.js → index-DV76KW3-.js} +4 -4
- package/admin/custom/assets/{index-D0iTiVVG.js → index-DlQbqHom.js} +1 -1
- package/admin/custom/assets/{index-BptSiKBK.js → index-FTmk0JJx.js} +5 -5
- package/admin/custom/assets/{jsx-runtime-DfVU3AZG.js → jsx-runtime-BplTyRFk.js} +1 -1
- package/admin/custom/assets/localSharedImportMap-ChjFDKsq.js +1 -0
- package/admin/custom/assets/useTheme-BV4xOYIL.js +1 -0
- package/admin/custom/assets/useTheme-ldICnlmf.js +1 -0
- package/admin/custom/assets/{virtualExposes-jSvr-kK3.js → virtualExposes-D402kVGY.js} +1 -1
- package/admin/custom/assets/{virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-Br3IlnqO.js → virtual_mf-REMOTE_ENTRY_ID_ConfigCustomJavascriptSet__customComponents_js-ClQrb2n1.js} +2 -2
- package/admin/custom/customComponents.js +1 -1
- package/admin/google-blockly/own/field_oid.js +151 -2
- package/admin/i18n/es.json +77 -77
- package/admin/mf-manifest.json +1 -1
- package/admin/remoteEntry.js +1 -1
- package/admin/tab.html +10 -8
- package/build/lib/debugger.js +1 -1
- package/build/lib/debugger.js.map +1 -1
- package/build/lib/eventObj.js +31 -14
- package/build/lib/eventObj.js.map +1 -1
- package/build/lib/inspect.js +3 -3
- package/build/lib/inspect.js.map +1 -1
- package/build/lib/sandbox.js +336 -119
- package/build/lib/sandbox.js.map +1 -1
- package/build/lib/scheduler.js +18 -13
- package/build/lib/scheduler.js.map +1 -1
- package/build/main.js +282 -149
- package/build/main.js.map +1 -1
- package/build/types.d.ts +18 -6
- package/io-package.json +27 -14
- package/package.json +11 -11
- package/admin/assets/ScriptEditorVanillaMonaco-CaFyKuJ2.js +0 -3
- package/admin/assets/index-SHs7_wDh.js +0 -1
- package/admin/assets/stateHoverProvider-CGChzV5W.js +0 -3
- package/admin/custom/assets/Components-B9ZyjeRI.js +0 -1
- package/admin/custom/assets/DefaultPropsProvider-CGb21NGY.js +0 -1
- package/admin/custom/assets/createTheme-Wd4QxQE0.js +0 -8
- package/admin/custom/assets/defaultTheme-F2iJF2G2.js +0 -1
- package/admin/custom/assets/index-AZB1OHDH.js +0 -1
- package/admin/custom/assets/index-C0RmLaAj.js +0 -1
- package/admin/custom/assets/index-sozs10Ij.js +0 -960
- package/admin/custom/assets/localSharedImportMap-BxT0VwTO.js +0 -1
- package/admin/custom/assets/useTheme-BfhDdtAr.js +0 -1
- package/admin/custom/assets/useTheme-DWZ6jNyq.js +0 -1
- /package/admin/custom/assets/{index-huvZQhZw.js → index-D_5kZmfw.js} +0 -0
- /package/admin/custom/assets/{index-CmJ8HzSc.js → index-DpJ18x6R.js} +0 -0
- /package/admin/custom/assets/{leaflet-src-k2rCV-Ix.js → leaflet-src-B472P7Y1.js} +0 -0
package/build/lib/sandbox.js
CHANGED
|
@@ -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;
|
|
@@ -90,7 +147,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
90
147
|
});
|
|
91
148
|
}
|
|
92
149
|
else {
|
|
93
|
-
|
|
150
|
+
// IO-3: Object.assign instead of Object.keys().forEach() – no temporary keys array
|
|
151
|
+
adapter.getForeignStates(pattern, (_err, _states) => _states && Object.assign(states, _states));
|
|
94
152
|
}
|
|
95
153
|
}
|
|
96
154
|
else {
|
|
@@ -210,14 +268,24 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
210
268
|
* @param str The selector string to transform into a regular expression
|
|
211
269
|
*/
|
|
212
270
|
function selectorStringToRegExp(str) {
|
|
271
|
+
const cached = _selectorRegExpCache.get(str);
|
|
272
|
+
if (cached) {
|
|
273
|
+
return cached;
|
|
274
|
+
}
|
|
213
275
|
const startsWithWildcard = str[0] === '*';
|
|
214
276
|
const endsWithWildcard = str[str.length - 1] === '*';
|
|
215
|
-
// Sanitize the selector, so it is safe to use in a RegEx
|
|
216
|
-
// Taken from https://stackoverflow.com/a/3561711/10179833 but modified
|
|
217
|
-
// since * has a special meaning in our selector and should not be escaped
|
|
218
277
|
// eslint-disable-next-line no-useless-escape
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
221
289
|
}
|
|
222
290
|
/**
|
|
223
291
|
* Adds a regular expression for selectors targeting the state ID
|
|
@@ -482,9 +550,21 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
482
550
|
if (!adapter.config.subscribe && context.interimStateValues[id]) {
|
|
483
551
|
// if the state is changed, we will compare it with interimStateValues
|
|
484
552
|
const oldState = context.interimStateValues[id];
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
553
|
+
// IO-1: for…in instead of Object.keys().filter().every() – no temporary array per call
|
|
554
|
+
let stateHasChanged = false;
|
|
555
|
+
for (const attr in stateAsObject) {
|
|
556
|
+
if (attr === 'ts') {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (stateAsObject[attr] === undefined) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (stateAsObject[attr] !== oldState[attr]) {
|
|
563
|
+
stateHasChanged = true;
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (stateHasChanged) {
|
|
488
568
|
// state is changed for sure, and we will call setForeignState
|
|
489
569
|
// and store new state to interimStateValues
|
|
490
570
|
context.interimStateValues[id] = stateAsObject;
|
|
@@ -728,17 +808,17 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
728
808
|
if (!context.channels || !context.devices) {
|
|
729
809
|
context.channels = {};
|
|
730
810
|
context.devices = {};
|
|
731
|
-
for (const _id
|
|
732
|
-
if (
|
|
811
|
+
for (const _id of Object.keys(objects)) {
|
|
812
|
+
if (objects[_id].type === 'state') {
|
|
733
813
|
const parts = _id.split('.');
|
|
734
814
|
parts.pop();
|
|
735
815
|
const chn = parts.join('.');
|
|
736
816
|
parts.pop();
|
|
737
817
|
const dev = parts.join('.');
|
|
738
|
-
context.devices[dev] = context.devices[dev] ||
|
|
739
|
-
context.devices[dev].
|
|
740
|
-
context.channels[chn] = context.channels[chn] ||
|
|
741
|
-
context.channels[chn].
|
|
818
|
+
context.devices[dev] = context.devices[dev] || new Set();
|
|
819
|
+
context.devices[dev].add(_id);
|
|
820
|
+
context.channels[chn] = context.channels[chn] || new Set();
|
|
821
|
+
context.channels[chn].add(_id);
|
|
742
822
|
}
|
|
743
823
|
}
|
|
744
824
|
}
|
|
@@ -746,8 +826,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
746
826
|
if (name === 'schedule') {
|
|
747
827
|
if (!context.schedules) {
|
|
748
828
|
context.schedules = [];
|
|
749
|
-
for (const _id
|
|
750
|
-
if (
|
|
829
|
+
for (const _id of Object.keys(objects)) {
|
|
830
|
+
if (objects[_id].type === 'schedule') {
|
|
751
831
|
context.schedules.push(_id);
|
|
752
832
|
}
|
|
753
833
|
}
|
|
@@ -851,7 +931,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
851
931
|
}
|
|
852
932
|
// retrieve the state ID collection for all remaining channels
|
|
853
933
|
res = res
|
|
854
|
-
.map(id => channels[id])
|
|
934
|
+
.map(id => [...(channels[id] || [])])
|
|
855
935
|
// and flatten the array to get only the state IDs
|
|
856
936
|
.reduce((acc, next) => acc.concat(next), []);
|
|
857
937
|
// now filter out those that don't match every ID selector for the state ID
|
|
@@ -884,7 +964,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
884
964
|
}
|
|
885
965
|
// retrieve the state ID collection for all remaining devices
|
|
886
966
|
res = res
|
|
887
|
-
.map(id => devices[id])
|
|
967
|
+
.map(id => [...(devices[id] || [])])
|
|
888
968
|
// and flatten the array to get only the state IDs
|
|
889
969
|
.reduce((acc, next) => acc.concat(next), []);
|
|
890
970
|
// now filter out those that don't match every ID selector for the state ID
|
|
@@ -921,12 +1001,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
921
1001
|
res = res.filter(id => applyEnumSelectors(id));
|
|
922
1002
|
}
|
|
923
1003
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (!resUnique.includes(res[i])) {
|
|
927
|
-
resUnique.push(res[i]);
|
|
928
|
-
}
|
|
929
|
-
}
|
|
1004
|
+
// IO-2: O(1) deduplication via Set instead of O(n²) resUnique.includes()
|
|
1005
|
+
const resUnique = [...new Set(res)];
|
|
930
1006
|
for (let i = 0; i < resUnique.length; i++) {
|
|
931
1007
|
result[i] = resUnique[i];
|
|
932
1008
|
}
|
|
@@ -1078,7 +1154,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1078
1154
|
sandbox.log(`Invalid callback for onLog`, 'warn');
|
|
1079
1155
|
return 0;
|
|
1080
1156
|
}
|
|
1081
|
-
const handler = { id:
|
|
1157
|
+
const handler = { id: _handlerIdCounter++, cb: callback, sandbox, severity };
|
|
1082
1158
|
context.logSubscriptions[sandbox.scriptName] = context.logSubscriptions[sandbox.scriptName] || [];
|
|
1083
1159
|
context.logSubscriptions[sandbox.scriptName].push(handler);
|
|
1084
1160
|
context.updateLogSubscriptions();
|
|
@@ -1355,8 +1431,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1355
1431
|
if (oPattern?.id && Array.isArray(oPattern.id)) {
|
|
1356
1432
|
const result = [];
|
|
1357
1433
|
for (let t = 0; t < oPattern.id.length; t++) {
|
|
1358
|
-
|
|
1359
|
-
pa
|
|
1434
|
+
// IO-4: Spread instead of JSON.parse(JSON.stringify()) – no deep clone needed (only primitive fields)
|
|
1435
|
+
const pa = { ...oPattern, id: oPattern.id[t] };
|
|
1360
1436
|
result.push(sandbox.subscribe(pa, callbackOrChangeTypeOrId, value));
|
|
1361
1437
|
}
|
|
1362
1438
|
return result;
|
|
@@ -1431,10 +1507,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1431
1507
|
const _adapter = `system.adapter.${a}`;
|
|
1432
1508
|
if (objects[_adapter] && objects[_adapter].common && objects[_adapter].common.subscribable) {
|
|
1433
1509
|
const alive = `system.adapter.${a}.alive`;
|
|
1434
|
-
context.adapterSubs[alive] = context.adapterSubs[alive] ||
|
|
1435
|
-
|
|
1510
|
+
context.adapterSubs[alive] = context.adapterSubs[alive] || new Set();
|
|
1511
|
+
// Set.has() is O(1) and automatically prevents duplicates
|
|
1512
|
+
const subExists = context.adapterSubs[alive].has(oPattern.id);
|
|
1436
1513
|
if (!subExists) {
|
|
1437
|
-
context.adapterSubs[alive].
|
|
1514
|
+
context.adapterSubs[alive].add(oPattern.id);
|
|
1438
1515
|
adapter.sendTo(a, 'subscribe', oPattern.id);
|
|
1439
1516
|
}
|
|
1440
1517
|
}
|
|
@@ -1445,6 +1522,16 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1445
1522
|
subscribePattern(script, oPattern.id);
|
|
1446
1523
|
subs.patternCompareFunctions = getPatternCompareFunctions(oPattern);
|
|
1447
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
|
+
}
|
|
1448
1535
|
if (oPattern.enumName || oPattern.enumId) {
|
|
1449
1536
|
context.isEnums = true;
|
|
1450
1537
|
}
|
|
@@ -1452,13 +1539,13 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1452
1539
|
},
|
|
1453
1540
|
getSubscriptions: function () {
|
|
1454
1541
|
const result = {};
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
});
|
|
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 });
|
|
1462
1549
|
}
|
|
1463
1550
|
if (sandbox.verbose) {
|
|
1464
1551
|
sandbox.log(`getSubscriptions() => ${JSON.stringify(result)}`, 'info');
|
|
@@ -1491,8 +1578,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1491
1578
|
if (objects[_adapter]?.common?.subscribable) {
|
|
1492
1579
|
const a = `${parts[0]}.${parts[1]}`;
|
|
1493
1580
|
const alive = `system.adapter.${a}.alive`;
|
|
1494
|
-
context.adapterSubs[alive] = context.adapterSubs[alive] ||
|
|
1495
|
-
context.adapterSubs[alive].
|
|
1581
|
+
context.adapterSubs[alive] = context.adapterSubs[alive] || new Set();
|
|
1582
|
+
context.adapterSubs[alive].add(id);
|
|
1496
1583
|
if (sandbox.verbose) {
|
|
1497
1584
|
sandbox.log(`adapterSubscribe: ${a} - ${id}`, 'info');
|
|
1498
1585
|
}
|
|
@@ -1517,8 +1604,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1517
1604
|
if ((0, tools_1.isObject)(idOrObject)) {
|
|
1518
1605
|
for (let i = context.subscriptions.length - 1; i >= 0; i--) {
|
|
1519
1606
|
if (context.subscriptions[i] === idOrObject) {
|
|
1520
|
-
|
|
1607
|
+
const sub = context.subscriptions[i];
|
|
1608
|
+
unsubscribePattern(script, sub.pattern.id);
|
|
1521
1609
|
context.subscriptions.splice(i, 1);
|
|
1610
|
+
// Remove from O(1) dispatch structures
|
|
1611
|
+
removeFromDispatchIndex(context, sub);
|
|
1522
1612
|
sandbox.__engine.__subscriptions--;
|
|
1523
1613
|
return true;
|
|
1524
1614
|
}
|
|
@@ -1529,8 +1619,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1529
1619
|
for (let i = context.subscriptions.length - 1; i >= 0; i--) {
|
|
1530
1620
|
if (context.subscriptions[i].name === name && context.subscriptions[i].pattern.id === idOrObject) {
|
|
1531
1621
|
deleted++;
|
|
1532
|
-
|
|
1622
|
+
const sub = context.subscriptions[i];
|
|
1623
|
+
unsubscribePattern(script, sub.pattern.id);
|
|
1533
1624
|
context.subscriptions.splice(i, 1);
|
|
1625
|
+
// Remove from O(1) dispatch structures
|
|
1626
|
+
removeFromDispatchIndex(context, sub);
|
|
1534
1627
|
sandbox.__engine.__subscriptions--;
|
|
1535
1628
|
}
|
|
1536
1629
|
}
|
|
@@ -1542,7 +1635,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1542
1635
|
return sandbox.subscribe(pattern, callbackOrChangeTypeOrId, value);
|
|
1543
1636
|
},
|
|
1544
1637
|
onEnumMembers: function (enumId, callback) {
|
|
1545
|
-
if (enums.
|
|
1638
|
+
if (enums.has(enumId)) {
|
|
1546
1639
|
const subscriptions = {};
|
|
1547
1640
|
const init = () => {
|
|
1548
1641
|
const obj = objects[enumId];
|
|
@@ -1557,7 +1650,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
1557
1650
|
}
|
|
1558
1651
|
// Subscribe to all members of enum
|
|
1559
1652
|
for (const objId of members) {
|
|
1560
|
-
|
|
1653
|
+
// IO-6: `in` operator instead of Object.keys().includes() – O(1) instead of O(n)
|
|
1654
|
+
if (!(objId in subscriptions)) {
|
|
1561
1655
|
if (objects?.[objId]?.type === 'state') {
|
|
1562
1656
|
// Just subscribe to states
|
|
1563
1657
|
subscriptions[objId] = sandbox.subscribe(objId, callback); // TODO: more features
|
|
@@ -2059,11 +2153,13 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2059
2153
|
const schedules = context.scheduler?.getList() || [];
|
|
2060
2154
|
if (allScripts) {
|
|
2061
2155
|
Object.keys(context.scripts).forEach(name => context.scripts[name].schedules &&
|
|
2062
|
-
|
|
2156
|
+
// IO-8: Spread instead of JSON.parse(JSON.stringify()) – _ioBroker has only primitive fields
|
|
2157
|
+
context.scripts[name].schedules.forEach(s => schedules.push({ ...s._ioBroker })));
|
|
2063
2158
|
}
|
|
2064
2159
|
else {
|
|
2065
2160
|
script.schedules &&
|
|
2066
|
-
|
|
2161
|
+
// IO-8: Spread instead of JSON.parse(JSON.stringify())
|
|
2162
|
+
script.schedules.forEach(s => schedules.push({ ...s._ioBroker }));
|
|
2067
2163
|
}
|
|
2068
2164
|
return schedules;
|
|
2069
2165
|
},
|
|
@@ -2100,10 +2196,22 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2100
2196
|
if (timers[id]) {
|
|
2101
2197
|
sandbox.verbose &&
|
|
2102
2198
|
sandbox.log(`setStateDelayed: clear ${timers[id].length} running timers`, 'info');
|
|
2199
|
+
// collect affected scriptNames before deleting
|
|
2200
|
+
const affectedScripts = new Set(timers[id].map(e => e.scriptName));
|
|
2103
2201
|
for (let i = 0; i < timers[id].length; i++) {
|
|
2104
2202
|
clearTimeout(timers[id][i].t);
|
|
2105
2203
|
}
|
|
2106
2204
|
delete timers[id];
|
|
2205
|
+
// update timersByScript reverse-index for all affected scripts
|
|
2206
|
+
for (const scriptName of affectedScripts) {
|
|
2207
|
+
const scriptSet = context.timersByScript.get(scriptName);
|
|
2208
|
+
if (scriptSet) {
|
|
2209
|
+
scriptSet.delete(id);
|
|
2210
|
+
if (!scriptSet.size) {
|
|
2211
|
+
context.timersByScript.delete(scriptName);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2107
2215
|
}
|
|
2108
2216
|
else {
|
|
2109
2217
|
if (sandbox.verbose) {
|
|
@@ -2130,18 +2238,36 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2130
2238
|
if (timers[_id]) {
|
|
2131
2239
|
// optimisation
|
|
2132
2240
|
if (timers[_id].length === 1) {
|
|
2241
|
+
const scriptName = timers[_id][0].scriptName;
|
|
2133
2242
|
delete timers[_id];
|
|
2243
|
+
// update timersByScript reverse-index
|
|
2244
|
+
const scriptSet = context.timersByScript.get(scriptName);
|
|
2245
|
+
if (scriptSet) {
|
|
2246
|
+
scriptSet.delete(_id);
|
|
2247
|
+
if (!scriptSet.size) {
|
|
2248
|
+
context.timersByScript.delete(scriptName);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2134
2251
|
}
|
|
2135
2252
|
else {
|
|
2136
2253
|
for (let t = 0; t < timers[_id].length; t++) {
|
|
2137
2254
|
if (timers[_id][t].id === _timerId) {
|
|
2255
|
+
const scriptName = timers[_id][t].scriptName;
|
|
2138
2256
|
timers[_id].splice(t, 1);
|
|
2257
|
+
if (!timers[_id].length) {
|
|
2258
|
+
delete timers[_id];
|
|
2259
|
+
// update timersByScript reverse-index
|
|
2260
|
+
const scriptSet = context.timersByScript.get(scriptName);
|
|
2261
|
+
if (scriptSet) {
|
|
2262
|
+
scriptSet.delete(_id);
|
|
2263
|
+
if (!scriptSet.size) {
|
|
2264
|
+
context.timersByScript.delete(scriptName);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2139
2268
|
break;
|
|
2140
2269
|
}
|
|
2141
2270
|
}
|
|
2142
|
-
if (!timers[_id].length) {
|
|
2143
|
-
delete timers[_id];
|
|
2144
|
-
}
|
|
2145
2271
|
}
|
|
2146
2272
|
}
|
|
2147
2273
|
}, delay, context.timerId, id, state, isAck);
|
|
@@ -2161,6 +2287,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2161
2287
|
: isAck,
|
|
2162
2288
|
scriptName: name,
|
|
2163
2289
|
});
|
|
2290
|
+
// Keep reverse-index in sync for O(1) cleanup in stopScript
|
|
2291
|
+
if (!context.timersByScript.has(name)) {
|
|
2292
|
+
context.timersByScript.set(name, new Set());
|
|
2293
|
+
}
|
|
2294
|
+
context.timersByScript.get(name).add(id);
|
|
2164
2295
|
return context.timerId;
|
|
2165
2296
|
},
|
|
2166
2297
|
clearStateDelayed: function (id, timerId) {
|
|
@@ -2172,14 +2303,18 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2172
2303
|
sandbox.log(`clearStateDelayed(id=${id}, timerId=${timerId})`, 'info');
|
|
2173
2304
|
}
|
|
2174
2305
|
if (timers[id]) {
|
|
2306
|
+
// collect scriptNames of entries that will be removed
|
|
2307
|
+
const removedScripts = new Set();
|
|
2175
2308
|
for (let i = timers[id].length - 1; i >= 0; i--) {
|
|
2176
2309
|
if (timerId === undefined || timers[id][i].id === timerId) {
|
|
2310
|
+
const clearedTimerId = timers[id][i].id;
|
|
2311
|
+
removedScripts.add(timers[id][i].scriptName);
|
|
2177
2312
|
clearTimeout(timers[id][i].t);
|
|
2178
2313
|
if (timerId !== undefined) {
|
|
2179
2314
|
timers[id].splice(i, 1);
|
|
2180
2315
|
}
|
|
2181
2316
|
if (sandbox.verbose) {
|
|
2182
|
-
sandbox.log(`clearStateDelayed: clear timer ${
|
|
2317
|
+
sandbox.log(`clearStateDelayed: clear timer ${clearedTimerId}`, 'info');
|
|
2183
2318
|
}
|
|
2184
2319
|
}
|
|
2185
2320
|
}
|
|
@@ -2191,6 +2326,25 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2191
2326
|
delete timers[id];
|
|
2192
2327
|
}
|
|
2193
2328
|
}
|
|
2329
|
+
// IO-7: keep the timersByScript reverse-index in sync. For every script whose
|
|
2330
|
+
// timer(s) we just removed, drop `id` from its set – unless that script still has
|
|
2331
|
+
// another timer for this state (other scripts' timers may keep timers[id] alive).
|
|
2332
|
+
if (removedScripts.size) {
|
|
2333
|
+
const remaining = timers[id]; // undefined if the whole entry was deleted
|
|
2334
|
+
for (const scriptName of removedScripts) {
|
|
2335
|
+
const stateIds = context.timersByScript.get(scriptName);
|
|
2336
|
+
if (!stateIds) {
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
if (remaining?.some(e => e.scriptName === scriptName)) {
|
|
2340
|
+
continue;
|
|
2341
|
+
}
|
|
2342
|
+
stateIds.delete(id);
|
|
2343
|
+
if (!stateIds.size) {
|
|
2344
|
+
context.timersByScript.delete(scriptName);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2194
2348
|
return true;
|
|
2195
2349
|
}
|
|
2196
2350
|
return false;
|
|
@@ -2388,7 +2542,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2388
2542
|
}
|
|
2389
2543
|
let result;
|
|
2390
2544
|
try {
|
|
2391
|
-
result =
|
|
2545
|
+
result = objects[id] ? structuredClone(objects[id]) : undefined;
|
|
2392
2546
|
}
|
|
2393
2547
|
catch (err) {
|
|
2394
2548
|
void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
|
|
@@ -2413,9 +2567,9 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2413
2567
|
}
|
|
2414
2568
|
if (enumName) {
|
|
2415
2569
|
const e = eventObj.getObjectEnumsSync(context, id);
|
|
2416
|
-
const obj =
|
|
2417
|
-
obj.enumIds =
|
|
2418
|
-
obj.enumNames =
|
|
2570
|
+
const obj = structuredClone(objects[id]);
|
|
2571
|
+
obj.enumIds = structuredClone(e.enumIds);
|
|
2572
|
+
obj.enumNames = structuredClone(e.enumNames);
|
|
2419
2573
|
if (typeof enumName === 'string') {
|
|
2420
2574
|
const r = new RegExp(`^enum\\.${enumName}\\.`);
|
|
2421
2575
|
for (let i = obj.enumIds.length - 1; i >= 0; i--) {
|
|
@@ -2431,7 +2585,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2431
2585
|
}
|
|
2432
2586
|
let result;
|
|
2433
2587
|
try {
|
|
2434
|
-
result =
|
|
2588
|
+
result = structuredClone(objects[id]);
|
|
2435
2589
|
}
|
|
2436
2590
|
catch (err) {
|
|
2437
2591
|
void adapter.setState(`scriptProblem.${name.substring(SCRIPT_CODE_MARKER.length)}`, {
|
|
@@ -2489,11 +2643,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2489
2643
|
getEnums: function (enumName) {
|
|
2490
2644
|
const result = [];
|
|
2491
2645
|
const r = enumName ? new RegExp(`^enum\\.${enumName}\\.`) : false;
|
|
2492
|
-
for (
|
|
2493
|
-
if (!r || r.test(
|
|
2494
|
-
const common = objects[
|
|
2646
|
+
for (const enumId of enums) {
|
|
2647
|
+
if (!r || r.test(enumId)) {
|
|
2648
|
+
const common = objects[enumId].common || {};
|
|
2495
2649
|
result.push({
|
|
2496
|
-
id:
|
|
2650
|
+
id: enumId,
|
|
2497
2651
|
members: common.members || [],
|
|
2498
2652
|
name: common.name || '',
|
|
2499
2653
|
});
|
|
@@ -2502,7 +2656,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
2502
2656
|
if (sandbox.verbose) {
|
|
2503
2657
|
sandbox.log(`getEnums(enumName=${enumName}) => ${JSON.stringify(result)}`, 'info');
|
|
2504
2658
|
}
|
|
2505
|
-
return
|
|
2659
|
+
return structuredClone(result);
|
|
2506
2660
|
},
|
|
2507
2661
|
createAlias: function (name, alias, forceCreation, common, native, callback) {
|
|
2508
2662
|
if (typeof native === 'function') {
|
|
@@ -3154,19 +3308,39 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3154
3308
|
adapter.sendTo(_adapter, cmd, msg, cbFunc, options);
|
|
3155
3309
|
}
|
|
3156
3310
|
else {
|
|
3157
|
-
//
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
return;
|
|
3162
|
-
}
|
|
3163
|
-
const instances = res.rows.map(item => item.id.substring('system.adapter.'.length));
|
|
3164
|
-
instances.forEach(instance => {
|
|
3311
|
+
// IO-9: Cache sendTo broadcast instance list – avoid repeated getObjectView DB calls
|
|
3312
|
+
const cached = context.sendToInstanceCache.get(_adapter);
|
|
3313
|
+
if (cached) {
|
|
3314
|
+
cached.forEach(instance => {
|
|
3165
3315
|
sandbox.verbose &&
|
|
3166
|
-
sandbox.log(`sendTo(instance=${instance}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'})`, 'info');
|
|
3316
|
+
sandbox.log(`sendTo(instance=${instance}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'}) [cached]`, 'info');
|
|
3167
3317
|
adapter.sendTo(instance, cmd, msg, cbFunc, options);
|
|
3168
3318
|
});
|
|
3169
|
-
}
|
|
3319
|
+
}
|
|
3320
|
+
else {
|
|
3321
|
+
// Send it to all instances
|
|
3322
|
+
context.adapter.getObjectView('system', 'instance', { startkey: `system.adapter.${_adapter}.`, endkey: `system.adapter.${_adapter}.\u9999` }, options, (err, res) => {
|
|
3323
|
+
if (err || !res) {
|
|
3324
|
+
sandbox.log(`sendTo failed: ${err?.message}`, 'error');
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const instances = res.rows.map(item => item.id.substring('system.adapter.'.length));
|
|
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
|
+
}
|
|
3336
|
+
context.sendToInstanceCache.set(_adapter, instances);
|
|
3337
|
+
instances.forEach(instance => {
|
|
3338
|
+
sandbox.verbose &&
|
|
3339
|
+
sandbox.log(`sendTo(instance=${instance}, cmd=${cmd}, msg=${JSON.stringify(msg)}, hasCallback=${typeof callback === 'function'})`, 'info');
|
|
3340
|
+
adapter.sendTo(instance, cmd, msg, cbFunc, options);
|
|
3341
|
+
});
|
|
3342
|
+
});
|
|
3343
|
+
}
|
|
3170
3344
|
}
|
|
3171
3345
|
},
|
|
3172
3346
|
sendto: function (_adapter, cmd, msg, callback) {
|
|
@@ -3230,7 +3404,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3230
3404
|
errorInCallback(err);
|
|
3231
3405
|
}
|
|
3232
3406
|
}, ms);
|
|
3233
|
-
|
|
3407
|
+
// IO-10: Set.add() – O(1) instead of Array.push()
|
|
3408
|
+
script.intervals.add(int);
|
|
3234
3409
|
if (sandbox.verbose) {
|
|
3235
3410
|
sandbox.log(`setInterval(ms=${ms})`, 'info');
|
|
3236
3411
|
}
|
|
@@ -3240,13 +3415,13 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3240
3415
|
return null;
|
|
3241
3416
|
},
|
|
3242
3417
|
clearInterval: function (id) {
|
|
3243
|
-
|
|
3244
|
-
if (
|
|
3418
|
+
// IO-10: Set.has/delete – O(1) instead of Array.indexOf+splice O(n)
|
|
3419
|
+
if (script.intervals.has(id)) {
|
|
3245
3420
|
if (sandbox.verbose) {
|
|
3246
3421
|
sandbox.log('clearInterval() => cleared', 'info');
|
|
3247
3422
|
}
|
|
3248
3423
|
clearInterval(id);
|
|
3249
|
-
script.intervals.
|
|
3424
|
+
script.intervals.delete(id);
|
|
3250
3425
|
}
|
|
3251
3426
|
else {
|
|
3252
3427
|
if (sandbox.verbose) {
|
|
@@ -3257,11 +3432,8 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3257
3432
|
setTimeout: function (callback, ms, ...args) {
|
|
3258
3433
|
if (typeof callback === 'function') {
|
|
3259
3434
|
const to = setTimeout(() => {
|
|
3260
|
-
//
|
|
3261
|
-
|
|
3262
|
-
if (pos !== -1) {
|
|
3263
|
-
script.timeouts.splice(pos, 1);
|
|
3264
|
-
}
|
|
3435
|
+
// IO-10: Set.delete – O(1) instead of Array.indexOf+splice O(n)
|
|
3436
|
+
script.timeouts.delete(to);
|
|
3265
3437
|
try {
|
|
3266
3438
|
callback.call(sandbox, ...args);
|
|
3267
3439
|
}
|
|
@@ -3272,20 +3444,21 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3272
3444
|
if (sandbox.verbose) {
|
|
3273
3445
|
sandbox.log(`setTimeout(ms=${ms})`, 'info');
|
|
3274
3446
|
}
|
|
3275
|
-
|
|
3447
|
+
// IO-10: Set.add – O(1) instead of Array.push
|
|
3448
|
+
script.timeouts.add(to);
|
|
3276
3449
|
return to;
|
|
3277
3450
|
}
|
|
3278
3451
|
sandbox.log(`Invalid callback for setTimeout! - ${typeof callback}`, 'error');
|
|
3279
3452
|
return null;
|
|
3280
3453
|
},
|
|
3281
3454
|
clearTimeout: function (id) {
|
|
3282
|
-
|
|
3283
|
-
if (
|
|
3455
|
+
// IO-10: Set.has/delete – O(1) instead of Array.indexOf+splice O(n)
|
|
3456
|
+
if (script.timeouts.has(id)) {
|
|
3284
3457
|
if (sandbox.verbose) {
|
|
3285
3458
|
sandbox.log('clearTimeout() => cleared', 'info');
|
|
3286
3459
|
}
|
|
3287
3460
|
clearTimeout(id);
|
|
3288
|
-
script.timeouts.
|
|
3461
|
+
script.timeouts.delete(id);
|
|
3289
3462
|
}
|
|
3290
3463
|
else {
|
|
3291
3464
|
if (sandbox.verbose) {
|
|
@@ -3632,50 +3805,45 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
3632
3805
|
const day = 24 * hour;
|
|
3633
3806
|
const neg = diff < 0;
|
|
3634
3807
|
diff = Math.abs(diff);
|
|
3635
|
-
if (
|
|
3808
|
+
if (FTD_TEST_D.test(text)) {
|
|
3636
3809
|
const days = Math.floor(diff / day);
|
|
3637
|
-
text = text
|
|
3638
|
-
.replace(/(?<!\\)(DD|TT|ДД)/g, days.toString().padStart(2, '0'))
|
|
3639
|
-
.replace(/(?<!\\)(D|T|Д)/g, days.toString());
|
|
3810
|
+
text = text.replace(FTD_REPL_DD, days.toString().padStart(2, '0')).replace(FTD_REPL_D, days.toString());
|
|
3640
3811
|
if (sandbox.verbose) {
|
|
3641
3812
|
sandbox.log(`formatTimeDiff(format=${format}, text=${text}, days=${days})`, 'debug');
|
|
3642
3813
|
}
|
|
3643
3814
|
diff -= days * day;
|
|
3644
3815
|
}
|
|
3645
|
-
if (
|
|
3816
|
+
if (FTD_TEST_H.test(text)) {
|
|
3646
3817
|
const hours = Math.floor(diff / hour);
|
|
3647
3818
|
text = text
|
|
3648
|
-
.replace(
|
|
3649
|
-
.replace(
|
|
3819
|
+
.replace(FTD_REPL_HH, hours.toString().padStart(2, '0'))
|
|
3820
|
+
.replace(FTD_REPL_H, hours.toString());
|
|
3650
3821
|
if (sandbox.verbose) {
|
|
3651
3822
|
sandbox.log(`formatTimeDiff(format=${format}, text=${text}, hours=${hours})`, 'debug');
|
|
3652
3823
|
}
|
|
3653
3824
|
diff -= hours * hour;
|
|
3654
3825
|
}
|
|
3655
|
-
if (
|
|
3826
|
+
if (FTD_TEST_M.test(text)) {
|
|
3656
3827
|
const minutes = Math.floor(diff / minute);
|
|
3657
3828
|
text = text
|
|
3658
|
-
.replace(
|
|
3659
|
-
.replace(
|
|
3829
|
+
.replace(FTD_REPL_MM, minutes.toString().padStart(2, '0'))
|
|
3830
|
+
.replace(FTD_REPL_M, minutes.toString());
|
|
3660
3831
|
if (sandbox.verbose) {
|
|
3661
3832
|
sandbox.log(`formatTimeDiff(format=${format}, text=${text}, minutes=${minutes})`, 'debug');
|
|
3662
3833
|
}
|
|
3663
3834
|
diff -= minutes * minute;
|
|
3664
3835
|
}
|
|
3665
|
-
if (
|
|
3836
|
+
if (FTD_TEST_S.test(text)) {
|
|
3666
3837
|
const seconds = Math.floor(diff / second);
|
|
3667
3838
|
text = text
|
|
3668
|
-
.replace(
|
|
3669
|
-
.replace(
|
|
3839
|
+
.replace(FTD_REPL_SS, seconds.toString().padStart(2, '0'))
|
|
3840
|
+
.replace(FTD_REPL_S, seconds.toString());
|
|
3670
3841
|
sandbox.verbose &&
|
|
3671
3842
|
sandbox.log(`formatTimeDiff(format=${format}, text=${text}, seconds=${seconds})`, 'debug');
|
|
3672
|
-
// diff -= seconds * second; // no milliseconds
|
|
3673
3843
|
}
|
|
3674
|
-
|
|
3675
|
-
.replace(
|
|
3676
|
-
|
|
3677
|
-
.replace(/\\(m|м)/g, '$1')
|
|
3678
|
-
.replace(/\\(s|с)/g, '$1');
|
|
3844
|
+
for (const [pattern, replacement] of FTD_UNESCAPE) {
|
|
3845
|
+
text = text.replace(pattern, replacement);
|
|
3846
|
+
}
|
|
3679
3847
|
if (sandbox.verbose) {
|
|
3680
3848
|
sandbox.log(`formatTimeDiff(format=${format}, text=${text})`, 'debug');
|
|
3681
3849
|
}
|
|
@@ -4299,7 +4467,7 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4299
4467
|
context.messageBusHandlers[sandbox.scriptName] = context.messageBusHandlers[sandbox.scriptName] || {};
|
|
4300
4468
|
context.messageBusHandlers[sandbox.scriptName][messageName] =
|
|
4301
4469
|
context.messageBusHandlers[sandbox.scriptName][messageName] || [];
|
|
4302
|
-
const handler = { id:
|
|
4470
|
+
const handler = { id: _handlerIdCounter++, cb: callback, sandbox };
|
|
4303
4471
|
context.messageBusHandlers[sandbox.scriptName][messageName].push(handler);
|
|
4304
4472
|
sandbox.__engine.__subscriptionsMessage += 1;
|
|
4305
4473
|
if (sandbox.__engine.__subscriptionsMessage %
|
|
@@ -4314,23 +4482,18 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4314
4482
|
let found = false;
|
|
4315
4483
|
if (ctx) {
|
|
4316
4484
|
if (typeof idOrName === 'number') {
|
|
4317
|
-
for (const messageName
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
sandbox.__engine.__subscriptionsMessage--;
|
|
4325
|
-
}
|
|
4326
|
-
found = true;
|
|
4327
|
-
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--;
|
|
4328
4492
|
}
|
|
4493
|
+
found = true;
|
|
4494
|
+
break outer;
|
|
4329
4495
|
}
|
|
4330
4496
|
}
|
|
4331
|
-
if (found) {
|
|
4332
|
-
break;
|
|
4333
|
-
}
|
|
4334
4497
|
}
|
|
4335
4498
|
}
|
|
4336
4499
|
else if (idOrName && ctx[idOrName]) {
|
|
@@ -4401,6 +4564,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4401
4564
|
}
|
|
4402
4565
|
adapter.subscribeForeignObjects(pattern);
|
|
4403
4566
|
context.subscriptionsObject.push(subs);
|
|
4567
|
+
// Keep O(1) dispatch map in sync
|
|
4568
|
+
if (!context.subscriptionsObjectMap.has(pattern)) {
|
|
4569
|
+
context.subscriptionsObjectMap.set(pattern, []);
|
|
4570
|
+
}
|
|
4571
|
+
context.subscriptionsObjectMap.get(pattern).push(subs);
|
|
4404
4572
|
return subs;
|
|
4405
4573
|
},
|
|
4406
4574
|
unsubscribeObject: function (subObject) {
|
|
@@ -4418,6 +4586,17 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4418
4586
|
if (context.subscriptionsObject[i] === subObject) {
|
|
4419
4587
|
adapter.unsubscribeForeignObjects(subObject.pattern);
|
|
4420
4588
|
context.subscriptionsObject.splice(i, 1);
|
|
4589
|
+
// Keep O(1) dispatch map in sync
|
|
4590
|
+
const mapSubs = context.subscriptionsObjectMap.get(subObject.pattern);
|
|
4591
|
+
if (mapSubs) {
|
|
4592
|
+
const pos = mapSubs.indexOf(subObject);
|
|
4593
|
+
if (pos !== -1) {
|
|
4594
|
+
mapSubs.splice(pos, 1);
|
|
4595
|
+
}
|
|
4596
|
+
if (!mapSubs.length) {
|
|
4597
|
+
context.subscriptionsObjectMap.delete(subObject.pattern);
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4421
4600
|
sandbox.__engine.__subscriptionsObject--;
|
|
4422
4601
|
return true;
|
|
4423
4602
|
}
|
|
@@ -4428,6 +4607,17 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4428
4607
|
context.subscriptionsObject[i].pattern === subObject.pattern) {
|
|
4429
4608
|
deleted++;
|
|
4430
4609
|
adapter.unsubscribeForeignObjects(subObject.pattern);
|
|
4610
|
+
// Keep O(1) dispatch map in sync
|
|
4611
|
+
const mapSubsP = context.subscriptionsObjectMap.get(subObject.pattern);
|
|
4612
|
+
if (mapSubsP) {
|
|
4613
|
+
const pos = mapSubsP.indexOf(context.subscriptionsObject[i]);
|
|
4614
|
+
if (pos !== -1) {
|
|
4615
|
+
mapSubsP.splice(pos, 1);
|
|
4616
|
+
}
|
|
4617
|
+
if (!mapSubsP.length) {
|
|
4618
|
+
context.subscriptionsObjectMap.delete(subObject.pattern);
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4431
4621
|
context.subscriptionsObject.splice(i, 1);
|
|
4432
4622
|
sandbox.__engine.__subscriptionsObject--;
|
|
4433
4623
|
}
|
|
@@ -4552,11 +4742,33 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4552
4742
|
if (sandbox.verbose) {
|
|
4553
4743
|
sandbox.log(`extendObject(id=${id}, obj=${JSON.stringify(obj)})`, 'info');
|
|
4554
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
|
+
}
|
|
4555
4767
|
if (callback) {
|
|
4556
|
-
adapter.extendForeignObject(id,
|
|
4768
|
+
adapter.extendForeignObject(id, objClone, callback);
|
|
4557
4769
|
}
|
|
4558
4770
|
else {
|
|
4559
|
-
void adapter.extendForeignObject(id,
|
|
4771
|
+
void adapter.extendForeignObject(id, objClone);
|
|
4560
4772
|
}
|
|
4561
4773
|
}
|
|
4562
4774
|
};
|
|
@@ -4612,6 +4824,11 @@ function sandBox(script, name, verbose, debug, context) {
|
|
|
4612
4824
|
writable: false,
|
|
4613
4825
|
});
|
|
4614
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
|
+
}
|
|
4615
4832
|
return sandbox;
|
|
4616
4833
|
}
|
|
4617
4834
|
//# sourceMappingURL=sandbox.js.map
|