@ninetailed/experience.js 7.11.0 → 7.11.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.
package/index.cjs.js CHANGED
@@ -148,14 +148,12 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
148
148
  instance.dispatch({
149
149
  type: PROFILE_RESET
150
150
  });
151
- instance.storage.removeItem(ANONYMOUS_ID);
152
- instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
153
- instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
151
+ this.clearCaches();
154
152
  experience_jsShared.logger.debug('Removed old profile data from localstorage.');
155
153
  if (typeof this.onInitProfileId === 'function') {
156
154
  const profileId = yield this.onInitProfileId(undefined);
157
155
  if (typeof profileId === 'string') {
158
- instance.storage.setItem(ANONYMOUS_ID, profileId);
156
+ this.setAnonymousId(profileId);
159
157
  }
160
158
  }
161
159
  yield this.ninetailed.track('nt_reset');
@@ -201,13 +199,13 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
201
199
  const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
202
200
  if (legacyAnonymousId) {
203
201
  experience_jsShared.logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
204
- instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
202
+ this.setAnonymousId(legacyAnonymousId);
205
203
  instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
206
204
  }
207
205
  if (typeof this.onInitProfileId === 'function') {
208
- const profileId = yield this.onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
206
+ const profileId = yield this.onInitProfileId(this.getAnonymousId());
209
207
  if (typeof profileId === 'string') {
210
- instance.storage.setItem(ANONYMOUS_ID, profileId);
208
+ this.setAnonymousId(profileId);
211
209
  }
212
210
  }
213
211
  experience_jsShared.logger.debug('Ninetailed Core plugin initialized.');
@@ -316,6 +314,49 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
316
314
  }
317
315
  return this._instance;
318
316
  }
317
+ getAnonymousId() {
318
+ var _c;
319
+ return (_c = this.instance.storage.getItem(ANONYMOUS_ID)) !== null && _c !== void 0 ? _c : undefined;
320
+ }
321
+ setAnonymousId(id) {
322
+ this.instance.storage.setItem(ANONYMOUS_ID, id);
323
+ }
324
+ clearAnonymousId() {
325
+ this.instance.storage.removeItem(ANONYMOUS_ID);
326
+ }
327
+ setFallbackProfile(profile) {
328
+ this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
329
+ }
330
+ getFallbackProfile() {
331
+ var _c;
332
+ return (_c = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE)) !== null && _c !== void 0 ? _c : undefined;
333
+ }
334
+ clearFallbackProfile() {
335
+ this.instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
336
+ }
337
+ setFallbackExperiences(experiences) {
338
+ this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
339
+ }
340
+ getFallbackExperiences() {
341
+ return this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
342
+ }
343
+ clearFallbackExperiences() {
344
+ this.instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
345
+ }
346
+ clearCaches() {
347
+ this.clearAnonymousId();
348
+ this.clearFallbackProfile();
349
+ this.clearFallbackExperiences();
350
+ }
351
+ populateCaches({
352
+ experiences,
353
+ profile,
354
+ anonymousId
355
+ }) {
356
+ this.setAnonymousId(anonymousId);
357
+ this.setFallbackProfile(profile);
358
+ this.setFallbackExperiences(experiences);
359
+ }
319
360
  _flush() {
320
361
  return __awaiter(this, void 0, void 0, function* () {
321
362
  const events = Object.assign([], this.queue);
@@ -327,7 +368,7 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
327
368
  };
328
369
  }
329
370
  try {
330
- const anonymousId = this.instance.storage.getItem(ANONYMOUS_ID);
371
+ const anonymousId = this.getAnonymousId();
331
372
  const {
332
373
  profile,
333
374
  experiences
@@ -338,15 +379,18 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
338
379
  locale: this.locale,
339
380
  enabledFeatures: this.enabledFeatures
340
381
  });
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);
382
+ this.populateCaches({
383
+ anonymousId: profile.id,
384
+ profile,
385
+ experiences
386
+ });
344
387
  experience_jsShared.logger.debug('Profile from api: ', profile);
345
388
  experience_jsShared.logger.debug('Experiences from api: ', experiences);
346
389
  this.instance.dispatch({
347
390
  type: PROFILE_CHANGE,
348
391
  profile,
349
- experiences
392
+ experiences,
393
+ error: undefined
350
394
  });
351
395
  yield delay(20);
352
396
  return {
@@ -354,14 +398,15 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
354
398
  };
355
399
  } catch (error) {
356
400
  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) || [];
401
+ const fallbackProfile = this.getFallbackProfile();
402
+ const fallbackExperiences = this.getFallbackExperiences();
359
403
  if (fallbackProfile) {
360
404
  experience_jsShared.logger.debug('Found a fallback profile - will use this.');
361
405
  this.instance.dispatch({
362
406
  type: PROFILE_CHANGE,
363
407
  profile: fallbackProfile,
364
- experiences: fallbackExperiences
408
+ experiences: fallbackExperiences,
409
+ error: undefined
365
410
  });
366
411
  } else {
367
412
  experience_jsShared.logger.debug('No fallback profile found - setting profile to null.');
@@ -369,7 +414,7 @@ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyt
369
414
  type: PROFILE_CHANGE,
370
415
  profile: null,
371
416
  experiences: fallbackExperiences,
372
- error
417
+ error: error
373
418
  });
374
419
  }
375
420
  return {
@@ -436,19 +481,17 @@ const isInterestedInHiddenPage = arg => {
436
481
  };
437
482
 
438
483
  const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
439
- return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
440
- const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
441
- const variantIndex = parseInt(_variantIndex);
484
+ const experientVariantsAssignments = encodedExperienceVariantsMap.split(',');
485
+ const experienceVariantsMap = {};
486
+ for (const experienceVariantAssignment of experientVariantsAssignments) {
487
+ const [experienceId, variantIndexString] = experienceVariantAssignment.split('=');
488
+ const variantIndex = parseInt(variantIndexString);
442
489
  if (!experienceId || !variantIndex) {
443
- return null;
490
+ continue;
444
491
  }
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
- }), {});
492
+ experienceVariantsMap[experienceId] = variantIndex;
493
+ }
494
+ return experienceVariantsMap;
452
495
  };
