@absmartly/sdk-plugins 1.1.2 → 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 (98) hide show
  1. package/dist/absmartly-dom-changes-core.min.js +1 -1
  2. package/dist/absmartly-dom-changes-lite.min.js +1 -1
  3. package/dist/absmartly-overrides-full.min.js +1 -1
  4. package/dist/absmartly-overrides-lite.min.js +1 -1
  5. package/dist/absmartly-sdk-plugins.min.js +1 -1
  6. package/es/core/DOMChangesPluginLite.js +192 -220
  7. package/es/core/DOMChangesPluginLite.js.map +1 -1
  8. package/es/core/DOMManipulatorLite.js +66 -9
  9. package/es/core/DOMManipulatorLite.js.map +1 -1
  10. package/es/core/ExposureTracker.js +136 -11
  11. package/es/core/ExposureTracker.js.map +1 -1
  12. package/es/entries/dom-with-overrides-lite.js +3 -0
  13. package/es/entries/dom-with-overrides-lite.js.map +1 -1
  14. package/es/index.js +9 -2
  15. package/es/index.js.map +1 -1
  16. package/es/overrides/OverridesPlugin.js +2 -2
  17. package/es/overrides/OverridesPlugin.js.map +1 -1
  18. package/es/overrides/OverridesPluginLite.js +29 -1
  19. package/es/overrides/OverridesPluginLite.js.map +1 -1
  20. package/es/overrides/overridesUtils.js +27 -18
  21. package/es/overrides/overridesUtils.js.map +1 -1
  22. package/es/parsers/VariantExtractor.js +14 -7
  23. package/es/parsers/VariantExtractor.js.map +1 -1
  24. package/es/url-redirect/URLRedirectExtractor.js +185 -0
  25. package/es/url-redirect/URLRedirectExtractor.js.map +1 -0
  26. package/es/url-redirect/URLRedirectMatcher.js +115 -0
  27. package/es/url-redirect/URLRedirectMatcher.js.map +1 -0
  28. package/es/url-redirect/URLRedirectPlugin.js +350 -0
  29. package/es/url-redirect/URLRedirectPlugin.js.map +1 -0
  30. package/es/url-redirect/index.js +5 -0
  31. package/es/url-redirect/index.js.map +1 -0
  32. package/es/url-redirect/types.js +2 -0
  33. package/es/url-redirect/types.js.map +1 -0
  34. package/es/utils/persistence.js +278 -0
  35. package/es/utils/persistence.js.map +1 -0
  36. package/es/utils/plugin-registry.js +47 -0
  37. package/es/utils/plugin-registry.js.map +1 -0
  38. package/lib/core/DOMChangesPluginLite.d.ts +10 -7
  39. package/lib/core/DOMChangesPluginLite.js +192 -220
  40. package/lib/core/DOMChangesPluginLite.js.map +1 -1
  41. package/lib/core/DOMManipulatorLite.js +66 -9
  42. package/lib/core/DOMManipulatorLite.js.map +1 -1
  43. package/lib/core/ExposureTracker.d.ts +6 -0
  44. package/lib/core/ExposureTracker.js +136 -11
  45. package/lib/core/ExposureTracker.js.map +1 -1
  46. package/lib/entries/dom-with-overrides-lite.d.ts +2 -0
  47. package/lib/entries/dom-with-overrides-lite.js +3 -1
  48. package/lib/entries/dom-with-overrides-lite.js.map +1 -1
  49. package/lib/index.d.ts +8 -2
  50. package/lib/index.js +17 -1
  51. package/lib/index.js.map +1 -1
  52. package/lib/overrides/OverridesPlugin.js +2 -2
  53. package/lib/overrides/OverridesPlugin.js.map +1 -1
  54. package/lib/overrides/OverridesPluginLite.d.ts +8 -0
  55. package/lib/overrides/OverridesPluginLite.js +29 -1
  56. package/lib/overrides/OverridesPluginLite.js.map +1 -1
  57. package/lib/overrides/overridesUtils.d.ts +10 -5
  58. package/lib/overrides/overridesUtils.js +28 -18
  59. package/lib/overrides/overridesUtils.js.map +1 -1
  60. package/lib/parsers/VariantExtractor.js +14 -7
  61. package/lib/parsers/VariantExtractor.js.map +1 -1
  62. package/lib/types/index.d.ts +1 -0
  63. package/lib/url-redirect/URLRedirectExtractor.d.ts +16 -0
  64. package/lib/url-redirect/URLRedirectExtractor.js +188 -0
  65. package/lib/url-redirect/URLRedirectExtractor.js.map +1 -0
  66. package/lib/url-redirect/URLRedirectMatcher.d.ts +8 -0
  67. package/lib/url-redirect/URLRedirectMatcher.js +118 -0
  68. package/lib/url-redirect/URLRedirectMatcher.js.map +1 -0
  69. package/lib/url-redirect/URLRedirectPlugin.d.ts +21 -0
  70. package/lib/url-redirect/URLRedirectPlugin.js +353 -0
  71. package/lib/url-redirect/URLRedirectPlugin.js.map +1 -0
  72. package/lib/url-redirect/index.d.ts +4 -0
  73. package/lib/url-redirect/index.js +25 -0
  74. package/lib/url-redirect/index.js.map +1 -0
  75. package/lib/url-redirect/types.d.ts +29 -0
  76. package/lib/url-redirect/types.js +3 -0
  77. package/lib/url-redirect/types.js.map +1 -0
  78. package/lib/utils/persistence.d.ts +23 -0
  79. package/lib/utils/persistence.js +281 -0
  80. package/lib/utils/persistence.js.map +1 -0
  81. package/lib/utils/plugin-registry.d.ts +40 -0
  82. package/lib/utils/plugin-registry.js +53 -0
  83. package/lib/utils/plugin-registry.js.map +1 -0
  84. package/package.json +59 -3
  85. package/types/core/DOMChangesPluginLite.d.ts +10 -7
  86. package/types/core/ExposureTracker.d.ts +6 -0
  87. package/types/entries/dom-with-overrides-lite.d.ts +2 -0
  88. package/types/index.d.ts +8 -2
  89. package/types/overrides/OverridesPluginLite.d.ts +8 -0
  90. package/types/overrides/overridesUtils.d.ts +10 -5
  91. package/types/types/index.d.ts +1 -0
  92. package/types/url-redirect/URLRedirectExtractor.d.ts +16 -0
  93. package/types/url-redirect/URLRedirectMatcher.d.ts +8 -0
  94. package/types/url-redirect/URLRedirectPlugin.d.ts +21 -0
  95. package/types/url-redirect/index.d.ts +4 -0
  96. package/types/url-redirect/types.d.ts +29 -0
  97. package/types/utils/persistence.d.ts +23 -0
  98. package/types/utils/plugin-registry.d.ts +40 -0
