@ninetailed/experience.js 7.5.0-beta.9 → 7.6.0-beta.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 +435 -132
- package/index.js +436 -134
- package/package.json +3 -2
- package/src/lib/Ninetailed.d.ts +15 -3
- package/src/lib/constants.d.ts +1 -0
- package/src/lib/experience/makeExperienceSelectMiddleware.d.ts +8 -8
- package/src/lib/guards/hasExperienceSelectionMiddleware.d.ts +1 -1
- package/src/lib/plugins/selectPluginsHavingExperienceSelectionMiddleware.d.ts +1 -1
- package/src/lib/types/OnSelectVariant.d.ts +51 -0
- package/src/lib/types/index.d.ts +8 -2
- package/src/lib/types/interfaces/HasExperienceSelectionMiddleware.d.ts +10 -10
- package/src/lib/utils/EventBuilder.d.ts +221 -0
- package/src/lib/utils/noop.d.ts +1 -0
package/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { FEATURES, logger, ConsoleLogSink, buildPageEvent, buildTrackEvent, buildIdentifyEvent, unionBy, NinetailedApiClient, OnLogLogSink, OnErrorLogSink, PageviewProperties, Properties, Traits,
|
|
1
|
+
import { FEATURES, logger, ConsoleLogSink, buildPageEvent, buildTrackEvent, buildIdentifyEvent, buildComponentViewEvent, unionBy, pipe, selectHasVariants, selectExperience, selectVariant as selectVariant$1, selectBaselineWithVariants, NinetailedApiClient, OnLogLogSink, OnErrorLogSink, PageviewProperties, Properties, Traits, isPageViewEvent, isTrackEvent, isIdentifyEvent, isComponentViewEvent } from '@ninetailed/experience.js-shared';
|
|
2
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
3
|
import Analytics from 'analytics';
|
|
4
|
+
import { v4 } from 'uuid';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
|
|
6
7
|
const HAS_SEEN_COMPONENT = 'has_seen_component';
|
|
@@ -8,6 +9,7 @@ const HAS_SEEN_ELEMENT = 'has_seen_element';
|
|
|
8
9
|
const COMPONENT = 'component';
|
|
9
10
|
const COMPONENT_START = 'componentStart';
|
|
10
11
|
const PAGE_HIDDEN = 'page_hidden';
|
|
12
|
+
const HAS_SEEN_STICKY_COMPONENT = 'sticky_component_view';
|
|
11
13
|
|
|
12
14
|
/******************************************************************************
|
|
13
15
|
Copyright (c) Microsoft Corporation.
|
|
@@ -265,6 +267,20 @@ const ninetailedCorePlugin = ({
|
|
|
265
267
|
ctx
|
|
266
268
|
}));
|
|
267
269
|
}),
|
|
270
|
+
[HAS_SEEN_STICKY_COMPONENT]: ({
|
|
271
|
+
payload
|
|
272
|
+
}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
273
|
+
logger.info('Sending Sticky Components event.');
|
|
274
|
+
const ctx = buildContext();
|
|
275
|
+
return enqueueEvent(buildComponentViewEvent({
|
|
276
|
+
messageId: payload.meta.rid,
|
|
277
|
+
timestamp: payload.meta.ts,
|
|
278
|
+
componentId: payload.componentId,
|
|
279
|
+
experienceId: payload.experienceId,
|
|
280
|
+
variantIndex: payload.variantIndex,
|
|
281
|
+
ctx
|
|
282
|
+
}));
|
|
283
|
+
}),
|
|
268
284
|
setItemStart: ({
|
|
269
285
|
abort,
|
|
270
286
|
payload
|
|
@@ -361,6 +377,189 @@ const isInterestedInHiddenPage = arg => {
|
|
|
361
377
|
return typeof arg === 'object' && arg !== null && PAGE_HIDDEN in arg && typeof arg[PAGE_HIDDEN] === 'function';
|
|
362
378
|
};
|
|
363
379
|
|
|
380
|
+
const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
|
|
381
|
+
return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
|
|
382
|
+
const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
|
|
383
|
+
const variantIndex = parseInt(_variantIndex);
|
|
384
|
+
if (!experienceId || !variantIndex) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
experienceId,
|
|
389
|
+
variantIndex
|
|
390
|
+
};
|
|
391
|
+
}).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
|
|
392
|
+
[curr.experienceId]: curr.variantIndex
|
|
393
|
+
}), {});
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
class OnChangeEmitter {
|
|
397
|
+
constructor() {
|
|
398
|
+
this.onChangeListeners = [];
|
|
399
|
+
}
|
|
400
|
+
addListener(listener) {
|
|
401
|
+
this.onChangeListeners.push(listener);
|
|
402
|
+
return () => {
|
|
403
|
+
this.removeOnChangeListener(listener);
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
invokeListeners() {
|
|
407
|
+
this.onChangeListeners.forEach(listener => listener());
|
|
408
|
+
}
|
|
409
|
+
removeOnChangeListener(listener) {
|
|
410
|
+
this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const hasOnChangeEmitter = arg => {
|
|
415
|
+
return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const selectPluginsHavingOnChangeEmitter = plugins => {
|
|
419
|
+
const filteredPlugins = [];
|
|
420
|
+
for (const plugin of plugins) {
|
|
421
|
+
if (hasOnChangeEmitter(plugin)) {
|
|
422
|
+
filteredPlugins.push(plugin);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return filteredPlugins;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const hasExperienceSelectionMiddleware = arg => {
|
|
429
|
+
return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
|
|
433
|
+
const filteredPlugins = [];
|
|
434
|
+
for (const plugin of plugins) {
|
|
435
|
+
if (hasExperienceSelectionMiddleware(plugin)) {
|
|
436
|
+
filteredPlugins.push(plugin);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return filteredPlugins;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const createPassThroughMiddleware = () => {
|
|
443
|
+
return ({
|
|
444
|
+
experience,
|
|
445
|
+
variant,
|
|
446
|
+
variantIndex
|
|
447
|
+
}) => {
|
|
448
|
+
return {
|
|
449
|
+
experience,
|
|
450
|
+
variant,
|
|
451
|
+
variantIndex
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
};
|
|
455
|
+
const makeExperienceSelectMiddleware = ({
|
|
456
|
+
plugins,
|
|
457
|
+
onChange,
|
|
458
|
+
experiences,
|
|
459
|
+
baseline,
|
|
460
|
+
profile
|
|
461
|
+
}) => {
|
|
462
|
+
let removeChangeListeners = [];
|
|
463
|
+
const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
|
|
464
|
+
const prepareMiddleware = () => {
|
|
465
|
+
if (profile === null) {
|
|
466
|
+
return createPassThroughMiddleware();
|
|
467
|
+
}
|
|
468
|
+
const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
|
|
469
|
+
const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
|
|
470
|
+
experiences,
|
|
471
|
+
baseline
|
|
472
|
+
}));
|
|
473
|
+
return pipe(...middlewareFunctions);
|
|
474
|
+
};
|
|
475
|
+
const middleware = prepareMiddleware();
|
|
476
|
+
const addListeners = () => {
|
|
477
|
+
removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
|
|
478
|
+
const listener = () => {
|
|
479
|
+
onChange(middleware);
|
|
480
|
+
};
|
|
481
|
+
return plugin.onChangeEmitter.addListener(listener);
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
const removeListeners = () => {
|
|
485
|
+
removeChangeListeners.forEach(listener => listener());
|
|
486
|
+
};
|
|
487
|
+
return {
|
|
488
|
+
addListeners,
|
|
489
|
+
removeListeners,
|
|
490
|
+
middleware
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
class EventBuilder {
|
|
495
|
+
constructor(buildRequestContext) {
|
|
496
|
+
this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
|
|
497
|
+
}
|
|
498
|
+
page(properties, data) {
|
|
499
|
+
return buildPageEvent(Object.assign(Object.assign({
|
|
500
|
+
messageId: (data === null || data === void 0 ? void 0 : data.messageId) || v4()
|
|
501
|
+
}, data), {
|
|
502
|
+
timestamp: Date.now(),
|
|
503
|
+
properties: properties || {},
|
|
504
|
+
ctx: this.buildRequestContext()
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
track(event, properties, data) {
|
|
508
|
+
return buildTrackEvent(Object.assign(Object.assign({
|
|
509
|
+
messageId: (data === null || data === void 0 ? void 0 : data.messageId) || v4(),
|
|
510
|
+
timestamp: Date.now()
|
|
511
|
+
}, data), {
|
|
512
|
+
event,
|
|
513
|
+
properties: properties || {},
|
|
514
|
+
ctx: this.buildRequestContext()
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
identify(userId, traits, data) {
|
|
518
|
+
return buildIdentifyEvent(Object.assign(Object.assign({
|
|
519
|
+
messageId: (data === null || data === void 0 ? void 0 : data.messageId) || v4(),
|
|
520
|
+
timestamp: Date.now()
|
|
521
|
+
}, data), {
|
|
522
|
+
traits: traits || {},
|
|
523
|
+
userId: userId || '',
|
|
524
|
+
ctx: this.buildRequestContext()
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
527
|
+
component(componentId, experienceId, variantIndex, data) {
|
|
528
|
+
return buildComponentViewEvent(Object.assign(Object.assign({
|
|
529
|
+
messageId: (data === null || data === void 0 ? void 0 : data.messageId) || v4(),
|
|
530
|
+
timestamp: Date.now()
|
|
531
|
+
}, data), {
|
|
532
|
+
componentId,
|
|
533
|
+
experienceId: experienceId || '',
|
|
534
|
+
variantIndex: variantIndex || 0,
|
|
535
|
+
ctx: this.buildRequestContext()
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const buildOverrideMiddleware = experienceSelectionMiddleware => _a => {
|
|
541
|
+
var {
|
|
542
|
+
experience: originalExperience,
|
|
543
|
+
variant: originalVariant,
|
|
544
|
+
variantIndex: originalVariantIndex
|
|
545
|
+
} = _a,
|
|
546
|
+
other = __rest(_a, ["experience", "variant", "variantIndex"]);
|
|
547
|
+
const {
|
|
548
|
+
experience,
|
|
549
|
+
variant,
|
|
550
|
+
variantIndex
|
|
551
|
+
} = experienceSelectionMiddleware({
|
|
552
|
+
experience: originalExperience,
|
|
553
|
+
variant: originalVariant,
|
|
554
|
+
variantIndex: originalVariantIndex
|
|
555
|
+
});
|
|
556
|
+
return Object.assign(Object.assign({}, other), {
|
|
557
|
+
audience: (experience === null || experience === void 0 ? void 0 : experience.audience) ? experience.audience : null,
|
|
558
|
+
experience,
|
|
559
|
+
variant,
|
|
560
|
+
variantIndex
|
|
561
|
+
});
|
|
562
|
+
};
|
|
364
563
|
class Ninetailed {
|
|
365
564
|
constructor(ninetailedApiClientInstanceOrOptions, {
|
|
366
565
|
plugins,
|
|
@@ -372,7 +571,8 @@ class Ninetailed {
|
|
|
372
571
|
buildClientContext,
|
|
373
572
|
onInitProfileId,
|
|
374
573
|
componentViewTrackingThreshold = 2000,
|
|
375
|
-
storageImpl
|
|
574
|
+
storageImpl,
|
|
575
|
+
useClientSideEvaluation = false
|
|
376
576
|
} = {}) {
|
|
377
577
|
this.isInitialized = false;
|
|
378
578
|
this.page = (data, options) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -409,6 +609,78 @@ class Ninetailed {
|
|
|
409
609
|
throw error;
|
|
410
610
|
}
|
|
411
611
|
});
|
|
612
|
+
this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
|
|
613
|
+
try {
|
|
614
|
+
const result = Traits.default({}).safeParse(traits);
|
|
615
|
+
if (!result.success) {
|
|
616
|
+
throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
|
|
617
|
+
}
|
|
618
|
+
yield this.waitUntilInitialized();
|
|
619
|
+
yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
|
|
620
|
+
return this.ninetailedCorePlugin.flush();
|
|
621
|
+
} catch (error) {
|
|
622
|
+
logger.error(error);
|
|
623
|
+
if (error instanceof RangeError) {
|
|
624
|
+
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.`);
|
|
625
|
+
}
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
this.batch = events => __awaiter(this, void 0, void 0, function* () {
|
|
630
|
+
try {
|
|
631
|
+
yield this.waitUntilInitialized();
|
|
632
|
+
const promises = events.map(event => {
|
|
633
|
+
if (isPageViewEvent(event)) {
|
|
634
|
+
return this.instance.page(event.properties);
|
|
635
|
+
}
|
|
636
|
+
if (isTrackEvent(event)) {
|
|
637
|
+
return this.instance.track(event.event, event.properties);
|
|
638
|
+
}
|
|
639
|
+
if (isIdentifyEvent(event)) {
|
|
640
|
+
return this.instance.identify(event.userId || EMPTY_MERGE_ID, event.traits);
|
|
641
|
+
}
|
|
642
|
+
if (isComponentViewEvent(event)) {
|
|
643
|
+
return this.instance.dispatch({
|
|
644
|
+
experienceId: event.experienceId,
|
|
645
|
+
componentId: event.componentId,
|
|
646
|
+
variantIndex: event.variantIndex,
|
|
647
|
+
type: HAS_SEEN_STICKY_COMPONENT
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return Promise.resolve();
|
|
651
|
+
});
|
|
652
|
+
yield Promise.all(promises);
|
|
653
|
+
return this.ninetailedCorePlugin.flush();
|
|
654
|
+
} catch (error) {
|
|
655
|
+
logger.error(error);
|
|
656
|
+
if (error instanceof RangeError) {
|
|
657
|
+
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.`);
|
|
658
|
+
}
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
this.trackStickyComponentView = ({
|
|
663
|
+
experienceId,
|
|
664
|
+
componentId,
|
|
665
|
+
variantIndex
|
|
666
|
+
}) => __awaiter(this, void 0, void 0, function* () {
|
|
667
|
+
try {
|
|
668
|
+
yield this.waitUntilInitialized();
|
|
669
|
+
yield this.instance.dispatch({
|
|
670
|
+
experienceId,
|
|
671
|
+
componentId,
|
|
672
|
+
variantIndex,
|
|
673
|
+
type: HAS_SEEN_STICKY_COMPONENT
|
|
674
|
+
});
|
|
675
|
+
return this.ninetailedCorePlugin.flush();
|
|
676
|
+
} catch (error) {
|
|
677
|
+
logger.error(error);
|
|
678
|
+
if (error instanceof RangeError) {
|
|
679
|
+
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.`);
|
|
680
|
+
}
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
});
|
|
412
684
|
/**
|
|
413
685
|
* @deprecated The legacy datamodel is not recommended anymore
|
|
414
686
|
* Will be removed in the next version of the SDK
|
|
@@ -452,23 +724,6 @@ class Ninetailed {
|
|
|
452
724
|
}, payload));
|
|
453
725
|
}
|
|
454
726
|
};
|
|
455
|
-
this.identify = (uid, traits, options) => __awaiter(this, void 0, void 0, function* () {
|
|
456
|
-
try {
|
|
457
|
-
const result = Traits.default({}).safeParse(traits);
|
|
458
|
-
if (!result.success) {
|
|
459
|
-
throw new Error(`[Validation Error] "identify" was called with invalid params. Traits are no valid json: ${result.error.format()}`);
|
|
460
|
-
}
|
|
461
|
-
yield this.waitUntilInitialized();
|
|
462
|
-
yield this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, this.buildOptions(options));
|
|
463
|
-
return this.ninetailedCorePlugin.flush();
|
|
464
|
-
} catch (error) {
|
|
465
|
-
logger.error(error);
|
|
466
|
-
if (error instanceof RangeError) {
|
|
467
|
-
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.`);
|
|
468
|
-
}
|
|
469
|
-
throw error;
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
727
|
this.reset = () => __awaiter(this, void 0, void 0, function* () {
|
|
473
728
|
yield this.waitUntilInitialized();
|
|
474
729
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
@@ -503,6 +758,165 @@ class Ninetailed {
|
|
|
503
758
|
}
|
|
504
759
|
});
|
|
505
760
|
};
|
|
761
|
+
this.onSelectVariant = ({
|
|
762
|
+
baseline,
|
|
763
|
+
experiences
|
|
764
|
+
}, cb) => {
|
|
765
|
+
let middlewareChangeListeners = [];
|
|
766
|
+
let state = null;
|
|
767
|
+
const removeMiddlewareChangeListeners = () => {
|
|
768
|
+
middlewareChangeListeners.forEach(removeListener => removeListener());
|
|
769
|
+
middlewareChangeListeners = [];
|
|
770
|
+
};
|
|
771
|
+
const setSelectedVariant = newState => {
|
|
772
|
+
state = newState;
|
|
773
|
+
cb(state);
|
|
774
|
+
};
|
|
775
|
+
const removeProfileChangeListener = this.onProfileChange(profileState => {
|
|
776
|
+
const {
|
|
777
|
+
addListeners,
|
|
778
|
+
removeListeners,
|
|
779
|
+
middleware: experienceSelectionMiddleware
|
|
780
|
+
} = makeExperienceSelectMiddleware({
|
|
781
|
+
plugins: this.plugins,
|
|
782
|
+
experiences,
|
|
783
|
+
baseline,
|
|
784
|
+
profile: profileState.profile,
|
|
785
|
+
onChange: middleware => {
|
|
786
|
+
const overrideResult = buildOverrideMiddleware(middleware);
|
|
787
|
+
if (state !== null) {
|
|
788
|
+
setSelectedVariant(overrideResult(state));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
addListeners();
|
|
793
|
+
middlewareChangeListeners.push(removeListeners);
|
|
794
|
+
const overrideResult = buildOverrideMiddleware(experienceSelectionMiddleware);
|
|
795
|
+
const hasVariants = experiences.map(experience => selectHasVariants(experience, baseline)).reduce((acc, curr) => acc || curr, false);
|
|
796
|
+
const baseReturn = Object.assign(Object.assign({}, profileState), {
|
|
797
|
+
hasVariants,
|
|
798
|
+
baseline
|
|
799
|
+
});
|
|
800
|
+
const emptyReturn = Object.assign(Object.assign({}, baseReturn), {
|
|
801
|
+
experience: null,
|
|
802
|
+
variant: baseline,
|
|
803
|
+
variantIndex: 0,
|
|
804
|
+
audience: null,
|
|
805
|
+
isPersonalized: false,
|
|
806
|
+
profile: null,
|
|
807
|
+
error: null
|
|
808
|
+
});
|
|
809
|
+
if (profileState.status === 'loading') {
|
|
810
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
811
|
+
loading: true,
|
|
812
|
+
status: 'loading'
|
|
813
|
+
})));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (profileState.status === 'error') {
|
|
817
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
818
|
+
loading: false,
|
|
819
|
+
status: 'error',
|
|
820
|
+
error: profileState.error
|
|
821
|
+
})));
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const {
|
|
825
|
+
profile,
|
|
826
|
+
experiences: selectedExperiences
|
|
827
|
+
} = profileState;
|
|
828
|
+
if (!profile || !selectedExperiences) {
|
|
829
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
830
|
+
loading: false,
|
|
831
|
+
status: 'error',
|
|
832
|
+
error: new Error('No Profile or Selected Experiences were returned by the API')
|
|
833
|
+
})));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (this.useClientSideEvaluation) {
|
|
837
|
+
const _experience = selectExperience({
|
|
838
|
+
experiences,
|
|
839
|
+
profile
|
|
840
|
+
});
|
|
841
|
+
if (!_experience) {
|
|
842
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
843
|
+
loading: false,
|
|
844
|
+
status: 'success',
|
|
845
|
+
profile
|
|
846
|
+
})));
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
const {
|
|
850
|
+
variant: _variant,
|
|
851
|
+
index
|
|
852
|
+
} = selectVariant$1({
|
|
853
|
+
baseline,
|
|
854
|
+
experience: _experience,
|
|
855
|
+
profile
|
|
856
|
+
});
|
|
857
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, baseReturn), {
|
|
858
|
+
status: 'success',
|
|
859
|
+
loading: false,
|
|
860
|
+
error: null,
|
|
861
|
+
experience: _experience,
|
|
862
|
+
variant: _variant,
|
|
863
|
+
variantIndex: index,
|
|
864
|
+
audience: _experience.audience ? _experience.audience : null,
|
|
865
|
+
profile,
|
|
866
|
+
isPersonalized: true
|
|
867
|
+
})));
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const experience = experiences.find(experience => selectedExperiences.some(selectedExperience => selectedExperience.experienceId === experience.id));
|
|
871
|
+
const selectedExperience = selectedExperiences.find(({
|
|
872
|
+
experienceId
|
|
873
|
+
}) => experienceId === (experience === null || experience === void 0 ? void 0 : experience.id));
|
|
874
|
+
if (!experience || !selectedExperience) {
|
|
875
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
876
|
+
loading: false,
|
|
877
|
+
status: 'success',
|
|
878
|
+
profile
|
|
879
|
+
})));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const baselineVariants = selectBaselineWithVariants(experience, baseline);
|
|
883
|
+
if (!baselineVariants) {
|
|
884
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
885
|
+
loading: false,
|
|
886
|
+
status: 'success',
|
|
887
|
+
profile
|
|
888
|
+
})));
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
const {
|
|
892
|
+
variants
|
|
893
|
+
} = baselineVariants;
|
|
894
|
+
const variant = variants[selectedExperience.variantIndex - 1];
|
|
895
|
+
if (!variant) {
|
|
896
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, emptyReturn), {
|
|
897
|
+
loading: false,
|
|
898
|
+
status: 'success',
|
|
899
|
+
profile
|
|
900
|
+
})));
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
setSelectedVariant(overrideResult(Object.assign(Object.assign({}, baseReturn), {
|
|
904
|
+
status: 'success',
|
|
905
|
+
loading: false,
|
|
906
|
+
error: null,
|
|
907
|
+
experience,
|
|
908
|
+
variant,
|
|
909
|
+
variantIndex: selectedExperience.variantIndex,
|
|
910
|
+
audience: experience.audience ? experience.audience : null,
|
|
911
|
+
profile,
|
|
912
|
+
isPersonalized: true
|
|
913
|
+
})));
|
|
914
|
+
});
|
|
915
|
+
return () => {
|
|
916
|
+
removeProfileChangeListener();
|
|
917
|
+
removeMiddlewareChangeListeners();
|
|
918
|
+
};
|
|
919
|
+
};
|
|
506
920
|
this.onIsInitialized = onIsInitialized => {
|
|
507
921
|
if (typeof onIsInitialized === 'function') {
|
|
508
922
|
if (this.isInitialized) {
|
|
@@ -533,6 +947,7 @@ class Ninetailed {
|
|
|
533
947
|
}
|
|
534
948
|
});
|
|
535
949
|
};
|
|
950
|
+
this.useClientSideEvaluation = useClientSideEvaluation;
|
|
536
951
|
if (ninetailedApiClientInstanceOrOptions instanceof NinetailedApiClient) {
|
|
537
952
|
this.apiClient = ninetailedApiClientInstanceOrOptions;
|
|
538
953
|
} else {
|
|
@@ -572,6 +987,7 @@ class Ninetailed {
|
|
|
572
987
|
if (typeof onError === 'function') {
|
|
573
988
|
logger.addSink(new OnErrorLogSink(onError));
|
|
574
989
|
}
|
|
990
|
+
this.eventBuilder = new EventBuilder(buildClientContext);
|
|
575
991
|
this.logger = logger;
|
|
576
992
|
this.ninetailedCorePlugin = ninetailedCorePlugin({
|
|
577
993
|
apiClient: this.apiClient,
|
|
@@ -756,118 +1172,4 @@ const ElementSeenPayloadSchema = z.object({
|
|
|
756
1172
|
variantIndex: z.number()
|
|
757
1173
|
});
|
|
758
1174
|
|
|
759
|
-
|
|
760
|
-
return encodedExperienceVariantsMap.split(',').map(experienceIdWithVariant => {
|
|
761
|
-
const [experienceId, _variantIndex] = experienceIdWithVariant.split('=');
|
|
762
|
-
const variantIndex = parseInt(_variantIndex);
|
|
763
|
-
if (!experienceId || !variantIndex) {
|
|
764
|
-
return null;
|
|
765
|
-
}
|
|
766
|
-
return {
|
|
767
|
-
experienceId,
|
|
768
|
-
variantIndex
|
|
769
|
-
};
|
|
770
|
-
}).filter(x => !!x).reduce((acc, curr) => Object.assign(Object.assign({}, acc), {
|
|
771
|
-
[curr.experienceId]: curr.variantIndex
|
|
772
|
-
}), {});
|
|
773
|
-
};
|
|
774
|
-
|
|
775
|
-
class OnChangeEmitter {
|
|
776
|
-
constructor() {
|
|
777
|
-
this.onChangeListeners = [];
|
|
778
|
-
}
|
|
779
|
-
addListener(listener) {
|
|
780
|
-
this.onChangeListeners.push(listener);
|
|
781
|
-
return () => {
|
|
782
|
-
this.removeOnChangeListener(listener);
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
invokeListeners() {
|
|
786
|
-
this.onChangeListeners.forEach(listener => listener());
|
|
787
|
-
}
|
|
788
|
-
removeOnChangeListener(listener) {
|
|
789
|
-
this.onChangeListeners = this.onChangeListeners.filter(l => l !== listener);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
const hasOnChangeEmitter = arg => {
|
|
794
|
-
return typeof arg === 'object' && arg !== null && 'onChangeEmitter' in arg && typeof arg.onChangeEmitter === 'object' && arg.onChangeEmitter !== null && arg.onChangeEmitter.constructor === OnChangeEmitter;
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
const selectPluginsHavingOnChangeEmitter = plugins => {
|
|
798
|
-
const filteredPlugins = [];
|
|
799
|
-
for (const plugin of plugins) {
|
|
800
|
-
if (hasOnChangeEmitter(plugin)) {
|
|
801
|
-
filteredPlugins.push(plugin);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return filteredPlugins;
|
|
805
|
-
};
|
|
806
|
-
|
|
807
|
-
const hasExperienceSelectionMiddleware = arg => {
|
|
808
|
-
return typeof arg === 'object' && arg !== null && 'getExperienceSelectionMiddleware' in arg && typeof arg.getExperienceSelectionMiddleware === 'function';
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
const selectPluginsHavingExperienceSelectionMiddleware = plugins => {
|
|
812
|
-
const filteredPlugins = [];
|
|
813
|
-
for (const plugin of plugins) {
|
|
814
|
-
if (hasExperienceSelectionMiddleware(plugin)) {
|
|
815
|
-
filteredPlugins.push(plugin);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
return filteredPlugins;
|
|
819
|
-
};
|
|
820
|
-
|
|
821
|
-
const createPassThroughMiddleware = () => {
|
|
822
|
-
return ({
|
|
823
|
-
experience,
|
|
824
|
-
variant,
|
|
825
|
-
variantIndex
|
|
826
|
-
}) => {
|
|
827
|
-
return {
|
|
828
|
-
experience,
|
|
829
|
-
variant,
|
|
830
|
-
variantIndex
|
|
831
|
-
};
|
|
832
|
-
};
|
|
833
|
-
};
|
|
834
|
-
const makeExperienceSelectMiddleware = ({
|
|
835
|
-
plugins,
|
|
836
|
-
onChange,
|
|
837
|
-
experiences,
|
|
838
|
-
baseline,
|
|
839
|
-
profile
|
|
840
|
-
}) => {
|
|
841
|
-
let removeChangeListeners = [];
|
|
842
|
-
const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
|
|
843
|
-
const prepareMiddleware = () => {
|
|
844
|
-
if (profile === null) {
|
|
845
|
-
return createPassThroughMiddleware();
|
|
846
|
-
}
|
|
847
|
-
const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
|
|
848
|
-
const middlewareFunctions = pluginsWithMiddleware.map(plugin => plugin.getExperienceSelectionMiddleware({
|
|
849
|
-
experiences,
|
|
850
|
-
baseline
|
|
851
|
-
}));
|
|
852
|
-
return pipe(...middlewareFunctions);
|
|
853
|
-
};
|
|
854
|
-
const addListeners = () => {
|
|
855
|
-
removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
|
|
856
|
-
const listener = () => {
|
|
857
|
-
onChange();
|
|
858
|
-
};
|
|
859
|
-
return plugin.onChangeEmitter.addListener(listener);
|
|
860
|
-
});
|
|
861
|
-
};
|
|
862
|
-
const removeListeners = () => {
|
|
863
|
-
removeChangeListeners.forEach(listener => listener());
|
|
864
|
-
};
|
|
865
|
-
const middleware = prepareMiddleware();
|
|
866
|
-
return {
|
|
867
|
-
addListeners,
|
|
868
|
-
removeListeners,
|
|
869
|
-
middleware
|
|
870
|
-
};
|
|
871
|
-
};
|
|
872
|
-
|
|
873
|
-
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 };
|
|
1175
|
+
export { ANONYMOUS_ID, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, ElementSeenPayloadSchema, HAS_SEEN_COMPONENT, HAS_SEEN_ELEMENT, HAS_SEEN_STICKY_COMPONENT, 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 };
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninetailed/experience.js",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.6.0-beta.1",
|
|
4
4
|
"license": "BSL-1.1",
|
|
5
5
|
"module": "./index.js",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"types": "./src/index.d.ts",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@ninetailed/experience.js-shared": "7.
|
|
10
|
+
"@ninetailed/experience.js-shared": "7.6.0-beta.1",
|
|
11
11
|
"analytics": "0.8.1",
|
|
12
|
+
"uuid": "9.0.0",
|
|
12
13
|
"zod": "3.21.4"
|
|
13
14
|
},
|
|
14
15
|
"peerDependencies": {}
|