453
496
 
454
497
  class OnChangeEmitter {
@@ -510,6 +553,28 @@ const createPassThroughMiddleware = () => {
510
553
  };
511
554
  };
512
555
  };
556
+ function createExperienceSelectionMiddleware({
557
+ plugins,
558
+ experiences,
559
+ baseline,
560
+ profile
561
+ }) {
562
+ if (profile === null) {
563
+ return createPassThroughMiddleware();
564
+ }
565
+ const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
566
+ const middlewareFunctions = [];
567
+ for (const plugin of pluginsWithMiddleware) {
568
+ const middleware = plugin.getExperienceSelectionMiddleware({
569
+ experiences,
570
+ baseline
571
+ });
572
+ if (middleware !== undefined) {
573
+ middlewareFunctions.push(middleware);
574
+ }
575
+ }
576
+ return experience_jsShared.pipe(...middlewareFunctions);
577
+ }
513
578
  const makeExperienceSelectMiddleware = ({
514
579
  plugins,
515
580
  onChange,
@@ -519,18 +584,12 @@ const makeExperienceSelectMiddleware = ({
519
584
  }) => {
520
585
  let removeChangeListeners = [];
521
586
  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();
587
+ const middleware = createExperienceSelectionMiddleware({
588
+ plugins,
589
+ experiences,
590
+ baseline,
591
+ profile
592
+ });
534
593
  const addListeners = () => {
535
594
  removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
536
595
  const listener = () => {
@@ -539,8 +598,12 @@ const makeExperienceSelectMiddleware = ({
539
598
  return plugin.onChangeEmitter.addListener(listener);
540
599
  });
541
600
  };
601
+ // WARNING: This specific implementation using forEach is required.
602
+ // DO NOT replace with for...of or other loop constructs as they will break functionality.
603
+ // The exact reason is uncertain but appears related to the transplier.
604
+ // TODO: Come back and find out why this is the case, maybe a version bump is in order.
542
605
  const removeListeners = () => {
543
- removeChangeListeners.forEach(listener => listener());
606
+ removeChangeListeners.forEach(removeListener => removeListener());
544
607
  };
545
608
  return {
546
609
  addListeners,
@@ -553,44 +616,36 @@ class EventBuilder {
553
616
  constructor(buildRequestContext) {
554
617
  this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
555
618
  }
556
- page(properties, data) {
557
- return experience_jsShared.buildPageEvent(Object.assign(Object.assign({
619
+ buildEventBase(data) {
620
+ return Object.assign(Object.assign({
558
621
  messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4()
559
622
  }, data), {
560
623
  timestamp: Date.now(),
561
- properties: properties || {},
562
624
  ctx: this.buildRequestContext()
625
+ });
626
+ }
627
+ page(properties, data) {
628
+ return experience_jsShared.buildPageEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
629
+ properties: properties || {}
563
630
  }));
564
631
  }
565
632
  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), {
633
+ return experience_jsShared.buildTrackEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
570
634
  event,
571
- properties: properties || {},
572
- ctx: this.buildRequestContext()
635
+ properties: properties || {}
573
636
  }));
574
637
  }
575
638
  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), {
639
+ return experience_jsShared.buildIdentifyEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
580
640
  traits: traits || {},
581
- userId: userId || '',
582
- ctx: this.buildRequestContext()
641
+ userId: userId || ''
583
642
  }));
584
643
  }
585
644
  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), {
645
+ return experience_jsShared.buildComponentViewEvent(Object.assign(Object.assign({}, this.buildEventBase(data)), {
590
646
  componentId,
591
647
  experienceId: experienceId || '',
592
- variantIndex: variantIndex || 0,
593
- ctx: this.buildRequestContext()
648
+ variantIndex: variantIndex || 0
594
649
  }));
595
650
  }
596
651
  }
@@ -647,11 +702,7 @@ class Ninetailed {
647
702
  yield this.instance.page(data, this.buildOptions(options));
648
703
  return this.ninetailedCorePlugin.flush();
649
704
  } 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;
705
+ return this.handleMethodError(error, 'page');
655
706
  }
656
707
  });
657
708
  this.track = (event, properties, options) => __awaiter(this, void 0, void 0, function* () {
@@ -664,11 +715,7 @@ class Ninetailed {
664
715
  yield this.instance.track(event.toString(), result.data, this.buildOptions(options));
665
716
  return this.ninetailedCorePlugin.flush();
666
717
  } 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;
718
+ this.handleMethodError(error, 'track');
672
719
  }
673
720
  });
674
721
  this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
@@ -681,11 +728,7 @@ class Ninetailed {
681
728
  yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
682
729
  return this.ninetailedCorePlugin.flush();
683
730
  } 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;
731
+ this.handleMethodError(error, 'identify');
689
732
  }
690
733
  });
691
734
  this.batch = events => __awaiter(this, void 0, void 0, function* () {
@@ -714,11 +757,7 @@ class Ninetailed {
714
757
  yield Promise.all(promises);
715
758
  return this.ninetailedCorePlugin.flush();
716
759
  } 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;
760
+ this.handleMethodError(error, 'batch');
722
761
  }
723
762
  });
724
763
  this.trackStickyComponentView = ({
@@ -736,11 +775,7 @@ class Ninetailed {
736
775
  });
737
776
  return this.ninetailedCorePlugin.flush();
738
777
  } 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;
778
+ this.handleMethodError(error, 'trackStickyComponentView');
744
779
  }
745
780
  });