@@ -82,6 +82,8 @@ 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
89
  var _this = this;
@@ -91,11 +93,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
91
93
  this.eventListeners = new Map();
92
94
  this.styleManagers = new Map();
93
95
  this.initialized = false;
94
- this.watchedElements = new WeakMap();
95
- this.persistenceObserver = null;
96
- this.reapplyingElements = new Set();
97
- this.reapplyLogThrottle = new Map();
98
- this.appliedChanges = new Map();
96
+ this.persistenceManager = null;
99
97
  this.antiFlickerTimeout = null;
100
98
  this.antiFlickerStyleId = 'absmartly-antiflicker';
101
99
  this.config = {
@@ -120,6 +118,17 @@ var DOMChangesPluginLite = /** @class */ (function () {
120
118
  this.variantExtractor = new VariantExtractor_1.VariantExtractor(this.config.context, this.config.variableName, this.config.debug);
121
119
  this.exposureTracker = new ExposureTracker_1.ExposureTracker(this.config.context, this.config.debug);
122
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
+ });
123
132
  // Auto-initialize when context is ready
124
133
  this.readyPromise = this.config.context
125
134
  .ready()
@@ -163,9 +172,14 @@ var DOMChangesPluginLite = /** @class */ (function () {
163
172
  case 1:
164
173
  _a.trys.push([1, 4, , 5]);
165
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
166
177
  this.setupMutationObserver();
167
178
  this.setupURLChangeListener();
168
179
  }
180
+ else {
181
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] SPA mode disabled - skipping body observer');
182
+ }
169
183
  if (!this.config.autoApply) return [3 /*break*/, 3];
170
184
  // Apply HTML injections and DOM changes in parallel to minimize flickering
171
185
  return [4 /*yield*/, this.applyInjectionsAndChanges()];
@@ -176,6 +190,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
176
190
  case 3:
177
191
  this.initialized = true;
178
192
  this.registerWithContext();
193
+ this.registerGlobally();
179
194
  this.emit('initialized');
180
195
  duration = performance.now() - startTime;
181
196
  (0, debug_1.logPerformance)('Plugin initialization', duration);
@@ -201,10 +216,12 @@ var DOMChangesPluginLite = /** @class */ (function () {
201
216
  if (mutation.type === 'childList') {
202
217
  // Check if any added nodes match our applied change selectors
203
218
  mutation.addedNodes.forEach(function (node) {
219
+ var _a;
204
220
  if (node.nodeType === Node.ELEMENT_NODE) {
205
221
  var element_1 = node;
206
222
  // Check all experiments with applied changes (not just style changes)
207
- _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) {
208
225
  changes.forEach(function (change) {
209
226
  try {
210
227
  // Check if this new element or any of its descendants match the selector
@@ -234,11 +251,30 @@ var DOMChangesPluginLite = /** @class */ (function () {
234
251
  }
235
252
  });
236
253
  });
237
- observer.observe(document.body, {
238
- childList: true,
239
- subtree: true,
240
- });
241
- 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
+ }
242
278
  };
243
279
  /**
244
280
  * Set up URL change listener for SPA mode
@@ -305,7 +341,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
305
341
  // The stylesheets will be reused or cleared on next applyChanges
306
342
  this.styleManagers.clear();
307
343
  // Clear applied changes
308
- this.appliedChanges.clear();
344
+ if (this.persistenceManager) {
345
+ this.persistenceManager.clearAll();
346
+ }
309
347
  // Clear HTML injections
310
348
  this.htmlInjector.destroy();
311
349
  // Clear variant extractor cache to force re-extraction
@@ -422,22 +460,46 @@ var DOMChangesPluginLite = /** @class */ (function () {
422
460
  allExperiments = this.getAllExperimentsData();
423
461
  totalApplied = 0;
424
462
  experimentStats = new Map();
425
- (0, debug_1.logDebug)('[ABsmartly] Experiments to process:', Array.from(allExperiments.keys()));
426
- (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
+ }
427
471
  try {
428
472
  for (allExperiments_1 = __values(allExperiments), allExperiments_1_1 = allExperiments_1.next(); !allExperiments_1_1.done; allExperiments_1_1 = allExperiments_1.next()) {
429
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
+ });
430
478
  // Skip if filtering by specific experiment
431
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
+ });
432
483
  continue;
433
484
  }
434
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));
435
493
  variantData = experimentData.variantData, urlFilter = experimentData.urlFilter, globalDefaults = experimentData.globalDefaults;
436
494
  anyVariantMatchesURL = this.variantExtractor.anyVariantMatchesURL(expName, currentURL);
437
495
  if (!anyVariantMatchesURL) {
438
- (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
+ });
439
500
  continue;
440
501
  }
502
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u2713 At least one variant matches current URL"));
441
503
  shouldApplyVisualChanges = this.shouldApplyVisualChanges(variantData, urlFilter, currentURL);
442
504
  changes = this.extractChangesFromData(variantData, globalDefaults);
443
505
  allVariantChanges = this.extractAllVariantChanges(expName);
@@ -451,7 +513,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
451
513
  }
