@absmartly/sdk-plugins 1.1.2 → 1.2.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 (128) 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/cookies/cookieUtils.js +2 -1
  9. package/es/cookies/cookieUtils.js.map +1 -1
  10. package/es/core/DOMChangesPluginLite.js +195 -221
  11. package/es/core/DOMChangesPluginLite.js.map +1 -1
  12. package/es/core/DOMManipulatorLite.js +76 -10
  13. package/es/core/DOMManipulatorLite.js.map +1 -1
  14. package/es/core/ExposureTracker.js +136 -11
  15. package/es/core/ExposureTracker.js.map +1 -1
  16. package/es/core/PendingChangeManager.js +14 -14
  17. package/es/core/PendingChangeManager.js.map +1 -1
  18. package/es/entries/dom-with-overrides-lite.js +3 -0
  19. package/es/entries/dom-with-overrides-lite.js.map +1 -1
  20. package/es/generated/buildInfo.js +3 -0
  21. package/es/generated/buildInfo.js.map +1 -0
  22. package/es/index.js +9 -2
  23. package/es/index.js.map +1 -1
  24. package/es/overrides/OverridesPlugin.js +2 -2
  25. package/es/overrides/OverridesPlugin.js.map +1 -1
  26. package/es/overrides/OverridesPluginLite.js +29 -1
  27. package/es/overrides/OverridesPluginLite.js.map +1 -1
  28. package/es/overrides/overridesUtils.js +27 -18
  29. package/es/overrides/overridesUtils.js.map +1 -1
  30. package/es/parsers/VariantExtractor.js +14 -7
  31. package/es/parsers/VariantExtractor.js.map +1 -1
  32. package/es/url-redirect/URLRedirectExtractor.js +185 -0
  33. package/es/url-redirect/URLRedirectExtractor.js.map +1 -0
  34. package/es/url-redirect/URLRedirectMatcher.js +116 -0
  35. package/es/url-redirect/URLRedirectMatcher.js.map +1 -0
  36. package/es/url-redirect/URLRedirectPlugin.js +351 -0
  37. package/es/url-redirect/URLRedirectPlugin.js.map +1 -0
  38. package/es/url-redirect/index.js +5 -0
  39. package/es/url-redirect/index.js.map +1 -0
  40. package/es/url-redirect/types.js +2 -0
  41. package/es/url-redirect/types.js.map +1 -0
  42. package/es/utils/URLMatcher.js +4 -3
  43. package/es/utils/URLMatcher.js.map +1 -1
  44. package/es/utils/debug.js +4 -3
  45. package/es/utils/debug.js.map +1 -1
  46. package/es/utils/persistence.js +278 -0
  47. package/es/utils/persistence.js.map +1 -0
  48. package/es/utils/plugin-registry.js +47 -0
  49. package/es/utils/plugin-registry.js.map +1 -0
  50. package/lib/cookies/cookieUtils.d.ts +0 -4
  51. package/lib/cookies/cookieUtils.js +6 -5
  52. package/lib/cookies/cookieUtils.js.map +1 -1
  53. package/lib/core/DOMChangesPluginLite.d.ts +10 -7
  54. package/lib/core/DOMChangesPluginLite.js +195 -221
  55. package/lib/core/DOMChangesPluginLite.js.map +1 -1
  56. package/lib/core/DOMManipulatorLite.js +76 -10
  57. package/lib/core/DOMManipulatorLite.js.map +1 -1
  58. package/lib/core/ExposureTracker.d.ts +6 -0
  59. package/lib/core/ExposureTracker.js +136 -11
  60. package/lib/core/ExposureTracker.js.map +1 -1
  61. package/lib/core/PendingChangeManager.d.ts +0 -1
  62. package/lib/core/PendingChangeManager.js +14 -14
  63. package/lib/core/PendingChangeManager.js.map +1 -1
  64. package/lib/entries/dom-with-overrides-lite.d.ts +2 -0
  65. package/lib/entries/dom-with-overrides-lite.js +3 -1
  66. package/lib/entries/dom-with-overrides-lite.js.map +1 -1
  67. package/lib/generated/buildInfo.d.ts +2 -0
  68. package/lib/generated/buildInfo.js +6 -0
  69. package/lib/generated/buildInfo.js.map +1 -0
  70. package/lib/index.d.ts +8 -2
  71. package/lib/index.js +17 -1
  72. package/lib/index.js.map +1 -1
  73. package/lib/overrides/OverridesPlugin.js +2 -2
  74. package/lib/overrides/OverridesPlugin.js.map +1 -1
  75. package/lib/overrides/OverridesPluginLite.d.ts +8 -0
  76. package/lib/overrides/OverridesPluginLite.js +29 -1
  77. package/lib/overrides/OverridesPluginLite.js.map +1 -1
  78. package/lib/overrides/overridesUtils.d.ts +10 -5
  79. package/lib/overrides/overridesUtils.js +28 -18
  80. package/lib/overrides/overridesUtils.js.map +1 -1
  81. package/lib/parsers/VariantExtractor.js +14 -7
  82. package/lib/parsers/VariantExtractor.js.map +1 -1
  83. package/lib/types/index.d.ts +1 -0
  84. package/lib/url-redirect/URLRedirectExtractor.d.ts +16 -0
  85. package/lib/url-redirect/URLRedirectExtractor.js +188 -0
  86. package/lib/url-redirect/URLRedirectExtractor.js.map +1 -0
  87. package/lib/url-redirect/URLRedirectMatcher.d.ts +8 -0
  88. package/lib/url-redirect/URLRedirectMatcher.js +119 -0
  89. package/lib/url-redirect/URLRedirectMatcher.js.map +1 -0
  90. package/lib/url-redirect/URLRedirectPlugin.d.ts +21 -0
  91. package/lib/url-redirect/URLRedirectPlugin.js +354 -0
  92. package/lib/url-redirect/URLRedirectPlugin.js.map +1 -0
  93. package/lib/url-redirect/index.d.ts +4 -0
  94. package/lib/url-redirect/index.js +25 -0
  95. package/lib/url-redirect/index.js.map +1 -0
  96. package/lib/url-redirect/types.d.ts +29 -0
  97. package/lib/url-redirect/types.js +3 -0
  98. package/lib/url-redirect/types.js.map +1 -0
  99. package/lib/utils/URLMatcher.js +4 -3
  100. package/lib/utils/URLMatcher.js.map +1 -1
  101. package/lib/utils/debug.d.ts +1 -1
  102. package/lib/utils/debug.js +4 -3
  103. package/lib/utils/debug.js.map +1 -1
  104. package/lib/utils/persistence.d.ts +23 -0
  105. package/lib/utils/persistence.js +281 -0
  106. package/lib/utils/persistence.js.map +1 -0
  107. package/lib/utils/plugin-registry.d.ts +40 -0
  108. package/lib/utils/plugin-registry.js +53 -0
  109. package/lib/utils/plugin-registry.js.map +1 -0
  110. package/package.json +64 -6
  111. package/types/cookies/cookieUtils.d.ts +0 -4
  112. package/types/core/DOMChangesPluginLite.d.ts +10 -7
  113. package/types/core/ExposureTracker.d.ts +6 -0
  114. package/types/core/PendingChangeManager.d.ts +0 -1
  115. package/types/entries/dom-with-overrides-lite.d.ts +2 -0
  116. package/types/generated/buildInfo.d.ts +2 -0
  117. package/types/index.d.ts +8 -2
  118. package/types/overrides/OverridesPluginLite.d.ts +8 -0
  119. package/types/overrides/overridesUtils.d.ts +10 -5
  120. package/types/types/index.d.ts +1 -0
  121. package/types/url-redirect/URLRedirectExtractor.d.ts +16 -0
  122. package/types/url-redirect/URLRedirectMatcher.d.ts +8 -0
  123. package/types/url-redirect/URLRedirectPlugin.d.ts +21 -0
  124. package/types/url-redirect/index.d.ts +4 -0
  125. package/types/url-redirect/types.d.ts +29 -0
  126. package/types/utils/debug.d.ts +1 -1
  127. package/types/utils/persistence.d.ts +23 -0
  128. package/types/utils/plugin-registry.d.ts +40 -0
