@absmartly/sdk-plugins 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +34 -33
  2. package/dist/absmartly-cookie.min.js +1 -1
  3. package/dist/absmartly-dom-changes-core.min.js +1 -1
  4. package/dist/absmartly-dom-changes-lite.min.js +1 -1
  5. package/dist/absmartly-overrides-full.min.js +1 -1
  6. package/dist/absmartly-overrides-lite.min.js +1 -1
  7. package/dist/absmartly-sdk-plugins.min.js +1 -1
  8. package/dist/absmartly-vitals.min.js +1 -1
  9. package/es/cookies/CookiePlugin.js +10 -8
  10. package/es/cookies/CookiePlugin.js.map +1 -1
  11. package/es/core/DOMChangesPluginLite.js +226 -248
  12. package/es/core/DOMChangesPluginLite.js.map +1 -1
  13. package/es/core/DOMManipulatorLite.js +68 -10
  14. package/es/core/DOMManipulatorLite.js.map +1 -1
  15. package/es/core/ExposureTracker.js +136 -11
  16. package/es/core/ExposureTracker.js.map +1 -1
  17. package/es/entries/dom-with-overrides-lite.js +3 -0
  18. package/es/entries/dom-with-overrides-lite.js.map +1 -1
  19. package/es/index.js +9 -2
  20. package/es/index.js.map +1 -1
  21. package/es/overrides/OverridesPlugin.js +2 -2
  22. package/es/overrides/OverridesPlugin.js.map +1 -1
  23. package/es/overrides/OverridesPluginLite.js +29 -1
  24. package/es/overrides/OverridesPluginLite.js.map +1 -1
  25. package/es/overrides/overridesUtils.js +27 -18
  26. package/es/overrides/overridesUtils.js.map +1 -1
  27. package/es/parsers/VariantExtractor.js +80 -67
  28. package/es/parsers/VariantExtractor.js.map +1 -1
  29. package/es/url-redirect/URLRedirectExtractor.js +185 -0
  30. package/es/url-redirect/URLRedirectExtractor.js.map +1 -0
  31. package/es/url-redirect/URLRedirectMatcher.js +115 -0
  32. package/es/url-redirect/URLRedirectMatcher.js.map +1 -0
  33. package/es/url-redirect/URLRedirectPlugin.js +350 -0
  34. package/es/url-redirect/URLRedirectPlugin.js.map +1 -0
  35. package/es/url-redirect/index.js +5 -0
  36. package/es/url-redirect/index.js.map +1 -0
  37. package/es/url-redirect/types.js +2 -0
  38. package/es/url-redirect/types.js.map +1 -0
  39. package/es/utils/persistence.js +278 -0
  40. package/es/utils/persistence.js.map +1 -0
  41. package/es/utils/plugin-registry.js +47 -0
  42. package/es/utils/plugin-registry.js.map +1 -0
  43. package/es/vitals/WebVitalsPlugin.js +19 -4
  44. package/es/vitals/WebVitalsPlugin.js.map +1 -1
  45. package/lib/cookies/CookiePlugin.d.ts +2 -1
  46. package/lib/cookies/CookiePlugin.js +10 -8
  47. package/lib/cookies/CookiePlugin.js.map +1 -1
  48. package/lib/core/DOMChangesPluginLite.d.ts +13 -9
  49. package/lib/core/DOMChangesPluginLite.js +226 -248
  50. package/lib/core/DOMChangesPluginLite.js.map +1 -1
  51. package/lib/core/DOMManipulatorLite.js +68 -10
  52. package/lib/core/DOMManipulatorLite.js.map +1 -1
  53. package/lib/core/ExposureTracker.d.ts +6 -0
  54. package/lib/core/ExposureTracker.js +136 -11
  55. package/lib/core/ExposureTracker.js.map +1 -1
  56. package/lib/entries/dom-with-overrides-lite.d.ts +2 -0
  57. package/lib/entries/dom-with-overrides-lite.js +3 -1
  58. package/lib/entries/dom-with-overrides-lite.js.map +1 -1
  59. package/lib/index.d.ts +8 -2
  60. package/lib/index.js +17 -1
  61. package/lib/index.js.map +1 -1
  62. package/lib/overrides/OverridesPlugin.js +2 -2
  63. package/lib/overrides/OverridesPlugin.js.map +1 -1
  64. package/lib/overrides/OverridesPluginLite.d.ts +8 -0
  65. package/lib/overrides/OverridesPluginLite.js +29 -1
  66. package/lib/overrides/OverridesPluginLite.js.map +1 -1
  67. package/lib/overrides/overridesUtils.d.ts +10 -5
  68. package/lib/overrides/overridesUtils.js +28 -18
  69. package/lib/overrides/overridesUtils.js.map +1 -1
  70. package/lib/parsers/VariantExtractor.d.ts +4 -5
  71. package/lib/parsers/VariantExtractor.js +80 -67
  72. package/lib/parsers/VariantExtractor.js.map +1 -1
  73. package/lib/types/index.d.ts +5 -12
  74. package/lib/url-redirect/URLRedirectExtractor.d.ts +16 -0
  75. package/lib/url-redirect/URLRedirectExtractor.js +188 -0
  76. package/lib/url-redirect/URLRedirectExtractor.js.map +1 -0
  77. package/lib/url-redirect/URLRedirectMatcher.d.ts +8 -0
  78. package/lib/url-redirect/URLRedirectMatcher.js +118 -0
  79. package/lib/url-redirect/URLRedirectMatcher.js.map +1 -0
  80. package/lib/url-redirect/URLRedirectPlugin.d.ts +21 -0
  81. package/lib/url-redirect/URLRedirectPlugin.js +353 -0
  82. package/lib/url-redirect/URLRedirectPlugin.js.map +1 -0
  83. package/lib/url-redirect/index.d.ts +4 -0
  84. package/lib/url-redirect/index.js +25 -0
  85. package/lib/url-redirect/index.js.map +1 -0
  86. package/lib/url-redirect/types.d.ts +29 -0
  87. package/lib/url-redirect/types.js +3 -0
  88. package/lib/url-redirect/types.js.map +1 -0
  89. package/lib/utils/persistence.d.ts +23 -0
  90. package/lib/utils/persistence.js +281 -0
  91. package/lib/utils/persistence.js.map +1 -0
  92. package/lib/utils/plugin-registry.d.ts +40 -0
  93. package/lib/utils/plugin-registry.js +53 -0
  94. package/lib/utils/plugin-registry.js.map +1 -0
  95. package/lib/vitals/WebVitalsPlugin.d.ts +2 -0
  96. package/lib/vitals/WebVitalsPlugin.js +19 -4
  97. package/lib/vitals/WebVitalsPlugin.js.map +1 -1
  98. package/package.json +59 -3
  99. package/types/cookies/CookiePlugin.d.ts +2 -1
  100. package/types/core/DOMChangesPluginLite.d.ts +13 -9
  101. package/types/core/ExposureTracker.d.ts +6 -0
  102. package/types/entries/dom-with-overrides-lite.d.ts +2 -0
  103. package/types/index.d.ts +8 -2
  104. package/types/overrides/OverridesPluginLite.d.ts +8 -0
  105. package/types/overrides/overridesUtils.d.ts +10 -5
  106. package/types/parsers/VariantExtractor.d.ts +4 -5
  107. package/types/types/index.d.ts +5 -12
  108. package/types/url-redirect/URLRedirectExtractor.d.ts +16 -0
  109. package/types/url-redirect/URLRedirectMatcher.d.ts +8 -0
  110. package/types/url-redirect/URLRedirectPlugin.d.ts +21 -0
  111. package/types/url-redirect/index.d.ts +4 -0
  112. package/types/url-redirect/types.d.ts +29 -0
  113. package/types/utils/persistence.d.ts +23 -0
  114. package/types/utils/plugin-registry.d.ts +40 -0
  115. package/types/vitals/WebVitalsPlugin.d.ts +2 -0
