@od-oneapp/analytics 2026.2.1701 → 2026.2.2001-canary.1

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/{ai-YMnynb-t.mjs → ai-BSaTnqoc.mjs} +2 -2
  3. package/{ai-YMnynb-t.mjs.map → ai-BSaTnqoc.mjs.map} +1 -1
  4. package/{client-D339NFJS.mjs → client-B0v807xN.mjs} +1 -1
  5. package/{client-D339NFJS.mjs.map → client-B0v807xN.mjs.map} +1 -1
  6. package/{client-CcFTauAh.mjs → client-BrLI6DF9.mjs} +1 -1
  7. package/{client-CcFTauAh.mjs.map → client-BrLI6DF9.mjs.map} +1 -1
  8. package/{client-CeOLjbac.mjs → client-ClA-Qeks.mjs} +21 -6
  9. package/client-ClA-Qeks.mjs.map +1 -0
  10. package/client-next.d.mts +7 -7
  11. package/client-next.d.mts.map +1 -1
  12. package/client-next.mjs +1616 -31
  13. package/client-next.mjs.map +1 -1
  14. package/{client-CTzJVFU5.mjs → client-uX7stpb0.mjs} +2 -2
  15. package/{client-CTzJVFU5.mjs.map → client-uX7stpb0.mjs.map} +1 -1
  16. package/client.d.mts +9 -9
  17. package/client.mjs +8 -116
  18. package/client.mjs.map +1 -1
  19. package/{config-DPS6bSYo.d.mts → config-BA1zbRTh.d.mts} +2 -2
  20. package/{config-DPS6bSYo.d.mts.map → config-BA1zbRTh.d.mts.map} +1 -1
  21. package/{config-P6P5adJg.mjs → config-DKN8G0TI.mjs} +1 -1
  22. package/{config-P6P5adJg.mjs.map → config-DKN8G0TI.mjs.map} +1 -1
  23. package/{console-8bND3mMU.mjs → console-CDP0gY0K.mjs} +2 -2
  24. package/console-CDP0gY0K.mjs.map +1 -0
  25. package/core-C3oFD9Vr.mjs +95 -0
  26. package/core-C3oFD9Vr.mjs.map +1 -0
  27. package/{ecommerce-Cgu4wlux.mjs → ecommerce-DAo98u3I.mjs} +2 -2
  28. package/{ecommerce-Cgu4wlux.mjs.map → ecommerce-DAo98u3I.mjs.map} +1 -1
  29. package/{emitters-DldkVSPp.d.mts → emitters-B2gDJtjd.d.mts} +2 -2
  30. package/{emitters-DldkVSPp.d.mts.map → emitters-B2gDJtjd.d.mts.map} +1 -1
  31. package/{emitters-6-nKo8i-.mjs → emitters-RheHbeX2.mjs} +1 -1
  32. package/{emitters-6-nKo8i-.mjs.map → emitters-RheHbeX2.mjs.map} +1 -1
  33. package/{index-jPzXRn52.d.mts → index-Bq051UsT.d.mts} +3 -3
  34. package/{index-jPzXRn52.d.mts.map → index-Bq051UsT.d.mts.map} +1 -1
  35. package/{index-BfNWgfa5.d.mts → index-CuDXrdLY.d.mts} +14 -2
  36. package/{index-BfNWgfa5.d.mts.map → index-CuDXrdLY.d.mts.map} +1 -1
  37. package/{index-BkIWe--N.d.mts → index-uQHwlV0P.d.mts} +2 -2
  38. package/{index-BkIWe--N.d.mts.map → index-uQHwlV0P.d.mts.map} +1 -1
  39. package/logs-8LsA--uG.mjs +112 -0
  40. package/logs-8LsA--uG.mjs.map +1 -0
  41. package/{manager-DvRRjza6.d.mts → manager-Dh9loalA.d.mts} +3 -2
  42. package/manager-Dh9loalA.d.mts.map +1 -0
  43. package/module-D5xR4CAy.mjs +5808 -0
  44. package/module-D5xR4CAy.mjs.map +1 -0
  45. package/package.json +21 -27
  46. package/{posthog-bootstrap-DWxFrxlt.d.mts → posthog-bootstrap-D7Ot--Qc.d.mts} +3 -3
  47. package/{posthog-bootstrap-DWxFrxlt.d.mts.map → posthog-bootstrap-D7Ot--Qc.d.mts.map} +1 -1
  48. package/{posthog-bootstrap-CYfIy_WS.mjs → posthog-bootstrap-DkDBbvMJ.mjs} +81 -46
  49. package/posthog-bootstrap-DkDBbvMJ.mjs.map +1 -0
  50. package/providers-http-client.d.mts +1 -1
  51. package/providers-http-client.d.mts.map +1 -1
  52. package/providers-http-client.mjs +35 -7
  53. package/providers-http-client.mjs.map +1 -1
  54. package/providers-http-server.d.mts +1 -1
  55. package/providers-http-server.d.mts.map +1 -1
  56. package/providers-http-server.mjs +19 -3
  57. package/providers-http-server.mjs.map +1 -1
  58. package/providers-http.d.mts +9 -1
  59. package/providers-http.d.mts.map +1 -1
  60. package/server-edge.d.mts +3 -3
  61. package/server-edge.mjs +4 -4
  62. package/server-edge.mjs.map +1 -1
  63. package/server-next.d.mts +9 -9
  64. package/server-next.mjs +5 -5
  65. package/server.d.mts +9 -9
  66. package/server.mjs +5 -5
  67. package/{service-Duqnlppl.mjs → service-tn0JFfVH.mjs} +49 -116
  68. package/service-tn0JFfVH.mjs.map +1 -0
  69. package/shared.d.mts +4 -4
  70. package/shared.mjs +3 -3
  71. package/{types-BxBnNQ0V.d.mts → types-BbXOa_UL.d.mts} +1 -1
  72. package/{types-BxBnNQ0V.d.mts.map → types-BbXOa_UL.d.mts.map} +1 -1
  73. package/{types-CBvxUEaF.d.mts → types-DC5uYgdR.d.mts} +1 -1
  74. package/{types-CBvxUEaF.d.mts.map → types-DC5uYgdR.d.mts.map} +1 -1
  75. package/types.d.mts +3 -3
  76. package/{vercel-types-lwakUfoI.d.mts → vercel-types-tUdlBxJ-.d.mts} +1 -1
  77. package/{vercel-types-lwakUfoI.d.mts.map → vercel-types-tUdlBxJ-.d.mts.map} +1 -1
  78. package/client-CeOLjbac.mjs.map +0 -1
  79. package/console-8bND3mMU.mjs.map +0 -1
  80. package/manager-DvRRjza6.d.mts.map +0 -1
  81. package/posthog-bootstrap-CYfIy_WS.mjs.map +0 -1
  82. package/service-Duqnlppl.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"posthog-bootstrap-DkDBbvMJ.mjs","names":[],"sources":["../src/shared/utils/rate-limit.ts","../src/shared/utils/security.ts","../src/shared/utils/manager.ts","../src/shared/emitters/helpers.ts","../src/shared/utils/posthog-bootstrap.ts"],"sourcesContent":["/**\n * @fileoverview Rate limiting utilities for analytics calls\n * Rate limiting utilities for analytics calls\n *\n * Prevents overwhelming provider APIs, hitting rate limits, and excessive costs.\n * Uses token bucket algorithm for smooth rate limiting with bursts.\n *\n * @module rate-limit\n */\n\nimport { logWarn } from '@od-oneapp/shared/logger';\n\n/**\n * Rate limiter configuration\n */\nexport interface RateLimiterConfig {\n /** Maximum number of calls per window */\n maxCalls: number;\n /** Window size in milliseconds */\n windowMs: number;\n /** Maximum number of concurrent calls */\n maxConcurrent?: number;\n /** Whether to queue excess calls or drop them */\n queueExcess?: boolean;\n /** Maximum queue size */\n maxQueueSize?: number;\n}\n\n/**\n * Rate limiter for analytics calls\n *\n * Implements token bucket algorithm with optional call queuing.\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter({\n * maxCalls: 100,\n * windowMs: 1000, // 100 calls per second\n * maxConcurrent: 10,\n * });\n *\n * // Check if call is allowed\n * if (await limiter.tryAcquire()) {\n * await analytics.track('Event');\n * limiter.release();\n * }\n *\n * // Or use wrapper\n * await limiter.execute(() => analytics.track('Event'));\n * ```\n */\nexport class RateLimiter {\n private callCount = 0;\n private concurrentCount = 0;\n private windowStart = Date.now();\n private queue: Array<() => void> = [];\n private readonly config: Required<RateLimiterConfig>;\n\n constructor(config: RateLimiterConfig) {\n this.config = {\n maxCalls: config.maxCalls,\n windowMs: config.windowMs,\n maxConcurrent: config.maxConcurrent ?? Infinity,\n queueExcess: config.queueExcess ?? false,\n maxQueueSize: config.maxQueueSize ?? 1000,\n };\n }\n\n /**\n * Try to acquire a rate limit token\n *\n * @returns Promise resolving to whether the call is allowed\n */\n async tryAcquire(): Promise<boolean> {\n // Reset window if needed\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n\n // Check rate limit\n if (this.callCount >= this.config.maxCalls) {\n if (this.config.queueExcess && this.queue.length < this.config.maxQueueSize) {\n // Queue the call - will re-check rate limit when dequeued\n return new Promise<boolean>(resolve => {\n this.queue.push(() => {\n // Re-check rate limit when dequeued to prevent bypassing\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n if (this.callCount < this.config.maxCalls) {\n this.callCount++;\n resolve(true);\n } else {\n resolve(false);\n }\n });\n });\n }\n\n if (process.env.NODE_ENV === 'development') {\n logWarn('Rate limit exceeded', {\n maxCalls: this.config.maxCalls,\n windowMs: this.config.windowMs,\n currentCount: this.callCount,\n });\n }\n\n return false;\n }\n\n // Check concurrency limit\n if (this.concurrentCount >= this.config.maxConcurrent) {\n if (this.config.queueExcess && this.queue.length < this.config.maxQueueSize) {\n // Queue the call\n return new Promise<boolean>(resolve => {\n this.queue.push(() => resolve(true));\n });\n }\n\n return false;\n }\n\n this.callCount++;\n this.concurrentCount++;\n return true;\n }\n\n /**\n * Release a rate limit token\n */\n release(): void {\n this.concurrentCount = Math.max(0, this.concurrentCount - 1);\n\n // Process queue - only process if concurrency allows and rate limit allows\n if (this.queue.length > 0 && this.concurrentCount < this.config.maxConcurrent) {\n // Reset rate limit window if needed before processing queue\n const now = Date.now();\n if (now - this.windowStart >= this.config.windowMs) {\n this.callCount = 0;\n this.windowStart = now;\n }\n\n // Only process if rate limit allows\n if (this.callCount < this.config.maxCalls) {\n const next = this.queue.shift();\n if (next) {\n this.concurrentCount++;\n next();\n }\n }\n }\n }\n\n /**\n * Execute a function with rate limiting\n *\n * @param fn - Function to execute\n * @returns Promise resolving to function result\n */\n async execute<T>(fn: () => Promise<T>): Promise<T | null> {\n const allowed = await this.tryAcquire();\n\n if (!allowed) {\n return null;\n }\n\n try {\n return await fn();\n } finally {\n this.release();\n }\n }\n\n /**\n * Get current rate limiter stats\n */\n getStats(): {\n callCount: number;\n concurrentCount: number;\n queueSize: number;\n remainingCalls: number;\n windowStart: number;\n } {\n return {\n callCount: this.callCount,\n concurrentCount: this.concurrentCount,\n queueSize: this.queue.length,\n remainingCalls: Math.max(0, this.config.maxCalls - this.callCount),\n windowStart: this.windowStart,\n };\n }\n\n /**\n * Reset the rate limiter\n */\n reset(): void {\n this.callCount = 0;\n this.concurrentCount = 0;\n this.windowStart = Date.now();\n this.queue = [];\n }\n}\n\n/**\n * Simple in-memory rate limiter using Map\n *\n * Useful for per-provider or per-user rate limiting.\n */\nexport class MapRateLimiter {\n private limiters = new Map<string, RateLimiter>();\n private readonly config: RateLimiterConfig;\n\n constructor(config: RateLimiterConfig) {\n this.config = config;\n }\n\n /**\n * Get or create a rate limiter for a key\n */\n private getLimiter(key: string): RateLimiter {\n let limiter = this.limiters.get(key);\n\n if (!limiter) {\n limiter = new RateLimiter(this.config);\n this.limiters.set(key, limiter);\n }\n\n return limiter;\n }\n\n /**\n * Try to acquire a rate limit token for a key\n */\n async tryAcquire(key: string): Promise<boolean> {\n return this.getLimiter(key).tryAcquire();\n }\n\n /**\n * Release a rate limit token for a key\n */\n release(key: string): void {\n this.getLimiter(key).release();\n }\n\n /**\n * Execute a function with rate limiting for a key\n */\n async execute<T>(key: string, fn: () => Promise<T>): Promise<T | null> {\n return this.getLimiter(key).execute(fn);\n }\n\n /**\n * Get stats for a key\n */\n getStats(key: string): ReturnType<RateLimiter['getStats']> | null {\n const limiter = this.limiters.get(key);\n return limiter ? limiter.getStats() : null;\n }\n\n /**\n * Reset rate limiter for a key\n */\n reset(key: string): void {\n const limiter = this.limiters.get(key);\n if (limiter) {\n limiter.reset();\n }\n }\n\n /**\n * Clean up old limiters (call periodically)\n */\n cleanup(maxAge: number = 60000): void {\n const now = Date.now();\n\n for (const [key, limiter] of this.limiters.entries()) {\n const stats = limiter.getStats();\n\n // Remove if inactive for maxAge\n if (now - stats.windowStart > maxAge && stats.callCount === 0 && stats.queueSize === 0) {\n this.limiters.delete(key);\n }\n }\n }\n}\n","/**\n * @fileoverview Security and sanitization utilities for analytics data\n *\n * This module provides comprehensive security utilities for analytics data:\n *\n * - **PII Detection**: Detects personally identifiable information (emails, phones, SSNs, credit cards, IPs)\n * - **PII Redaction**: Redacts PII from strings before sending to analytics\n * - **XSS Protection**: Strips HTML and script tags to prevent XSS attacks\n * - **Property Validation**: Validates property keys to prevent prototype pollution\n * - **Payload Sanitization**: Comprehensive sanitization with size limits and depth checks\n * - **Size Limits**: Enforces maximum property and payload sizes\n *\n * **Security Features**:\n * - Maximum property value size: 100KB\n * - Maximum payload size: 1MB\n * - Maximum nesting depth: 10 levels\n * - Dangerous key filtering (prevents prototype pollution)\n * - HTML/script tag removal\n *\n * @module @od-oneapp/analytics/shared/utils/security\n */\n\nimport { logWarn } from '@od-oneapp/shared/logger';\n\n/**\n * Maximum allowed property value size (100KB).\n *\n * Prevents sending excessively large property values to analytics providers.\n *\n * @internal\n */\nconst MAX_PROPERTY_VALUE_SIZE = 100 * 1024;\n\n/**\n * Maximum allowed total payload size (1MB).\n *\n * Prevents sending excessively large payloads to analytics providers.\n *\n * @internal\n */\nconst MAX_PAYLOAD_SIZE = 1024 * 1024;\n\n/**\n * Maximum allowed object nesting depth.\n *\n * Prevents deeply nested objects that could cause performance issues or stack overflows.\n *\n * @internal\n */\nconst MAX_DEPTH = 10;\n\n/**\n * PII patterns for detection and filtering\n * Note: These regexes are intentionally complex for accurate PII detection.\n * They are used only on bounded, sanitized input with size limits.\n */\n/* eslint-disable security/detect-unsafe-regex */\nconst PII_PATTERNS = {\n email: /\\b[\\w%+.-]+@[\\d.A-Za-z-]+\\.[A-Za-z|]{2,}\\b/g,\n phone: /\\b(?:\\+?1[.-]?)?\\(?(\\d{3})\\)?[.-]?(\\d{3})[.-]?(\\d{4})\\b/g,\n ssn: /\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g,\n creditCard: /\\b(?:\\d{4}[ -]?){3}\\d{4}\\b/g,\n ipv4: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g,\n ipv6: /\\b(?:[\\da-f]{1,4}:){7}[\\da-f]{1,4}\\b/gi,\n};\n/* eslint-enable security/detect-unsafe-regex */\n\n/**\n * Dangerous property keys that should never be allowed\n */\nconst DANGEROUS_KEYS = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n]);\n\n/**\n * HTML/Script patterns for XSS protection\n * Note: These regexes are intentionally complex for accurate XSS detection.\n * They are used only on bounded, sanitized input with size limits.\n */\n/* eslint-disable security/detect-unsafe-regex */\nconst XSS_PATTERNS = {\n scriptTag: /<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi,\n onEvent: /\\bon\\w+\\s*=\\s*[\"'][^\"']*[\"']/gi,\n javascript: /javascript:/gi,\n dataUri: /data:text\\/html/gi,\n};\n/* eslint-enable security/detect-unsafe-regex */\n\n/**\n * Sanitization options.\n *\n * Configures how sanitization should be performed, including size limits,\n * depth limits, and what content to strip.\n */\nexport interface SanitizationOptions {\n /** Maximum object nesting depth */\n maxDepth?: number;\n /** Maximum size per property value in bytes */\n maxPropertySize?: number;\n /** Maximum total payload size in bytes */\n maxPayloadSize?: number;\n /** Pattern for allowed property keys */\n allowedKeyPattern?: RegExp;\n /** Whether to strip PII */\n stripPII?: boolean;\n /** Whether to strip HTML/scripts */\n stripHTML?: boolean;\n /** Whether to allow prototype pollution keys */\n allowDangerousKeys?: boolean;\n}\n\n/**\n * Sanitization result.\n *\n * Contains the sanitized data, whether modifications were made, and any warnings\n * generated during sanitization.\n */\nexport interface SanitizationResult<T = unknown> {\n /** Sanitized data */\n data: T;\n /** Whether sanitization made changes */\n modified: boolean;\n /** Warnings generated during sanitization */\n warnings: string[];\n}\n\n/**\n * Check if a value contains PII (Personally Identifiable Information).\n *\n * Detects common PII patterns including:\n * - Email addresses\n * - Phone numbers\n * - Social Security Numbers\n * - Credit card numbers\n * - IPv4 and IPv6 addresses\n *\n * @param {string} value - String value to check for PII\n * @returns {boolean} `true` if PII is detected, `false` otherwise\n *\n * @example\n * ```typescript\n * if (containsPII(userInput)) {\n * const sanitized = redactPII(userInput);\n * // Use sanitized value\n * }\n * ```\n */\nexport function containsPII(value: string): boolean {\n return Object.values(PII_PATTERNS).some(pattern => {\n // Global regexes are stateful; reset to ensure deterministic results.\n pattern.lastIndex = 0;\n return pattern.test(value);\n });\n}\n\n/**\n * Redact PII from a string.\n *\n * Replaces detected PII patterns with `[REDACTED_TYPE]` placeholders.\n * Useful for logging or analytics where PII should not be stored.\n *\n * @param {string} value - String value to redact PII from\n * @returns {string} String with PII redacted\n *\n * @example\n * ```typescript\n * const sanitized = redactPII('Contact john@example.com at 555-1234');\n * // Returns: 'Contact [REDACTED_EMAIL] at [REDACTED_PHONE]'\n * ```\n */\nexport function redactPII(value: string): string {\n let redacted = value;\n\n for (const [type, pattern] of Object.entries(PII_PATTERNS)) {\n // Ensure full replacement starts at index 0 on each call.\n pattern.lastIndex = 0;\n redacted = redacted.replace(pattern, `[REDACTED_${type.toUpperCase()}]`);\n }\n\n return redacted;\n}\n\n/**\n * Strip HTML and script tags from a string.\n *\n * Removes HTML tags, script tags, event handlers, and other potentially dangerous\n * content to prevent XSS attacks. Also removes `data:text/html` URIs.\n *\n * @param {string} value - String value to strip HTML from\n * @returns {string} String with HTML and scripts removed\n *\n * @example\n * ```typescript\n * const sanitized = stripHTML('<script>alert(\"xss\")</script>Hello');\n * // Returns: 'Hello'\n * ```\n */\nexport function stripHTML(value: string): string {\n let stripped = value;\n\n for (const pattern of Object.values(XSS_PATTERNS)) {\n stripped = stripped.replace(pattern, '');\n }\n\n // Additional cleanup for remaining tags\n stripped = stripped.replaceAll(/<[^>]*>/g, '');\n\n return stripped;\n}\n\n/**\n * Validate that a property key is safe.\n *\n * Checks for dangerous keys that could cause prototype pollution or other\n * security issues. Also validates against allowed key patterns if provided.\n *\n * @param {string} key - Property key to validate\n * @param {SanitizationOptions} [options] - Sanitization options\n * @returns {{ valid: boolean; reason?: string }} Validation result with reason if invalid\n *\n * @example\n * ```typescript\n * const result = isValidPropertyKey('__proto__');\n * if (!result.valid) {\n * console.warn('Unsafe key:', result.reason);\n * }\n * ```\n */\nexport function isValidPropertyKey(\n key: string,\n options: SanitizationOptions = {},\n): { valid: boolean; reason?: string } {\n // Check for dangerous keys\n if (!options.allowDangerousKeys && DANGEROUS_KEYS.has(key)) {\n return { valid: false, reason: 'Dangerous key that could cause prototype pollution' };\n }\n\n // Check against allowed pattern\n if (options.allowedKeyPattern && !options.allowedKeyPattern.test(key)) {\n return { valid: false, reason: 'Key does not match allowed pattern' };\n }\n\n // Check for special characters that could cause issues\n if (/[<>[\\\\\\]{}]/.test(key)) {\n return { valid: false, reason: 'Key contains potentially dangerous characters' };\n }\n\n return { valid: true };\n}\n\n/**\n * Get the size of a value in bytes\n */\nfunction getValueSize(value: unknown): number {\n if (typeof value === 'string') {\n return new Blob([value]).size;\n }\n\n try {\n return new Blob([JSON.stringify(value)]).size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Sanitize a single property value\n */\nfunction sanitizeValue(\n value: unknown,\n options: SanitizationOptions,\n warnings: string[],\n): { value: unknown; modified: boolean } {\n if (value === null || value === undefined) {\n return { value, modified: false };\n }\n\n let modified = false;\n\n // Handle strings\n if (typeof value === 'string') {\n let sanitized = value;\n\n // Check size\n const size = getValueSize(value);\n const maxSize = options.maxPropertySize ?? MAX_PROPERTY_VALUE_SIZE;\n\n if (size > maxSize) {\n sanitized = value.slice(0, Math.max(0, maxSize));\n warnings.push(`String value truncated from ${size} to ${maxSize} bytes`);\n modified = true;\n }\n\n // Strip PII if requested\n if (options.stripPII && containsPII(sanitized)) {\n sanitized = redactPII(sanitized);\n warnings.push('PII detected and redacted from value');\n modified = true;\n }\n\n // Strip HTML if requested\n if (options.stripHTML) {\n const stripped = stripHTML(sanitized);\n if (stripped !== sanitized) {\n sanitized = stripped;\n warnings.push('HTML/scripts stripped from value');\n modified = true;\n }\n }\n\n return { value: sanitized, modified };\n }\n\n // Handle numbers, booleans, etc.\n if (typeof value !== 'object') {\n return { value, modified: false };\n }\n\n // Arrays and objects handled by sanitizeProperties\n return { value, modified: false };\n}\n\n/**\n * Sanitize analytics properties recursively.\n *\n * Comprehensive sanitization of analytics properties including:\n * - Size limit enforcement\n * - Depth limit enforcement\n * - PII detection and redaction\n * - HTML/script tag removal\n * - Dangerous key filtering\n * - Circular reference detection\n *\n * @param {T} properties - Properties to sanitize\n * @param {SanitizationOptions} [options] - Sanitization options\n * @returns {SanitizationResult<T>} Sanitization result with sanitized data and warnings\n *\n * @throws {Error} If properties contain circular references or exceed payload size limit\n *\n * @example\n * ```typescript\n * const result = sanitizeProperties({\n * user_email: 'test@example.com',\n * phone: '555-1234',\n * description: '<script>alert(\"xss\")</script>Hello',\n * }, {\n * stripPII: true,\n * stripHTML: true,\n * });\n *\n * console.log(result.data);\n * // {\n * // user_email: '[REDACTED_EMAIL]',\n * // phone: '[REDACTED_PHONE]',\n * // description: 'Hello'\n * // }\n * ```\n */\nexport function sanitizeProperties<T extends Record<string, unknown>>(\n properties: T,\n options: SanitizationOptions = {},\n): SanitizationResult<T> {\n const warnings: string[] = [];\n let modified = false;\n\n // Check for circular references\n try {\n JSON.stringify(properties);\n } catch {\n throw new Error('Properties contain circular references');\n }\n\n // Check total payload size\n const totalSize = getValueSize(properties);\n const maxPayloadSize = options.maxPayloadSize ?? MAX_PAYLOAD_SIZE;\n\n if (totalSize > maxPayloadSize) {\n throw new Error(`Payload size ${totalSize} bytes exceeds maximum ${maxPayloadSize} bytes`);\n }\n\n /**\n * Recursive sanitization helper\n */\n function sanitizeRecursive(\n obj: Record<string, unknown>,\n depth: number = 0,\n ): Record<string, unknown> {\n const maxDepth = options.maxDepth ?? MAX_DEPTH;\n\n if (depth > maxDepth) {\n warnings.push(`Maximum nesting depth ${maxDepth} exceeded, truncating`);\n modified = true;\n return {};\n }\n\n const sanitized: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // Validate key\n const keyValidation = isValidPropertyKey(key, options);\n if (!keyValidation.valid) {\n warnings.push(`Skipping dangerous key \"${key}\": ${keyValidation.reason}`);\n modified = true;\n continue;\n }\n\n // Handle null/undefined\n if (value === null || value === undefined) {\n sanitized[key] = value;\n continue;\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n sanitized[key] = value.map(item => {\n if (typeof item === 'object' && item !== null) {\n return sanitizeRecursive(item as Record<string, unknown>, depth + 1);\n }\n\n const result = sanitizeValue(item, options, warnings);\n if (result.modified) modified = true;\n return result.value;\n });\n continue;\n }\n\n // Handle objects\n if (typeof value === 'object') {\n sanitized[key] = sanitizeRecursive(value as Record<string, unknown>, depth + 1);\n continue;\n }\n\n // Handle primitives\n const result = sanitizeValue(value, options, warnings);\n if (result.modified) modified = true;\n sanitized[key] = result.value;\n }\n\n return sanitized;\n }\n\n const sanitized = sanitizeRecursive(properties) as T;\n\n // Log warnings in development\n if (warnings.length > 0 && process.env.NODE_ENV === 'development') {\n logWarn('Analytics properties sanitization warnings', { warnings });\n }\n\n return {\n data: sanitized,\n modified,\n warnings,\n };\n}\n\n/**\n * Validate event name.\n *\n * Checks that an event name is safe and follows best practices:\n * - Non-empty string\n * - Not exceeding 255 characters\n * - No XSS patterns\n * - Only alphanumeric characters, spaces, dots, underscores, and hyphens\n *\n * @param {string} event - Event name to validate\n * @returns {{ valid: boolean; reason?: string }} Validation result with reason if invalid\n *\n * @example\n * ```typescript\n * const result = validateEventName('Button Clicked');\n * if (!result.valid) {\n * console.error('Invalid event name:', result.reason);\n * }\n * ```\n */\nexport function validateEventName(event: string): {\n valid: boolean;\n reason?: string;\n} {\n if (!event || typeof event !== 'string') {\n return { valid: false, reason: 'Event name must be a non-empty string' };\n }\n\n if (event.trim() === '') {\n return { valid: false, reason: 'Event name cannot be empty or whitespace only' };\n }\n\n if (event.length > 255) {\n return { valid: false, reason: 'Event name cannot exceed 255 characters' };\n }\n\n // Check for XSS patterns\n if (\n Object.values(XSS_PATTERNS).some(pattern => {\n pattern.lastIndex = 0;\n return pattern.test(event);\n })\n ) {\n return { valid: false, reason: 'Event name contains potentially malicious content' };\n }\n\n // Check for valid characters: alphanumeric, spaces, dots, underscores, hyphens\n if (!/^[\\s\\w.-]+$/.test(event)) {\n return { valid: false, reason: 'Event name can only contain alphanumeric characters, spaces, dots, underscores, and hyphens' };\n }\n\n return { valid: true };\n}\n\n/**\n * Safe property value types that can be tracked.\n *\n * Only includes primitive types that are safe to send to analytics providers.\n */\nexport type SafePropertyValue = string | number | boolean | null | undefined;\n\n/**\n * Safe properties object type.\n *\n * Properties object with only safe value types.\n */\nexport type SafeProperties = Record<string, SafePropertyValue | SafePropertyValue[]>;\n\n/**\n * Create a type-safe analytics properties object.\n *\n * Sanitizes raw properties and returns a type-safe properties object.\n * Automatically strips PII in production and HTML/scripts always.\n *\n * @param {Record<string, unknown>} properties - Raw properties to make safe\n * @returns {SafeProperties} Type-safe properties object\n *\n * @example\n * ```typescript\n * const safeProps = createSafeProperties({\n * userId: 'user-123',\n * email: 'user@example.com', // Will be redacted in production\n * html: '<script>alert(\"xss\")</script>' // Will be stripped\n * });\n * ```\n */\nexport function createSafeProperties(properties: Record<string, unknown>): SafeProperties {\n const result = sanitizeProperties(properties, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n if (result.warnings.length > 0 && process.env.NODE_ENV === 'development') {\n logWarn('Created safe properties with sanitization', {\n warnings: result.warnings,\n });\n }\n\n return result.data as SafeProperties;\n}\n","/**\n * @fileoverview Analytics Manager - Core orchestration for multi-provider analytics\n *\n * This module provides the core AnalyticsManager class that orchestrates multiple\n * analytics providers. It includes:\n *\n * - **Multi-Provider Support**: Manages multiple providers simultaneously\n * - **Event Deduplication**: LRU cache prevents duplicate events\n * - **Rate Limiting**: Per-provider rate limiting (100 calls/second default)\n * - **Batch Processing**: Concurrent batch processing with configurable concurrency\n * - **Performance Metrics**: Tracks provider performance and reliability\n * - **Error Handling**: Comprehensive error handling with graceful degradation\n *\n * **Node.js 22+ Features Used**:\n * - `Promise.withResolvers()`: External promise control for complex workflows\n * - `AbortSignal.timeout()`: Context-aware timeouts for provider operations\n * - High-resolution timing: Precise performance measurement (`process.hrtime.bigint()`)\n * - `Object.hasOwn()`: Safer property existence checks\n *\n * **Optimizations**:\n * - Spread operator instead of `structuredClone()` for better performance\n * - LRU cache for event deduplication (prevents memory leaks)\n * - Concurrent batch processing with configurable concurrency\n * - Comprehensive input sanitization and validation\n * - Per-provider rate limiting\n *\n * @module @od-oneapp/analytics/shared/utils/manager\n */\n\nimport { RateLimiter } from './rate-limit';\nimport { sanitizeProperties, validateEventName } from './security';\n\nimport type {\n EmitterAliasPayload,\n EmitterGroupPayload,\n EmitterIdentifyPayload,\n EmitterPagePayload,\n EmitterPayload,\n EmitterScreenPayload,\n EmitterTrackPayload,\n} from '../emitters/emitter-types';\nimport type {\n AnalyticsConfig,\n AnalyticsContext,\n AnalyticsProvider,\n EcommerceEventSpec,\n GroupTraits,\n PageProperties,\n Properties,\n PropertyObject,\n ProviderRegistry,\n TrackingOptions,\n UserTraits,\n} from '../types/types';\n\n/**\n * LRU Cache for event deduplication.\n *\n * Prevents memory leaks by limiting cache size. Uses least-recently-used eviction\n * policy to maintain bounded memory usage.\n *\n * @template K - Cache key type\n * @template V - Cache value type\n *\n * @internal\n */\nclass LRUCache<K, V> {\n private cache = new Map<K, V>();\n private readonly maxSize: number;\n\n constructor(maxSize: number = 1000) {\n this.maxSize = maxSize;\n }\n\n get(key: K): V | undefined {\n const value = this.cache.get(key);\n if (value !== undefined) {\n // Move to end (most recently used)\n this.cache.delete(key);\n this.cache.set(key, value);\n }\n return value;\n }\n\n set(key: K, value: V): void {\n // Remove if exists (to re-add at end)\n if (this.cache.has(key)) {\n this.cache.delete(key);\n }\n\n // Add to end\n this.cache.set(key, value);\n\n // Evict oldest if over capacity\n if (this.cache.size > this.maxSize) {\n const firstKey = this.cache.keys().next().value;\n if (firstKey !== undefined) {\n this.cache.delete(firstKey);\n }\n }\n }\n\n has(key: K): boolean {\n return this.cache.get(key) !== undefined;\n }\n\n clear(): void {\n this.cache.clear();\n }\n\n get size(): number {\n return this.cache.size;\n }\n}\n\n/**\n * Provider metrics for tracking performance and reliability.\n *\n * Tracks initialization time, call counts, error counts, and last usage time\n * for each provider to monitor performance and reliability.\n *\n * @internal\n */\ninterface ProviderMetrics {\n initTime: bigint;\n callCount: number;\n errorCount: number;\n lastUsed: bigint;\n}\n\n/**\n * Event metadata for deduplication and tracking.\n *\n * Stores metadata about events to prevent duplicate processing and track\n * event processing status.\n *\n * @internal\n */\ninterface EventMetadata {\n timestamp: bigint;\n processed: boolean;\n}\n\n/**\n * Analytics Manager - Main entry point for analytics tracking.\n *\n * Orchestrates multiple analytics providers, handles event deduplication,\n * rate limiting, batch processing, and error handling.\n *\n * **Key Features**:\n * - Multi-provider support (PostHog, Segment, Vercel, Console)\n * - Event deduplication via LRU cache\n * - Rate limiting (100 calls/second per provider)\n * - Batch processing with concurrency control\n * - Performance metrics tracking\n * - Graceful error handling\n *\n * **Usage**:\n * ```typescript\n * const manager = new AnalyticsManager(config, providerRegistry);\n * await manager.initialize();\n * await manager.emit(track('Event Name', { property: 'value' }));\n * ```\n */\nexport class AnalyticsManager {\n private providers = new Map<string, AnalyticsProvider>();\n private context: AnalyticsContext = {};\n private isInitialized = false;\n\n // Provider metrics tracking (provider name -> metrics)\n private readonly providerMetrics = new Map<string, ProviderMetrics>();\n\n // Event deduplication with LRU cache (prevents memory leaks)\n private readonly eventCache = new LRUCache<string, EventMetadata>(1000);\n\n // Rate limiting per provider (100 calls per second default)\n private readonly rateLimiter = new RateLimiter({\n maxCalls: 100,\n windowMs: 1000,\n maxConcurrent: 10,\n queueExcess: false,\n });\n\n constructor(\n private config: AnalyticsConfig,\n private availableProviders: ProviderRegistry,\n ) {}\n\n /**\n * Initialize all configured providers\n * Throws error if all providers fail to initialize\n */\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n const initPromises: Promise<void>[] = [];\n const initStartTime = process.hrtime.bigint();\n\n for (const [providerName, providerConfig] of Object.entries(this.config.providers)) {\n const providerFactory = this.availableProviders[providerName];\n\n if (providerFactory) {\n try {\n const provider = providerFactory(providerConfig);\n this.providers.set(providerName, provider);\n\n // Initialize provider with enhanced error handling and timeout (Node 22+)\n initPromises.push(\n (async () => {\n const providerInitStart: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000); // Fallback for non-Node environments\n\n try {\n // Use AbortSignal.timeout() for initialization timeout (Node 22+)\n const timeoutSignal = AbortSignal.timeout(10000); // 10 second timeout\n\n const initPromise = provider.initialize(providerConfig);\n\n // Race against timeout\n await Promise.race([\n initPromise,\n new Promise<void>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error(`Provider ${providerName} initialization timed out`)),\n );\n }),\n ]);\n\n // Track successful initialization metrics\n const now: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000); // Fallback for non-Node environments\n this.providerMetrics.set(providerName, {\n initTime: now - providerInitStart,\n callCount: 0,\n errorCount: 0,\n lastUsed: now,\n });\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'initialize',\n });\n }\n // Remove failed provider to ensure it doesn't affect others\n this.providers.delete(providerName);\n throw error; // Re-throw to track failure\n }\n })(),\n );\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'create',\n });\n }\n }\n } else if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Provider ${providerName} not available in this environment`);\n }\n }\n\n // Wait for all providers to initialize with enhanced monitoring\n const results = await Promise.allSettled(initPromises);\n const initEndTime = process.hrtime.bigint();\n\n const successCount = results.filter(r => r.status === 'fulfilled').length;\n const failureCount = results.filter(r => r.status === 'rejected').length;\n\n // FIX #10: Throw error if all providers fail\n if (successCount === 0 && initPromises.length > 0) {\n throw new Error(\n `Analytics initialization failed: All ${initPromises.length} providers failed to initialize`,\n );\n }\n\n this.isInitialized = true;\n\n if (this.config.debug && this.config.onInfo) {\n const initTimeMs = Number(initEndTime - initStartTime) / 1_000_000;\n this.config.onInfo(\n `Analytics initialized in ${initTimeMs.toFixed(2)}ms with ${successCount} providers ` +\n `(${failureCount} failed): ${[...this.providers.keys()].join(', ')}`,\n );\n }\n }\n\n /**\n * Set global analytics context\n * FIX #13: Use spread operator instead of structuredClone for better performance\n */\n setContext(context: AnalyticsContext): void {\n // Use spread operator for better performance\n this.context = { ...this.context, ...context };\n\n // Update context on providers that support it with enhanced error handling\n for (const [providerName, provider] of this.providers) {\n if (provider.setContext) {\n try {\n // Use spread operator instead of structuredClone\n provider.setContext({ ...this.context });\n\n // Update metrics for successful context update\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'setContext',\n context: Object.keys(context).join(', '),\n });\n }\n }\n }\n }\n }\n\n /**\n * Get current analytics context\n * FIX #13: Use spread operator instead of structuredClone\n */\n getContext(): AnalyticsContext {\n return { ...this.context };\n }\n\n /**\n * Build per-call context without mutating global manager state.\n */\n private mergeCallContext(\n options?: TrackingOptions,\n extraContext?: AnalyticsContext,\n ): AnalyticsContext {\n return {\n ...this.context,\n ...(options?.context ?? {}),\n ...(extraContext ?? {}),\n };\n }\n\n /**\n * Track an event\n * FIX #3: Replace any types with proper TypeScript types\n * FIX #14/#15: Integrate security utilities and rate limiting\n */\n async track(payload: EmitterTrackPayload): Promise<void>;\n async track(event: string, properties?: Properties, options?: TrackingOptions): Promise<void>;\n async track(\n eventOrPayload: string | EmitterTrackPayload,\n properties?: Properties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof eventOrPayload === 'object') {\n const payload = eventOrPayload;\n return this.track(payload.event, payload.properties, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n // Traditional track call\n const event = eventOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n event,\n method: 'track',\n });\n }\n return;\n }\n\n // FIX #14: Validate event name\n const eventValidation = validateEventName(event);\n if (!eventValidation.valid) {\n if (this.config.onError) {\n this.config.onError(new Error(`Invalid event name: ${eventValidation.reason}`), {\n provider: 'analytics',\n event,\n method: 'track',\n });\n }\n return;\n }\n\n // FIX #14: Sanitize properties\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options);\n const enhancedProperties = { ...callContext, ...sanitized.data };\n\n const promises = [...targetProviders.entries()].map(async ([name, provider]) => {\n // FIX #15: Apply rate limiting\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${name}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${name}`), {\n provider: name,\n event,\n method: 'track',\n });\n }\n return;\n }\n\n try {\n await provider.track(event, enhancedProperties as PropertyObject, callContext);\n\n // Update metrics\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n // Update error metrics\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.errorCount++;\n }\n\n // Error boundary: report error but don't let it affect other providers\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n event,\n method: 'track',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n });\n\n // Use allSettled to ensure all providers are called even if some fail\n await Promise.allSettled(promises);\n }\n\n /**\n * Identify a user\n * FIX #3: Replace any types with proper TypeScript types\n */\n async identify(payload: EmitterIdentifyPayload): Promise<void>;\n async identify(userId: string, traits?: UserTraits, options?: TrackingOptions): Promise<void>;\n async identify(\n userIdOrPayload: string | EmitterIdentifyPayload,\n traits?: UserTraits,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof userIdOrPayload === 'object') {\n const payload = userIdOrPayload;\n return this.identify(payload.userId, payload.traits, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n // Traditional identify call\n const userId = userIdOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n method: 'identify',\n userId,\n });\n }\n return;\n }\n\n // Sanitize traits\n const sanitized = sanitizeProperties(traits ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options, {\n userId,\n ...sanitized.data,\n });\n const enhancedTraits = { ...sanitized.data } as UserTraits;\n\n const promises = [...targetProviders.entries()].map(async ([name, provider]) => {\n if (provider.identify) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${name}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${name}`), {\n provider: name,\n userId,\n method: 'identify',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.identify expects TrackingOptions as third parameter)\n await provider.identify(userId, enhancedTraits, {\n ...options,\n context: callContext,\n });\n\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(name);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'identify',\n userId,\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Track a page view\n * FIX #3: Replace any types with proper TypeScript types\n */\n async page(payload: EmitterPagePayload): Promise<void>;\n async page(name?: string, properties?: PageProperties, options?: TrackingOptions): Promise<void>;\n async page(\n nameOrPayload?: string | EmitterPagePayload,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof nameOrPayload === 'object') {\n const payload = nameOrPayload;\n return this.page(payload.name, payload.properties, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n // Traditional page call\n const name = nameOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n name,\n method: 'page',\n });\n }\n return;\n }\n\n // Sanitize properties\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options);\n const enhancedProperties = { ...sanitized.data } as PageProperties;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.page) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n name,\n method: 'page',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.page expects TrackingOptions as third parameter)\n await provider.page(name ?? '', enhancedProperties, {\n ...options,\n context: callContext,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n name: name ?? '',\n method: 'page',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Track a screen view (mobile equivalent of page)\n */\n async screen(payload: EmitterScreenPayload): Promise<void>;\n async screen(\n name?: string,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void>;\n async screen(\n nameOrPayload?: string | EmitterScreenPayload,\n properties?: PageProperties,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof nameOrPayload === 'object') {\n const payload = nameOrPayload;\n return this.screen(payload.name, payload.properties, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n const name = nameOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n name,\n method: 'screen',\n });\n }\n return;\n }\n\n const sanitized = sanitizeProperties(properties ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options);\n const enhancedProperties = { ...sanitized.data } as PageProperties;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.screen) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n name,\n method: 'screen',\n });\n }\n return;\n }\n\n try {\n await provider.screen(name ?? '', enhancedProperties, {\n ...options,\n context: callContext,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n name: name ?? '',\n method: 'screen',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Associate user with a group\n * FIX #3: Replace any types with proper TypeScript types\n */\n async group(payload: EmitterGroupPayload): Promise<void>;\n async group(groupId: string, traits?: GroupTraits, options?: TrackingOptions): Promise<void>;\n async group(\n groupIdOrPayload: string | EmitterGroupPayload,\n traits?: GroupTraits,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof groupIdOrPayload === 'object') {\n const payload = groupIdOrPayload;\n return this.group(payload.groupId, payload.traits, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n // Traditional group call\n const groupId = groupIdOrPayload;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n groupId,\n method: 'group',\n });\n }\n return;\n }\n\n // Sanitize traits\n const sanitized = sanitizeProperties(traits ?? {}, {\n stripPII: process.env.NODE_ENV === 'production',\n stripHTML: true,\n allowDangerousKeys: false,\n });\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options, {\n organizationId: groupId,\n ...sanitized.data,\n });\n const enhancedTraits = { ...sanitized.data } as GroupTraits;\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.group) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n groupId,\n method: 'group',\n });\n }\n return;\n }\n\n try {\n // Pass context as TrackingOptions (provider.group expects TrackingOptions as third parameter)\n await provider.group(groupId, enhancedTraits, {\n ...options,\n context: callContext,\n });\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n groupId,\n method: 'group',\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Alias one user ID to another\n * No any types - already properly typed\n */\n async alias(payload: EmitterAliasPayload): Promise<void>;\n async alias(userId: string, previousId: string, options?: TrackingOptions): Promise<void>;\n async alias(\n userIdOrPayload: string | EmitterAliasPayload,\n previousId?: string,\n options?: TrackingOptions,\n ): Promise<void> {\n // If first argument is an emitter payload, use it\n if (typeof userIdOrPayload === 'object') {\n const payload = userIdOrPayload;\n return this.alias(payload.userId, payload.previousId, {\n ...options,\n context: { ...(options?.context ?? {}), ...(payload.context ?? {}) },\n });\n }\n\n // Traditional alias call\n const userId = userIdOrPayload;\n const prevId = previousId as string;\n\n if (!this.isInitialized) {\n if (this.config.onError) {\n this.config.onError(new Error('Analytics not initialized'), {\n provider: 'analytics',\n method: 'alias',\n previousId: prevId,\n userId,\n });\n }\n return;\n }\n\n const targetProviders = await this.getTargetProviders(options);\n const callContext = this.mergeCallContext(options, { userId });\n\n const promises = [...targetProviders.entries()].map(async ([providerName, provider]) => {\n if (provider.alias) {\n const allowed = await this.rateLimiter.tryAcquire();\n if (!allowed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo(`Rate limit exceeded for provider ${providerName}`);\n }\n // Report rate limit rejection to onError handler to prevent silent event loss\n if (this.config.onError) {\n this.config.onError(new Error(`Rate limit exceeded for provider ${providerName}`), {\n provider: providerName,\n userId,\n previousId,\n method: 'alias',\n });\n }\n return;\n }\n\n try {\n await provider.alias(userId, prevId, callContext);\n\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.callCount++;\n metrics.lastUsed = process.hrtime.bigint();\n }\n } catch (error) {\n const metrics = this.providerMetrics.get(providerName);\n if (metrics) {\n metrics.errorCount++;\n }\n\n if (this.config.onError) {\n this.config.onError(error, {\n provider: providerName,\n method: 'alias',\n previousId: prevId,\n userId,\n });\n }\n } finally {\n this.rateLimiter.release();\n }\n }\n });\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Get list of active provider names\n */\n getActiveProviders(): string[] {\n return [...this.providers.keys()];\n }\n\n /**\n * Get a specific provider instance\n */\n getProvider(name: string): AnalyticsProvider | undefined {\n return this.providers.get(name);\n }\n\n /**\n * Reset analytics context and identity\n */\n reset(): void {\n this.context = {};\n this.eventCache.clear();\n }\n\n /**\n * Shutdown all providers and cleanup resources\n */\n async shutdown(): Promise<void> {\n const shutdownPromises = [...this.providers.entries()].map(async ([name, provider]) => {\n try {\n if (typeof (provider as any).destroy === 'function') {\n await (provider as any).destroy();\n }\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'shutdown',\n });\n }\n }\n });\n\n await Promise.allSettled(shutdownPromises);\n this.providers.clear();\n this.isInitialized = false;\n }\n\n /**\n * Process any emitter payload with Node 22+ optimizations\n * FIX #12: Use LRU cache for event deduplication (prevents memory leaks)\n */\n async emit(payload: EmitterPayload, options?: { timeout?: number }): Promise<void> {\n const emitStartTime = process.hrtime.bigint();\n\n // FIX #12: Use LRU cache for deduplication instead of WeakMap\n // Create order-independent cache key by sorting object keys\n const createCacheKey = (p: EmitterPayload): string => {\n const sorted = (obj: unknown): unknown => {\n if (obj === null || typeof obj !== 'object' || obj instanceof Array) {\n return obj;\n }\n const sortedObj: Record<string, unknown> = {};\n for (const key of Object.keys(obj).sort()) {\n sortedObj[key] = sorted((obj as Record<string, unknown>)[key]);\n }\n return sortedObj;\n };\n return JSON.stringify(sorted(p));\n };\n const cacheKey = createCacheKey(payload);\n if (this.eventCache.has(cacheKey)) {\n const metadata = this.eventCache.get(cacheKey);\n if (metadata?.processed) {\n if (this.config.debug && this.config.onInfo) {\n this.config.onInfo('Skipping duplicate event processing');\n }\n return;\n }\n }\n\n // Mark as being processed and track metadata\n this.eventCache.set(cacheKey, {\n timestamp: emitStartTime,\n processed: true,\n });\n\n try {\n // Apply timeout if specified using AbortSignal.timeout() (Node 22+)\n if (options?.timeout) {\n const timeoutSignal = AbortSignal.timeout(options.timeout);\n await Promise.race([\n this.processPayloadByType(payload),\n new Promise<never>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error(`Event processing timed out after ${options.timeout}ms`)),\n );\n }),\n ]);\n } else {\n await this.processPayloadByType(payload);\n }\n\n // Log performance metrics in debug mode\n if (this.config.debug && this.config.onInfo) {\n const processingTime = Number(process.hrtime.bigint() - emitStartTime) / 1_000_000;\n this.config.onInfo(`Event processed in ${processingTime.toFixed(2)}ms`);\n }\n } catch (error) {\n // Update metadata to mark as failed\n this.eventCache.set(cacheKey, {\n timestamp: emitStartTime,\n processed: false,\n });\n throw error;\n }\n }\n\n /**\n * Route payload to appropriate method based on type\n */\n private async processPayloadByType(payload: EmitterPayload): Promise<void> {\n switch (payload.type) {\n case 'track':\n return this.track(payload);\n case 'identify':\n return this.identify(payload);\n case 'page':\n return this.page(payload);\n case 'screen':\n return this.screen(payload);\n case 'group':\n return this.group(payload);\n case 'alias':\n return this.alias(payload);\n }\n }\n\n /**\n * Process an emitter payload (legacy method - use emit() instead)\n * @deprecated Use emit() for better type safety\n */\n async processEmitterPayload(payload: EmitterPayload): Promise<void> {\n return this.emit(payload);\n }\n\n /**\n * Batch emit multiple payloads with Node 22+ optimizations\n * FIX #14: Process chunks concurrently instead of sequentially\n * FIX #13: Use spread operator instead of structuredClone\n */\n async emitBatch(\n payloads: EmitterPayload[],\n options?: {\n timeout?: number;\n concurrency?: number;\n failFast?: boolean;\n },\n ): Promise<void> {\n const batchStartTime = process.hrtime.bigint();\n const concurrency = Math.max(1, options?.concurrency ?? 10);\n\n if (payloads.length === 0) return;\n\n // FIX #13: Use structuredClone for deep cloning to ensure nested data safety\n const clonedPayloads = payloads.map(p => structuredClone(p));\n const workQueue = [...clonedPayloads];\n const workerCount = Math.min(concurrency, workQueue.length);\n\n const runWorker = async (failFast: boolean): Promise<void> => {\n while (workQueue.length > 0) {\n const payload = workQueue.shift();\n if (!payload) return;\n\n if (failFast) {\n await this.emit(\n payload,\n options?.timeout !== undefined ? { timeout: options.timeout } : undefined,\n );\n } else {\n try {\n await this.emit(\n payload,\n options?.timeout !== undefined ? { timeout: options.timeout } : undefined,\n );\n } catch {\n // Best-effort mode: continue processing remaining events.\n }\n }\n }\n };\n\n await Promise.all(\n Array.from({ length: workerCount }, () => runWorker(options?.failFast === true)),\n );\n\n // Log batch processing metrics in debug mode\n if (this.config.debug && this.config.onInfo) {\n const batchTime = Number(process.hrtime.bigint() - batchStartTime) / 1_000_000;\n this.config.onInfo(\n `Batch of ${payloads.length} events processed in ${batchTime.toFixed(2)}ms ` +\n `(concurrency: ${concurrency})`,\n );\n }\n }\n\n /**\n * Create a bound emitter function for convenience\n */\n createEmitter(): (payload: EmitterPayload) => Promise<void> {\n // Extract arrow function to avoid consistent-function-scoping warning\n const boundEmit = this.emit.bind(this);\n return boundEmit;\n }\n\n /**\n * Track an ecommerce event specification\n * @deprecated Use emit(ecommerce.EVENT_NAME(...)) instead\n */\n async trackEcommerce(eventSpec: EcommerceEventSpec): Promise<void> {\n return this.track(eventSpec.name, eventSpec.properties);\n }\n\n /**\n * Get target providers based on tracking options\n */\n private async getTargetProviders(options?: TrackingOptions): Promise<Map<string, AnalyticsProvider>> {\n let targetProviders = new Map(this.providers);\n\n if (options) {\n // Handle runtime provider additions with enhanced error handling\n if (options.providers && Object.hasOwn(options, 'providers')) {\n for (const [name, config] of Object.entries(options.providers)) {\n const factory = this.availableProviders[name];\n if (factory) {\n try {\n const providerInitStart: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000);\n const provider = factory(config);\n await provider.initialize(config);\n targetProviders.set(name, provider);\n\n // Initialize runtime metrics tracking (Node 22+)\n const now: bigint = typeof process.hrtime?.bigint === 'function'\n ? process.hrtime.bigint()\n : BigInt(Date.now() * 1000000);\n this.providerMetrics.set(name, {\n initTime: now - providerInitStart,\n callCount: 0,\n errorCount: 0,\n lastUsed: now,\n });\n } catch (error) {\n if (this.config.onError) {\n this.config.onError(error, {\n provider: name,\n method: 'runtime-create',\n });\n }\n }\n }\n }\n }\n\n // Handle 'only' option with safer property checking\n if (options.only && Object.hasOwn(options, 'only')) {\n const onlyProviders = new Map();\n for (const name of options.only) {\n if (targetProviders.has(name)) {\n onlyProviders.set(name, targetProviders.get(name));\n }\n }\n targetProviders = onlyProviders;\n }\n\n // Handle 'exclude' option with safer property checking\n if (options.exclude && Object.hasOwn(options, 'exclude')) {\n for (const name of options.exclude) {\n targetProviders.delete(name);\n }\n }\n }\n\n return targetProviders;\n }\n\n /**\n * Get analytics performance metrics using Node 22+ features\n */\n getAnalyticsMetrics(): {\n providers: Record<\n string,\n {\n initTimeMs: number;\n callCount: number;\n errorCount: number;\n lastUsedMs: number;\n successRate: number;\n }\n >;\n events: {\n totalProcessed: number;\n cacheSize: number;\n memoryUsage: {\n rss: number;\n heapTotal: number;\n heapUsed: number;\n external: number;\n arrayBuffers: number;\n };\n };\n } {\n const now = process.hrtime.bigint();\n const providers: Record<\n string,\n {\n initTimeMs: number;\n callCount: number;\n errorCount: number;\n lastUsedMs: number;\n successRate: number;\n }\n > = {};\n\n // Collect provider metrics\n for (const [providerName, metrics] of this.providerMetrics) {\n providers[providerName] = {\n initTimeMs: Number(metrics.initTime) / 1_000_000,\n callCount: metrics.callCount,\n errorCount: metrics.errorCount,\n lastUsedMs: Number(now - metrics.lastUsed) / 1_000_000,\n successRate: metrics.callCount > 0 ? 1 - metrics.errorCount / metrics.callCount : 1,\n };\n }\n\n return {\n providers,\n events: {\n totalProcessed: this.eventCache.size,\n cacheSize: this.eventCache.size,\n memoryUsage: process.memoryUsage(),\n },\n };\n }\n\n /**\n * Health check for analytics system using Node 22+ timing\n * FIX #16: Send actual test events instead of just setContext\n */\n async healthCheck(timeout: number = 5000): Promise<{\n healthy: boolean;\n providers: Record<string, boolean>;\n metrics: ReturnType<AnalyticsManager['getAnalyticsMetrics']>;\n totalCheckTime: number;\n }> {\n const checkStartTime = process.hrtime.bigint();\n const providerHealth: Record<string, boolean> = {};\n\n // Use AbortSignal.timeout() for health check timeout (Node 22+)\n const timeoutSignal = AbortSignal.timeout(timeout);\n\n try {\n const healthPromises = [...this.providers.entries()].map(async ([name, provider]) => {\n try {\n // FIX #16: Send actual test event instead of just setContext\n await Promise.race([\n provider.track('__health_check__', { timestamp: Date.now(), provider: name }),\n new Promise<void>((_resolve, reject) => {\n timeoutSignal.addEventListener('abort', () =>\n reject(new Error('Health check timeout')),\n );\n }),\n ]);\n providerHealth[name] = true;\n } catch {\n providerHealth[name] = false;\n }\n });\n\n await Promise.allSettled(healthPromises);\n\n const checkEndTime = process.hrtime.bigint();\n const totalCheckTime = Number(checkEndTime - checkStartTime) / 1_000_000;\n\n const healthyCount = Object.values(providerHealth).filter(Boolean).length;\n\n return {\n healthy: healthyCount > 0 && this.isInitialized,\n providers: providerHealth,\n metrics: this.getAnalyticsMetrics(),\n totalCheckTime,\n };\n } catch {\n return {\n healthy: false,\n providers: providerHealth,\n metrics: this.getAnalyticsMetrics(),\n totalCheckTime: Number(process.hrtime.bigint() - checkStartTime) / 1_000_000,\n };\n }\n }\n}\n\n/**\n * Factory function to create analytics manager\n */\nexport function createAnalyticsManager(\n config: AnalyticsConfig,\n availableProviders: ProviderRegistry,\n): AnalyticsManager {\n return new AnalyticsManager(config, availableProviders);\n}\n","/**\n * @fileoverview Helper Functions for Analytics Emitters\n *\n * These utilities make it easier to use the emitter-first approach for analytics.\n * Provides builders, batch processing, session management, and convenience\n * functions for common tracking patterns.\n *\n * **Builders**:\n * - `ContextBuilder`: Build consistent context across events\n * - `PayloadBuilder`: Chain emitter creation with shared options\n * - `EventBatch`: Batch related events with shared context\n *\n * **Session Management**:\n * - `createUserSession()`: Create a user session tracking flow\n * - `createAnonymousSession()`: Create an anonymous user tracking flow\n *\n * **Convenience Functions**:\n * - `withMetadata()`: Add consistent metadata to events\n * - `withUTM()`: Add UTM parameters to events\n * - Type guards: `isTrackPayload()`, `isIdentifyPayload()`, etc.\n *\n * @module @od-oneapp/analytics/shared/emitters/helpers\n */\n\nimport { alias, group, identify, page, track } from './emitters';\n\nimport type {\n EmitterAliasPayload,\n EmitterContext,\n EmitterGroupPayload,\n EmitterIdentifyPayload,\n EmitterOptions,\n EmitterPagePayload,\n EmitterPayload,\n EmitterTrackPayload,\n} from './emitter-types';\n\n/**\n * Builder for creating consistent context across analytics events.\n *\n * @remarks\n * Use this builder to create a reusable context object that can be applied\n * to multiple events. This ensures consistency and reduces repetition.\n *\n * @example\n * ```typescript\n * const context = new ContextBuilder()\n * .setUser('user-123', { plan: 'pro' })\n * .setOrganization('org-456')\n * .setPage({ path: '/dashboard', title: 'Dashboard' })\n * .build();\n *\n * await analytics.emit(track('Button Clicked', {}, { context }));\n * ```\n */\nexport class ContextBuilder {\n private context: EmitterContext = {};\n\n constructor(initialContext?: Partial<EmitterContext>) {\n if (initialContext) {\n this.context = { ...initialContext };\n }\n }\n\n setUser(_userId: string, traits?: Record<string, any>): this {\n this.context.traits = { ...this.context.traits, ...traits };\n return this;\n }\n\n setOrganization(groupId: string): this {\n this.context.groupId = groupId;\n return this;\n }\n\n setPage(pageInfo: { path?: string; url?: string; title?: string; referrer?: string }): this {\n this.context.page = { ...this.context.page, ...pageInfo };\n return this;\n }\n\n setCampaign(utmParams: Record<string, string>): this {\n this.context.campaign = { ...this.context.campaign, ...utmParams };\n return this;\n }\n\n setDevice(deviceInfo: Record<string, any>): this {\n this.context.device = { ...this.context.device, ...deviceInfo };\n return this;\n }\n\n build(): EmitterContext {\n return { ...this.context };\n }\n}\n\n/**\n * Builder for chaining emitter creation with shared options.\n *\n * @remarks\n * Use this builder to create multiple events with shared options like\n * timestamp, anonymousId, or integrations. This is useful when tracking\n * multiple related events in a single flow.\n *\n * @example\n * ```typescript\n * const builder = new PayloadBuilder(context)\n * .withTimestamp(new Date())\n * .withAnonymousId('anon-123');\n *\n * await analytics.emit(builder.track('Event 1', {}));\n * await analytics.emit(builder.track('Event 2', {}));\n * ```\n */\nexport class PayloadBuilder {\n private options: EmitterOptions = {};\n\n constructor(context?: EmitterContext) {\n if (context) {\n this.options.context = context;\n }\n }\n\n withTimestamp(timestamp: Date | string): this {\n this.options.timestamp = timestamp;\n return this;\n }\n\n withAnonymousId(anonymousId: string): this {\n this.options.anonymousId = anonymousId;\n return this;\n }\n\n withIntegrations(integrations: Record<string, boolean | Record<string, any>>): this {\n this.options.integrations = integrations;\n return this;\n }\n\n track(event: string, properties?: Record<string, any>): EmitterTrackPayload {\n return track(event, properties, this.options);\n }\n\n identify(userId: string, traits?: Record<string, any>): EmitterIdentifyPayload {\n return identify(userId, traits, this.options);\n }\n\n page(name?: string, properties?: Record<string, any>): EmitterPagePayload {\n return page(undefined, name, properties, this.options);\n }\n\n group(groupId: string, traits?: Record<string, any>): EmitterGroupPayload {\n return group(groupId, traits, this.options);\n }\n\n alias(userId: string, previousId: string): EmitterAliasPayload {\n return alias(userId, previousId, this.options);\n }\n}\n\n/**\n * Batch processor for related events with shared context.\n *\n * @remarks\n * Use this class to collect multiple events and emit them together with\n * a shared context. This is useful for tracking user flows or multi-step\n * processes where events should be grouped together.\n *\n * @example\n * ```typescript\n * const batch = new EventBatch(context)\n * .addTrack('Step 1 Completed', { step: 1 })\n * .addTrack('Step 2 Completed', { step: 2 })\n * .addTrack('Flow Completed', { totalSteps: 2 });\n *\n * for (const event of batch.getEvents()) {\n * await analytics.emit(event);\n * }\n * ```\n */\nexport class EventBatch {\n private events: EmitterPayload[] = [];\n private sharedContext: EmitterContext;\n\n constructor(context?: EmitterContext) {\n this.sharedContext = context ?? {};\n }\n\n add(payload: EmitterPayload): this {\n // Merge shared context with payload context\n const enrichedPayload = {\n ...payload,\n context: { ...this.sharedContext, ...payload.context },\n };\n this.events.push(enrichedPayload);\n return this;\n }\n\n addTrack(event: string, properties?: Record<string, any>): this {\n return this.add(track(event, properties, { context: this.sharedContext }));\n }\n\n addIdentify(userId: string, traits?: Record<string, any>): this {\n return this.add(identify(userId, traits, { context: this.sharedContext }));\n }\n\n addPage(name?: string, properties?: Record<string, any>): this {\n return this.add(page(undefined, name, properties, { context: this.sharedContext }));\n }\n\n addGroup(groupId: string, traits?: Record<string, any>): this {\n return this.add(group(groupId, traits, { context: this.sharedContext }));\n }\n\n getEvents(): EmitterPayload[] {\n return [...this.events];\n }\n\n clear(): void {\n this.events = [];\n }\n}\n\n/**\n * Creates a user session tracking flow with pre-configured context.\n *\n * @remarks\n * This helper creates a session object with methods for tracking events\n * within a specific user session. All events will include the sessionId\n * and user context automatically.\n *\n * @param userId - Unique identifier for the user\n * @param sessionId - Unique identifier for the session\n * @returns Session object with `identify`, `track`, `page`, and `group` methods\n *\n * @example\n * ```typescript\n * const session = createUserSession('user-123', 'session-456');\n * await analytics.emit(session.track('Button Clicked', { button: 'signup' }));\n * await analytics.emit(session.page('Dashboard'));\n * ```\n */\nexport function createUserSession(userId: string, sessionId: string) {\n const context = new ContextBuilder().setUser(userId).build();\n\n return {\n // Identify the user\n identify: (traits?: Record<string, any>) =>\n identify(userId, { ...traits, sessionId }, { context }),\n\n // Track an event in this session\n track: (event: string, properties?: Record<string, any>) =>\n track(event, { ...properties, sessionId }, { context }),\n\n // Track a page view in this session\n page: (name?: string, properties?: Record<string, any>) =>\n page(undefined, name, { ...properties, sessionId }, { context }),\n\n // Associate with a group\n group: (groupId: string, traits?: Record<string, any>) =>\n group(groupId, { ...traits, sessionId }, { context }),\n };\n}\n\n/**\n * Creates an anonymous user tracking flow.\n *\n * @remarks\n * This helper creates a session object for tracking anonymous users before\n * they are identified. When the user signs up or logs in, use the `alias`\n * method to link their anonymous activity to their user ID.\n *\n * @param anonymousId - Unique identifier for the anonymous user\n * @returns Session object with `track`, `page`, `identify`, and `alias` methods\n *\n * @example\n * ```typescript\n * const anonymousSession = createAnonymousSession('anon-123');\n * await analytics.emit(anonymousSession.track('Page Viewed', { page: 'home' }));\n *\n * // Later, when user signs up\n * await analytics.emit(anonymousSession.alias('user-123'));\n * await analytics.emit(anonymousSession.identify('user-123', { email: 'user@example.com' }));\n * ```\n */\nexport function createAnonymousSession(anonymousId: string) {\n const options: EmitterOptions = { anonymousId };\n\n return {\n // Track an event\n track: (event: string, properties?: Record<string, any>) => track(event, properties, options),\n\n // Track a page view\n page: (name?: string, properties?: Record<string, any>) =>\n page(undefined, name, properties, options),\n\n // Convert to identified user\n identify: (userId: string, traits?: Record<string, any>) => identify(userId, traits, options),\n\n // Alias when user signs up\n alias: (userId: string) => alias(userId, anonymousId, options),\n };\n}\n\n/**\n * Type guard to check if a payload is a track event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a track event\n */\nexport const isTrackPayload = (payload: EmitterPayload): payload is EmitterTrackPayload =>\n payload.type === 'track';\n\n/**\n * Type guard to check if a payload is an identify event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is an identify event\n */\nexport const isIdentifyPayload = (payload: EmitterPayload): payload is EmitterIdentifyPayload =>\n payload.type === 'identify';\n\n/**\n * Type guard to check if a payload is a page event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a page event\n */\nexport const isPagePayload = (payload: EmitterPayload): payload is EmitterPagePayload =>\n payload.type === 'page';\n\n/**\n * Type guard to check if a payload is a group event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is a group event\n */\nexport const isGroupPayload = (payload: EmitterPayload): payload is EmitterGroupPayload =>\n payload.type === 'group';\n\n/**\n * Type guard to check if a payload is an alias event.\n *\n * @param payload - The payload to check\n * @returns True if the payload is an alias event\n */\nexport const isAliasPayload = (payload: EmitterPayload): payload is EmitterAliasPayload =>\n payload.type === 'alias';\n\n/**\n * Adds consistent metadata to an analytics event payload.\n *\n * @remarks\n * This helper enriches an event payload with metadata that will be included\n * in the event's context.app object. Useful for tracking version, source,\n * or other application-level metadata.\n *\n * @param payload - The event payload to enrich\n * @param metadata - Metadata to add (version, source, or custom properties)\n * @returns The enriched payload with metadata in context.app\n *\n * @example\n * ```typescript\n * const event = track('Button Clicked', { button: 'signup' });\n * const enriched = withMetadata(event, { version: '1.0.0', source: 'webapp' });\n * await analytics.emit(enriched);\n * ```\n */\nexport function withMetadata<T extends EmitterPayload>(\n payload: T,\n metadata: { version?: string; source?: string; [key: string]: unknown },\n): T {\n return {\n ...payload,\n context: {\n ...payload.context,\n app: {\n ...payload.context?.app,\n ...metadata,\n },\n },\n };\n}\n\n/**\n * Adds UTM parameters to an analytics event payload.\n *\n * @remarks\n * This helper enriches an event payload with UTM campaign parameters that\n * will be included in the event's context.campaign object. Useful for\n * tracking marketing campaign attribution.\n *\n * @param payload - The event payload to enrich\n * @param utm - UTM parameters (source, medium, campaign, term, content)\n * @returns The enriched payload with UTM parameters in context.campaign\n *\n * @example\n * ```typescript\n * const event = track('Signup Started', {});\n * const enriched = withUTM(event, {\n * source: 'google',\n * medium: 'cpc',\n * campaign: 'summer-sale'\n * });\n * await analytics.emit(enriched);\n * ```\n */\nexport function withUTM<T extends EmitterPayload>(\n payload: T,\n utm: { source?: string; medium?: string; campaign?: string; term?: string; content?: string },\n): T {\n return {\n ...payload,\n context: {\n ...payload.context,\n campaign: {\n ...payload.context?.campaign,\n ...utm,\n },\n },\n };\n}\n","/**\n * @fileoverview PostHog bootstrap utilities for Next.js server-side rendering\n * PostHog bootstrap utilities for Next.js server-side rendering\n * Enables consistent feature flag delivery from server to client\n */\n\nimport type { BootstrapData, PostHogCookie } from '../types/posthog-types';\n\n/**\n * Generate a unique distinct ID (compatible with PostHog format)\n */\nexport function generateDistinctId(): string {\n // Use a similar format to PostHog's default distinct ID generation\n const timestamp = Date.now().toString(36);\n const randomStr = Math.random().toString(36).slice(2, 15);\n return `${timestamp}-${randomStr}`;\n}\n\n/**\n * Parse PostHog cookie to extract distinct ID\n */\nexport function parsePostHogCookie(\n cookieValue: string,\n _projectApiKey: string,\n): PostHogCookie | null {\n try {\n const parsed = JSON.parse(cookieValue);\n\n // Validate the cookie structure\n if (parsed && typeof parsed.distinct_id === 'string') {\n return parsed as PostHogCookie;\n }\n\n return null;\n } catch {\n // Silently fail - cookies may be malformed\n return null;\n }\n}\n\n/**\n * Get PostHog cookie name for a project\n */\nexport function getPostHogCookieName(projectApiKey: string): string {\n return `ph_${projectApiKey}_posthog`;\n}\n\n/**\n * Extract distinct ID from cookies (Next.js compatible)\n */\nexport function getDistinctIdFromCookies(\n cookies: any, // Next.js cookies object or cookie string\n projectApiKey: string,\n): string | null {\n try {\n const cookieName = getPostHogCookieName(projectApiKey);\n\n // Handle Next.js cookies() object\n if (cookies && typeof cookies.get === 'function') {\n const cookie = cookies.get(cookieName);\n if (cookie?.value) {\n const parsed = parsePostHogCookie(cookie.value, projectApiKey);\n return parsed?.distinct_id ?? null;\n }\n }\n\n // Handle raw cookie string\n if (typeof cookies === 'string') {\n const cookieMatch = cookies.match(new RegExp(`${cookieName}=([^;]+)`));\n if (cookieMatch?.[1]) {\n const parsed = parsePostHogCookie(decodeURIComponent(cookieMatch[1]), projectApiKey);\n return parsed?.distinct_id ?? null;\n }\n }\n\n return null;\n } catch {\n // Silently fail - cookies may be malformed or missing\n return null;\n }\n}\n\n/**\n * Create bootstrap data for PostHog client initialization\n */\nexport function createBootstrapData(distinctId: string): BootstrapData {\n return {\n distinctID: distinctId,\n };\n}\n\n/**\n * Cached bootstrap data fetcher (React cache compatible)\n */\nconst bootstrapCache = new Map<string, { data: BootstrapData; timestamp: number }>();\nconst CACHE_TTL = 30000; // 30 seconds\n\nexport function getCachedBootstrapData(distinctId: string): BootstrapData | null {\n const cached = bootstrapCache.get(distinctId);\n\n if (cached && Date.now() - cached.timestamp < CACHE_TTL) {\n return cached.data;\n }\n\n // Clean up expired entries\n for (const [key, value] of bootstrapCache.entries()) {\n if (Date.now() - value.timestamp >= CACHE_TTL) {\n bootstrapCache.delete(key);\n }\n }\n\n return null;\n}\n\nexport function setCachedBootstrapData(distinctId: string, data: BootstrapData): void {\n bootstrapCache.set(distinctId, {\n data,\n timestamp: Date.now(),\n });\n}\n\n/**\n * Validate bootstrap data structure\n */\nexport function validateBootstrapData(data: any): data is BootstrapData {\n return data && typeof data === 'object' && typeof data.distinctID === 'string';\n}\n\n/**\n * Create minimal bootstrap data when full data is unavailable\n */\nexport function createMinimalBootstrapData(distinctId?: string): BootstrapData {\n return {\n distinctID: distinctId ?? generateDistinctId(),\n };\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as GroupTraits, F as UserTraits, M as Properties, c as ProviderConfig, i as AnalyticsProvider, j as PageProperties, n as AnalyticsContext } from "./types-BxBnNQ0V.mjs";
1
+ import { A as GroupTraits, F as UserTraits, M as Properties, c as ProviderConfig, i as AnalyticsProvider, j as PageProperties, n as AnalyticsContext } from "./types-BbXOa_UL.mjs";
2
2
 
3
3
  //#region src/providers/http/client.d.ts
4
4
  declare class HttpClientProvider implements AnalyticsProvider {
@@ -1 +1 @@
1
- {"version":3,"file":"providers-http-client.d.mts","names":[],"sources":["../src/providers/http/client.ts"],"mappings":";;;cA6Da,kBAAA,YAA8B,iBAAA;EAAA,SAChC,IAAA;EAAA,QAED,MAAA;EAAA,QAIA,aAAA;EAAA,QACA,KAAA;EAAA,QACA,UAAA;EAAA,QACA,MAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;cAEI,cAAA,EAAgB,cAAA;EAmBtB,UAAA,CAAA,GAAc,OAAA;EA8Bd,KAAA,CACJ,KAAA,UACA,UAAA,GAAY,UAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,QAAA,CACJ,MAAA,UACA,MAAA,GAAQ,UAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAkBG,IAAA,CACJ,IAAA,WACA,UAAA,GAAY,cAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CACJ,OAAA,UACA,MAAA,GAAQ,WAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CAAM,MAAA,UAAgB,UAAA,UAAoB,QAAA,GAAW,gBAAA,GAAmB,OAAA;EAmBxE,KAAA,CAAA,GAAS,OAAA;EA2BT,QAAA,CAAA,GAAY,OAAA;EAsBlB,cAAA,CAAA;EAAA,QAQQ,sBAAA;EAAA,QAMA,kBAAA;EAAA,QAoBA,OAAA;EAAA,QAgBM,SAAA;EAAA,QAkDA,WAAA;EAAA,QAwCN,sBAAA;EAAA,QAkBA,mBAAA;EAAA,QAUA,KAAA;EAAA,QAQA,GAAA;EAAA,QAMA,IAAA;EAAA,QAIA,KAAA;AAAA"}
1
+ {"version":3,"file":"providers-http-client.d.mts","names":[],"sources":["../src/providers/http/client.ts"],"mappings":";;;cA2Fa,kBAAA,YAA8B,iBAAA;EAAA,SAChC,IAAA;EAAA,QAED,MAAA;EAAA,QAOA,aAAA;EAAA,QACA,KAAA;EAAA,QACA,UAAA;EAAA,QACA,MAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;cAEI,cAAA,EAAgB,cAAA;EAkCtB,UAAA,CAAA,GAAc,OAAA;EA8Bd,KAAA,CACJ,KAAA,UACA,UAAA,GAAY,UAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,QAAA,CACJ,MAAA,UACA,MAAA,GAAQ,UAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAkBG,IAAA,CACJ,IAAA,WACA,UAAA,GAAY,cAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CACJ,OAAA,UACA,MAAA,GAAQ,WAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CAAM,MAAA,UAAgB,UAAA,UAAoB,QAAA,GAAW,gBAAA,GAAmB,OAAA;EAmBxE,KAAA,CAAA,GAAS,OAAA;EA2BT,QAAA,CAAA,GAAY,OAAA;EAsBlB,cAAA,CAAA;EAAA,QAQQ,sBAAA;EAAA,QAMA,kBAAA;EAAA,QAuBA,OAAA;EAAA,QAqBM,SAAA;EAAA,QAkDA,WAAA;EAAA,QAwCN,sBAAA;EAAA,QAkBA,mBAAA;EAAA,QAUA,KAAA;EAAA,QAQA,GAAA;EAAA,QAMA,IAAA;EAAA,QAIA,KAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { logError, logInfo, logWarn } from "@od-oneapp/shared/logs";
1
+ import { i as logWarn, n as logError, r as logInfo } from "./logs-8LsA--uG.mjs";
2
2
 
3
3
  //#region src/providers/http/client.ts
4
4
  /**
@@ -21,12 +21,24 @@ const DEFAULTS = {
21
21
  batchSize: 10,
22
22
  flushInterval: 5e3,
23
23
  timeout: 1e4,
24
- retries: 3
24
+ retries: 3,
25
+ maxQueueSize: 1e4
25
26
  };
26
27
  /** Base delay for exponential backoff in ms */
27
28
  const BACKOFF_BASE_MS = 1e3;
28
29
  /** LocalStorage key for anonymous ID */
29
30
  const ANON_ID_KEY = "analytics_anonymous_id";
31
+ const isNodeLikeRuntime = () => typeof process !== "undefined" && typeof process.versions === "object" && typeof process.versions.node === "string";
32
+ const isJsdomRuntime = () => {
33
+ if (typeof navigator === "undefined" || typeof navigator.userAgent !== "string") return false;
34
+ return /node|jsdom/i.test(navigator.userAgent);
35
+ };
36
+ const shouldUseWindowLocalStorage = () => {
37
+ if (isNodeLikeRuntime() || isJsdomRuntime()) return false;
38
+ if (typeof window === "undefined") return false;
39
+ if (typeof window.localStorage === "undefined") return false;
40
+ return true;
41
+ };
30
42
  /**
31
43
  * HTTP Analytics Provider for browser environments.
32
44
  *
@@ -57,12 +69,20 @@ var HttpClientProvider = class {
57
69
  constructor(providerConfig) {
58
70
  const options = providerConfig.options ?? {};
59
71
  if (!options.endpoint) throw new Error("HttpProvider requires an endpoint URL");
72
+ try {
73
+ if (new URL(options.endpoint).protocol !== "https:") throw new Error("HttpProvider endpoint must use HTTPS");
74
+ } catch {
75
+ throw new Error("HttpProvider endpoint must be a valid HTTPS URL");
76
+ }
77
+ const resolvedQueueSize = options.maxQueueSize ?? DEFAULTS.maxQueueSize;
78
+ if (!Number.isInteger(resolvedQueueSize) || resolvedQueueSize <= 0) throw new Error("HttpProvider maxQueueSize must be a positive integer");
60
79
  this.config = {
61
80
  ...options,
62
81
  batchSize: options.batchSize ?? DEFAULTS.batchSize,
63
82
  flushInterval: options.flushInterval ?? DEFAULTS.flushInterval,
64
83
  timeout: options.timeout ?? DEFAULTS.timeout,
65
- retries: options.retries ?? DEFAULTS.retries
84
+ retries: options.retries ?? DEFAULTS.retries,
85
+ maxQueueSize: resolvedQueueSize
66
86
  };
67
87
  this.userId = options.userId;
68
88
  this.anonymousId = options.anonymousId ?? this.getOrCreateAnonymousId();
@@ -212,13 +232,20 @@ var HttpClientProvider = class {
212
232
  };
213
233
  handleBeforeUnload = () => {
214
234
  if (this.queue.length > 0 && typeof navigator?.sendBeacon === "function") {
215
- const payload = JSON.stringify({ batch: this.queue });
235
+ const payload = JSON.stringify({
236
+ batch: this.queue,
237
+ ...this.config.apiKey ? { apiKey: this.config.apiKey } : {}
238
+ });
216
239
  const blob = new Blob([payload], { type: "application/json" });
217
240
  navigator.sendBeacon(this.config.endpoint, blob);
218
241
  this.queue = [];
219
242
  }
220
243
  };
221
244
  enqueue(event) {
245
+ if (this.queue.length >= this.config.maxQueueSize) {
246
+ this.queue.shift();
247
+ this.warn("Http provider queue full. Dropping oldest event to maintain bounded memory.");
248
+ }
222
249
  this.queue.push(event);
223
250
  if (this.config.debug) this.log("Event queued", {
224
251
  type: event.type,
@@ -288,11 +315,12 @@ var HttpClientProvider = class {
288
315
  }
289
316
  }
290
317
  getOrCreateAnonymousId() {
291
- if (typeof localStorage !== "undefined") try {
292
- const stored = localStorage.getItem(ANON_ID_KEY);
318
+ if (shouldUseWindowLocalStorage()) try {
319
+ const storage = window.localStorage;
320
+ const stored = storage.getItem(ANON_ID_KEY);
293
321
  if (stored) return stored;
294
322
  const newId = this.generateAnonymousId();
295
- localStorage.setItem(ANON_ID_KEY, newId);
323
+ storage.setItem(ANON_ID_KEY, newId);
296
324
  return newId;
297
325
  } catch {}
298
326
  return this.generateAnonymousId();
@@ -1 +1 @@
1
- {"version":3,"file":"providers-http-client.mjs","names":[],"sources":["../src/providers/http/client.ts"],"sourcesContent":["/**\n * @fileoverview HTTP provider for browser/client environments\n *\n * Provides HTTP-based analytics event sending for client-side applications.\n * Sends events to a remote ingestion endpoint (e.g., oneapp-api/ingest).\n *\n * Features:\n * - Event batching for efficiency\n * - Automatic flush on interval, batch size, or page unload\n * - Retry with exponential backoff\n * - Anonymous ID persistence in localStorage\n * - Offline queue support (events queued when offline)\n *\n * @module @od-oneapp/analytics/providers/http/client\n */\n\nimport { logInfo, logWarn, logError } from '@repo/shared/logs';\n\nimport type { HttpProviderConfig, IngestionResponse, QueuedEvent } from './types';\nimport type {\n AnalyticsContext,\n AnalyticsProvider,\n GroupTraits,\n PageProperties,\n Properties,\n ProviderConfig,\n UserTraits,\n} from '../../shared/types/types';\n\n/** Default configuration values */\nconst DEFAULTS = {\n batchSize: 10,\n flushInterval: 5000,\n timeout: 10000,\n retries: 3,\n} as const;\n\n/** Base delay for exponential backoff in ms */\nconst BACKOFF_BASE_MS = 1000;\n\n/** LocalStorage key for anonymous ID */\nconst ANON_ID_KEY = 'analytics_anonymous_id';\n\n/**\n * HTTP Analytics Provider for browser environments.\n *\n * Sends analytics events to a remote endpoint via HTTP POST requests.\n * Events are batched and flushed periodically for efficiency.\n *\n * @example\n * ```typescript\n * const provider = new HttpClientProvider({\n * options: {\n * endpoint: 'https://api.oneapp.dev/v1/ingest',\n * apiKey: process.env.NEXT_PUBLIC_ANALYTICS_API_KEY,\n * },\n * });\n * await provider.initialize();\n * await provider.track('Button Clicked', { button: 'signup' });\n * ```\n */\nexport class HttpClientProvider implements AnalyticsProvider {\n readonly name = 'http';\n\n private config: Required<\n Pick<HttpProviderConfig, 'batchSize' | 'flushInterval' | 'timeout' | 'retries'>\n > &\n HttpProviderConfig;\n private isInitialized = false;\n private queue: QueuedEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private userId?: string;\n private anonymousId: string;\n private isFlushing = false;\n\n constructor(providerConfig: ProviderConfig) {\n const options = (providerConfig.options ?? {}) as unknown as HttpProviderConfig;\n\n if (!options.endpoint) {\n throw new Error('HttpProvider requires an endpoint URL');\n }\n\n this.config = {\n ...options,\n batchSize: options.batchSize ?? DEFAULTS.batchSize,\n flushInterval: options.flushInterval ?? DEFAULTS.flushInterval,\n timeout: options.timeout ?? DEFAULTS.timeout,\n retries: options.retries ?? DEFAULTS.retries,\n };\n\n this.userId = options.userId;\n this.anonymousId = options.anonymousId ?? this.getOrCreateAnonymousId();\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n // Start auto-flush timer if interval > 0\n if (this.config.flushInterval > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n // Flush on page unload/visibility change\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleVisibilityChange);\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', this.handleBeforeUnload);\n }\n\n if (this.config.debug) {\n this.log('HTTP Analytics Provider initialized', {\n endpoint: this.config.endpoint,\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n });\n }\n\n this.isInitialized = true;\n }\n\n async track(\n event: string,\n properties: Properties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'track', event });\n return;\n }\n\n this.enqueue({\n type: 'track',\n event,\n properties,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async identify(\n userId: string,\n traits: UserTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'identify', userId });\n return;\n }\n\n // Update stored user ID\n this.userId = userId;\n\n this.enqueue({\n type: 'identify',\n userId,\n traits,\n timestamp: new Date().toISOString(),\n anonymousId: this.anonymousId,\n });\n }\n\n async page(\n name?: string,\n properties: PageProperties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'page', name });\n return;\n }\n\n this.enqueue({\n type: 'page',\n name,\n properties,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async group(\n groupId: string,\n traits: GroupTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'group', groupId });\n return;\n }\n\n this.enqueue({\n type: 'group',\n groupId,\n traits,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async alias(userId: string, previousId: string, _context?: AnalyticsContext): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'alias', userId, previousId });\n return;\n }\n\n this.enqueue({\n type: 'alias',\n userId,\n previousId,\n timestamp: new Date().toISOString(),\n anonymousId: this.anonymousId,\n });\n }\n\n /**\n * Flush all queued events to the remote endpoint.\n * Called automatically on interval, batch size, or page events.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0 || this.isFlushing) return;\n\n this.isFlushing = true;\n\n // Take all events from queue\n const events = [...this.queue];\n this.queue = [];\n\n try {\n await this.sendBatch(events);\n } catch (error) {\n // Re-queue events on failure (they'll be retried on next flush)\n this.queue.unshift(...events);\n this.error('Failed to flush events, re-queued for retry', {\n eventCount: events.length,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n this.isFlushing = false;\n }\n }\n\n /**\n * Shutdown the provider gracefully.\n * Flushes remaining events and removes event listeners.\n */\n async shutdown(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleVisibilityChange);\n }\n\n if (typeof window !== 'undefined') {\n window.removeEventListener('beforeunload', this.handleBeforeUnload);\n }\n\n // Final flush\n await this.flush();\n this.isInitialized = false;\n }\n\n /**\n * Get the current queue length (for testing/monitoring).\n */\n getQueueLength(): number {\n return this.queue.length;\n }\n\n // ============================================================================\n // Event Handlers\n // ============================================================================\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n void this.flush();\n }\n };\n\n private handleBeforeUnload = (): void => {\n // Use sendBeacon for reliable delivery on page unload\n if (this.queue.length > 0 && typeof navigator?.sendBeacon === 'function') {\n const payload = JSON.stringify({ batch: this.queue });\n const headers: Record<string, string> = {\n type: 'application/json',\n };\n\n // Note: sendBeacon doesn't support custom headers, so we include API key in payload\n // The server should accept it from either header or body\n const blob = new Blob([payload], headers);\n navigator.sendBeacon(this.config.endpoint, blob);\n this.queue = [];\n }\n };\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n private enqueue(event: QueuedEvent): void {\n this.queue.push(event);\n\n if (this.config.debug) {\n this.log('Event queued', {\n type: event.type,\n queueLength: this.queue.length,\n });\n }\n\n // Flush if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n private async sendBatch(events: QueuedEvent[]): Promise<void> {\n const payload = { batch: events };\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.config.retries; attempt++) {\n try {\n const response = await this.sendRequest(payload);\n\n if (response.success) {\n if (this.config.debug) {\n this.log('Batch sent successfully', {\n accepted: response.accepted,\n rejected: response.rejected,\n });\n }\n return;\n }\n\n // Request succeeded but ingestion failed\n this.warn('Batch partially rejected', {\n accepted: response.accepted,\n rejected: response.rejected,\n error: response.error,\n });\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt < this.config.retries) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = BACKOFF_BASE_MS * Math.pow(2, attempt);\n\n if (this.config.debug) {\n this.log('Retrying batch send', {\n attempt: attempt + 1,\n maxRetries: this.config.retries,\n delayMs: delay,\n });\n }\n\n await this.sleep(delay);\n }\n }\n }\n\n // All retries exhausted\n throw lastError ?? new Error('Failed to send batch after retries');\n }\n\n private async sendRequest(payload: { batch: QueuedEvent[] }): Promise<IngestionResponse> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.headers,\n };\n\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n // Include credentials for cross-origin requests if needed\n credentials: 'omit',\n });\n\n if (!response.ok) {\n // Handle rate limiting\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n throw new Error(`Rate limited. Retry after ${retryAfter ?? 'unknown'} seconds`);\n }\n\n const errorBody = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorBody}`);\n }\n\n return (await response.json()) as IngestionResponse;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private getOrCreateAnonymousId(): string {\n // Try to get from localStorage\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(ANON_ID_KEY);\n if (stored) return stored;\n\n const newId = this.generateAnonymousId();\n localStorage.setItem(ANON_ID_KEY, newId);\n return newId;\n } catch {\n // localStorage not available or blocked\n }\n }\n\n return this.generateAnonymousId();\n }\n\n private generateAnonymousId(): string {\n // Use crypto.randomUUID if available\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n\n // Fallback\n return `anon_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 9)}`;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n // ============================================================================\n // Logging (browser-compatible)\n // ============================================================================\n\n private log(message: string, data?: Record<string, unknown>): void {\n if (this.config.debug) {\n logInfo(`[Analytics:HTTP] ${message}`, data);\n }\n }\n\n private warn(message: string, data?: Record<string, unknown>): void {\n logWarn(`[Analytics:HTTP] ${message}`, data);\n }\n\n private error(message: string, data?: Record<string, unknown>): void {\n logError(`[Analytics:HTTP] ${message}`, data);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"providers-http-client.mjs","names":[],"sources":["../src/providers/http/client.ts"],"sourcesContent":["/**\n * @fileoverview HTTP provider for browser/client environments\n *\n * Provides HTTP-based analytics event sending for client-side applications.\n * Sends events to a remote ingestion endpoint (e.g., oneapp-api/ingest).\n *\n * Features:\n * - Event batching for efficiency\n * - Automatic flush on interval, batch size, or page unload\n * - Retry with exponential backoff\n * - Anonymous ID persistence in localStorage\n * - Offline queue support (events queued when offline)\n *\n * @module @od-oneapp/analytics/providers/http/client\n */\n\nimport { logInfo, logWarn, logError } from '@od-oneapp/shared/logs';\n\nimport type { HttpProviderConfig, IngestionResponse, QueuedEvent } from './types';\nimport type {\n AnalyticsContext,\n AnalyticsProvider,\n GroupTraits,\n PageProperties,\n Properties,\n ProviderConfig,\n UserTraits,\n} from '../../shared/types/types';\n\n/** Default configuration values */\nconst DEFAULTS = {\n batchSize: 10,\n flushInterval: 5000,\n timeout: 10000,\n retries: 3,\n maxQueueSize: 10000,\n} as const;\n\n/** Base delay for exponential backoff in ms */\nconst BACKOFF_BASE_MS = 1000;\n\n/** LocalStorage key for anonymous ID */\nconst ANON_ID_KEY = 'analytics_anonymous_id';\n\nconst isNodeLikeRuntime = (): boolean =>\n typeof process !== 'undefined' &&\n typeof process.versions === 'object' &&\n typeof process.versions.node === 'string';\n\nconst isJsdomRuntime = (): boolean => {\n if (typeof navigator === 'undefined' || typeof navigator.userAgent !== 'string') {\n return false;\n }\n\n return /node|jsdom/i.test(navigator.userAgent);\n};\n\nconst shouldUseWindowLocalStorage = (): boolean => {\n if (isNodeLikeRuntime() || isJsdomRuntime()) {\n return false;\n }\n\n if (typeof window === 'undefined') {\n return false;\n }\n\n if (typeof window.localStorage === 'undefined') {\n return false;\n }\n\n return true;\n};\n\n/**\n * HTTP Analytics Provider for browser environments.\n *\n * Sends analytics events to a remote endpoint via HTTP POST requests.\n * Events are batched and flushed periodically for efficiency.\n *\n * @example\n * ```typescript\n * const provider = new HttpClientProvider({\n * options: {\n * endpoint: 'https://api.oneapp.dev/v1/ingest',\n * apiKey: process.env.NEXT_PUBLIC_ANALYTICS_API_KEY,\n * },\n * });\n * await provider.initialize();\n * await provider.track('Button Clicked', { button: 'signup' });\n * ```\n */\nexport class HttpClientProvider implements AnalyticsProvider {\n readonly name = 'http';\n\n private config: Required<\n Pick<\n HttpProviderConfig,\n 'batchSize' | 'flushInterval' | 'timeout' | 'retries' | 'maxQueueSize'\n >\n > &\n HttpProviderConfig;\n private isInitialized = false;\n private queue: QueuedEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private userId?: string;\n private anonymousId: string;\n private isFlushing = false;\n\n constructor(providerConfig: ProviderConfig) {\n const options = (providerConfig.options ?? {}) as unknown as HttpProviderConfig;\n\n if (!options.endpoint) {\n throw new Error('HttpProvider requires an endpoint URL');\n }\n\n try {\n const parsed = new URL(options.endpoint);\n if (parsed.protocol !== 'https:') {\n throw new Error('HttpProvider endpoint must use HTTPS');\n }\n } catch {\n throw new Error('HttpProvider endpoint must be a valid HTTPS URL');\n }\n\n const resolvedQueueSize = options.maxQueueSize ?? DEFAULTS.maxQueueSize;\n if (!Number.isInteger(resolvedQueueSize) || resolvedQueueSize <= 0) {\n throw new Error('HttpProvider maxQueueSize must be a positive integer');\n }\n\n this.config = {\n ...options,\n batchSize: options.batchSize ?? DEFAULTS.batchSize,\n flushInterval: options.flushInterval ?? DEFAULTS.flushInterval,\n timeout: options.timeout ?? DEFAULTS.timeout,\n retries: options.retries ?? DEFAULTS.retries,\n maxQueueSize: resolvedQueueSize,\n };\n\n this.userId = options.userId;\n this.anonymousId = options.anonymousId ?? this.getOrCreateAnonymousId();\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n // Start auto-flush timer if interval > 0\n if (this.config.flushInterval > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n // Flush on page unload/visibility change\n if (typeof document !== 'undefined') {\n document.addEventListener('visibilitychange', this.handleVisibilityChange);\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', this.handleBeforeUnload);\n }\n\n if (this.config.debug) {\n this.log('HTTP Analytics Provider initialized', {\n endpoint: this.config.endpoint,\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n });\n }\n\n this.isInitialized = true;\n }\n\n async track(\n event: string,\n properties: Properties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'track', event });\n return;\n }\n\n this.enqueue({\n type: 'track',\n event,\n properties,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async identify(\n userId: string,\n traits: UserTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'identify', userId });\n return;\n }\n\n // Update stored user ID\n this.userId = userId;\n\n this.enqueue({\n type: 'identify',\n userId,\n traits,\n timestamp: new Date().toISOString(),\n anonymousId: this.anonymousId,\n });\n }\n\n async page(\n name?: string,\n properties: PageProperties = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'page', name });\n return;\n }\n\n this.enqueue({\n type: 'page',\n name,\n properties,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async group(\n groupId: string,\n traits: GroupTraits = {},\n _context?: AnalyticsContext,\n ): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'group', groupId });\n return;\n }\n\n this.enqueue({\n type: 'group',\n groupId,\n traits,\n timestamp: new Date().toISOString(),\n userId: this.userId,\n anonymousId: this.anonymousId,\n });\n }\n\n async alias(userId: string, previousId: string, _context?: AnalyticsContext): Promise<void> {\n if (!this.isInitialized) {\n this.warn('HTTP provider not initialized', { operation: 'alias', userId, previousId });\n return;\n }\n\n this.enqueue({\n type: 'alias',\n userId,\n previousId,\n timestamp: new Date().toISOString(),\n anonymousId: this.anonymousId,\n });\n }\n\n /**\n * Flush all queued events to the remote endpoint.\n * Called automatically on interval, batch size, or page events.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0 || this.isFlushing) return;\n\n this.isFlushing = true;\n\n // Take all events from queue\n const events = [...this.queue];\n this.queue = [];\n\n try {\n await this.sendBatch(events);\n } catch (error) {\n // Re-queue events on failure (they'll be retried on next flush)\n this.queue.unshift(...events);\n this.error('Failed to flush events, re-queued for retry', {\n eventCount: events.length,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n this.isFlushing = false;\n }\n }\n\n /**\n * Shutdown the provider gracefully.\n * Flushes remaining events and removes event listeners.\n */\n async shutdown(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n\n if (typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.handleVisibilityChange);\n }\n\n if (typeof window !== 'undefined') {\n window.removeEventListener('beforeunload', this.handleBeforeUnload);\n }\n\n // Final flush\n await this.flush();\n this.isInitialized = false;\n }\n\n /**\n * Get the current queue length (for testing/monitoring).\n */\n getQueueLength(): number {\n return this.queue.length;\n }\n\n // ============================================================================\n // Event Handlers\n // ============================================================================\n\n private handleVisibilityChange = (): void => {\n if (document.visibilityState === 'hidden') {\n void this.flush();\n }\n };\n\n private handleBeforeUnload = (): void => {\n // Use sendBeacon for reliable delivery on page unload\n if (this.queue.length > 0 && typeof navigator?.sendBeacon === 'function') {\n const payload = JSON.stringify({\n batch: this.queue,\n ...(this.config.apiKey ? { apiKey: this.config.apiKey } : {}),\n });\n const headers: Record<string, string> = {\n type: 'application/json',\n };\n\n // sendBeacon cannot set custom headers. Include API key in body for\n // endpoints that validate keys from either header or request payload.\n const blob = new Blob([payload], headers);\n navigator.sendBeacon(this.config.endpoint, blob);\n this.queue = [];\n }\n };\n\n // ============================================================================\n // Private Methods\n // ============================================================================\n\n private enqueue(event: QueuedEvent): void {\n if (this.queue.length >= this.config.maxQueueSize) {\n this.queue.shift();\n this.warn('Http provider queue full. Dropping oldest event to maintain bounded memory.');\n }\n\n this.queue.push(event);\n\n if (this.config.debug) {\n this.log('Event queued', {\n type: event.type,\n queueLength: this.queue.length,\n });\n }\n\n // Flush if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n private async sendBatch(events: QueuedEvent[]): Promise<void> {\n const payload = { batch: events };\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.config.retries; attempt++) {\n try {\n const response = await this.sendRequest(payload);\n\n if (response.success) {\n if (this.config.debug) {\n this.log('Batch sent successfully', {\n accepted: response.accepted,\n rejected: response.rejected,\n });\n }\n return;\n }\n\n // Request succeeded but ingestion failed\n this.warn('Batch partially rejected', {\n accepted: response.accepted,\n rejected: response.rejected,\n error: response.error,\n });\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt < this.config.retries) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = BACKOFF_BASE_MS * Math.pow(2, attempt);\n\n if (this.config.debug) {\n this.log('Retrying batch send', {\n attempt: attempt + 1,\n maxRetries: this.config.retries,\n delayMs: delay,\n });\n }\n\n await this.sleep(delay);\n }\n }\n }\n\n // All retries exhausted\n throw lastError ?? new Error('Failed to send batch after retries');\n }\n\n private async sendRequest(payload: { batch: QueuedEvent[] }): Promise<IngestionResponse> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.config.headers,\n };\n\n if (this.config.apiKey) {\n headers['X-API-Key'] = this.config.apiKey;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);\n\n try {\n const response = await fetch(this.config.endpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller.signal,\n // Include credentials for cross-origin requests if needed\n credentials: 'omit',\n });\n\n if (!response.ok) {\n // Handle rate limiting\n if (response.status === 429) {\n const retryAfter = response.headers.get('Retry-After');\n throw new Error(`Rate limited. Retry after ${retryAfter ?? 'unknown'} seconds`);\n }\n\n const errorBody = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorBody}`);\n }\n\n return (await response.json()) as IngestionResponse;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private getOrCreateAnonymousId(): string {\n if (shouldUseWindowLocalStorage()) {\n try {\n const storage = window.localStorage;\n const stored = storage.getItem(ANON_ID_KEY);\n if (stored) return stored;\n\n const newId = this.generateAnonymousId();\n storage.setItem(ANON_ID_KEY, newId);\n return newId;\n } catch {\n // localStorage not available or blocked\n }\n }\n\n return this.generateAnonymousId();\n }\n\n private generateAnonymousId(): string {\n // Use crypto.randomUUID if available\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n\n // Fallback\n return `anon_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 9)}`;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n\n // ============================================================================\n // Logging (browser-compatible)\n // ============================================================================\n\n private log(message: string, data?: Record<string, unknown>): void {\n if (this.config.debug) {\n logInfo(`[Analytics:HTTP] ${message}`, data);\n }\n }\n\n private warn(message: string, data?: Record<string, unknown>): void {\n logWarn(`[Analytics:HTTP] ${message}`, data);\n }\n\n private error(message: string, data?: Record<string, unknown>): void {\n logError(`[Analytics:HTTP] ${message}`, data);\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { A as GroupTraits, F as UserTraits, M as Properties, c as ProviderConfig, i as AnalyticsProvider, j as PageProperties, n as AnalyticsContext } from "./types-BxBnNQ0V.mjs";
1
+ import { A as GroupTraits, F as UserTraits, M as Properties, c as ProviderConfig, i as AnalyticsProvider, j as PageProperties, n as AnalyticsContext } from "./types-BbXOa_UL.mjs";
2
2
 
3
3
  //#region src/providers/http/server.d.ts
4
4
  declare class HttpServerProvider implements AnalyticsProvider {
@@ -1 +1 @@
1
- {"version":3,"file":"providers-http-server.d.mts","names":[],"sources":["../src/providers/http/server.ts"],"mappings":";;;cA0Da,kBAAA,YAA8B,iBAAA;EAAA,SAChC,IAAA;EAAA,QAED,MAAA;EAAA,QAIA,aAAA;EAAA,QACA,KAAA;EAAA,QACA,UAAA;EAAA,QACA,MAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;cAEI,cAAA,EAAgB,cAAA;EAmBtB,UAAA,CAAA,GAAc,OAAA;EA2Bd,KAAA,CACJ,KAAA,UACA,UAAA,GAAY,UAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,QAAA,CACJ,MAAA,UACA,MAAA,GAAQ,UAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAkBG,IAAA,CACJ,IAAA,WACA,UAAA,GAAY,cAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CACJ,OAAA,UACA,MAAA,GAAQ,WAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CAAM,MAAA,UAAgB,UAAA,UAAoB,QAAA,GAAW,gBAAA,GAAmB,OAAA;EAwBxE,KAAA,CAAA,GAAS,OAAA;EA4BT,QAAA,CAAA,GAAY,OAAA;EAclB,cAAA,CAAA;EAAA,QAQQ,OAAA;EAAA,QAiBM,SAAA;EAAA,QAqDA,WAAA;EAAA,QAsCN,mBAAA;EAAA,QAUA,KAAA;AAAA"}
1
+ {"version":3,"file":"providers-http-server.d.mts","names":[],"sources":["../src/providers/http/server.ts"],"mappings":";;;cA2Da,kBAAA,YAA8B,iBAAA;EAAA,SAChC,IAAA;EAAA,QAED,MAAA;EAAA,QAOA,aAAA;EAAA,QACA,KAAA;EAAA,QACA,UAAA;EAAA,QACA,MAAA;EAAA,QACA,WAAA;EAAA,QACA,UAAA;cAEI,cAAA,EAAgB,cAAA;EAkCtB,UAAA,CAAA,GAAc,OAAA;EA2Bd,KAAA,CACJ,KAAA,UACA,UAAA,GAAY,UAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,QAAA,CACJ,MAAA,UACA,MAAA,GAAQ,UAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAkBG,IAAA,CACJ,IAAA,WACA,UAAA,GAAY,cAAA,EACZ,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CACJ,OAAA,UACA,MAAA,GAAQ,WAAA,EACR,QAAA,GAAW,gBAAA,GACV,OAAA;EAgBG,KAAA,CAAM,MAAA,UAAgB,UAAA,UAAoB,QAAA,GAAW,gBAAA,GAAmB,OAAA;EAwBxE,KAAA,CAAA,GAAS,OAAA;EA4BT,QAAA,CAAA,GAAY,OAAA;EAclB,cAAA,CAAA;EAAA,QAQQ,OAAA;EAAA,QAyBM,SAAA;EAAA,QAqDA,WAAA;EAAA,QAsCN,mBAAA;EAAA,QAUA,KAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { logDebug, logError, logWarn } from "@od-oneapp/shared/logs";
1
+ import { i as logWarn, n as logError, t as logDebug } from "./logs-8LsA--uG.mjs";
2
2
 
3
3
  //#region src/providers/http/server.ts
4
4
  /**
@@ -20,7 +20,8 @@ const DEFAULTS = {
20
20
  batchSize: 10,
21
21
  flushInterval: 5e3,
22
22
  timeout: 1e4,
23
- retries: 3
23
+ retries: 3,
24
+ maxQueueSize: 1e4
24
25
  };
25
26
  /** Base delay for exponential backoff in ms */
26
27
  const BACKOFF_BASE_MS = 1e3;
@@ -55,12 +56,20 @@ var HttpServerProvider = class {
55
56
  constructor(providerConfig) {
56
57
  const options = providerConfig.options ?? {};
57
58
  if (!options.endpoint) throw new Error("HttpProvider requires an endpoint URL");
59
+ try {
60
+ if (new URL(options.endpoint).protocol !== "https:") throw new Error("HttpProvider endpoint must use HTTPS");
61
+ } catch {
62
+ throw new Error("HttpProvider endpoint must be a valid HTTPS URL");
63
+ }
64
+ const resolvedQueueSize = options.maxQueueSize ?? DEFAULTS.maxQueueSize;
65
+ if (!Number.isInteger(resolvedQueueSize) || resolvedQueueSize <= 0) throw new Error("HttpProvider maxQueueSize must be a positive integer");
58
66
  this.config = {
59
67
  ...options,
60
68
  batchSize: options.batchSize ?? DEFAULTS.batchSize,
61
69
  flushInterval: options.flushInterval ?? DEFAULTS.flushInterval,
62
70
  timeout: options.timeout ?? DEFAULTS.timeout,
63
- retries: options.retries ?? DEFAULTS.retries
71
+ retries: options.retries ?? DEFAULTS.retries,
72
+ maxQueueSize: resolvedQueueSize
64
73
  };
65
74
  this.userId = options.userId;
66
75
  this.anonymousId = options.anonymousId ?? this.generateAnonymousId();
@@ -212,6 +221,13 @@ var HttpServerProvider = class {
212
221
  return this.queue.length;
213
222
  }
214
223
  enqueue(event) {
224
+ if (this.queue.length >= this.config.maxQueueSize) {
225
+ this.queue.shift();
226
+ logWarn("Http provider queue full. Dropping oldest event to maintain bounded memory.", {
227
+ provider: "http",
228
+ queueSize: this.config.maxQueueSize
229
+ });
230
+ }
215
231
  this.queue.push(event);
216
232
  if (this.config.debug) logDebug("Event queued", {
217
233
  provider: "http",