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