452
514
  stats = { total: (changes === null || changes === void 0 ? void 0 : changes.length) || 0, success: 0, pending: 0 };
453
515
  if (this.config.debug) {
454
- (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,
455
519
  urlMatches: shouldApplyVisualChanges,
456
520
  changeCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
457
521
  userVariantHasChanges: ((changes === null || changes === void 0 ? void 0 : changes.length) || 0) > 0,
@@ -462,11 +526,28 @@ var DOMChangesPluginLite = /** @class */ (function () {
462
526
  }); })) || [],
463
527
  });
464
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
+ }
465
538
  // Apply visual changes only if URL matches for user's variant AND user has changes
466
539
  if (shouldApplyVisualChanges && changes && changes.length > 0) {
467
540
  try {
468
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()) {
469
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
+ }
470
551
  success = this.domManipulator.applyChange(change, expName);
471
552
  if (success) {
472
553
  totalApplied++;
@@ -584,14 +665,41 @@ var DOMChangesPluginLite = /** @class */ (function () {
584
665
  finally { if (e_3) throw e_3.error; }
585
666
  }
586
667
  if (this.config.debug) {
587
- (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
+ });
588
677
  }
589
678
  // CRITICAL: Always register experiment for tracking if ANY variant has ANY trigger type
590
679
  // This prevents SRM even when user's variant doesn't match URL filter
591
680
  // Pass the URL-filtered trigger flags to ExposureTracker
592
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
+ });
593
693
  this.exposureTracker.registerExperiment(expName, currentVariant || 0, changes || [], allVariantChanges, hasAnyImmediateTriggerInAnyVariant, hasAnyViewportTriggerInAnyVariant);
