import type { WidgetConfig } from './types'; /** * Attribution client events (DESIGN decision #23). * * The plugin's un-delayable bootstrap owns the events contract and exposes * `window.aisthetixTrackEvent(eventName, extra)`. The widget emits * `aisthetix_tryon_completed` through that single code path so there is ONE * implementation of the /api/events POST (headers, idempotency, consent gate). * * If the bootstrap helper is somehow unavailable (e.g. the widget was loaded * without it), we fall back to a direct, fire-and-forget POST that mirrors the * bootstrap exactly. Analytics must NEVER break the try-on flow, so everything * here is wrapped and best-effort. */ declare global { interface Window { aisthetixTrackEvent?: ( eventName: string, extra?: Record ) => void; } } function makeEventId(): string { try { if ( window.crypto && 'randomUUID' in window.crypto ) { return window.crypto.randomUUID(); } } catch { /* fall through */ } return `t${ Date.now().toString( 36 ) }${ Math.random().toString( 36 ).slice( 2, 10 ) }`; } /** * Emit a try-on completion event. `productId` is the only shaped field for the * try-on event (mirrors the Shopify pixel: try-on -> productId only). */ export function trackTryOnCompleted( config: WidgetConfig, productId: string ): void { try { const extra = { productId: productId ? String( productId ) : null }; if ( typeof window.aisthetixTrackEvent === 'function' ) { window.aisthetixTrackEvent( 'aisthetix_tryon_completed', extra ); return; } // Fallback: direct POST identical in shape to the bootstrap's sendEvent. if ( ! config.eventsUrl || ! config.publishableKey ) { return; } const payload = { eventName: 'aisthetix_tryon_completed', eventId: makeEventId(), visitorKey: config.visitorKey || null, customerId: config.customerId || null, url: window.location?.href || null, referrer: document.referrer || null, ...extra, }; fetch( config.eventsUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Aisthetix-Key': config.publishableKey, }, body: JSON.stringify( payload ), keepalive: true, credentials: 'omit', } ).catch( () => {} ); } catch { /* analytics must never break the try-on */ } }