@ninetailed/experience.js 7.11.0-beta.1 → 7.12.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +226 -116
- package/index.esm.js +232 -117
- package/package.json +3 -3
- package/src/lib/Ninetailed.d.ts +18 -2
- package/src/lib/NinetailedCorePlugin/NinetailedCorePlugin.d.ts +21 -5
- package/src/lib/NinetailedCorePlugin/actions.d.ts +59 -0
- package/src/lib/NinetailedCorePlugin/constants.d.ts +1 -0
- package/src/lib/experience/makeExperienceSelectMiddleware.d.ts +15 -10
- package/src/lib/types/index.d.ts +34 -10
- package/src/lib/utils/EventBuilder.d.ts +10 -4
package/index.esm.js
CHANGED
|
@@ -54,6 +54,7 @@ const ANONYMOUS_ID = '__nt_anonymous_id__';
|
|
|
54
54
|
const DEBUG_FLAG = '__nt_debug__';
|
|
55
55
|
const PROFILE_FALLBACK_CACHE = '__nt_profile__';
|
|
56
56
|
const EXPERIENCES_FALLBACK_CACHE = '__nt_experiences__';
|
|
57
|
+
const CHANGES_FALLBACK_CACHE = '__nt_changes__';
|
|
57
58
|
const PROFILE_CHANGE = 'profile-change';
|
|
58
59
|
const PROFILE_RESET = 'profile-reset';
|
|
59
60
|
const CONSENT = '__nt-consent__';
|
|
@@ -110,14 +111,12 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
110
111
|
instance.dispatch({
|
|
111
112
|
type: PROFILE_RESET
|
|
112
113
|
});
|
|
113
|
-
|
|
114
|
-
instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
|
|
115
|
-
instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
|
|
114
|
+
_this.clearCaches();
|
|
116
115
|
logger.debug('Removed old profile data from localstorage.');
|
|
117
116
|
if (typeof _this.onInitProfileId === 'function') {
|
|
118
117
|
const profileId = await _this.onInitProfileId(undefined);
|
|
119
118
|
if (typeof profileId === 'string') {
|
|
120
|
-
|
|
119
|
+
_this.setAnonymousId(profileId);
|
|
121
120
|
}
|
|
122
121
|
}
|
|
123
122
|
await _this.ninetailed.track('nt_reset');
|
|
@@ -163,13 +162,13 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
163
162
|
const legacyAnonymousId = instance.storage.getItem(LEGACY_ANONYMOUS_ID);
|
|
164
163
|
if (legacyAnonymousId) {
|
|
165
164
|
logger.debug('Found legacy anonymousId, migrating to new one.', legacyAnonymousId);
|
|
166
|
-
|
|
165
|
+
this.setAnonymousId(legacyAnonymousId);
|
|
167
166
|
instance.storage.removeItem(LEGACY_ANONYMOUS_ID);
|
|
168
167
|
}
|
|
169
168
|
if (typeof this.onInitProfileId === 'function') {
|
|
170
|
-
const profileId = await this.onInitProfileId(
|
|
169
|
+
const profileId = await this.onInitProfileId(this.getAnonymousId());
|
|
171
170
|
if (typeof profileId === 'string') {
|
|
172
|
-
|
|
171
|
+
this.setAnonymousId(profileId);
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
174
|
logger.debug('Ninetailed Core plugin initialized.');
|
|
@@ -267,6 +266,61 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
267
266
|
}
|
|
268
267
|
return this._instance;
|
|
269
268
|
}
|
|
269
|
+
getAnonymousId() {
|
|
270
|
+
var _ref;
|
|
271
|
+
return (_ref = this.instance.storage.getItem(ANONYMOUS_ID)) != null ? _ref : undefined;
|
|
272
|
+
}
|
|
273
|
+
setAnonymousId(id) {
|
|
274
|
+
this.instance.storage.setItem(ANONYMOUS_ID, id);
|
|
275
|
+
}
|
|
276
|
+
clearAnonymousId() {
|
|
277
|
+
this.instance.storage.removeItem(ANONYMOUS_ID);
|
|
278
|
+
}
|
|
279
|
+
setFallbackProfile(profile) {
|
|
280
|
+
this.instance.storage.setItem(PROFILE_FALLBACK_CACHE, profile);
|
|
281
|
+
}
|
|
282
|
+
getFallbackProfile() {
|
|
283
|
+
var _ref2;
|
|
284
|
+
return (_ref2 = this.instance.storage.getItem(PROFILE_FALLBACK_CACHE)) != null ? _ref2 : undefined;
|
|
285
|
+
}
|
|
286
|
+
clearFallbackProfile() {
|
|
287
|
+
this.instance.storage.removeItem(PROFILE_FALLBACK_CACHE);
|
|
288
|
+
}
|
|
289
|
+
setFallbackExperiences(experiences) {
|
|
290
|
+
this.instance.storage.setItem(EXPERIENCES_FALLBACK_CACHE, experiences);
|
|
291
|
+
}
|
|
292
|
+
getFallbackExperiences() {
|
|
293
|
+
return this.instance.storage.getItem(EXPERIENCES_FALLBACK_CACHE) || [];
|
|
294
|
+
}
|
|
295
|
+
clearFallbackExperiences() {
|
|
296
|
+
this.instance.storage.removeItem(EXPERIENCES_FALLBACK_CACHE);
|
|
297
|
+
}
|
|
298
|
+
setFallbackChanges(changes) {
|
|
299
|
+
this.instance.storage.setItem(CHANGES_FALLBACK_CACHE, changes);
|
|
300
|
+
}
|
|
301
|
+
getFallbackChanges() {
|
|
302
|
+
return this.instance.storage.getItem(CHANGES_FALLBACK_CACHE) || [];
|
|
303
|
+
}
|
|
304
|
+
clearFallbackChanges() {
|
|
305
|
+
this.instance.storage.removeItem(CHANGES_FALLBACK_CACHE);
|
|
306
|
+
}
|
|
307
|
+
clearCaches() {
|
|
308
|
+
this.clearAnonymousId();
|
|
309
|
+
this.clearFallbackProfile();
|
|
310
|
+
this.clearFallbackExperiences();
|
|
311
|
+
this.clearFallbackChanges();
|
|
312
|
+
}
|
|
313
|
+
populateCaches({
|
|
314
|
+
experiences,
|
|
315
|
+
profile,
|
|
316
|
+
anonymousId,
|
|
317
|
+
changes
|
|
318
|
+
}) {
|
|
319
|
+
this.setAnonymousId(anonymousId);
|
|
320
|
+
this.setFallbackProfile(profile);
|
|
321
|
+
this.setFallbackExperiences(experiences);
|
|
322
|
+
this.setFallbackChanges(changes);
|
|
323
|
+
}
|
|
270
324
|
async _flush() {
|
|
271
325
|
const events = Object.assign([], this.queue);
|
|
272
326
|
logger.info('Start flushing events.');
|
|
@@ -277,10 +331,11 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
277
331
|
};
|
|
278
332
|
}
|
|
279
333
|
try {
|
|
280
|
-
const anonymousId = this.
|
|
334
|
+
const anonymousId = this.getAnonymousId();
|
|
281
335
|
const {
|
|
282
336
|
profile,
|
|
283
|
-
experiences
|
|
337
|
+
experiences,
|
|
338
|
+
changes
|
|
284
339
|
} = await this.apiClient.upsertProfile({
|
|
285
340
|
profileId: anonymousId,
|
|
286
341
|
events
|
|
@@ -288,15 +343,20 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
288
343
|
locale: this.locale,
|
|
289
344
|
enabledFeatures: this.enabledFeatures
|
|
290
345
|
});
|
|
291
|
-
this.
|
|
292
|
-
|
|
293
|
-
|
|
346
|
+
this.populateCaches({
|
|
347
|
+
anonymousId: profile.id,
|
|
348
|
+
profile,
|
|
349
|
+
experiences,
|
|
350
|
+
changes
|
|
351
|
+
});
|
|
294
352
|
logger.debug('Profile from api: ', profile);
|
|
295
353
|
logger.debug('Experiences from api: ', experiences);
|
|
296
354
|
this.instance.dispatch({
|
|
297
355
|
type: PROFILE_CHANGE,
|
|
298
356
|
profile,
|
|
299
|
-
experiences
|
|
357
|
+
experiences,
|
|
358
|
+
changes,
|
|
359
|
+
error: undefined
|
|
300
360
|
});
|
|
301
361
|
await delay(20);
|
|
302
362
|
return {
|
|
@@ -304,22 +364,26 @@ class NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
|
304
364
|
};
|
|
305
365
|
} catch (error) {
|
|
306
366
|
logger.debug('An error occurred during flushing the events: ', error);
|
|
307
|
-
const fallbackProfile = this.
|
|
308
|
-
const fallbackExperiences = this.
|
|
367
|
+
const fallbackProfile = this.getFallbackProfile();
|
|
368
|
+
const fallbackExperiences = this.getFallbackExperiences();
|
|
369
|
+
const fallbackChanges = this.getFallbackChanges();
|
|
309
370
|
if (fallbackProfile) {
|
|
310
371
|
logger.debug('Found a fallback profile - will use this.');
|
|
311
372
|
this.instance.dispatch({
|
|
312
373
|
type: PROFILE_CHANGE,
|
|
313
374
|
profile: fallbackProfile,
|
|
314
|
-
experiences: fallbackExperiences
|
|
375
|
+
experiences: fallbackExperiences,
|
|
376
|
+
changes: fallbackChanges,
|
|
377
|
+
error: undefined
|
|
315
378
|
});
|
|
316
379
|
} else {
|
|
317
380
|
logger.debug('No fallback profile found - setting profile to null.');
|
|
318
381
|
this.instance.dispatch({
|
|
319
382
|
type: PROFILE_CHANGE,
|
|
320
383
|
profile: null,
|
|
384
|
+
changes: fallbackChanges,
|
|
321
385
|
experiences: fallbackExperiences,
|
|
322
|
-
error
|
|
386
|
+
error: error
|
|
323
387
|
});
|
|
324
388
|
}
|
|
325
389
|
return {
|
|
@@ -400,19 +464,17 @@ const isInterestedInHiddenPage = arg => {
|
|
|
400
464
|
};
|
|
401
465
|
|
|
402
466
|
const decodeExperienceVariantsMap = encodedExperienceVariantsMap => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
467
|
+
const experientVariantsAssignments = encodedExperienceVariantsMap.split(',');
|
|
468
|
+
const experienceVariantsMap = {};
|
|
469
|
+
for (const experienceVariantAssignment of experientVariantsAssignments) {
|
|
470
|
+
const [experienceId, variantIndexString] = experienceVariantAssignment.split('=');
|
|
471
|
+
const variantIndex = parseInt(variantIndexString);
|
|
406
472
|
if (!experienceId || !variantIndex) {
|
|
407
|
-
|
|
473
|
+
continue;
|
|
408
474
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
};
|
|
413
|
-
}).filter(x => !!x).reduce((acc, curr) => Object.assign({}, acc, {
|
|
414
|
-
[curr.experienceId]: curr.variantIndex
|
|
415
|
-
}), {});
|
|
475
|
+
experienceVariantsMap[experienceId] = variantIndex;
|
|
476
|
+
}
|
|
477
|
+
return experienceVariantsMap;
|
|
416
478
|
};
|
|
417
479
|
|
|
418
480
|
class OnChangeEmitter {
|
|
@@ -474,6 +536,28 @@ const createPassThroughMiddleware = () => {
|
|
|
474
536
|
};
|
|
475
537
|
};
|
|
476
538
|
};
|
|
539
|
+
function createExperienceSelectionMiddleware({
|
|
540
|
+
plugins,
|
|
541
|
+
experiences,
|
|
542
|
+
baseline,
|
|
543
|
+
profile
|
|
544
|
+
}) {
|
|
545
|
+
if (profile === null) {
|
|
546
|
+
return createPassThroughMiddleware();
|
|
547
|
+
}
|
|
548
|
+
const pluginsWithMiddleware = selectPluginsHavingExperienceSelectionMiddleware(plugins);
|
|
549
|
+
const middlewareFunctions = [];
|
|
550
|
+
for (const plugin of pluginsWithMiddleware) {
|
|
551
|
+
const middleware = plugin.getExperienceSelectionMiddleware({
|
|
552
|
+
experiences,
|
|
553
|
+
baseline
|
|
554
|
+
});
|
|
555
|
+
if (middleware !== undefined) {
|
|
556
|
+
middlewareFunctions.push(middleware);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return pipe(...middlewareFunctions);
|
|
560
|
+
}
|
|
477
561
|
const makeExperienceSelectMiddleware = ({
|
|
478
562
|
plugins,
|
|
479
563
|
onChange,
|
|
@@ -483,18 +567,12 @@ const makeExperienceSelectMiddleware = ({
|
|
|
483
567
|
}) => {
|
|
484
568
|
let removeChangeListeners = [];
|
|
485
569
|
const pluginsHavingChangeEmitters = selectPluginsHavingOnChangeEmitter(plugins);
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
experiences,
|
|
493
|
-
baseline
|
|
494
|
-
})).filter(result => typeof result !== 'undefined');
|
|
495
|
-
return pipe(...middlewareFunctions);
|
|
496
|
-
};
|
|
497
|
-
const middleware = prepareMiddleware();
|
|
570
|
+
const middleware = createExperienceSelectionMiddleware({
|
|
571
|
+
plugins,
|
|
572
|
+
experiences,
|
|
573
|
+
baseline,
|
|
574
|
+
profile
|
|
575
|
+
});
|
|
498
576
|
const addListeners = () => {
|
|
499
577
|
removeChangeListeners = pluginsHavingChangeEmitters.map(plugin => {
|
|
500
578
|
const listener = () => {
|
|
@@ -503,8 +581,13 @@ const makeExperienceSelectMiddleware = ({
|
|
|
503
581
|
return plugin.onChangeEmitter.addListener(listener);
|
|
504
582
|
});
|
|
505
583
|
};
|
|
584
|
+
|
|
585
|
+
// WARNING: This specific implementation using forEach is required.
|
|
586
|
+
// DO NOT replace with for...of or other loop constructs as they will break functionality.
|
|
587
|
+
// The exact reason is uncertain but appears related to the transplier.
|
|
588
|
+
// TODO: Come back and find out why this is the case, maybe a version bump is in order.
|
|
506
589
|
const removeListeners = () => {
|
|
507
|
-
removeChangeListeners.forEach(
|
|
590
|
+
removeChangeListeners.forEach(removeListener => removeListener());
|
|
508
591
|
};
|
|
509
592
|
return {
|
|
510
593
|
addListeners,
|
|
@@ -518,44 +601,36 @@ class EventBuilder {
|
|
|
518
601
|
this.buildRequestContext = void 0;
|
|
519
602
|
this.buildRequestContext = buildRequestContext || buildClientNinetailedRequestContext;
|
|
520
603
|
}
|
|
521
|
-
|
|
522
|
-
return
|
|
604
|
+
buildEventBase(data) {
|
|
605
|
+
return Object.assign({
|
|
523
606
|
messageId: (data == null ? void 0 : data.messageId) || v4()
|
|
524
607
|
}, data, {
|
|
525
608
|
timestamp: Date.now(),
|
|
526
|
-
properties: properties || {},
|
|
527
609
|
ctx: this.buildRequestContext()
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
page(properties, data) {
|
|
613
|
+
return buildPageEvent(Object.assign({}, this.buildEventBase(data), {
|
|
614
|
+
properties: properties || {}
|
|
528
615
|
}));
|
|
529
616
|
}
|
|
530
617
|
track(event, properties, data) {
|
|
531
|
-
return buildTrackEvent(Object.assign({
|
|
532
|
-
messageId: (data == null ? void 0 : data.messageId) || v4(),
|
|
533
|
-
timestamp: Date.now()
|
|
534
|
-
}, data, {
|
|
618
|
+
return buildTrackEvent(Object.assign({}, this.buildEventBase(data), {
|
|
535
619
|
event,
|
|
536
|
-
properties: properties || {}
|
|
537
|
-
ctx: this.buildRequestContext()
|
|
620
|
+
properties: properties || {}
|
|
538
621
|
}));
|
|
539
622
|
}
|
|
540
623
|
identify(userId, traits, data) {
|
|
541
|
-
return buildIdentifyEvent(Object.assign({
|
|
542
|
-
messageId: (data == null ? void 0 : data.messageId) || v4(),
|
|
543
|
-
timestamp: Date.now()
|
|
544
|
-
}, data, {
|
|
624
|
+
return buildIdentifyEvent(Object.assign({}, this.buildEventBase(data), {
|
|
545
625
|
traits: traits || {},
|
|
546
|
-
userId: userId || ''
|
|
547
|
-
ctx: this.buildRequestContext()
|
|
626
|
+
userId: userId || ''
|
|
548
627
|
}));
|
|
549
628
|
}
|
|
550
629
|
component(componentId, experienceId, variantIndex, data) {
|
|
551
|
-
return buildComponentViewEvent(Object.assign({
|
|
552
|
-
messageId: (data == null ? void 0 : data.messageId) || v4(),
|
|
553
|
-
timestamp: Date.now()
|
|
554
|
-
}, data, {
|
|
630
|
+
return buildComponentViewEvent(Object.assign({}, this.buildEventBase(data), {
|
|
555
631
|
componentId,
|
|
556
632
|
experienceId: experienceId || '',
|
|
557
|
-
variantIndex: variantIndex || 0
|
|
558
|
-
ctx: this.buildRequestContext()
|
|
633
|
+
variantIndex: variantIndex || 0
|
|
559
634
|
}));
|
|
560
635
|
}
|
|
561
636
|
}
|
|
@@ -628,11 +703,7 @@ class Ninetailed {
|
|
|
628
703
|
await _this.instance.page(data, _this.buildOptions(options));
|
|
629
704
|
return _this.ninetailedCorePlugin.flush();
|
|
630
705
|
} catch (error) {
|
|
631
|
-
|
|
632
|
-
if (error instanceof RangeError) {
|
|
633
|
-
throw new Error(`[Validation Error] "page" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
634
|
-
}
|
|
635
|
-
throw error;
|
|
706
|
+
return _this.handleMethodError(error, 'page');
|
|
636
707
|
}
|
|
637
708
|
};
|
|
638
709
|
this.track = async function (event, properties, options) {
|
|
@@ -645,11 +716,7 @@ class Ninetailed {
|
|
|
645
716
|
await _this.instance.track(event.toString(), result.data, _this.buildOptions(options));
|
|
646
717
|
return _this.ninetailedCorePlugin.flush();
|
|
647
718
|
} catch (error) {
|
|
648
|
-
|
|
649
|
-
if (error instanceof RangeError) {
|
|
650
|
-
throw new Error(`[Validation Error] "track" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
651
|
-
}
|
|
652
|
-
throw error;
|
|
719
|
+
_this.handleMethodError(error, 'track');
|
|
653
720
|
}
|
|
654
721
|
};
|
|
655
722
|
this.identify = async function (uid, traits, options) {
|
|
@@ -662,11 +729,7 @@ class Ninetailed {
|
|
|
662
729
|
await _this.instance.identify(uid && uid.toString() !== '' ? uid.toString() : EMPTY_MERGE_ID, result.data, _this.buildOptions(options));
|
|
663
730
|
return _this.ninetailedCorePlugin.flush();
|
|
664
731
|
} catch (error) {
|
|
665
|
-
|
|
666
|
-
if (error instanceof RangeError) {
|
|
667
|
-
throw new Error(`[Validation Error] "identify" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
668
|
-
}
|
|
669
|
-
throw error;
|
|
732
|
+
_this.handleMethodError(error, 'identify');
|
|
670
733
|
}
|
|
671
734
|
};
|
|
672
735
|
this.batch = async function (events) {
|
|
@@ -695,11 +758,7 @@ class Ninetailed {
|
|
|
695
758
|
await Promise.all(promises);
|
|
696
759
|
return _this.ninetailedCorePlugin.flush();
|
|
697
760
|
} catch (error) {
|
|
698
|
-
|
|
699
|
-
if (error instanceof RangeError) {
|
|
700
|
-
throw new Error(`[Validation Error] "batch" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
701
|
-
}
|
|
702
|
-
throw error;
|
|
761
|
+
_this.handleMethodError(error, 'batch');
|
|
703
762
|
}
|
|
704
763
|
};
|
|
705
764
|
this.trackStickyComponentView = async function ({
|
|
@@ -717,11 +776,7 @@ class Ninetailed {
|
|
|
717
776
|
});
|
|
718
777
|
return _this.ninetailedCorePlugin.flush();
|
|
719
778
|
} catch (error) {
|
|
720
|
-
|
|
721
|
-
if (error instanceof RangeError) {
|
|
722
|
-
throw new Error(`[Validation Error] "trackStickyComponentView" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
723
|
-
}
|
|
724
|
-
throw error;
|
|
779
|
+
_this.handleMethodError(error, 'trackStickyComponentView');
|
|
725
780
|
}
|
|
726
781
|
};
|
|
727
782
|
/**
|
|
@@ -744,33 +799,13 @@ class Ninetailed {
|
|
|
744
799
|
const {
|
|
745
800
|
element
|
|
746
801
|
} = payload,
|
|
747
|
-
|
|
802
|
+
remainingPayload = _objectWithoutPropertiesLoose(payload, _excluded2);
|
|
748
803
|
if (!(element instanceof Element)) {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
|
|
752
|
-
logger.warn(`ElementSeenObserver.observeElement was called with an invalid element. Expected an Element but got ${typeof element}${isConstructorNameNotObject ? ` of type ${constructorName}` : ''}. This call will be ignored.`);
|
|
753
|
-
} else {
|
|
754
|
-
const existingPayloads = this.observedElements.get(element);
|
|
755
|
-
const delays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
|
|
756
|
-
const uniqueDelays = Array.from(new Set([...delays, options == null ? void 0 : options.delay]));
|
|
757
|
-
if (!existingPayloads) {
|
|
758
|
-
this.observedElements.set(element, [remaingPayload]);
|
|
759
|
-
} else {
|
|
760
|
-
const isPayloadAlreadyObserved = existingPayloads.some(payload => {
|
|
761
|
-
return JSON.stringify(payload) === JSON.stringify(remaingPayload);
|
|
762
|
-
});
|
|
763
|
-
if (isPayloadAlreadyObserved) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
this.observedElements.set(element, [...existingPayloads, remaingPayload]);
|
|
767
|
-
}
|
|
768
|
-
uniqueDelays.forEach(delay => {
|
|
769
|
-
this.elementSeenObserver.observe(element, {
|
|
770
|
-
delay
|
|
771
|
-
});
|
|
772
|
-
});
|
|
804
|
+
this.logInvalidElement(element);
|
|
805
|
+
return;
|
|
773
806
|
}
|
|
807
|
+
this.storeElementPayload(element, remainingPayload);
|
|
808
|
+
this.setupElementObservation(element, options == null ? void 0 : options.delay);
|
|
774
809
|
};
|
|
775
810
|
this.unobserveElement = element => {
|
|
776
811
|
this.observedElements.delete(element);
|
|
@@ -817,6 +852,7 @@ class Ninetailed {
|
|
|
817
852
|
cb(Object.assign({}, this._profileState, {
|
|
818
853
|
status: 'success',
|
|
819
854
|
profile: payload.profile,
|
|
855
|
+
changes: payload.changes,
|
|
820
856
|
experiences: payload.experiences,
|
|
821
857
|
error: null
|
|
822
858
|
}));
|
|
@@ -985,14 +1021,26 @@ class Ninetailed {
|
|
|
985
1021
|
removeMiddlewareChangeListeners();
|
|
986
1022
|
};
|
|
987
1023
|
};
|
|
988
|
-
|
|
989
|
-
|
|
1024
|
+
/**
|
|
1025
|
+
* Registers a callback to be notified when changes occur in the profile state.
|
|
1026
|
+
*
|
|
1027
|
+
* @param cb - Callback function that receives the changes state
|
|
1028
|
+
* @returns Function to unsubscribe from changes updates
|
|
1029
|
+
*/
|
|
1030
|
+
this.onChangesChange = cb => {
|
|
1031
|
+
this.notifyChangesCallback(cb, this._profileState);
|
|
1032
|
+
return this.onProfileChange(profileState => {
|
|
1033
|
+
this.notifyChangesCallback(cb, profileState);
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
this.onIsInitialized = onIsInitializedCallback => {
|
|
1037
|
+
if (typeof onIsInitializedCallback === 'function') {
|
|
990
1038
|
if (this.isInitialized) {
|
|
991
|
-
|
|
1039
|
+
onIsInitializedCallback();
|
|
992
1040
|
} else {
|
|
993
1041
|
const detachOnReadyListener = this.instance.on('ready', () => {
|
|
994
1042
|
this.isInitialized = true;
|
|
995
|
-
|
|
1043
|
+
onIsInitializedCallback();
|
|
996
1044
|
detachOnReadyListener();
|
|
997
1045
|
});
|
|
998
1046
|
}
|
|
@@ -1072,6 +1120,7 @@ class Ninetailed {
|
|
|
1072
1120
|
status: 'loading',
|
|
1073
1121
|
profile: null,
|
|
1074
1122
|
experiences: null,
|
|
1123
|
+
changes: null,
|
|
1075
1124
|
error: null,
|
|
1076
1125
|
from: 'api'
|
|
1077
1126
|
};
|
|
@@ -1119,6 +1168,72 @@ class Ninetailed {
|
|
|
1119
1168
|
get pluginsWithCustomComponentViewThreshold() {
|
|
1120
1169
|
return [this.ninetailedCorePlugin, ...this.plugins].filter(plugin => hasComponentViewTrackingThreshold(plugin));
|
|
1121
1170
|
}
|
|
1171
|
+
logInvalidElement(element) {
|
|
1172
|
+
const isObject = typeof element === 'object' && element !== null;
|
|
1173
|
+
const constructorName = isObject ? element.constructor.name : '';
|
|
1174
|
+
const isConstructorNameNotObject = constructorName && constructorName !== 'Object';
|
|
1175
|
+
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.`);
|
|
1176
|
+
}
|
|
1177
|
+
storeElementPayload(element, payload) {
|
|
1178
|
+
const existingPayloads = this.observedElements.get(element) || [];
|
|
1179
|
+
|
|
1180
|
+
// Check if the payload is already being observed for this element
|
|
1181
|
+
const isPayloadAlreadyObserved = existingPayloads.some(existingPayload => JSON.stringify(existingPayload) === JSON.stringify(payload));
|
|
1182
|
+
if (isPayloadAlreadyObserved) {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// Store the new or updated payloads
|
|
1187
|
+
this.observedElements.set(element, [...existingPayloads, payload]);
|
|
1188
|
+
}
|
|
1189
|
+
setupElementObservation(element, delay) {
|
|
1190
|
+
// Get all relevant delays from plugins and the custom delay
|
|
1191
|
+
const pluginDelays = this.pluginsWithCustomComponentViewThreshold.map(plugin => plugin.getComponentViewTrackingThreshold());
|
|
1192
|
+
|
|
1193
|
+
// Ensure we only observe each delay once
|
|
1194
|
+
const uniqueDelays = Array.from(new Set([...pluginDelays, delay]));
|
|
1195
|
+
|
|
1196
|
+
// Set up observation for each delay
|
|
1197
|
+
uniqueDelays.forEach(delay => {
|
|
1198
|
+
this.elementSeenObserver.observe(element, {
|
|
1199
|
+
delay
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Helper method to extract changes state from profile state and notify callback
|
|
1205
|
+
* @private
|
|
1206
|
+
*/
|
|
1207
|
+
notifyChangesCallback(cb, profileState) {
|
|
1208
|
+
if (profileState.status === 'loading') {
|
|
1209
|
+
cb({
|
|
1210
|
+
status: 'loading',
|
|
1211
|
+
changes: null,
|
|
1212
|
+
error: null
|
|
1213
|
+
});
|
|
1214
|
+
} else if (profileState.status === 'error') {
|
|
1215
|
+
cb({
|
|
1216
|
+
status: 'error',
|
|
1217
|
+
changes: null,
|
|
1218
|
+
error: profileState.error
|
|
1219
|
+
});
|
|
1220
|
+
} else {
|
|
1221
|
+
cb({
|
|
1222
|
+
status: 'success',
|
|
1223
|
+
changes: profileState.changes,
|
|
1224
|
+
error: null
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Always throws, never returns
|
|
1230
|
+
handleMethodError(error, method) {
|
|
1231
|
+
logger.error(error);
|
|
1232
|
+
if (error instanceof RangeError) {
|
|
1233
|
+
throw new Error(`[Validation Error] "${method}" was called with invalid params. Could not validate due to "RangeError: Maximum call stack size exceeded". This can be caused by passing a cyclic data structure as a parameter. Refrain from passing a cyclic data structure or sanitize it beforehand.`);
|
|
1234
|
+
}
|
|
1235
|
+
throw error;
|
|
1236
|
+
}
|
|
1122
1237
|
get profileState() {
|
|
1123
1238
|
return this._profileState;
|
|
1124
1239
|
}
|
|
@@ -1228,4 +1343,4 @@ const selectVariant = (baseline, variants, {
|
|
|
1228
1343
|
};
|
|
1229
1344
|
};
|
|
1230
1345
|
|
|
1231
|
-
export { ANONYMOUS_ID, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, EventBuilder, HAS_SEEN_STICKY_COMPONENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedCorePlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeExperienceSelectMiddleware, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };
|
|
1346
|
+
export { ANONYMOUS_ID, CHANGES_FALLBACK_CACHE, COMPONENT, COMPONENT_START, CONSENT, DEBUG_FLAG, EMPTY_MERGE_ID, EXPERIENCES_FALLBACK_CACHE, EventBuilder, HAS_SEEN_STICKY_COMPONENT, LEGACY_ANONYMOUS_ID, Ninetailed, NinetailedCorePlugin, OnChangeEmitter, PAGE_HIDDEN, PLUGIN_NAME, PROFILE_CHANGE, PROFILE_FALLBACK_CACHE, PROFILE_RESET, SET_ENABLED_FEATURES, buildClientNinetailedRequestContext, decodeExperienceVariantsMap, makeExperienceSelectMiddleware, selectPluginsHavingExperienceSelectionMiddleware, selectPluginsHavingOnChangeEmitter, selectVariant };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninetailed/experience.js",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.12.0-beta.0",
|
|
4
4
|
"description": "Ninetailed SDK for javascript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"directory": "packages/sdks/javascript"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@ninetailed/experience.js-plugin-analytics": "7.
|
|
13
|
-
"@ninetailed/experience.js-shared": "7.
|
|
12
|
+
"@ninetailed/experience.js-plugin-analytics": "7.12.0-beta.0",
|
|
13
|
+
"@ninetailed/experience.js-shared": "7.12.0-beta.0",
|
|
14
14
|
"analytics": "0.8.1",
|
|
15
15
|
"uuid": "9.0.0"
|
|
16
16
|
},
|
package/src/lib/Ninetailed.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="analytics" />
|
|
2
2
|
import { Locale, Traits, OnLogHandler, OnErrorHandler, Logger, PageviewProperties, Properties, NinetailedApiClient, NinetailedApiClientOptions, NinetailedRequestContext, Reference, Event } from '@ninetailed/experience.js-shared';
|
|
3
3
|
import { OnInitProfileId } from './NinetailedCorePlugin';
|
|
4
|
-
import { EventFunctionOptions, NinetailedInstance, OnIsInitializedCallback, OnProfileChangeCallback, ProfileState, TrackHasSeenComponent, TrackComponentView } from './types';
|
|
4
|
+
import { EventFunctionOptions, NinetailedInstance, OnIsInitializedCallback, OnProfileChangeCallback, ProfileState, TrackHasSeenComponent, TrackComponentView, OnChangesChangeCallback } from './types';
|
|
5
5
|
import { ObserveOptions } from './ElementSeenObserver';
|
|
6
6
|
import { OnSelectVariantArgs, OnSelectVariantCallback } from './types/OnSelectVariant';
|
|
7
7
|
import { ElementSeenPayload, NinetailedPlugin } from '@ninetailed/experience.js-plugin-analytics';
|
|
@@ -71,13 +71,29 @@ export declare class Ninetailed implements NinetailedInstance {
|
|
|
71
71
|
trackComponentView: TrackComponentView;
|
|
72
72
|
private get pluginsWithCustomComponentViewThreshold();
|
|
73
73
|
observeElement: (payload: ElementSeenPayload, options?: ObserveOptions) => void;
|
|
74
|
+
private logInvalidElement;
|
|
75
|
+
private storeElementPayload;
|
|
76
|
+
private setupElementObservation;
|
|
74
77
|
unobserveElement: (element: Element) => void;
|
|
75
78
|
private onElementSeen;
|
|
76
79
|
reset: () => Promise<void>;
|
|
77
80
|
debug: (enabled: boolean) => Promise<void>;
|
|
78
81
|
onProfileChange: (cb: OnProfileChangeCallback) => import("analytics").DetachListeners;
|
|
79
82
|
onSelectVariant: <Baseline extends Reference, Variant extends Reference>({ baseline, experiences }: OnSelectVariantArgs<Baseline, Variant>, cb: OnSelectVariantCallback<Baseline, Variant>) => () => void;
|
|
80
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Registers a callback to be notified when changes occur in the profile state.
|
|
85
|
+
*
|
|
86
|
+
* @param cb - Callback function that receives the changes state
|
|
87
|
+
* @returns Function to unsubscribe from changes updates
|
|
88
|
+
*/
|
|
89
|
+
onChangesChange: (cb: OnChangesChangeCallback) => import("analytics").DetachListeners;
|
|
90
|
+
/**
|
|
91
|
+
* Helper method to extract changes state from profile state and notify callback
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
private notifyChangesCallback;
|
|
95
|
+
private handleMethodError;
|
|
96
|
+
onIsInitialized: (onIsInitializedCallback: OnIsInitializedCallback) => void;
|
|
81
97
|
private waitUntilInitialized;
|
|
82
98
|
get profileState(): ProfileState;
|
|
83
99
|
private buildOptions;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { AnalyticsInstance } from 'analytics';
|
|
1
|
+
import { AnalyticsInstance, DetachListeners } from 'analytics';
|
|
2
2
|
import { Locale, NinetailedApiClient, NinetailedRequestContext } from '@ninetailed/experience.js-shared';
|
|
3
3
|
import { NinetailedAnalyticsPlugin, SanitizedElementSeenPayload } from '@ninetailed/experience.js-plugin-analytics';
|
|
4
4
|
import { SET_ENABLED_FEATURES } from './constants';
|
|
5
5
|
import { NinetailedInstance, FlushResult } from '../types';
|
|
6
6
|
import { HAS_SEEN_STICKY_COMPONENT } from '../constants';
|
|
7
|
+
import { DispatchAction, ActionPayloadMap } from './actions';
|
|
7
8
|
export type OnInitProfileId = (profileId?: string) => Promise<string | undefined> | string | undefined;
|
|
8
9
|
type AnalyticsPluginNinetailedConfig = {
|
|
9
10
|
apiClient: NinetailedApiClient;
|
|
@@ -16,14 +17,15 @@ type AnalyticsPluginNinetailedConfig = {
|
|
|
16
17
|
export declare const PLUGIN_NAME = "ninetailed:core";
|
|
17
18
|
type EventFn = {
|
|
18
19
|
payload: any;
|
|
19
|
-
instance:
|
|
20
|
+
instance: EventHandlerAnalyticsInstance;
|
|
20
21
|
};
|
|
21
22
|
type AbortableFnParams = {
|
|
22
23
|
abort: () => void;
|
|
23
24
|
payload: unknown;
|
|
24
25
|
};
|
|
25
|
-
type
|
|
26
|
-
dispatch: (action:
|
|
26
|
+
export type EventHandlerAnalyticsInstance = Omit<AnalyticsInstance, 'on' | 'dispatch'> & {
|
|
27
|
+
dispatch: (action: DispatchAction) => Promise<void>;
|
|
28
|
+
on<T extends DispatchAction['type']>(action: T, handler: (data: ActionPayloadMap[T]) => void): DetachListeners;
|
|
27
29
|
};
|
|
28
30
|
export interface NinetailedCorePlugin extends NinetailedAnalyticsPlugin {
|
|
29
31
|
flush: (args: void) => Promise<FlushResult>;
|
|
@@ -40,7 +42,7 @@ export declare class NinetailedCorePlugin extends NinetailedAnalyticsPlugin impl
|
|
|
40
42
|
private readonly ninetailed;
|
|
41
43
|
constructor({ apiClient, locale, ninetailed, onInitProfileId, buildClientContext, }: AnalyticsPluginNinetailedConfig);
|
|
42
44
|
initialize({ instance, }: {
|
|
43
|
-
instance:
|
|
45
|
+
instance: EventHandlerAnalyticsInstance;
|
|
44
46
|
}): Promise<void>;
|
|
45
47
|
[SET_ENABLED_FEATURES]: ({ payload }: EventFn) => Promise<void>;
|
|
46
48
|
pageStart(params: AbortableFnParams): unknown;
|
|
@@ -65,6 +67,20 @@ export declare class NinetailedCorePlugin extends NinetailedAnalyticsPlugin impl
|
|
|
65
67
|
private static abortNonClientEvents;
|
|
66
68
|
private get instance();
|
|
67
69
|
flush: (args: void) => Promise<FlushResult>;
|
|
70
|
+
private getAnonymousId;
|
|
71
|
+
private setAnonymousId;
|
|
72
|
+
private clearAnonymousId;
|
|
73
|
+
private setFallbackProfile;
|
|
74
|
+
private getFallbackProfile;
|
|
75
|
+
private clearFallbackProfile;
|
|
76
|
+
private setFallbackExperiences;
|
|
77
|
+
private getFallbackExperiences;
|
|
78
|
+
private clearFallbackExperiences;
|
|
79
|
+
private setFallbackChanges;
|
|
80
|
+
private getFallbackChanges;
|
|
81
|
+
private clearFallbackChanges;
|
|
82
|
+
private clearCaches;
|
|
83
|
+
private populateCaches;
|
|
68
84
|
private _flush;
|
|
69
85
|
}
|
|
70
86
|
export {};
|