594
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
+ }
595
703
  // Note: We do NOT call treatment() here anymore to avoid duplicate calls.
596
704
  // The ExposureTracker.registerExperiment() will call triggerExposure() internally
597
705
  // when hasImmediateTrigger is true (see ExposureTracker line 169-174).
@@ -647,18 +755,43 @@ var DOMChangesPluginLite = /** @class */ (function () {
647
755
  var e_5, _a;
648
756
  var experiments = new Map();
649
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
+ });
650
762
  try {
651
763
  for (var allVariants_1 = __values(allVariants), allVariants_1_1 = allVariants_1.next(); !allVariants_1_1.done; allVariants_1_1 = allVariants_1.next()) {
652
764
  var _b = __read(allVariants_1_1.value, 1), expName = _b[0];
653
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
+ });
654
771
  if (currentVariant === undefined || currentVariant === null) {
772
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u23ED\uFE0F Skipping - context.peek() returned ").concat(currentVariant));
655
773
  continue;
656
774
  }
657
775
  var variantsData = this.variantExtractor.getAllVariantsData(expName);
658
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
+ }
659
786
  if (!variantData) {
660
- 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
661
793
  }
794
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u2713 Including experiment (variant ").concat(currentVariant, ")"));
662
795
  // Extract URL filter and global defaults if using wrapped format
663
796
  var urlFilter = null;
664
797
  var globalDefaults = {};
