@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
package/index.js DELETED
@@ -1,886 +0,0 @@
1
- import { FEATURES, logger, ConsoleLogSink, buildPageEvent, buildTrackEvent, buildIdentifyEvent, unionBy, NinetailedApiClient, OnLogLogSink, OnErrorLogSink, PageviewProperties, Properties, Traits, pipe } from '@ninetailed/experience.js-shared';
2
- export { EXPERIENCE_TRAIT_PREFIX, isExperienceMatch, selectActiveExperiments, selectDistribution, selectExperience, selectBaselineWithVariants as selectExperienceBaselineWithVariants, selectVariant as selectExperienceVariant, selectVariants as selectExperienceVariants, selectHasVariants as selectHasExperienceVariants } from '@ninetailed/experience.js-shared';
3
- import Analytics from 'analytics';
4
- import { z } from 'zod';
5
-
6
- const HAS_SEEN_COMPONENT = 'has_seen_component';
7
- const HAS_SEEN_ELEMENT = 'has_seen_element';
8
- const COMPONENT = 'component';
9
- const COMPONENT_START = 'componentStart';
10
- const PAGE_HIDDEN = 'page_hidden';
11
-
12
- /******************************************************************************
13
- Copyright (c) Microsoft Corporation.
14
-
15
- Permission to use, copy, modify, and/or distribute this software for any
16
- purpose with or without fee is hereby granted.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
19
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
20
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
21
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
22
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
23
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
24
- PERFORMANCE OF THIS SOFTWARE.
25
- ***************************************************************************** */
26
-
27
- function __rest(s, e) {
28
- var t = {};
29
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
30
- t[p] = s[p];
31
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
32
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
33
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
34
- t[p[i]] = s[p[i]];
35
- }
36
- return t;
37
- }
38
-
39
- function __awaiter(thisArg, _arguments, P, generator) {
40
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
41
- return new (P || (P = Promise))(function (resolve, reject) {
42
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
43
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
44
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
45
- step((generator = generator.apply(thisArg, _arguments || [])).next());
46
- });
47
- }
48
-
49
- const buildClientLocale = () => navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language;
50
-
51
- const buildClientNinetailedRequestContext = () => ({
52
- url: window.location.href,
53
- referrer: document.referrer,
54
- locale: buildClientLocale(),
55
- userAgent: navigator.userAgent,
56
- document: {
57
- title: document.title
58
- }
59
- });
60
-
61
- /**
62
- * Similar to _.throttle but waits for the promise to resolve.
63
- * There is no "wait time" because you can simply await `Promise.timeout` inside `fn` to wait some time before the next call.
64
- */
65
- function asyncThrottle(fn) {
66
- let runningPromise;
67
- let queuedPromise;
68
- let nextArgs;
69
- return args => __awaiter(this, void 0, void 0, function* () {
70
- if (runningPromise) {
71
- nextArgs = args;
72
- if (queuedPromise) {
73
- return queuedPromise;
74
- } else {
75
- queuedPromise = runningPromise.then(() => {
76
- queuedPromise = undefined;
77
- runningPromise = fn(nextArgs);
78
- return runningPromise;
79
- });
80
- return queuedPromise;
81
- }
82
- } else {
83
- runningPromise = fn(args);
84
- return runningPromise;
85
- }
86
- });
87
- }
88
-
89
- const LEGACY_ANONYMOUS_ID = '__anon_id';
90
- const ANONYMOUS_ID = '__nt_anonymous_id__';
91
- const DEBUG_FLAG = '__nt_debug__';
92
- const PROFILE_FALLBACK_CACHE = '__nt_profile__';
93
- const EXPERIENCES_FALLBACK_CACHE = '__nt_experiences__';
94
- const PROFILE_CHANGE = 'profile-change';
95
- const PROFILE_RESET = 'profile-reset';
96
- const CONSENT = '__nt-consent__';
97
- const SET_ENABLED_FEATURES = 'set-enabled-features';
98
- const EMPTY_MERGE_ID = 'nt:empty-merge-id';
99
-
100
- const PLUGIN_NAME = 'ninetailed';
101
- const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
102
- const ninetailedCorePlugin = ({
103
- apiClient,
104
- locale,
105
- ninetailed,
106
- onInitProfileId,
107
- buildClientContext
108
- }) => {
109
- let _instance;
110
- let queue = [];
111
- let enabledFeatures = Object.values(FEATURES);
112
- const buildContext = buildClientContext || buildClientNinetailedRequestContext;
113
- const flush = () => __awaiter(void 0, void 0, void 0, function* () {
114
- const events = Object.assign([], queue);
115
- logger.info('Start flushing events.');
116
- queue = [];
117
- if (!events.length) {
118
- return {
119
- success: true
120
- };
121
- }
122
- try {
123
- const anonymousId = _instance.storage.getItem(ANONYMOUS_ID);
124
- const {
125
- profile,
126
- experiences
127
- } = yield apiClient.upsertProfile({
128
- profileId: anonymousId,
129
- events
130
- }, {
131
- locale,
132
- enabledFeatures
133
- });
134
- _instance.storage.setItem(ANONYMOUS_ID, profile.id);
135
- _instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
136
- _instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
137
- logger.debug('Profile from api: ', profile);
138
- logger.debug('Experiences from api: ', experiences);
139
- _instance.dispatch({
140
- type: PROFILE_CHANGE,
141
- profile,
142
- experiences
143
- });
144
- yield delay(20);
145
- return {
146
- success: true
147
- };
148
- } catch (error) {
149
- logger.debug('An error occurred during flushing the events: ', error);
150
- const fallbackProfile = _instance.storage.getItem(PROFILE_FALLBACK_CACHE);
151
- const fallbackExperiences = _instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
152
- if (fallbackProfile) {
153
- logger.debug('Found a fallback profile - will use this.');
154
- _instance.dispatch({
155
- type: PROFILE_CHANGE,
156
- profile: fallbackProfile,
157
- experiences: fallbackExperiences
158
- });
159
- } else {
160
- logger.debug('No fallback profile found - setting profile to null.');
161
- _instance.dispatch({
162
- type: PROFILE_CHANGE,
163
- profile: null,
164
- experiences: fallbackExperiences,
165
- error
166
- });
167
- }
168
- return {
169
- success: false
170
- };
171
- }
172
- });
173
- const enqueueEvent = event => __awaiter(void 0, void 0, void 0, function* () {
174
- queue = unionBy([event], queue, 'messageId');
175
- });
176
- const abortNonClientEvents = ({
177
- abort,
178
- payload
179
- }) => {
180
- if (typeof window !== 'object') {
181
- return abort();
182
- }
183
- return payload;
184
- };
185
- return {
186
- name: 'ninetailed',
187
- config: {},
188
- initialize: ({
189
- instance
190
- }) => __awaiter(void 0, void 0, void 0, function* () {
191
- _instance = instance;
192
- if (instance.storage.getItem(DEBUG_FLAG)) {
193
- logger.addSink(new ConsoleLogSink());
194
- logger.info('Ninetailed Debug Mode is enabled.');
195
- }
196
- // legacy support for the old anonymousId
197
- const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
198
- if (legacyAnonymousId) {
199
- logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
200
- instance.storage.setItem(ANONYMOUS_ID, legacyAnonymousId);
201
- instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
202
- }
203
- if (typeof onInitProfileId === 'function') {
204
- const profileId = yield onInitProfileId(instance.storage.getItem(ANONYMOUS_ID));
205
- if (typeof profileId === 'string') {
206
- instance.storage.setItem(ANONYMOUS_ID, profileId);
207
- }
208
- }
209
- instance.on(SET_ENABLED_FEATURES, ({
210
- payload
211
- }) => {
212
- enabledFeatures = payload.features || [];
213
- });
214
- logger.debug('Ninetailed Core plugin initialized.');
215
- }),
216
- flush: asyncThrottle(flush),
217
- pageStart: params => {
218
- return abortNonClientEvents(params);
219
- },
220
- page: ({
221
- payload
222
- }) => __awaiter(void 0, void 0, void 0, function* () {
223
- logger.info('Sending Page event.');
224
- const ctx = buildContext();
225
- return enqueueEvent(buildPageEvent({
226
- messageId: payload.meta.rid,
227
- timestamp: payload.meta.ts,
228
- properties: payload.properties,
229
- ctx
230
- }));
231
- }),
232
- trackStart: params => {
233
- return abortNonClientEvents(params);
234
- },
235
- track: ({
236
- payload
237
- }) => __awaiter(void 0, void 0, void 0, function* () {
238
- logger.info('Sending Track event.');
239
- const ctx = buildContext();
240
- return enqueueEvent(buildTrackEvent({
241
- messageId: payload.meta.rid,
242
- timestamp: payload.meta.ts,
243
- event: payload.event,
244
- properties: payload.properties,
245
- ctx
246
- }));
247
- }),
248
- identifyStart: params => {
249
- return abortNonClientEvents(params);
250
- },
251
- identify: ({
252
- payload
253
- }) => __awaiter(void 0, void 0, void 0, function* () {
254
- logger.info('Sending Identify event.');
255
- const ctx = buildContext();
256
- if (payload.userId === EMPTY_MERGE_ID && (!payload.traits || typeof payload.traits === 'object' && Object.keys(payload.traits).length === 0)) {
257
- logger.info('Skipping Identify event as no userId and no traits are set.');
258
- return;
259
- }
260
- return enqueueEvent(buildIdentifyEvent({
261
- messageId: payload.meta.rid,
262
- timestamp: payload.meta.ts,
263
- traits: payload.traits,
264
- userId: payload.userId === EMPTY_MERGE_ID ? '' : payload.userId,
265
- ctx
266
- }));
267
- }),
268
- setItemStart: ({
269
- abort,
270
- payload
271
- }) => {
272
- if (![ANONYMOUS_ID, DEBUG_FLAG, PROFILE_FALLBACK_CACHE, EXPERIENCES_FALLBACK_CACHE, CONSENT].includes(payload.key)) {
273
- return abort();
274
- }
275
- return payload;
276
- },
277
- methods: {
278
- reset: (...args) => __awaiter(void 0, void 0, void 0, function* () {
279
- logger.debug('Resetting profile.');
280
- const instance = args[args.length - 1];
281
- instance.dispatch({
282
- type: PROFILE_RESET
283
- });
284
- instance.storage.removeItem(ANONYMOUS_ID);
285
- instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
286
- instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
287
- logger.debug('Removed old profile data from localstorage.');
288
- if (typeof onInitProfileId === 'function') {
289
- const profileId = yield onInitProfileId(undefined);
290
- if (typeof profileId === 'string') {
291
- instance.storage.setItem(ANONYMOUS_ID, profileId);
292
- }
293
- }
294
- yield ninetailed.track('nt_reset');
295
- logger.info('Profile reset successful.');
296
- yield delay(10);
297
- }),
298
- debug: (...args) => __awaiter(void 0, void 0, void 0, function* () {
299
- const enabled = args[0];
300
- const instance = args[args.length - 1];
301
- const consoleLogSink = new ConsoleLogSink();
302
- if (enabled) {
303
- instance.storage.setItem(DEBUG_FLAG, true);
304
- logger.addSink(consoleLogSink);
305
- logger.info('Debug mode enabled.');
306
- } else {
307
- instance.storage.removeItem(DEBUG_FLAG);
308
- logger.info('Debug mode disabled.');
309
- logger.removeSink(consoleLogSink.name);
310
- }
311
- })
312
- }
313
- };
314
- };
315
-
316
- class ElementSeenObserver {
317
- constructor(_options) {
318
- this._options = _options;
319
- this._elementDelays = new WeakMap();
320
- this._intersectionTimers = new WeakMap();
321
- if (typeof IntersectionObserver !== 'undefined') {
322
- this._intersectionObserver = new IntersectionObserver(this.onIntersection.bind(this));
323
- }
324
- }
325
- onIntersection(entries) {
326
- entries.forEach(entry => {
327
- const {
328
- isIntersecting,
329
- target
330
- } = entry;
331
- if (isIntersecting) {
332
- const delay = this._elementDelays.get(target);
333
- const timeOut = window.setTimeout(() => {
334
- this._options.onElementSeen(target);
335
- }, delay);
336
- this._intersectionTimers.set(target, timeOut);
337
- } else {
338
- const timeOut = this._intersectionTimers.get(target);
339
- if (typeof timeOut !== 'undefined') {
340
- window.clearTimeout(timeOut);
341
- }
342
- }
343
- });
344
- }
345
- observe(element, options) {
346
- var _a, _b;
347
- this._elementDelays.set(element, (_a = options === null || options === void 0 ? void 0 : options.delay) !== null && _a !== void 0 ? _a : 2000);
348
- (_b = this._intersectionObserver) === null || _b === void 0 ? void 0 : _b.observe(element);
349
- }
350
- unobserve(element) {
351
- var _a;
352
- (_a = this._intersectionObserver) === null || _a === void 0 ? void 0 : _a.unobserve(element);
353
- }
354
- }
355
-
356
- const acceptsCredentials = plugin => {
357
- return typeof plugin === 'object' && plugin !== null && 'setCredentials' in plugin && typeof plugin.setCredentials === 'function';
358
- };
359
-
360
- const isInterestedInHiddenPage = arg => {
361
- return typeof arg === 'object' && arg !== null && PAGE_HIDDEN in arg && typeof arg[PAGE_HIDDEN] === 'function';
362
- };
363
-
364
- class Ninetailed {
365
- constructor(ninetailedApiClientInstanceOrOptions, {
366
- plugins,
367
- url,
368
- locale,
369
- requestTimeout,
370
- onLog,
371
- onError,
372
- buildClientContext,
373
- onInitProfileId,
374
- componentViewTrackingThreshold = 2000,
375
- storageImpl
376
- } = {}) {
377
- this.isInitialized = false;
378
- this.page = (data, options) => __awaiter(this, void 0, void 0, function* () {
379
- try {
380
- const result = PageviewProperties.partial().default({}).safeParse(data);
381
- if (!result.success) {
382
- throw new Error(`[Validation Error] "page" was called with invalid params. Page data is not valid: ${result.error.format()}`);
383
- }
384
- yield this.waitUntilInitialized();
385
- yield this.instance.page(data, this.buildOptions(options));
386
- return this.ninetailedCorePlugin.flush();
387
- } catch (error) {
388
- logger.error(error);
389
- if (error instanceof RangeError) {
390
- 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.`);
391
- }
392
- throw error;
393
- }
394
- });
395
- this.track = (event, properties, options) => __awaiter(this, void 0, void 0, function* () {
396
- try {
397
- const result = Properties.default({}).safeParse(properties);
398
- if (!result.success) {
399
- throw new Error(`[Validation Error] "track" was called with invalid params. Properties are no valid json object: ${result.error.format()}`);
400
- }
401
- yield this.waitUntilInitialized();
402
- yield this.instance.track(event.toString(), result.data, this.buildOptions(options));
403
- return this.ninetailedCorePlugin.flush();
404
- } catch (error) {
405
- logger.error(error);
406
- if (error instanceof RangeError) {
407
- 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.`);
408
- }
409
- throw error;
410
- }
411
- });
412
- /**
413
- * @deprecated The legacy datamodel is not recommended anymore
414
- * Will be removed in the next version of the SDK
415
- */
416
- this.trackHasSeenComponent = properties => __awaiter(this, void 0, void 0, function* () {
417
- return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
418
- type: HAS_SEEN_COMPONENT
419
- }));
420
- });
421
- this.trackComponentView = properties => {
422
- return this.instance.dispatch(Object.assign(Object.assign({}, properties), {
423
- type: HAS_SEEN_ELEMENT
424
- }));
425
- };
426
- this.observeElement = (payload, options) => {
427
- const {
428
- element
429
- } = payload,
430
- remaingPayload = __rest(payload, ["element"]);
431
- if (!(element instanceof Element)) {
432
- const isObject = typeof element === 'object' && element !== null;
433
- const constructorName = isObject ? element.constructor.name : '';
434
- const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
435
- 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.`);
436
- } else {
437
- const existingPayloads = this.observedElements.get(element);
438
- if (!existingPayloads) {
439
- this.observedElements.set(element, [remaingPayload]);
440
- } else {
441
- const isPayloadAlreadyObserved = existingPayloads.some(payload => {
442
- return JSON.stringify(payload) === JSON.stringify(remaingPayload);
443
- });
444
- if (isPayloadAlreadyObserved) {
445
- return;
446
- }
447
- this.observedElements.set(element, [...existingPayloads, remaingPayload]);
448
- }
449
- this.elementSeenObserver.observe(element, Object.assign({
450
- delay: this.componentViewTrackingThreshold
451
- }, options));
452
- }
453
- };
454
- this.unobserveElement = element => {
455
- this.observedElements.delete(element);
456
- this.elementSeenObserver.unobserve(element);
457
- };
458
- this.onElementSeen = element => {
459
- const payloads = this.observedElements.get(element);
460
- if (typeof payloads !== 'undefined') {
461
- for (const payload of payloads) {
462
- this.trackComponentView(Object.assign({
463
- element
464
- }, payload));
465
- }
466
- }
467
- };
468
- this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
469
- try {
470
- const result = Traits.default({}).safeParse(traits);
471
- if (!result.success) {
472
- throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
473
- }
474
- yield this.waitUntilInitialized();
475
- yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
476
- return this.ninetailedCorePlugin.flush();
477
- } catch (error) {
478
- logger.error(error);
479
- if (error instanceof RangeError) {
480
- 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.`);
481
- }
482
- throw error;
483
- }
484
- });
485
- this.reset = () => __awaiter(this, void 0, void 0, function* () {
486
- yield this.waitUntilInitialized();
487
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
488
- // @ts-ignore
489
- this.instance.plugins[PLUGIN_NAME].reset();
490
- });
491
- this.debug = enabled => __awaiter(this, void 0, void 0, function* () {
492
- yield this.waitUntilInitialized();
493
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
494
- // @ts-ignore
495
- this.instance.plugins[PLUGIN_NAME].debug(enabled);
496
- });
497
- this.onProfileChange = cb => {
498
- cb(this.profileState);
499
- return this.instance.on(PROFILE_CHANGE, ({
500
- payload
501
- }) => {
502
- if (payload.error) {
503
- cb(Object.assign(Object.assign({}, this._profileState), {
504
- status: 'error',
505
- profile: payload.profile,
506
- experiences: payload.experiences,
507
- error: payload.error
508
- }));
509
- } else {
510
- cb(Object.assign(Object.assign({}, this._profileState), {
511
- status: 'success',
512
- profile: payload.profile,
513
- experiences: payload.experiences,
514
- error: null
515
- }));
516
- }
517
- });
518
- };
519
- this.onIsInitialized = onIsInitialized => {
520
- if (typeof onIsInitialized === 'function') {
521
- if (this.isInitialized) {
522
- onIsInitialized();
523
- } else {
524
- const detachOnReadyListener = this.instance.on('ready', () => {
525
- this.isInitialized = true;
526
- onIsInitialized();
527
- detachOnReadyListener();
528
- });
529
- }
530
- }
531
- };
532
- this.waitUntilInitialized = () => {
533
- return new Promise(resolve => {
534
- this.onIsInitialized(resolve);
535
- });
536
- };
537
- this.onVisibilityChange = () => {
538
- if (typeof document === 'undefined') {
539
- return;
540
- }
541
- document.addEventListener('visibilitychange', () => {
542
- if (document.visibilityState === 'hidden') {
543
- this.instance.dispatch({
544
- type: PAGE_HIDDEN
545
- });
546
- }
547
- });
548
- };
549
- if (ninetailedApiClientInstanceOrOptions instanceof NinetailedApiClient) {
550
- this.apiClient = ninetailedApiClientInstanceOrOptions;
551
- } else {
552
- const {
553
- clientId,
554
- environment,
555
- preview
556
- } = ninetailedApiClientInstanceOrOptions;
557
- this.clientId = clientId;
558
- this.environment = environment || 'main';
559
- this.apiClient = new NinetailedApiClient({
560
- clientId,
561
- environment,
562
- url,
563
- preview
564
- });
565
- }
566
- this.plugins = (plugins !== null && plugins !== void 0 ? plugins : []).flat();
567
- this.plugins.forEach(plugin => {
568
- if (acceptsCredentials(plugin) && this.clientId && this.environment) {
569
- plugin.setCredentials({
570
- clientId: this.clientId,
571
- environment: this.environment
572
- });
573
- }
574
- });
575
- this._profileState = {
576
- status: 'loading',
577
- profile: null,
578
- experiences: null,
579
- error: null,
580
- from: 'api'
581
- };
582
- if (typeof onLog === 'function') {
583
- logger.addSink(new OnLogLogSink(onLog));
584
- }
585
- if (typeof onError === 'function') {
586
- logger.addSink(new OnErrorLogSink(onError));
587
- }
588
- this.logger = logger;
589
- this.ninetailedCorePlugin = ninetailedCorePlugin({
590
- apiClient: this.apiClient,
591
- locale,
592
- requestTimeout,
593
- buildClientContext,
594
- onInitProfileId,
595
- ninetailed: this
596
- });
597
- this.instance = Analytics(Object.assign({
598
- app: 'ninetailed',
599
- plugins: [...this.plugins, this.ninetailedCorePlugin]
600
- }, storageImpl ? {
601
- storage: storageImpl
602
- } : {}));
603
- const detachOnReadyListener = this.instance.on('ready', () => {
604
- this.isInitialized = true;
605
- logger.info('Ninetailed Experience.js SDK is completely initialized.');
606
- detachOnReadyListener();
607
- });
608
- // put in private method
609
- this.onProfileChange(profileState => {
610
- this._profileState = profileState;
611
- if (typeof window !== 'undefined') {
612
- window.ninetailed = Object.assign({}, window.ninetailed, {
613
- profile: this.profileState.profile,
614
- experiences: this.profileState.experiences
615
- });
616
- }
617
- });
618
- this.observedElements = new WeakMap();
619
- this.elementSeenObserver = new ElementSeenObserver({
620
- onElementSeen: this.onElementSeen.bind(this)
621
- });
622
- this.componentViewTrackingThreshold = componentViewTrackingThreshold;
623
- const hasPluginsInterestedInHiddenPage = this.plugins.some(isInterestedInHiddenPage);
624
- if (hasPluginsInterestedInHiddenPage) {
625
- this.onVisibilityChange();
626
- }
627
- this.registerWindowHandlers();
628
- }
629
- get profileState() {
630
- return this._profileState;
631
- }
632
- buildOptions(options = {}) {
633
- return Object.assign({}, options);
634
- }
635
- registerWindowHandlers() {
636
- if (typeof window !== 'undefined') {
637
- window.ninetailed = Object.assign({}, window.ninetailed, {
638
- page: this.page.bind(this),
639
- track: this.track.bind(this),
640
- identify: this.identify.bind(this),
641
- reset: this.reset.bind(this),
642
- debug: this.debug.bind(this),
643
- profile: this.profileState.profile
644
- });
645
- }
646
- }
647
- }
648
-
649
- const selectVariant = (baseline, variants, {
650
- status,
651
- profile,
652
- error
653
- }, options = {
654
- holdout: -1
655
- }) => {
656
- if (status === 'loading') {
657
- return {
658
- loading: true,
659
- variant: Object.assign(Object.assign({}, baseline), {
660
- id: 'baseline',
661
- audience: {
662
- id: 'baseline'
663
- }
664
- }),
665
- audience: {
666
- id: 'baseline'
667
- },
668
- isPersonalized: false,
669
- error: null
670
- };
671
- }
672
- if (status === 'error') {
673
- return {
674
- loading: false,
675
- variant: Object.assign(Object.assign({}, baseline), {
676
- id: 'baseline',
677
- audience: {
678
- id: 'baseline'
679
- }
680
- }),
681
- audience: {
682
- id: 'baseline'
683
- },
684
- isPersonalized: false,
685
- error: error
686
- };
687
- }
688
- const variant = variants.find(variant => {
689
- var _a, _b;
690
- return (_a = profile === null || profile === void 0 ? void 0 : profile.audiences) === null || _a === void 0 ? void 0 : _a.includes((_b = variant.audience) === null || _b === void 0 ? void 0 : _b.id);
691
- });
692
- if (variant) {
693
- if ((options === null || options === void 0 ? void 0 : options.holdout) || -1 > ((profile === null || profile === void 0 ? void 0 : profile.random) || 0)) {
694
- return {
695
- loading: false,
696
- variant: Object.assign(Object.assign({}, baseline), {
697
- audience: {
698
- id: 'baseline'
699
- }
700
- }),
701
- audience: Object.assign(Object.assign({}, variant.audience), {
702
- id: variant.audience.id
703
- }),
704
- isPersonalized: false,
705
- error: null
706
- };
707
- }
708
- return {
709
- loading: false,
710
- variant,
711
- audience: Object.assign(Object.assign({}, variant.audience), {
712
- id: variant.audience.id
713
- }),
714
- isPersonalized: true,
715
- error: null
716
- };
717
- }
718
- /**
719
- * There was no matching audience found.
720
- */
721
- return {
722
- loading: false,
723
- variant: Object.assign(Object.assign({}, baseline), {
724
- id: 'baseline',
725
- audience: {
726
- id: 'baseline'
727
- }
728
- }),
729
- audience: {
730
- id: 'baseline'
731
- },
732
- isPersonalized: false,
733
- error: null
734
- };
735
- };
736
-
737
- const TrackComponentProperties = z.object({
738
- variant: z.object({
739
- id: z.string()
740
- }),
741
- audience: z.object({
742
- id: z.string()
743
- }),
744
- isPersonalized: z.boolean()
745
- });
746
-
747
- class NinetailedPlugin {}
748
-
749
- const ElementSeenPayloadSchema = z.object({
750
- element: z.any(),
751
- experience: z.object({
752
- id: z.string(),
753
- type: z.union([z.literal('nt_experiment'), z.literal('nt_personalization')]),
754
- name: z.string().optional(),
755
- description: z.string().optional()
756
- }).optional().nullable(),
757
- audience: z.object({
758
- id: z.string(),
759
- name: z.string().optional(),
760
- description: z.string().optional()
761
- }).optional().nullable().default({
762
- id: 'ALL_VISITORS',
763
- name: 'All Visitors',
764
- description: 'This is the default all visitors audience as no audience was set.'
765
- }),
766
- variant: z.object({
767
- id: z.string()
768
- }).catchall(z.unknown()),
769
- variantIndex: z.number()
770
- });
771
-
772
- const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
773
- return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
774
- const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
775
- const variantIndex = parseInt(_variantIndex);
776
- if (!experienceId || !variantIndex) {
777
- return null;
778
- }
779
- return {
780
- experienceId,
781
- variantIndex
782
- };
783
- }).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
784
- [curr.experienceId]: curr.variantIndex
785
- }), {});
786
- };
787
-
788
- class OnChangeEmitter {
789
- constructor() {
790
- this.onChangeListeners = [];
791
- }
792
- addListener(listener) {
793
- this.onChangeListeners.push(listener);
794
- return () => {
795
- this.removeOnChangeListener(listener);
796
- };
797
- }
798
- invokeListeners() {
799
- this.onChangeListeners.forEach(listener => listener());
800
- }
801
- removeOnChangeListener(listener) {
802
- this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
803
- }
804
- }
805
-
806
- const hasOnChangeEmitter = arg => {
807
- return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
808
- };
809
-
810
- const selectPluginsHavingOnChangeEmitter = plugins => {
811
- const filteredPlugins = [];
812
- for (const plugin of plugins) {
813
- if (hasOnChangeEmitter(plugin)) {
814
- filteredPlugins.push(plugin);
815
- }
816
- }
817
- return filteredPlugins;
818
- };
819
-
820
- const hasExperienceSelectionMiddleware = arg => {
821
- return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
822
- };
823
-
824
- const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
825
- const filteredPlugins = [];
826
- for (const plugin of plugins) {
827
- if (hasExperienceSelectionMiddleware(plugin)) {
828
- filteredPlugins.push(plugin);
829
- }
830
- }
831
- return filteredPlugins;
832
- };
833
-
834
- const createPassThroughMiddleware = () => {
835
- return ({
836
- experience,
837
- variant,
838
- variantIndex
839
- }) => {
840
- return {
841
- experience,
842
- variant,
843
- variantIndex
844
- };
845
- };
846
- };
847
- const makeExperienceSelectMiddleware = ({
848
- plugins,
849
- onChange,
850
- experiences,
851
- baseline,
852
- profile
853
- }) => {
854
- let removeChangeListeners = [];
855
- const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
856
- const prepareMiddleware = () => {
857
- if (profile === null) {
858
- return createPassThroughMiddleware();
859
- }
860
- const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
861
- const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
862
- experiences,
863
- baseline
864
- }));
865
- return pipe(...middlewareFunctions);
866
- };
867
- const addListeners = () => {
868
- removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
869
- const listener = () => {
870
- onChange();
871
- };
872
- return plugin.onChangeEmitter.addListener(listener);
873
- });
874
- };
875
- const removeListeners = () => {
876
- removeChangeListeners.forEach(listener => listener());
877
- };
878
- const middleware = prepareMiddleware();
879
- return {
880
- addListeners,
881
- removeListeners,
882
- middleware
883
- };
884
- };
885
-
886
- export { ANONYMOUS_ID, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, ElementSeenPayloadSchema, HAS_SEEN_COMPONENT, HAS_SEEN_ELEMENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedPlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, TrackComponentProperties, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeExperienceSelectMiddleware, ninetailedCorePlugin, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };