@absmartly/sdk-plugins 1.1.1 → 1.2.1

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