746
781
  /**
@@ -763,33 +798,13 @@ class Ninetailed {
763
798
  const {
764
799
  element
765
800
  } = payload,
766
- remaingPayload = __rest(payload, ["element"]);
801
+ remainingPayload = __rest(payload, ["element"]);
767
802
  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
- });
803
+ this.logInvalidElement(element);
804
+ return;
792
805
  }
806
+ this.storeElementPayload(element, remainingPayload);
807
+ this.setupElementObservation(element, options === null || options === void 0 ? void 0 : options.delay);
793
808
  };
794
809
  this.unobserveElement = element => {
795
810
  this.observedElements.delete(element);
@@ -1003,14 +1018,14 @@ class Ninetailed {
1003
1018
  removeMiddlewareChangeListeners();
1004
1019
  };
1005
1020
  };
1006
- this.onIsInitialized = onIsInitialized => {
1007
- if (typeof onIsInitialized === 'function') {
1021
+ this.onIsInitialized = onIsInitializedCallback => {
1022
+ if (typeof onIsInitializedCallback === 'function') {
1008
1023
  if (this.isInitialized) {
1009
- onIsInitialized();
1024
+ onIsInitializedCallback();
1010
1025
  } else {
1011
1026
  const detachOnReadyListener = this.instance.on('ready', () => {
1012
1027
  this.isInitialized = true;
1013
- onIsInitialized();
1028
+ onIsInitializedCallback();
1014
1029
  detachOnReadyListener();
1015
1030
  });
1016
1031
  }
@@ -1136,6 +1151,42 @@ class Ninetailed {
1136
1151
  get pluginsWithCustomComponentViewThreshold() {
1137
1152
  return [this.ninetailedCorePlugin, ...this.plugins].filter(plugin => experience_jsPluginAnalytics.hasComponentViewTrackingThreshold(plugin));
1138
1153
  }
1154
+ logInvalidElement(element) {
1155
+ const isObject = typeof element === 'object' && element !== null;
1156
+ const constructorName = isObject ? element.constructor.name : '';
1157
+ const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
1158
+ 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.`);
1159
+ }
1160
+ storeElementPayload(element, payload) {
1161
+ const existingPayloads = this.observedElements.get(element) || [];
1162
+ // Check if the payload is already being observed for this element
1163
+ const isPayloadAlreadyObserved = existingPayloads.some(existingPayload => JSON.stringify(existingPayload) === JSON.stringify(payload));
1164
+ if (isPayloadAlreadyObserved) {
1165
+ return;
1166
+ }
1167
+ // Store the new or updated payloads
1168
+ this.observedElements.set(element, [...existingPayloads, payload]);
1169
+ }
1170
+ setupElementObservation(element, delay) {
1171
+ // Get all relevant delays from plugins and the custom delay
1172
+ const pluginDelays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
1173
+ // Ensure we only observe each delay once
1174
+ const uniqueDelays = Array.from(new Set([...pluginDelays, delay]));
1175
+ // Set up observation for each delay
1176
+ uniqueDelays.forEach(delay => {
1177
+ this.elementSeenObserver.observe(element, {
1178
+ delay
1179
+ });
1180
+ });
1181
+ }
1182
+ // Always throws, never returns
1183
+ handleMethodError(error, method) {
1184
+ experience_jsShared.logger.error(error);
1185
+ if (error instanceof RangeError) {
1186
+ 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.`);
1187
+ }
1188
+ throw error;
1189
+ }
1139
1190
  get profileState() {
1140
1191
  return this._profileState;
1141
1192
  }
package/index.esm.js CHANGED
@@ -110,14 +110,12 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
110
110
  instance.dispatch({
111
111
  type: PROFILE_RESET
112
112
  });
113
- instance.storage.removeItem(ANONYMOUS_ID);
114
- instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
115
- instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
113
+ _this.clearCaches();
116
114
  logger.debug('Removed old profile data from localstorage.');
117
115
  if (typeof _this.onInitProfileId === 'function') {
118
116
  const profileId = await _this.onInitProfileId(undefined);
119
117
  if (typeof profileId === 'string') {
120
- instance.storage.setItem(ANONYMOUS_ID, profileId);
118
+ _this.setAnonymousId(profileId);
121
119
  }
122
120
  }
123
121
  await _this.ninetailed.track('nt_reset');
@@ -163,13 +161,13 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
163
161
  const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
164
162
  if (legacyAnonymousId) {
165
163
  logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
166
- instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
164
+ this.setAnonymousId(legacyAnonymousId);
167
165
  instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
168
166
  }
169
167
  if (typeof this.onInitProfileId === 'function') {
170
- const profileId = await this.onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
168
+ const profileId = await this.onInitProfileId(this.getAnonymousId());
171
169
  if (typeof profileId === 'string') {
172
- instance.storage.setItem(ANONYMOUS_ID, profileId);
170
+ this.setAnonymousId(profileId);
173
171
  }
174
172
  }
175
173
  logger.debug('Ninetailed Core plugin initialized.');
@@ -267,6 +265,49 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
267
265
  }
268
266
  return this._instance;
269
267
  }
268
+ getAnonymousId() {
269
+ var _ref;
270
+ return (_ref = this.instance.storage.getItem(ANONYMOUS_ID)) != null ? _ref : undefined;
271
+ }
272
+ setAnonymousId(id) {
273
+ this.instance.storage.setItem(ANONYMOUS_ID, id);
274
+ }
275
+ clearAnonymousId() {
276
+ this.instance.storage.removeItem(ANONYMOUS_ID);
277
+ }
278
+ setFallbackProfile(profile) {
279
+ this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
280
+ }
281
+ getFallbackProfile() {
282
+ var _ref2;
283
+ return (_ref2 = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE)) != null ? _ref2 : undefined;
284
+ }
285
+ clearFallbackProfile() {
286
+ this.instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
287
+ }
288
+ setFallbackExperiences(experiences) {
289
+ this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
290
+ }
291
+ getFallbackExperiences() {
292
+ return this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
293
+ }
294
+ clearFallbackExperiences() {
295
+ this.instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
296
+ }
297
+ clearCaches() {
298
+ this.clearAnonymousId();
299
+ this.clearFallbackProfile();
300
+ this.clearFallbackExperiences();
301
+ }
302
+ populateCaches({
303
+ experiences,
304
+ profile,
305
+ anonymousId
306
+ }) {
307
+ this.setAnonymousId(anonymousId);
308
+ this.setFallbackProfile(profile);
309
+ this.setFallbackExperiences(experiences);
310
+ }
270
311
  async _flush() {
271
312
  const events = Object.assign([], this.queue);
272
313
  logger.info('Start flushing events.');
@@ -277,7 +318,7 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
277
318
  };
278
319
  }
279
320
  try {
280
- const anonymousId = this.instance.storage.getItem(ANONYMOUS_ID);
321
+ const anonymousId = this.getAnonymousId();
281
322
  const {
282
323
  profile,
283
324
  experiences
@@ -288,15 +329,18 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
288
329
  locale: this.locale,
289
330
  enabledFeatures: this.enabledFeatures
290
331
  });
291
- this.instance.storage.setItem(ANONYMOUS_ID, profile.id);
292
- this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
293
- this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
332
+ this.populateCaches({
333
+ anonymousId: profile.id,
334
+ profile,
335
+ experiences
336
+ });
294
337
  logger.debug('Profile from api: ', profile);
295
338
  logger.debug('Experiences from api: ', experiences);
296
339
  this.instance.dispatch({
297
340
  type: PROFILE_CHANGE,
298
341
  profile,
299
- experiences
342
+ experiences,
343
+ error: undefined
300
344
  });
301
345
  await delay(20);
302
346
  return {
@@ -304,14 +348,15 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
304
348
  };
305
349
  } catch (error) {
306
350
  logger.debug('An error occurred during flushing the events: ', error);
307
- const fallbackProfile = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE);
308
- const fallbackExperiences = this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
351
+ const fallbackProfile = this.getFallbackProfile();
352
+ const fallbackExperiences = this.getFallbackExperiences();
309
353
  if (fallbackProfile) {
310
354
  logger.debug('Found a fallback profile - will use this.');
311
355
  this.instance.dispatch({
312
356
  type: PROFILE_CHANGE,
313
357
  profile: fallbackProfile,
314
- experiences: fallbackExperiences
358
+ experiences: fallbackExperiences,
359
+ error: undefined
315
360
  });
316
361
  } else {
317
362
  logger.debug('No fallback profile found - setting profile to null.');
@@ -319,7 +364,7 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
319
364
  type: PROFILE_CHANGE,
320
365
  profile: null,
321
366
  experiences: fallbackExperiences,
322
- error
367
+ error: error
323
368
  });
324
369
  }
325
370
  return {
@@ -400,19 +445,17 @@ const isInterestedInHiddenPage = arg => {
400
445
  };
401
446
 
402
447
  const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
403
- return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
404
- const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
405
- const variantIndex = parseInt(_variantIndex);
448
+ const experientVariantsAssignments = encodedExperienceVariantsMap.split(',');
449
+ const experienceVariantsMap = {};
450
+ for (const experienceVariantAssignment of experientVariantsAssignments) {
451
+ const [experienceId, variantIndexString] = experienceVariantAssignment.split('=');
452
+ const variantIndex = parseInt(variantIndexString);
406
453
  if (!experienceId || !variantIndex) {
407
- return null;
454
+ continue;
408
455
  }
409
- return {
410
- experienceId,
411
- variantIndex
412
- };
413
- }).filter(x => !!x).reduce((acc, curr) => Object.assign({}, acc, {
414
- [curr.experienceId]: curr.variantIndex
415
- }), {});
456
+ experienceVariantsMap[experienceId] = variantIndex;
457
+ }
458
+ return experienceVariantsMap;
416
459
  };
417
460
 
418
461
  class OnChangeEmitter {
@@ -474,6 +517,28 @@ const createPassThroughMiddleware = () => {
474
517
  };
475
518
  };
476
519
  };
520
+ function createExperienceSelectionMiddleware({
521
+ plugins,
522
+ experiences,
523
+ baseline,
524
+ profile
525
+ }) {
526
+ if (profile === null) {
527
+ return createPassThroughMiddleware();
528
+ }
529
+ const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
530
+ const middlewareFunctions = [];
531
+ for (const plugin of pluginsWithMiddleware) {
532
+ const middleware = plugin.getExperienceSelectionMiddleware({
533
+ experiences,
534
+ baseline
535
+ });
536
+ if (middleware !== undefined) {
537
+ middlewareFunctions.push(middleware);
538
+ }
539
+ }
540
+ return pipe(...middlewareFunctions);
541
+ }
477
542
  const makeExperienceSelectMiddleware = ({
478
543
  plugins,
479
544
  onChange,
@@ -483,18 +548,12 @@ const makeExperienceSelectMiddleware = ({
483
548
  }) => {
484
549
  let removeChangeListeners = [];
485
550
  const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
486
- const prepareMiddleware = () => {
487
- if (profile === null) {
488
- return createPassThroughMiddleware();
489
- }
490
- const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
491
- const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
492
- experiences,
493
- baseline
494
- })).filter(result => typeof result !== 'undefined');
495
- return pipe(...middlewareFunctions);
496
- };
497
- const middleware = prepareMiddleware();
551
+ const middleware = createExperienceSelectionMiddleware({
552
+ plugins,
553
+ experiences,
554
+ baseline,
555
+ profile
556
+ });
498
557
  const addListeners = () => {
499
558
  removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
500
559
  const listener = () => {
@@ -503,8 +562,13 @@ const makeExperienceSelectMiddleware = ({
503
562
  return plugin.onChangeEmitter.addListener(listener);
504
563
  });
505
564
  };
565
+
566
+ // WARNING: This specific implementation using forEach is required.
567
+ // DO NOT replace with for...of or other loop constructs as they will break functionality.
568
+ // The exact reason is uncertain but appears related to the transplier.
569
+ // TODO: Come back and find out why this is the case, maybe a version bump is in order.
506
570
  const removeListeners = () => {
507
- removeChangeListeners.forEach(listener => listener());
571
+ removeChangeListeners.forEach(removeListener => removeListener());
508
572
  };
509
573
  return {
510
574
  addListeners,
@@ -518,44 +582,36 @@ class EventBuilder {
518
582
  this.buildRequestContext = void 0;
519
583
  this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
520
584
  }
521
- page(properties, data) {
522
- return buildPageEvent(Object.assign({
585
+ buildEventBase(data) {
586
+ return Object.assign({
523
587
  messageId: (data == null ? void 0 : data.messageId) || v4()
524
588
  }, data, {
525
589
  timestamp: Date.now(),
526
- properties: properties || {},
527
590
  ctx: this.buildRequestContext()
591
+ });
592
+ }
593
+ page(properties, data) {
594
+ return buildPageEvent(Object.assign({}, this.buildEventBase(data), {
595
+ properties: properties || {}
528
596
  }));
529
597
  }
530
598
  track(event, properties, data) {
531
- return buildTrackEvent(Object.assign({
532
- messageId: (data == null ? void 0 : data.messageId) || v4(),
533
- timestamp: Date.now()
534
- }, data, {
599
+ return buildTrackEvent(Object.assign({}, this.buildEventBase(data), {
535
600
  event,
536
- properties: properties || {},
537
- ctx: this.buildRequestContext()
601
+ properties: properties || {}
538
602
  }));
539
603
  }
540
604
  identify(userId, traits, data) {
541
- return buildIdentifyEvent(Object.assign({
542
- messageId: (data == null ? void 0 : data.messageId) || v4(),
543
- timestamp: Date.now()
544
- }, data, {
605
+ return buildIdentifyEvent(Object.assign({}, this.buildEventBase(data), {
545
606
  traits: traits || {},
546
- userId: userId || '',
547
- ctx: this.buildRequestContext()
607
+ userId: userId || ''
548
608
  }));
549
609
  }
550
610
  component(componentId, experienceId, variantIndex, data) {
551
- return buildComponentViewEvent(Object.assign({
552
- messageId: (data == null ? void 0 : data.messageId) || v4(),
553
- timestamp: Date.now()
554
- }, data, {
611
+ return buildComponentViewEvent(Object.assign({}, this.buildEventBase(data), {
555
612
  componentId,
556
613
  experienceId: experienceId || '',
557
- variantIndex: variantIndex || 0,
558
- ctx: this.buildRequestContext()
614
+ variantIndex: variantIndex || 0
559
615
  }));
560
616
  }
561
617
  }
@@ -628,11 +684,7 @@ class Ninetailed {
628
684
  await _this.instance.page(data, _this.buildOptions(options));
629
685
  return _this.ninetailedCorePlugin.flush();
630
686
  } catch (error) {
631
- logger.error(error);
632
- if (error instanceof RangeError) {
633
- 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.`);
634
- }
635
- throw error;
687
+ return _this.handleMethodError(error, 'page');
636
688
  }
637
689
  };
638
690
  this.track = async function (event, properties, options) {
@@ -645,11 +697,7 @@ class Ninetailed {
645
697
  await _this.instance.track(event.toString(), result.data, _this.buildOptions(options));
646
698
  return _this.ninetailedCorePlugin.flush();
647
699
  } catch (error) {
648
- logger.error(error);
649
- if (error instanceof RangeError) {
650
- 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.`);
651
- }
652
- throw error;
700
+ _this.handleMethodError(error, 'track');
653
701
  }
654
702
  };
655
703
  this.identify = async function (uid, traits, options) {
@@ -662,11 +710,7 @@ class Ninetailed {
662
710
  await _this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, _this.buildOptions(options));
663
711
  return _this.ninetailedCorePlugin.flush();
664
712
  } catch (error) {
665
- logger.error(error);
666
- if (error instanceof RangeError) {
667
- 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.`);
668
- }
669
- throw error;
713
+ _this.handleMethodError(error, 'identify');
670
714
  }
671
715
  };
672
716
  this.batch = async function (events) {
@@ -695,11 +739,7 @@ class Ninetailed {
695
739
  await Promise.all(promises);
696
740
  return _this.ninetailedCorePlugin.flush();
697
741
  } catch (error) {
698
- logger.error(error);
699
- if (error instanceof RangeError) {
700
- 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.`);
701
- }
702
- throw error;
742
+ _this.handleMethodError(error, 'batch');
703
743
  }
704
744
  };
705
745
  this.trackStickyComponentView = async function ({
@@ -717,11 +757,7 @@ class Ninetailed {
717
757
  });
718
758
  return _this.ninetailedCorePlugin.flush();
719
759
  } catch (error) {
720
- logger.error(error);
721
- if (error instanceof RangeError) {
722
- 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.`);
723
- }
724
- throw error;
760
+ _this.handleMethodError(error, 'trackStickyComponentView');
725
761
  }
726
762
  };
727
763
  /**
@@ -744,33 +780,13 @@ class Ninetailed {
744
780
  const {
745
781
  element
746
782
  } = payload,
747
- remaingPayload = _objectWithoutPropertiesLoose(payload, _excluded2);
783
+ remainingPayload = _objectWithoutPropertiesLoose(payload, _excluded2);
748
784
  if (!(element instanceof Element)) {
749
- const isObject = typeof element === 'object' && element !== null;
750
- const constructorName = isObject ? element.constructor.name : '';
751
- const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
752
- 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.`);
753
- } else {
754
- const existingPayloads = this.observedElements.get(element);
755
- const delays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
756
- const uniqueDelays = Array.from(new Set([...delays, options == null ? void 0 : options.delay]));
757
- if (!existingPayloads) {
758
- this.observedElements.set(element, [remaingPayload]);
759
- } else {
760
- const isPayloadAlreadyObserved = existingPayloads.some(payload => {
761
- return JSON.stringify(payload) === JSON.stringify(remaingPayload);
762
- });
763
- if (isPayloadAlreadyObserved) {
764
- return;
765
- }
766
- this.observedElements.set(element, [...existingPayloads, remaingPayload]);
767
- }
768
- uniqueDelays.forEach(delay => {
769
- this.elementSeenObserver.observe(element, {
770
- delay
771
- });
772
- });
785
+ this.logInvalidElement(element);
786
+ return;
773
787
  }
788
+ this.storeElementPayload(element, remainingPayload);
789
+ this.setupElementObservation(element, options == null ? void 0 : options.delay);
774
790
  };
775
791
  this.unobserveElement = element => {
776
792
  this.observedElements.delete(element);
@@ -985,14 +1001,14 @@ class Ninetailed {
985
1001
  removeMiddlewareChangeListeners();
986
1002
  };
987
1003
  };
988
- this.onIsInitialized = onIsInitialized => {
989
- if (typeof onIsInitialized === 'function') {
1004
+ this.onIsInitialized = onIsInitializedCallback => {
1005
+ if (typeof onIsInitializedCallback === 'function') {
990
1006
  if (this.isInitialized) {
991
- onIsInitialized();
1007
+ onIsInitializedCallback();
992
1008
  } else {
993
1009
  const detachOnReadyListener = this.instance.on('ready', () => {
994
1010
  this.isInitialized = true;
995
- onIsInitialized();
1011
+ onIsInitializedCallback();
996
1012
  detachOnReadyListener();
997
1013
  });
998
1014
  }
@@ -1119,6 +1135,46 @@ class Ninetailed {
1119
1135
  get pluginsWithCustomComponentViewThreshold() {
1120
1136
  return [this.ninetailedCorePlugin, ...this.plugins].filter(plugin => hasComponentViewTrackingThreshold(plugin));
1121
1137
  }
1138
+ logInvalidElement(element) {
1139
+ const isObject = typeof element === 'object' && element !== null;
1140
+ const constructorName = isObject ? element.constructor.name : '';
1141
+ const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
1142
+ 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.`);
1143
+ }
1144
+ storeElementPayload(element, payload) {
1145
+ const existingPayloads = this.observedElements.get(element) || [];
1146
+
1147
+ // Check if the payload is already being observed for this element
1148
+ const isPayloadAlreadyObserved = existingPayloads.some(existingPayload => JSON.stringify(existingPayload) === JSON.stringify(payload));
1149
+ if (isPayloadAlreadyObserved) {
1150
+ return;
1151
+ }
1152
+
1153
+ // Store the new or updated payloads
1154
+ this.observedElements.set(element, [...existingPayloads, payload]);
1155
+ }
1156
+ setupElementObservation(element, delay) {
1157
+ // Get all relevant delays from plugins and the custom delay
1158
+ const pluginDelays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
1159
+
1160
+ // Ensure we only observe each delay once
1161
+ const uniqueDelays = Array.from(new Set([...pluginDelays, delay]));
1162
+
1163
+ // Set up observation for each delay
1164
+ uniqueDelays.forEach(delay => {
1165
+ this.elementSeenObserver.observe(element, {
1166
+ delay
1167
+ });
1168
+ });
1169
+ }
1170
+ // Always throws, never returns
1171
+ handleMethodError(error, method) {
1172
+ logger.error(error);
1173
+ if (error instanceof RangeError) {
1174
+ 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.`);
1175
+ }
1176
+ throw error;
1177
+ }
1122
1178
  get profileState() {
1123
1179
  return this._profileState;
1124
1180
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninetailed/experience.js",
3
- "version": "7.11.0",
3
+ "version": "7.11.1",
4
4
  "description": "Ninetailed SDK for javascript",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,8 +9,8 @@
9
9
  "directory": "packages/sdks/javascript"
10
10
  },
11
11
  "dependencies": {
12
- "@ninetailed/experience.js-plugin-analytics": "7.11.0",
13
- "@ninetailed/experience.js-shared": "7.11.0",
12
+ "@ninetailed/experience.js-plugin-analytics": "7.11.1",
13
+ "@ninetailed/experience.js-shared": "7.11.1",
14
14
  "analytics": "0.8.1",
15
15
  "uuid": "9.0.0"
16
16
  },
@@ -71,13 +71,17 @@ export declare class Ninetailed implements NinetailedInstance {
71
71
  trackComponentView: TrackComponentView;
72
72
  private get pluginsWithCustomComponentViewThreshold();
73
73
  observeElement: (payload: ElementSeenPayload, options?: ObserveOptions) => void;
74
+ private logInvalidElement;
75
+ private storeElementPayload;
76
+ private setupElementObservation;
74
77
  unobserveElement: (element: Element) => void;
75
78
  private onElementSeen;
76
79
  reset: () => Promise<void>;
77
80
  debug: (enabled: boolean) => Promise<void>;
78
81
  onProfileChange: (cb: OnProfileChangeCallback) => import("analytics").DetachListeners;
79
82
  onSelectVariant: <Baseline extends Reference, Variant extends Reference>({ baseline, experiences }: OnSelectVariantArgs<Baseline, Variant>, cb: OnSelectVariantCallback<Baseline, Variant>) => () => void;
80
- onIsInitialized: (onIsInitialized: OnIsInitializedCallback) => void;
83
+ private handleMethodError;
84
+ onIsInitialized: (onIsInitializedCallback: OnIsInitializedCallback) => void;
81
85
  private waitUntilInitialized;
82
86
  get profileState(): ProfileState;
83
87
  private buildOptions;
@@ -1,9 +1,10 @@
1
- import { AnalyticsInstance } from 'analytics';
1
+ import { AnalyticsInstance, DetachListeners } from 'analytics';
2
2
  import { Locale, NinetailedApiClient, NinetailedRequestContext } from '@ninetailed/experience.js-shared';
3
3
  import { NinetailedAnalyticsPlugin, SanitizedElementSeenPayload } from '@ninetailed/experience.js-plugin-analytics';
4
4
  import { SET_ENABLED_FEATURES } from './constants';
5
5
  import { NinetailedInstance, FlushResult } from '../types';
6
6
  import { HAS_SEEN_STICKY_COMPONENT } from '../constants';
7
+ import { DispatchAction, ActionPayloadMap } from './actions';
7
8
  export type OnInitProfileId = (profileId?: string) => Promise<string | undefined> | string | undefined;
8
9
  type AnalyticsPluginNinetailedConfig = {
9
10
  apiClient: NinetailedApiClient;
@@ -16,14 +17,15 @@ type AnalyticsPluginNinetailedConfig = {
16
17
  export declare const PLUGIN_NAME = "ninetailed:core";
17
18
  type EventFn = {
18
19
  payload: any;
19
- instance: InternalAnalyticsInstance;
20
+ instance: EventHandlerAnalyticsInstance;
20
21
  };
21
22
  type AbortableFnParams = {
22
23
  abort: () => void;
23
24
  payload: unknown;
24
25
  };
25
- type InternalAnalyticsInstance = AnalyticsInstance & {
26
- dispatch: (action: any) => void;
26
+ export type EventHandlerAnalyticsInstance = Omit<AnalyticsInstance, 'on' | 'dispatch'> & {
27
+ dispatch: (action: DispatchAction) => Promise<void>;
28
+ on<T extends DispatchAction['type']>(action: T, handler: (data: ActionPayloadMap[T]) => void): DetachListeners;
27
29
  };
28
30
  export interface NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
29
31
  flush: (args: void) => Promise<FlushResult>;
@@ -40,7 +42,7 @@ export declare class NinetailedCorePlugin extends NinetailedAnalyticsPlugin impl
40
42
  private readonly ninetailed;
41
43
  constructor({ apiClient, locale, ninetailed, onInitProfileId, buildClientContext, }: AnalyticsPluginNinetailedConfig);
42
44
  initialize({ instance, }: {
43
- instance: InternalAnalyticsInstance;
45
+ instance: EventHandlerAnalyticsInstance;
44
46
  }): Promise<void>;
45
47
  [SET_ENABLED_FEATURES]: ({ payload }: EventFn) => Promise<void>;
46
48
  pageStart(params: AbortableFnParams): unknown;
@@ -65,6 +67,17 @@ export declare class NinetailedCorePlugin extends NinetailedAnalyticsPlugin impl
65
67
  private static abortNonClientEvents;
66
68
  private get instance();
67
69
  flush: (args: void) => Promise<FlushResult>;
70
+ private getAnonymousId;
71
+ private setAnonymousId;
72
+ private clearAnonymousId;
73
+ private setFallbackProfile;
74
+ private getFallbackProfile;
75
+ private clearFallbackProfile;
76
+ private setFallbackExperiences;
77
+ private getFallbackExperiences;
78
+ private clearFallbackExperiences;
79
+ private clearCaches;
80
+ private populateCaches;
68
81
  private _flush;
69
82
  }
70
83
  export {};
@@ -0,0 +1,57 @@
1
+ import { HAS_SEEN_COMPONENT, HAS_SEEN_ELEMENT } from '@ninetailed/experience.js-plugin-analytics';
2
+ import { PROFILE_CHANGE, Profile, SelectedVariantInfo, PROFILE_RESET } from '@ninetailed/experience.js-shared';
3
+ import { HAS_SEEN_STICKY_COMPONENT, PAGE_HIDDEN } from '../constants';
4
+ type ReadyAction = {
5
+ type: 'ready';
6
+ };
7
+ type HasSeenElementAction = {
8
+ type: typeof HAS_SEEN_ELEMENT;
9
+ seenFor: number | undefined;
10
+ element: Element;
11
+ };
12
+ type PageHiddenAction = {
13
+ type: typeof PAGE_HIDDEN;
14
+ };
15
+ type ProfileChangeAction = {
16
+ type: typeof PROFILE_CHANGE;
17
+ profile: Profile;
18
+ experiences: SelectedVariantInfo[];
19
+ error: undefined | null;
20
+ } | {
21
+ type: typeof PROFILE_CHANGE;
22
+ profile: Profile | null;
23
+ experiences: SelectedVariantInfo[];
24
+ error: Error;
25
+ };
26
+ type ProfileResetAction = {
27
+ type: typeof PROFILE_RESET;
28
+ };
29
+ type ProfileHasSeenStickyComponentAction = {
30
+ type: typeof HAS_SEEN_STICKY_COMPONENT;
31
+ componentId: string;
32
+ experienceId: string | undefined;
33
+ variantIndex: number | undefined;
34
+ };
35
+ type ProfileHasSeenComponentAction = {
36
+ type: typeof HAS_SEEN_COMPONENT;
37
+ variant: {
38
+ id: string;
39
+ };
40
+ audience: {
41
+ id: string;
42
+ };
43
+ isPersonalized: boolean;
44
+ };
45
+ export type DispatchAction = ReadyAction | ProfileChangeAction | ProfileResetAction | ProfileHasSeenStickyComponentAction | HasSeenElementAction | ProfileHasSeenComponentAction | PageHiddenAction;
46
+ type RemoveTypeField<Type> = {
47
+ [Property in keyof Type as Exclude<Property, 'type'>]: Type[Property];
48
+ };
49
+ export type ExtractPayload<T extends {
50
+ type: string;
51
+ }> = {
52
+ payload: RemoveTypeField<T>;
53
+ };
54
+ export type ActionPayloadMap = {
55
+ [A in DispatchAction as A['type']]: ExtractPayload<A>;
56
+ };
57
+ export {};
@@ -1,20 +1,25 @@
1
1
  import { ExperienceConfiguration, Profile, Reference } from '@ninetailed/experience.js-shared';
2
2
  import { NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
3
- import { ExperienceSelectionMiddleware, ExperienceSelectionMiddlewareArg } from '../types/interfaces/HasExperienceSelectionMiddleware';
4
- type MakeExperienceSelectMiddlewareArg<Baseline extends Reference, Variant extends Reference> = {
3
+ import { ExperienceSelectionMiddleware } from '../types/interfaces/HasExperienceSelectionMiddleware';
4
+ /**
5
+ * Args for creating an experience selection middleware
6
+ */
7
+ type CreateExperienceSelectionMiddlewareArg<Baseline extends Reference, Variant extends Reference> = {
5
8
  plugins: NinetailedPlugin[];
6
9
  experiences: ExperienceConfiguration<Variant>[];
7
- baseline: Reference;
10
+ baseline: Baseline;
8
11
  profile: Profile | null;
12
+ };
13
+ type MakeExperienceSelectMiddlewareArg<Baseline extends Reference, Variant extends Reference> = CreateExperienceSelectionMiddlewareArg<Baseline, Variant> & {
9
14
  onChange: (middleware: ExperienceSelectionMiddleware<Baseline, Variant>) => void;
10
15
  };
