@absmartly/sdk-plugins 1.2.2 → 1.3.2

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 (124) hide show
  1. package/dist/absmartly-cookie.min.js +1 -1
  2. package/dist/absmartly-dom-changes-core.min.js +1 -1
  3. package/dist/absmartly-dom-changes-lite.min.js +1 -1
  4. package/dist/absmartly-overrides-full.min.js +1 -1
  5. package/dist/absmartly-overrides-lite.min.js +1 -1
  6. package/dist/absmartly-sdk-plugins.min.js +1 -1
  7. package/dist/absmartly-vitals.min.js +1 -1
  8. package/es/analytics/AnalyticsPlugin.js +96 -0
  9. package/es/analytics/AnalyticsPlugin.js.map +1 -0
  10. package/es/analytics/index.js +2 -0
  11. package/es/analytics/index.js.map +1 -0
  12. package/es/analytics/types.js +2 -0
  13. package/es/analytics/types.js.map +1 -0
  14. package/es/cookies/CookiePlugin.js +59 -18
  15. package/es/cookies/CookiePlugin.js.map +1 -1
  16. package/es/core/DOMChangesPluginLite.js +217 -106
  17. package/es/core/DOMChangesPluginLite.js.map +1 -1
  18. package/es/core/DOMManipulatorLite.js +285 -153
  19. package/es/core/DOMManipulatorLite.js.map +1 -1
  20. package/es/core/ExposureTracker.js +310 -108
  21. package/es/core/ExposureTracker.js.map +1 -1
  22. package/es/core/HTMLInjector.js +11 -1
  23. package/es/core/HTMLInjector.js.map +1 -1
  24. package/es/core/PendingChangeManager.js +114 -54
  25. package/es/core/PendingChangeManager.js.map +1 -1
  26. package/es/generated/buildInfo.js +1 -1
  27. package/es/index.js +1 -0
  28. package/es/index.js.map +1 -1
  29. package/es/overrides/BrowserCookieAdapter.js +3 -31
  30. package/es/overrides/BrowserCookieAdapter.js.map +1 -1
  31. package/es/overrides/OverridesPlugin.js +39 -107
  32. package/es/overrides/OverridesPlugin.js.map +1 -1
  33. package/es/overrides/OverridesPluginLite.js +5 -136
  34. package/es/overrides/OverridesPluginLite.js.map +1 -1
  35. package/es/parsers/VariantExtractor.js +21 -18
  36. package/es/parsers/VariantExtractor.js.map +1 -1
  37. package/es/url-redirect/URLRedirectExtractor.js +8 -2
  38. package/es/url-redirect/URLRedirectExtractor.js.map +1 -1
  39. package/es/url-redirect/URLRedirectMatcher.js +130 -0
  40. package/es/url-redirect/URLRedirectMatcher.js.map +1 -1
  41. package/es/url-redirect/URLRedirectPlugin.js +14 -5
  42. package/es/url-redirect/URLRedirectPlugin.js.map +1 -1
  43. package/es/utils/URLMatcher.js.map +1 -1
  44. package/es/utils/debug.js +11 -0
  45. package/es/utils/debug.js.map +1 -1
  46. package/es/utils/persistence.js +143 -75
  47. package/es/utils/persistence.js.map +1 -1
  48. package/es/utils/plugin-registry.js.map +1 -1
  49. package/es/vitals/WebVitalsPlugin.js +9 -4
  50. package/es/vitals/WebVitalsPlugin.js.map +1 -1
  51. package/lib/analytics/AnalyticsPlugin.d.ts +8 -0
  52. package/lib/analytics/AnalyticsPlugin.js +99 -0
  53. package/lib/analytics/AnalyticsPlugin.js.map +1 -0
  54. package/lib/analytics/index.d.ts +2 -0
  55. package/lib/analytics/index.js +6 -0
  56. package/lib/analytics/index.js.map +1 -0
  57. package/lib/analytics/types.d.ts +5 -0
  58. package/lib/analytics/types.js +3 -0
  59. package/lib/analytics/types.js.map +1 -0
  60. package/lib/cookies/CookiePlugin.js +59 -18
  61. package/lib/cookies/CookiePlugin.js.map +1 -1
  62. package/lib/core/DOMChangesPluginLite.d.ts +5 -2
  63. package/lib/core/DOMChangesPluginLite.js +217 -106
  64. package/lib/core/DOMChangesPluginLite.js.map +1 -1
  65. package/lib/core/DOMManipulatorLite.d.ts +24 -0
  66. package/lib/core/DOMManipulatorLite.js +284 -152
  67. package/lib/core/DOMManipulatorLite.js.map +1 -1
  68. package/lib/core/ExposureTracker.js +310 -108
  69. package/lib/core/ExposureTracker.js.map +1 -1
  70. package/lib/core/HTMLInjector.d.ts +1 -0
  71. package/lib/core/HTMLInjector.js +11 -1
  72. package/lib/core/HTMLInjector.js.map +1 -1
  73. package/lib/core/PendingChangeManager.js +114 -54
  74. package/lib/core/PendingChangeManager.js.map +1 -1
  75. package/lib/generated/buildInfo.d.ts +1 -1
  76. package/lib/generated/buildInfo.js +1 -1
  77. package/lib/index.d.ts +2 -0
  78. package/lib/index.js +3 -1
  79. package/lib/index.js.map +1 -1
  80. package/lib/overrides/BrowserCookieAdapter.js +3 -31
  81. package/lib/overrides/BrowserCookieAdapter.js.map +1 -1
  82. package/lib/overrides/OverridesPlugin.js +39 -107
  83. package/lib/overrides/OverridesPlugin.js.map +1 -1
  84. package/lib/overrides/OverridesPluginLite.d.ts +5 -1
  85. package/lib/overrides/OverridesPluginLite.js +5 -136
  86. package/lib/overrides/OverridesPluginLite.js.map +1 -1
  87. package/lib/parsers/VariantExtractor.js +21 -18
  88. package/lib/parsers/VariantExtractor.js.map +1 -1
  89. package/lib/types/index.d.ts +5 -0
  90. package/lib/url-redirect/URLRedirectExtractor.js +8 -2
  91. package/lib/url-redirect/URLRedirectExtractor.js.map +1 -1
  92. package/lib/url-redirect/URLRedirectMatcher.d.ts +3 -0
  93. package/lib/url-redirect/URLRedirectMatcher.js +130 -0
  94. package/lib/url-redirect/URLRedirectMatcher.js.map +1 -1
  95. package/lib/url-redirect/URLRedirectPlugin.js +13 -4
  96. package/lib/url-redirect/URLRedirectPlugin.js.map +1 -1
  97. package/lib/url-redirect/types.d.ts +1 -1
  98. package/lib/utils/URLMatcher.d.ts +1 -7
  99. package/lib/utils/URLMatcher.js.map +1 -1
  100. package/lib/utils/debug.d.ts +5 -0
  101. package/lib/utils/debug.js +12 -0
  102. package/lib/utils/debug.js.map +1 -1
  103. package/lib/utils/persistence.js +143 -75
  104. package/lib/utils/persistence.js.map +1 -1
  105. package/lib/utils/plugin-registry.d.ts +1 -0
  106. package/lib/utils/plugin-registry.js.map +1 -1
  107. package/lib/vitals/WebVitalsPlugin.js +9 -4
  108. package/lib/vitals/WebVitalsPlugin.js.map +1 -1
  109. package/package.json +13 -3
  110. package/types/analytics/AnalyticsPlugin.d.ts +8 -0
  111. package/types/analytics/index.d.ts +2 -0
  112. package/types/analytics/types.d.ts +5 -0
  113. package/types/core/DOMChangesPluginLite.d.ts +5 -2
  114. package/types/core/DOMManipulatorLite.d.ts +24 -0
  115. package/types/core/HTMLInjector.d.ts +1 -0
  116. package/types/generated/buildInfo.d.ts +1 -1
  117. package/types/index.d.ts +2 -0
  118. package/types/overrides/OverridesPluginLite.d.ts +5 -1
  119. package/types/types/index.d.ts +5 -0
  120. package/types/url-redirect/URLRedirectMatcher.d.ts +3 -0
  121. package/types/url-redirect/types.d.ts +1 -1
  122. package/types/utils/URLMatcher.d.ts +1 -7
  123. package/types/utils/debug.d.ts +5 -0
  124. package/types/utils/plugin-registry.d.ts +1 -0
