@ninetailed/experience.js 7.5.3 → 7.6.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/index.cjs.d.ts +1 -0
  2. package/{index.cjs → index.cjs.js} +697 -375
  3. package/index.esm.js +1203 -0
  4. package/package.json +6 -9
  5. package/src/index.d.ts +1 -1
  6. package/src/lib/ElementSeenObserver.d.ts +1 -1
  7. package/src/lib/Ninetailed.d.ts +19 -5
  8. package/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.d.ts +69 -0
  9. package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/index.d.ts +1 -1
  10. package/src/lib/constants.d.ts +1 -2
  11. package/src/lib/experience/makeExperienceSelectMiddleware.d.ts +9 -9
  12. package/src/lib/guards/hasComponentViewTrackingThreshold.d.ts +3 -0
  13. package/src/lib/guards/hasExperienceSelectionMiddleware.d.ts +1 -1
  14. package/src/lib/plugins/selectPluginsHavingExperienceSelectionMiddleware.d.ts +2 -2
  15. package/src/lib/plugins/selectPluginsHavingOnChangeEmitter.d.ts +1 -1
  16. package/src/lib/types/OnSelectVariant.d.ts +51 -0
  17. package/src/lib/types/index.d.ts +9 -9
  18. package/src/lib/types/interfaces/HasComponentViewTrackingThreshold.d.ts +3 -0
  19. package/src/lib/types/interfaces/HasExperienceSelectionMiddleware.d.ts +10 -10
  20. package/src/lib/types/interfaces/InterestedInHiddenPage.d.ts +1 -1
  21. package/src/lib/types/interfaces/InterestedInProfileChange.d.ts +1 -1
  22. package/src/lib/types/interfaces/InterestedInSeenElements.d.ts +1 -3
  23. package/src/lib/utils/EventBuilder.d.ts +221 -0
  24. package/src/lib/utils/noop.d.ts +1 -0
  25. package/index.js +0 -886
  26. package/src/lib/ninetailedCorePlugin/ninetailedCorePlugin.d.ts +0 -17
  27. package/src/lib/test-helpers/intersection-observer-test-helper.d.ts +0 -2
  28. package/src/lib/types/ElementSeenPayload.d.ts +0 -82
  29. package/src/lib/types/EventHandler.d.ts +0 -6
  30. package/src/lib/types/NinetailedPlugin.d.ts +0 -11
  31. package/src/lib/types/TrackingProperties.d.ts +0 -35
  32. /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/build-context.d.ts +0 -0
  33. /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/build-locale.d.ts +0 -0
  34. /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/Events/index.d.ts +0 -0
  35. /package/src/lib/{ninetailedCorePlugin → NinetailedCorePlugin}/constants.d.ts +0 -0
@@ -3,18 +3,18 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var experience_jsShared = require('@ninetailed/experience.js-shared');
6
+ var experience_jsPluginAnalytics = require('@ninetailed/experience.js-plugin-analytics');
6
7
  var Analytics = require('analytics');
7
- var zod = require('zod');
8
+ var uuid = require('uuid');
8
9
 
9
10
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
10
11
 
11
12
  var Analytics__default = /*#__PURE__*/_interopDefaultLegacy(Analytics);
12
13
 
13
- const HAS_SEEN_COMPONENT = 'has_seen_component';
14
- const HAS_SEEN_ELEMENT = 'has_seen_element';
15
14
  const COMPONENT = 'component';
16
15
  const COMPONENT_START = 'componentStart';
17
16
  const PAGE_HIDDEN = 'page_hidden';
17
+ const HAS_SEEN_STICKY_COMPONENT = 'sticky_component_view';
18
18
 