11
- export declare const makeExperienceSelectMiddleware: <TBaseline extends Reference, TVariant extends Reference>({ plugins, onChange, experiences, baseline, profile, }: MakeExperienceSelectMiddlewareArg<TBaseline, TVariant>) => {
16
+ /**
17
+ * Result of creating an experience selection middleware
18
+ */
19
+ interface ExperienceSelectMiddlewareResult<Baseline extends Reference, Variant extends Reference> {
12
20
  addListeners: () => void;
13
21
  removeListeners: () => void;
14
- middleware: ({ experience, variant, variantIndex, }: ExperienceSelectionMiddlewareArg<TBaseline, TVariant>) => {
15
- experience: ExperienceConfiguration<TVariant> | null;
16
- variant: TBaseline | TVariant | import("@ninetailed/experience.js-shared").VariantRef;
17
- variantIndex: number;
18
- };
19
- };
22
+ middleware: ExperienceSelectionMiddleware<Baseline, Variant>;
23
+ }
24
+ export declare const makeExperienceSelectMiddleware: <Baseline extends Reference, Variant extends Reference>({ plugins, onChange, experiences, baseline, profile, }: MakeExperienceSelectMiddlewareArg<Baseline, Variant>) => ExperienceSelectMiddlewareResult<Baseline, Variant>;
20
25
  export {};
