@outlit/browser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +435 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +409 -0
- package/dist/index.mjs.map +1 -0
- package/dist/outlit.global.js +2 -0
- package/dist/outlit.global.js.map +1 -0
- package/dist/react/index.d.mts +170 -0
- package/dist/react/index.d.ts +170 -0
- package/dist/react/index.js +511 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +487 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/tracker-CocH64L9.d.mts +107 -0
- package/dist/tracker-CocH64L9.d.ts +107 -0
- package/package.json +83 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/script.ts","../../core/src/types.ts","../../core/src/utils.ts","../../core/src/payload.ts","../src/autocapture.ts","../src/storage.ts","../src/tracker.ts"],"sourcesContent":["/**\n * IIFE entry point for CDN script tag usage.\n *\n * Usage (with stub snippet - recommended):\n * <script>\n * !function(w,d,src,key,auto){\n * if(w.outlit&&w.outlit._loaded)return;\n * w.outlit=w.outlit||{_q:[]};\n * [\"init\",\"track\",\"identify\",\"enableTracking\",\"isTrackingEnabled\",\"getVisitorId\"].forEach(function(m){\n * w.outlit[m]=w.outlit[m]||function(){w.outlit._q.push([m,[].slice.call(arguments)])};\n * });\n * var s=d.createElement(\"script\");s.async=1;s.src=src;\n * s.dataset.publicKey=key;if(auto!==undefined)s.dataset.autoTrack=auto;\n * (d.body||d.head).appendChild(s);\n * }(window,document,\"https://cdn.outlit.ai/outlit.js\",\"pk_xxx\");\n * </script>\n *\n * Usage (simple script tag):\n * <script src=\"https://cdn.outlit.ai/outlit.js\" data-public-key=\"pk_xxx\" async></script>\n *\n * Usage (with consent management):\n * Pass `false` as last param to stub, or use data-auto-track=\"false\" on script tag\n */\n\nimport type { BrowserIdentifyOptions, BrowserTrackOptions } from \"@outlit/core\"\nimport { Tracker, type TrackerOptions } from \"./tracker\"\n\n// ============================================\n// TYPES\n// ============================================\n\n// Stub queue format: [methodName, arguments]\ntype StubQueueItem = [string, unknown[]]\n\ninterface OutlitStub {\n _q?: StubQueueItem[]\n [key: string]: unknown\n}\n\ninterface OutlitGlobal {\n _initialized: boolean\n _instance: Tracker | null\n _queue: Array<() => void>\n init: (options: TrackerOptions) => void\n track: (eventName: string, properties?: BrowserTrackOptions[\"properties\"]) => void\n identify: (options: BrowserIdentifyOptions) => void\n getVisitorId: () => string | null\n enableTracking: () => void\n isTrackingEnabled: () => boolean\n}\n\n// ============================================\n// GLOBAL API\n// ============================================\n\n// Check for existing stub with queued calls\nconst existingStub =\n typeof window !== \"undefined\" ? (window as { outlit?: OutlitStub }).outlit : undefined\nconst stubQueue: StubQueueItem[] = existingStub?._q || []\n\n// Create global object with queuing support\nconst outlit: OutlitGlobal & { _loaded?: boolean } = {\n _initialized: false,\n _instance: null,\n _queue: [],\n _loaded: true, // Marks that the real SDK has loaded (for double-load protection)\n\n init(options: TrackerOptions) {\n if (this._initialized) {\n console.warn(\"[Outlit] Already initialized\")\n return\n }\n\n this._instance = new Tracker(options)\n this._initialized = true\n\n // Process calls queued by the stub snippet (before SDK loaded)\n for (const [method, args] of stubQueue) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const self = this as any\n if (method in self && typeof self[method] === \"function\") {\n self[method](...args)\n }\n }\n\n // Process calls queued after SDK loaded but before init\n while (this._queue.length > 0) {\n const fn = this._queue.shift()\n fn?.()\n }\n },\n\n track(eventName: string, properties?: BrowserTrackOptions[\"properties\"]) {\n if (!this._initialized || !this._instance) {\n // Queue the call for after initialization\n this._queue.push(() => this.track(eventName, properties))\n return\n }\n this._instance.track(eventName, properties)\n },\n\n identify(options: BrowserIdentifyOptions) {\n if (!this._initialized || !this._instance) {\n // Queue the call for after initialization\n this._queue.push(() => this.identify(options))\n return\n }\n this._instance.identify(options)\n },\n\n getVisitorId() {\n if (!this._instance) return null\n return this._instance.getVisitorId()\n },\n\n /**\n * Enable tracking after user consent.\n * Call this in your consent management tool's callback.\n */\n enableTracking() {\n if (!this._initialized || !this._instance) {\n // Queue the call for after initialization\n this._queue.push(() => this.enableTracking())\n return\n }\n this._instance.enableTracking()\n },\n\n /**\n * Check if tracking is currently enabled.\n */\n isTrackingEnabled() {\n if (!this._instance) return false\n return this._instance.isEnabled()\n },\n}\n\n// ============================================\n// AUTO-INITIALIZATION\n// ============================================\n\n/**\n * Auto-initialize from script tag attributes.\n */\nfunction autoInit(): void {\n // Find the script tag - currentScript is only available during synchronous execution\n // When called from DOMContentLoaded, we need to fall back to querySelector\n let script = document.currentScript as HTMLScriptElement | null\n\n if (!script) {\n // Fallback: find script tag with data-public-key attribute\n script = document.querySelector(\"script[data-public-key]\") as HTMLScriptElement | null\n }\n\n if (!script) {\n console.warn(\"[Outlit] No script tag found with data-public-key attribute\")\n return\n }\n\n const publicKey = script.getAttribute(\"data-public-key\")\n if (!publicKey) {\n console.warn(\"[Outlit] Missing data-public-key attribute on script tag\")\n return\n }\n\n // Get optional attributes\n const apiHost = script.getAttribute(\"data-api-host\") ?? undefined\n const trackPageviews = script.getAttribute(\"data-track-pageviews\") !== \"false\"\n const trackForms = script.getAttribute(\"data-track-forms\") !== \"false\"\n const autoTrack = script.getAttribute(\"data-auto-track\") !== \"false\"\n const autoIdentify = script.getAttribute(\"data-auto-identify\") !== \"false\"\n\n // Initialize\n outlit.init({\n publicKey,\n apiHost,\n trackPageviews,\n trackForms,\n autoTrack,\n autoIdentify,\n })\n}\n\n// ============================================\n// EXPOSE GLOBAL & AUTO-INIT\n// ============================================\n\n// Expose on window\nif (typeof window !== \"undefined\") {\n // @ts-expect-error - Adding to window\n window.outlit = outlit\n\n // Auto-initialize when DOM is ready\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", autoInit)\n } else {\n // DOM is already ready\n autoInit()\n }\n}\n\n// Also export for module usage if needed\nexport { outlit }\n","// ============================================\n// EVENT TYPES\n// ============================================\n\nexport type EventType = \"pageview\" | \"form\" | \"identify\" | \"custom\"\n\nexport type SourceType = \"pixel\" | \"server\" | \"integration\"\n\n// ============================================\n// UTM PARAMETERS\n// ============================================\n\nexport interface UtmParams {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n// ============================================\n// TRACKER CONFIGURATION\n// ============================================\n\nexport interface TrackerConfig {\n publicKey: string\n apiHost?: string // default: 'https://app.outlit.ai'\n}\n\n// ============================================\n// BROWSER-SPECIFIC TYPES (anonymous allowed)\n// visitorId is auto-managed by the browser SDK\n// ============================================\n\nexport interface BrowserTrackOptions {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface BrowserIdentifyOptions {\n email?: string\n userId?: string\n traits?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// SERVER-SPECIFIC TYPES (identity required)\n// No anonymous tracking - must identify the user\n// ============================================\n\nexport interface ServerTrackOptions {\n email?: string // At least one of email/userId required\n userId?: string // At least one of email/userId required\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n timestamp?: number\n}\n\nexport interface ServerIdentifyOptions {\n email?: string // At least one of email/userId required\n userId?: string // At least one of email/userId required\n traits?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// INTERNAL EVENT TYPES\n// These are the full event objects sent to the API\n// ============================================\n\ninterface BaseEvent {\n type: EventType\n timestamp: number // Unix timestamp in milliseconds\n url: string\n path: string\n referrer?: string\n utm?: UtmParams\n}\n\nexport interface PageviewEvent extends BaseEvent {\n type: \"pageview\"\n title?: string\n}\n\nexport interface FormEvent extends BaseEvent {\n type: \"form\"\n formId?: string\n formFields?: Record<string, string>\n}\n\nexport interface IdentifyEvent extends BaseEvent {\n type: \"identify\"\n email?: string\n userId?: string\n traits?: Record<string, string | number | boolean | null>\n}\n\nexport interface CustomEvent extends BaseEvent {\n type: \"custom\"\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport type TrackerEvent = PageviewEvent | FormEvent | IdentifyEvent | CustomEvent\n\n// ============================================\n// INGEST PAYLOAD\n// This is what gets sent to the API\n// ============================================\n\nexport interface IngestPayload {\n visitorId?: string // Required for pixel, optional for server\n source: SourceType\n events: TrackerEvent[]\n}\n\n// ============================================\n// API RESPONSE\n// ============================================\n\nexport interface IngestResponse {\n success: boolean\n processed: number\n errors?: Array<{\n index: number\n message: string\n }>\n}\n\n// ============================================\n// CONSTANTS\n// ============================================\n\nexport const DEFAULT_API_HOST = \"https://app.outlit.ai\"\n\nexport const DEFAULT_DENIED_FORM_FIELDS = [\n \"password\",\n \"passwd\",\n \"pass\",\n \"pwd\",\n \"token\",\n \"secret\",\n \"api_key\",\n \"apikey\",\n \"api-key\",\n \"credit_card\",\n \"creditcard\",\n \"credit-card\",\n \"cc_number\",\n \"ccnumber\",\n \"card_number\",\n \"cardnumber\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"social_security\",\n \"socialsecurity\",\n \"bank_account\",\n \"bankaccount\",\n \"routing_number\",\n \"routingnumber\",\n]\n","import { DEFAULT_DENIED_FORM_FIELDS, type UtmParams } from \"./types\"\n\n// ============================================\n// UTM EXTRACTION\n// ============================================\n\n/**\n * Extract UTM parameters from a URL.\n */\nexport function extractUtmParams(url: string): UtmParams | undefined {\n try {\n const urlObj = new URL(url)\n const params = urlObj.searchParams\n\n const utm: UtmParams = {}\n\n if (params.has(\"utm_source\")) utm.source = params.get(\"utm_source\") ?? undefined\n if (params.has(\"utm_medium\")) utm.medium = params.get(\"utm_medium\") ?? undefined\n if (params.has(\"utm_campaign\")) utm.campaign = params.get(\"utm_campaign\") ?? undefined\n if (params.has(\"utm_term\")) utm.term = params.get(\"utm_term\") ?? undefined\n if (params.has(\"utm_content\")) utm.content = params.get(\"utm_content\") ?? undefined\n\n return Object.keys(utm).length > 0 ? utm : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Extract path from a URL.\n */\nexport function extractPathFromUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n return urlObj.pathname\n } catch {\n return \"/\"\n }\n}\n\n// ============================================\n// FORM FIELD SANITIZATION\n// ============================================\n\n/**\n * Check if a field name should be denied (case-insensitive).\n */\nexport function isFieldDenied(fieldName: string, denylist: string[]): boolean {\n const normalizedName = fieldName.toLowerCase().replace(/[-_\\s]/g, \"\")\n return denylist.some((denied) => {\n const normalizedDenied = denied.toLowerCase().replace(/[-_\\s]/g, \"\")\n return normalizedName.includes(normalizedDenied)\n })\n}\n\n/**\n * Check if a value looks like sensitive data (e.g., credit card number).\n */\nfunction looksLikeSensitiveValue(value: string): boolean {\n // Remove spaces and dashes\n const cleaned = value.replace(/[\\s-]/g, \"\")\n\n // Check for credit card patterns (13-19 digits)\n if (/^\\d{13,19}$/.test(cleaned)) {\n return true\n }\n\n // Check for SSN pattern (9 digits)\n if (/^\\d{9}$/.test(cleaned) || /^\\d{3}-\\d{2}-\\d{4}$/.test(value)) {\n return true\n }\n\n return false\n}\n\n/**\n * Sanitize form fields by removing sensitive data.\n * Returns a new object with denied fields removed.\n */\nexport function sanitizeFormFields(\n fields: Record<string, string> | undefined,\n customDenylist?: string[],\n): Record<string, string> | undefined {\n if (!fields) return undefined\n\n const denylist = customDenylist ?? DEFAULT_DENIED_FORM_FIELDS\n const sanitized: Record<string, string> = {}\n\n for (const [key, value] of Object.entries(fields)) {\n if (!isFieldDenied(key, denylist)) {\n // Also check for credit card patterns in values\n if (!looksLikeSensitiveValue(value)) {\n sanitized[key] = value\n }\n }\n }\n\n return Object.keys(sanitized).length > 0 ? sanitized : undefined\n}\n\n// ============================================\n// VISITOR ID DERIVATION (for server SDK)\n// ============================================\n\n/**\n * Derive a deterministic visitor ID from email and/or userId.\n * This is used by the server SDK to create consistent IDs for API compatibility.\n *\n * Uses a simple hash to create a UUID-like string that will be consistent\n * for the same email/userId combination.\n */\nexport function deriveVisitorIdFromIdentity(email?: string, userId?: string): string {\n const identity = [email?.toLowerCase(), userId].filter(Boolean).join(\"|\")\n if (!identity) {\n throw new Error(\"Either email or userId must be provided\")\n }\n\n // Simple hash function to create a deterministic UUID-like string\n let hash = 0\n for (let i = 0; i < identity.length; i++) {\n const char = identity.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n\n // Convert to hex and format as UUID-like string\n const hex = Math.abs(hash).toString(16).padStart(8, \"0\")\n const part1 = hex.slice(0, 8)\n const part2 = identity.length.toString(16).padStart(4, \"0\")\n const part3 = \"4000\" // Version 4 UUID marker\n const part4 = (((hash >>> 16) & 0x0fff) | 0x8000).toString(16)\n const part5 = Math.abs(hash * 31)\n .toString(16)\n .padStart(12, \"0\")\n .slice(0, 12)\n\n return `${part1}-${part2}-${part3}-${part4}-${part5}`\n}\n\n// ============================================\n// VALIDATION\n// ============================================\n\n/**\n * Validate that at least one identity field is provided.\n * Used by the server SDK to enforce identity requirements.\n */\nexport function validateServerIdentity(email?: string, userId?: string): void {\n if (!email && !userId) {\n throw new Error(\n \"Server SDK requires either email or userId for all track/identify calls. \" +\n \"Anonymous tracking is only supported in the browser SDK.\",\n )\n }\n}\n\n// ============================================\n// AUTO-IDENTIFY: EMAIL & NAME EXTRACTION\n// ============================================\n\n/**\n * Validate that a string looks like a valid email address.\n */\nexport function isValidEmail(value: string): boolean {\n if (!value || typeof value !== \"string\") return false\n // Basic email regex - intentionally permissive to avoid false negatives\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n return emailRegex.test(value.trim())\n}\n\n/**\n * Email field name patterns (case-insensitive, normalized).\n * Order matters - more specific patterns first.\n */\nconst EMAIL_FIELD_PATTERNS = [\n /^e?-?mail$/i,\n /^email[_-]?address$/i,\n /^user[_-]?email$/i,\n /^work[_-]?email$/i,\n /^contact[_-]?email$/i,\n /^primary[_-]?email$/i,\n /^business[_-]?email$/i,\n]\n\n/**\n * Full name field patterns.\n */\nconst FULL_NAME_PATTERNS = [\n /^name$/i,\n /^full[_-]?name$/i,\n /^your[_-]?name$/i,\n /^customer[_-]?name$/i,\n /^contact[_-]?name$/i,\n /^display[_-]?name$/i,\n]\n\n/**\n * First name field patterns.\n */\nconst FIRST_NAME_PATTERNS = [\n /^first[_-]?name$/i,\n /^firstname$/i,\n /^first$/i,\n /^fname$/i,\n /^given[_-]?name$/i,\n /^forename$/i,\n]\n\n/**\n * Last name field patterns.\n */\nconst LAST_NAME_PATTERNS = [\n /^last[_-]?name$/i,\n /^lastname$/i,\n /^last$/i,\n /^lname$/i,\n /^surname$/i,\n /^family[_-]?name$/i,\n]\n\n/**\n * Check if a field name matches any of the given patterns.\n */\nfunction matchesPatterns(fieldName: string, patterns: RegExp[]): boolean {\n const normalized = fieldName.trim()\n return patterns.some((pattern) => pattern.test(normalized))\n}\n\n/**\n * Find an email value from form fields.\n *\n * Priority:\n * 1. Fields with input type=\"email\" (if inputTypes map provided)\n * 2. Field names matching email patterns\n * 3. Any field with a value that looks like an email\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns The email value if found, undefined otherwise\n */\nexport function findEmailField(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): string | undefined {\n // Priority 1: Check fields with type=\"email\"\n if (inputTypes) {\n for (const [fieldName, inputType] of inputTypes.entries()) {\n if (inputType === \"email\") {\n const value = fields[fieldName]\n if (value && isValidEmail(value)) {\n return value.trim()\n }\n }\n }\n }\n\n // Priority 2: Check field names matching email patterns\n for (const [fieldName, value] of Object.entries(fields)) {\n if (matchesPatterns(fieldName, EMAIL_FIELD_PATTERNS) && isValidEmail(value)) {\n return value.trim()\n }\n }\n\n // Priority 3: Any field with email-like value (fallback)\n for (const value of Object.values(fields)) {\n if (isValidEmail(value)) {\n return value.trim()\n }\n }\n\n return undefined\n}\n\n/**\n * Extract name fields from form data.\n *\n * Looks for:\n * - Full name fields (name, full_name, etc.)\n * - First name fields (first_name, fname, etc.)\n * - Last name fields (last_name, lname, etc.)\n *\n * If only first/last names are found, combines them into a full name.\n *\n * @param fields - Form field key-value pairs\n * @returns Object with name, firstName, and/or lastName if found\n */\nexport function findNameFields(fields: Record<string, string>): {\n name?: string\n firstName?: string\n lastName?: string\n} {\n let fullName: string | undefined\n let firstName: string | undefined\n let lastName: string | undefined\n\n for (const [fieldName, value] of Object.entries(fields)) {\n const trimmedValue = value?.trim()\n if (!trimmedValue) continue\n\n // Check for full name\n if (!fullName && matchesPatterns(fieldName, FULL_NAME_PATTERNS)) {\n fullName = trimmedValue\n }\n\n // Check for first name\n if (!firstName && matchesPatterns(fieldName, FIRST_NAME_PATTERNS)) {\n firstName = trimmedValue\n }\n\n // Check for last name\n if (!lastName && matchesPatterns(fieldName, LAST_NAME_PATTERNS)) {\n lastName = trimmedValue\n }\n }\n\n const result: { name?: string; firstName?: string; lastName?: string } = {}\n\n // If we have a full name, use it\n if (fullName) {\n result.name = fullName\n }\n // If we have first and last, combine them\n else if (firstName && lastName) {\n result.name = `${firstName} ${lastName}`\n result.firstName = firstName\n result.lastName = lastName\n }\n // If we only have first name\n else if (firstName) {\n result.firstName = firstName\n }\n // If we only have last name\n else if (lastName) {\n result.lastName = lastName\n }\n\n return result\n}\n\n/**\n * Identity extracted from a form submission.\n */\nexport interface ExtractedIdentity {\n email: string\n name?: string\n firstName?: string\n lastName?: string\n}\n\n/**\n * Extract identity information (email + name) from form fields.\n *\n * Returns undefined if no valid email is found (email is required for identification).\n *\n * @param fields - Form field key-value pairs\n * @param inputTypes - Optional map of field names to input types\n * @returns Extracted identity with email and optional name fields, or undefined\n */\nexport function extractIdentityFromForm(\n fields: Record<string, string>,\n inputTypes?: Map<string, string>,\n): ExtractedIdentity | undefined {\n const email = findEmailField(fields, inputTypes)\n\n // Email is required for identification\n if (!email) {\n return undefined\n }\n\n const nameFields = findNameFields(fields)\n\n return {\n email,\n ...nameFields,\n }\n}\n","import type {\n CustomEvent,\n FormEvent,\n IdentifyEvent,\n IngestPayload,\n PageviewEvent,\n SourceType,\n TrackerEvent,\n UtmParams,\n} from \"./types\"\nimport { extractPathFromUrl, extractUtmParams } from \"./utils\"\n\n// ============================================\n// EVENT BUILDERS\n// ============================================\n\ninterface BaseEventParams {\n url: string\n referrer?: string\n timestamp?: number\n}\n\n/**\n * Build a pageview event.\n */\nexport function buildPageviewEvent(params: BaseEventParams & { title?: string }): PageviewEvent {\n const { url, referrer, timestamp, title } = params\n return {\n type: \"pageview\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n title,\n }\n}\n\n/**\n * Build a form event.\n */\nexport function buildFormEvent(\n params: BaseEventParams & {\n formId?: string\n formFields?: Record<string, string>\n },\n): FormEvent {\n const { url, referrer, timestamp, formId, formFields } = params\n return {\n type: \"form\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n formId,\n formFields,\n }\n}\n\n/**\n * Build an identify event.\n */\nexport function buildIdentifyEvent(\n params: BaseEventParams & {\n email?: string\n userId?: string\n traits?: Record<string, string | number | boolean | null>\n },\n): IdentifyEvent {\n const { url, referrer, timestamp, email, userId, traits } = params\n return {\n type: \"identify\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n email,\n userId,\n traits,\n }\n}\n\n/**\n * Build a custom event.\n */\nexport function buildCustomEvent(\n params: BaseEventParams & {\n eventName: string\n properties?: Record<string, string | number | boolean | null>\n },\n): CustomEvent {\n const { url, referrer, timestamp, eventName, properties } = params\n return {\n type: \"custom\",\n timestamp: timestamp ?? Date.now(),\n url,\n path: extractPathFromUrl(url),\n referrer,\n utm: extractUtmParams(url),\n eventName,\n properties,\n }\n}\n\n// ============================================\n// PAYLOAD BUILDER\n// ============================================\n\n/**\n * Build an ingest payload from events.\n */\nexport function buildIngestPayload(\n visitorId: string,\n source: SourceType,\n events: TrackerEvent[],\n): IngestPayload {\n return {\n visitorId,\n source,\n events,\n }\n}\n\n// ============================================\n// BATCH HELPERS\n// ============================================\n\n/**\n * Maximum number of events in a single batch.\n */\nexport const MAX_BATCH_SIZE = 100\n\n/**\n * Split events into batches of MAX_BATCH_SIZE.\n */\nexport function batchEvents(events: TrackerEvent[]): TrackerEvent[][] {\n const batches: TrackerEvent[][] = []\n for (let i = 0; i < events.length; i += MAX_BATCH_SIZE) {\n batches.push(events.slice(i, i + MAX_BATCH_SIZE))\n }\n return batches\n}\n","import { type ExtractedIdentity, extractIdentityFromForm, sanitizeFormFields } from \"@outlit/core\"\n\n// ============================================\n// PAGEVIEW TRACKING\n// ============================================\n\ntype PageviewCallback = (url: string, referrer: string, title: string) => void\n\nlet pageviewCallback: PageviewCallback | null = null\nlet lastUrl: string | null = null\n\n/**\n * Initialize automatic pageview tracking.\n * Captures initial pageview and listens for SPA navigation.\n */\nexport function initPageviewTracking(callback: PageviewCallback): void {\n pageviewCallback = callback\n\n // Capture initial pageview\n capturePageview()\n\n // Listen for SPA navigation\n setupSpaListeners()\n}\n\n/**\n * Capture a pageview event.\n */\nfunction capturePageview(): void {\n if (!pageviewCallback) return\n\n const url = window.location.href\n const referrer = document.referrer\n const title = document.title\n\n // Avoid duplicate pageviews for the same URL\n if (url === lastUrl) return\n lastUrl = url\n\n pageviewCallback(url, referrer, title)\n}\n\n/**\n * Set up listeners for SPA navigation.\n */\nfunction setupSpaListeners(): void {\n // Listen for popstate (browser back/forward)\n window.addEventListener(\"popstate\", () => {\n capturePageview()\n })\n\n // Monkey-patch pushState and replaceState\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n\n history.pushState = function (...args) {\n originalPushState.apply(this, args)\n capturePageview()\n }\n\n history.replaceState = function (...args) {\n originalReplaceState.apply(this, args)\n capturePageview()\n }\n}\n\n// ============================================\n// FORM TRACKING\n// ============================================\n\ntype FormCallback = (\n url: string,\n formId: string | undefined,\n fields: Record<string, string>,\n) => void\n\ntype IdentityCallback = (identity: ExtractedIdentity) => void\n\nlet formCallback: FormCallback | null = null\nlet formDenylist: string[] | undefined\nlet identityCallback: IdentityCallback | null = null\n\n/**\n * Initialize automatic form tracking.\n * Captures form submissions with field sanitization.\n *\n * @param callback - Called when a form is submitted with sanitized fields\n * @param denylist - Optional list of field names to exclude\n * @param onIdentity - Optional callback for auto-identification when email is found\n */\nexport function initFormTracking(\n callback: FormCallback,\n denylist?: string[],\n onIdentity?: IdentityCallback,\n): void {\n formCallback = callback\n formDenylist = denylist\n identityCallback = onIdentity ?? null\n\n // Listen for form submissions\n document.addEventListener(\"submit\", handleFormSubmit, true)\n}\n\n/**\n * Handle form submission events.\n */\nfunction handleFormSubmit(event: Event): void {\n if (!formCallback) return\n\n const form = event.target as HTMLFormElement\n if (!(form instanceof HTMLFormElement)) return\n\n const url = window.location.href\n const formId = form.id || form.name || undefined\n\n // Extract form fields and input types\n const formData = new FormData(form)\n const fields: Record<string, string> = {}\n const inputTypes = new Map<string, string>()\n\n // Get input types for better email detection\n const inputs = form.querySelectorAll(\"input, select, textarea\")\n for (const input of inputs) {\n const name = input.getAttribute(\"name\")\n if (name && input instanceof HTMLInputElement) {\n inputTypes.set(name, input.type)\n }\n }\n\n formData.forEach((value, key) => {\n // Only capture string values, skip files\n if (typeof value === \"string\") {\n fields[key] = value\n }\n })\n\n // Sanitize fields to remove sensitive data\n const sanitizedFields = sanitizeFormFields(fields, formDenylist)\n\n // Auto-identify if callback is set and we find identity fields\n // Use unsanitized fields for identity extraction (email might be in there)\n if (identityCallback) {\n const identity = extractIdentityFromForm(fields, inputTypes)\n if (identity) {\n identityCallback(identity)\n }\n }\n\n // Emit form event (with sanitized fields)\n if (sanitizedFields && Object.keys(sanitizedFields).length > 0) {\n formCallback(url, formId, sanitizedFields)\n }\n}\n\n// ============================================\n// CLEANUP\n// ============================================\n\n/**\n * Stop all autocapture tracking.\n */\nexport function stopAutocapture(): void {\n pageviewCallback = null\n formCallback = null\n identityCallback = null\n document.removeEventListener(\"submit\", handleFormSubmit, true)\n}\n","// ============================================\n// VISITOR ID STORAGE\n// ============================================\n\nconst VISITOR_ID_KEY = \"outlit_visitor_id\"\n\n/**\n * Generate a UUID v4.\n * Uses crypto.randomUUID if available, otherwise falls back to manual generation.\n */\nexport function generateVisitorId(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n\n // Fallback for older browsers\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === \"x\" ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get the visitor ID from storage, generating a new one if needed.\n * Tries localStorage first, falls back to cookie.\n */\nexport function getOrCreateVisitorId(): string {\n // Try localStorage first\n try {\n const stored = localStorage.getItem(VISITOR_ID_KEY)\n if (stored && isValidUuid(stored)) {\n return stored\n }\n } catch {\n // localStorage not available\n }\n\n // Try cookie fallback\n const cookieValue = getCookie(VISITOR_ID_KEY)\n if (cookieValue && isValidUuid(cookieValue)) {\n // Also store in localStorage for consistency\n try {\n localStorage.setItem(VISITOR_ID_KEY, cookieValue)\n } catch {\n // Ignore\n }\n return cookieValue\n }\n\n // Generate new visitor ID\n const visitorId = generateVisitorId()\n persistVisitorId(visitorId)\n return visitorId\n}\n\n/**\n * Persist visitor ID to both localStorage and cookie.\n */\nfunction persistVisitorId(visitorId: string): void {\n // Store in localStorage\n try {\n localStorage.setItem(VISITOR_ID_KEY, visitorId)\n } catch {\n // localStorage not available\n }\n\n // Also store in cookie for cross-subdomain support\n setCookie(VISITOR_ID_KEY, visitorId, 365) // 1 year\n}\n\n/**\n * Basic UUID validation.\n */\nfunction isValidUuid(value: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)\n}\n\n// ============================================\n// COOKIE HELPERS\n// ============================================\n\nfunction getCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null\n\n const value = `; ${document.cookie}`\n const parts = value.split(`; ${name}=`)\n if (parts.length === 2) {\n return parts.pop()?.split(\";\").shift() ?? null\n }\n return null\n}\n\n/**\n * Get the root domain for cross-subdomain cookie sharing.\n * e.g., \"www.example.com\" → \"example.com\"\n * \"app.staging.example.com\" → \"example.com\"\n * \"localhost\" → null (no domain attribute needed)\n */\nfunction getRootDomain(): string | null {\n if (typeof window === \"undefined\") return null\n\n const hostname = window.location.hostname\n\n // Don't set domain for localhost or IP addresses\n if (hostname === \"localhost\" || /^(\\d{1,3}\\.){3}\\d{1,3}$/.test(hostname)) {\n return null\n }\n\n // Split hostname into parts\n const parts = hostname.split(\".\")\n\n // For simple domains like \"example.com\", return \".example.com\"\n // For subdomains like \"www.example.com\" or \"app.example.com\", return \".example.com\"\n if (parts.length >= 2) {\n // Handle common TLDs with two parts (e.g., .co.uk, .com.au)\n const twoPartTlds = [\"co.uk\", \"com.au\", \"co.nz\", \"org.uk\", \"net.au\", \"com.br\"]\n const lastTwo = parts.slice(-2).join(\".\")\n\n if (twoPartTlds.includes(lastTwo) && parts.length >= 3) {\n // e.g., \"www.example.co.uk\" → \"example.co.uk\"\n return parts.slice(-3).join(\".\")\n }\n\n // Standard case: \"www.example.com\" → \"example.com\"\n return parts.slice(-2).join(\".\")\n }\n\n return null\n}\n\nfunction setCookie(name: string, value: string, days: number): void {\n if (typeof document === \"undefined\") return\n\n const expires = new Date()\n expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000)\n\n // Build cookie string\n let cookie = `${name}=${value};expires=${expires.toUTCString()};path=/;SameSite=Lax`\n\n // Add domain for cross-subdomain support\n const rootDomain = getRootDomain()\n if (rootDomain) {\n cookie += `;domain=${rootDomain}`\n }\n\n document.cookie = cookie\n}\n","import {\n type BrowserIdentifyOptions,\n type BrowserTrackOptions,\n DEFAULT_API_HOST,\n type TrackerConfig,\n type TrackerEvent,\n buildCustomEvent,\n buildFormEvent,\n buildIdentifyEvent,\n buildIngestPayload,\n buildPageviewEvent,\n} from \"@outlit/core\"\nimport { initFormTracking, initPageviewTracking, stopAutocapture } from \"./autocapture\"\nimport { getOrCreateVisitorId } from \"./storage\"\n\n// ============================================\n// TRACKER CLASS\n// ============================================\n\nexport interface TrackerOptions extends TrackerConfig {\n /**\n * Automatically start tracking on init.\n * Set to false if you need to wait for user consent before tracking.\n * Call enableTracking() to start tracking after consent is obtained.\n * @default true\n */\n autoTrack?: boolean\n trackPageviews?: boolean\n trackForms?: boolean\n formFieldDenylist?: string[]\n flushInterval?: number\n /**\n * Automatically identify users when they submit forms with email fields.\n * Extracts email and name (first/last) from form fields using heuristics.\n * @default true\n */\n autoIdentify?: boolean\n}\n\nexport class Tracker {\n private publicKey: string\n private apiHost: string\n private visitorId: string | null = null\n private eventQueue: TrackerEvent[] = []\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushInterval: number\n private isInitialized = false\n private isTrackingEnabled = false\n private options: TrackerOptions\n\n constructor(options: TrackerOptions) {\n this.publicKey = options.publicKey\n this.apiHost = options.apiHost ?? DEFAULT_API_HOST\n this.flushInterval = options.flushInterval ?? 5000\n this.options = options\n\n // Set up beforeunload handler\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"beforeunload\", () => {\n this.flush()\n })\n }\n\n this.isInitialized = true\n\n // Start tracking immediately unless autoTrack is explicitly false\n if (options.autoTrack !== false) {\n this.enableTracking()\n }\n }\n\n // ============================================\n // PUBLIC API\n // ============================================\n\n /**\n * Enable tracking. Call this after obtaining user consent.\n * This will:\n * - Generate/retrieve the visitor ID\n * - Start automatic pageview and form tracking (if configured)\n * - Begin sending events to the server\n *\n * If autoTrack is true (default), this is called automatically on init.\n */\n enableTracking(): void {\n if (this.isTrackingEnabled) {\n return // Already enabled\n }\n\n // Now we can generate/retrieve the visitor ID (sets cookies/localStorage)\n this.visitorId = getOrCreateVisitorId()\n\n // Start the flush timer\n this.startFlushTimer()\n\n // Initialize autocapture if enabled\n if (this.options.trackPageviews !== false) {\n this.initPageviewTracking()\n }\n\n if (this.options.trackForms !== false) {\n this.initFormTracking(this.options.formFieldDenylist)\n }\n\n this.isTrackingEnabled = true\n }\n\n /**\n * Check if tracking is currently enabled.\n */\n isEnabled(): boolean {\n return this.isTrackingEnabled\n }\n\n /**\n * Track a custom event.\n */\n track(eventName: string, properties?: BrowserTrackOptions[\"properties\"]): void {\n if (!this.isTrackingEnabled) {\n console.warn(\"[Outlit] Tracking not enabled. Call enableTracking() first.\")\n return\n }\n\n const event = buildCustomEvent({\n url: window.location.href,\n referrer: document.referrer,\n eventName,\n properties,\n })\n this.enqueue(event)\n }\n\n /**\n * Identify the current visitor.\n * Links the anonymous visitor to a known user.\n */\n identify(options: BrowserIdentifyOptions): void {\n if (!this.isTrackingEnabled) {\n console.warn(\"[Outlit] Tracking not enabled. Call enableTracking() first.\")\n return\n }\n\n const event = buildIdentifyEvent({\n url: window.location.href,\n referrer: document.referrer,\n email: options.email,\n userId: options.userId,\n traits: options.traits,\n })\n this.enqueue(event)\n }\n\n /**\n * Get the current visitor ID.\n * Returns null if tracking is not enabled.\n */\n getVisitorId(): string | null {\n return this.visitorId\n }\n\n /**\n * Manually flush the event queue.\n */\n async flush(): Promise<void> {\n if (this.eventQueue.length === 0) return\n\n const events = [...this.eventQueue]\n this.eventQueue = []\n\n await this.sendEvents(events)\n }\n\n /**\n * Shutdown the tracker.\n */\n async shutdown(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n stopAutocapture()\n await this.flush()\n }\n\n // ============================================\n // INTERNAL METHODS\n // ============================================\n\n private initPageviewTracking(): void {\n initPageviewTracking((url, referrer, title) => {\n const event = buildPageviewEvent({ url, referrer, title })\n this.enqueue(event)\n })\n }\n\n private initFormTracking(denylist?: string[]): void {\n // Create identity callback if autoIdentify is enabled (default: true)\n const identityCallback =\n this.options.autoIdentify !== false\n ? (identity: { email: string; name?: string; firstName?: string; lastName?: string }) => {\n // Build traits from extracted name fields\n const traits: Record<string, string> = {}\n if (identity.name) traits.name = identity.name\n if (identity.firstName) traits.firstName = identity.firstName\n if (identity.lastName) traits.lastName = identity.lastName\n\n this.identify({\n email: identity.email,\n traits: Object.keys(traits).length > 0 ? traits : undefined,\n })\n }\n : undefined\n\n initFormTracking(\n (url, formId, fields) => {\n const event = buildFormEvent({\n url,\n referrer: document.referrer,\n formId,\n formFields: fields,\n })\n this.enqueue(event)\n },\n denylist,\n identityCallback,\n )\n }\n\n private enqueue(event: TrackerEvent): void {\n this.eventQueue.push(event)\n\n // Flush immediately if queue is getting large\n if (this.eventQueue.length >= 10) {\n this.flush()\n }\n }\n\n private startFlushTimer(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setInterval(() => {\n this.flush()\n }, this.flushInterval)\n }\n\n private async sendEvents(events: TrackerEvent[]): Promise<void> {\n if (events.length === 0) return\n if (!this.visitorId) return // Can't send without a visitor ID\n\n const payload = buildIngestPayload(this.visitorId, \"pixel\", events)\n const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`\n\n try {\n // Use sendBeacon for better reliability on page unload\n if (typeof navigator !== \"undefined\" && navigator.sendBeacon) {\n const blob = new Blob([JSON.stringify(payload)], { type: \"application/json\" })\n const sent = navigator.sendBeacon(url, blob)\n if (sent) return\n }\n\n // Fallback to fetch\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n keepalive: true,\n })\n } catch (error) {\n // Silently fail - we don't want to break the user's site\n console.warn(\"[Outlit] Failed to send events:\", error)\n }\n }\n}\n\n// ============================================\n// SINGLETON INSTANCE\n// ============================================\n\nlet instance: Tracker | null = null\n\n/**\n * Initialize the Outlit tracker.\n * Should be called once at app startup.\n */\nexport function init(options: TrackerOptions): Tracker {\n if (instance) {\n console.warn(\"[Outlit] Tracker already initialized\")\n return instance\n }\n\n instance = new Tracker(options)\n return instance\n}\n\n/**\n * Get the tracker instance.\n * Throws if not initialized.\n */\nexport function getInstance(): Tracker {\n if (!instance) {\n throw new Error(\"[Outlit] Tracker not initialized. Call init() first.\")\n }\n return instance\n}\n\n/**\n * Track a custom event.\n * Convenience method that uses the singleton instance.\n */\nexport function track(eventName: string, properties?: BrowserTrackOptions[\"properties\"]): void {\n getInstance().track(eventName, properties)\n}\n\n/**\n * Identify the current visitor.\n * Convenience method that uses the singleton instance.\n */\nexport function identify(options: BrowserIdentifyOptions): void {\n getInstance().identify(options)\n}\n\n/**\n * Enable tracking after consent is obtained.\n * Call this in your consent management tool's callback.\n * Convenience method that uses the singleton instance.\n */\nexport function enableTracking(): void {\n getInstance().enableTracking()\n}\n\n/**\n * Check if tracking is currently enabled.\n * Convenience method that uses the singleton instance.\n */\nexport function isTrackingEnabled(): boolean {\n return getInstance().isEnabled()\n}\n"],"mappings":"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","names":["script_exports","__export","outlit","DEFAULT_API_HOST","DEFAULT_DENIED_FORM_FIELDS","extractUtmParams","url","params","utm","extractPathFromUrl","isFieldDenied","fieldName","denylist","normalizedName","denied","normalizedDenied","looksLikeSensitiveValue","value","cleaned","sanitizeFormFields","fields","customDenylist","sanitized","key","isValidEmail","value","EMAIL_FIELD_PATTERNS","FULL_NAME_PATTERNS","FIRST_NAME_PATTERNS","LAST_NAME_PATTERNS","matchesPatterns","fieldName","patterns","normalized","pattern","findEmailField","fields","inputTypes","inputType","findNameFields","fullName","firstName","lastName","trimmedValue","result","extractIdentityFromForm","email","nameFields","buildPageviewEvent","params","url","referrer","timestamp","title","extractPathFromUrl","extractUtmParams","buildFormEvent","formId","formFields","buildIdentifyEvent","userId","traits","buildCustomEvent","eventName","properties","buildIngestPayload","visitorId","source","events","pageviewCallback","lastUrl","initPageviewTracking","callback","capturePageview","setupSpaListeners","url","referrer","title","originalPushState","originalReplaceState","args","formCallback","formDenylist","identityCallback","initFormTracking","denylist","onIdentity","handleFormSubmit","event","form","formId","formData","fields","inputTypes","inputs","input","name","value","key","sanitizedFields","sanitizeFormFields","identity","extractIdentityFromForm","stopAutocapture","VISITOR_ID_KEY","generateVisitorId","c","r","getOrCreateVisitorId","stored","isValidUuid","cookieValue","getCookie","visitorId","persistVisitorId","setCookie","value","name","parts","getRootDomain","hostname","twoPartTlds","lastTwo","days","expires","cookie","rootDomain","Tracker","options","DEFAULT_API_HOST","getOrCreateVisitorId","eventName","properties","event","buildCustomEvent","buildIdentifyEvent","events","stopAutocapture","initPageviewTracking","url","referrer","title","buildPageviewEvent","denylist","identityCallback","identity","traits","initFormTracking","formId","fields","buildFormEvent","payload","buildIngestPayload","blob","error","existingStub","stubQueue","outlit","options","Tracker","method","args","self","eventName","properties","autoInit","script","publicKey","apiHost","trackPageviews","trackForms","autoTrack","autoIdentify"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { c as TrackerOptions, T as Tracker } from '../tracker-CocH64L9.mjs';
|
|
5
|
+
import { BrowserTrackOptions, BrowserIdentifyOptions } from '@outlit/core';
|
|
6
|
+
export { BrowserIdentifyOptions, BrowserTrackOptions, TrackerConfig } from '@outlit/core';
|
|
7
|
+
|
|
8
|
+
interface OutlitContextValue {
|
|
9
|
+
tracker: Tracker | null;
|
|
10
|
+
isInitialized: boolean;
|
|
11
|
+
isTrackingEnabled: boolean;
|
|
12
|
+
enableTracking: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare const OutlitContext: react.Context<OutlitContextValue>;
|
|
15
|
+
interface OutlitProviderProps extends Omit<TrackerOptions, "trackPageviews"> {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to automatically track pageviews.
|
|
19
|
+
* When true (default), tracks pageviews on route changes.
|
|
20
|
+
*/
|
|
21
|
+
trackPageviews?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether to start tracking automatically on mount.
|
|
24
|
+
* Set to false if you need to wait for user consent.
|
|
25
|
+
* Call enableTracking() (from useOutlit hook) after consent is obtained.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
autoTrack?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Outlit Provider component.
|
|
32
|
+
* Initializes the tracker and provides it to child components via context.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // layout.tsx - Auto tracking (default)
|
|
37
|
+
* import { OutlitProvider } from '@outlit/tracker/react'
|
|
38
|
+
*
|
|
39
|
+
* export default function RootLayout({ children }) {
|
|
40
|
+
* return (
|
|
41
|
+
* <OutlitProvider publicKey="pk_xxx" trackPageviews>
|
|
42
|
+
* {children}
|
|
43
|
+
* </OutlitProvider>
|
|
44
|
+
* )
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* // layout.tsx - With consent management
|
|
51
|
+
* import { OutlitProvider } from '@outlit/tracker/react'
|
|
52
|
+
*
|
|
53
|
+
* export default function RootLayout({ children }) {
|
|
54
|
+
* return (
|
|
55
|
+
* <OutlitProvider publicKey="pk_xxx" autoTrack={false}>
|
|
56
|
+
* {children}
|
|
57
|
+
* </OutlitProvider>
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* // ConsentBanner.tsx
|
|
62
|
+
* import { useOutlit } from '@outlit/tracker/react'
|
|
63
|
+
*
|
|
64
|
+
* function ConsentBanner() {
|
|
65
|
+
* const { enableTracking } = useOutlit()
|
|
66
|
+
* return <button onClick={enableTracking}>Accept Cookies</button>
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
|
|
71
|
+
|
|
72
|
+
interface UseOutlitReturn {
|
|
73
|
+
/**
|
|
74
|
+
* Track a custom event.
|
|
75
|
+
*/
|
|
76
|
+
track: (eventName: string, properties?: BrowserTrackOptions["properties"]) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Identify the current visitor.
|
|
79
|
+
* Links the anonymous visitor to a known user.
|
|
80
|
+
*/
|
|
81
|
+
identify: (options: BrowserIdentifyOptions) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Get the current visitor ID.
|
|
84
|
+
* Returns null if tracking is not enabled.
|
|
85
|
+
*/
|
|
86
|
+
getVisitorId: () => string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Whether the tracker is initialized.
|
|
89
|
+
*/
|
|
90
|
+
isInitialized: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether tracking is currently enabled.
|
|
93
|
+
* Will be false if autoTrack is false and enableTracking() hasn't been called.
|
|
94
|
+
*/
|
|
95
|
+
isTrackingEnabled: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Enable tracking. Call this after obtaining user consent.
|
|
98
|
+
* Only needed if you initialized with autoTrack: false.
|
|
99
|
+
*/
|
|
100
|
+
enableTracking: () => void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Hook to access the Outlit tracker.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* import { useOutlit } from '@outlit/tracker/react'
|
|
108
|
+
*
|
|
109
|
+
* function MyComponent() {
|
|
110
|
+
* const { track, identify } = useOutlit()
|
|
111
|
+
*
|
|
112
|
+
* return (
|
|
113
|
+
* <button onClick={() => track('button_clicked', { id: 'cta' })}>
|
|
114
|
+
* Click me
|
|
115
|
+
* </button>
|
|
116
|
+
* )
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* // With consent management
|
|
123
|
+
* function ConsentBanner() {
|
|
124
|
+
* const { enableTracking, isTrackingEnabled } = useOutlit()
|
|
125
|
+
*
|
|
126
|
+
* if (isTrackingEnabled) return null
|
|
127
|
+
*
|
|
128
|
+
* return (
|
|
129
|
+
* <div>
|
|
130
|
+
* <p>We use cookies to improve your experience.</p>
|
|
131
|
+
* <button onClick={enableTracking}>Accept</button>
|
|
132
|
+
* </div>
|
|
133
|
+
* )
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
declare function useOutlit(): UseOutlitReturn;
|
|
138
|
+
/**
|
|
139
|
+
* Convenience hook that returns just the track function.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* import { useTrack } from '@outlit/tracker/react'
|
|
144
|
+
*
|
|
145
|
+
* function MyComponent() {
|
|
146
|
+
* const track = useTrack()
|
|
147
|
+
* return <button onClick={() => track('clicked')}>Click</button>
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function useTrack(): (eventName: string, properties?: BrowserTrackOptions["properties"]) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Convenience hook that returns just the identify function.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```tsx
|
|
157
|
+
* import { useIdentify } from '@outlit/tracker/react'
|
|
158
|
+
*
|
|
159
|
+
* function LoginForm() {
|
|
160
|
+
* const identify = useIdentify()
|
|
161
|
+
*
|
|
162
|
+
* const onLogin = (user) => {
|
|
163
|
+
* identify({ email: user.email, traits: { name: user.name } })
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function useIdentify(): (options: BrowserIdentifyOptions) => void;
|
|
169
|
+
|
|
170
|
+
export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, useIdentify, useOutlit, useTrack };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { c as TrackerOptions, T as Tracker } from '../tracker-CocH64L9.js';
|
|
5
|
+
import { BrowserTrackOptions, BrowserIdentifyOptions } from '@outlit/core';
|
|
6
|
+
export { BrowserIdentifyOptions, BrowserTrackOptions, TrackerConfig } from '@outlit/core';
|
|
7
|
+
|
|
8
|
+
interface OutlitContextValue {
|
|
9
|
+
tracker: Tracker | null;
|
|
10
|
+
isInitialized: boolean;
|
|
11
|
+
isTrackingEnabled: boolean;
|
|
12
|
+
enableTracking: () => void;
|
|
13
|
+
}
|
|
14
|
+
declare const OutlitContext: react.Context<OutlitContextValue>;
|
|
15
|
+
interface OutlitProviderProps extends Omit<TrackerOptions, "trackPageviews"> {
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Whether to automatically track pageviews.
|
|
19
|
+
* When true (default), tracks pageviews on route changes.
|
|
20
|
+
*/
|
|
21
|
+
trackPageviews?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether to start tracking automatically on mount.
|
|
24
|
+
* Set to false if you need to wait for user consent.
|
|
25
|
+
* Call enableTracking() (from useOutlit hook) after consent is obtained.
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
autoTrack?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Outlit Provider component.
|
|
32
|
+
* Initializes the tracker and provides it to child components via context.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // layout.tsx - Auto tracking (default)
|
|
37
|
+
* import { OutlitProvider } from '@outlit/tracker/react'
|
|
38
|
+
*
|
|
39
|
+
* export default function RootLayout({ children }) {
|
|
40
|
+
* return (
|
|
41
|
+
* <OutlitProvider publicKey="pk_xxx" trackPageviews>
|
|
42
|
+
* {children}
|
|
43
|
+
* </OutlitProvider>
|
|
44
|
+
* )
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* // layout.tsx - With consent management
|
|
51
|
+
* import { OutlitProvider } from '@outlit/tracker/react'
|
|
52
|
+
*
|
|
53
|
+
* export default function RootLayout({ children }) {
|
|
54
|
+
* return (
|
|
55
|
+
* <OutlitProvider publicKey="pk_xxx" autoTrack={false}>
|
|
56
|
+
* {children}
|
|
57
|
+
* </OutlitProvider>
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* // ConsentBanner.tsx
|
|
62
|
+
* import { useOutlit } from '@outlit/tracker/react'
|
|
63
|
+
*
|
|
64
|
+
* function ConsentBanner() {
|
|
65
|
+
* const { enableTracking } = useOutlit()
|
|
66
|
+
* return <button onClick={enableTracking}>Accept Cookies</button>
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
declare function OutlitProvider({ children, publicKey, apiHost, trackPageviews, trackForms, formFieldDenylist, flushInterval, autoTrack, autoIdentify, }: OutlitProviderProps): react_jsx_runtime.JSX.Element;
|
|
71
|
+
|
|
72
|
+
interface UseOutlitReturn {
|
|
73
|
+
/**
|
|
74
|
+
* Track a custom event.
|
|
75
|
+
*/
|
|
76
|
+
track: (eventName: string, properties?: BrowserTrackOptions["properties"]) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Identify the current visitor.
|
|
79
|
+
* Links the anonymous visitor to a known user.
|
|
80
|
+
*/
|
|
81
|
+
identify: (options: BrowserIdentifyOptions) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Get the current visitor ID.
|
|
84
|
+
* Returns null if tracking is not enabled.
|
|
85
|
+
*/
|
|
86
|
+
getVisitorId: () => string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Whether the tracker is initialized.
|
|
89
|
+
*/
|
|
90
|
+
isInitialized: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether tracking is currently enabled.
|
|
93
|
+
* Will be false if autoTrack is false and enableTracking() hasn't been called.
|
|
94
|
+
*/
|
|
95
|
+
isTrackingEnabled: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Enable tracking. Call this after obtaining user consent.
|
|
98
|
+
* Only needed if you initialized with autoTrack: false.
|
|
99
|
+
*/
|
|
100
|
+
enableTracking: () => void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Hook to access the Outlit tracker.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* import { useOutlit } from '@outlit/tracker/react'
|
|
108
|
+
*
|
|
109
|
+
* function MyComponent() {
|
|
110
|
+
* const { track, identify } = useOutlit()
|
|
111
|
+
*
|
|
112
|
+
* return (
|
|
113
|
+
* <button onClick={() => track('button_clicked', { id: 'cta' })}>
|
|
114
|
+
* Click me
|
|
115
|
+
* </button>
|
|
116
|
+
* )
|
|
117
|
+
* }
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* // With consent management
|
|
123
|
+
* function ConsentBanner() {
|
|
124
|
+
* const { enableTracking, isTrackingEnabled } = useOutlit()
|
|
125
|
+
*
|
|
126
|
+
* if (isTrackingEnabled) return null
|
|
127
|
+
*
|
|
128
|
+
* return (
|
|
129
|
+
* <div>
|
|
130
|
+
* <p>We use cookies to improve your experience.</p>
|
|
131
|
+
* <button onClick={enableTracking}>Accept</button>
|
|
132
|
+
* </div>
|
|
133
|
+
* )
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
declare function useOutlit(): UseOutlitReturn;
|
|
138
|
+
/**
|
|
139
|
+
* Convenience hook that returns just the track function.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```tsx
|
|
143
|
+
* import { useTrack } from '@outlit/tracker/react'
|
|
144
|
+
*
|
|
145
|
+
* function MyComponent() {
|
|
146
|
+
* const track = useTrack()
|
|
147
|
+
* return <button onClick={() => track('clicked')}>Click</button>
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function useTrack(): (eventName: string, properties?: BrowserTrackOptions["properties"]) => void;
|
|
152
|
+
/**
|
|
153
|
+
* Convenience hook that returns just the identify function.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```tsx
|
|
157
|
+
* import { useIdentify } from '@outlit/tracker/react'
|
|
158
|
+
*
|
|
159
|
+
* function LoginForm() {
|
|
160
|
+
* const identify = useIdentify()
|
|
161
|
+
*
|
|
162
|
+
* const onLogin = (user) => {
|
|
163
|
+
* identify({ email: user.email, traits: { name: user.name } })
|
|
164
|
+
* }
|
|
165
|
+
* }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare function useIdentify(): (options: BrowserIdentifyOptions) => void;
|
|
169
|
+
|
|
170
|
+
export { OutlitContext, type OutlitContextValue, OutlitProvider, type OutlitProviderProps, type UseOutlitReturn, useIdentify, useOutlit, useTrack };
|