@@ -82,6 +82,9 @@ 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");
87
+ var buildInfo_1 = require("../generated/buildInfo");
85
88
  var DOMChangesPluginLite = /** @class */ (function () {
86
89
  function DOMChangesPluginLite(config) {
87
90
  var _this = this;
@@ -91,11 +94,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
91
94
  this.eventListeners = new Map();
92
95
  this.styleManagers = new Map();
93
96
  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();
97
+ this.persistenceManager = null;
99
98
  this.antiFlickerTimeout = null;
100
99
  this.antiFlickerStyleId = 'absmartly-antiflicker';
101
100
  this.config = {
@@ -120,6 +119,17 @@ var DOMChangesPluginLite = /** @class */ (function () {
120
119
  this.variantExtractor = new VariantExtractor_1.VariantExtractor(this.config.context, this.config.variableName, this.config.debug);
121
120
  this.exposureTracker = new ExposureTracker_1.ExposureTracker(this.config.context, this.config.debug);
122
121
  this.htmlInjector = new HTMLInjector_1.HTMLInjector(this.config.debug);
122
+ this.persistenceManager = new persistence_1.DOMPersistenceManager({
123
+ debug: this.config.debug,
124
+ onReapply: function (change, experimentName) {
125
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] Re-applying change due to mutation', {
126
+ experimentName: experimentName,
127
+ selector: change.selector,
128
+ type: change.type,
129
+ });
130
+ _this.domManipulator.applyChange(change, experimentName);
131
+ },
132
+ });
123
133
  // Auto-initialize when context is ready
124
134
  this.readyPromise = this.config.context
125
135
  .ready()
@@ -163,9 +173,14 @@ var DOMChangesPluginLite = /** @class */ (function () {
163
173
  case 1:
164
174
  _a.trys.push([1, 4, , 5]);
165
175
  if (this.config.spa) {
176
+ // SPA mode: observe body for React/Vue hydration recovery
177
+ // and listen for URL changes to re-apply changes on navigation
166
178
  this.setupMutationObserver();
167
179
  this.setupURLChangeListener();
168
180
  }
181
+ else {
182
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] SPA mode disabled - skipping body observer');
183
+ }
169
184
  if (!this.config.autoApply) return [3 /*break*/, 3];
170
185
  // Apply HTML injections and DOM changes in parallel to minimize flickering
171
186
  return [4 /*yield*/, this.applyInjectionsAndChanges()];
@@ -176,6 +191,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
176
191
  case 3:
177
192
  this.initialized = true;
178
193
  this.registerWithContext();
194
+ this.registerGlobally();
179
195
  this.emit('initialized');
180
196
  duration = performance.now() - startTime;
181
197
  (0, debug_1.logPerformance)('Plugin initialization', duration);
@@ -201,10 +217,12 @@ var DOMChangesPluginLite = /** @class */ (function () {
201
217
  if (mutation.type === 'childList') {
202
218
  // Check if any added nodes match our applied change selectors
203
219
  mutation.addedNodes.forEach(function (node) {
220
+ var _a;
204
221
  if (node.nodeType === Node.ELEMENT_NODE) {
205
222
  var element_1 = node;
206
223
  // Check all experiments with applied changes (not just style changes)
207
- _this.appliedChanges.forEach(function (changes, experimentName) {
224
+ var appliedChanges = ((_a = _this.persistenceManager) === null || _a === void 0 ? void 0 : _a.getAppliedChanges()) || new Map();
225
+ appliedChanges.forEach(function (changes, experimentName) {
208
226
  changes.forEach(function (change) {
209
227
  try {
210
228
  // Check if this new element or any of its descendants match the selector
@@ -234,11 +252,30 @@ var DOMChangesPluginLite = /** @class */ (function () {
234
252
  }
235
253
  });
236
254
  });
237
- observer.observe(document.body, {
238
- childList: true,
239
- subtree: true,
240
- });
241
- this.mutationObserver = observer;
255
+ // Wait for document.body to exist before observing
256
+ // This prevents "parameter 1 is not of type 'Node'" errors when SDK loads in <head>
257
+ var startObserving = function () {
258
+ observer.observe(document.body, {
259
+ childList: true,
260
+ subtree: true,
261
+ });
262
+ _this.mutationObserver = observer;
263
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] MutationObserver started on document.body');
264
+ };
265
+ if (document.body) {
266
+ startObserving();
267
+ }
268
+ else {
269
+ // Use MutationObserver on documentElement to detect when body is added
270
+ var bodyObserver = new MutationObserver(function (_mutations, obs) {
271
+ if (document.body) {
272
+ obs.disconnect();
273
+ startObserving();
274
+ }
275
+ });
276
+ bodyObserver.observe(document.documentElement, { childList: true });
277
+ (0, debug_1.logDebug)('[DOMChangesPluginLite] Waiting for document.body...');
278
+ }
242
279
  };
243
280
  /**
244
281
  * Set up URL change listener for SPA mode
@@ -305,7 +342,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
305
342
  // The stylesheets will be reused or cleared on next applyChanges
306
343
  this.styleManagers.clear();
307
344
  // Clear applied changes
308
- this.appliedChanges.clear();
345
+ if (this.persistenceManager) {
346
+ this.persistenceManager.clearAll();
347
+ }
309
348
  // Clear HTML injections
310
349
  this.htmlInjector.destroy();
311
350
  // Clear variant extractor cache to force re-extraction
@@ -422,22 +461,46 @@ var DOMChangesPluginLite = /** @class */ (function () {
422
461
  allExperiments = this.getAllExperimentsData();
423
462
  totalApplied = 0;
424
463
  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);
464
+ (0, debug_1.logDebug)('[APPLY-CHANGES] Experiments found with DOM changes:', {
465
+ experimentNames: Array.from(allExperiments.keys()),
466
+ totalCount: allExperiments.size,
467
+ currentURL: currentURL,
468
+ });
469
+ if (allExperiments.size === 0) {
470
+ (0, debug_1.logDebug)('[APPLY-CHANGES] ⚠️ No experiments found with DOM changes - nothing to process');
471
+ }
427
472
  try {
428
473
  for (allExperiments_1 = __values(allExperiments), allExperiments_1_1 = allExperiments_1.next(); !allExperiments_1_1.done; allExperiments_1_1 = allExperiments_1.next()) {
429
474
  _a = __read(allExperiments_1_1.value, 2), expName = _a[0], experimentData = _a[1];
475
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] Starting to process experiment"), {
476
+ experimentName: expName,
477
+ currentURL: currentURL,
478
+ });
430
479
  // Skip if filtering by specific experiment
431
480
  if (experimentName && expName !== experimentName) {
481
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - filtering for different experiment"), {
482
+ filteringFor: experimentName,
483
+ });
432
484
  continue;
433
485
  }
434
486
  currentVariant = this.config.context.peek(expName);
487
+ if (currentVariant === undefined || currentVariant === null) {
488
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant assigned (peek returned ").concat(currentVariant, ")"), {
489
+ experimentName: expName,
490
+ });
491
+ continue;
492
+ }
493
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] User assigned to variant ").concat(currentVariant));
435
494
  variantData = experimentData.variantData, urlFilter = experimentData.urlFilter, globalDefaults = experimentData.globalDefaults;
436
495
  anyVariantMatchesURL = this.variantExtractor.anyVariantMatchesURL(expName, currentURL);
437
496
  if (!anyVariantMatchesURL) {
438
- (0, debug_1.logDebug)("[ABsmartly] Skipping experiment '".concat(expName, "' - no variant matches URL: ").concat(currentURL));
497
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant matches URL: ").concat(currentURL), {
498
+ experimentName: expName,
499
+ currentURL: currentURL,
500
+ });
439
501
  continue;
440
502
  }
503
+ (0, debug_1.logDebug)("[APPLY-CHANGES] [".concat(expName, "] \u2713 At least one variant matches current URL"));
441
504
  shouldApplyVisualChanges = this.shouldApplyVisualChanges(variantData, urlFilter, currentURL);
442
505
  changes = this.extractChangesFromData(variantData, globalDefaults);
443
506
  allVariantChanges = this.extractAllVariantChanges(expName);
@@ -451,7 +514,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
451
514
  }
452
515
  stats = { total: (changes === null || changes === void 0 ? void 0 : changes.length) || 0, success: 0, pending: 0 };
453
516
  if (this.config.debug) {
454
- (0, debug_1.logDebug)("[ABsmartly] Processing experiment '".concat(expName, "' (variant ").concat(currentVariant, "):"), {
517
+ (0, debug_1.logDebug)("[ABsmartly] Processing experiment '".concat(expName, "' - User is in variant ").concat(currentVariant, ":"), {
518
+ experimentName: expName,
519
+ userVariant: currentVariant,
455
520
  urlMatches: shouldApplyVisualChanges,
456
521
  changeCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
457
522
  userVariantHasChanges: ((changes === null || changes === void 0 ? void 0 : changes.length) || 0) > 0,
@@ -462,11 +527,28 @@ var DOMChangesPluginLite = /** @class */ (function () {
462
527
  }); })) || [],
463
528
  });
464
529
  }
530
+ // Log if user is in control variant (no changes)
531
+ if (!changes || changes.length === 0) {
532
+ if (this.config.debug) {
533
+ (0, debug_1.logDebug)("[ABsmartly] User is in control variant ".concat(currentVariant, " with no changes, but will still track for SRM prevention"), {
534
+ experimentName: expName,
535
+ currentVariant: currentVariant,
536
+ });
537
+ }
538
+ }
465
539
  // Apply visual changes only if URL matches for user's variant AND user has changes
466
540
  if (shouldApplyVisualChanges && changes && changes.length > 0) {
467
541
  try {
468
542
  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
543
  change = changes_1_1.value;
544
+ // Skip delete changes with viewport triggers - they must be applied AFTER exposure
545
+ // Otherwise the element is removed before it can be observed
546
+ if (change.type === 'delete' && change.trigger_on_view) {
547
+ if (this.config.debug) {
548
+ (0, debug_1.logDebug)("[ABsmartly] Deferring delete change until after viewport trigger for '".concat(expName, "'"), { selector: change.selector });
549
+ }
550
+ continue;
551
+ }
470
552
  success = this.domManipulator.applyChange(change, expName);
471
553
  if (success) {
472
554
  totalApplied++;
@@ -584,14 +666,41 @@ var DOMChangesPluginLite = /** @class */ (function () {
584
666
  finally { if (e_3) throw e_3.error; }
585
667
  }
586
668
  if (this.config.debug) {
587
- (0, debug_1.logDebug)("[ABsmartly] Final trigger decision for '".concat(expName, "': hasImmediate=").concat(hasAnyImmediateTriggerInAnyVariant, ", hasViewport=").concat(hasAnyViewportTriggerInAnyVariant));
669
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] Final trigger decision for '").concat(expName, "':"), {
670
+ experimentName: expName,
671
+ userVariant: currentVariant,
672
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
673
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
674
+ willRegisterExperiment: hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant,
675
+ userChangesCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
676
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
677
+ });
588
678
  }
589
679
  // CRITICAL: Always register experiment for tracking if ANY variant has ANY trigger type
590
680
  // This prevents SRM even when user's variant doesn't match URL filter
591
681
  // Pass the URL-filtered trigger flags to ExposureTracker
592
682
  if (hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant) {
683
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] \u2713 REGISTERING EXPERIMENT - Calling exposureTracker.registerExperiment()"), {
684
+ experimentName: expName,
685
+ userVariant: currentVariant,
686
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
687
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
688
+ changesForUser: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
689
+ allVariantCounts: allVariantChanges.map(function (vc, idx) { return ({
690
+ variant: idx,
691
+ count: vc.length,
692
+ }); }),
693
+ });
593
694
  this.exposureTracker.registerExperiment(expName, currentVariant || 0, changes || [], allVariantChanges, hasAnyImmediateTriggerInAnyVariant, hasAnyViewportTriggerInAnyVariant);
594
695
  }
696
+ else {
697
+ (0, debug_1.logDebug)("[TRIGGER-DETECTION] [".concat(expName, "] \u26A0\uFE0F No triggers detected - experiment will NOT be registered!"), {
698
+ experimentName: expName,
699
+ userVariant: currentVariant,
700
+ hasAnyChangesInAnyVariant: hasAnyChangesInAnyVariant,
701
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
702
+ });
703
+ }
595
704
  // Note: We do NOT call treatment() here anymore to avoid duplicate calls.
596
705
  // The ExposureTracker.registerExperiment() will call triggerExposure() internally
597
706
  // when hasImmediateTrigger is true (see ExposureTracker line 169-174).
@@ -634,6 +743,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
634
743
  if (this.config.hideUntilReady) {
635
744
  this.showContent();
636
745
  }
746
+ (0, debug_1.logDebug)('[ABsmartly] DOM changes applied');
637
747
  this.emit('changes-applied', { count: totalApplied, experimentName: experimentName });
638
748
  return [2 /*return*/];
639
749
  }