19
19
  /******************************************************************************
20
20
  Copyright (c) Microsoft Corporation.
@@ -104,98 +104,87 @@ const CONSENT = '__nt-consent__';
104
104
  const SET_ENABLED_FEATURES = 'set-enabled-features';
105
105
  const EMPTY_MERGE_ID = 'nt:empty-merge-id';
106
106
 
107
- const PLUGIN_NAME = 'ninetailed';
107
+ var _a;
108
+ const PLUGIN_NAME = 'ninetailed:core';
108
109
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
109
- const ninetailedCorePlugin = ({
110
- apiClient,
111
- locale,
112
- ninetailed,
113
- onInitProfileId,
114
- buildClientContext
115
- }) => {
116
- let _instance;
117
- let queue = [];
118
- let enabledFeatures = Object.values(experience_jsShared.FEATURES);
119
- const buildContext = buildClientContext || buildClientNinetailedRequestContext;
120
- const flush = () => __awaiter(void 0, void 0, void 0, function* () {
121
- const events = Object.assign([], queue);
122
- experience_jsShared.logger.info('Start flushing events.');
123
- queue = [];
124
- if (!events.length) {
125
- return {
126
- success: true
127
- };
128
- }
129
- try {
130
- const anonymousId = _instance.storage.getItem(ANONYMOUS_ID);
131
- const {
132
- profile,
133
- experiences
134
- } = yield apiClient.upsertProfile({
135
- profileId: anonymousId,
136
- events
137
- }, {
138
- locale,
139
- enabledFeatures
140
- });
141
- _instance.storage.setItem(ANONYMOUS_ID, profile.id);
142
- _instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
143
- _instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
144
- experience_jsShared.logger.debug('Profile from api: ', profile);
145
- experience_jsShared.logger.debug('Experiences from api: ', experiences);
146
- _instance.dispatch({
147
- type: PROFILE_CHANGE,
148
- profile,
149
- experiences
150
- });
151
- yield delay(20);
152
- return {
153
- success: true
154
- };
155
- } catch (error) {
156
- experience_jsShared.logger.debug('An error occurred during flushing the events: ', error);
157
- const fallbackProfile = _instance.storage.getItem(PROFILE_FALLBACK_CACHE);
158
- const fallbackExperiences = _instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
159
- if (fallbackProfile) {
160
- experience_jsShared.logger.debug('Found a fallback profile - will use this.');
161
- _instance.dispatch({
162
- type: PROFILE_CHANGE,
163
- profile: fallbackProfile,
164
- experiences: fallbackExperiences
165
- });
166
- } else {
167
- experience_jsShared.logger.debug('No fallback profile found - setting profile to null.');
168
- _instance.dispatch({
169
- type: PROFILE_CHANGE,
170
- profile: null,
171
- experiences: fallbackExperiences,
172
- error
110
+ class NinetailedCorePlugin extends experience_jsPluginAnalytics.NinetailedAnalyticsPlugin {
111
+ constructor({
112
+ apiClient,
113
+ locale,
114
+ ninetailed,
115
+ onInitProfileId,
116
+ buildClientContext
117
+ }) {
118
+ super();
119
+ this.name = PLUGIN_NAME;
120
+ this.queue = [];
121
+ this.enabledFeatures = Object.values(experience_jsShared.FEATURES);
122
+ this.buildContext = buildClientNinetailedRequestContext;
123
+ this[_a] = ({
124
+ payload
125
+ }) => __awaiter(this, void 0, void 0, function* () {
126
+ const ctx = this.buildContext();
127
+ return this.enqueueEvent(experience_jsShared.buildComponentViewEvent({
128
+ messageId: payload.meta.rid,
129
+ timestamp: payload.meta.ts,
130
+ componentId: payload.componentId,
131
+ experienceId: payload.experienceId,
132
+ variantIndex: payload.variantIndex,
133
+ ctx
134
+ }));
135
+ });
136
+ this.methods = {
137
+ reset: (...args) => __awaiter(this, void 0, void 0, function* () {
138
+ experience_jsShared.logger.debug('Resetting profile.');
139
+ const instance = args[args.length - 1];
140
+ instance.dispatch({
141
+ type: PROFILE_RESET
173
142
  });
174
- }
175
- return {
176
- success: false
177
- };
143
+ instance.storage.removeItem(ANONYMOUS_ID);
144
+ instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
145
+ instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
146
+ experience_jsShared.logger.debug('Removed old profile data from localstorage.');
147
+ if (typeof this.onInitProfileId === 'function') {
148
+ const profileId = yield this.onInitProfileId(undefined);
149
+ if (typeof profileId === 'string') {
150
+ instance.storage.setItem(ANONYMOUS_ID, profileId);
151
+ }
152
+ }
153
+ yield this.ninetailed.track('nt_reset');
154
+ experience_jsShared.logger.info('Profile reset successful.');
155
+ yield delay(10);
156
+ }),
157
+ debug: (...args) => __awaiter(this, void 0, void 0, function* () {
158
+ const enabled = args[0];
159
+ const instance = args[args.length - 1];
160
+ const consoleLogSink = new experience_jsShared.ConsoleLogSink();
161
+ if (enabled) {
162
+ instance.storage.setItem(DEBUG_FLAG, true);
163
+ experience_jsShared.logger.addSink(consoleLogSink);
164
+ experience_jsShared.logger.info('Debug mode enabled.');
165
+ } else {
166
+ instance.storage.removeItem(DEBUG_FLAG);
167
+ experience_jsShared.logger.info('Debug mode disabled.');
168
+ experience_jsShared.logger.removeSink(consoleLogSink.name);
169
+ }
170
+ })
171
+ };
172
+ this.flush = asyncThrottle(this._flush.bind(this));
173
+ if (onInitProfileId) {
174
+ this.onInitProfileId = onInitProfileId;
178
175
  }
179
- });
180
- const enqueueEvent = event => __awaiter(void 0, void 0, void 0, function* () {
181
- queue = experience_jsShared.unionBy([event], queue, 'messageId');
182
- });
183
- const abortNonClientEvents = ({
184
- abort,
185
- payload
186
- }) => {
187
- if (typeof window !== 'object') {
188
- return abort();
176
+ if (buildClientContext) {
177
+ this.buildContext = buildClientContext;
189
178
  }
190
- return payload;
191
- };
192
- return {
193
- name: 'ninetailed',
194
- config: {},
195
- initialize: ({
196
- instance
197
- }) => __awaiter(void 0, void 0, void 0, function* () {
198
- _instance = instance;
179
+ this.apiClient = apiClient;
180
+ this.ninetailed = ninetailed;
181
+ this.locale = locale;
182
+ }
183
+ initialize({
184
+ instance
185
+ }) {
186
+ return __awaiter(this, void 0, void 0, function* () {
187
+ this._instance = instance;
199
188
  if (instance.storage.getItem(DEBUG_FLAG)) {
200
189
  experience_jsShared.logger.addSink(new experience_jsShared.ConsoleLogSink());
201
190
  experience_jsShared.logger.info('Ninetailed Debug Mode is enabled.');
@@ -207,8 +196,8 @@ const ninetailedCorePlugin = ({
207
196
  instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
208
197
  instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
209
198
  }
210
- if (typeof onInitProfileId === 'function') {
211
- const profileId = yield onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
199
+ if (typeof this.onInitProfileId === 'function') {
200
+ const profileId = yield this.onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
212
201
  if (typeof profileId === 'string') {
213
202
  instance.storage.setItem(ANONYMOUS_ID, profileId);
214
203
  }
@@ -216,109 +205,181 @@ const ninetailedCorePlugin = ({
216
205
  instance.on(SET_ENABLED_FEATURES, ({
217
206
  payload
218
207
  }) => {
219
- enabledFeatures = payload.features || [];
208
+ this.enabledFeatures = payload.features || [];
220
209
  });
221
210
  experience_jsShared.logger.debug('Ninetailed Core plugin initialized.');
222
- }),
223
- flush: asyncThrottle(flush),
224
- pageStart: params => {
225
- return abortNonClientEvents(params);
226
- },
227
- page: ({
228
- payload
229
- }) => __awaiter(void 0, void 0, void 0, function* () {
211
+ });
212
+ }
213
+ pageStart(params) {
214
+ return this.abortNonClientEvents(params);
215
+ }
216
+ page({
217
+ payload
218
+ }) {
219
+ return __awaiter(this, void 0, void 0, function* () {
230
220
  experience_jsShared.logger.info('Sending Page event.');
231
- const ctx = buildContext();
232
- return enqueueEvent(experience_jsShared.buildPageEvent({
221
+ const ctx = this.buildContext();
222
+ return this.enqueueEvent(experience_jsShared.buildPageEvent({
233
223
  messageId: payload.meta.rid,
234
224
  timestamp: payload.meta.ts,
235
225
  properties: payload.properties,
236
226
  ctx
237
227
  }));
238
- }),
239
- trackStart: params => {
240
- return abortNonClientEvents(params);
241
- },
242
- track: ({
243
- payload
244
- }) => __awaiter(void 0, void 0, void 0, function* () {
228
+ });
229
+ }
230
+ trackStart(params) {
231
+ return this.abortNonClientEvents(params);
232
+ }
233
+ track({
234
+ payload
235
+ }) {
236
+ return __awaiter(this, void 0, void 0, function* () {
245
237
  experience_jsShared.logger.info('Sending Track event.');
246
- const ctx = buildContext();
247
- return enqueueEvent(experience_jsShared.buildTrackEvent({
238
+ const ctx = this.buildContext();
239
+ return this.enqueueEvent(experience_jsShared.buildTrackEvent({
248
240
  messageId: payload.meta.rid,
249
241
  timestamp: payload.meta.ts,
250
242
  event: payload.event,
251
243
  properties: payload.properties,
252
244
  ctx
253
245
  }));
254
- }),
255
- identifyStart: params => {
256
- return abortNonClientEvents(params);
257
- },
258
- identify: ({
259
- payload
260
- }) => __awaiter(void 0, void 0, void 0, function* () {
246
+ });
247
+ }
248
+ identifyStart(params) {
249
+ return this.abortNonClientEvents(params);
250
+ }
251
+ identify({
252
+ payload
253
+ }) {
254
+ return __awaiter(this, void 0, void 0, function* () {
261
255
  experience_jsShared.logger.info('Sending Identify event.');
262
- const ctx = buildContext();
256
+ const ctx = this.buildContext();
263
257
  if (payload.userId === EMPTY_MERGE_ID && (!payload.traits || typeof payload.traits === 'object' && Object.keys(payload.traits).length === 0)) {
264
258
  experience_jsShared.logger.info('Skipping Identify event as no userId and no traits are set.');
265
259
  return;
266
260
  }
267
- return enqueueEvent(experience_jsShared.buildIdentifyEvent({
261
+ return this.enqueueEvent(experience_jsShared.buildIdentifyEvent({
268
262
  messageId: payload.meta.rid,
269
263
  timestamp: payload.meta.ts,
270
264
  traits: payload.traits,
271
265
  userId: payload.userId === EMPTY_MERGE_ID ? '' : payload.userId,
272
266
  ctx
273
267
  }));
274
- }),
275
- setItemStart: ({
276
- abort,
277
- payload
278
- }) => {
279
- if (![ANONYMOUS_ID, DEBUG_FLAG, PROFILE_FALLBACK_CACHE, EXPERIENCES_FALLBACK_CACHE, CONSENT].includes(payload.key)) {
280
- return abort();
268
+ });
269
+ }
270
+ getComponentViewTrackingThreshold() {
271
+ return 0;
272
+ }
273
+ onTrackExperience(properties) {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ if (properties.experience.sticky) {
276
+ yield this.instance.dispatch({
277
+ type: HAS_SEEN_STICKY_COMPONENT,
278
+ componentId: properties.selectedVariant.id,
279
+ experienceId: properties.experience.id,
280
+ variantIndex: properties.selectedVariantIndex
281
+ });
282
+ this.flush();
281
283
  }
282
- return payload;
283
- },
284
- methods: {
285
- reset: (...args) => __awaiter(void 0, void 0, void 0, function* () {
286
- experience_jsShared.logger.debug('Resetting profile.');
287
- const instance = args[args.length - 1];
288
- instance.dispatch({
289
- type: PROFILE_RESET
284
+ return Promise.resolve();
285
+ });
286
+ }
287
+ onTrackComponent() {
288
+ return Promise.resolve();
289
+ }
290
+ setItemStart({
291
+ abort,
292
+ payload
293
+ }) {
294
+ if (![ANONYMOUS_ID, DEBUG_FLAG, PROFILE_FALLBACK_CACHE, EXPERIENCES_FALLBACK_CACHE, CONSENT].includes(payload.key)) {
295
+ return abort();
296
+ }
297
+ return payload;
298
+ }
299
+ enqueueEvent(event) {
300
+ return __awaiter(this, void 0, void 0, function* () {
301
+ this.queue = experience_jsShared.unionBy([event], this.queue, 'messageId');
302
+ });
303
+ }
304
+ abortNonClientEvents({
305
+ abort,
306
+ payload
307
+ }) {
308
+ if (typeof window !== 'object') {
309
+ return abort();
310
+ }
311
+ return payload;
312
+ }
313
+ get instance() {
314
+ if (!this._instance) {
315
+ throw new Error('Ninetailed Core plugin not initialized.');
316
+ }
317
+ return this._instance;
318
+ }
319
+ _flush() {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const events = Object.assign([], this.queue);
322
+ experience_jsShared.logger.info('Start flushing events.');
323
+ this.queue = [];
324
+ if (!events.length) {
325
+ return {
326
+ success: true
327
+ };
328
+ }
329
+ try {
330
+ const anonymousId = this.instance.storage.getItem(ANONYMOUS_ID);
331
+ const {
332
+ profile,
333
+ experiences
334
+ } = yield this.apiClient.upsertProfile({
335
+ profileId: anonymousId,
336
+ events
337
+ }, {
338
+ locale: this.locale,
339
+ enabledFeatures: this.enabledFeatures
290
340
  });
291
- instance.storage.removeItem(ANONYMOUS_ID);
292
- instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
293
- instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
294
- experience_jsShared.logger.debug('Removed old profile data from localstorage.');
295
- if (typeof onInitProfileId === 'function') {
296
- const profileId = yield onInitProfileId(undefined);
297
- if (typeof profileId === 'string') {
298
- instance.storage.setItem(ANONYMOUS_ID, profileId);
299
- }
300
- }
301
- yield ninetailed.track('nt_reset');
302
- experience_jsShared.logger.info('Profile reset successful.');
303
- yield delay(10);
304
- }),
305
- debug: (...args) => __awaiter(void 0, void 0, void 0, function* () {
306
- const enabled = args[0];
307
- const instance = args[args.length - 1];
308
- const consoleLogSink = new experience_jsShared.ConsoleLogSink();
309
- if (enabled) {
310
- instance.storage.setItem(DEBUG_FLAG, true);
311
- experience_jsShared.logger.addSink(consoleLogSink);
312
- experience_jsShared.logger.info('Debug mode enabled.');
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);
344
+ experience_jsShared.logger.debug('Profile from api: ', profile);
345
+ experience_jsShared.logger.debug('Experiences from api: ', experiences);
346
+ this.instance.dispatch({
347
+ type: PROFILE_CHANGE,
348
+ profile,
349
+ experiences
350
+ });
351
+ yield delay(20);
352
+ return {
353
+ success: true
354
+ };
355
+ } catch (error) {
356
+ 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) || [];
359
+ if (fallbackProfile) {
360
+ experience_jsShared.logger.debug('Found a fallback profile - will use this.');
361
+ this.instance.dispatch({
362
+ type: PROFILE_CHANGE,
363
+ profile: fallbackProfile,
364
+ experiences: fallbackExperiences
365
+ });
313
366
  } else {
314
- instance.storage.removeItem(DEBUG_FLAG);
315
- experience_jsShared.logger.info('Debug mode disabled.');
316
- experience_jsShared.logger.removeSink(consoleLogSink.name);
367
+ experience_jsShared.logger.debug('No fallback profile found - setting profile to null.');
368
+ this.instance.dispatch({
369
+ type: PROFILE_CHANGE,
370
+ profile: null,
371
+ experiences: fallbackExperiences,
372
+ error
373
+ });
317
374
  }
318
- })
319
- }
320
- };
321
- };
375
+ return {
376
+ success: false
377
+ };
378
+ }
379
+ });
380
+ }
381
+ }
382
+ _a = HAS_SEEN_STICKY_COMPONENT;
322
383
 
323
384
  class ElementSeenObserver {
324
385
  constructor(_options) {
@@ -336,23 +397,29 @@ class ElementSeenObserver {
336
397
  target
337
398
  } = entry;
338
399
  if (isIntersecting) {
339
- const delay = this._elementDelays.get(target);
340
- const timeOut = window.setTimeout(() => {
341
- this._options.onElementSeen(target);
342
- }, delay);
343
- this._intersectionTimers.set(target, timeOut);
400
+ const delays = this._elementDelays.get(target);
401
+ delays === null || delays === void 0 ? void 0 : delays.forEach(delay => {
402
+ const timeOut = window.setTimeout(() => {
403
+ this._options.onElementSeen(target, delay);
404
+ }, delay);
405
+ const currentTimers = this._intersectionTimers.get(target) || [];
406
+ this._intersectionTimers.set(target, [...currentTimers, timeOut]);
407
+ });
344
408
  } else {
345
- const timeOut = this._intersectionTimers.get(target);
346
- if (typeof timeOut !== 'undefined') {
347
- window.clearTimeout(timeOut);
348
- }
409
+ const timeOuts = this._intersectionTimers.get(target);
410
+ timeOuts === null || timeOuts === void 0 ? void 0 : timeOuts.forEach(timeOut => {
411
+ if (typeof timeOut !== 'undefined') {
412
+ window.clearTimeout(timeOut);
413
+ }
414
+ });
349
415
  }
350
416
  });
351
417
  }
352
418
  observe(element, options) {
353
- var _a, _b;
354
- this._elementDelays.set(element, (_a = options === null || options === void 0 ? void 0 : options.delay) !== null && _a !== void 0 ? _a : 2000);
355
- (_b = this._intersectionObserver) === null || _b === void 0 ? void 0 : _b.observe(element);
419
+ var _a;
420
+ const delays = this._elementDelays.get(element) || [];
421
+ this._elementDelays.set(element, Array.from(new Set([...delays, (options === null || options === void 0 ? void 0 : options.delay) || 0])));
422
+ (_a = this._intersectionObserver) === null || _a === void 0 ? void 0 : _a.observe(element);
356
423
  }
357
424
  unobserve(element) {
358
425
  var _a;
@@ -368,6 +435,193 @@ const isInterestedInHiddenPage = arg => {
368
435
  return typeof arg === 'object' && arg !== null && PAGE_HIDDEN in arg && typeof arg[PAGE_HIDDEN] === 'function';
369
436
  };
370
437
 
438
+ const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
439
+ return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
440
+ const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
441
+ const variantIndex = parseInt(_variantIndex);
442
+ if (!experienceId || !variantIndex) {
443
+ return null;
444
+ }
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
+ }), {});
452
+ };
453
+
454
+ class OnChangeEmitter {
455
+ constructor() {
456
+ this.onChangeListeners = [];
457
+ }
458
+ addListener(listener) {
459
+ this.onChangeListeners.push(listener);
460
+ return () => {
461
+ this.removeOnChangeListener(listener);
462
+ };
463
+ }
464
+ invokeListeners() {
465
+ this.onChangeListeners.forEach(listener => listener());
466
+ }
467
+ removeOnChangeListener(listener) {
468
+ this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
469
+ }
470
+ }
471
+
472
+ const hasOnChangeEmitter = arg => {
473
+ return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
474
+ };
475
+
476
+ const selectPluginsHavingOnChangeEmitter = plugins => {
477
+ const filteredPlugins = [];
478
+ for (const plugin of plugins) {
479
+ if (hasOnChangeEmitter(plugin)) {
480
+ filteredPlugins.push(plugin);
481
+ }
482
+ }
483
+ return filteredPlugins;
484
+ };
485
+
486
+ const hasExperienceSelectionMiddleware = arg => {
487
+ return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
488
+ };
489
+
490
+ const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
491
+ const filteredPlugins = [];
492
+ for (const plugin of plugins) {
493
+ if (hasExperienceSelectionMiddleware(plugin)) {
494
+ filteredPlugins.push(plugin);
495
+ }
496
+ }
497
+ return filteredPlugins;
498
+ };
499
+
500
+ const createPassThroughMiddleware = () => {
501
+ return ({
502
+ experience,
503
+ variant,
504
+ variantIndex
505
+ }) => {
506
+ return {
507
+ experience,
508
+ variant,
509
+ variantIndex
510
+ };
511
+ };
512
+ };
513
+ const makeExperienceSelectMiddleware = ({
514
+ plugins,
515
+ onChange,
516
+ experiences,
517
+ baseline,
518
+ profile
519
+ }) => {
520
+ let removeChangeListeners = [];
521
+ 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
+ }));
531
+ return experience_jsShared.pipe(...middlewareFunctions);
532
+ };
533
+ const middleware = prepareMiddleware();
534
+ const addListeners = () => {
535
+ removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
536
+ const listener = () => {
537
+ onChange(middleware);
538
+ };
539
+ return plugin.onChangeEmitter.addListener(listener);
540
+ });
541
+ };
542
+ const removeListeners = () => {
543
+ removeChangeListeners.forEach(listener => listener());
544
+ };
545
+ return {
546
+ addListeners,
547
+ removeListeners,
548
+ middleware
549
+ };
550
+ };
551
+
552
+ class EventBuilder {
553
+ constructor(buildRequestContext) {
554
+ this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
555
+ }
556
+ page(properties, data) {
557
+ return experience_jsShared.buildPageEvent(Object.assign(Object.assign({
558
+ messageId: (data === null || data === void 0 ? void 0 : data.messageId) || uuid.v4()
559
+ }, data), {
560
+ timestamp: Date.now(),
561
+ properties: properties || {},
562
+ ctx: this.buildRequestContext()
563
+ }));
564
+ }
565
+ 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), {
570
+ event,
571
+ properties: properties || {},
572
+ ctx: this.buildRequestContext()
573
+ }));
574
+ }
575
+ 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), {
580
+ traits: traits || {},
581
+ userId: userId || '',
582
+ ctx: this.buildRequestContext()
583
+ }));
584
+ }
585
+ 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), {
590
+ componentId,
591
+ experienceId: experienceId || '',
592
+ variantIndex: variantIndex || 0,
593
+ ctx: this.buildRequestContext()
594
+ }));
595
+ }
596
+ }
597
+
598
+ const hasComponentViewTrackingThreshold = arg => {
599
+ return typeof arg === 'object' && arg !== null && 'getComponentViewTrackingThreshold' in arg && typeof arg['getComponentViewTrackingThreshold'] === 'function';
600
+ };
601
+
602
+ const buildOverrideMiddleware = experienceSelectionMiddleware => _a => {
603
+ var {
604
+ experience: originalExperience,
605
+ variant: originalVariant,
606
+ variantIndex: originalVariantIndex
607
+ } = _a,
608
+ other = __rest(_a, ["experience", "variant", "variantIndex"]);
609
+ const {
610
+ experience,
611
+ variant,
612
+ variantIndex
613
+ } = experienceSelectionMiddleware({
614
+ experience: originalExperience,
615
+ variant: originalVariant,
616
+ variantIndex: originalVariantIndex
617
+ });
618
+ return Object.assign(Object.assign({}, other), {
619
+ audience: (experience === null || experience === void 0 ? void 0 : experience.audience) ? experience.audience : null,
620
+ experience,
621
+ variant,
622
+ variantIndex
623
+ });
624
+ };
371
625
  class Ninetailed {
372
626
  constructor(ninetailedApiClientInstanceOrOptions, {
373
627
  plugins,
@@ -379,7 +633,8 @@ class Ninetailed {
379
633
  buildClientContext,
380
634
  onInitProfileId,
381
635
  componentViewTrackingThreshold = 2000,
382
- storageImpl
636
+ storageImpl,
637
+ useClientSideEvaluation = false
383
638
  } = {}) {
384
639
  this.isInitialized = false;
385
640
  this.page = (data, options) => __awaiter(this, void 0, void 0, function* () {
@@ -416,18 +671,90 @@ class Ninetailed {
416
671
  throw error;
417
672
  }
418
673
  });
674
+ this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
675
+ try {
676
+ const result = experience_jsShared.Traits.default({}).safeParse(traits);
677
+ if (!result.success) {
678
+ throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
679
+ }
680
+ yield this.waitUntilInitialized();
681
+ yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
682
+ return this.ninetailedCorePlugin.flush();
683
+ } 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;
689
+ }
690
+ });
691
+ this.batch = events => __awaiter(this, void 0, void 0, function* () {
692
+ try {
693
+ yield this.waitUntilInitialized();
694
+ const promises = events.map(event => {
695
+ if (experience_jsShared.isPageViewEvent(event)) {
696
+ return this.instance.page(event.properties);
697
+ }
698
+ if (experience_jsShared.isTrackEvent(event)) {
699
+ return this.instance.track(event.event, event.properties);
700
+ }
701
+ if (experience_jsShared.isIdentifyEvent(event)) {
702
+ return this.instance.identify(event.userId || EMPTY_MERGE_ID, event.traits);
703
+ }
704
+ if (experience_jsShared.isComponentViewEvent(event)) {
705
+ return this.instance.dispatch({
706
+ experienceId: event.experienceId,
707
+ componentId: event.componentId,
708
+ variantIndex: event.variantIndex,
709
+ type: HAS_SEEN_STICKY_COMPONENT
710
+ });
711
+ }
712
+ return Promise.resolve();
713
+ });
714
+ yield Promise.all(promises);
715
+ return this.ninetailedCorePlugin.flush();
716
+ } 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;
722
+ }
723
+ });
724
+ this.trackStickyComponentView = ({
725
+ experienceId,
726
+ componentId,
727
+ variantIndex
728
+ }) => __awaiter(this, void 0, void 0, function* () {
729
+ try {
730
+ yield this.waitUntilInitialized();
731
+ yield this.instance.dispatch({
732
+ experienceId,
733
+ componentId,
734
+ variantIndex,
735
+ type: HAS_SEEN_STICKY_COMPONENT
736
+ });
737
+ return this.ninetailedCorePlugin.flush();
738
+ } 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;
744
+ }
745
+ });
419
746
  /**
420
747
  * @deprecated The legacy datamodel is not recommended anymore
421
748
  * Will be removed in the next version of the SDK
422
749
  */
