@chordcommerce/analytics
Version:
Chord Commerce event tracking
913 lines • 77.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { addCdpSnippet, cdpQueue } from './cdp.js';
import { addCdpConsent } from './consent/index.js';
import { eventSchemas } from '../validators/index.js';
import { pruneNullValues } from '../utils.js';
const DEBUG_MODE_STORAGE_KEY = 'chordcommerce/analytics/debug';
const CONSENT_QUEUE_STORAGE_KEY = 'chordcommerce/analytics/consent-queue';
const CONSENT_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const CONSENT_QUEUE_MAX_SIZE_BYTES = 100 * 1024; // 100KB max queue size
const CONSENT_QUEUE_MAX_EVENTS = 50; // Max number of events to queue
export class ChordAnalytics {
constructor(options) {
var _a, _b, _c, _d;
/**
* Allows snippet.js to detect whether this library has been initialized yet.
*/
this.initialize = true;
/**
* Allows snippet.js to detect whether the snippet has started running yet.
*/
this.invoked = true;
/**
* Middleware to be applied to the event properties
*/
this.middleware = [];
/**
* Promise that resolves when CDP is ready
*/
this.cdpReadyPromise = null;
this.consentConfig = null;
// Non-null while reset() is in flight. Acts as both the coalescing handle and
// the gate that track/identify/page await before executing.
this.resetCompletePromise = null;
/**
* Queue for events waiting for consent to be ready
*/
this.consentEventQueue = [];
/**
* Whether we're currently waiting for consent and watching for it
*/
this.consentWatcherActive = false;
/**
* Promise that resolves when consent is ready (shared across all queued events)
*/
this.consentReadyPromise = null;
/**
* Persist the consent event queue to sessionStorage for recovery on instance recreation.
* Validates queue size before storing to prevent QuotaExceededError.
*/
this.persistQueueToStorage = () => {
if (typeof window === 'undefined' || !window.sessionStorage)
return;
try {
// Limit number of events to prevent unbounded queue growth
const eventsToStore = this.consentEventQueue.slice(-CONSENT_QUEUE_MAX_EVENTS);
const persistedEvents = eventsToStore.map((item) => ({
type: item.type,
args: item.args,
timestamp: item.timestamp,
}));
const serialized = JSON.stringify(persistedEvents);
// Check serialized size before attempting to store
const sizeInBytes = new Blob([serialized]).size;
if (sizeInBytes > CONSENT_QUEUE_MAX_SIZE_BYTES) {
this.logger(`[Chord Analytics]: Consent queue too large (${Math.round(sizeInBytes / 1024)}KB), skipping persistence`);
return;
}
window.sessionStorage.setItem(CONSENT_QUEUE_STORAGE_KEY, serialized);
}
catch (error) {
this.logger('[Chord Analytics]: Error persisting queue to storage:', error);
}
};
/**
* Restore pending events from sessionStorage (keeps events less than 5 min old).
* Called on new instance initialization.
*/
this.restoreQueueFromStorage = () => {
if (typeof window === 'undefined' || !window.sessionStorage)
return;
try {
const stored = window.sessionStorage.getItem(CONSENT_QUEUE_STORAGE_KEY);
if (!stored)
return;
let persistedEvents;
try {
persistedEvents = JSON.parse(stored);
}
catch (parseError) {
this.logger('[Chord Analytics]: Invalid JSON in stored consent queue, clearing storage:', parseError);
window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
return;
}
// Validate that parsed data is an array
if (!Array.isArray(persistedEvents)) {
this.logger('[Chord Analytics]: Stored consent queue is not an array, clearing storage');
window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
return;
}
const now = Date.now();
// Filter out expired events and events with invalid structure
const validEvents = persistedEvents.filter((event) => {
// Validate event structure
if (typeof event !== 'object' || event === null)
return false;
if (!['track', 'page', 'identify'].includes(event.type))
return false;
if (!Array.isArray(event.args))
return false;
if (typeof event.timestamp !== 'number')
return false;
// Filter out expired events
return now - event.timestamp < CONSENT_QUEUE_TTL_MS;
});
if (validEvents.length === 0) {
window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
return;
}
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log(`[Chord Analytics DEBUG]: Restoring ${validEvents.length} events from sessionStorage`);
}
// Add restored events to the queue
validEvents.forEach((event) => {
this.consentEventQueue.push({
type: event.type,
args: event.args,
timestamp: event.timestamp,
// eslint-disable-next-line @typescript-eslint/no-empty-function
resolve: () => { },
});
});
// Start watching for consent if we restored events
if (validEvents.length > 0 && !this.consentWatcherActive) {
this.consentWatcherActive = true;
this.watchForConsentAndFlush();
}
}
catch (error) {
this.logger('[Chord Analytics]: Error restoring queue from storage:', error);
// Clear corrupted storage
try {
window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
}
catch (_a) {
// Ignore cleanup errors
}
}
};
/**
* Clear the persisted queue from sessionStorage after successful flush.
*/
this.clearQueueFromStorage = () => {
if (typeof window === 'undefined' || !window.sessionStorage)
return;
try {
window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
}
catch (error) {
this.logger('[Chord Analytics]: Error clearing queue from storage:', error);
}
};
/**
* Set debug mode for Chord Analytics
* @param enabled - Whether debug mode should be enabled
*/
// eslint-disable-next-line class-methods-use-this
this.setDebugMode = (enabled) => {
if (window === null || window === void 0 ? void 0 : window.localStorage) {
window.localStorage.setItem(DEBUG_MODE_STORAGE_KEY, enabled.toString());
}
};
/*
* Return the CDP instance provided in `options.cdp`.
*/
this.cdp = () => {
var _a;
const a = (_a = this.options) === null || _a === void 0 ? void 0 : _a.cdp;
if (typeof a === 'function')
return a();
return a;
};
/*
* Return the Chord CDP instance or queue.
*/
this.ccdp = () => {
const { cdpDomain, cdpWriteKey, namespace } = this.options;
if (!cdpDomain || !cdpWriteKey)
return null;
return (window === null || window === void 0 ? void 0 : window[`_${namespace}`]) || cdpQueue(namespace);
};
/*
* Log a message to the console if `options.enableLogging` is true.
*/
this.logger = (message, ...optionalParams) => {
if (!this.options.enableLogging)
return;
// eslint-disable-next-line no-console
console.log(message, ...optionalParams);
};
this.isResetting = () => {
return this.resetCompletePromise !== null;
};
// Single gate for all outgoing operations. Returns a Promise only when there
// is actually something to wait for, preserving synchronous execution in the
// common case where CDP is ready and no reset is in flight.
this.waitForReadyState = () => {
const cdp = this.cdpReadyPromise;
const reset = this.resetCompletePromise;
if (!cdp && !reset)
return undefined;
// eslint-disable-next-line @typescript-eslint/no-empty-function
if (cdp && reset)
return Promise.all([cdp, reset]).then(() => { });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return cdp !== null && cdp !== void 0 ? cdp : reset;
};
/**
* Wait for consent manager to be ready (no timeout - queues indefinitely).
* Uses a shared promise so multiple events share the same watcher.
*/
this.waitForConsent = () => __awaiter(this, void 0, void 0, function* () {
if (!this.consentConfig)
return;
if (this.consentConfig.isConsentReady())
return;
// If already waiting, return the existing promise
if (this.consentReadyPromise) {
yield this.consentReadyPromise;
return;
}
// Start a new consent watcher - polls until consent is ready
this.consentReadyPromise = new Promise((resolve) => {
const interval = setInterval(() => {
var _a;
if ((_a = this.consentConfig) === null || _a === void 0 ? void 0 : _a.isConsentReady()) {
clearInterval(interval);
this.consentReadyPromise = null;
resolve(true);
}
}, 100);
});
yield this.consentReadyPromise;
});
/**
* Queue an event to be sent when consent is ready.
* Persists to sessionStorage for recovery on instance recreation.
* Starts a consent watcher if not already active.
*/
this.queueEventForConsent = (type, args) => {
return new Promise((resolve) => {
this.consentEventQueue.push({
type,
args,
timestamp: Date.now(),
resolve,
});
// Persist queue to sessionStorage for recovery on instance recreation
this.persistQueueToStorage();
// Start watching for consent if not already
if (!this.consentWatcherActive) {
this.consentWatcherActive = true;
this.watchForConsentAndFlush();
}
});
};
/**
* Watch for consent to become ready, then flush the queue.
*/
this.watchForConsentAndFlush = () => __awaiter(this, void 0, void 0, function* () {
yield this.waitForConsent();
yield this.flushConsentQueue();
this.consentWatcherActive = false;
});
/**
* Flush all queued events now that consent is ready.
* Clears sessionStorage after successful flush.
*/
this.flushConsentQueue = () => __awaiter(this, void 0, void 0, function* () {
const queue = [...this.consentEventQueue];
this.consentEventQueue = [];
// Clear persisted queue from storage since we're processing them now
this.clearQueueFromStorage();
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log(`[Chord Analytics DEBUG]: Flushing ${queue.length} queued events`);
}
const sendPromises = queue.map((item) => __awaiter(this, void 0, void 0, function* () {
try {
switch (item.type) {
case 'track':
yield this.sendTrackEvent(item.args[0], item.args[1], item.args[2]);
break;
case 'page':
yield this.sendPageEvent(item.args[0], item.args[1]);
break;
case 'identify':
yield this.sendIdentifyEvent(item.args[0], item.args[1], item.args[2]);
break;
default:
break;
}
}
catch (error) {
this.logger('[Chord Analytics]: Error sending queued event:', error);
}
item.resolve();
}));
yield Promise.all(sendPromises);
});
/**
* Configure CDP with fresh consent categories before each event.
* Uses Jitsu's configure() to update privacy.consentCategories.
*/
this.configureConsentOnCdp = () => {
if (!this.consentConfig)
return;
const categories = this.consentConfig.getCategories();
const privacyConfig = { privacy: { consentCategories: categories } };
const a = this.ccdp();
if (a && typeof a.configure === 'function') {
a.configure(privacyConfig);
}
const b = this.cdp();
if (b && typeof b.configure === 'function') {
b.configure(privacyConfig);
}
};
this.validate = (event, props) => {
if (!event) {
this.logger('No event name provided');
return [{ success: false }];
}
const schema = event && eventSchemas[event];
if (!schema)
return [{ success: true, data: props }];
return schema.map((s) => s.safeParse(props));
};
/**
* Generate the event `meta` property.
*/
this.meta = () => {
var _a, _b, _c, _d, _e, _f;
return Object.assign(Object.assign({}, this.options.metadata), { ownership: {
oms_id: (_b = (_a = this.options.metadata) === null || _a === void 0 ? void 0 : _a.ownership) === null || _b === void 0 ? void 0 : _b.omsId,
store_id: (_d = (_c = this.options.metadata) === null || _c === void 0 ? void 0 : _c.ownership) === null || _d === void 0 ? void 0 : _d.storeId,
tenant_id: (_f = (_e = this.options.metadata) === null || _e === void 0 ? void 0 : _e.ownership) === null || _f === void 0 ? void 0 : _f.tenantId,
}, version: {
// TODO: make this dynamic
major: 3,
minor: 0,
patch: 0,
} });
};
// eslint-disable-next-line class-methods-use-this
this.addSourceMiddleware = (func) => {
this.middleware.push(func);
};
/**
* Send a `track` event to the CDP with any event name and properties.
* If consent is not ready, the event is queued until consent is available.
*/
this.track = (event, props, options) => __awaiter(this, void 0, void 0, function* () {
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log(`[Chord Analytics DEBUG]: track() called with event: "${event}"`);
}
if (!event)
this.logger('No event name provided');
const gate = this.waitForReadyState();
if (gate)
yield gate;
// If consent is configured but not ready, queue the event
if (this.consentConfig && !this.consentConfig.isConsentReady()) {
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log(`[Chord Analytics DEBUG]: track("${event}") consent not ready, queuing event`);
}
return this.queueEventForConsent('track', [event, props, options]);
}
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log(`[Chord Analytics DEBUG]: track("${event}") sending event now`);
}
// Consent ready (or not configured) - send immediately
return this.sendTrackEvent(event, props, options);
});
/**
* Internal method to actually send a track event to CDP.
*/
this.sendTrackEvent = (event, props, options) => __awaiter(this, void 0, void 0, function* () {
const formattedProps = this.options.stripNull && props ? pruneNullValues(props) : props;
let middlewareProps = {};
if (this.middleware.length > 0) {
const searchParams = typeof window !== 'undefined' ? window.location.search : '';
const urlSearchParams = new URLSearchParams(searchParams);
const searchData = Object.fromEntries(urlSearchParams.entries());
// process middleware synchronously
this.middleware.forEach((func) => {
try {
const result = func({
payload: {
obj: {
properties: props,
event,
context: {
page: {
search: searchParams,
searchParams: searchData,
},
},
},
},
next: (_) => _,
});
if (result && typeof result === 'object') {
middlewareProps = Object.assign(Object.assign({}, middlewareProps), result);
}
}
catch (error) {
console.warn('Middleware error:', error);
}
});
}
const finalEventProps = Object.assign(Object.assign(Object.assign({}, formattedProps), middlewareProps), { meta: this.meta() });
if (this.options.debug) {
this.validate(event, finalEventProps).forEach((valid) => {
if (!valid.success) {
this.logger('Chord tracking plan violation', valid.error);
}
});
}
// Configure CDP with fresh consent before sending
this.configureConsentOnCdp();
yield new Promise((resolve) => {
const cdpPromises = [];
try {
const a = this.ccdp();
if (a && typeof a.track === 'function') {
cdpPromises.push(new Promise((cdpResolve) => {
a.track(event, finalEventProps, options, () => cdpResolve());
}));
}
const b = this.cdp();
if (b && typeof b.track === 'function') {
cdpPromises.push(new Promise((cdpResolve) => {
b.track(event, finalEventProps, options, () => cdpResolve());
}));
}
if (cdpPromises.length > 0) {
Promise.allSettled(cdpPromises).then(() => resolve());
}
else {
resolve();
}
}
catch (error) {
this.logger(error);
resolve();
}
});
});
/**
* Send an `identify` event to the CDP with user id and traits.
* If consent is not ready, the event is queued until consent is available.
*/
this.identify = (userIdOrTraits, traitsOrOptions, options) => __awaiter(this, void 0, void 0, function* () {
const gate = this.waitForReadyState();
if (gate)
yield gate;
// If consent is configured but not ready, queue the event
if (this.consentConfig && !this.consentConfig.isConsentReady()) {
return this.queueEventForConsent('identify', [
userIdOrTraits,
traitsOrOptions,
options,
]);
}
// Consent ready (or not configured) - send immediately
return this.sendIdentifyEvent(userIdOrTraits, traitsOrOptions, options);
});
/**
* Internal method to actually send an identify event to CDP.
*/
this.sendIdentifyEvent = (userIdOrTraits, traitsOrOptions, options) => __awaiter(this, void 0, void 0, function* () {
// Parse arguments to extract userId, traits, and options
let userId;
let traits;
let opts;
if (typeof userIdOrTraits === 'string') {
userId = userIdOrTraits;
traits = traitsOrOptions;
opts = options;
}
else {
traits = userIdOrTraits;
opts = traitsOrOptions;
}
// Configure CDP with fresh consent before sending
this.configureConsentOnCdp();
try {
const identifyPromises = [];
const a = this.ccdp();
if (a && typeof a.identify === 'function') {
identifyPromises.push(new Promise((resolve) => {
if (userId) {
a.identify(userId, traits, opts, () => resolve());
}
else {
a.identify(traits, opts, () => resolve());
}
}));
}
const b = this.cdp();
if (b && typeof b.identify === 'function') {
identifyPromises.push(new Promise((resolve) => {
if (userId) {
b.identify(userId, traits, opts, () => resolve());
}
else {
b.identify(traits, opts, () => resolve());
}
}));
}
const results = yield Promise.allSettled(identifyPromises);
this.logger('identify event sent', results);
}
catch (error) {
this.logger(error);
}
});
/**
* Send a `page` event to the CDP.
* If consent is not ready, the event is queued until consent is available.
*/
this.page = (options, props = {}) => __awaiter(this, void 0, void 0, function* () {
const gate = this.waitForReadyState();
if (gate)
yield gate;
// If consent is configured but not ready, queue the event
if (this.consentConfig && !this.consentConfig.isConsentReady()) {
return this.queueEventForConsent('page', [options, props]);
}
// Consent ready (or not configured) - send immediately
return this.sendPageEvent(options, props);
});
/**
* Internal method to actually send a page event to CDP.
*/
this.sendPageEvent = (options, props = {}) => __awaiter(this, void 0, void 0, function* () {
// Configure CDP with fresh consent before sending
this.configureConsentOnCdp();
yield new Promise((resolve) => {
const pagePromises = [];
try {
const a = this.ccdp();
if (a && typeof a.page === 'function') {
pagePromises.push(new Promise((cdpResolve) => {
a.page(Object.assign(Object.assign({}, props), { meta: this.meta() }), options, () => cdpResolve());
}));
}
const b = this.cdp();
if (b && typeof b.page === 'function') {
pagePromises.push(new Promise((cdpResolve) => {
b.page(Object.assign(Object.assign({}, props), { meta: this.meta() }), options, () => cdpResolve());
}));
}
if (pagePromises.length > 0) {
Promise.allSettled(pagePromises).then((results) => {
this.logger('page event sent', results);
resolve();
});
}
else {
resolve();
}
}
catch (error) {
this.logger(error);
resolve();
}
});
});
// Coalesces concurrent reset() calls — a second caller gets the same in-flight
// Promise rather than triggering a duplicate reset.
this.reset = () => {
if (!this.resetCompletePromise) {
this.resetCompletePromise = this.performReset();
}
return this.resetCompletePromise;
};
this.performReset = () => __awaiter(this, void 0, void 0, function* () {
var _e;
try {
const a = this.ccdp();
if (a && typeof a.reset === 'function') {
a.reset();
}
const b = this.cdp();
if (b && typeof b.reset === 'function') {
b.reset();
}
const maxAttempts = 50;
let attempts = 0;
while (attempts < maxAttempts) {
// eslint-disable-next-line
yield new Promise((resolve) => setTimeout(resolve, 10));
try {
const userState = ((_e = a === null || a === void 0 ? void 0 : a.user) === null || _e === void 0 ? void 0 : _e.call(a)) || {};
if (userState.userId === null || userState.userId === undefined) {
break;
}
}
catch (_f) {
break;
}
attempts++;
}
}
catch (error) {
this.logger(error);
}
finally {
this.resetCompletePromise = null;
}
});
/**
* Handler for postMessage events from the Shopify Web Pixel sandbox.
* Bound to the instance for proper `this` context and cleanup.
*/
this.webPixelMessageHandler = null;
/**
* Set up a listener for postMessage events from the Shopify Web Pixel sandbox.
* This allows the web pixel (running in Shopify's sandboxed iframe)
* to forward analytics events to this ChordAnalytics instance on the storefront.
*
* The web pixel sends messages in the format:
* - { source: 'chord-web-pixel', type: 'track', payload: [eventName, properties] }
* - { source: 'chord-web-pixel', type: 'identify', payload: [userId, traits] }
* - { source: 'chord-web-pixel', type: 'page', payload: [properties] }
*
* @returns A cleanup function to remove the listener
*/
this.setupWebPixelListener = () => {
if (typeof window === 'undefined') {
this.logger('[Chord Analytics]: setupWebPixelListener called in non-browser environment');
// eslint-disable-next-line @typescript-eslint/no-empty-function
return () => { };
}
// Remove existing listener if one exists
if (this.webPixelMessageHandler) {
window.removeEventListener('message', this.webPixelMessageHandler);
}
this.webPixelMessageHandler = (event) => {
// Validate message structure
if (!event.data || typeof event.data !== 'object')
return;
if (event.data.source !== 'chord-web-pixel')
return;
// Validate origin — only accept messages from the same hostname or from
// null-origin sandboxed iframes (Shopify web pixel sandbox has origin "null")
try {
if (event.origin && event.origin !== 'null') {
const eventHostname = new URL(event.origin).hostname;
if (eventHostname !== window.location.hostname)
return;
}
}
catch (_a) {
return;
}
const message = event.data;
if (this.options.enableLogging && this.options.debug) {
// eslint-disable-next-line no-console
console.log('[Chord Analytics DEBUG]: Received web pixel message:', message.type, message.payload);
}
try {
switch (message.type) {
case 'track': {
const [eventName, properties] = message.payload;
if (eventName) {
Promise.resolve(this.track(eventName, properties)).catch((err) => this.logger('[Chord Analytics]: Error in web pixel track:', err));
}
break;
}
case 'identify': {
const [userId, traits] = message.payload;
Promise.resolve(this.identify(userId, traits)).catch((err) => this.logger('[Chord Analytics]: Error in web pixel identify:', err));
break;
}
case 'page': {
const [properties] = message.payload;
Promise.resolve(this.page(undefined, properties)).catch((err) => this.logger('[Chord Analytics]: Error in web pixel page:', err));
break;
}
default:
this.logger(`[Chord Analytics]: Unknown web pixel message type: ${message.type}`);
}
}
catch (error) {
this.logger('[Chord Analytics]: Error processing web pixel message:', error);
}
};
window.addEventListener('message', this.webPixelMessageHandler);
this.logger('[Chord Analytics]: Web pixel listener initialized');
// Return cleanup function
return () => {
if (this.webPixelMessageHandler) {
window.removeEventListener('message', this.webPixelMessageHandler);
this.webPixelMessageHandler = null;
this.logger('[Chord Analytics]: Web pixel listener removed');
}
};
};
// TODO: Add support for props.products
this.trackCartViewed = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
const cart = (_k = (_j = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.formatters) === null || _h === void 0 ? void 0 : _h.objects) === null || _j === void 0 ? void 0 : _j.cart) === null || _k === void 0 ? void 0 : _k.call(_j, {
cart: props === null || props === void 0 ? void 0 : props.cart,
});
const payload = {
cart_id: cart === null || cart === void 0 ? void 0 : cart.cart_id,
currency: cart === null || cart === void 0 ? void 0 : cart.currency,
products: cart === null || cart === void 0 ? void 0 : cart.products,
value: cart === null || cart === void 0 ? void 0 : cart.value,
};
if ((_o = (_m = (_l = this.options) === null || _l === void 0 ? void 0 : _l.formatters) === null || _m === void 0 ? void 0 : _m.events) === null || _o === void 0 ? void 0 : _o.cartViewed) {
const formattedPayload = (_r = (_q = (_p = this.options) === null || _p === void 0 ? void 0 : _p.formatters) === null || _q === void 0 ? void 0 : _q.events) === null || _r === void 0 ? void 0 : _r.cartViewed(props, payload);
return this.track('Cart Viewed', formattedPayload, options);
}
return this.track('Cart Viewed', payload, options);
});
// TODO: Add support for props.products
this.trackCheckoutStarted = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _s, _t, _u, _v, _w, _x, _y;
const formatter = (_u = (_t = (_s = this.options) === null || _s === void 0 ? void 0 : _s.formatters) === null || _t === void 0 ? void 0 : _t.events) === null || _u === void 0 ? void 0 : _u.checkoutStarted;
const checkout = (_y = (_x = (_w = (_v = this.options) === null || _v === void 0 ? void 0 : _v.formatters) === null || _w === void 0 ? void 0 : _w.objects) === null || _x === void 0 ? void 0 : _x.checkout) === null || _y === void 0 ? void 0 : _y.call(_x, {
checkout: props === null || props === void 0 ? void 0 : props.checkout,
});
const payload = checkout;
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Checkout Started', formattedPayload, options);
}
return this.track('Checkout Started', payload, options);
});
this.trackCheckoutStepCompleted = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _z, _0, _1;
const formatter = (_1 = (_0 = (_z = this.options) === null || _z === void 0 ? void 0 : _z.formatters) === null || _0 === void 0 ? void 0 : _0.events) === null || _1 === void 0 ? void 0 : _1.checkoutStepCompleted;
const payload = {
checkout_id: props === null || props === void 0 ? void 0 : props.checkoutId,
payment_method: props === null || props === void 0 ? void 0 : props.paymentMethod,
shipping_method: props === null || props === void 0 ? void 0 : props.shippingMethod,
step: props === null || props === void 0 ? void 0 : props.step,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Checkout Step Completed', formattedPayload, options);
}
return this.track('Checkout Step Completed', payload, options);
});
this.trackCheckoutStepViewed = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _2, _3, _4;
const formatter = (_4 = (_3 = (_2 = this.options) === null || _2 === void 0 ? void 0 : _2.formatters) === null || _3 === void 0 ? void 0 : _3.events) === null || _4 === void 0 ? void 0 : _4.checkoutStepViewed;
const payload = {
checkout_id: props === null || props === void 0 ? void 0 : props.checkoutId,
payment_method: props === null || props === void 0 ? void 0 : props.paymentMethod,
shipping_method: props === null || props === void 0 ? void 0 : props.shippingMethod,
step: props === null || props === void 0 ? void 0 : props.step,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Checkout Step Viewed', formattedPayload, options);
}
return this.track('Checkout Step Viewed', payload, options);
});
this.trackCouponApplied = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _5, _6, _7;
const formatter = (_7 = (_6 = (_5 = this.options) === null || _5 === void 0 ? void 0 : _5.formatters) === null || _6 === void 0 ? void 0 : _6.events) === null || _7 === void 0 ? void 0 : _7.couponApplied;
const payload = {
cart_id: props === null || props === void 0 ? void 0 : props.cartId,
coupon_id: props === null || props === void 0 ? void 0 : props.couponId,
coupon_name: props === null || props === void 0 ? void 0 : props.couponName,
discount: props === null || props === void 0 ? void 0 : props.discount,
order_id: props === null || props === void 0 ? void 0 : props.orderId,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Coupon Applied', formattedPayload, options);
}
return this.track('Coupon Applied', payload, options);
});
this.trackCouponDenied = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _8, _9, _10;
const formatter = (_10 = (_9 = (_8 = this.options) === null || _8 === void 0 ? void 0 : _8.formatters) === null || _9 === void 0 ? void 0 : _9.events) === null || _10 === void 0 ? void 0 : _10.couponDenied;
const payload = {
cart_id: props === null || props === void 0 ? void 0 : props.cartId,
coupon_id: props === null || props === void 0 ? void 0 : props.couponId,
coupon_name: props === null || props === void 0 ? void 0 : props.couponName,
order_id: props === null || props === void 0 ? void 0 : props.orderId,
reason: props === null || props === void 0 ? void 0 : props.reason,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Coupon Denied', formattedPayload, options);
}
return this.track('Coupon Denied', payload, options);
});
this.trackCouponEntered = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _11, _12, _13;
const formatter = (_13 = (_12 = (_11 = this.options) === null || _11 === void 0 ? void 0 : _11.formatters) === null || _12 === void 0 ? void 0 : _12.events) === null || _13 === void 0 ? void 0 : _13.couponEntered;
const payload = {
cart_id: props === null || props === void 0 ? void 0 : props.cartId,
coupon_id: props === null || props === void 0 ? void 0 : props.couponId,
coupon_name: props === null || props === void 0 ? void 0 : props.couponName,
order_id: props === null || props === void 0 ? void 0 : props.orderId,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Coupon Entered', formattedPayload, options);
}
return this.track('Coupon Entered', payload, options);
});
this.trackCouponRemoved = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _14, _15, _16;
const formatter = (_16 = (_15 = (_14 = this.options) === null || _14 === void 0 ? void 0 : _14.formatters) === null || _15 === void 0 ? void 0 : _15.events) === null || _16 === void 0 ? void 0 : _16.couponRemoved;
const payload = {
cart_id: props === null || props === void 0 ? void 0 : props.cartId,
coupon_id: props === null || props === void 0 ? void 0 : props.couponId,
coupon_name: props === null || props === void 0 ? void 0 : props.couponName,
discount: props === null || props === void 0 ? void 0 : props.discount,
order_id: props === null || props === void 0 ? void 0 : props.orderId,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Coupon Removed', formattedPayload, options);
}
return this.track('Coupon Removed', payload, options);
});
this.trackEmailCaptured = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _17, _18, _19;
const formatter = (_19 = (_18 = (_17 = this.options) === null || _17 === void 0 ? void 0 : _17.formatters) === null || _18 === void 0 ? void 0 : _18.events) === null || _19 === void 0 ? void 0 : _19.emailCaptured;
const payload = {
email: props === null || props === void 0 ? void 0 : props.email,
placement_component: props === null || props === void 0 ? void 0 : props.placementComponent,
placement_page: props === null || props === void 0 ? void 0 : props.placementPage,
};
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Email Captured', formattedPayload, options);
}
return this.track('Email Captured', payload, options);
});
this.trackProductAdded = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31;
const formatter = (_22 = (_21 = (_20 = this.options) === null || _20 === void 0 ? void 0 : _20.formatters) === null || _21 === void 0 ? void 0 : _21.events) === null || _22 === void 0 ? void 0 : _22.productAdded;
const product = (_26 = (_25 = (_24 = (_23 = this.options) === null || _23 === void 0 ? void 0 : _23.formatters) === null || _24 === void 0 ? void 0 : _24.objects) === null || _25 === void 0 ? void 0 : _25.product) === null || _26 === void 0 ? void 0 : _26.call(_25, props === null || props === void 0 ? void 0 : props.product);
const cart = (_30 = (_29 = (_28 = (_27 = this.options) === null || _27 === void 0 ? void 0 : _27.formatters) === null || _28 === void 0 ? void 0 : _28.objects) === null || _29 === void 0 ? void 0 : _29.cart) === null || _30 === void 0 ? void 0 : _30.call(_29, {
cart: props === null || props === void 0 ? void 0 : props.cart,
});
const quantity = ((_31 = props === null || props === void 0 ? void 0 : props.product) === null || _31 === void 0 ? void 0 : _31.quantity) || 1;
const payload = Object.assign(Object.assign({}, product), { cart_id: cart.cart_id, currency: cart.currency, products: [Object.assign(Object.assign({}, product), { quantity })], total: product.price * (quantity || 1), value: product.price * (quantity || 1) });
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Product Added', formattedPayload, options);
}
return this.track('Product Added', payload, options);
});
this.trackProductClicked = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44;
const formatter = (_34 = (_33 = (_32 = this.options) === null || _32 === void 0 ? void 0 : _32.formatters) === null || _33 === void 0 ? void 0 : _33.events) === null || _34 === void 0 ? void 0 : _34.productClicked;
const product = (_38 = (_37 = (_36 = (_35 = this.options) === null || _35 === void 0 ? void 0 : _35.formatters) === null || _36 === void 0 ? void 0 : _36.objects) === null || _37 === void 0 ? void 0 : _37.product) === null || _38 === void 0 ? void 0 : _38.call(_37, props === null || props === void 0 ? void 0 : props.product);
const cart = (_42 = (_41 = (_40 = (_39 = this.options) === null || _39 === void 0 ? void 0 : _39.formatters) === null || _40 === void 0 ? void 0 : _40.objects) === null || _41 === void 0 ? void 0 : _41.cart) === null || _42 === void 0 ? void 0 : _42.call(_41, {
cart: props === null || props === void 0 ? void 0 : props.cart,
});
const quantity = ((_43 = props === null || props === void 0 ? void 0 : props.product) === null || _43 === void 0 ? void 0 : _43.quantity) || 1;
const payload = Object.assign(Object.assign({}, product), { cart_id: cart.cart_id, currency: cart.currency, item_list_id: (_44 = props === null || props === void 0 ? void 0 : props.listId) === null || _44 === void 0 ? void 0 : _44.toString(), item_list_name: props === null || props === void 0 ? void 0 : props.listName, products: [Object.assign(Object.assign({}, product), { quantity })] });
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Product Clicked', formattedPayload, options);
}
return this.track('Product Clicked', payload, options);
});
this.trackVariantClicked = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56;
const formatter = (_47 = (_46 = (_45 = this.options) === null || _45 === void 0 ? void 0 : _45.formatters) === null || _46 === void 0 ? void 0 : _46.events) === null || _47 === void 0 ? void 0 : _47.variantClicked;
const product = (_51 = (_50 = (_49 = (_48 = this.options) === null || _48 === void 0 ? void 0 : _48.formatters) === null || _49 === void 0 ? void 0 : _49.objects) === null || _50 === void 0 ? void 0 : _50.product) === null || _51 === void 0 ? void 0 : _51.call(_50, props === null || props === void 0 ? void 0 : props.product);
const cart = (_55 = (_54 = (_53 = (_52 = this.options) === null || _52 === void 0 ? void 0 : _52.formatters) === null || _53 === void 0 ? void 0 : _53.objects) === null || _54 === void 0 ? void 0 : _54.cart) === null || _55 === void 0 ? void 0 : _55.call(_54, {
cart: props === null || props === void 0 ? void 0 : props.cart,
});
const quantity = ((_56 = props === null || props === void 0 ? void 0 : props.product) === null || _56 === void 0 ? void 0 : _56.quantity) || 1;
const payload = Object.assign(Object.assign({}, product), { cart_id: cart.cart_id, currency: cart.currency, quantity: quantity, line_item_id: props === null || props === void 0 ? void 0 : props.lineItemId, coupon: props === null || props === void 0 ? void 0 : props.coupon });
if (typeof formatter === 'function') {
const formattedPayload = formatter(props, payload);
return this.track('Variant Clicked', formattedPayload, options);
}
return this.track('Variant Clicked', payload, options);
});
this.trackProductListFiltered = (props, options) => __awaiter(this, void 0, void 0, function* () {
var _57, _58, _59, _60, _61;
const formatter = (_59 = (_58 = (_57 = this.options) === null || _57 === void 0 ? void 0 : _57.formatters) === null || _58 === void 0 ? void 0 : _58.events) === null || _59 === void 0 ? void 0 : _59.productListFiltered;
const payload = {
category: props === null || props === void 0 ? void 0 : props.category,
filters: props === null || props === void 0 ? void 0 : props.filters,
list_id: (_60 = props === null || props === void 0 ? void 0 :