UNPKG

@ninetailed/experience.js-plugin-analytics

Version:
210 lines (200 loc) 8.53 kB
'use strict'; 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;