423
750
  this.trackHasSeenComponent = properties => __awaiter(this, void 0, void 0, function* () {
424
751
  return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
425
- type: HAS_SEEN_COMPONENT
752
+ type: experience_jsPluginAnalytics.HAS_SEEN_COMPONENT
426
753
  }));
427
754
  });
428
755
  this.trackComponentView = properties => {
429
756
  return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
430
- type: HAS_SEEN_ELEMENT
757
+ type: experience_jsPluginAnalytics.HAS_SEEN_ELEMENT
431
758
  }));
432
759
  };
433
760
  this.observeElement = (payload, options) => {
@@ -441,54 +768,38 @@ class Ninetailed {
441
768
  const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
442
769
  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.`);
443
770
  } else {
444
- const existingPayloads = this.observedElements.get(element);
445
- if (!existingPayloads) {
446
- this.observedElements.set(element, [remaingPayload]);
447
- } else {
448
- const isPayloadAlreadyObserved = existingPayloads.some(payload => {
449
- return JSON.stringify(payload) === JSON.stringify(remaingPayload);
771
+ this.observedElements.set(element, remaingPayload);
772
+ const delays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
773
+ const uniqueDelays = Array.from(new Set([...delays, (options === null || options === void 0 ? void 0 : options.delay) || this.componentViewTrackingThreshold]));
774
+ uniqueDelays.forEach(delay => {
775
+ this.elementSeenObserver.observe(element, {
776
+ delay
450
777
  });
451
- if (isPayloadAlreadyObserved) {
452
- return;
453
- }
454
- this.observedElements.set(element, [...existingPayloads, remaingPayload]);
455
- }
456
- this.elementSeenObserver.observe(element, Object.assign({
457
- delay: this.componentViewTrackingThreshold
458
- }, options));
778
+ });
459
779
  }
460
780
  };
461
781
  this.unobserveElement = element => {
462
782
  this.observedElements.delete(element);
463
783
  this.elementSeenObserver.unobserve(element);
464
784
  };
465
- this.onElementSeen = element => {
466
- const payloads = this.observedElements.get(element);
467
- if (typeof payloads !== 'undefined') {
468
- for (const payload of payloads) {
469
- this.trackComponentView(Object.assign({
470
- element
471
- }, payload));
785
+ this.onElementSeen = (element, delay) => {
786
+ const payload = this.observedElements.get(element);
787
+ if (typeof payload !== 'undefined') {
788
+ const pluginNamesInterestedInSeenElementMessage = [...this.pluginsWithCustomComponentViewThreshold.filter(plugin => plugin.getComponentViewTrackingThreshold() === delay), ...this.plugins.filter(plugin => !hasComponentViewTrackingThreshold(plugin))].map(plugin => plugin.name);
789
+ if (pluginNamesInterestedInSeenElementMessage.length === 0) {
790
+ return;
472
791
  }
792
+ this.instance.dispatch(Object.assign(Object.assign({}, payload), {
793
+ element,
794
+ type: experience_jsPluginAnalytics.HAS_SEEN_ELEMENT,
795
+ plugins: Object.assign({
796
+ all: false
797
+ }, pluginNamesInterestedInSeenElementMessage.reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
798
+ [curr]: true
799
+ }), {}))
800
+ }));
473
801
  }
474
802
  };
475
- this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
476
- try {
477
- const result = experience_jsShared.Traits.default({}).safeParse(traits);
478
- if (!result.success) {
479
- throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
480
- }
481
- yield this.waitUntilInitialized();
482
- yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
483
- return this.ninetailedCorePlugin.flush();
484
- } catch (error) {
485
- experience_jsShared.logger.error(error);
486
- if (error instanceof RangeError) {
487
- 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.`);
488
- }
489
- throw error;
490
- }
491
- });
492
803
  this.reset = () => __awaiter(this, void 0, void 0, function* () {
493
804
  yield this.waitUntilInitialized();
494
805
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -523,6 +834,165 @@ class Ninetailed {
523
834
  }
524
835
  });
