UNPKG

@cruxstack/node-sdk

Version:

A Node.js SDK for event tracking, user traits lookup, validation, and automatic retry with queueing.

179 lines (178 loc) 8.06 kB
import { v4 as uuidv4 } from 'uuid'; import { eventDataSchema, enrichedEventSchema } from './validators/eventSchema'; import { getConfig, isSDKInitialized } from './init'; import { sendEvent, sendBulkEvents } from './api'; import { addToQueue } from './queue'; export async function cruxTrack(categoryName, eventData) { // Check if SDK is initialized if (!isSDKInitialized()) { throw new Error('SDK not initialized. Call init() first.'); } // Handle bulk case if (Array.isArray(categoryName)) { return await handleBulkEvents(categoryName); } // Handle single event case with strict validation if (!eventData) { throw new Error('eventData is required when categoryName is a string'); } return await handleSingleEvent(categoryName, eventData); } // Extract single event logic async function handleSingleEvent(categoryName, eventData) { // Validate category name if (!categoryName || typeof categoryName !== 'string' || categoryName.trim().length === 0) { throw new Error('Category name is required and must be a non-empty string'); } try { // Validate event data const validatedEventData = eventDataSchema.parse(eventData); // Get config const config = getConfig(); // Map user-friendly fields to internal format const mappedEventData = { ...validatedEventData, uid: validatedEventData.userId || "undefined", dtm: validatedEventData.eventTime || Date.now() }; // Enrich the event with required fields const enrichedEvent = { eid: uuidv4(), cid: validatedEventData.customerId || "undefined", cna: validatedEventData.customerName || "undefined", client_id: config.clientId, e: categoryName.trim(), dtm: mappedEventData.dtm, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, ev: validatedEventData, uid: mappedEventData.uid, // Static values tna: "node", tv: "v1", // Optional fields from user input ...(eventData.sessionId && { sid: eventData.sessionId }), ...(eventData.userAgent && { ua: eventData.userAgent }), ...(eventData.screenHeight && { sh: eventData.screenHeight }), ...(eventData.screenWidth && { sw: eventData.screenWidth }), ...(eventData.language && { l: eventData.language }), ...(eventData.platform && { p: eventData.platform }), ...(eventData.adBlock !== undefined && { an: eventData.adBlock }), ...(eventData.viewportHeight && { vh: eventData.viewportHeight }), ...(eventData.viewportWidth && { vw: eventData.viewportWidth }), ...(eventData.pageTitle && { pt: eventData.pageTitle }), ...(eventData.pageUrl && { pu: eventData.pageUrl }), ...(eventData.pagePath && { pp: eventData.pagePath }), ...(eventData.pageDomain && { pd: eventData.pageDomain }), ...(eventData.pageLoadTime && { pl: eventData.pageLoadTime }), ...(eventData.referrer && { pr: eventData.referrer }), ...(eventData.ipAddress && { ip: eventData.ipAddress }) }; // Validate the enriched event const validatedEvent = enrichedEventSchema.parse(enrichedEvent); // Try to send the event try { await sendEvent(validatedEvent); } catch (error) { // If sending fails, add to queue for retry console.warn(`Failed to send event immediately, adding to queue: ${error instanceof Error ? error.message : 'Unknown error'}`); addToQueue(validatedEvent); } } catch (error) { if (error instanceof Error) { throw new Error(`Event tracking failed: ${error.message}`); } throw new Error('Event tracking failed: Unknown error'); } } // Add bulk event handler async function handleBulkEvents(events) { if (!events || events.length === 0) { throw new Error('Events array cannot be empty'); } if (events.length > 1000) { throw new Error('Maximum 1000 events per batch'); } try { const config = getConfig(); const enrichedEvents = []; // Process each event for (const { categoryName, eventData } of events) { if (!categoryName || typeof categoryName !== 'string' || categoryName.trim().length === 0) { throw new Error('Category name is required and must be a non-empty string'); } const validatedEventData = eventDataSchema.parse(eventData); const mappedEventData = { ...validatedEventData, uid: validatedEventData.userId || "undefined", dtm: validatedEventData.eventTime || Date.now() }; const enrichedEvent = { eid: uuidv4(), cid: validatedEventData.customerId || "undefined", cna: validatedEventData.customerName || "undefined", client_id: config.clientId, e: categoryName.trim(), dtm: mappedEventData.dtm, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, ev: validatedEventData, uid: mappedEventData.uid, tna: "node", tv: "v1", ...(eventData.sessionId && { sid: eventData.sessionId }), ...(eventData.userAgent && { ua: eventData.userAgent }), ...(eventData.screenHeight && { sh: eventData.screenHeight }), ...(eventData.screenWidth && { sw: eventData.screenWidth }), ...(eventData.language && { l: eventData.language }), ...(eventData.platform && { p: eventData.platform }), ...(eventData.adBlock !== undefined && { an: eventData.adBlock }), ...(eventData.viewportHeight && { vh: eventData.viewportHeight }), ...(eventData.viewportWidth && { vw: eventData.viewportWidth }), ...(eventData.pageTitle && { pt: eventData.pageTitle }), ...(eventData.pageUrl && { pu: eventData.pageUrl }), ...(eventData.pagePath && { pp: eventData.pagePath }), ...(eventData.pageDomain && { pd: eventData.pageDomain }), ...(eventData.pageLoadTime && { pl: eventData.pageLoadTime }), ...(eventData.referrer && { pr: eventData.referrer }), ...(eventData.ipAddress && { ip: eventData.ipAddress }) }; const validatedEvent = enrichedEventSchema.parse(enrichedEvent); enrichedEvents.push(validatedEvent); } // Try bulk send first try { return await sendBulkEvents(enrichedEvents); } catch (error) { // Fall back to individual sends console.warn(`Bulk send failed, falling back to individual sends: ${error instanceof Error ? error.message : 'Unknown error'}`); let processed = 0; let failed = 0; const errors = []; for (const event of enrichedEvents) { try { await sendEvent(event); processed++; } catch (err) { failed++; errors.push(err instanceof Error ? err.message : 'Unknown error'); addToQueue(event); } } return { success: failed === 0, processed, failed, errors: errors.length > 0 ? errors : undefined }; } } catch (error) { if (error instanceof Error) { throw new Error(`Bulk event tracking failed: ${error.message}`); } throw new Error('Bulk event tracking failed: Unknown error'); } }