@ninetailed/experience.js 7.11.0 → 7.12.0-beta.0

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.
package/index.cjs.js CHANGED
@@ -98,6 +98,7 @@ const ANONYMOUS_ID = '__nt_anonymous_id__';
98
98
  const DEBUG_FLAG = '__nt_debug__';
99
99
  const PROFILE_FALLBACK_CACHE = '__nt_profile__';
100
100
  const EXPERIENCES_FALLBACK_CACHE = '__nt_experiences__';
101
+ const CHANGES_FALLBACK_CACHE = '__nt_changes__';
101
102
  const PROFILE_CHANGE = 'profile-change';
102
103
  const PROFILE_RESET = 'profile-reset';
103
104
  const CONSENT = '__nt-consent__';
@@ -148,14 +149,12 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
148
149
  instance.dispatch({
149
150
  type: PROFILE_RESET
150
151
  });
151
- instance.storage.removeItem(ANONYMOUS_ID);
152
- instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
153
- instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
152
+ this.clearCaches();
154
153
  experience_jsShared.logger.debug('Removed old profile data from localstorage.');
155
154
  if (typeof this.onInitProfileId === 'function') {
156
155
  const profileId = yield this.onInitProfileId(undefined);
157
156
  if (typeof profileId === 'string') {
158
- instance.storage.setItem(ANONYMOUS_ID, profileId);
157
+ this.setAnonymousId(profileId);
159
158
  }
160
159
  }
161
160
  yield this.ninetailed.track('nt_reset');
@@ -201,13 +200,13 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
201
200
  const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
202
201
  if (legacyAnonymousId) {
203
202
  experience_jsShared.logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
204
- instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
203
+ this.setAnonymousId(legacyAnonymousId);
205
204
  instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
206
205
  }
207
206
  if (typeof this.onInitProfileId === 'function') {
208
- const profileId = yield this.onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
207
+ const profileId = yield this.onInitProfileId(this.getAnonymousId());
209
208
  if (typeof profileId === 'string') {
210
- instance.storage.setItem(ANONYMOUS_ID, profileId);
209
+ this.setAnonymousId(profileId);
211
210
  }
212
211
  }
213
212
  experience_jsShared.logger.debug('Ninetailed Core plugin initialized.');
@@ -316,6 +315,61 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
316
315
  }
317
316
  return this._instance;
318
317
  }