@@ -647,18 +757,43 @@ var DOMChangesPluginLite = /** @class */ (function () {
647
757
  var e_5, _a;
648
758
  var experiments = new Map();
649
759
  var allVariants = this.variantExtractor.extractAllChanges();
760
+ (0, debug_1.logDebug)('[GET-EXPERIMENTS] Extracting experiment data from context', {
761
+ experimentsWithChanges: Array.from(allVariants.keys()),
762
+ totalCount: allVariants.size,
763
+ });
650
764
  try {
651
765
  for (var allVariants_1 = __values(allVariants), allVariants_1_1 = allVariants_1.next(); !allVariants_1_1.done; allVariants_1_1 = allVariants_1.next()) {
652
766
  var _b = __read(allVariants_1_1.value, 1), expName = _b[0];
653
767
  var currentVariant = this.config.context.peek(expName);
768
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] Checking experiment"), {
769
+ experimentName: expName,
770
+ currentVariant: currentVariant,
771
+ variantIsValid: currentVariant !== undefined && currentVariant !== null,
772
+ });
654
773
  if (currentVariant === undefined || currentVariant === null) {
774
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u23ED\uFE0F Skipping - context.peek() returned ").concat(currentVariant));
655
775
  continue;
656
776
  }
657
777
  var variantsData = this.variantExtractor.getAllVariantsData(expName);
658
778
  var variantData = variantsData.get(currentVariant);
779
+ if (this.config.debug) {
780
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] Variant data lookup:"), {
781
+ experimentName: expName,
782
+ userVariant: currentVariant,
783
+ variantDataExists: !!variantData,
784
+ allVariantsInMap: Array.from(variantsData.keys()),
785
+ userVariantInMap: variantsData.has(currentVariant),
786
+ });
787
+ }
659
788
  if (!variantData) {
660
- continue;
789
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u26A0\uFE0F User's variant ").concat(currentVariant, " has no DOM changes (control variant)"), {
790
+ availableVariants: Array.from(variantsData.keys()),
791
+ note: 'Will still track for SRM prevention',
792
+ });
793
+ // Don't skip! Include experiments even if user's variant has no changes
794
+ // This is critical for SRM prevention - we need to track ALL experiments
661
795
  }