@@ -691,12 +824,15 @@ var DOMChangesPluginLite = /** @class */ (function () {
691
824
  * Extract changes from DOMChangesData and apply global defaults
692
825
  */
693
826
  DOMChangesPluginLite.prototype.extractChangesFromData = function (data, globalDefaults) {
827
+ if (!data) {
828
+ return null;
829
+ }
694
830
  var changes = null;
695
831
  // Extract changes array
696
832
  if (Array.isArray(data)) {
697
833
  changes = data;
698
834
  }
699
- else if (data && typeof data === 'object' && 'changes' in data) {
835
+ else if (typeof data === 'object' && 'changes' in data) {
700
836
  changes = data.changes;
701
837
  }
702
838
  if (!changes || changes.length === 0) {
@@ -753,6 +889,10 @@ var DOMChangesPluginLite = /** @class */ (function () {
753
889
  * Determine if visual changes should be applied based on URL filter
754
890
  */
755
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
+ }
756
896
  // Legacy array format has no URL filter - always apply
757
897
  if (Array.isArray(variantData)) {
758
898
  return true;
@@ -886,9 +1026,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
886
1026
  this.mutationObserver.disconnect();
887
1027
  this.mutationObserver = null;
888
1028
  }
889
- if (this.persistenceObserver) {
890
- this.persistenceObserver.disconnect();
891
- this.persistenceObserver = null;
1029
+ if (this.persistenceManager) {
1030
+ this.persistenceManager.destroy();
1031
+ this.persistenceManager = null;
892
1032
  }
893
1033
  // Clean up anti-flicker timeout and style
894
1034
  if (this.antiFlickerTimeout !== null) {
@@ -901,11 +1041,8 @@ var DOMChangesPluginLite = /** @class */ (function () {
901
1041
  }
902
1042
  this.eventListeners.clear();
903
1043
  this.exposedExperiments.clear();
904
- this.watchedElements = new WeakMap();
905
- this.appliedChanges.clear();
906
- this.reapplyingElements.clear();
907
- this.reapplyLogThrottle.clear();
908
1044
  this.unregisterFromContext();
1045
+ this.unregisterGlobally();
909
1046
  this.initialized = false;
910
1047
  if (this.config.debug) {
911
1048
  (0, debug_1.logDebug)('[ABsmartly] DOM Changes Plugin Lite destroyed');
@@ -944,6 +1081,31 @@ var DOMChangesPluginLite = /** @class */ (function () {
944
1081
  }
945
1082
  }
946
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
+ };
947
1109
  /**
948
1110
  * Hide content to prevent flicker before experiments are applied
949
1111
  */
@@ -1023,204 +1185,14 @@ var DOMChangesPluginLite = /** @class */ (function () {
1023
1185
  }
1024
1186
  };
1025
1187
  DOMChangesPluginLite.prototype.watchElement = function (element, experimentName, change) {
1026
- var experiments = this.watchedElements.get(element);
1027
- if (!experiments) {
1028
- experiments = new Set();
1029
- this.watchedElements.set(element, experiments);
1030
- }
1031
- experiments.add(experimentName);
1032
- // Store the applied change for this experiment (ALL types: style, class, attribute, html, text, etc.)
1033
- if (!this.appliedChanges.has(experimentName)) {
1034
- this.appliedChanges.set(experimentName, []);
1035
- }
1036
- var changes = this.appliedChanges.get(experimentName);
1037
- var isNewWatch = !changes.includes(change);
1038
- if (isNewWatch) {
1039
- changes.push(change);
1040
- // Only log when actually adding a new watch, not on every reapply
1041
- if (this.config.debug) {
1042
- var currentStyles_1 = {};
1043
- if (change.value && typeof change.value === 'object') {
1044
- Object.keys(change.value).forEach(function (prop) {
1045
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1046
- currentStyles_1[cssProp] = element.style.getPropertyValue(cssProp);
1047
- });
1048
- }
1049
- (0, debug_1.logDebug)('[WATCH-ELEMENT] Started watching element for persistence/hydration recovery', {
1050
- experimentName: experimentName,
1051
- selector: change.selector,
1052
- element: element.tagName,
1053
- changeType: change.type,
1054
- value: change.value,
1055
- currentStyles: change.type === 'style' ? currentStyles_1 : undefined,
1056
- timestamp: Date.now(),
1057
- });
1058
- }
1059
- }
1060
- if (!this.persistenceObserver) {
1061
- this.setupPersistenceObserver();
1188
+ if (this.persistenceManager) {
1189
+ this.persistenceManager.watchElement(element, experimentName, change);
1062
1190
  }
1063
1191
  };
1064
1192
  DOMChangesPluginLite.prototype.unwatchElement = function (element, experimentName) {
1065
- var experiments = this.watchedElements.get(element);
1066
- if (experiments) {
1067
- experiments.delete(experimentName);
1068
- if (experiments.size === 0) {
1069
- this.watchedElements.delete(element);
1070
- }
1071
- }
1072
- };
1073
- DOMChangesPluginLite.prototype.setupPersistenceObserver = function () {
1074
- var _this = this;
1075
- if (this.persistenceObserver)
1076
- return;
1077
- if (this.config.debug) {
1078
- (0, debug_1.logDebug)('[PERSISTENCE-OBSERVER] Setting up persistence observer', {
1079
- timestamp: Date.now(),
1080
- watchedElementsCount: this.watchedElements ? 'has WeakMap' : 'no WeakMap',
1081
- });
1082
- }
1083
- this.persistenceObserver = new MutationObserver(function (mutations) {
1084
- if (_this.config.debug) {
1085
- (0, debug_1.logDebug)('[MUTATION-DETECTED] Persistence observer detected mutations', {
1086
- mutationCount: mutations.length,
1087
- timestamp: Date.now(),
1088
- mutations: mutations.map(function (m) {
1089
- var _a;
1090
- return ({
1091
- target: m.target.tagName,
1092
- oldValue: (_a = m.oldValue) === null || _a === void 0 ? void 0 : _a.substring(0, 100),
1093
- });
1094
- }),
1095
- });
1096
- }
1097
- mutations.forEach(function (mutation) {
1098
- var element = mutation.target;
1099
- if (_this.reapplyingElements.has(element)) {
1100
- if (_this.config.debug) {
1101
- (0, debug_1.logDebug)('[MUTATION-SKIP] Skipping mutation - currently reapplying', {
1102
- element: element.tagName,
1103
- });
1104
- }
1105
- return;
1106
- }
1107
- var experiments = _this.watchedElements.get(element);
1108
- if (experiments) {
1109
- // Throttle mutation detection logs to once per 5 seconds per element
1110
- if (_this.config.debug) {
1111
- var elementKey = "mutation:".concat(element.tagName, ":").concat(element.getAttribute('name') || element.className);
1112
- var now = Date.now();
1113
- var lastLogged = _this.reapplyLogThrottle.get(elementKey) || 0;
1114
- if (now - lastLogged > 5000) {
1115
- (0, debug_1.logDebug)('[MUTATION-ON-WATCHED] Style mutation detected on watched element', {
1116
- element: element.tagName,
1117
- selector: element.getAttribute('name') || element.className,
1118
- oldValue: mutation.oldValue,
1119
- newValue: element.getAttribute('style'),
1120
- experiments: Array.from(experiments),
1121
- });
1122
- _this.reapplyLogThrottle.set(elementKey, now);
1123
- }
1124
- }
1125
- experiments.forEach(function (experimentName) {
1126
- var appliedChanges = _this.appliedChanges.get(experimentName);
1127
- if (appliedChanges) {
1128
- appliedChanges.forEach(function (change) {
1129
- var e_7, _a;
1130
- // Skip the .matches() check - we already know this element should have this change
1131
- // because we added it to watchedElements when we applied it successfully.
1132
- // The .matches() check fails for complex selectors with parent relationships (e.g., "div > p")
1133
- if (change.type === 'style') {
1134
- var needsReapply = _this.checkStyleOverwritten(element, change.value);
1135
- if (needsReapply) {
1136
- _this.reapplyingElements.add(element);
1137
- var logKey = "".concat(experimentName, "-").concat(change.selector);
1138
- var now = Date.now();
1139
- var lastLogged = _this.reapplyLogThrottle.get(logKey) || 0;
1140
- if (_this.config.debug && now - lastLogged > 5000) {
1141
- (0, debug_1.logDebug)('[REAPPLY-TRIGGERED] Reapplying style after mutation (React/framework conflict detected)', {
1142
- experimentName: experimentName,
1143
- selector: change.selector,
1144
- element: element.tagName,
1145
- timestamp: now,
1146
- note: 'This happens when the page framework (React/Vue/etc) fights with DOM changes',
1147
- });
1148
- _this.reapplyLogThrottle.set(logKey, now);
1149
- // Cleanup old throttle entries to prevent memory leak
1150
- if (_this.reapplyLogThrottle.size > 100) {
1151
- var oldestAllowed = now - 60000; // 1 minute
1152
- try {
1153
- for (var _b = __values(_this.reapplyLogThrottle.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
1154
- var _d = __read(_c.value, 2), key = _d[0], time = _d[1];
1155
- if (time < oldestAllowed) {
1156
- _this.reapplyLogThrottle.delete(key);
1157
- }
1158
- }
1159
- }
1160
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
1161
- finally {
1162
- try {
1163
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1164
- }
1165
- finally { if (e_7) throw e_7.error; }
1166
- }
1167
- }
1168
- }
1169
- _this.domManipulator.applyChange(change, experimentName);
1170
- setTimeout(function () {
1171
- _this.reapplyingElements.delete(element);
1172
- }, 0);
1173
- }
1174
- else if (_this.config.debug) {
1175
- (0, debug_1.logDebug)('[MUTATION-NO-REAPPLY] Style mutation detected but no reapply needed', {
1176
- experimentName: experimentName,
1177
- selector: change.selector,
1178
- });
1179
- }
1180
- }
1181
- });
1182
- }
1183
- });
1184
- }
1185
- });
1186
- });
1187
- this.persistenceObserver.observe(document.body, {
1188
- attributes: true,
1189
- attributeFilter: ['style'],
1190
- subtree: true,
1191
- attributeOldValue: true,
1192
- });
1193
- if (this.config.debug) {
1194
- (0, debug_1.logDebug)('[PERSISTENCE-OBSERVER] Setup complete - now observing style mutations', {
1195
- target: 'document.body',
1196
- attributeFilter: ['style'],
1197
- subtree: true,
1198
- attributeOldValue: true,
1199
- timestamp: Date.now(),
1200
- });
1201
- }
1202
- };
1203
- DOMChangesPluginLite.prototype.checkStyleOverwritten = function (element, expectedStyles) {
1204
- var e_8, _a;
1205
- try {
1206
- for (var _b = __values(Object.entries(expectedStyles)), _c = _b.next(); !_c.done; _c = _b.next()) {
1207
- var _d = __read(_c.value, 2), prop = _d[0], value = _d[1];
1208
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1209
- var currentValue = element.style.getPropertyValue(cssProp);
1210
- var currentPriority = element.style.getPropertyPriority(cssProp);
1211
- if (currentValue !== value || (value.includes('!important') && !currentPriority)) {
1212
- return true;
1213
- }
1214
- }
1215
- }
1216
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
1217
- finally {
1218
- try {
1219
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1220
- }
1221
- finally { if (e_8) throw e_8.error; }
1193
+ if (this.persistenceManager) {
1194
+ this.persistenceManager.unwatchElement(element, experimentName);
1222
1195
  }
1223
- return false;
1224
1196
  };
1225
1197
  DOMChangesPluginLite.VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : '1.1.2';
1226
1198
  return DOMChangesPluginLite;