318
+ getAnonymousId() {
319
+ var _c;
320
+ return (_c = this.instance.storage.getItem(ANONYMOUS_ID)) !== null && _c !== void 0 ? _c : undefined;
321
+ }
322
+ setAnonymousId(id) {
323
+ this.instance.storage.setItem(ANONYMOUS_ID, id);
324
+ }
325
+ clearAnonymousId() {
326
+ this.instance.storage.removeItem(ANONYMOUS_ID);
327
+ }
328
+ setFallbackProfile(profile) {
329
+ this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
330
+ }
331
+ getFallbackProfile() {
332
+ var _c;
333
+ return (_c = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE)) !== null && _c !== void 0 ? _c : undefined;
334
+ }
335
+ clearFallbackProfile() {
336
+ this.instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
337
+ }
338
+ setFallbackExperiences(experiences) {
339
+ this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
340
+ }
341
+ getFallbackExperiences() {
342
+ return this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
343
+ }
344
+ clearFallbackExperiences() {
345
+ this.instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
346
+ }
347
+ setFallbackChanges(changes) {
348
+ this.instance.storage.setItem(CHANGES_FALLBACK_CACHE, changes);
349
+ }
350
+ getFallbackChanges() {
351
+ return this.instance.storage.getItem(CHANGES_FALLBACK_CACHE) || [];
352
+ }
353
+ clearFallbackChanges() {
354
+ this.instance.storage.removeItem(CHANGES_FALLBACK_CACHE);
355
+ }
356
+ clearCaches() {
357
+ this.clearAnonymousId();
358
+ this.clearFallbackProfile();
359
+ this.clearFallbackExperiences();
360
+ this.clearFallbackChanges();
361
+ }
362
+ populateCaches({
363
+ experiences,
364
+ profile,
365
+ anonymousId,
366
+ changes
367
+ }) {
368
+ this.setAnonymousId(anonymousId);
369
+ this.setFallbackProfile(profile);
370
+ this.setFallbackExperiences(experiences);
371
+ this.setFallbackChanges(changes);
372
+ }
319
373
  _flush() {
320
374
  return __awaiter(this, void 0, void 0, function* () {
321
375
  const events = Object.assign([], this.queue);
@@ -327,10 +381,11 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
327
381
  };
328
382
  }
329
383
  try {
330
- const anonymousId = this.instance.storage.getItem(ANONYMOUS_ID);
384
+ const anonymousId = this.getAnonymousId();
331
385
  const {
332
386
  profile,
333
- experiences
387
+ experiences,
388
+ changes
334
389
  } = yield this.apiClient.upsertProfile({
335
390
  profileId: anonymousId,
336
391
  events
@@ -338,15 +393,20 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
338
393
  locale: this.locale,
339
394
  enabledFeatures: this.enabledFeatures
340
395
  });
341
- this.instance.storage.setItem(ANONYMOUS_ID, profile.id);
342
- this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
343
- this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
396
+ this.populateCaches({
397
+ anonymousId: profile.id,
398
+ profile,
399
+ experiences,
400
+ changes
401
+ });
344
402
  experience_jsShared.logger.debug('Profile from api: ', profile);
345
403
  experience_jsShared.logger.debug('Experiences from api: ', experiences);
346
404
  this.instance.dispatch({
347
405
  type: PROFILE_CHANGE,
348
406
  profile,
349
- experiences
407
+ experiences,
408
+ changes,
409
+ error: undefined
350
410
  });
351
411
  yield delay(20);
352
412
  return {
@@ -354,22 +414,26 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
354
414
  };
355
415
  } catch (error) {
356
416
  experience_jsShared.logger.debug('An error occurred during flushing the events: ', error);
357
- const fallbackProfile = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE);
358
- const fallbackExperiences = this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
417
+ const fallbackProfile = this.getFallbackProfile();
418
+ const fallbackExperiences = this.getFallbackExperiences();
419
+ const fallbackChanges = this.getFallbackChanges();
359
420
  if (fallbackProfile) {
360
421
  experience_jsShared.logger.debug('Found a fallback profile - will use this.');
361
422
  this.instance.dispatch({
362
423
  type: PROFILE_CHANGE,
363
424
  profile: fallbackProfile,
364
- experiences: fallbackExperiences
425
+ experiences: fallbackExperiences,
426
+ changes: fallbackChanges,
427
+ error: undefined
365
428
  });
366
429
  } else {
367
430
  experience_jsShared.logger.debug('No fallback profile found - setting profile to null.');
368
431
  this.instance.dispatch({
369
432
  type: PROFILE_CHANGE,
370
433
  profile: null,
434
+ changes: fallbackChanges,
371
435
  experiences: fallbackExperiences,
372
- error
436
+ error: error
373
437
  });
374
438
  }
375
439
  return {
@@ -436,19 +500,17 @@ const isInterestedInHiddenPage = arg => {
436
500
  };
437
501
 
438
502
  const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
439
- return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
440
- const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
441
- const variantIndex = parseInt(_variantIndex);
503
+ const experientVariantsAssignments = encodedExperienceVariantsMap.split(',');
504
+ const experienceVariantsMap = {};
505
+ for (const experienceVariantAssignment of experientVariantsAssignments) {
506
+ const [experienceId, variantIndexString] = experienceVariantAssignment.split('=');
507
+ const variantIndex = parseInt(variantIndexString);
442
508
  if (!experienceId || !variantIndex) {
443
- return null;
509
+ continue;
444
510
  }
445
- return {
446
- experienceId,
447
- variantIndex
448
- };
449
- }).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
450
- [curr.experienceId]: curr.variantIndex
451
- }), {});
511
+ experienceVariantsMap[experienceId] = variantIndex;
512
+ }
513
+ return experienceVariantsMap;
452
514
  };
453
515
 
454
516
  class OnChangeEmitter {
@@ -510,6 +572,28 @@ const createPassThroughMiddleware = () => {
510
572
  };
511
573
  };
512
574
  };