@@ -4,27 +4,25 @@ import { ElementSeenPayload, NinetailedPlugin, TrackComponentProperties } from '
4
4
  import { type Ninetailed } from '../Ninetailed';
5
5
  import { OnSelectVariant } from './OnSelectVariant';
6
6
  import { EventBuilder } from '../utils/EventBuilder';
7
- type Loading = {
7
+ export type ProfileState = {
8
8
  status: 'loading';
9
9
  profile: null;
10
10
  experiences: null;
11
11
  error: null;
12
- };
13
- type Success = {
12
+ from: 'api' | 'hydrated';
13
+ } | {
14
14
  status: 'success';
15
15
  profile: Profile;
16
16
  experiences: SelectedVariantInfo[];
17
17
  error: null;
18
- };
19
- type Fail = {
18
+ from: 'api' | 'hydrated';
19
+ } | {
20
20
  status: 'error';
21
- profile: null;
22
- experiences: null;
21
+ profile: Profile | null;
22
+ experiences: SelectedVariantInfo[] | null;
23
23
  error: Error;
24
- };
25
- export type ProfileState = {
26
24
  from: 'api' | 'hydrated';
27
- } & (Loading | Success | Fail);
25
+ };
28
26
  export type OnIsInitializedCallback = () => void;
29
27
  export type OnIsInitialized = (cb: OnIsInitializedCallback) => void;