525
836
  };
837
+ this.onSelectVariant = ({
838
+ baseline,
839
+ experiences
840
+ }, cb) => {
841
+ let middlewareChangeListeners = [];
842
+ let state = null;
843
+ const removeMiddlewareChangeListeners = () => {
844
+ middlewareChangeListeners.forEach(removeListener => removeListener());
845
+ middlewareChangeListeners = [];
846
+ };
847
+ const setSelectedVariant = newState => {
848
+ state = newState;
849
+ cb(state);
850
+ };
851
+ const removeProfileChangeListener = this.onProfileChange(profileState => {
852
+ const {
853
+ addListeners,
854
+ removeListeners,
855
+ middleware: experienceSelectionMiddleware
856
+ } = makeExperienceSelectMiddleware({
857
+ plugins: this.plugins,
858
+ experiences,
859
+ baseline,
860
+ profile: profileState.profile,
861
+ onChange: middleware => {
862
+ const overrideResult = buildOverrideMiddleware(middleware);
863
+ if (state !== null) {
864
+ setSelectedVariant(overrideResult(state));
865
+ }
866
+ }
867
+ });
868
+ addListeners();
869
+ middlewareChangeListeners.push(removeListeners);
870
+ const overrideResult = buildOverrideMiddleware(experienceSelectionMiddleware);
871
+ const hasVariants = experiences.map(experience => experience_jsShared.selectHasVariants(experience, baseline)).reduce((acc, curr) => acc || curr, false);
872
+ const baseReturn = Object.assign(Object.assign({}, profileState), {
873
+ hasVariants,
874
+ baseline
875
+ });
876
+ const emptyReturn = Object.assign(Object.assign({}, baseReturn), {
877
+ experience: null,
878
+ variant: baseline,
879
+ variantIndex: 0,
880
+ audience: null,
881
+ isPersonalized: false,
882
+ profile: null,
883
+ error: null
884
+ });
885
+ if (profileState.status === 'loading') {
886
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
887
+ loading: true,
888
+ status: 'loading'
889
+ })));
890
+ return;
891
+ }
892
+ if (profileState.status === 'error') {
893
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
894
+ loading: false,
895
+ status: 'error',
896
+ error: profileState.error
897
+ })));
898
+ return;
899
+ }
900
+ const {
901
+ profile,
902
+ experiences: selectedExperiences
903
+ } = profileState;
904
+ if (!profile || !selectedExperiences) {
905
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
906
+ loading: false,
907
+ status: 'error',
908
+ error: new Error('No Profile or Selected Experiences were returned by the API')
909
+ })));
910
+ return;
911
+ }
912
+ if (this.useClientSideEvaluation) {
913
+ const _experience = experience_jsShared.selectExperience({
914
+ experiences,
915
+ profile
916
+ });
917
+ if (!_experience) {
918
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
919
+ loading: false,
920
+ status: 'success',
921
+ profile
922
+ })));
923
+ return;
924
+ }
925
+ const {
926
+ variant: _variant,
927
+ index
928
+ } = experience_jsShared.selectVariant({
929
+ baseline,
930
+ experience: _experience,
931
+ profile
932
+ });
933
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, baseReturn), {
934
+ status: 'success',
935
+ loading: false,
936
+ error: null,
937
+ experience: _experience,
938
+ variant: _variant,
939
+ variantIndex: index,
940
+ audience: _experience.audience ? _experience.audience : null,
941
+ profile,
942
+ isPersonalized: true
943
+ })));
944
+ return;
945
+ }
946
+ const experience = experiences.find(experience => selectedExperiences.some(selectedExperience => selectedExperience.experienceId === experience.id));
947
+ const selectedExperience = selectedExperiences.find(({
948
+ experienceId
949
+ }) => experienceId === (experience === null || experience === void 0 ? void 0 : experience.id));
950
+ if (!experience || !selectedExperience) {
951
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
952
+ loading: false,
953
+ status: 'success',
954
+ profile
955
+ })));
956
+ return;
957
+ }
958
+ const baselineVariants = experience_jsShared.selectBaselineWithVariants(experience, baseline);
959
+ if (!baselineVariants) {
960
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
961
+ loading: false,
962
+ status: 'success',
963
+ profile
964
+ })));
965
+ return;
966
+ }
967
+ const {
968
+ variants
969
+ } = baselineVariants;
970
+ const variant = variants[selectedExperience.variantIndex - 1];
971
+ if (!variant) {
972
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
973
+ loading: false,
974
+ status: 'success',
975
+ profile
976
+ })));
977
+ return;
978
+ }
979
+ setSelectedVariant(overrideResult(Object.assign(Object.assign({}, baseReturn), {
980
+ status: 'success',
981
+ loading: false,
982
+ error: null,
983
+ experience,
984
+ variant,
985
+ variantIndex: selectedExperience.variantIndex,
986
+ audience: experience.audience ? experience.audience : null,
987
+ profile,
988
+ isPersonalized: true
989
+ })));
990
+ });
991
+ return () => {
992
+ removeProfileChangeListener();
993
+ removeMiddlewareChangeListeners();
994
+ };
995
+ };
526
996
  this.onIsInitialized = onIsInitialized => {
527
997
  if (typeof onIsInitialized === 'function') {
528
998
  if (this.isInitialized) {
@@ -553,6 +1023,7 @@ class Ninetailed {
553
1023
  }
554
1024
  });
555
1025
  };
