@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
@@ -79,6 +79,9 @@ import { ExposureTracker } from './ExposureTracker';
79
79
  import { HTMLInjector } from './HTMLInjector';
80
80
  import { logDebug, logExperimentSummary, logPerformance, DEBUG } from '../utils/debug';
81
81
  import { URLMatcher } from '../utils/URLMatcher';
82
+ import { DOMPersistenceManager } from '../utils/persistence';
83
+ import { registerPlugin, unregisterPlugin } from '../utils/plugin-registry';
84
+ import { BUILD_VERSION } from '../generated/buildInfo';
82
85
  var DOMChangesPluginLite = /** @class */ (function () {
83
86
  function DOMChangesPluginLite(config) {
84
87
  var _this = this;
@@ -88,11 +91,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
88
91
  this.eventListeners = new Map();
89
92
  this.styleManagers = new Map();
90
93
  this.initialized = false;
91
- this.watchedElements = new WeakMap();
92
- this.persistenceObserver = null;
93
- this.reapplyingElements = new Set();
94
- this.reapplyLogThrottle = new Map();
95
- this.appliedChanges = new Map();
94
+ this.persistenceManager = null;
96
95
  this.antiFlickerTimeout = null;
97
96
  this.antiFlickerStyleId = 'absmartly-antiflicker';
98
97
  this.config = {
@@ -117,6 +116,17 @@ var DOMChangesPluginLite = /** @class */ (function () {
117
116
  this.variantExtractor = new VariantExtractor(this.config.context, this.config.variableName, this.config.debug);
118
117
  this.exposureTracker = new ExposureTracker(this.config.context, this.config.debug);
119
118
  this.htmlInjector = new HTMLInjector(this.config.debug);
119
+ this.persistenceManager = new DOMPersistenceManager({
120
+ debug: this.config.debug,
121
+ onReapply: function (change, experimentName) {
122
+ logDebug('[DOMChangesPluginLite] Re-applying change due to mutation', {
123
+ experimentName: experimentName,
124
+ selector: change.selector,
125
+ type: change.type,
126
+ });
127
+ _this.domManipulator.applyChange(change, experimentName);
128
+ },
129
+ });
120
130
  // Auto-initialize when context is ready
121
131
  this.readyPromise = this.config.context
122
132
  .ready()
@@ -160,9 +170,14 @@ var DOMChangesPluginLite = /** @class */ (function () {
160
170
  case 1:
161
171
  _a.trys.push([1, 4, , 5]);
162
172
  if (this.config.spa) {
173
+ // SPA mode: observe body for React/Vue hydration recovery
174
+ // and listen for URL changes to re-apply changes on navigation
163
175
  this.setupMutationObserver();
164
176
  this.setupURLChangeListener();
165
177
  }
178
+ else {
179
+ logDebug('[DOMChangesPluginLite] SPA mode disabled - skipping body observer');
180
+ }
166
181
  if (!this.config.autoApply) return [3 /*break*/, 3];
167
182
  // Apply HTML injections and DOM changes in parallel to minimize flickering
168
183
  return [4 /*yield*/, this.applyInjectionsAndChanges()];
@@ -173,6 +188,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
173
188
  case 3:
174
189
  this.initialized = true;
175
190
  this.registerWithContext();
191
+ this.registerGlobally();
176
192
  this.emit('initialized');
177
193
  duration = performance.now() - startTime;
178
194
  logPerformance('Plugin initialization', duration);
@@ -198,10 +214,12 @@ var DOMChangesPluginLite = /** @class */ (function () {
198
214
  if (mutation.type === 'childList') {
199
215
  // Check if any added nodes match our applied change selectors
200
216
  mutation.addedNodes.forEach(function (node) {
217
+ var _a;
201
218
  if (node.nodeType === Node.ELEMENT_NODE) {
202
219
  var element_1 = node;
203
220
  // Check all experiments with applied changes (not just style changes)
204
- _this.appliedChanges.forEach(function (changes, experimentName) {
221
+ var appliedChanges = ((_a = _this.persistenceManager) === null || _a === void 0 ? void 0 : _a.getAppliedChanges()) || new Map();
222
+ appliedChanges.forEach(function (changes, experimentName) {
205
223
  changes.forEach(function (change) {
206
224
  try {
207
225
  // Check if this new element or any of its descendants match the selector
@@ -231,11 +249,30 @@ var DOMChangesPluginLite = /** @class */ (function () {
231
249
  }
232
250
  });
233
251
  });
234
- observer.observe(document.body, {
235
- childList: true,
236
- subtree: true,
237
- });
238
- this.mutationObserver = observer;
252
+ // Wait for document.body to exist before observing
253
+ // This prevents "parameter 1 is not of type 'Node'" errors when SDK loads in <head>
254
+ var startObserving = function () {
255
+ observer.observe(document.body, {
256
+ childList: true,
257
+ subtree: true,
258
+ });
259
+ _this.mutationObserver = observer;
260
+ logDebug('[DOMChangesPluginLite] MutationObserver started on document.body');
261
+ };
262
+ if (document.body) {
263
+ startObserving();
264
+ }
265
+ else {
266
+ // Use MutationObserver on documentElement to detect when body is added
267
+ var bodyObserver = new MutationObserver(function (_mutations, obs) {
268
+ if (document.body) {
269
+ obs.disconnect();
270
+ startObserving();
271
+ }
272
+ });
273
+ bodyObserver.observe(document.documentElement, { childList: true });
274
+ logDebug('[DOMChangesPluginLite] Waiting for document.body...');
275
+ }
239
276
  };
240
277
  /**
241
278
  * Set up URL change listener for SPA mode
@@ -302,7 +339,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
302
339
  // The stylesheets will be reused or cleared on next applyChanges
303
340
  this.styleManagers.clear();
304
341
  // Clear applied changes
305
- this.appliedChanges.clear();
342
+ if (this.persistenceManager) {
343
+ this.persistenceManager.clearAll();
344
+ }
306
345
  // Clear HTML injections
307
346
  this.htmlInjector.destroy();
308
347
  // Clear variant extractor cache to force re-extraction
@@ -419,22 +458,46 @@ var DOMChangesPluginLite = /** @class */ (function () {
419
458
  allExperiments = this.getAllExperimentsData();
420
459
  totalApplied = 0;
421
460
  experimentStats = new Map();
422
- logDebug('[ABsmartly] Experiments to process:', Array.from(allExperiments.keys()));
423
- logDebug('[ABsmartly] Total experiments with changes:', allExperiments.size);
461
+ logDebug('[APPLY-CHANGES] Experiments found with DOM changes:', {
462
+ experimentNames: Array.from(allExperiments.keys()),
463
+ totalCount: allExperiments.size,
464
+ currentURL: currentURL,
465
+ });
466
+ if (allExperiments.size === 0) {
467
+ logDebug('[APPLY-CHANGES] ⚠️ No experiments found with DOM changes - nothing to process');
468
+ }
424
469
  try {
425
470
  for (allExperiments_1 = __values(allExperiments), allExperiments_1_1 = allExperiments_1.next(); !allExperiments_1_1.done; allExperiments_1_1 = allExperiments_1.next()) {
426
471
  _a = __read(allExperiments_1_1.value, 2), expName = _a[0], experimentData = _a[1];
472
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] Starting to process experiment"), {
473
+ experimentName: expName,
474
+ currentURL: currentURL,
475
+ });
427
476
  // Skip if filtering by specific experiment
428
477
  if (experimentName && expName !== experimentName) {
478
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - filtering for different experiment"), {
479
+ filteringFor: experimentName,
480
+ });
429
481
  continue;
430
482
  }
431
483
  currentVariant = this.config.context.peek(expName);
484
+ if (currentVariant === undefined || currentVariant === null) {
485
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant assigned (peek returned ").concat(currentVariant, ")"), {
486
+ experimentName: expName,
487
+ });
488
+ continue;
489
+ }
490
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] User assigned to variant ").concat(currentVariant));
432
491
  variantData = experimentData.variantData, urlFilter = experimentData.urlFilter, globalDefaults = experimentData.globalDefaults;
433
492
  anyVariantMatchesURL = this.variantExtractor.anyVariantMatchesURL(expName, currentURL);
434
493
  if (!anyVariantMatchesURL) {
435
- logDebug("[ABsmartly] Skipping experiment '".concat(expName, "' - no variant matches URL: ").concat(currentURL));
494
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] \u23ED\uFE0F Skipping - no variant matches URL: ").concat(currentURL), {
495
+ experimentName: expName,
496
+ currentURL: currentURL,
497
+ });
436
498
  continue;
437
499
  }
500
+ logDebug("[APPLY-CHANGES] [".concat(expName, "] \u2713 At least one variant matches current URL"));
438
501
  shouldApplyVisualChanges = this.shouldApplyVisualChanges(variantData, urlFilter, currentURL);
439
502
  changes = this.extractChangesFromData(variantData, globalDefaults);
440
503
  allVariantChanges = this.extractAllVariantChanges(expName);
@@ -448,7 +511,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
448
511
  }
449
512
  stats = { total: (changes === null || changes === void 0 ? void 0 : changes.length) || 0, success: 0, pending: 0 };
450
513
  if (this.config.debug) {
451
- logDebug("[ABsmartly] Processing experiment '".concat(expName, "' (variant ").concat(currentVariant, "):"), {
514
+ logDebug("[ABsmartly] Processing experiment '".concat(expName, "' - User is in variant ").concat(currentVariant, ":"), {
515
+ experimentName: expName,
516
+ userVariant: currentVariant,
452
517
  urlMatches: shouldApplyVisualChanges,
453
518
  changeCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
454
519
  userVariantHasChanges: ((changes === null || changes === void 0 ? void 0 : changes.length) || 0) > 0,
@@ -459,11 +524,28 @@ var DOMChangesPluginLite = /** @class */ (function () {
459
524
  }); })) || [],
460
525
  });
461
526
  }
527
+ // Log if user is in control variant (no changes)
528
+ if (!changes || changes.length === 0) {
529
+ if (this.config.debug) {
530
+ logDebug("[ABsmartly] User is in control variant ".concat(currentVariant, " with no changes, but will still track for SRM prevention"), {
531
+ experimentName: expName,
532
+ currentVariant: currentVariant,
533
+ });
534
+ }
535
+ }
462
536
  // Apply visual changes only if URL matches for user's variant AND user has changes
463
537
  if (shouldApplyVisualChanges && changes && changes.length > 0) {
464
538
  try {
465
539
  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()) {
466
540
  change = changes_1_1.value;
541
+ // Skip delete changes with viewport triggers - they must be applied AFTER exposure
542
+ // Otherwise the element is removed before it can be observed
543
+ if (change.type === 'delete' && change.trigger_on_view) {
544
+ if (this.config.debug) {
545
+ logDebug("[ABsmartly] Deferring delete change until after viewport trigger for '".concat(expName, "'"), { selector: change.selector });
546
+ }
547
+ continue;
548
+ }
467
549
  success = this.domManipulator.applyChange(change, expName);
468
550
  if (success) {
469
551
  totalApplied++;
@@ -581,14 +663,41 @@ var DOMChangesPluginLite = /** @class */ (function () {
581
663
  finally { if (e_3) throw e_3.error; }
582
664
  }
583
665
  if (this.config.debug) {
584
- logDebug("[ABsmartly] Final trigger decision for '".concat(expName, "': hasImmediate=").concat(hasAnyImmediateTriggerInAnyVariant, ", hasViewport=").concat(hasAnyViewportTriggerInAnyVariant));
666
+ logDebug("[TRIGGER-DETECTION] [".concat(expName, "] Final trigger decision for '").concat(expName, "':"), {
667
+ experimentName: expName,
668
+ userVariant: currentVariant,
669
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
670
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
671
+ willRegisterExperiment: hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant,
672
+ userChangesCount: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
673
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
674
+ });
585
675
  }
586
676
  // CRITICAL: Always register experiment for tracking if ANY variant has ANY trigger type
587
677
  // This prevents SRM even when user's variant doesn't match URL filter
588
678
  // Pass the URL-filtered trigger flags to ExposureTracker
589
679
  if (hasAnyViewportTriggerInAnyVariant || hasAnyImmediateTriggerInAnyVariant) {
680
+ logDebug("[TRIGGER-DETECTION] [".concat(expName, "] \u2713 REGISTERING EXPERIMENT - Calling exposureTracker.registerExperiment()"), {
681
+ experimentName: expName,
682
+ userVariant: currentVariant,
683
+ hasImmediateTrigger: hasAnyImmediateTriggerInAnyVariant,
684
+ hasViewportTrigger: hasAnyViewportTriggerInAnyVariant,
685
+ changesForUser: (changes === null || changes === void 0 ? void 0 : changes.length) || 0,
686
+ allVariantCounts: allVariantChanges.map(function (vc, idx) { return ({
687
+ variant: idx,
688
+ count: vc.length,
689
+ }); }),
690
+ });
590
691
  this.exposureTracker.registerExperiment(expName, currentVariant || 0, changes || [], allVariantChanges, hasAnyImmediateTriggerInAnyVariant, hasAnyViewportTriggerInAnyVariant);
591
692
  }
693
+ else {
694
+ logDebug("[TRIGGER-DETECTION] [".concat(expName, "] \u26A0\uFE0F No triggers detected - experiment will NOT be registered!"), {
695
+ experimentName: expName,
696
+ userVariant: currentVariant,
697
+ hasAnyChangesInAnyVariant: hasAnyChangesInAnyVariant,
698
+ allVariantsChangesCount: allVariantChanges.map(function (vc) { return vc.length; }),
699
+ });
700
+ }
592
701
  // Note: We do NOT call treatment() here anymore to avoid duplicate calls.
593
702
  // The ExposureTracker.registerExperiment() will call triggerExposure() internally
594
703
  // when hasImmediateTrigger is true (see ExposureTracker line 169-174).
@@ -631,6 +740,7 @@ var DOMChangesPluginLite = /** @class */ (function () {
631
740
  if (this.config.hideUntilReady) {
632
741
  this.showContent();
633
742
  }
743
+ logDebug('[ABsmartly] DOM changes applied');
634
744
  this.emit('changes-applied', { count: totalApplied, experimentName: experimentName });
635
745
  return [2 /*return*/];
636
746
  }
@@ -644,18 +754,43 @@ var DOMChangesPluginLite = /** @class */ (function () {
644
754
  var e_5, _a;
645
755
  var experiments = new Map();
646
756
  var allVariants = this.variantExtractor.extractAllChanges();
757
+ logDebug('[GET-EXPERIMENTS] Extracting experiment data from context', {
758
+ experimentsWithChanges: Array.from(allVariants.keys()),
759
+ totalCount: allVariants.size,
760
+ });
647
761
  try {
648
762
  for (var allVariants_1 = __values(allVariants), allVariants_1_1 = allVariants_1.next(); !allVariants_1_1.done; allVariants_1_1 = allVariants_1.next()) {
649
763
  var _b = __read(allVariants_1_1.value, 1), expName = _b[0];
650
764
  var currentVariant = this.config.context.peek(expName);
765
+ logDebug("[GET-EXPERIMENTS] [".concat(expName, "] Checking experiment"), {
766
+ experimentName: expName,
767
+ currentVariant: currentVariant,
768
+ variantIsValid: currentVariant !== undefined && currentVariant !== null,
769
+ });
651
770
  if (currentVariant === undefined || currentVariant === null) {
771
+ logDebug("[GET-EXPERIMENTS] [".concat(expName, "] \u23ED\uFE0F Skipping - context.peek() returned ").concat(currentVariant));
652
772
  continue;
653
773
  }
654
774
  var variantsData = this.variantExtractor.getAllVariantsData(expName);
655
775
  var variantData = variantsData.get(currentVariant);
776
+ if (this.config.debug) {
777
+ logDebug("[GET-EXPERIMENTS] [".concat(expName, "] Variant data lookup:"), {
778
+ experimentName: expName,
779
+ userVariant: currentVariant,
780
+ variantDataExists: !!variantData,
781
+ allVariantsInMap: Array.from(variantsData.keys()),
782
+ userVariantInMap: variantsData.has(currentVariant),
783
+ });
784
+ }
656
785
  if (!variantData) {
657
- continue;
786
+ logDebug("[GET-EXPERIMENTS] [".concat(expName, "] \u26A0\uFE0F User's variant ").concat(currentVariant, " has no DOM changes (control variant)"), {
787
+ availableVariants: Array.from(variantsData.keys()),
788
+ note: 'Will still track for SRM prevention',
789
+ });
790
+ // Don't skip! Include experiments even if user's variant has no changes
791
+ // This is critical for SRM prevention - we need to track ALL experiments
658
792
  }
793
+ logDebug("[GET-EXPERIMENTS] [".concat(expName, "] \u2713 Including experiment (variant ").concat(currentVariant, ")"));
659
794
  // Extract URL filter and global defaults if using wrapped format
660
795
  var urlFilter = null;
661
796
  var globalDefaults = {};
@@ -688,12 +823,15 @@ var DOMChangesPluginLite = /** @class */ (function () {
688
823
  * Extract changes from DOMChangesData and apply global defaults
689
824
  */
690
825
  DOMChangesPluginLite.prototype.extractChangesFromData = function (data, globalDefaults) {
826
+ if (!data) {
827
+ return null;
828
+ }
691
829
  var changes = null;
692
830
  // Extract changes array
693
831
  if (Array.isArray(data)) {
694
832
  changes = data;
695
833
  }
696
- else if (data && typeof data === 'object' && 'changes' in data) {
834
+ else if (typeof data === 'object' && 'changes' in data) {
697
835
  changes = data.changes;
698
836
  }
699
837
  if (!changes || changes.length === 0) {
@@ -750,6 +888,10 @@ var DOMChangesPluginLite = /** @class */ (function () {
750
888
  * Determine if visual changes should be applied based on URL filter
751
889
  */
752
890
  DOMChangesPluginLite.prototype.shouldApplyVisualChanges = function (variantData, urlFilter, url) {
891
+ // No data for this variant - no changes to apply
892
+ if (!variantData) {
893
+ return false;
894
+ }
753
895
  // Legacy array format has no URL filter - always apply
754
896
  if (Array.isArray(variantData)) {
755
897
  return true;
@@ -883,9 +1025,9 @@ var DOMChangesPluginLite = /** @class */ (function () {
883
1025
  this.mutationObserver.disconnect();
884
1026
  this.mutationObserver = null;
885
1027
  }
886
- if (this.persistenceObserver) {
887
- this.persistenceObserver.disconnect();
888
- this.persistenceObserver = null;
1028
+ if (this.persistenceManager) {
1029
+ this.persistenceManager.destroy();
1030
+ this.persistenceManager = null;
889
1031
  }
890
1032
  // Clean up anti-flicker timeout and style
891
1033
  if (this.antiFlickerTimeout !== null) {
@@ -898,11 +1040,8 @@ var DOMChangesPluginLite = /** @class */ (function () {
898
1040
  }
899
1041
  this.eventListeners.clear();
900
1042
  this.exposedExperiments.clear();
901
- this.watchedElements = new WeakMap();
902
- this.appliedChanges.clear();
903
- this.reapplyingElements.clear();
904
- this.reapplyLogThrottle.clear();
905
1043
  this.unregisterFromContext();
1044
+ this.unregisterGlobally();
906
1045
  this.initialized = false;
907
1046
  if (this.config.debug) {
908
1047
  logDebug('[ABsmartly] DOM Changes Plugin Lite destroyed');
@@ -941,6 +1080,31 @@ var DOMChangesPluginLite = /** @class */ (function () {
941
1080
  }
942
1081
  }
943
1082
  };
1083
+ /**
1084
+ * Register plugin in global registry for detection
1085
+ */
1086
+ DOMChangesPluginLite.prototype.registerGlobally = function () {
1087
+ registerPlugin('dom', {
1088
+ name: 'DOMChangesPluginLite',
1089
+ version: DOMChangesPluginLite.VERSION,
1090
+ initialized: true,
1091
+ timestamp: Date.now(),
1092
+ capabilities: ['spa', 'visibility', 'auto-apply'],
1093
+ instance: this,
1094
+ });
1095
+ if (this.config.debug) {
1096
+ logDebug('[ABsmartly] DOMChangesPluginLite registered in global window.__ABSMARTLY_PLUGINS__');
1097
+ }
1098
+ };
1099
+ /**
1100
+ * Unregister plugin from global registry
1101
+ */
1102
+ DOMChangesPluginLite.prototype.unregisterGlobally = function () {
1103
+ unregisterPlugin('dom');
1104
+ if (this.config.debug) {
1105
+ logDebug('[ABsmartly] DOMChangesPluginLite unregistered from global registry');
1106
+ }
1107
+ };
944
1108
  /**
945
1109
  * Hide content to prevent flicker before experiments are applied
946
1110
  */
@@ -1020,206 +1184,16 @@ var DOMChangesPluginLite = /** @class */ (function () {
1020
1184
  }
1021
1185
  };
1022
1186
  DOMChangesPluginLite.prototype.watchElement = function (element, experimentName, change) {
1023
- var experiments = this.watchedElements.get(element);
1024
- if (!experiments) {
1025
- experiments = new Set();
1026
- this.watchedElements.set(element, experiments);
1027
- }
1028
- experiments.add(experimentName);
1029
- // Store the applied change for this experiment (ALL types: style, class, attribute, html, text, etc.)
1030
- if (!this.appliedChanges.has(experimentName)) {
1031
- this.appliedChanges.set(experimentName, []);
1032
- }
1033
- var changes = this.appliedChanges.get(experimentName);
1034
- var isNewWatch = !changes.includes(change);
1035
- if (isNewWatch) {
1036
- changes.push(change);
1037
- // Only log when actually adding a new watch, not on every reapply
1038
- if (this.config.debug) {
1039
- var currentStyles_1 = {};
1040
- if (change.value && typeof change.value === 'object') {
1041
- Object.keys(change.value).forEach(function (prop) {
1042
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1043
- currentStyles_1[cssProp] = element.style.getPropertyValue(cssProp);
1044
- });
1045
- }
1046
- logDebug('[WATCH-ELEMENT] Started watching element for persistence/hydration recovery', {
1047
- experimentName: experimentName,
1048
- selector: change.selector,
1049
- element: element.tagName,
1050
- changeType: change.type,
1051
- value: change.value,
1052
- currentStyles: change.type === 'style' ? currentStyles_1 : undefined,
1053
- timestamp: Date.now(),
1054
- });
1055
- }
1056
- }
1057
- if (!this.persistenceObserver) {
1058
- this.setupPersistenceObserver();
1187
+ if (this.persistenceManager) {
1188
+ this.persistenceManager.watchElement(element, experimentName, change);
1059
1189
  }
1060
1190
  };
1061
1191
  DOMChangesPluginLite.prototype.unwatchElement = function (element, experimentName) {
1062
- var experiments = this.watchedElements.get(element);
1063
- if (experiments) {
1064
- experiments.delete(experimentName);
1065
- if (experiments.size === 0) {
1066
- this.watchedElements.delete(element);
1067
- }
1068
- }
1069
- };
1070
- DOMChangesPluginLite.prototype.setupPersistenceObserver = function () {
1071
- var _this = this;
1072
- if (this.persistenceObserver)
1073
- return;
1074
- if (this.config.debug) {
1075
- logDebug('[PERSISTENCE-OBSERVER] Setting up persistence observer', {
1076
- timestamp: Date.now(),
1077
- watchedElementsCount: this.watchedElements ? 'has WeakMap' : 'no WeakMap',
1078
- });
1079
- }
1080
- this.persistenceObserver = new MutationObserver(function (mutations) {
1081
- if (_this.config.debug) {
1082
- logDebug('[MUTATION-DETECTED] Persistence observer detected mutations', {
1083
- mutationCount: mutations.length,
1084
- timestamp: Date.now(),
1085
- mutations: mutations.map(function (m) {
1086
- var _a;
1087
- return ({
1088
- target: m.target.tagName,
1089
- oldValue: (_a = m.oldValue) === null || _a === void 0 ? void 0 : _a.substring(0, 100),
1090
- });
1091
- }),
1092
- });
1093
- }
1094
- mutations.forEach(function (mutation) {
1095
- var element = mutation.target;
1096
- if (_this.reapplyingElements.has(element)) {
1097
- if (_this.config.debug) {
1098
- logDebug('[MUTATION-SKIP] Skipping mutation - currently reapplying', {
1099
- element: element.tagName,
1100
- });
1101
- }
1102
- return;
1103
- }
1104
- var experiments = _this.watchedElements.get(element);
1105
- if (experiments) {
1106
- // Throttle mutation detection logs to once per 5 seconds per element
1107
- if (_this.config.debug) {
1108
- var elementKey = "mutation:".concat(element.tagName, ":").concat(element.getAttribute('name') || element.className);
1109
- var now = Date.now();
1110
- var lastLogged = _this.reapplyLogThrottle.get(elementKey) || 0;
1111
- if (now - lastLogged > 5000) {
1112
- logDebug('[MUTATION-ON-WATCHED] Style mutation detected on watched element', {
1113
- element: element.tagName,
1114
- selector: element.getAttribute('name') || element.className,
1115
- oldValue: mutation.oldValue,
1116
- newValue: element.getAttribute('style'),
1117
- experiments: Array.from(experiments),
1118
- });
1119
- _this.reapplyLogThrottle.set(elementKey, now);
1120
- }
1121
- }
1122
- experiments.forEach(function (experimentName) {
1123
- var appliedChanges = _this.appliedChanges.get(experimentName);
1124
- if (appliedChanges) {
1125
- appliedChanges.forEach(function (change) {
1126
- var e_7, _a;
1127
- // Skip the .matches() check - we already know this element should have this change
1128
- // because we added it to watchedElements when we applied it successfully.
1129
- // The .matches() check fails for complex selectors with parent relationships (e.g., "div > p")
1130
- if (change.type === 'style') {
1131
- var needsReapply = _this.checkStyleOverwritten(element, change.value);
1132
- if (needsReapply) {
1133
- _this.reapplyingElements.add(element);
1134
- var logKey = "".concat(experimentName, "-").concat(change.selector);
1135
- var now = Date.now();
1136
- var lastLogged = _this.reapplyLogThrottle.get(logKey) || 0;
1137
- if (_this.config.debug && now - lastLogged > 5000) {
1138
- logDebug('[REAPPLY-TRIGGERED] Reapplying style after mutation (React/framework conflict detected)', {
1139
- experimentName: experimentName,
1140
- selector: change.selector,
1141
- element: element.tagName,
1142
- timestamp: now,
1143
- note: 'This happens when the page framework (React/Vue/etc) fights with DOM changes',
1144
- });
1145
- _this.reapplyLogThrottle.set(logKey, now);
1146
- // Cleanup old throttle entries to prevent memory leak
1147
- if (_this.reapplyLogThrottle.size > 100) {
1148
- var oldestAllowed = now - 60000; // 1 minute
1149
- try {
1150
- for (var _b = __values(_this.reapplyLogThrottle.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
1151
- var _d = __read(_c.value, 2), key = _d[0], time = _d[1];
1152
- if (time < oldestAllowed) {
1153
- _this.reapplyLogThrottle.delete(key);
1154
- }
1155
- }
1156
- }
1157
- catch (e_7_1) { e_7 = { error: e_7_1 }; }
1158
- finally {
1159
- try {
1160
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1161
- }
1162
- finally { if (e_7) throw e_7.error; }
1163
- }
1164
- }
1165
- }
1166
- _this.domManipulator.applyChange(change, experimentName);
1167
- setTimeout(function () {
1168
- _this.reapplyingElements.delete(element);
1169
- }, 0);
1170
- }
1171
- else if (_this.config.debug) {
1172
- logDebug('[MUTATION-NO-REAPPLY] Style mutation detected but no reapply needed', {
1173
- experimentName: experimentName,
1174
- selector: change.selector,
1175
- });
1176
- }
1177
- }
1178
- });
1179
- }
1180
- });
1181
- }
1182
- });
1183
- });
1184
- this.persistenceObserver.observe(document.body, {
1185
- attributes: true,
1186
- attributeFilter: ['style'],
1187
- subtree: true,
1188
- attributeOldValue: true,
1189
- });
1190
- if (this.config.debug) {
1191
- logDebug('[PERSISTENCE-OBSERVER] Setup complete - now observing style mutations', {
1192
- target: 'document.body',
1193
- attributeFilter: ['style'],
1194
- subtree: true,
1195
- attributeOldValue: true,
1196
- timestamp: Date.now(),
1197
- });
1198
- }
1199
- };
1200
- DOMChangesPluginLite.prototype.checkStyleOverwritten = function (element, expectedStyles) {
1201
- var e_8, _a;
1202
- try {
1203
- for (var _b = __values(Object.entries(expectedStyles)), _c = _b.next(); !_c.done; _c = _b.next()) {
1204
- var _d = __read(_c.value, 2), prop = _d[0], value = _d[1];
1205
- var cssProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
1206
- var currentValue = element.style.getPropertyValue(cssProp);
1207
- var currentPriority = element.style.getPropertyPriority(cssProp);
1208
- if (currentValue !== value || (value.includes('!important') && !currentPriority)) {
1209
- return true;
1210
- }
1211
- }
1212
- }
1213
- catch (e_8_1) { e_8 = { error: e_8_1 }; }
1214
- finally {
1215
- try {
1216
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
1217
- }
1218
- finally { if (e_8) throw e_8.error; }
1192
+ if (this.persistenceManager) {
1193
+ this.persistenceManager.unwatchElement(element, experimentName);
1219
1194
  }
1220
- return false;
1221
1195
  };
1222
- DOMChangesPluginLite.VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : '1.1.2';
1196
+ DOMChangesPluginLite.VERSION = BUILD_VERSION;
1223
1197
  return DOMChangesPluginLite;
1224
1198
  }());
1225
1199
  export { DOMChangesPluginLite };