@@ -82,19 +82,18 @@ var ExposureTracker_1 = require("./ExposureTracker");
82
82
  var HTMLInjector_1 = require("./HTMLInjector");
83
83
  var debug_1 = require("../utils/debug");
84
84
  var URLMatcher_1 = require("../utils/URLMatcher");
85
+ var persistence_1 = require("../utils/persistence");
86
+ var plugin_registry_1 = require("../utils/plugin-registry");
85
87
  var DOMChangesPluginLite = /** @class */ (function () {
86
88
  function DOMChangesPluginLite(config) {
87
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
89
+ var _this = this;
90
+ var _a, _b, _c, _d, _e, _f, _g, _h;
88
91
  this.mutationObserver = null;
89
92
  this.exposedExperiments = new Set();
90
93
  this.eventListeners = new Map();
91
94
  this.styleManagers = new Map();
92
95
  this.initialized = false;
93
- this.watchedElements = new WeakMap();
94
- this.persistenceObserver = null;
95
- this.reapplyingElements = new Set();
96
- this.reapplyLogThrottle = new Map();
97
- this.appliedChanges = new Map();
96
+ this.persistenceManager = null;
98
97
  this.antiFlickerTimeout = null;
99
98
  this.antiFlickerStyleId = 'absmartly-antiflicker';
100
99
  this.config = {
@@ -102,28 +101,54 @@ var DOMChangesPluginLite = /** @class */ (function () {
102
101
  autoApply: (_a = config.autoApply) !== null && _a !== void 0 ? _a : true,
103
102
  spa: (_b = config.spa) !== null && _b !== void 0 ? _b : true,
104
103
  visibilityTracking: (_c = config.visibilityTracking) !== null && _c !== void 0 ? _c : true,
105
- extensionBridge: false, // Always false for lite version
106
- dataSource: (_d = config.dataSource) !== null && _d !== void 0 ? _d : 'variable',
107
- dataFieldName: (_e = config.dataFieldName) !== null && _e !== void 0 ? _e : '__dom_changes',
108
- debug: (_f = config.debug) !== null && _f !== void 0 ? _f : false,
109
- hideUntilReady: (_g = config.hideUntilReady) !== null && _g !== void 0 ? _g : false,
110
- hideTimeout: (_h = config.hideTimeout) !== null && _h !== void 0 ? _h : 3000,
111
- hideSelector: (_j = config.hideSelector) !== null && _j !== void 0 ? _j : '[data-absmartly-hide]',
112
- hideTransition: (_k = config.hideTransition) !== null && _k !== void 0 ? _k : false,
104
+ variableName: (_d = config.variableName) !== null && _d !== void 0 ? _d : '__dom_changes',
105
+ debug: (_e = config.debug) !== null && _e !== void 0 ? _e : false,
106
+ hideUntilReady: (_f = config.hideUntilReady) !== null && _f !== void 0 ? _f : false,
107
+ hideTimeout: (_g = config.hideTimeout) !== null && _g !== void 0 ? _g : 3000,
108
+ hideTransition: (_h = config.hideTransition) !== null && _h !== void 0 ? _h : false,
113
109
  };
114
110
  if (!this.config.context) {
115
111
  throw new Error('[ABsmartly] Context is required');
116
112
  }
117
- // Apply anti-flicker hiding immediately if enabled
113
+ console.log("[ABsmartly] DOMChangesPluginLite v".concat(DOMChangesPluginLite.VERSION, " initialized"));
118
114
  if (this.config.hideUntilReady) {
119
115
  this.hideContent();
120
116
  }
121
117
  this.domManipulator = new DOMManipulatorLite_1.DOMManipulatorLite(this.config.debug, this);
122
- this.variantExtractor = new VariantExtractor_1.VariantExtractor(this.config.context, this.config.dataSource, this.config.dataFieldName, this.config.debug);
118
+ this.variantExtractor = new VariantExtractor_1.VariantExtractor(this.config.context, this.config.variableName, this.config.debug);
123
119
  this.exposureTracker = new ExposureTracker_1.ExposureTracker(this.config.context, this.config.debug);
124
120
  this.htmlInjector = new HTMLInjector_1.HTMLInjector(this.config.debug);
121
+ this.persistenceManager = new persistence_1.DOMPersistenceManager({
122
+ debug: this.config.debug,
123
+ onReapply: function (change, experimentName) {
124
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] Re-applying change due to mutation', {
125
+ experimentName: experimentName,
126
+ selector: change.selector,
127
+ type: change.type,
128
+ });
129
+ _this.domManipulator.applyChange(change, experimentName);
130
+ },
131
+ });
132
+ // Auto-initialize when context is ready
133
+ this.readyPromise = this.config.context
134
+ .ready()
135
+ .then(function () {
136
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] Context is ready, starting initialization');
137
+ return _this.initialize();
138
+ })
139
+ .catch(function (error) {
140
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] ERROR during initialization:', error);
141
+ throw error;
142
+ });
125
143
  }