1026
+ this.useClientSideEvaluation = useClientSideEvaluation;
556
1027
  if (ninetailedApiClientInstanceOrOptions instanceof experience_jsShared.NinetailedApiClient) {
557
1028
  this.apiClient = ninetailedApiClientInstanceOrOptions;
558
1029
  } else {
@@ -592,8 +1063,9 @@ class Ninetailed {
592
1063
  if (typeof onError === 'function') {
593
1064
  experience_jsShared.logger.addSink(new experience_jsShared.OnErrorLogSink(onError));
594
1065
  }
1066
+ this.eventBuilder = new EventBuilder(buildClientContext);
595
1067
  this.logger = experience_jsShared.logger;
596
- this.ninetailedCorePlugin = ninetailedCorePlugin({
1068
+ this.ninetailedCorePlugin = new NinetailedCorePlugin({
597
1069
  apiClient: this.apiClient,
598
1070
  locale,
599
1071
  requestTimeout,
@@ -633,6 +1105,9 @@ class Ninetailed {
633
1105
  }
634
1106
  this.registerWindowHandlers();
635
1107
  }
1108
+ get pluginsWithCustomComponentViewThreshold() {
1109
+ return [this.ninetailedCorePlugin, ...this.plugins].filter(plugin => hasComponentViewTrackingThreshold(plugin));
1110
+ }
636
1111
  get profileState() {
637
1112
  return this._profileState;
638
1113
  }
@@ -741,155 +1216,6 @@ const selectVariant = (baseline, variants, {
741
1216
  };
742
1217
  };
743
1218
 
744
- const TrackComponentProperties = zod.z.object({
745
- variant: zod.z.object({
746
- id: zod.z.string()
747
- }),
748
- audience: zod.z.object({
749
- id: zod.z.string()
750
- }),
751
- isPersonalized: zod.z.boolean()
752
- });
753
-
754
- class NinetailedPlugin {}
755
-
756
- const ElementSeenPayloadSchema = zod.z.object({
757
- element: zod.z.any(),
758
- experience: zod.z.object({
759
- id: zod.z.string(),
760
- type: zod.z.union([zod.z.literal('nt_experiment'), zod.z.literal('nt_personalization')]),
761
- name: zod.z.string().optional(),
762
- description: zod.z.string().optional()
763
- }).optional().nullable(),
764
- audience: zod.z.object({
765
- id: zod.z.string(),
766
- name: zod.z.string().optional(),
767
- description: zod.z.string().optional()
768
- }).optional().nullable().default({
769
- id: 'ALL_VISITORS',
770
- name: 'All Visitors',
771
- description: 'This is the default all visitors audience as no audience was set.'
772
- }),
773
- variant: zod.z.object({
774
- id: zod.z.string()
775
- }).catchall(zod.z.unknown()),
776
- variantIndex: zod.z.number()
777
- });
778
-
779
- const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
780
- return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
781
- const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
782
- const variantIndex = parseInt(_variantIndex);
783
- if (!experienceId || !variantIndex) {
784
- return null;
785
- }
786
- return {
787
- experienceId,
788
- variantIndex
789
- };
790
- }).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
791
- [curr.experienceId]: curr.variantIndex
792
- }), {});
793
- };
794
-
795
- class OnChangeEmitter {
796
- constructor() {
797
- this.onChangeListeners = [];
798
- }
799
- addListener(listener) {
800
- this.onChangeListeners.push(listener);
801
- return () => {
802
- this.removeOnChangeListener(listener);
803
- };
804
- }
805
- invokeListeners() {
806
- this.onChangeListeners.forEach(listener => listener());
807
- }
808
- removeOnChangeListener(listener) {
809
- this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
810
- }
811
- }
812
-
813
- const hasOnChangeEmitter = arg => {
814
- return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
815
- };
816
-
817
- const selectPluginsHavingOnChangeEmitter = plugins => {
818
- const filteredPlugins = [];
819
- for (const plugin of plugins) {
820
- if (hasOnChangeEmitter(plugin)) {
821
- filteredPlugins.push(plugin);
822
- }
823
- }
824
- return filteredPlugins;
825
- };
826
-
827
- const hasExperienceSelectionMiddleware = arg => {
828
- return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
829
- };
830
-
831
- const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
832
- const filteredPlugins = [];
833
- for (const plugin of plugins) {
834
- if (hasExperienceSelectionMiddleware(plugin)) {
835
- filteredPlugins.push(plugin);
836
- }
837
- }
838
- return filteredPlugins;
839
- };
840
-
841
- const createPassThroughMiddleware = () => {
842
- return ({
843
- experience,
844
- variant,
845
- variantIndex
846
- }) => {
847
- return {
848
- experience,
849
- variant,
850
- variantIndex
851
- };
852
- };
853
- };
854
- const makeExperienceSelectMiddleware = ({
855
- plugins,
856
- onChange,
857
- experiences,
858
- baseline,
859
- profile
860
- }) => {
861
- let removeChangeListeners = [];
862
- const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
863
- const prepareMiddleware = () => {
864
- if (profile === null) {
865
- return createPassThroughMiddleware();
866
- }
867
- const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
868
- const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
869
- experiences,
870
- baseline
871
- }));
872
- return experience_jsShared.pipe(...middlewareFunctions);
873
- };
874
- const addListeners = () => {
875
- removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
876
- const listener = () => {
877
- onChange();
878
- };
879
- return plugin.onChangeEmitter.addListener(listener);
880
- });
881
- };
882
- const removeListeners = () => {
883
- removeChangeListeners.forEach(listener => listener());
884
- };
885
- const middleware = prepareMiddleware();
886
- return {
887
- addListeners,
888
- removeListeners,
889
- middleware
890
- };
891
- };
892
-
893
1219
  Object.defineProperty(exports, 'EXPERIENCE_TRAIT_PREFIX', {
894
1220
  enumerable: true,
895
1221
  get: function () { return experience_jsShared.EXPERIENCE_TRAIT_PREFIX; }
@@ -933,12 +1259,10 @@ exports.CONSENT = CONSENT;
933
1259
  exports.DEBUG_FLAG = DEBUG_FLAG;
934
1260
  exports.EMPTY_MERGE_ID = EMPTY_MERGE_ID;
935
1261
  exports.EXPERIENCES_FALLBACK_CACHE = EXPERIENCES_FALLBACK_CACHE;
936
- exports.ElementSeenPayloadSchema = ElementSeenPayloadSchema;
937
- exports.HAS_SEEN_COMPONENT = HAS_SEEN_COMPONENT;
938
- exports.HAS_SEEN_ELEMENT = HAS_SEEN_ELEMENT;
1262
+ exports.HAS_SEEN_STICKY_COMPONENT = HAS_SEEN_STICKY_COMPONENT;
939
1263
  exports.LEGACY_ANONYMOUS_ID = LEGACY_ANONYMOUS_ID;
940
1264
  exports.Ninetailed = Ninetailed;
941
- exports.NinetailedPlugin = NinetailedPlugin;
1265
+ exports.NinetailedCorePlugin = NinetailedCorePlugin;
942
1266
  exports.OnChangeEmitter = OnChangeEmitter;
943
1267
  exports.PAGE_HIDDEN = PAGE_HIDDEN;
944
1268
  exports.PLUGIN_NAME = PLUGIN_NAME;
@@ -946,11 +1270,9 @@ exports.PROFILE_CHANGE = PROFILE_CHANGE;
946
1270
  exports.PROFILE_FALLBACK_CACHE = PROFILE_FALLBACK_CACHE;
947
1271
  exports.PROFILE_RESET = PROFILE_RESET;
948
1272
  exports.SET_ENABLED_FEATURES = SET_ENABLED_FEATURES;
949
- exports.TrackComponentProperties = TrackComponentProperties;
950
1273
  exports.buildClientNinetailedRequestContext = buildClientNinetailedRequestContext;
951
1274
  exports.decodeExperienceVariantsMap = decodeExperienceVariantsMap;
952
1275
  exports.makeExperienceSelectMiddleware = makeExperienceSelectMiddleware;
953
- exports.ninetailedCorePlugin = ninetailedCorePlugin;
954
1276
  exports.selectPluginsHavingExperienceSelectionMiddleware = selectPluginsHavingExperienceSelectionMiddleware;
955
1277
  exports.selectPluginsHavingOnChangeEmitter = selectPluginsHavingOnChangeEmitter;
956
1278
  exports.selectVariant = selectVariant;