@@ -34,7 +34,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
34
34
  }
35
35
  return to.concat(ar || Array.prototype.slice.call(from));
36
36
  };
37
- import { logDebug, logChangeApplication } from '../utils/debug';
37
+ import { logDebug, logChangeApplication, logProductionWarn } from '../utils/debug';
38
38
  import { PendingChangeManager } from './PendingChangeManager';
39
39
  var DOMManipulatorLite = /** @class */ (function () {
40
40
  function DOMManipulatorLite(debug, plugin) {
@@ -50,9 +50,120 @@ var DOMManipulatorLite = /** @class */ (function () {
50
50
  return _this.applyChange(change, experimentName);
51
51
  }, debug);
52
52
  }
53
+ /**
54
+ * Execute a user-authored JavaScript DOM change against an element.
55
+ *
56
+ * Strategy:
57
+ * 1. Primary path uses `new Function(...)`. On pages whose CSP includes
58
+ * `unsafe-eval` this succeeds and keeps the fast debug-friendly path
59
+ * with a `//# sourceURL` tag.
60
+ * 2. When CSP blocks dynamic code evaluation the fallback injects a
61
+ * one-shot `<script>` element that calls an anonymous IIFE with the
62
+ * target element bound through a temporary `window` slot. This path
63
+ * works on pages that allow inline scripts but not eval.
64
+ * 3. If both paths fail, the error is logged unconditionally at
65
+ * `console.error` and surfaced via a `CustomEvent('absmartly:js-error', ...)`
66
+ * on `document` so hosting code (browser extension preview, telemetry,
67
+ * etc.) can distinguish CSP failures from runtime errors.
68
+ *
69
+ * Returns true when the code executed successfully through either path.
70
+ */
71
+ DOMManipulatorLite.prototype.executeUserJavaScript = function (code, element, experimentName, selector) {
72
+ var sourceTag = "absmartly-experiment-".concat(experimentName || 'unknown', ".js");
73
+ var debugPrelude = "var _debug = (typeof _debug !== 'undefined' ? _debug : { debugLog: console.log.bind(console), debugWarn: console.warn.bind(console), debugError: console.error.bind(console) });\n";
74
+ try {
75
+ var fn = new Function('element', debugPrelude + code + "\n//# sourceURL=".concat(sourceTag));
76
+ fn(element);
77
+ return true;
78
+ }
79
+ catch (evalError) {
80
+ var evalMessage = evalError instanceof Error ? evalError.message : String(evalError);
81
+ var cspBlockedEval = evalError instanceof EvalError ||
82
+ /unsafe-eval|Content Security Policy|CSP|refused to evaluate/i.test(evalMessage);
83
+ if (!cspBlockedEval) {
84
+ // Runtime error in user code — don't try the fallback, surface immediately.
85
+ this.reportJsExecutionFailure({
86
+ experimentName: experimentName,
87
+ selector: selector,
88
+ reason: 'runtime',
89
+ error: evalMessage,
90
+ stack: evalError instanceof Error ? evalError.stack : undefined,
91
+ });
92
+ return false;
93
+ }
94
+ // Fallback: inline <script> tag that binds the element through a temporary window slot.
95
+ // When CSP blocks inline scripts, appendChild does NOT throw — Chrome silently refuses
96
+ // execution and logs a console violation. We detect that by flipping a sentinel inside
97
+ // the injected script and checking it after append.
98
+ try {
99
+ var slotId = "__absmartly_js_target_".concat(Date.now(), "_").concat(Math.floor(Math.random() * 1e9));
100
+ var sentinelId = "".concat(slotId, "_ran");
101
+ window[slotId] = element;
102
+ window[sentinelId] = false;
103
+ var script = document.createElement('script');
104
+ script.textContent =
105
+ "(function(element){".concat(debugPrelude).concat(code, "\n})(window['").concat(slotId, "']);\n") +
106
+ "window['".concat(sentinelId, "'] = true;\n") +
107
+ "try { delete window['".concat(slotId, "']; } catch(_e) { window['").concat(slotId, "'] = undefined; }\n") +
108
+ "//# sourceURL=".concat(sourceTag);
109
+ var parent_1 = document.head || document.documentElement;
110
+ parent_1.appendChild(script);
111
+ script.remove();
112
+ var ran = window[sentinelId] === true;
113
+ try {
114
+ delete window[sentinelId];
115
+ }
116
+ catch (_a) {
117
+ window[sentinelId] = undefined;
118
+ }
119
+ if (!ran) {
120
+ this.reportJsExecutionFailure({
121
+ experimentName: experimentName,
122
+ selector: selector,
123
+ reason: 'csp',
124
+ error: "CSP blocked dynamic script execution. eval(): ".concat(evalMessage, "; inline <script> did not execute (likely blocked by script-src without 'unsafe-inline')."),
125
+ });
126
+ return false;
127
+ }
128
+ return true;
129
+ }
130
+ catch (scriptError) {
131
+ var scriptMessage = scriptError instanceof Error ? scriptError.message : String(scriptError);
132
+ this.reportJsExecutionFailure({
133
+ experimentName: experimentName,
134
+ selector: selector,
135
+ reason: 'csp',
136
+ error: "CSP blocked dynamic script execution. eval(): ".concat(evalMessage, "; inline <script>: ").concat(scriptMessage),
137
+ stack: scriptError instanceof Error ? scriptError.stack : undefined,
138
+ });
139
+ return false;
140
+ }
141
+ }
142
+ };
143
+ /**
144
+ * Report a JavaScript execution failure to listeners, without relying on this.debug.
145
+ * Always logs at `console.error` and dispatches a `CustomEvent` on `document`.
146
+ */
147
+ DOMManipulatorLite.prototype.reportJsExecutionFailure = function (detail) {
148
+ try {
149
+ logProductionWarn("[ABsmartly] JavaScript DOM change failed (".concat(detail.reason, ") for \"").concat(detail.experimentName, "\" selector \"").concat(detail.selector, "\": ").concat(detail.error), detail.stack ? { stack: detail.stack } : undefined);
150
+ }
151
+ catch (_a) {
152
+ // console unavailable; ignore
153
+ }
154
+ try {
155
+ if (typeof document !== 'undefined' && typeof CustomEvent === 'function') {
156
+ document.dispatchEvent(new CustomEvent('absmartly:js-error', { detail: detail }));
157
+ }
158
+ }
159
+ catch (_b) {
160
+ // environments without CustomEvent support
161
+ }
162
+ };
53
163
  DOMManipulatorLite.prototype.applyChange = function (change, experimentName) {
164
+ var e_1, _a, e_2, _b, e_3, _c;
54
165
  var _this = this;
55
- var _a, _b, _c, _d;
166
+ var _d, _e, _f, _g, _h, _j, _k, _l;
56
167
  if (!change.enabled && change.enabled !== undefined) {
57
168
  logDebug("Skipping disabled change for experiment: ".concat(experimentName), {
58
169
  experimentName: experimentName,
@@ -64,7 +175,7 @@ var DOMManipulatorLite = /** @class */ (function () {
64
175
  var isReapplying = !!this.plugin.reapplyingElements &&
65
176
  Array.from(this.plugin.reapplyingElements).length > 0;
66
177
  // Get user's variant for this experiment
67
- var userVariant = (_b = (_a = this.plugin.config) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.peek(experimentName);
178
+ var userVariant = (_e = (_d = this.plugin.config) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.peek(experimentName);
68
179
  if (this.debug) {
69
180
  logDebug("[DOM-APPLY] [".concat(experimentName, "] ").concat(isReapplying ? 'RE-APPLYING' : 'APPLYING', " change"), {
70
181
  experimentName: experimentName,
@@ -72,7 +183,7 @@ var DOMManipulatorLite = /** @class */ (function () {
72
183
  selector: change.selector,
73
184
  type: change.type,
74
185
  timestamp: Date.now(),
75
- callStack: (_c = new Error().stack) === null || _c === void 0 ? void 0 : _c.split('\n').slice(2, 4).join('\n'),
186
+ callStack: (_f = new Error().stack) === null || _f === void 0 ? void 0 : _f.split('\n').slice(2, 4).join('\n'),
76
187
  });
77
188
  }
78
189
  try {
@@ -92,10 +203,10 @@ var DOMManipulatorLite = /** @class */ (function () {
92
203
  return false;
93
204
  }
94
205
  var elements = document.querySelectorAll(change.selector);
95
- var appliedElements_1 = [];
206
+ var appliedElements = [];
96
207
  if (elements.length === 0) {
97
208
  // Add to pending if waitForElement is explicitly true OR if SPA mode is enabled
98
- var shouldWaitForElement = change.waitForElement || ((_d = this.plugin.config) === null || _d === void 0 ? void 0 : _d.spa);
209
+ var shouldWaitForElement = change.waitForElement || ((_g = this.plugin.config) === null || _g === void 0 ? void 0 : _g.spa);
99
210
  if (shouldWaitForElement) {
100
211
  if (this.debug) {
101
212
  logDebug("[ABsmartly] Element not found, adding to pending: ".concat(change.selector));
@@ -117,45 +228,65 @@ var DOMManipulatorLite = /** @class */ (function () {
117
228
  });
118
229
  return false;
119
230
  }
120
- elements.forEach(function (element) {
121
- var _a, _b, _c, _d;
122
- if (_this.debug && change.type === 'style') {
123
- var oldStyles_1 = {};
124
- if (change.value && typeof change.value === 'object') {
125
- Object.keys(change.value).forEach(function (prop) {
126
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
127
- oldStyles_1[cssProp] = element.style.getPropertyValue(cssProp);
231
+ try {
232
+ for (var elements_1 = __values(elements), elements_1_1 = elements_1.next(); !elements_1_1.done; elements_1_1 = elements_1.next()) {
233
+ var element = elements_1_1.value;
234
+ if (this.debug && change.type === 'style') {
235
+ var oldStyles = {};
236
+ if (change.value && typeof change.value === 'object') {
237
+ try {
238
+ for (var _m = (e_2 = void 0, __values(Object.keys(change.value))), _o = _m.next(); !_o.done; _o = _m.next()) {
239
+ var prop = _o.value;
240
+ var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
241
+ oldStyles[cssProp] = element.style.getPropertyValue(cssProp);
242
+ }
243
+ }
244
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
245
+ finally {
246
+ try {
247
+ if (_o && !_o.done && (_b = _m.return)) _b.call(_m);
248
+ }
249
+ finally { if (e_2) throw e_2.error; }
250
+ }
251
+ }
252
+ logDebug("[DOM-BEFORE-APPLY] Element styles before change", {
253
+ experimentName: experimentName,
254
+ selector: change.selector,
255
+ element: element.tagName,
256
+ oldStyles: oldStyles,
257
+ newStyles: change.value,
128
258
  });
129
259
  }
130
- logDebug("[DOM-BEFORE-APPLY] Element styles before change", {
131
- experimentName: experimentName,
132
- selector: change.selector,
133
- element: element.tagName,
134
- oldStyles: oldStyles_1,
135
- newStyles: change.value,
136
- });
137
- }
138
- _this.applyChangeToElement(element, change);
139
- if (_this.debug && change.type === 'style') {
140
- var appliedStyles_1 = {};
141
- if (change.value && typeof change.value === 'object') {
142
- Object.keys(change.value).forEach(function (prop) {
143
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
144
- appliedStyles_1[cssProp] = element.style.getPropertyValue(cssProp);
260
+ this.applyChangeToElement(element, change);
261
+ if (this.debug && change.type === 'style') {
262
+ var appliedStyles = {};
263
+ if (change.value && typeof change.value === 'object') {
264
+ try {
265
+ for (var _p = (e_3 = void 0, __values(Object.keys(change.value))), _q = _p.next(); !_q.done; _q = _p.next()) {
266
+ var prop = _q.value;
267
+ var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
268
+ appliedStyles[cssProp] = element.style.getPropertyValue(cssProp);
269
+ }
270
+ }
271
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
272
+ finally {
273
+ try {
274
+ if (_q && !_q.done && (_c = _p.return)) _c.call(_p);
275
+ }
276
+ finally { if (e_3) throw e_3.error; }
277
+ }
278
+ }
279
+ logDebug("[DOM-AFTER-APPLY] Element styles after change", {
280
+ experimentName: experimentName,
281
+ selector: change.selector,
282
+ element: element.tagName,
283
+ appliedStyles: appliedStyles,
145
284
  });
146
285
  }
147
- logDebug("[DOM-AFTER-APPLY] Element styles after change", {
148
- experimentName: experimentName,
149
- selector: change.selector,
150
- element: element.tagName,
151
- appliedStyles: appliedStyles_1,
152
- });
153
- }
154
- if (change.type === 'javascript') {
155
- if (change.value) {
156
- var userVariant_1 = (_b = (_a = _this.plugin.config) === null || _a === void 0 ? void 0 : _a.context) === null || _b === void 0 ? void 0 : _b.peek(experimentName);
157
- try {
158
- if (_this.debug) {
286
+ if (change.type === 'javascript') {
287
+ if (change.value) {
288
+ var userVariant_1 = (_j = (_h = this.plugin.config) === null || _h === void 0 ? void 0 : _h.context) === null || _j === void 0 ? void 0 : _j.peek(experimentName);
289
+ if (this.debug) {
159
290
  logDebug("[JAVASCRIPT] [".concat(experimentName, "] Executing JavaScript change"), {
160
291
  experimentName: experimentName,
161
292
  userVariant: userVariant_1,
@@ -165,93 +296,85 @@ var DOMManipulatorLite = /** @class */ (function () {
165
296
  });
166
297
  }
167
298
  var codeString = String(change.value);
168
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] Creating function with code: ").concat(codeString));
169
- var fn = new Function('element', codeString);
170
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] Function created, now executing..."));
171
- fn(element);
172
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] Function executed, element is:"), {
173
- elementTag: element.tagName,
174
- elementClass: element.className,
175
- elementId: element.id,
176
- });
177
- appliedElements_1.push(element);
178
- if (_this.debug) {
179
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2713 JavaScript executed successfully"), {
180
- experimentName: experimentName,
181
- userVariant: userVariant_1,
182
- selector: change.selector,
183
- element: element.tagName,
184
- code: codeString,
185
- });
299
+ var executed = this.executeUserJavaScript(codeString, element, experimentName, change.selector);
300
+ if (executed) {
301
+ appliedElements.push(element);
302
+ if (this.debug) {
303
+ logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2713 JavaScript executed successfully"), {
304
+ experimentName: experimentName,
305
+ userVariant: userVariant_1,
306
+ selector: change.selector,
307
+ element: element.tagName,
308
+ code: codeString,
309
+ });
310
+ }
186
311
  }
187
312
  }
188
- catch (error) {
189
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2717 JavaScript execution error:"), {
313
+ else {
314
+ logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2717 No JavaScript code provided in change.value"), {
190
315
  experimentName: experimentName,
191
- userVariant: userVariant_1,
192
316
  selector: change.selector,
193
- element: element.tagName,
194
- code: String(change.value),
195
- error: error instanceof Error ? error.message : String(error),
196
- stack: error instanceof Error ? error.stack : undefined,
197
317
  });
198
318
  }
199
319
  }
320
+ else if (change.type === 'move') {
321
+ var targetSelector = change.targetSelector ||
322
+ (change.value && typeof change.value === 'object'
323
+ ? change.value.targetSelector
324
+ : null);
325
+ var position = change.position ||
326
+ (change.value && typeof change.value === 'object'
327
+ ? change.value.position
328
+ : null);
329
+ if (targetSelector) {
330
+ var target = document.querySelector(targetSelector);
331
+ if (target) {
332
+ this.moveElement(element, target, position);
333
+ appliedElements.push(element);
334
+ }
335
+ else if (this.debug) {
336
+ logDebug("[ABsmartly] Move target not found: ".concat(targetSelector));
337
+ }
338
+ }
339
+ }
200
340
  else {
201
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2717 No JavaScript code provided in change.value"), {
341
+ appliedElements.push(element);
342
+ }
343
+ // Watch element for persistence or SPA hydration recovery
344
+ var shouldWatch = (change.type === 'style' && change.persistStyle) ||
345
+ (change.type === 'attribute' && change.persistAttribute) ||
346
+ (change.type === 'javascript' && change.persistScript === true) ||
347
+ ((_k = this.plugin.config) === null || _k === void 0 ? void 0 : _k.spa); // SPA mode watches ALL types for hydration recovery
348
+ if (shouldWatch) {
349
+ this.plugin.watchElement(element, experimentName, change);
350
+ }
351
+ else if (this.debug) {
352
+ logDebug("[WATCH-SKIP] NOT watching element - no persist flag and SPA disabled", {
202
353
  experimentName: experimentName,
203
354
  selector: change.selector,
355
+ element: element.tagName,
356
+ changeType: change.type,
357
+ spaMode: (_l = this.plugin.config) === null || _l === void 0 ? void 0 : _l.spa,
204
358
  });
205
359
  }
206
360
  }
207
- else if (change.type === 'move') {
208
- var targetSelector = change.targetSelector ||
209
- (change.value && typeof change.value === 'object'
210
- ? change.value.targetSelector
211
- : null);
212
- var position = change.position ||
213
- (change.value && typeof change.value === 'object'
214
- ? change.value.position
215
- : null);
216
- if (targetSelector) {
217
- var target = document.querySelector(targetSelector);
218
- if (target) {
219
- _this.moveElement(element, target, position);
220
- appliedElements_1.push(element);
221
- }
222
- else if (_this.debug) {
223
- logDebug("[ABsmartly] Move target not found: ".concat(targetSelector));
224
- }
225
- }
226
- }
227
- else {
228
- appliedElements_1.push(element);
229
- }
230
- // Watch element for React hydration recovery (ALL types) OR style persistence (styles only)
231
- var shouldWatch = (change.type === 'style' && change.persistStyle) || // Explicit style persistence
232
- ((_c = _this.plugin.config) === null || _c === void 0 ? void 0 : _c.spa); // SPA mode watches ALL types for hydration recovery
233
- if (shouldWatch) {
234
- _this.plugin.watchElement(element, experimentName, change);
235
- }
236
- else if (change.type === 'style' && _this.debug) {
237
- logDebug("[WATCH-SKIP] NOT watching element - persistStyle and SPA both disabled", {
238
- experimentName: experimentName,
239
- selector: change.selector,
240
- element: element.tagName,
241
- persistStyle: change.persistStyle,
242
- spaMode: (_d = _this.plugin.config) === null || _d === void 0 ? void 0 : _d.spa,
243
- });
361
+ }
362
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
363
+ finally {
364
+ try {
365
+ if (elements_1_1 && !elements_1_1.done && (_a = elements_1.return)) _a.call(elements_1);
244
366
  }
245
- });
246
- if (appliedElements_1.length > 0) {
367
+ finally { if (e_1) throw e_1.error; }
368
+ }
369
+ if (appliedElements.length > 0) {
247
370
  this.trackAppliedChange(experimentName, change);
248
371
  if (change.waitForElement) {
249
372
  this.pendingManager.removePending(change.selector, experimentName, change.observerRoot);
250
373
  }
251
374
  // Throttle logs during style persistence reapplies (animations can trigger many times per second)
252
- var isReapplying_1 = appliedElements_1.some(function (el) { var _a; return (_a = _this.plugin.reapplyingElements) === null || _a === void 0 ? void 0 : _a.has(el); });
375
+ var isReapplying_1 = appliedElements.some(function (el) { var _a; return (_a = _this.plugin.reapplyingElements) === null || _a === void 0 ? void 0 : _a.has(el); });
253
376
  if (!isReapplying_1) {
254
- logChangeApplication(experimentName, change.selector, change.type, appliedElements_1.length, true);
377
+ logChangeApplication(experimentName, change.selector, change.type, appliedElements.length, true);
255
378
  }
256
379
  return true;
257
380
  }
@@ -300,7 +423,7 @@ var DOMManipulatorLite = /** @class */ (function () {
300
423
  }
301
424
  };
302
425
  DOMManipulatorLite.prototype.createElement = function (change, _experimentName) {
303
- var e_1, _a;
426
+ var e_4, _a;
304
427
  if (!change.element || !change.targetSelector) {
305
428
  return null;
306
429
  }
@@ -324,12 +447,12 @@ var DOMManipulatorLite = /** @class */ (function () {
324
447
  this.moveElement(child, target, change.position);
325
448
  }
326
449
  }
327
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
450
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
328
451
  finally {
329
452
  try {
330
453
  if (children_1_1 && !children_1_1.done && (_a = children_1.return)) _a.call(children_1);
331
454
  }
332
- finally { if (e_1) throw e_1.error; }
455
+ finally { if (e_4) throw e_4.error; }
333
456
  }
334
457
  // Return the first element for compatibility
335
458
  return children[0];
@@ -369,7 +492,7 @@ var DOMManipulatorLite = /** @class */ (function () {
369
492
  }
370
493
  };
371
494
  DOMManipulatorLite.prototype.applyChangeToElement = function (element, change) {
372
- var _a, _b;
495
+ var e_5, _a, _b, _c, e_6, _d;
373
496
  switch (change.type) {
374
497
  case 'text':
375
498
  if (change.value !== undefined) {
@@ -383,33 +506,51 @@ var DOMManipulatorLite = /** @class */ (function () {
383
506
  break;
384
507
  case 'style':
385
508
  if (change.value && typeof change.value === 'object') {
386
- Object.entries(change.value).forEach(function (_a) {
387
- var _b = __read(_a, 2), property = _b[0], value = _b[1];
388
- var cssProperty = property.replace(/([A-Z])/g, '-$1').toLowerCase();
389
- var priority = change.important === true ? 'important' : '';
390
- element.style.setProperty(cssProperty, String(value), priority);
391
- });
509
+ try {
510
+ for (var _e = __values(Object.entries(change.value)), _f = _e.next(); !_f.done; _f = _e.next()) {
511
+ var _g = __read(_f.value, 2), property = _g[0], value = _g[1];
512
+ var cssProperty = property.replace(/([A-Z])/g, '-$1').toLowerCase();
513
+ var priority = change.important === true ? 'important' : '';
514
+ element.style.setProperty(cssProperty, String(value), priority);
515
+ }
516
+ }
517
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
518
+ finally {
519
+ try {
520
+ if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
521
+ }
522
+ finally { if (e_5) throw e_5.error; }
523
+ }
392
524
  }
393
525
  break;
394
526
  case 'class':
395
527
  if (change.add && Array.isArray(change.add)) {
396
- (_a = element.classList).add.apply(_a, __spreadArray([], __read(change.add), false));
528
+ (_b = element.classList).add.apply(_b, __spreadArray([], __read(change.add), false));
397
529
  }
398
530
  if (change.remove && Array.isArray(change.remove)) {
399
- (_b = element.classList).remove.apply(_b, __spreadArray([], __read(change.remove), false));
531
+ (_c = element.classList).remove.apply(_c, __spreadArray([], __read(change.remove), false));
400
532
  }
401
533
  break;
402
534
  case 'attribute':
403
535
  if (change.value && typeof change.value === 'object') {
404
- Object.entries(change.value).forEach(function (_a) {
405
- var _b = __read(_a, 2), attr = _b[0], value = _b[1];
406
- if (value === null || value === undefined) {
407
- element.removeAttribute(attr);
536
+ try {
537
+ for (var _h = __values(Object.entries(change.value)), _j = _h.next(); !_j.done; _j = _h.next()) {
538
+ var _k = __read(_j.value, 2), attr = _k[0], value = _k[1];
539
+ if (value === null || value === undefined) {
540
+ element.removeAttribute(attr);
541
+ }
542
+ else {
543
+ element.setAttribute(attr, String(value));
544
+ }
408
545
  }
409
- else {
410
- element.setAttribute(attr, String(value));
546
+ }
547
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
548
+ finally {
549
+ try {
550
+ if (_j && !_j.done && (_d = _h.return)) _d.call(_h);
411
551
  }
412
- });
552
+ finally { if (e_6) throw e_6.error; }
553
+ }
413
554
  }
414
555
  break;
415
556
  case 'move':
@@ -434,36 +575,25 @@ var DOMManipulatorLite = /** @class */ (function () {
434
575
  try {
435
576
  this.applyChangeToElement(element, change);
436
577
  if (change.type === 'javascript' && change.value) {
437
- try {
438
- if (this.debug) {
439
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] Executing JavaScript on specific element"), {
440
- experimentName: experimentName,
441
- selector: change.selector,
442
- element: element.tagName,
443
- code: String(change.value).substring(0, 100) + '...',
444
- });
445
- }
446
- var fn = new Function('element', String(change.value));
447
- fn(element);
448
- if (this.debug) {
449
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2713 JavaScript executed successfully"), {
450
- experimentName: experimentName,
451
- selector: change.selector,
452
- element: element.tagName,
453
- });
454
- }
455
- }
456
- catch (error) {
457
- logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2717 JavaScript execution error on specific element:"), {
578
+ if (this.debug) {
579
+ logDebug("[JAVASCRIPT] [".concat(experimentName, "] Executing JavaScript on specific element"), {
458
580
  experimentName: experimentName,
459
581
  selector: change.selector,
460
582
  element: element.tagName,
461
- code: String(change.value),
462
- error: error instanceof Error ? error.message : String(error),
463
- stack: error instanceof Error ? error.stack : undefined,
583
+ code: String(change.value).substring(0, 100) + '...',
464
584
  });
585
+ }
586
+ var executed = this.executeUserJavaScript(String(change.value), element, experimentName, change.selector);
587
+ if (!executed) {
465
588
  return false;
466
589
  }
590
+ if (this.debug) {
591
+ logDebug("[JAVASCRIPT] [".concat(experimentName, "] \u2713 JavaScript executed successfully"), {
592
+ experimentName: experimentName,
593
+ selector: change.selector,
594
+ element: element.tagName,
595
+ });
596
+ }
467
597
  }
468
598
  else if (change.type === 'move') {
469
599
  var targetSelector = change.targetSelector ||
@@ -484,8 +614,10 @@ var DOMManipulatorLite = /** @class */ (function () {
484
614
  }
485
615
  }
486
616
  }
487
- // Watch element for React hydration recovery (ALL types) OR style persistence (styles only)
488
- var shouldWatch = (change.type === 'style' && change.persistStyle) || // Explicit style persistence
617
+ // Watch element for persistence or SPA hydration recovery
618
+ var shouldWatch = (change.type === 'style' && change.persistStyle) ||
619
+ (change.type === 'attribute' && change.persistAttribute) ||
620
+ (change.type === 'javascript' && change.persistScript === true) ||
489
621
  ((_a = this.plugin.config) === null || _a === void 0 ? void 0 : _a.spa); // SPA mode watches ALL types for hydration recovery
490
622
  if (shouldWatch) {
491
623
  this.plugin.watchElement(element, experimentName, change);