126
144
  DOMChangesPluginLite.prototype.ready = function () {
145
+ return __awaiter(this, void 0, void 0, function () {
146
+ return __generator(this, function (_a) {
147
+ return [2 /*return*/, this.readyPromise];
148
+ });
149
+ });
150
+ };
151
+ DOMChangesPluginLite.prototype.initialize = function () {
127
152
  return __awaiter(this, void 0, void 0, function () {
128
153
  var startTime, duration, error_1;
129
154
  return __generator(this, function (_a) {
@@ -140,7 +165,6 @@ var DOMChangesPluginLite = /** @class */ (function () {
140
165
  autoApply: this.config.autoApply,
141
166
  spa: this.config.spa,
142
167
  visibilityTracking: this.config.visibilityTracking,
143
- dataSource: this.config.dataSource,
144
168
  DEBUG: debug_1.DEBUG,
145
169
  },
146
170
  });
@@ -148,9 +172,14 @@ var DOMChangesPluginLite = /** @class */ (function () {
148
172
  case 1:
149
173
  _a.trys.push([1, 4, , 5]);
150
174
  if (this.config.spa) {
175
+ // SPA mode: observe body for React/Vue hydration recovery
176
+ // and listen for URL changes to re-apply changes on navigation
151
177
  this.setupMutationObserver();
152
178
  this.setupURLChangeListener();
153
179
  }
180
+ else {
181
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] SPA mode disabled - skipping body observer');
182
+ }
154
183
  if (!this.config.autoApply) return [3 /*break*/, 3];
155
184
  // Apply HTML injections and DOM changes in parallel to minimize flickering
156
185
  return [4 /*yield*/, this.applyInjectionsAndChanges()];
@@ -161,6 +190,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
161
190
  case 3:
162
191
  this.initialized = true;
163
192
  this.registerWithContext();
193
+ this.registerGlobally();
164
194
  this.emit('initialized');
165
195
  duration = performance.now() - startTime;
166
196
  (0, debug_1.logPerformance)('Plugin initialization', duration);
@@ -178,13 +208,6 @@ var DOMChangesPluginLite = /** @class */ (function () {
178
208
  });
179
209
  });
180
210
  };
181
- DOMChangesPluginLite.prototype.initialize = function () {
182
- return __awaiter(this, void 0, void 0, function () {
183
- return __generator(this, function (_a) {
184
- return [2 /*return*/, this.ready()];
185
- });
186
- });
187
- };
188
211
  DOMChangesPluginLite.prototype.setupMutationObserver = function () {
189
212
  var _this = this;
190
213
  var observer = new MutationObserver(function (mutations) {
@@ -193,10 +216,12 @@ var DOMChangesPluginLite = /** @class */ (function () {
193
216
  if (mutation.type === 'childList') {
194
217
  // Check if any added nodes match our applied change selectors
195
218
  mutation.addedNodes.forEach(function (node) {
219
+ var _a;
196
220
  if (node.nodeType === Node.ELEMENT_NODE) {
197
221
  var element_1 = node;
198
222
  // Check all experiments with applied changes (not just style changes)
199
- _this.appliedChanges.forEach(function (changes, experimentName) {
223
+ var appliedChanges = ((_a = _this.persistenceManager) === null || _a === void 0 ? void 0 : _a.getAppliedChanges()) || new Map();
224
+ appliedChanges.forEach(function (changes, experimentName) {
200
225
  changes.forEach(function (change) {
201
226
  try {
202
227
  // Check if this new element or any of its descendants match the selector
@@ -226,11 +251,30 @@ var DOMChangesPluginLite = /** @class */ (function () {
226
251
  }
227
252
  });
228
253
  });
229
- observer.observe(document.body, {
230
- childList: true,
231
- subtree: true,
232
- });
233
- this.mutationObserver = observer;
254
+ // Wait for document.body to exist before observing
255
+ // This prevents "parameter 1 is not of type 'Node'" errors when SDK loads in <head>
256
+ var startObserving = function () {
257
+ observer.observe(document.body, {
258
+ childList: true,
259
+ subtree: true,
260
+ });
261
+ _this.mutationObserver = observer;
262
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] MutationObserver started on document.body');
263
+ };
264
+ if (document.body) {
265
+ startObserving();
266
+ }
267
+ else {
268
+ // Use MutationObserver on documentElement to detect when body is added
269
+ var bodyObserver = new MutationObserver(function (_mutations, obs) {
270
+ if (document.body) {
271
+ obs.disconnect();
272
+ startObserving();
273
+ }
274
+ });
275
+ bodyObserver.observe(document.documentElement, { childList: true });
276
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] Waiting for document.body...');
277
+ }
234
278
  };
235
279
  /**
236
280
  * Set up URL change listener for SPA mode
@@ -297,7 +341,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
297
341
  // The stylesheets will be reused or cleared on next applyChanges
298
342
  this.styleManagers.clear();
299
343
  // Clear applied changes
300
- this.appliedChanges.clear();
344
+ if (this.persistenceManager) {
345
+ this.persistenceManager.clearAll();
346
+ }
301
347
  // Clear HTML injections
302
348
  this.htmlInjector.destroy();
303
349
  // Clear variant extractor cache to force re-extraction
@@ -414,22 +460,46 @@ var DOMChangesPluginLite = /** @class */ (function () {
414
460
  allExperiments = this.getAllExperimentsData();
415
461
  totalApplied = 0;
416
462
  experimentStats = new Map();
417
- (0, debug_1.logDebug)('[ABsmartly] Experiments to process:', Array.from(allExperiments.keys()));
418
- (0, debug_1.logDebug)('[ABsmartly] Total experiments with changes:', allExperiments.size);
463
+ (0, debug_1.logDebug)('[APPLY-CHANGES] Experiments found with DOM changes:', {
464
+ experimentNames: Array.from(allExperiments.keys()),
465
+ totalCount: allExperiments.size,
466
+ currentURL: currentURL,
467
+ });
468
+ if (allExperiments.size === 0) {
469
+ (0, debug_1.logDebug)('[APPLY-CHANGES] ⚠️ No experiments found with DOM changes - nothing to process');
470
+ }
419
471
  try {
420
472
  for (allExperiments_1 = __values(allExperiments), allExperiments_1_1 = allExperiments_1.next(); !allExperiments_1_1.done; allExperiments_1_1 = allExperiments_1.next()) {
421
473
  _a = __read(allExperiments_1_1.value, 2), expName = _a[0], experimentData = _a[1];
474
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] Starting to process experiment"), {
475
+ experimentName: expName,
476
+ currentURL: currentURL,
477
+ });
422
478
  // Skip if filtering by specific experiment
423
479
  if (experimentName && expName !== experimentName) {
480
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - filtering for different experiment"), {
481
+ filteringFor: experimentName,
482
+ });
424
483
  continue;
425
484
  }
426
485
  currentVariant = this.config.context.peek(expName);
486
+ if (currentVariant === undefined || currentVariant === null) {
487
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant assigned (peek returned ").concat(currentVariant, ")"), {
488
+ experimentName: expName,
489
+ });
490
+ continue;
491
+ }
492
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] User assigned to variant ").concat(currentVariant));
427
493
  variantData = experimentData.variantData, urlFilter = experimentData.urlFilter, globalDefaults = experimentData.globalDefaults;
428
494
  anyVariantMatchesURL = this.variantExtractor.anyVariantMatchesURL(expName, currentURL);
429
495
  if (!anyVariantMatchesURL) {
430
- (0, debug_1.logDebug)("[ABsmartly] Skipping experiment '".concat(expName, "' - no variant matches URL: ").concat(currentURL));
496
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant matches URL: ").concat(currentURL), {
497
+ experimentName: expName,
498
+ currentURL: currentURL,
499
+ });
431
500
  continue;
432
501
  }
502
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u2713 At least one variant matches current URL"));
433
503
  shouldApplyVisualChanges = this.shouldApplyVisualChanges(variantData, urlFilter, currentURL);
434
504
  changes = this.extractChangesFromData(variantData, globalDefaults);
435
505
  allVariantChanges = this.extractAllVariantChanges(expName);
@@ -443,7 +513,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
443
513
  }
444
514
  stats = { total: (changes === null || changes === void 0 ? void 0 : changes.length) || 0, success: 0, pending: 0 };
445
515
  if (this.config.debug) {
446
- (0, debug_1.logDebug)("[ABsmartly] Processing experiment '".concat(expName, "' (variant ").concat(currentVariant, "):"), {
516
+ (0, debug_1.logDebug)("[ABsmartly] Processing experiment '".concat(expName, "' - User is in variant ").concat(currentVariant, ":"), {
517
+ experimentName: expName,
518
+ userVariant: currentVariant,
447
519
  urlMatches: shouldApplyVisualChanges,
448
520
  changeCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
449
521
  userVariantHasChanges: ((changes === null || changes === void 0 ? void 0 : changes.length) || 0) > 0,
@@ -454,11 +526,28 @@ var DOMChangesPluginLite = /** @class */ (function () {
454
526
  }); })) || [],
455
527
  });
456
528
  }
529
+ // Log if user is in control variant (no changes)
530
+ if (!changes || changes.length === 0) {
531
+ if (this.config.debug) {
532
+ (0, debug_1.logDebug)("[ABsmartly] User is in control variant ".concat(currentVariant, " with no changes, but will still track for SRM prevention"), {
533
+ experimentName: expName,
534
+ currentVariant: currentVariant,
535
+ });
536
+ }
537
+ }
457
538
  // Apply visual changes only if URL matches for user's variant AND user has changes
458
539
  if (shouldApplyVisualChanges && changes && changes.length > 0) {
459
540
  try {
460
541
  for (changes_1 = (e_2 = void 0, __values(changes)), changes_1_1 = changes_1.next(); !changes_1_1.done; changes_1_1 = changes_1.next()) {
461
542
  change = changes_1_1.value;
543
+ // Skip delete changes with viewport triggers - they must be applied AFTER exposure
544
+ // Otherwise the element is removed before it can be observed
545
+ if (change.type === 'delete' && change.trigger_on_view) {
546
+ if (this.config.debug) {
547
+ (0, debug_1.logDebug)("[ABsmartly] Deferring delete change until after viewport trigger for '".concat(expName, "'"), { selector: change.selector });
548
+ }
549
+ continue;
550
+ }
462
551
  success = this.domManipulator.applyChange(change, expName);
463
552
  if (success) {
464
553
  totalApplied++;
@@ -576,14 +665,41 @@ var DOMChangesPluginLite = /** @class */ (function () {
576
665
  finally { if (e_3) throw e_3.error; }
577
666
  }
578
667
  if (this.config.debug) {
579
- (0, debug_1.logDebug)("[ABsmartly] Final trigger decision for '".concat(expName, "': hasImmediate=").concat(hasAnyImmediateTriggerInAnyVariant, ", hasViewport=").concat(hasAnyViewportTriggerInAnyVariant));
668
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] Final trigger decision for '").concat(expName, "':"), {
669
+ experimentName: expName,
670
+ userVariant: currentVariant,
671
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
672
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
673
+ willRegisterExperiment: hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant,
674
+ userChangesCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
675
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
676
+ });
580
677
  }
581
678
  // CRITICAL: Always register experiment for tracking if ANY variant has ANY trigger type
582
679
  // This prevents SRM even when user's variant doesn't match URL filter
583
680
  // Pass the URL-filtered trigger flags to ExposureTracker
584
681
  if (hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant) {
682
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] \u2713 REGISTERING EXPERIMENT - Calling exposureTracker.registerExperiment()"), {
683
+ experimentName: expName,
684
+ userVariant: currentVariant,
685
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
686
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
687
+ changesForUser: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
688
+ allVariantCounts: allVariantChanges.map(function (vc, idx) { return ({
689
+ variant: idx,
690
+ count: vc.length,
691
+ }); }),
692
+ });
585
693
  this.exposureTracker.registerExperiment(expName, currentVariant || 0, changes || [], allVariantChanges, hasAnyImmediateTriggerInAnyVariant, hasAnyViewportTriggerInAnyVariant);
586
694
  }
695
+ else {
696
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] \u26A0\uFE0F No triggers detected - experiment will NOT be registered!"), {
697
+ experimentName: expName,
698
+ userVariant: currentVariant,
699
+ hasAnyChangesInAnyVariant: hasAnyChangesInAnyVariant,
700
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
701
+ });
702
+ }
587
703
  // Note: We do NOT call treatment() here anymore to avoid duplicate calls.