796
+ (0, debug_1.logDebug)("[GET-EXPERIMENTS] [".concat(expName, "] \u2713 Including experiment (variant ").concat(currentVariant, ")"));
662
797
  // Extract URL filter and global defaults if using wrapped format
663
798
  var urlFilter = null;
664
799
  var globalDefaults = {};
@@ -691,12 +826,15 @@ var DOMChangesPluginLite = /** @class */ (function () {
691
826
  * Extract changes from DOMChangesData and apply global defaults
692
827
  */
693
828
  DOMChangesPluginLite.prototype.extractChangesFromData = function (data, globalDefaults) {
829
+ if (!data) {
830
+ return null;
831
+ }
694
832
  var changes = null;
695
833
  // Extract changes array
696
834
  if (Array.isArray(data)) {
697
835
  changes = data;
698
836
  }
699
- else if (data && typeof data === 'object' && 'changes' in data) {
837
+ else if (typeof data === 'object' && 'changes' in data) {
700
838
  changes = data.changes;
701
839
  }
702
840
  if (!changes || changes.length === 0) {
@@ -753,6 +891,10 @@ var DOMChangesPluginLite = /** @class */ (function () {
753
891
  * Determine if visual changes should be applied based on URL filter
754
892
  */
755
893
  DOMChangesPluginLite.prototype.shouldApplyVisualChanges = function (variantData, urlFilter, url) {
894
+ // No data for this variant - no changes to apply
895
+ if (!variantData) {
896
+ return false;
897
+ }
756
898
  // Legacy array format has no URL filter - always apply
757
899
  if (Array.isArray(variantData)) {
758
900
  return true;
@@ -886,9 +1028,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
886
1028
  this.mutationObserver.disconnect();
887
1029
  this.mutationObserver = null;
888
1030
  }
889
- if (this.persistenceObserver) {
890
- this.persistenceObserver.disconnect();
891
- this.persistenceObserver = null;
1031
+ if (this.persistenceManager) {
1032
+ this.persistenceManager.destroy();
1033
+ this.persistenceManager = null;
892
1034
  }
893
1035
  // Clean up anti-flicker timeout and style
894
1036
  if (this.antiFlickerTimeout !== null) {
@@ -901,11 +1043,8 @@ var DOMChangesPluginLite = /** @class */ (function () {
901
1043
  }
902
1044
  this.eventListeners.clear();
903
1045
  this.exposedExperiments.clear();
904
- this.watchedElements = new WeakMap();
905
- this.appliedChanges.clear();
906
- this.reapplyingElements.clear();
907
- this.reapplyLogThrottle.clear();
908
1046
  this.unregisterFromContext();
1047
+ this.unregisterGlobally();
909
1048
  this.initialized = false;
910
1049
  if (this.config.debug) {
911
1050
  (0, debug_1.logDebug)('[ABsmartly] DOM Changes Plugin Lite destroyed');
@@ -944,6 +1083,31 @@ var DOMChangesPluginLite = /** @class */ (function () {
944
1083
  }
945
1084
  }
946
1085
  };
1086
+ /**
1087
+ * Register plugin in global registry for detection
1088
+ */
1089
+ DOMChangesPluginLite.prototype.registerGlobally = function () {
1090
+ (0, plugin_registry_1.registerPlugin)('dom', {
1091
+ name: 'DOMChangesPluginLite',
1092
+ version: DOMChangesPluginLite.VERSION,
1093
+ initialized: true,
1094
+ timestamp: Date.now(),
1095
+ capabilities: ['spa', 'visibility', 'auto-apply'],
1096
+ instance: this,
1097
+ });
1098
+ if (this.config.debug) {
1099
+ (0, debug_1.logDebug)('[ABsmartly] DOMChangesPluginLite registered in global window.__ABSMARTLY_PLUGINS__');
1100
+ }
1101
+ };
1102
+ /**
1103
+ * Unregister plugin from global registry
1104
+ */
1105
+ DOMChangesPluginLite.prototype.unregisterGlobally = function () {
1106
+ (0, plugin_registry_1.unregisterPlugin)('dom');
1107
+ if (this.config.debug) {
1108
+ (0, debug_1.logDebug)('[ABsmartly] DOMChangesPluginLite unregistered from global registry');
1109
+ }
1110
+ };
947
1111
  /**
948
1112
  * Hide content to prevent flicker before experiments are applied
949
1113
  */
@@ -1023,206 +1187,16 @@ var DOMChangesPluginLite = /** @class */ (function () {
1023
1187
  }
1024
1188
  };
1025
1189
  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();
1190
+ if (this.persistenceManager) {
1191
+ this.persistenceManager.watchElement(element, experimentName, change);
1062
1192
  }
1063
1193
  };
1064
1194
  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; }
1195
+ if (this.persistenceManager) {
1196
+ this.persistenceManager.unwatchElement(element, experimentName);
1222
1197
  }
1223
- return false;
1224
1198
  };
1225
- DOMChangesPluginLite.VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : '1.1.2';
1199
+ DOMChangesPluginLite.VERSION = buildInfo_1.BUILD_VERSION;
1226
1200
  return DOMChangesPluginLite;
1227
1201
  }());
1228
1202
  exports.DOMChangesPluginLite = DOMChangesPluginLite;