@ninetailed/experience.js-plugin-analytics
Version:
Ninetailed SDK plugin for analytics
210 lines (200 loc) • 8.53 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var radash = require('radash');
var experience_jsShared = require('@ninetailed/experience.js-shared');
var zod = require('zod');
// Base schema with shared properties
const BaseSeenPayloadSchema = zod.z.object({
componentType: zod.z.union([zod.z.literal('Entry'), zod.z.literal('Variable')]),
variant: zod.z.object({
id: zod.z.string()
}).catchall(zod.z.unknown()),
variantIndex: zod.z.number()
});
// Element specific schema
const ElementSeenPayloadSchema = BaseSeenPayloadSchema.extend({
element: zod.z.any(),
experience: zod.z.object({
id: zod.z.string(),
type: zod.z.union([zod.z.literal('nt_experiment'), zod.z.literal('nt_personalization')]),
name: zod.z.string().optional(),
description: zod.z.string().optional(),
sticky: zod.z.boolean().optional().default(false)
}).optional().nullable(),
audience: zod.z.object({
id: zod.z.string(),
name: zod.z.string().optional(),
description: zod.z.string().optional()
}).optional().nullable().default({
id: 'ALL_VISITORS',
name: 'All Visitors',
description: 'This is the default all visitors audience as no audience was set.'
}),
seenFor: zod.z.number().optional().default(0)
});
// Variable specific schema
const VariableSeenPayloadSchema = BaseSeenPayloadSchema.extend({
variable: experience_jsShared.SerializableObject,
experienceId: zod.z.string().optional()
});
const TrackComponentPropertiesSchema = zod.z.object({
variant: zod.z.object({
id: zod.z.string()
}),
audience: zod.z.object({
id: zod.z.string()
}),
isPersonalized: zod.z.boolean()
});
const HAS_SEEN_COMPONENT = 'has_seen_component';
const HAS_SEEN_ELEMENT_START = 'has_seen_elementStart';
const HAS_SEEN_ELEMENT = 'has_seen_element';
const HAS_SEEN_VARIABLE = 'has_seen_variable';
var _a$1, _b;
class NinetailedPlugin {
constructor() {
this.componentViewTrackingThreshold = 0;
this[_a$1] = event => {
if (event.payload.seenFor !== this.getComponentViewTrackingThreshold()) {
return;
}
this.onHasSeenElement(event);
};
this[_b] = event => {
this.onHasSeenVariable(event);
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.onHasSeenElement = () => {};
// eslint-disable-next-line @typescript-eslint/no-empty-function
this.onHasSeenVariable = () => {};
this.setComponentViewTrackingThreshold = threshold => {
this.componentViewTrackingThreshold = threshold;
};
this.getComponentViewTrackingThreshold = () => this.componentViewTrackingThreshold;
}
}
_a$1 = HAS_SEEN_ELEMENT, _b = HAS_SEEN_VARIABLE;
var _a;
const TEMPLATE_OPTIONS = {
interpolate: /{{([\s\S]+?)}}/g
};
class NinetailedAnalyticsPlugin extends NinetailedPlugin {
constructor(hasSeenExperienceEventTemplate = {}) {
super();
this.hasSeenExperienceEventTemplate = hasSeenExperienceEventTemplate;
this.seenElements = new WeakMap();
this.seenVariables = new Map();
this.getHasSeenExperienceEventPayload = data => {
const event = Object.entries(this.hasSeenExperienceEventTemplate).reduce((acc, [keyTemplate, valueTemplate]) => {
const key = () => {
try {
return experience_jsShared.template(keyTemplate, data, TEMPLATE_OPTIONS.interpolate);
} catch (error) {
experience_jsShared.logger.error(`Your Ninetailed Analytics Plugin's template is invalid. They key template ${keyTemplate} could not find the path in the specified experience.`);
return 'undefined';
}
};
const value = () => {
try {
return experience_jsShared.template(valueTemplate, data, TEMPLATE_OPTIONS.interpolate);
} catch (error) {
experience_jsShared.logger.error(`Your Ninetailed Analytics Plugin's template is invalid. They value template ${valueTemplate} could not find the path in the specified experience.`);
return 'undefined';
}
};
return Object.assign(Object.assign({}, acc), {
[key()]: value()
});
}, {});
return event;
};
this.onHasSeenElement = ({
payload
}) => {
const sanitizedPayload = ElementSeenPayloadSchema.safeParse(payload);
if (!sanitizedPayload.success) {
experience_jsShared.logger.error('Invalid payload for has_seen_element event', sanitizedPayload.error.format());
return;
}
if (!sanitizedPayload.data.experience || !sanitizedPayload.data.audience) {
return;
}
const elementPayloads = this.seenElements.get(payload.element) || [];
const selectedVariantSelector = sanitizedPayload.data.variantIndex === 0 ? 'control' : `variant ${sanitizedPayload.data.variantIndex}`;
const sanitizedTrackExperienceProperties = {
experience: sanitizedPayload.data.experience,
audience: sanitizedPayload.data.audience,
componentType: sanitizedPayload.data.componentType,
selectedVariant: sanitizedPayload.data.variant,
selectedVariantIndex: sanitizedPayload.data.variantIndex,
selectedVariantSelector
};
const isElementAlreadySeenWithPayload = elementPayloads.some(elementPayload => {
return radash.isEqual(elementPayload, sanitizedTrackExperienceProperties);
});
if (isElementAlreadySeenWithPayload) {
return;
}
this.seenElements.set(payload.element, [...elementPayloads, sanitizedTrackExperienceProperties]);
this.onTrackExperience(sanitizedTrackExperienceProperties, this.getHasSeenExperienceEventPayload(sanitizedTrackExperienceProperties));
};
this.onHasSeenVariable = ({
payload
}) => {
const sanitizedPayload = VariableSeenPayloadSchema.safeParse(payload);
if (!sanitizedPayload.success) {
experience_jsShared.logger.error('Invalid payload for has_seen_variable event', sanitizedPayload.error.format());
return;
}
const componentId = sanitizedPayload.data.variant.id;
if (typeof componentId === 'undefined') {
experience_jsShared.logger.error('Component ID is undefined in has_seen_variable event payload');
return;
}
const variableKey = componentId;
const variablePayloads = this.seenVariables.get(variableKey) || [];
const selectedVariantSelector = sanitizedPayload.data.variantIndex === 0 ? 'control' : `variant ${sanitizedPayload.data.variantIndex}`;
const sanitizedTrackVariableProperties = {
componentId,
componentType: sanitizedPayload.data.componentType,
selectedVariant: sanitizedPayload.data.variant,
selectedVariantIndex: sanitizedPayload.data.variantIndex,
selectedVariantSelector
};
const isVariableAlreadySeenWithPayload = variablePayloads.some(variablePayload => {
return radash.isEqual(variablePayload, sanitizedTrackVariableProperties);
});
if (isVariableAlreadySeenWithPayload) {
return;
}
this.seenVariables.set(variableKey, [...variablePayloads, sanitizedTrackVariableProperties]);
};
/**
* @deprecated
*/
this[_a] = ({
payload
}) => {
const sanitizedPayload = TrackComponentPropertiesSchema.safeParse(payload);
if (!sanitizedPayload.success) {
experience_jsShared.logger.error('Invalid payload for has_seen_component event', sanitizedPayload.error.format());
return;
}
this.onTrackComponent(sanitizedPayload.data);
};
}
}
_a = HAS_SEEN_COMPONENT;
const hasComponentViewTrackingThreshold = arg => {
return typeof arg === 'object' && arg !== null && 'getComponentViewTrackingThreshold' in arg && typeof arg['getComponentViewTrackingThreshold'] === 'function' && 'setComponentViewTrackingThreshold' in arg && typeof arg['setComponentViewTrackingThreshold'] === 'function';
};
exports.ElementSeenPayloadSchema = ElementSeenPayloadSchema;
exports.HAS_SEEN_COMPONENT = HAS_SEEN_COMPONENT;
exports.HAS_SEEN_ELEMENT = HAS_SEEN_ELEMENT;
exports.HAS_SEEN_ELEMENT_START = HAS_SEEN_ELEMENT_START;
exports.HAS_SEEN_VARIABLE = HAS_SEEN_VARIABLE;
exports.NinetailedAnalyticsPlugin = NinetailedAnalyticsPlugin;
exports.NinetailedPlugin = NinetailedPlugin;
exports.TrackComponentPropertiesSchema = TrackComponentPropertiesSchema;
exports.VariableSeenPayloadSchema = VariableSeenPayloadSchema;
exports.hasComponentViewTrackingThreshold = hasComponentViewTrackingThreshold;