588
704
  // The ExposureTracker.registerExperiment() will call triggerExposure() internally
589
705
  // when hasImmediateTrigger is true (see ExposureTracker line 169-174).
@@ -639,18 +755,43 @@ var DOMChangesPluginLite = /** @class */ (function () {
639
755
  var e_5, _a;
640
756
  var experiments = new Map();
641
757
  var allVariants = this.variantExtractor.extractAllChanges();
758
+ (0, debug_1.logDebug)('[GET-EXPERIMENTS] Extracting experiment data from context', {
759
+ experimentsWithChanges: Array.from(allVariants.keys()),
760
+ totalCount: allVariants.size,
761
+ });
642
762
  try {
643
763
  for (var allVariants_1 = __values(allVariants), allVariants_1_1 = allVariants_1.next(); !allVariants_1_1.done; allVariants_1_1 = allVariants_1.next()) {
644
764
  var _b = __read(allVariants_1_1.value, 1), expName = _b[0];
645
765
  var currentVariant = this.config.context.peek(expName);
766
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] Checking experiment"), {
767
+ experimentName: expName,
768
+ currentVariant: currentVariant,
769
+ variantIsValid: currentVariant !== undefined && currentVariant !== null,
770
+ });
646
771
  if (currentVariant === undefined || currentVariant === null) {
772
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u23ED\uFE0F Skipping - context.peek() returned ").concat(currentVariant));
647
773
  continue;
648
774
  }
649
775
  var variantsData = this.variantExtractor.getAllVariantsData(expName);
650
776
  var variantData = variantsData.get(currentVariant);
777
+ if (this.config.debug) {
778
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] Variant data lookup:"), {
779
+ experimentName: expName,
780
+ userVariant: currentVariant,
781
+ variantDataExists: !!variantData,
782
+ allVariantsInMap: Array.from(variantsData.keys()),
783
+ userVariantInMap: variantsData.has(currentVariant),
784
+ });
785
+ }
651
786
  if (!variantData) {
652
- continue;
787
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u26A0\uFE0F User's variant ").concat(currentVariant, " has no DOM changes (control variant)"), {
788
+ availableVariants: Array.from(variantsData.keys()),
789
+ note: 'Will still track for SRM prevention',
790
+ });
791
+ // Don't skip! Include experiments even if user's variant has no changes
792
+ // This is critical for SRM prevention - we need to track ALL experiments
653
793
  }
