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