575
+ function createExperienceSelectionMiddleware({
576
+ plugins,
577
+ experiences,
578
+ baseline,
579
+ profile
580
+ }) {
581
+ if (profile === null) {
582
+ return createPassThroughMiddleware();
583
+ }
584
+ const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
585
+ const middlewareFunctions = [];
586
+ for (const plugin of pluginsWithMiddleware) {
587
+ const middleware = plugin.getExperienceSelectionMiddleware({
588
+ experiences,
589
+ baseline
590
+ });
591
+ if (middleware !== undefined) {
592
+ middlewareFunctions.push(middleware);
593
+ }
594
+ }
595
+ return experience_jsShared.pipe(...middlewareFunctions);
596
+ }
513
597
  const makeExperienceSelectMiddleware = ({
514
598
  plugins,
515
599
  onChange,
@@ -519,18 +603,12 @@ const makeExperienceSelectMiddleware = ({
519
603
  }) => {
520
604
  let removeChangeListeners = [];
521
605
  const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
522
- const prepareMiddleware = () => {
523
- if (profile === null) {
524
- return createPassThroughMiddleware();
525
- }
526
- const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
527
- const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
528
- experiences,
529
- baseline
530
- })).filter(result => typeof result !== 'undefined');
531
- return experience_jsShared.pipe(...middlewareFunctions);
532
- };
533
- const middleware = prepareMiddleware();
606
+ const middleware = createExperienceSelectionMiddleware({
607
+ plugins,
608
+ experiences,
609
+ baseline,
610
+ profile
611
+ });
534
612
  const addListeners = () => {
535
613
  removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
536
614
  const listener = () => {
@@ -539,8 +617,12 @@ const makeExperienceSelectMiddleware = ({
539
617
  return plugin.onChangeEmitter.addListener(listener);
540
618
  });
541
619
  };
620
+ // WARNING: This specific implementation using forEach is required.
621
+ // DO NOT replace with for...of or other loop constructs as they will break functionality.
622
+ // The exact reason is uncertain but appears related to the transplier.
623
+ // TODO: Come back and find out why this is the case, maybe a version bump is in order.
542
624
  const removeListeners = () => {
543
- removeChangeListeners.forEach(listener => listener());
625
+ removeChangeListeners.forEach(removeListener => removeListener());
544
626
  };
545
627
  return {
546
628
  addListeners,
@@ -553,44 +635,36 @@ class EventBuilder {
553
635
  constructor(buildRequestContext) {
554
636
  this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
555
637
  }
556
- page(properties, data) {
557
- return experience_jsShared.buildPageEvent(Object.assign(Object.assign({
638
+ buildEventBase(data) {
639
+ return Object.assign(Object.assign({
558
640
  messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4()
559
641
  }, data), {
560
642
  timestamp: Date.now(),
561
- properties: properties || {},
562
643
  ctx: this.buildRequestContext()
644
+ });
645
+ }
646
+ page(properties, data) {
647
+ return experience_jsShared.buildPageEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
648
+ properties: properties || {}
563
649
  }));
564
650
  }
565
651
  track(event, properties, data) {
566
- return experience_jsShared.buildTrackEvent(Object.assign(Object.assign({
567
- messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4(),
568
- timestamp: Date.now()
569
- }, data), {
652
+ return experience_jsShared.buildTrackEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
570
653
  event,
571
- properties: properties || {},
572
- ctx: this.buildRequestContext()
654
+ properties: properties || {}
573
655
  }));
574
656
  }
575
657
  identify(userId, traits, data) {
576
- return experience_jsShared.buildIdentifyEvent(Object.assign(Object.assign({
577
- messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4(),
578
- timestamp: Date.now()
579
- }, data), {
658
+ return experience_jsShared.buildIdentifyEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
580
659
  traits: traits || {},
581
- userId: userId || '',
582
- ctx: this.buildRequestContext()
660
+ userId: userId || ''
583
661
  }));
584
662
  }
585
663
  component(componentId, experienceId, variantIndex, data) {
586
- return experience_jsShared.buildComponentViewEvent(Object.assign(Object.assign({
587
- messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4(),
588
- timestamp: Date.now()
589
- }, data), {
664
+ return experience_jsShared.buildComponentViewEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
590
665
  componentId,
591
666
  experienceId: experienceId || '',
592
- variantIndex: variantIndex || 0,
593
- ctx: this.buildRequestContext()
667
+ variantIndex: variantIndex || 0
594
668
  }));
595
669
  }
596
670
  }
@@ -647,11 +721,7 @@ class Ninetailed {
647
721
  yield this.instance.page(data, this.buildOptions(options));
648
722
  return this.ninetailedCorePlugin.flush();
649
723
  } catch (error) {
650
- experience_jsShared.logger.error(error);
651
- if (error instanceof RangeError) {
652
- throw new Error(`[Validation Error] "page" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
653
- }
654
- throw error;
724
+ return this.handleMethodError(error, 'page');
655
725
  }
656
726
  });
657
727
  this.track = (event, properties, options) => __awaiter(this, void 0, void 0, function* () {
@@ -664,11 +734,7 @@ class Ninetailed {
664
734
  yield this.instance.track(event.toString(), result.data, this.buildOptions(options));
665
735
  return this.ninetailedCorePlugin.flush();
666
736
  } catch (error) {
667
- experience_jsShared.logger.error(error);
668
- if (error instanceof RangeError) {
669
- throw new Error(`[Validation Error] "track" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
670
- }
671
- throw error;
737
+ this.handleMethodError(error, 'track');
672
738
  }
673
739
  });
674
740
  this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
@@ -681,11 +747,7 @@ class Ninetailed {
681
747
  yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
682
748
  return this.ninetailedCorePlugin.flush();
683
749
  } catch (error) {
684
- experience_jsShared.logger.error(error);
685
- if (error instanceof RangeError) {
686
- throw new Error(`[Validation Error] "identify" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
687
- }
688
- throw error;
750
+ this.handleMethodError(error, 'identify');
689
751
  }
690
752
  });
691
753
  this.batch = events => __awaiter(this, void 0, void 0, function* () {
@@ -714,11 +776,7 @@ class Ninetailed {
714
776
  yield Promise.all(promises);
715
777
  return this.ninetailedCorePlugin.flush();
716
778
  } catch (error) {
717
- experience_jsShared.logger.error(error);
718
- if (error instanceof RangeError) {
719
- throw new Error(`[Validation Error] "batch" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
720
- }
721
- throw error;
779
+ this.handleMethodError(error, 'batch');
722
780
  }
723
781
  });
724
782
  this.trackStickyComponentView = ({
@@ -736,11 +794,7 @@ class Ninetailed {
736
794
  });
737
795
  return this.ninetailedCorePlugin.flush();
738
796
  } catch (error) {
739
- experience_jsShared.logger.error(error);
740
- if (error instanceof RangeError) {
741
- throw new Error(`[Validation Error] "trackStickyComponentView" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
742
- }
743
- throw error;
797
+ this.handleMethodError(error, 'trackStickyComponentView');
744
798
  }
745
799
  });
746
800
  /**
@@ -763,33 +817,13 @@ class Ninetailed {
763
817
  const {
764
818
  element
765
819
  } = payload,
766
- remaingPayload = __rest(payload, ["element"]);
820
+ remainingPayload = __rest(payload, ["element"]);
767
821
  if (!(element instanceof Element)) {
768
- const isObject = typeof element === 'object' && element !== null;
769
- const constructorName = isObject ? element.constructor.name : '';
770
- const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
771
- experience_jsShared.logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
772
- } else {
773
- const existingPayloads = this.observedElements.get(element);
774
- const delays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
775
- const uniqueDelays = Array.from(new Set([...delays, options === null || options === void 0 ? void 0 : options.delay]));
776
- if (!existingPayloads) {
777
- this.observedElements.set(element, [remaingPayload]);
778
- } else {
779
- const isPayloadAlreadyObserved = existingPayloads.some(payload => {
780
- return JSON.stringify(payload) === JSON.stringify(remaingPayload);
781
- });
782
- if (isPayloadAlreadyObserved) {
783
- return;
784
- }
785
- this.observedElements.set(element, [...existingPayloads, remaingPayload]);
786
- }
787
- uniqueDelays.forEach(delay => {
788
- this.elementSeenObserver.observe(element, {
789
- delay
790
- });
791
- });
822
+ this.logInvalidElement(element);
823
+ return;
792
824
  }
825
+ this.storeElementPayload(element, remainingPayload);
826
+ this.setupElementObservation(element, options === null || options === void 0 ? void 0 : options.delay);
793
827
  };
794
828
  this.unobserveElement = element => {
795
829
  this.observedElements.delete(element);
@@ -835,6 +869,7 @@ class Ninetailed {
835
869
  cb(Object.assign(Object.assign({}, this._profileState), {
836
870
  status: 'success',
837
871
  profile: payload.profile,
872
+ changes: payload.changes,
838
873
  experiences: payload.experiences,
839
874
  error: null
840
875
  }));
@@ -1003,14 +1038,26 @@ class Ninetailed {
1003
1038
  removeMiddlewareChangeListeners();
1004
1039
  };
1005
1040
  };
1006
- this.onIsInitialized = onIsInitialized => {
1007
- if (typeof onIsInitialized === 'function') {
1041
+ /**
1042
+ * Registers a callback to be notified when changes occur in the profile state.
1043
+ *
1044
+ * @param cb - Callback function that receives the changes state
1045
+ * @returns Function to unsubscribe from changes updates
1046
+ */
1047
+ this.onChangesChange = cb => {
1048
+ this.notifyChangesCallback(cb, this._profileState);
1049
+ return this.onProfileChange(profileState => {
1050
+ this.notifyChangesCallback(cb, profileState);
1051
+ });
1052
+ };
1053
+ this.onIsInitialized = onIsInitializedCallback => {
1054
+ if (typeof onIsInitializedCallback === 'function') {
1008
1055
  if (this.isInitialized) {
1009
- onIsInitialized();
1056
+ onIsInitializedCallback();
1010
1057
  } else {
1011
1058
  const detachOnReadyListener = this.instance.on('ready', () => {
1012
1059
  this.isInitialized = true;
1013
- onIsInitialized();
1060
+ onIsInitializedCallback();
1014
1061
  detachOnReadyListener();
1015
1062
  });
1016
1063
  }
@@ -1090,6 +1137,7 @@ class Ninetailed {
1090
1137
  status: 'loading',
1091
1138
  profile: null,
1092
1139
  experiences: null,
1140
+ changes: null,
1093
1141
  error: null,
1094
1142
  from: 'api'
1095
1143
  };
@@ -1136,6 +1184,67 @@ class Ninetailed {
1136
1184
  get pluginsWithCustomComponentViewThreshold() {
1137
1185
  return [this.ninetailedCorePlugin, ...this.plugins].filter(plugin => experience_jsPluginAnalytics.hasComponentViewTrackingThreshold(plugin));
1138
1186
  }
1187
+ logInvalidElement(element) {
1188
+ const isObject = typeof element === 'object' && element !== null;
1189
+ const constructorName = isObject ? element.constructor.name : '';
1190
+ const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
1191
+ experience_jsShared.logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
1192
+ }
1193
+ storeElementPayload(element, payload) {
1194
+ const existingPayloads = this.observedElements.get(element) || [];
1195
+ // Check if the payload is already being observed for this element
1196
+ const isPayloadAlreadyObserved = existingPayloads.some(existingPayload => JSON.stringify(existingPayload) === JSON.stringify(payload));
1197
+ if (isPayloadAlreadyObserved) {
1198
+ return;
1199
+ }
1200
+ // Store the new or updated payloads
1201
+ this.observedElements.set(element, [...existingPayloads, payload]);
1202
+ }
1203
+ setupElementObservation(element, delay) {
1204
+ // Get all relevant delays from plugins and the custom delay
1205
+ const pluginDelays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
1206
+ // Ensure we only observe each delay once
1207
+ const uniqueDelays = Array.from(new Set([...pluginDelays, delay]));
1208
+ // Set up observation for each delay
1209
+ uniqueDelays.forEach(delay => {
1210
+ this.elementSeenObserver.observe(element, {
1211
+ delay
1212
+ });
1213
+ });
1214
+ }
1215
+ /**
1216
+ * Helper method to extract changes state from profile state and notify callback
1217
+ * @private
1218
+ */
1219
+ notifyChangesCallback(cb, profileState) {
1220
+ if (profileState.status === 'loading') {
1221
+ cb({
1222
+ status: 'loading',
1223
+ changes: null,
1224
+ error: null
1225
+ });
1226
+ } else if (profileState.status === 'error') {
1227
+ cb({
1228
+ status: 'error',
1229
+ changes: null,
1230
+ error: profileState.error
1231
+ });
1232
+ } else {
1233
+ cb({
1234
+ status: 'success',
1235
+ changes: profileState.changes,
1236
+ error: null
1237
+ });
1238
+ }
1239
+ }
1240
+ // Always throws, never returns
1241
+ handleMethodError(error, method) {
1242
+ experience_jsShared.logger.error(error);
1243
+ if (error instanceof RangeError) {
1244
+ throw new Error(`[Validation Error] "${method}" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
1245
+ }
1246
+ throw error;
1247
+ }
1139
1248
  get profileState() {
1140
1249
  return this._profileState;
1141
1250
  }
@@ -1281,6 +1390,7 @@ Object.defineProperty(exports, 'selectHasExperienceVariants', {
1281
1390
  get: function () { return experience_jsShared.selectHasVariants; }
1282
1391
  });
1283
1392
  exports.ANONYMOUS_ID = ANONYMOUS_ID;
1393
+ exports.CHANGES_FALLBACK_CACHE = CHANGES_FALLBACK_CACHE;
1284
1394
  exports.COMPONENT = COMPONENT;
1285
1395
  exports.COMPONENT_START = COMPONENT_START;
1286
1396
  exports.CONSENT = CONSENT;