794
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u2713 Including experiment (variant ").concat(currentVariant, ")"));
654
795
  // Extract URL filter and global defaults if using wrapped format
655
796
  var urlFilter = null;
656
797
  var globalDefaults = {};
@@ -683,12 +824,15 @@ var DOMChangesPluginLite = /** @class */ (function () {
683
824
  * Extract changes from DOMChangesData and apply global defaults
684
825
  */
685
826
  DOMChangesPluginLite.prototype.extractChangesFromData = function (data, globalDefaults) {
827
+ if (!data) {
828
+ return null;
829
+ }
686
830
  var changes = null;
687
831
  // Extract changes array
688
832
  if (Array.isArray(data)) {
689
833
  changes = data;
690
834
  }
691
- else if (data && typeof data === 'object' && 'changes' in data) {
835
+ else if (typeof data === 'object' && 'changes' in data) {
692
836
  changes = data.changes;
693
837
  }
694
838
  if (!changes || changes.length === 0) {
@@ -745,6 +889,10 @@ var DOMChangesPluginLite = /** @class */ (function () {
745
889
  * Determine if visual changes should be applied based on URL filter
746
890
  */
747
891
  DOMChangesPluginLite.prototype.shouldApplyVisualChanges = function (variantData, urlFilter, url) {
892
+ // No data for this variant - no changes to apply
893
+ if (!variantData) {
894
+ return false;
895
+ }
748
896
  // Legacy array format has no URL filter - always apply
749
897
  if (Array.isArray(variantData)) {
750
898
  return true;
@@ -878,9 +1026,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
878
1026
  this.mutationObserver.disconnect();
879
1027
  this.mutationObserver = null;
880
1028
  }
881
- if (this.persistenceObserver) {
882
- this.persistenceObserver.disconnect();
883
- this.persistenceObserver = null;
1029
+ if (this.persistenceManager) {
1030
+ this.persistenceManager.destroy();
1031
+ this.persistenceManager = null;
884
1032
  }
885
1033
  // Clean up anti-flicker timeout and style
886
1034
  if (this.antiFlickerTimeout !== null) {
@@ -893,11 +1041,8 @@ var DOMChangesPluginLite = /** @class */ (function () {
893
1041
  }
894
1042
  this.eventListeners.clear();
895
1043
  this.exposedExperiments.clear();
896
- this.watchedElements = new WeakMap();
897
- this.appliedChanges.clear();
898
- this.reapplyingElements.clear();
899
- this.reapplyLogThrottle.clear();
900
1044
  this.unregisterFromContext();
1045
+ this.unregisterGlobally();
901
1046
  this.initialized = false;
902
1047
  if (this.config.debug) {
903
1048
  (0, debug_1.logDebug)('[ABsmartly] DOM Changes Plugin Lite destroyed');
@@ -936,13 +1081,38 @@ var DOMChangesPluginLite = /** @class */ (function () {
936
1081
  }
937
1082
  }
938
1083
  };
1084
+ /**
1085
+ * Register plugin in global registry for detection
1086
+ */
1087
+ DOMChangesPluginLite.prototype.registerGlobally = function () {
1088
+ (0, plugin_registry_1.registerPlugin)('dom', {
1089
+ name: 'DOMChangesPluginLite',
1090
+ version: DOMChangesPluginLite.VERSION,
1091
+ initialized: true,
1092
+ timestamp: Date.now(),
1093
+ capabilities: ['spa', 'visibility', 'auto-apply'],
1094
+ instance: this,
1095
+ });
1096
+ if (this.config.debug) {
1097
+ (0, debug_1.logDebug)('[ABsmartly] DOMChangesPluginLite registered in global window.__ABSMARTLY_PLUGINS__');
1098
+ }
1099
+ };
1100
+ /**
1101
+ * Unregister plugin from global registry
1102
+ */
1103
+ DOMChangesPluginLite.prototype.unregisterGlobally = function () {
1104
+ (0, plugin_registry_1.unregisterPlugin)('dom');
1105
+ if (this.config.debug) {
1106
+ (0, debug_1.logDebug)('[ABsmartly] DOMChangesPluginLite unregistered from global registry');
1107
+ }
1108
+ };
939
1109
  /**
940
1110
  * Hide content to prevent flicker before experiments are applied
941
1111
  */
942
1112
  DOMChangesPluginLite.prototype.hideContent = function () {
943
1113
  var _this = this;
944
- var mode = this.config.hideUntilReady;
945
- if (!mode)
1114
+ var selector = this.config.hideUntilReady;
1115
+ if (!selector)
946
1116
  return;
947
1117
  // Check if style already exists (prevent duplicate injection)
948
1118
  if (document.getElementById(this.antiFlickerStyleId)) {
@@ -951,7 +1121,6 @@ var DOMChangesPluginLite = /** @class */ (function () {
951
1121
  var style = document.createElement('style');
952
1122
  style.id = this.antiFlickerStyleId;
953
1123
  var hasTransition = this.config.hideTransition !== false;
954
- var selector = mode === 'body' ? 'body' : this.config.hideSelector || '[data-absmartly-hide]';
955
1124
  if (hasTransition) {
956
1125
  // Use both visibility:hidden and opacity:0 for smooth transition
957
1126
  style.textContent = "\n ".concat(selector, " {\n visibility: hidden !important;\n opacity: 0 !important;\n }\n ");
@@ -961,7 +1130,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
961
1130
  style.textContent = "\n ".concat(selector, " {\n visibility: hidden !important;\n }\n ");
962
1131
  }
963
1132
  document.head.appendChild(style);
964
- // Set timeout to show content even if experiments fail to load
1133
+ // Set timeout to show content even if experiments fail to load or timeout expires
965
1134
  this.antiFlickerTimeout = window.setTimeout(function () {
966
1135
  if (_this.config.debug) {
967
1136
  (0, debug_1.logDebug)("[ABsmartly] Anti-flicker timeout reached (".concat(_this.config.hideTimeout, "ms), showing content"));
@@ -969,11 +1138,11 @@ var DOMChangesPluginLite = /** @class */ (function () {
969
1138
  _this.showContent();
970
1139
  }, this.config.hideTimeout);
971
1140
  if (this.config.debug) {
972
- (0, debug_1.logDebug)("[ABsmartly] Anti-flicker enabled (mode: ".concat(mode, ", transition: ").concat(hasTransition ? this.config.hideTransition : 'none', ", timeout: ").concat(this.config.hideTimeout, "ms)"));
1141
+ (0, debug_1.logDebug)("[ABsmartly] Anti-flicker enabled (selector: '".concat(selector, "', transition: ").concat(hasTransition ? this.config.hideTransition : 'none', ", timeout: ").concat(this.config.hideTimeout, "ms)"));
973
1142
  }
974
1143
  };
975
1144
  /**
976
- * Show hidden content after experiments are applied
1145
+ * Show hidden content after experiments are applied or timeout expires
977
1146
  */
978
1147
  DOMChangesPluginLite.prototype.showContent = function () {
979
1148
  var _this = this;
@@ -988,8 +1157,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
988
1157
  var hasTransition = this.config.hideTransition !== false;
989
1158
  if (hasTransition) {
990
1159
  // Smooth fade-in: remove visibility, add transition, then animate opacity
991
- var mode = this.config.hideUntilReady;
992
- var selector = mode === 'body' ? 'body' : this.config.hideSelector || '[data-absmartly-hide]';
1160
+ var selector = this.config.hideUntilReady;
993
1161
  // Step 1: Remove visibility:hidden, keep opacity:0, add transition
994
1162
  style.textContent = "\n ".concat(selector, " {\n opacity: 0 !important;\n transition: opacity ").concat(this.config.hideTransition, " !important;\n }\n ");
995
1163
  // Step 2: Force reflow to ensure transition applies
@@ -1017,206 +1185,16 @@ var DOMChangesPluginLite = /** @class */ (function () {
1017
1185
  }
1018
1186
  };
1019
1187
  DOMChangesPluginLite.prototype.watchElement = function (element, experimentName, change) {
1020
- var experiments = this.watchedElements.get(element);
1021
- if (!experiments) {
1022
- experiments = new Set();
1023
- this.watchedElements.set(element, experiments);
1024
- }
1025
- experiments.add(experimentName);
1026
- // Store the applied change for this experiment (ALL types: style, class, attribute, html, text, etc.)
1027
- if (!this.appliedChanges.has(experimentName)) {
1028
- this.appliedChanges.set(experimentName, []);
1029
- }
1030
- var changes = this.appliedChanges.get(experimentName);
1031
- var isNewWatch = !changes.includes(change);
1032
- if (isNewWatch) {
1033
- changes.push(change);
1034
- // Only log when actually adding a new watch, not on every reapply
1035
- if (this.config.debug) {
1036
- var currentStyles_1 = {};
1037
- if (change.value && typeof change.value === 'object') {
1038
- Object.keys(change.value).forEach(function (prop) {
1039
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1040
- currentStyles_1[cssProp] = element.style.getPropertyValue(cssProp);
1041
- });
1042
- }
1043
- (0, debug_1.logDebug)('[WATCH-ELEMENT] Started watching element for persistence/hydration recovery', {
1044
- experimentName: experimentName,
1045
- selector: change.selector,
1046
- element: element.tagName,
1047
- changeType: change.type,
1048
- value: change.value,
1049
- currentStyles: change.type === 'style' ? currentStyles_1 : undefined,
1050
- timestamp: Date.now(),
1051
- });
1052
- }
1053
- }
1054
- if (!this.persistenceObserver) {
1055
- this.setupPersistenceObserver();
1188
+ if (this.persistenceManager) {
1189
+ this.persistenceManager.watchElement(element, experimentName, change);
1056
1190
  }
1057
1191
  };
1058
1192
  DOMChangesPluginLite.prototype.unwatchElement = function (element, experimentName) {
1059
- var experiments = this.watchedElements.get(element);
1060
- if (experiments) {
1061
- experiments.delete(experimentName);
1062
- if (experiments.size === 0) {
1063
- this.watchedElements.delete(element);
1064
- }
1065
- }
1066
- };
1067
- DOMChangesPluginLite.prototype.setupPersistenceObserver = function () {
1068
- var _this = this;
1069
- if (this.persistenceObserver)
1070
- return;
1071
- if (this.config.debug) {
1072
- (0, debug_1.logDebug)('[PERSISTENCE-OBSERVER] Setting up persistence observer', {
1073
- timestamp: Date.now(),
1074
- watchedElementsCount: this.watchedElements ? 'has WeakMap' : 'no WeakMap',
1075
- });
1076
- }
1077
- this.persistenceObserver = new MutationObserver(function (mutations) {
1078
- if (_this.config.debug) {
1079
- (0, debug_1.logDebug)('[MUTATION-DETECTED] Persistence observer detected mutations', {
1080
- mutationCount: mutations.length,
1081
- timestamp: Date.now(),
1082
- mutations: mutations.map(function (m) {
1083
- var _a;
1084
- return ({
1085
- target: m.target.tagName,
1086
- oldValue: (_a = m.oldValue) === null || _a === void 0 ? void 0 : _a.substring(0, 100),
1087
- });
1088
- }),
1089
- });
1090
- }
1091
- mutations.forEach(function (mutation) {
1092
- var element = mutation.target;
1093
- if (_this.reapplyingElements.has(element)) {
1094
- if (_this.config.debug) {
1095
- (0, debug_1.logDebug)('[MUTATION-SKIP] Skipping mutation - currently reapplying', {
1096
- element: element.tagName,
1097
- });
1098
- }
1099
- return;
1100
- }
1101
- var experiments = _this.watchedElements.get(element);
1102
- if (experiments) {
1103
- // Throttle mutation detection logs to once per 5 seconds per element
1104
- if (_this.config.debug) {
1105
- var elementKey = "mutation:".concat(element.tagName, ":").concat(element.getAttribute('name') || element.className);
1106
- var now = Date.now();
1107
- var lastLogged = _this.reapplyLogThrottle.get(elementKey) || 0;
1108
- if (now - lastLogged > 5000) {
1109
- (0, debug_1.logDebug)('[MUTATION-ON-WATCHED] Style mutation detected on watched element', {
1110
- element: element.tagName,
1111
- selector: element.getAttribute('name') || element.className,
1112
- oldValue: mutation.oldValue,
1113
- newValue: element.getAttribute('style'),
1114
- experiments: Array.from(experiments),
1115
- });
1116
- _this.reapplyLogThrottle.set(elementKey, now);
1117
- }
1118
- }
1119
- experiments.forEach(function (experimentName) {
1120
- var appliedChanges = _this.appliedChanges.get(experimentName);
1121
- if (appliedChanges) {
1122
- appliedChanges.forEach(function (change) {
1123
- var e_7, _a;
1124
- // Skip the .matches() check - we already know this element should have this change
1125
- // because we added it to watchedElements when we applied it successfully.
1126
- // The .matches() check fails for complex selectors with parent relationships (e.g., "div > p")
1127
- if (change.type === 'style') {
1128
- var needsReapply = _this.checkStyleOverwritten(element, change.value);
1129
- if (needsReapply) {
1130
- _this.reapplyingElements.add(element);
1131
- var logKey = "".concat(experimentName, "-").concat(change.selector);
1132
- var now = Date.now();
1133
- var lastLogged = _this.reapplyLogThrottle.get(logKey) || 0;
1134
- if (_this.config.debug && now - lastLogged > 5000) {
1135
- (0, debug_1.logDebug)('[REAPPLY-TRIGGERED] Reapplying style after mutation (React/framework conflict detected)', {
1136
- experimentName: experimentName,
1137
- selector: change.selector,
1138
- element: element.tagName,
1139
- timestamp: now,
1140
- note: 'This happens when the page framework (React/Vue/etc) fights with DOM changes',
1141
- });
1142
- _this.reapplyLogThrottle.set(logKey, now);
1143
- // Cleanup old throttle entries to prevent memory leak
1144
- if (_this.reapplyLogThrottle.size > 100) {
1145
- var oldestAllowed = now - 60000; // 1 minute
1146
- try {
1147
- for (var _b = __values(_this.reapplyLogThrottle.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
1148
- var _d = __read(_c.value, 2), key = _d[0], time = _d[1];
1149
- if (time < oldestAllowed) {
1150
- _this.reapplyLogThrottle.delete(key);
1151
- }
1152
- }
1153
- }
1154
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
1155
- finally {
1156
- try {
1157
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1158
- }
1159
- finally { if (e_7) throw e_7.error; }
1160
- }
1161
- }
1162
- }
1163
- _this.domManipulator.applyChange(change, experimentName);
1164
- setTimeout(function () {
1165
- _this.reapplyingElements.delete(element);
1166
- }, 0);
1167
- }
1168
- else if (_this.config.debug) {
1169
- (0, debug_1.logDebug)('[MUTATION-NO-REAPPLY] Style mutation detected but no reapply needed', {
1170
- experimentName: experimentName,
1171
- selector: change.selector,
1172
- });
1173
- }
1174
- }
1175
- });
1176
- }
1177
- });
1178
- }
1179
- });
1180
- });
1181
- this.persistenceObserver.observe(document.body, {
1182
- attributes: true,
1183
- attributeFilter: ['style'],
1184
- subtree: true,
1185
- attributeOldValue: true,
1186
- });
1187
- if (this.config.debug) {
1188
- (0, debug_1.logDebug)('[PERSISTENCE-OBSERVER] Setup complete - now observing style mutations', {
1189
- target: 'document.body',
1190
- attributeFilter: ['style'],
1191
- subtree: true,
1192
- attributeOldValue: true,
1193
- timestamp: Date.now(),
1194
- });
1195
- }
1196
- };
1197
- DOMChangesPluginLite.prototype.checkStyleOverwritten = function (element, expectedStyles) {
1198
- var e_8, _a;
1199
- try {
1200
- for (var _b = __values(Object.entries(expectedStyles)), _c = _b.next(); !_c.done; _c = _b.next()) {
1201
- var _d = __read(_c.value, 2), prop = _d[0], value = _d[1];
1202
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1203
- var currentValue = element.style.getPropertyValue(cssProp);
1204
- var currentPriority = element.style.getPropertyPriority(cssProp);
1205
- if (currentValue !== value || (value.includes('!important') && !currentPriority)) {
1206
- return true;
1207
- }
1208
- }
1209
- }
1210
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
1211
- finally {
1212
- try {
1213
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1214
- }
1215
- finally { if (e_8) throw e_8.error; }
1193
+ if (this.persistenceManager) {
1194
+ this.persistenceManager.unwatchElement(element, experimentName);
1216
1195
  }
1217
- return false;
1218
1196
  };
1219
- DOMChangesPluginLite.VERSION = '1.0.0-lite';
1197
+ DOMChangesPluginLite.VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : '1.1.2';
1220
1198
  return DOMChangesPluginLite;
1221
1199
  }());
1222
1200
  exports.DOMChangesPluginLite = DOMChangesPluginLite;