30
28
  export type EventFunctionOptions = {
@@ -36,6 +34,13 @@ export type EventFunctionOptions = {
36
34
  export type FlushResult = {
37
35
  success: boolean;
38
36
  };
37
+ export type Result<T> = {
38
+ success: true;
39
+ data: T;
40
+ } | {
41
+ success: false;
42
+ error: Error;
43
+ };
39
44
  export type OnProfileChangeCallback = (profile: ProfileState) => void;
40
45
  export type Page = (data?: Partial<PageviewProperties>, options?: EventFunctionOptions) => Promise<FlushResult>;
41
46
  export type Track = (event: string, properties?: Properties, options?: EventFunctionOptions) => Promise<FlushResult>;
@@ -1,8 +1,13 @@
1
1
  import { BuildPageEventArgs, BuildTrackEventArgs, IdentifyEventArgs, NinetailedRequestContext, Properties } from '@ninetailed/experience.js-shared';
2
+ type PageData = Partial<Omit<BuildPageEventArgs, 'ctx' | 'properties'>>;
3
+ type TrackData = Partial<Omit<BuildTrackEventArgs, 'ctx' | 'event' | 'properties'>>;
4
+ type IdentifyData = Partial<Omit<IdentifyEventArgs, 'ctx' | 'userId' | 'traits'>>;
5
+ type ComponentData = Partial<Omit<BuildPageEventArgs, 'ctx' | 'componentId' | 'experienceId' | 'variantIndex'>>;
2
6
  export declare class EventBuilder {
3
7
  private readonly buildRequestContext;
4
8
  constructor(buildRequestContext?: () => NinetailedRequestContext);
5
- page(properties?: Properties, data?: Partial<Omit<BuildPageEventArgs, 'ctx' | 'properties'>>): {
9
+ private buildEventBase;
10
+ page(properties?: Properties, data?: PageData): {
6
11
  channel: import("@ninetailed/experience.js-shared").EventChanel;
7
12
  context: {
8
13
  app?: {
@@ -56,7 +61,7 @@ export declare class EventBuilder {
56
61
  name?: string | undefined;
57
62
  properties: import("@ninetailed/experience.js-shared").PageviewProperties;
58
63
  };
59
- track(event: string, properties?: Properties, data?: Partial<Omit<BuildTrackEventArgs, 'ctx' | 'event' | 'properties'>>): {
64
+ track(event: string, properties?: Properties, data?: TrackData): {
60
65
  channel: import("@ninetailed/experience.js-shared").EventChanel;
61
66
  context: {
62
67
  app?: {
@@ -110,7 +115,7 @@ export declare class EventBuilder {
110
115
  event: string;
111
116
  properties: Properties;
112
117
  };
113
- identify(userId: string, traits?: Properties, data?: Partial<Omit<IdentifyEventArgs, 'ctx' | 'userId' | 'traits'>>): {
118
+ identify(userId: string, traits?: Properties, data?: IdentifyData): {
114
119
  channel: import("@ninetailed/experience.js-shared").EventChanel;
115
120
  context: {
116
121
  app?: {
@@ -163,7 +168,7 @@ export declare class EventBuilder {
163
168
  userId?: string | undefined;
164
169
  traits: Properties;
165
170
  };
166
- component(componentId: string, experienceId?: string, variantIndex?: number, data?: Partial<Omit<BuildPageEventArgs, 'ctx' | 'componentId' | 'experienceId' | 'variantIndex'>>): {
171
+ component(componentId: string, experienceId?: string, variantIndex?: number, data?: ComponentData): {
167
172
  channel: import("@ninetailed/experience.js-shared").EventChanel;
168
173
  context: {
169
174
  app?: {
@@ -219,3 +224,4 @@ export declare class EventBuilder {
219
224
  variantIndex?: number | undefined;
220
225
  };
221
226
  }
227
+ export {};