UNPKG

@cruxstack/browser-sdk

Version:

A lightweight, privacy-focused JavaScript SDK for web analytics and event tracking. Built with TypeScript, featuring automatic event capture, event-time environment snapshots, intelligent queuing, and robust error handling.

235 lines (234 loc) 8.63 kB
import { setDebugLog, debug } from "./utils/log"; import { SessionManager } from "./session"; import { EventQueue } from "./queue"; import { ApiClient } from "./apiClient"; import { EventTracker } from "./tracker"; import { setupAutocapture, } from "../features/autocapture"; let sessionManager; let eventQueue; let apiClient; let eventTracker; let autocaptureCleanup = null; let globalConfig = null; // Store event listener references for proper cleanup let unloadHandler = null; let onlineHandler = null; // Generate plain UUID for event IDs function generateEventId() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } export function init(config) { try { // Prevent multiple initializations if (eventTracker && globalConfig) { if (config.debugLog) { console.warn("Cruxstack: SDK already initialized. Skipping re-initialization."); } return; } // Validate config if (!config.clientId) { throw new Error("Cruxstack: clientId is required. Please provide a valid client identifier."); } if (typeof config.clientId !== "string" || config.clientId.trim() === "") { throw new Error("Cruxstack: clientId must be a non-empty string."); } // Store config globally globalConfig = config; // Initialize core modules setDebugLog(config.debugLog || false); sessionManager = new SessionManager(config); eventQueue = new EventQueue(); apiClient = new ApiClient(config.clientId, config.customerId, config.customerName, config.debugLog || false); eventTracker = new EventTracker(apiClient, eventQueue, sessionManager, config.clientId, config.customerId, config.customerName); // Setup autocapture if enabled if (config.autoCapture !== false) { const autocaptureTrackers = { trackClick: (data) => { eventTracker.track({ type: "click", data, id: generateEventId(), clientId: config.clientId, customerId: config.customerId, customerName: config.customerName, }); }, trackForm: (data) => { eventTracker.track({ type: `form_${data.eventType}`, data, id: generateEventId(), clientId: config.clientId, customerId: config.customerId, customerName: config.customerName, }); }, trackPageView: (data) => { eventTracker.track({ type: "page_view", data, id: generateEventId(), clientId: config.clientId, customerId: config.customerId, customerName: config.customerName, }); }, }; autocaptureCleanup = setupAutocapture(autocaptureTrackers, config); } // Setup unload handler for queue flushing unloadHandler = () => { if (!eventTracker) return; const status = eventTracker.getQueueStatus(); if (status.length > 0) { if (config.debugLog) { console.log("Cruxstack: Attempting unload-safe delivery via sendBeacon for queued events"); } // Best-effort: do a one-shot flush; sendEvent will use sendBeacon in unload scenario // Do not await here to avoid blocking unload eventTracker.flushQueue(); } }; // Setup online handler for retry onlineHandler = () => { if (eventTracker && eventTracker.getQueueStatus().length > 0) { if (config.debugLog) { console.log("Cruxstack: Connection restored, retrying queued events"); } eventTracker.flushQueue(); } }; // Add event listeners window.addEventListener("beforeunload", unloadHandler); window.addEventListener("online", onlineHandler); // Attempt to flush any queued events immediately on init (ensures delivery of persisted events) if (eventTracker.getQueueStatus().length > 0) { eventTracker.flushQueue(); } debug("SDK initialized successfully", { clientId: config.clientId, customerId: config.customerId, autoCapture: config.autoCapture !== false, userId: config.userId, }); } catch (error) { console.error("Cruxstack: Initialization failed", error); throw error; } } export function trackEvent(eventData) { if (!eventTracker || !globalConfig) { throw new Error("Cruxstack: SDK not initialized. Please call init() with your configuration before tracking events."); } if (!eventData.type || typeof eventData.type !== "string") { throw new Error("Cruxstack: Event type is required and must be a string."); } if (eventData.type.trim() === "") { throw new Error("Cruxstack: Event type cannot be empty or contain only whitespace."); } // Validate data structure if (eventData.data !== undefined && (typeof eventData.data !== "object" || eventData.data === null)) { throw new Error("Cruxstack: Event data must be an object or undefined."); } eventTracker.track({ id: generateEventId(), type: eventData.type, data: eventData.data || {}, clientId: globalConfig.clientId, customerId: globalConfig.customerId, customerName: globalConfig.customerName, }); } // Public API methods for data fetching export async function getUserTraits(customerId) { if (!apiClient || !globalConfig) { throw new Error("Cruxstack: SDK not initialized. Please call init() with your configuration before using API methods."); } if (typeof customerId !== "string" || customerId.trim() === "") { throw new Error("Cruxstack: customerId must be a non-empty string."); } try { return await apiClient.getUserTraits(customerId); } catch (error) { if (globalConfig.debugLog) { console.error("Cruxstack: Failed to fetch user traits", error); } throw error; } } export function getSessionInfo() { if (!sessionManager) { return { sessionId: null, userId: null, isInitialized: false, }; } return { sessionId: sessionManager.getSessionId(), userId: sessionManager.getUserId(), isInitialized: true, }; } export function flushEvents() { if (!eventTracker) { throw new Error("Cruxstack: SDK not initialized. Please call init() with your configuration."); } return eventTracker.flushQueue(); } export function getQueueStatus() { if (!eventTracker) { return { length: 0, events: [], }; } return eventTracker.getQueueStatus(); } export function clearQueue() { if (!eventTracker) { throw new Error("Cruxstack: SDK not initialized. Please call init() with your configuration."); } eventTracker.clearQueue(); } export function resetSession() { if (!sessionManager) { throw new Error("Cruxstack: SDK not initialized. Please call init() with your configuration."); } sessionManager.resetSession(); } export function isInitialized() { return !!(eventTracker && globalConfig); } export function cleanup() { // Remove event listeners if (unloadHandler) { window.removeEventListener("beforeunload", unloadHandler); unloadHandler = null; } if (onlineHandler) { window.removeEventListener("online", onlineHandler); onlineHandler = null; } // Cleanup autocapture if (autocaptureCleanup) { autocaptureCleanup(); autocaptureCleanup = null; } // Reset all modules sessionManager = null; eventQueue = null; apiClient = null; eventTracker = null; globalConfig = null; debug("SDK cleaned up successfully"); }