@crelora/mark 0.0.18 → 0.1.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.
- package/README.md +38 -11
- package/dist/browser.es.js +618 -124
- package/dist/browser.es.js.map +1 -0
- package/dist/browser.umd.js +2 -1
- package/dist/browser.umd.js.map +1 -0
- package/dist/node.cjs +2 -1
- package/dist/node.cjs.map +1 -0
- package/dist/node.es.js +431 -66
- package/dist/node.es.js.map +1 -0
- package/dist/types/browser/BrowserStorage.d.ts +12 -0
- package/dist/types/browser/Mark.d.ts +18 -0
- package/dist/types/core/DeliveryQueue.d.ts +57 -0
- package/dist/types/core/HttpTransport.d.ts +6 -2
- package/dist/types/core/MarkCore.d.ts +46 -2
- package/dist/types/core/TransportError.d.ts +15 -0
- package/dist/types/core/adapters.d.ts +14 -1
- package/dist/types/node/StatelessStorage.d.ts +7 -0
- package/dist/types/node/index.d.ts +8 -0
- package/dist/types/types.d.ts +37 -0
- package/dist/web-vitals-CrnTllyu.js +221 -0
- package/dist/web-vitals-CrnTllyu.js.map +1 -0
- package/package.json +18 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.es.js","sources":["../src/core/constants.ts","../src/core/TransportError.ts","../src/core/DeliveryQueue.ts","../src/core/MarkCore.ts","../src/core/HttpTransport.ts","../src/node/StatelessStorage.ts","../src/node/index.ts"],"sourcesContent":["export const DEFAULT_ENDPOINT = 'https://ingest.onelence.com';\n\n","export class TransportError extends Error {\r\n public readonly status?: number;\r\n public readonly retryAfterMs?: number;\r\n\r\n constructor(message: string, options: { status?: number; retryAfterMs?: number } = {}) {\r\n super(message);\r\n this.name = 'TransportError';\r\n this.status = options.status;\r\n this.retryAfterMs = options.retryAfterMs;\r\n }\r\n}\r\n\r\n/**\r\n * Returns true when a TransportError status should NOT be retried by the queue.\r\n * 4xx client errors (other than 408 Request Timeout and 429 Too Many Requests)\r\n * indicate a request problem that re-sending will not fix.\r\n */\r\nexport function isNonRetriableStatus(status: number | undefined): boolean {\r\n if (typeof status !== 'number') return false;\r\n if (status < 400 || status >= 500) return false;\r\n if (status === 408 || status === 429) return false;\r\n return true;\r\n}\r\n\r\nexport function parseRetryAfter(headerValue: string | null | undefined): number | undefined {\r\n if (!headerValue) return undefined;\r\n const trimmed = headerValue.trim();\r\n if (!trimmed) return undefined;\r\n const numeric = Number(trimmed);\r\n if (Number.isFinite(numeric) && numeric >= 0) {\r\n return Math.floor(numeric * 1000);\r\n }\r\n const dateMs = Date.parse(trimmed);\r\n if (Number.isFinite(dateMs)) {\r\n const diff = dateMs - Date.now();\r\n return diff > 0 ? diff : 0;\r\n }\r\n return undefined;\r\n}\r\n","import type { TransportAdapter } from './adapters';\r\nimport { TransportError, isNonRetriableStatus } from './TransportError';\r\n\r\ninterface QueueItem {\r\n path: string;\r\n data: Record<string, unknown>;\r\n attempts: number;\r\n nextAttemptAt: number;\r\n enqueuedAt: number;\r\n}\r\n\r\nexport interface DeliveryQueueOptions {\r\n debug?: boolean;\r\n maxAttempts?: number;\r\n baseBackoffMs?: number;\r\n maxBackoffMs?: number;\r\n /**\r\n * Maximum age (ms) for persisted queue items before they are dropped on load\r\n * or next process tick. Defaults to 48 hours.\r\n */\r\n maxItemAgeMs?: number;\r\n loadPersisted?: () => QueueItem[];\r\n savePersisted?: (items: QueueItem[]) => void;\r\n onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n}\r\n\r\nconst DEFAULT_MAX_ATTEMPTS = 5;\r\nconst DEFAULT_BASE_BACKOFF_MS = 300;\r\nconst DEFAULT_MAX_BACKOFF_MS = 15000;\r\nconst DEFAULT_MAX_ITEM_AGE_MS = 48 * 60 * 60 * 1000;\r\n\r\nexport class DeliveryQueue {\r\n private readonly queue: QueueItem[] = [];\r\n private flushing = false;\r\n private sent = 0;\r\n private failed = 0;\r\n private dropped = 0;\r\n private readonly maxAttempts: number;\r\n private readonly baseBackoffMs: number;\r\n private readonly maxBackoffMs: number;\r\n private readonly maxItemAgeMs: number;\r\n private readonly debug: boolean;\r\n private readonly loadPersisted?: () => QueueItem[];\r\n private readonly savePersisted?: (items: QueueItem[]) => void;\r\n private readonly onError?: (error: unknown, event?: Record<string, unknown>) => void;\r\n\r\n constructor(private readonly transport: TransportAdapter, options: DeliveryQueueOptions = {}) {\r\n this.maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\r\n this.baseBackoffMs = options.baseBackoffMs ?? DEFAULT_BASE_BACKOFF_MS;\r\n this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;\r\n this.maxItemAgeMs = options.maxItemAgeMs ?? DEFAULT_MAX_ITEM_AGE_MS;\r\n this.debug = options.debug ?? false;\r\n this.loadPersisted = options.loadPersisted;\r\n this.savePersisted = options.savePersisted;\r\n this.onError = options.onError;\r\n const persisted = this.loadPersisted?.() ?? [];\r\n if (persisted.length > 0) {\r\n const now = Date.now();\r\n for (const item of persisted) {\r\n const enqueuedAt = item.enqueuedAt ?? now;\r\n if (now - enqueuedAt > this.maxItemAgeMs) {\r\n this.dropped += 1;\r\n continue;\r\n }\r\n this.queue.push({ ...item, enqueuedAt });\r\n }\r\n this.persist();\r\n }\r\n }\r\n\r\n public enqueue(path: string, data: Record<string, unknown>) {\r\n this.queue.push({\r\n path,\r\n data,\r\n attempts: 0,\r\n nextAttemptAt: Date.now(),\r\n enqueuedAt: Date.now(),\r\n });\r\n this.persist();\r\n void this.process();\r\n }\r\n\r\n public async flush() {\r\n await this.process(true);\r\n await this.transport.flush?.();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain using sendBeacon. Intended for page unload;\r\n * errors are swallowed because the tab is going away.\r\n */\r\n public drainViaBeacon() {\r\n if (this.queue.length === 0) return;\r\n const items = this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n for (const item of items) {\r\n const eventData = { ...item.data };\r\n delete eventData.__prefer_beacon;\r\n try {\r\n void this.transport.send(item.path, eventData, { preferBeacon: true });\r\n } catch {\r\n // ignore: browser is unloading\r\n }\r\n }\r\n }\r\n\r\n public getStats() {\r\n return {\r\n queued: this.queue.length,\r\n sent: this.sent,\r\n failed: this.failed,\r\n dropped: this.dropped,\r\n };\r\n }\r\n\r\n public clear() {\r\n this.queue.splice(0, this.queue.length);\r\n this.persist();\r\n }\r\n\r\n private persist() {\r\n this.savePersisted?.(this.queue);\r\n }\r\n\r\n private evictExpired() {\r\n if (this.queue.length === 0) return;\r\n const now = Date.now();\r\n const cutoff = now - this.maxItemAgeMs;\r\n let removed = 0;\r\n for (let i = this.queue.length - 1; i >= 0; i -= 1) {\r\n if (this.queue[i].enqueuedAt <= cutoff) {\r\n this.queue.splice(i, 1);\r\n this.dropped += 1;\r\n removed += 1;\r\n }\r\n }\r\n if (removed > 0) {\r\n this.persist();\r\n }\r\n }\r\n\r\n private async process(force = false): Promise<void> {\r\n if (this.flushing) {\r\n return;\r\n }\r\n this.flushing = true;\r\n try {\r\n this.evictExpired();\r\n while (this.queue.length > 0) {\r\n const item = this.queue[0];\r\n if (!force && item.nextAttemptAt > Date.now()) {\r\n break;\r\n }\r\n try {\r\n const eventData = { ...item.data };\r\n const preferBeacon = eventData.__prefer_beacon === true;\r\n delete eventData.__prefer_beacon;\r\n await this.transport.send(item.path, eventData, { preferBeacon });\r\n this.queue.shift();\r\n this.sent += 1;\r\n this.persist();\r\n } catch (error) {\r\n this.failed += 1;\r\n this.onError?.(error, item.data);\r\n\r\n const status = error instanceof TransportError ? error.status : undefined;\r\n if (isNonRetriableStatus(status)) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after non-retriable status', status, item.path);\r\n }\r\n continue;\r\n }\r\n\r\n item.attempts += 1;\r\n if (item.attempts >= this.maxAttempts) {\r\n this.queue.shift();\r\n this.dropped += 1;\r\n this.persist();\r\n if (this.debug) {\r\n console.error('[Mark] Dropping event after max retries', item.path, error);\r\n }\r\n continue;\r\n }\r\n const retryAfterMs = error instanceof TransportError ? error.retryAfterMs : undefined;\r\n let delay: number;\r\n if (typeof retryAfterMs === 'number') {\r\n delay = Math.min(this.maxBackoffMs, Math.max(0, retryAfterMs));\r\n } else {\r\n const jitter = Math.random() * this.baseBackoffMs;\r\n delay = Math.min(\r\n this.maxBackoffMs,\r\n this.baseBackoffMs * 2 ** (item.attempts - 1) + jitter\r\n );\r\n }\r\n item.nextAttemptAt = Date.now() + delay;\r\n this.persist();\r\n break;\r\n }\r\n }\r\n } finally {\r\n this.flushing = false;\r\n }\r\n }\r\n}\r\n","import type {\r\n MarkConfig,\r\n TrackEventData,\r\n ConversionProperties,\r\n IdentifyTraits,\r\n ConsentStatus,\r\n JsonValue,\r\n} from '../types';\r\nimport type { MarkDependencies } from './adapters';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport { DeliveryQueue } from './DeliveryQueue';\r\n\r\ntype ResolvedConfig = MarkConfig & { endpoint: string };\r\nconst TRACK_RESERVED_FIELDS = new Set(['event_name', 'user_id', 'consent_state', 'source', 'is_conversion']);\r\nconst IDENTIFY_RESERVED_FIELDS = new Set([\r\n 'user_id',\r\n 'visitor_id',\r\n 'click_id',\r\n 'campaign_id',\r\n 'query',\r\n 'consent_state',\r\n 'source',\r\n]);\r\n\r\nexport class MarkCore {\r\n private config: ResolvedConfig;\r\n private consentRequirement: boolean | 'auto';\r\n private siteId?: string;\r\n private siteHost?: string;\r\n private readonly queue: DeliveryQueue;\r\n private readonly sessionTimeoutMs: number;\r\n private batchTimer: ReturnType<typeof setTimeout> | null = null;\r\n private readonly batchedEvents: Array<Record<string, unknown>> = [];\r\n private tcfCachedAllowed = false;\r\n\r\n constructor(config: MarkConfig, private readonly deps: MarkDependencies) {\r\n this.validateConfig(config);\r\n this.config = {\r\n endpoint: config.endpoint ?? DEFAULT_ENDPOINT,\r\n ...config,\r\n include_page_context: config.include_page_context ?? true,\r\n };\r\n this.consentRequirement = config.require_consent ?? false;\r\n this.siteId = config.site_id;\r\n this.siteHost = config.site_host;\r\n this.sessionTimeoutMs = config.session_timeout_ms ?? 30 * 60 * 1000;\r\n this.queue = new DeliveryQueue(this.deps.transport, {\r\n debug: this.config.debug,\r\n loadPersisted: () => (this.deps.storage.getOutbox?.() as any[]) ?? [],\r\n savePersisted: (items) => this.deps.storage.setOutbox?.(items as unknown[]),\r\n onError: (error, event) => this.config.on_error?.(error, event),\r\n });\r\n this.warnMisconfiguredSiteHost();\r\n this.subscribeTcf();\r\n }\r\n\r\n /**\r\n * Best-effort synchronous drain that dispatches all queued events via\r\n * sendBeacon. Intended for use on page unload (visibilitychange=hidden,\r\n * pagehide) where async fetch may be cancelled by the browser.\r\n */\r\n public drainViaBeacon() {\r\n this.flushBatch();\r\n this.queue.drainViaBeacon();\r\n }\r\n\r\n /**\r\n * Kicks the queue to retry any pending items now. Safe to call repeatedly;\r\n * used by the browser wrapper in response to `online` events or periodic\r\n * timers.\r\n */\r\n public kickQueue() {\r\n void this.queue.flush();\r\n }\r\n\r\n public track(eventName: string, data: TrackEventData = {}) {\r\n return this.trackInternal(eventName, data, false);\r\n }\r\n\r\n private trackInternal(\r\n eventName: string,\r\n data: TrackEventData = {},\r\n isConversion = false,\r\n options?: { preferBeacon?: boolean }\r\n ) {\r\n if (!eventName) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] track called without event name');\r\n }\r\n return false;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Tracking blocked due to consent requirement.');\r\n }\r\n return false;\r\n }\r\n\r\n if (!isConversion && !this.shouldSampleTrack()) {\r\n return true;\r\n }\r\n\r\n const sanitizedData = this.sanitizeTrackData(data);\r\n const restData: TrackEventData = { ...sanitizedData };\r\n if ('query' in restData) {\r\n delete restData.query;\r\n }\r\n if ('site_id' in restData) {\r\n delete restData.site_id;\r\n }\r\n if ('site_host' in restData) {\r\n delete restData.site_host;\r\n }\r\n const payload: Record<string, unknown> = {\r\n event_name: eventName,\r\n message_id: this.createMessageId(),\r\n ...this.getIdentityFields(sanitizedData),\r\n ...restData,\r\n };\r\n if (isConversion) {\r\n payload.is_conversion = true;\r\n }\r\n\r\n // Add site_id and site_host if configured (event-level overrides init-level)\r\n const siteId = sanitizedData.site_id ?? this.siteId;\r\n const siteHost = sanitizedData.site_host ?? this.siteHost;\r\n\r\n if (siteId) {\r\n payload.site_id = siteId;\r\n }\r\n if (siteHost) {\r\n payload.site_host = siteHost;\r\n }\r\n\r\n if (this.config.include_page_context && typeof window !== 'undefined') {\r\n this.applyPageContext(payload);\r\n // If site_host wasn't explicitly set, use the host from page context\r\n if (!siteHost && payload.site) {\r\n payload.site_host = payload.site;\r\n }\r\n }\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) {\r\n return true;\r\n }\r\n\r\n this.ensureSession();\r\n if (this.config.batching?.enabled && !isConversion && !options?.preferBeacon) {\r\n this.enqueueBatch(transformed);\r\n return true;\r\n }\r\n this.queue.enqueue('/event', { ...transformed, __prefer_beacon: options?.preferBeacon === true });\r\n return true;\r\n }\r\n\r\n public identify(userId: string, traits: IdentifyTraits = {}) {\r\n if (!userId) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] identify called without userId');\r\n }\r\n return;\r\n }\r\n\r\n if (!this.hasConsent() || this.isDntBlocked()) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] Identify blocked due to consent requirement.');\r\n }\r\n return;\r\n }\r\n\r\n this.deps.storage.update({ user_id: userId });\r\n\r\n const payload: Record<string, unknown> = {\r\n user_id: userId,\r\n message_id: this.createMessageId(),\r\n ...this.sanitizeIdentifyTraits(traits),\r\n ...this.getIdentityFields(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n this.queue.enqueue('/identify', transformed);\r\n }\r\n\r\n public conversion(eventName: string, data: ConversionProperties = {}) {\r\n return this.trackInternal(eventName, data, true);\r\n }\r\n\r\n public trackWithOptions(eventName: string, data: TrackEventData = {}, options?: { preferBeacon?: boolean }) {\r\n return this.trackInternal(eventName, data, false, options);\r\n }\r\n\r\n /**\r\n * Returns the current visitor ID from storage, if any.\r\n * Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.\r\n */\r\n public getVisitorId(): string | undefined {\r\n return this.deps.storage.getVisitorId();\r\n }\r\n\r\n public setConsent(status: ConsentStatus) {\r\n const previous = this.deps.storage.getConsentStatus();\r\n this.deps.storage.setConsentStatus(status);\r\n if (status === 'denied') {\r\n this.deps.storage.clearAttribution?.();\r\n this.deps.storage.clearCookieVisitorId?.();\r\n } else if (\r\n status === 'granted' &&\r\n previous === 'denied' &&\r\n this.config.rotate_visitor_on_consent_change\r\n ) {\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n const payload: Record<string, unknown> = {\r\n visitor_id: this.deps.storage.getVisitorId(),\r\n consent_state: status,\r\n source: 'sdk',\r\n message_id: this.createMessageId(),\r\n };\r\n\r\n // Add site_id and site_host if configured\r\n if (this.siteId) {\r\n payload.site_id = this.siteId;\r\n }\r\n if (this.siteHost) {\r\n payload.site_host = this.siteHost;\r\n }\r\n\r\n const transformed = this.config.before_send ? this.config.before_send(payload) : payload;\r\n if (!transformed) return;\r\n this.queue.enqueue('/consent', transformed);\r\n }\r\n\r\n public reset() {\r\n this.deps.storage.update({\r\n user_id: undefined,\r\n last_click_id: undefined,\r\n campaign_id: undefined,\r\n query_params: undefined,\r\n session_id: undefined,\r\n session_started_at: undefined,\r\n last_activity_at: undefined,\r\n });\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n\r\n public flush() {\r\n this.flushBatch();\r\n return this.queue.flush();\r\n }\r\n\r\n public getStats() {\r\n return this.queue.getStats();\r\n }\r\n\r\n private getIdentityFields(data?: TrackEventData) {\r\n const visitorId = data?.visitor_id ?? this.deps.storage.getVisitorId();\r\n const userId = data?.user_id ?? this.deps.storage.getUserId?.();\r\n const clickId = data?.click_id ?? this.deps.storage.getLastClickId();\r\n const campaignId = data?.campaign_id ?? this.deps.storage.getCampaignId();\r\n const sessionId = data?.session_id ?? this.deps.storage.getSessionId?.();\r\n\r\n // Auto-capture query params from storage or event data\r\n const storedQuery = this.deps.storage.getQueryParams() ?? {};\r\n const inputQuery = (data?.query as Record<string, string> | undefined) ?? {};\r\n\r\n // Merge: stored values serve as defaults, input values override/append\r\n const query = { ...storedQuery, ...inputQuery };\r\n\r\n const identity: Record<string, unknown> = {};\r\n\r\n if (visitorId) identity.visitor_id = visitorId;\r\n if (userId) identity.user_id = userId;\r\n if (clickId) identity.click_id = clickId;\r\n if (campaignId) identity.campaign_id = campaignId;\r\n if (sessionId) identity.session_id = sessionId;\r\n\r\n if (Object.keys(query).length > 0) {\r\n identity.query = query;\r\n }\r\n\r\n return identity;\r\n }\r\n\r\n private hasConsent() {\r\n if (this.config.consent_source?.type === 'tcf' && typeof window !== 'undefined') {\r\n if (!this.tcfCachedAllowed) {\r\n return false;\r\n }\r\n }\r\n if (!this.consentRequirement) {\r\n return true;\r\n }\r\n\r\n const stored = this.deps.storage.getConsentStatus();\r\n const state = stored;\r\n\r\n if (this.consentRequirement === 'auto') {\r\n // In auto mode, default to deny until consent is explicitly granted.\r\n return state === 'granted';\r\n }\r\n\r\n return state === 'granted';\r\n }\r\n\r\n private sanitizeTrackData(data: TrackEventData) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(data)) {\r\n if (TRACK_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as TrackEventData;\r\n }\r\n\r\n private sanitizeIdentifyTraits(traits: IdentifyTraits) {\r\n const sanitized: Record<string, JsonValue | undefined> = {};\r\n for (const [key, value] of Object.entries(traits)) {\r\n if (IDENTIFY_RESERVED_FIELDS.has(key)) {\r\n continue;\r\n }\r\n sanitized[key] = value;\r\n }\r\n return sanitized as IdentifyTraits;\r\n }\r\n\r\n private validateConfig(config: MarkConfig) {\r\n if (!config.key || !config.key.trim()) {\r\n throw new Error('[Mark] `key` must be a non-empty string.');\r\n }\r\n if (config.endpoint) {\r\n try {\r\n // eslint-disable-next-line no-new\r\n new URL(config.endpoint);\r\n } catch {\r\n throw new Error('[Mark] `endpoint` must be a valid absolute URL.');\r\n }\r\n }\r\n if (typeof config.site_id === 'string' && !config.site_id.trim()) {\r\n throw new Error('[Mark] `site_id` cannot be an empty string.');\r\n }\r\n if (typeof config.site_host === 'string' && !config.site_host.trim()) {\r\n throw new Error('[Mark] `site_host` cannot be an empty string.');\r\n }\r\n }\r\n\r\n private applyPageContext(payload: Record<string, unknown>) {\r\n if (typeof document === 'undefined') {\r\n return;\r\n }\r\n if (!payload.site) payload.site = window.location.host;\r\n if (!payload.page) payload.page = window.location.pathname;\r\n if (!payload.title) payload.title = document.title;\r\n if (!payload.referrer && document.referrer) {\r\n payload.referrer = this.scrubReferrer(document.referrer);\r\n }\r\n // full URL omitted for privacy; include only if explicitly provided by the caller\r\n }\r\n\r\n private enqueueBatch(event: Record<string, unknown>) {\r\n this.batchedEvents.push(event);\r\n const maxSize = this.config.batching?.max_size ?? 20;\r\n if (this.batchedEvents.length >= maxSize) {\r\n this.flushBatch();\r\n return;\r\n }\r\n if (!this.batchTimer) {\r\n const intervalMs = this.config.batching?.flush_interval_ms ?? 2000;\r\n this.batchTimer = setTimeout(() => {\r\n this.batchTimer = null;\r\n this.flushBatch();\r\n }, intervalMs);\r\n }\r\n }\r\n\r\n private flushBatch() {\r\n if (this.batchedEvents.length === 0) return;\r\n const path = this.config.batching?.endpoint_path ?? '/events';\r\n const payload = this.batchedEvents.splice(0, this.batchedEvents.length);\r\n this.queue.enqueue(path, { events: payload, message_id: this.createMessageId() });\r\n }\r\n\r\n private createMessageId() {\r\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\r\n return crypto.randomUUID();\r\n }\r\n return `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`;\r\n }\r\n\r\n private ensureSession() {\r\n const now = Date.now();\r\n const currentId = this.deps.storage.getSessionId?.();\r\n const lastActivityIso = this.deps.storage.getLastActivityAt?.();\r\n const lastActivity = lastActivityIso ? Date.parse(lastActivityIso) : 0;\r\n const shouldRotate =\r\n !currentId ||\r\n !lastActivity ||\r\n now - lastActivity >= this.sessionTimeoutMs ||\r\n this.crossedUtcDay(lastActivity, now);\r\n if (shouldRotate) {\r\n const newSessionId = this.createMessageId();\r\n const nowIso = new Date(now).toISOString();\r\n this.deps.storage.update({\r\n session_id: newSessionId,\r\n session_started_at: nowIso,\r\n last_activity_at: nowIso,\r\n });\r\n return;\r\n }\r\n this.deps.storage.update({ last_activity_at: new Date(now).toISOString() });\r\n }\r\n\r\n private crossedUtcDay(previousTs: number, currentTs: number) {\r\n const prev = new Date(previousTs);\r\n const next = new Date(currentTs);\r\n return (\r\n prev.getUTCFullYear() !== next.getUTCFullYear() ||\r\n prev.getUTCMonth() !== next.getUTCMonth() ||\r\n prev.getUTCDate() !== next.getUTCDate()\r\n );\r\n }\r\n\r\n private shouldSampleTrack() {\r\n if (typeof this.config.sample_rate !== 'number') return true;\r\n if (this.config.sample_rate <= 0) return false;\r\n if (this.config.sample_rate >= 1) return true;\r\n return Math.random() <= this.config.sample_rate;\r\n }\r\n\r\n private isDntBlocked() {\r\n if (!this.config.honor_dnt || typeof navigator === 'undefined') {\r\n return false;\r\n }\r\n const dnt = navigator.doNotTrack;\r\n const gpc = (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl;\r\n return dnt === '1' || gpc === true;\r\n }\r\n\r\n private scrubReferrer(referrer: string) {\r\n try {\r\n const refUrl = new URL(referrer);\r\n if (typeof window === 'undefined') return referrer;\r\n const currentUrl = new URL(window.location.href);\r\n if (refUrl.origin !== currentUrl.origin) {\r\n refUrl.search = '';\r\n return refUrl.toString();\r\n }\r\n return referrer;\r\n } catch {\r\n return referrer;\r\n }\r\n }\r\n\r\n private warnMisconfiguredSiteHost() {\r\n if (!this.config.debug || !this.siteHost || typeof window === 'undefined') {\r\n return;\r\n }\r\n if (window.location.host !== this.siteHost) {\r\n console.warn('[Mark] config.site_host does not match current host', {\r\n expected: this.siteHost,\r\n actual: window.location.host,\r\n });\r\n }\r\n if (this.siteId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(this.siteId)) {\r\n console.warn('[Mark] config.site_id does not look like UUID v4', this.siteId);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribes to the IAB TCF v2 CMP via `__tcfapi('addEventListener', ...)`.\r\n * Result is cached in `tcfCachedAllowed` so the synchronous `hasConsent()`\r\n * path does not need to await the CMP. If the CMP is not yet present, we\r\n * poll briefly (CMPs commonly load asynchronously) and give up after ~2s.\r\n */\r\n private subscribeTcf() {\r\n const source = this.config.consent_source;\r\n if (source?.type !== 'tcf' || typeof window === 'undefined') return;\r\n const purposes = source.purposes;\r\n\r\n const attempt = (retries: number) => {\r\n try {\r\n const w = window as Window & {\r\n __tcfapi?: (\r\n command: string,\r\n version: number,\r\n callback: (\r\n tcData?: {\r\n eventStatus?: string;\r\n gdprApplies?: boolean;\r\n listenerId?: number;\r\n purpose?: { consents?: Record<string, boolean> };\r\n },\r\n ok?: boolean\r\n ) => void,\r\n parameter?: number\r\n ) => void;\r\n };\r\n if (typeof w.__tcfapi !== 'function') {\r\n if (retries > 0) {\r\n setTimeout(() => attempt(retries - 1), 200);\r\n }\r\n return;\r\n }\r\n w.__tcfapi('addEventListener', 2, (tcData, ok) => {\r\n if (!ok || !tcData) {\r\n this.tcfCachedAllowed = false;\r\n return;\r\n }\r\n if (tcData.gdprApplies === false) {\r\n this.tcfCachedAllowed = true;\r\n return;\r\n }\r\n const consents = tcData.purpose?.consents ?? {};\r\n this.tcfCachedAllowed = purposes.every((p) => consents[String(p)] === true);\r\n });\r\n } catch {\r\n this.tcfCachedAllowed = false;\r\n }\r\n };\r\n\r\n attempt(10);\r\n }\r\n}\r\n\r\n","import type { MarkConfig } from '../types';\r\nimport { DEFAULT_ENDPOINT } from './constants';\r\nimport type { TransportAdapter } from './adapters';\r\nimport { TransportError, parseRetryAfter } from './TransportError';\r\n\r\nexport class HttpTransport implements TransportAdapter {\r\n private readonly config: MarkConfig;\r\n private readonly endpoint: string;\r\n private readonly pending = new Set<Promise<void>>();\r\n\r\n constructor(config: MarkConfig) {\r\n this.config = config;\r\n this.endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\r\n }\r\n\r\n public async send(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const request = this.sendInternal(path, data, options);\r\n this.pending.add(request);\r\n try {\r\n await request;\r\n } finally {\r\n this.pending.delete(request);\r\n }\r\n }\r\n\r\n public async flush() {\r\n if (this.pending.size === 0) {\r\n return;\r\n }\r\n await Promise.allSettled(Array.from(this.pending));\r\n }\r\n\r\n private async sendInternal(path: string, data: unknown, options?: { preferBeacon?: boolean }) {\r\n const url = this.joinUrl(this.endpoint, path);\r\n const key = this.config.key;\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n [key.startsWith('sk_') ? 'x-secret-key' : 'x-publishable-key']: key,\r\n };\r\n\r\n if (this.config.debug) {\r\n console.log('[Mark] Sending', url, data);\r\n }\r\n\r\n const body = JSON.stringify(data);\r\n if (options?.preferBeacon && typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {\r\n const blob = new Blob([body], { type: 'application/json' });\r\n const sent = navigator.sendBeacon(url, blob);\r\n if (sent) {\r\n return;\r\n }\r\n }\r\n\r\n if (typeof fetch !== 'function') {\r\n if (this.config.debug) {\r\n console.error('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n throw new TransportError('[Mark] Global fetch is not available in this runtime.');\r\n }\r\n\r\n const timeoutMs = this.config.request_timeout_ms ?? 10000;\r\n const controller = new AbortController();\r\n let timedOut = false;\r\n const timer = setTimeout(() => {\r\n timedOut = true;\r\n controller.abort();\r\n }, timeoutMs);\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers,\r\n body,\r\n keepalive: true,\r\n signal: controller.signal,\r\n });\r\n if (!response.ok) {\r\n const snippet = await this.readErrorSnippet(response);\r\n const retryAfterMs = parseRetryAfter(response.headers.get('Retry-After'));\r\n if (this.config.debug) {\r\n console.error('[Mark] Request rejected', {\r\n url,\r\n status: response.status,\r\n statusText: response.statusText,\r\n body: snippet,\r\n retryAfterMs,\r\n });\r\n }\r\n throw new TransportError(\r\n `[Mark] Request rejected with status ${response.status}: ${snippet}`,\r\n { status: response.status, retryAfterMs }\r\n );\r\n }\r\n } catch (error) {\r\n if (this.config.debug) {\r\n console.error('[Mark] Failed to send', url, error);\r\n }\r\n if (error instanceof TransportError) {\r\n throw error;\r\n }\r\n if (timedOut) {\r\n throw new TransportError(`[Mark] Request timed out after ${timeoutMs}ms`, { status: 408 });\r\n }\r\n const message = error instanceof Error ? error.message : String(error);\r\n throw new TransportError(`[Mark] Network error: ${message}`);\r\n } finally {\r\n clearTimeout(timer);\r\n }\r\n }\r\n\r\n private joinUrl(endpoint: string, path: string) {\r\n const normalizedEndpoint = endpoint.replace(/\\/+$/, '');\r\n const normalizedPath = path.replace(/^\\/+/, '');\r\n return `${normalizedEndpoint}/${normalizedPath}`;\r\n }\r\n\r\n private async readErrorSnippet(response: Response) {\r\n try {\r\n const text = await response.text();\r\n return text.slice(0, 300);\r\n } catch {\r\n return '';\r\n }\r\n }\r\n}\r\n","import type { ConsentStatus, StorageData } from '../types';\nimport type { StorageAdapter } from '../core/adapters';\n\n/**\n * Stateless storage adapter used by default in server runtimes.\n * It simply exposes optional defaults and ignores updates.\n */\nexport class StatelessStorage implements StorageAdapter {\n constructor(private defaults: StorageData = {}) { }\n\n public getVisitorId() {\n return this.defaults.visitor_id;\n }\n\n public getLastClickId() {\n return this.defaults.last_click_id;\n }\n\n public getCampaignId() {\n return this.defaults.campaign_id;\n }\n\n public getQueryParams() {\n return this.defaults.query_params;\n }\n\n public getUserId() {\n return this.defaults.user_id;\n }\n\n public getSessionId() {\n return this.defaults.session_id;\n }\n\n public getSessionStartedAt() {\n return this.defaults.session_started_at;\n }\n\n public getLastActivityAt() {\n return this.defaults.last_activity_at;\n }\n\n public getConsentStatus(): ConsentStatus | undefined {\n return this.defaults.consent_status as ConsentStatus | undefined;\n }\n\n public update(): void {\n // Server runtimes should provide identifiers explicitly.\n }\n\n public setConsentStatus(): void {\n // Stateless; no-op.\n }\n\n public clearAttribution(): void {\n // Stateless; no-op.\n }\n\n public clearCookieVisitorId(): void {\n // Stateless; no-op.\n }\n\n public rotateVisitorId(): void {\n // Stateless; no-op.\n }\n}\n\n","import type {\n MarkConfig,\n TrackEventData,\n IdentifyTraits,\n StorageData,\n ConsentStatus,\n} from '../types';\nimport { MarkCore } from '../core/MarkCore';\nimport { HttpTransport } from '../core/HttpTransport';\nimport type { StorageAdapter, TransportAdapter } from '../core/adapters';\nimport { StatelessStorage } from './StatelessStorage';\n\nexport interface NodeMarkOptions {\n storage?: StorageAdapter;\n transport?: TransportAdapter;\n storageDefaults?: StorageData;\n}\n\nexport class NodeMark {\n constructor(private readonly client: MarkCore) {}\n\n public track(eventName: string, data: TrackEventData = {}) {\n this.client.track(eventName, data);\n }\n\n public conversion(eventName: string, data: TrackEventData = {}) {\n this.client.conversion(eventName, data);\n }\n\n public identify(userId: string, traits: IdentifyTraits = {}) {\n this.client.identify(userId, traits);\n }\n\n public setConsent(status: ConsentStatus) {\n this.client.setConsent(status);\n }\n\n public flush() {\n return this.client.flush();\n }\n\n public reset() {\n this.client.reset();\n }\n\n public getStats() {\n return this.client.getStats();\n }\n\n /**\n * Returns the visitor ID from the configured storage, if any.\n * With default StatelessStorage, this is the value passed via `storageDefaults.visitor_id` when creating the client.\n * Use it to associate server-side events with the same visitor dimension as browser events.\n */\n public getVisitorId(): string | undefined {\n return this.client.getVisitorId();\n }\n}\n\nexport const createNodeMark = (config: MarkConfig, options: NodeMarkOptions = {}) => {\n const storage =\n options.storage ??\n new StatelessStorage({\n visitor_id: options.storageDefaults?.visitor_id,\n user_id: options.storageDefaults?.user_id,\n session_id: options.storageDefaults?.session_id,\n session_started_at: options.storageDefaults?.session_started_at,\n last_activity_at: options.storageDefaults?.last_activity_at,\n last_click_id: options.storageDefaults?.last_click_id,\n campaign_id: options.storageDefaults?.campaign_id,\n query_params: options.storageDefaults?.query_params,\n consent_status: options.storageDefaults?.consent_status,\n });\n\n const transport = options.transport ?? new HttpTransport(config);\n const client = new MarkCore(config, { storage, transport });\n\n return new NodeMark(client);\n};\n\nexport { StatelessStorage };\n\n"],"names":["DEFAULT_ENDPOINT","TransportError","message","options","isNonRetriableStatus","status","parseRetryAfter","headerValue","trimmed","numeric","dateMs","diff","DEFAULT_MAX_ATTEMPTS","DEFAULT_BASE_BACKOFF_MS","DEFAULT_MAX_BACKOFF_MS","DEFAULT_MAX_ITEM_AGE_MS","DeliveryQueue","transport","persisted","now","item","enqueuedAt","path","data","items","eventData","cutoff","removed","force","preferBeacon","error","retryAfterMs","delay","jitter","TRACK_RESERVED_FIELDS","IDENTIFY_RESERVED_FIELDS","MarkCore","config","deps","event","eventName","isConversion","sanitizedData","restData","payload","siteId","siteHost","transformed","userId","traits","previous","visitorId","clickId","campaignId","sessionId","storedQuery","inputQuery","query","identity","state","sanitized","key","value","maxSize","intervalMs","currentId","lastActivityIso","lastActivity","newSessionId","nowIso","previousTs","currentTs","prev","next","dnt","gpc","referrer","refUrl","currentUrl","source","purposes","attempt","retries","w","tcData","ok","consents","p","HttpTransport","request","url","headers","body","blob","timeoutMs","controller","timedOut","timer","response","snippet","endpoint","normalizedEndpoint","normalizedPath","StatelessStorage","defaults","NodeMark","client","createNodeMark","storage"],"mappings":"AAAO,MAAMA,IAAmB;ACAzB,MAAMC,UAAuB,MAAM;AAAA,EACxB;AAAA,EACA;AAAA,EAEhB,YAAYC,GAAiBC,IAAsD,IAAI;AACrF,UAAMD,CAAO,GACb,KAAK,OAAO,kBACZ,KAAK,SAASC,EAAQ,QACtB,KAAK,eAAeA,EAAQ;AAAA,EAC9B;AACF;AAOO,SAASC,EAAqBC,GAAqC;AAGxE,SAFI,SAAOA,KAAW,YAClBA,IAAS,OAAOA,KAAU,OAC1BA,MAAW,OAAOA,MAAW;AAEnC;AAEO,SAASC,EAAgBC,GAA4D;AAC1F,MAAI,CAACA,EAAa;AAClB,QAAMC,IAAUD,EAAY,KAAA;AAC5B,MAAI,CAACC,EAAS;AACd,QAAMC,IAAU,OAAOD,CAAO;AAC9B,MAAI,OAAO,SAASC,CAAO,KAAKA,KAAW;AACzC,WAAO,KAAK,MAAMA,IAAU,GAAI;AAElC,QAAMC,IAAS,KAAK,MAAMF,CAAO;AACjC,MAAI,OAAO,SAASE,CAAM,GAAG;AAC3B,UAAMC,IAAOD,IAAS,KAAK,IAAA;AAC3B,WAAOC,IAAO,IAAIA,IAAO;AAAA,EAC3B;AAEF;ACZA,MAAMC,IAAuB,GACvBC,IAA0B,KAC1BC,IAAyB,MACzBC,IAA0B,OAAU,KAAK;AAExC,MAAMC,EAAc;AAAA,EAezB,YAA6BC,GAA6Bd,IAAgC,IAAI;AAAjE,SAAA,YAAAc,GAC3B,KAAK,cAAcd,EAAQ,eAAeS,GAC1C,KAAK,gBAAgBT,EAAQ,iBAAiBU,GAC9C,KAAK,eAAeV,EAAQ,gBAAgBW,GAC5C,KAAK,eAAeX,EAAQ,gBAAgBY,GAC5C,KAAK,QAAQZ,EAAQ,SAAS,IAC9B,KAAK,gBAAgBA,EAAQ,eAC7B,KAAK,gBAAgBA,EAAQ,eAC7B,KAAK,UAAUA,EAAQ;AACvB,UAAMe,IAAY,KAAK,gBAAA,KAAqB,CAAA;AAC5C,QAAIA,EAAU,SAAS,GAAG;AACxB,YAAMC,IAAM,KAAK,IAAA;AACjB,iBAAWC,KAAQF,GAAW;AAC5B,cAAMG,IAAaD,EAAK,cAAcD;AACtC,YAAIA,IAAME,IAAa,KAAK,cAAc;AACxC,eAAK,WAAW;AAChB;AAAA,QACF;AACA,aAAK,MAAM,KAAK,EAAE,GAAGD,GAAM,YAAAC,GAAY;AAAA,MACzC;AACA,WAAK,QAAA;AAAA,IACP;AAAA,EACF;AAAA,EApCiB,QAAqB,CAAA;AAAA,EAC9B,WAAW;AAAA,EACX,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EA0BV,QAAQC,GAAcC,GAA+B;AAC1D,SAAK,MAAM,KAAK;AAAA,MACd,MAAAD;AAAA,MACA,MAAAC;AAAA,MACA,UAAU;AAAA,MACV,eAAe,KAAK,IAAA;AAAA,MACpB,YAAY,KAAK,IAAA;AAAA,IAAI,CACtB,GACD,KAAK,QAAA,GACA,KAAK,QAAA;AAAA,EACZ;AAAA,EAEA,MAAa,QAAQ;AACnB,UAAM,KAAK,QAAQ,EAAI,GACvB,MAAM,KAAK,UAAU,QAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,iBAAiB;AACtB,QAAI,KAAK,MAAM,WAAW,EAAG;AAC7B,UAAMC,IAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,MAAM,MAAM;AACpD,SAAK,QAAA;AACL,eAAWJ,KAAQI,GAAO;AACxB,YAAMC,IAAY,EAAE,GAAGL,EAAK,KAAA;AAC5B,aAAOK,EAAU;AACjB,UAAI;AACF,QAAK,KAAK,UAAU,KAAKL,EAAK,MAAMK,GAAW,EAAE,cAAc,IAAM;AAAA,MACvE,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA,EAEO,WAAW;AAChB,WAAO;AAAA,MACL,QAAQ,KAAK,MAAM;AAAA,MACnB,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK;AAAA,IAAA;AAAA,EAElB;AAAA,EAEO,QAAQ;AACb,SAAK,MAAM,OAAO,GAAG,KAAK,MAAM,MAAM,GACtC,KAAK,QAAA;AAAA,EACP;AAAA,EAEQ,UAAU;AAChB,SAAK,gBAAgB,KAAK,KAAK;AAAA,EACjC;AAAA,EAEQ,eAAe;AACrB,QAAI,KAAK,MAAM,WAAW,EAAG;AAE7B,UAAMC,IADM,KAAK,IAAA,IACI,KAAK;AAC1B,QAAIC,IAAU;AACd,aAAS,IAAI,KAAK,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;AAC/C,MAAI,KAAK,MAAM,CAAC,EAAE,cAAcD,MAC9B,KAAK,MAAM,OAAO,GAAG,CAAC,GACtB,KAAK,WAAW,GAChBC,KAAW;AAGf,IAAIA,IAAU,KACZ,KAAK,QAAA;AAAA,EAET;AAAA,EAEA,MAAc,QAAQC,IAAQ,IAAsB;AAClD,QAAI,MAAK,UAGT;AAAA,WAAK,WAAW;AAChB,UAAI;AAEF,aADA,KAAK,aAAA,GACE,KAAK,MAAM,SAAS,KAAG;AAC5B,gBAAMR,IAAO,KAAK,MAAM,CAAC;AACzB,cAAI,CAACQ,KAASR,EAAK,gBAAgB,KAAK;AACtC;AAEF,cAAI;AACF,kBAAMK,IAAY,EAAE,GAAGL,EAAK,KAAA,GACtBS,IAAeJ,EAAU,oBAAoB;AACnD,mBAAOA,EAAU,iBACjB,MAAM,KAAK,UAAU,KAAKL,EAAK,MAAMK,GAAW,EAAE,cAAAI,GAAc,GAChE,KAAK,MAAM,MAAA,GACX,KAAK,QAAQ,GACb,KAAK,QAAA;AAAA,UACP,SAASC,GAAO;AACd,iBAAK,UAAU,GACf,KAAK,UAAUA,GAAOV,EAAK,IAAI;AAE/B,kBAAMf,IAASyB,aAAiB7B,IAAiB6B,EAAM,SAAS;AAChE,gBAAI1B,EAAqBC,CAAM,GAAG;AAChC,mBAAK,MAAM,MAAA,GACX,KAAK,WAAW,GAChB,KAAK,QAAA,GACD,KAAK,SACP,QAAQ,MAAM,oDAAoDA,GAAQe,EAAK,IAAI;AAErF;AAAA,YACF;AAGA,gBADAA,EAAK,YAAY,GACbA,EAAK,YAAY,KAAK,aAAa;AACrC,mBAAK,MAAM,MAAA,GACX,KAAK,WAAW,GAChB,KAAK,QAAA,GACD,KAAK,SACP,QAAQ,MAAM,2CAA2CA,EAAK,MAAMU,CAAK;AAE3E;AAAA,YACF;AACA,kBAAMC,IAAeD,aAAiB7B,IAAiB6B,EAAM,eAAe;AAC5E,gBAAIE;AACJ,gBAAI,OAAOD,KAAiB;AAC1B,cAAAC,IAAQ,KAAK,IAAI,KAAK,cAAc,KAAK,IAAI,GAAGD,CAAY,CAAC;AAAA,iBACxD;AACL,oBAAME,IAAS,KAAK,OAAA,IAAW,KAAK;AACpC,cAAAD,IAAQ,KAAK;AAAA,gBACX,KAAK;AAAA,gBACL,KAAK,gBAAgB,MAAMZ,EAAK,WAAW,KAAKa;AAAA,cAAA;AAAA,YAEpD;AACA,YAAAb,EAAK,gBAAgB,KAAK,IAAA,IAAQY,GAClC,KAAK,QAAA;AACL;AAAA,UACF;AAAA,QACF;AAAA,MACF,UAAA;AACE,aAAK,WAAW;AAAA,MAClB;AAAA;AAAA,EACF;AACF;ACjMA,MAAME,wBAA4B,IAAI,CAAC,cAAc,WAAW,iBAAiB,UAAU,eAAe,CAAC,GACrGC,wBAA+B,IAAI;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAMC,EAAS;AAAA,EAWpB,YAAYC,GAAqCC,GAAwB;AAAxB,SAAA,OAAAA,GAC/C,KAAK,eAAeD,CAAM,GAC1B,KAAK,SAAS;AAAA,MACZ,UAAUA,EAAO,YAAYrC;AAAA,MAC7B,GAAGqC;AAAA,MACH,sBAAsBA,EAAO,wBAAwB;AAAA,IAAA,GAEvD,KAAK,qBAAqBA,EAAO,mBAAmB,IACpD,KAAK,SAASA,EAAO,SACrB,KAAK,WAAWA,EAAO,WACvB,KAAK,mBAAmBA,EAAO,sBAAsB,OAAU,KAC/D,KAAK,QAAQ,IAAIrB,EAAc,KAAK,KAAK,WAAW;AAAA,MAClD,OAAO,KAAK,OAAO;AAAA,MACnB,eAAe,MAAO,KAAK,KAAK,QAAQ,YAAA,KAA2B,CAAA;AAAA,MACnE,eAAe,CAACQ,MAAU,KAAK,KAAK,QAAQ,YAAYA,CAAkB;AAAA,MAC1E,SAAS,CAACM,GAAOS,MAAU,KAAK,OAAO,WAAWT,GAAOS,CAAK;AAAA,IAAA,CAC/D,GACD,KAAK,0BAAA,GACL,KAAK,aAAA;AAAA,EACP;AAAA,EA7BQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACS;AAAA,EACA;AAAA,EACT,aAAmD;AAAA,EAC1C,gBAAgD,CAAA;AAAA,EACzD,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BpB,iBAAiB;AACtB,SAAK,WAAA,GACL,KAAK,MAAM,eAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,YAAY;AACjB,IAAK,KAAK,MAAM,MAAA;AAAA,EAClB;AAAA,EAEO,MAAMC,GAAmBjB,IAAuB,IAAI;AACzD,WAAO,KAAK,cAAciB,GAAWjB,GAAM,EAAK;AAAA,EAClD;AAAA,EAEQ,cACNiB,GACAjB,IAAuB,CAAA,GACvBkB,IAAe,IACftC,GACA;AACA,QAAI,CAACqC;AACH,aAAI,KAAK,OAAO,SACd,QAAQ,KAAK,wCAAwC,GAEhD;AAGT,QAAI,CAAC,KAAK,WAAA,KAAgB,KAAK;AAC7B,aAAI,KAAK,OAAO,SACd,QAAQ,KAAK,qDAAqD,GAE7D;AAGT,QAAI,CAACC,KAAgB,CAAC,KAAK;AACzB,aAAO;AAGT,UAAMC,IAAgB,KAAK,kBAAkBnB,CAAI,GAC3CoB,IAA2B,EAAE,GAAGD,EAAA;AACtC,IAAI,WAAWC,KACb,OAAOA,EAAS,OAEd,aAAaA,KACf,OAAOA,EAAS,SAEd,eAAeA,KACjB,OAAOA,EAAS;AAElB,UAAMC,IAAmC;AAAA,MACvC,YAAYJ;AAAA,MACZ,YAAY,KAAK,gBAAA;AAAA,MACjB,GAAG,KAAK,kBAAkBE,CAAa;AAAA,MACvC,GAAGC;AAAA,IAAA;AAEL,IAAIF,MACFG,EAAQ,gBAAgB;AAI1B,UAAMC,IAASH,EAAc,WAAW,KAAK,QACvCI,IAAWJ,EAAc,aAAa,KAAK;AAEjD,IAAIG,MACFD,EAAQ,UAAUC,IAEhBC,MACFF,EAAQ,YAAYE,IAGlB,KAAK,OAAO,wBAAwB,OAAO,SAAW,QACxD,KAAK,iBAAiBF,CAAO,GAEzB,CAACE,KAAYF,EAAQ,SACvBA,EAAQ,YAAYA,EAAQ;AAIhC,UAAMG,IAAc,KAAK,OAAO,cAAc,KAAK,OAAO,YAAYH,CAAO,IAAIA;AACjF,WAAKG,KAIL,KAAK,cAAA,GACD,KAAK,OAAO,UAAU,WAAW,CAACN,KAAgB,CAACtC,GAAS,gBAC9D,KAAK,aAAa4C,CAAW,GACtB,OAET,KAAK,MAAM,QAAQ,UAAU,EAAE,GAAGA,GAAa,iBAAiB5C,GAAS,iBAAiB,IAAM,GACzF,OATE;AAAA,EAUX;AAAA,EAEO,SAAS6C,GAAgBC,IAAyB,IAAI;AAC3D,QAAI,CAACD,GAAQ;AACX,MAAI,KAAK,OAAO,SACd,QAAQ,KAAK,uCAAuC;AAEtD;AAAA,IACF;AAEA,QAAI,CAAC,KAAK,WAAA,KAAgB,KAAK,gBAAgB;AAC7C,MAAI,KAAK,OAAO,SACd,QAAQ,KAAK,qDAAqD;AAEpE;AAAA,IACF;AAEA,SAAK,KAAK,QAAQ,OAAO,EAAE,SAASA,GAAQ;AAE5C,UAAMJ,IAAmC;AAAA,MACvC,SAASI;AAAA,MACT,YAAY,KAAK,gBAAA;AAAA,MACjB,GAAG,KAAK,uBAAuBC,CAAM;AAAA,MACrC,GAAG,KAAK,kBAAA;AAAA,IAAkB;AAI5B,IAAI,KAAK,WACPL,EAAQ,UAAU,KAAK,SAErB,KAAK,aACPA,EAAQ,YAAY,KAAK;AAG3B,UAAMG,IAAc,KAAK,OAAO,cAAc,KAAK,OAAO,YAAYH,CAAO,IAAIA;AACjF,IAAKG,KACL,KAAK,MAAM,QAAQ,aAAaA,CAAW;AAAA,EAC7C;AAAA,EAEO,WAAWP,GAAmBjB,IAA6B,IAAI;AACpE,WAAO,KAAK,cAAciB,GAAWjB,GAAM,EAAI;AAAA,EACjD;AAAA,EAEO,iBAAiBiB,GAAmBjB,IAAuB,CAAA,GAAIpB,GAAsC;AAC1G,WAAO,KAAK,cAAcqC,GAAWjB,GAAM,IAAOpB,CAAO;AAAA,EAC3D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,eAAmC;AACxC,WAAO,KAAK,KAAK,QAAQ,aAAA;AAAA,EAC3B;AAAA,EAEO,WAAWE,GAAuB;AACvC,UAAM6C,IAAW,KAAK,KAAK,QAAQ,iBAAA;AACnC,SAAK,KAAK,QAAQ,iBAAiB7C,CAAM,GACrCA,MAAW,YACb,KAAK,KAAK,QAAQ,mBAAA,GAClB,KAAK,KAAK,QAAQ,uBAAA,KAElBA,MAAW,aACX6C,MAAa,YACb,KAAK,OAAO,oCAEZ,KAAK,KAAK,QAAQ,kBAAA;AAEpB,UAAMN,IAAmC;AAAA,MACvC,YAAY,KAAK,KAAK,QAAQ,aAAA;AAAA,MAC9B,eAAevC;AAAA,MACf,QAAQ;AAAA,MACR,YAAY,KAAK,gBAAA;AAAA,IAAgB;AAInC,IAAI,KAAK,WACPuC,EAAQ,UAAU,KAAK,SAErB,KAAK,aACPA,EAAQ,YAAY,KAAK;AAG3B,UAAMG,IAAc,KAAK,OAAO,cAAc,KAAK,OAAO,YAAYH,CAAO,IAAIA;AACjF,IAAKG,KACL,KAAK,MAAM,QAAQ,YAAYA,CAAW;AAAA,EAC5C;AAAA,EAEO,QAAQ;AACb,SAAK,KAAK,QAAQ,OAAO;AAAA,MACvB,SAAS;AAAA,MACT,eAAe;AAAA,MACf,aAAa;AAAA,MACb,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,oBAAoB;AAAA,MACpB,kBAAkB;AAAA,IAAA,CACnB,GACD,KAAK,KAAK,QAAQ,kBAAA;AAAA,EACpB;AAAA,EAEO,QAAQ;AACb,gBAAK,WAAA,GACE,KAAK,MAAM,MAAA;AAAA,EACpB;AAAA,EAEO,WAAW;AAChB,WAAO,KAAK,MAAM,SAAA;AAAA,EACpB;AAAA,EAEQ,kBAAkBxB,GAAuB;AAC/C,UAAM4B,IAAY5B,GAAM,cAAc,KAAK,KAAK,QAAQ,aAAA,GAClDyB,IAASzB,GAAM,WAAW,KAAK,KAAK,QAAQ,YAAA,GAC5C6B,IAAU7B,GAAM,YAAY,KAAK,KAAK,QAAQ,eAAA,GAC9C8B,IAAa9B,GAAM,eAAe,KAAK,KAAK,QAAQ,cAAA,GACpD+B,IAAY/B,GAAM,cAAc,KAAK,KAAK,QAAQ,eAAA,GAGlDgC,IAAc,KAAK,KAAK,QAAQ,eAAA,KAAoB,CAAA,GACpDC,IAAcjC,GAAM,SAAgD,CAAA,GAGpEkC,IAAQ,EAAE,GAAGF,GAAa,GAAGC,EAAA,GAE7BE,IAAoC,CAAA;AAE1C,WAAIP,QAAoB,aAAaA,IACjCH,QAAiB,UAAUA,IAC3BI,QAAkB,WAAWA,IAC7BC,QAAqB,cAAcA,IACnCC,QAAoB,aAAaA,IAEjC,OAAO,KAAKG,CAAK,EAAE,SAAS,MAC9BC,EAAS,QAAQD,IAGZC;AAAA,EACT;AAAA,EAEQ,aAAa;AACnB,QAAI,KAAK,OAAO,gBAAgB,SAAS,SAAS,OAAO,SAAW,OAC9D,CAAC,KAAK;AACR,aAAO;AAGX,QAAI,CAAC,KAAK;AACR,aAAO;AAIT,UAAMC,IADS,KAAK,KAAK,QAAQ,iBAAA;AAGjC,WAAI,KAAK,uBAAuB,QAEvBA,MAAU;AAAA,EAIrB;AAAA,EAEQ,kBAAkBpC,GAAsB;AAC9C,UAAMqC,IAAmD,CAAA;AACzD,eAAW,CAACC,GAAKC,CAAK,KAAK,OAAO,QAAQvC,CAAI;AAC5C,MAAIW,EAAsB,IAAI2B,CAAG,MAGjCD,EAAUC,CAAG,IAAIC;AAEnB,WAAOF;AAAA,EACT;AAAA,EAEQ,uBAAuBX,GAAwB;AACrD,UAAMW,IAAmD,CAAA;AACzD,eAAW,CAACC,GAAKC,CAAK,KAAK,OAAO,QAAQb,CAAM;AAC9C,MAAId,EAAyB,IAAI0B,CAAG,MAGpCD,EAAUC,CAAG,IAAIC;AAEnB,WAAOF;AAAA,EACT;AAAA,EAEQ,eAAevB,GAAoB;AACzC,QAAI,CAACA,EAAO,OAAO,CAACA,EAAO,IAAI;AAC7B,YAAM,IAAI,MAAM,0CAA0C;AAE5D,QAAIA,EAAO;AACT,UAAI;AAEF,YAAI,IAAIA,EAAO,QAAQ;AAAA,MACzB,QAAQ;AACN,cAAM,IAAI,MAAM,iDAAiD;AAAA,MACnE;AAEF,QAAI,OAAOA,EAAO,WAAY,YAAY,CAACA,EAAO,QAAQ;AACxD,YAAM,IAAI,MAAM,6CAA6C;AAE/D,QAAI,OAAOA,EAAO,aAAc,YAAY,CAACA,EAAO,UAAU;AAC5D,YAAM,IAAI,MAAM,+CAA+C;AAAA,EAEnE;AAAA,EAEQ,iBAAiBO,GAAkC;AACzD,IAAI,OAAO,WAAa,QAGnBA,EAAQ,SAAMA,EAAQ,OAAO,OAAO,SAAS,OAC7CA,EAAQ,SAAMA,EAAQ,OAAO,OAAO,SAAS,WAC7CA,EAAQ,UAAOA,EAAQ,QAAQ,SAAS,QACzC,CAACA,EAAQ,YAAY,SAAS,aAChCA,EAAQ,WAAW,KAAK,cAAc,SAAS,QAAQ;AAAA,EAG3D;AAAA,EAEQ,aAAaL,GAAgC;AACnD,SAAK,cAAc,KAAKA,CAAK;AAC7B,UAAMwB,IAAU,KAAK,OAAO,UAAU,YAAY;AAClD,QAAI,KAAK,cAAc,UAAUA,GAAS;AACxC,WAAK,WAAA;AACL;AAAA,IACF;AACA,QAAI,CAAC,KAAK,YAAY;AACpB,YAAMC,IAAa,KAAK,OAAO,UAAU,qBAAqB;AAC9D,WAAK,aAAa,WAAW,MAAM;AACjC,aAAK,aAAa,MAClB,KAAK,WAAA;AAAA,MACP,GAAGA,CAAU;AAAA,IACf;AAAA,EACF;AAAA,EAEQ,aAAa;AACnB,QAAI,KAAK,cAAc,WAAW,EAAG;AACrC,UAAM1C,IAAO,KAAK,OAAO,UAAU,iBAAiB,WAC9CsB,IAAU,KAAK,cAAc,OAAO,GAAG,KAAK,cAAc,MAAM;AACtE,SAAK,MAAM,QAAQtB,GAAM,EAAE,QAAQsB,GAAS,YAAY,KAAK,gBAAA,GAAmB;AAAA,EAClF;AAAA,EAEQ,kBAAkB;AACxB,WAAI,OAAO,SAAW,OAAe,OAAO,OAAO,cAAe,aACzD,OAAO,WAAA,IAET,OAAO,KAAK,IAAA,CAAK,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,EACjE;AAAA,EAEQ,gBAAgB;AACtB,UAAMzB,IAAM,KAAK,IAAA,GACX8C,IAAY,KAAK,KAAK,QAAQ,eAAA,GAC9BC,IAAkB,KAAK,KAAK,QAAQ,oBAAA,GACpCC,IAAeD,IAAkB,KAAK,MAAMA,CAAe,IAAI;AAMrE,QAJE,CAACD,KACD,CAACE,KACDhD,IAAMgD,KAAgB,KAAK,oBAC3B,KAAK,cAAcA,GAAchD,CAAG,GACpB;AAChB,YAAMiD,IAAe,KAAK,gBAAA,GACpBC,IAAS,IAAI,KAAKlD,CAAG,EAAE,YAAA;AAC7B,WAAK,KAAK,QAAQ,OAAO;AAAA,QACvB,YAAYiD;AAAA,QACZ,oBAAoBC;AAAA,QACpB,kBAAkBA;AAAA,MAAA,CACnB;AACD;AAAA,IACF;AACA,SAAK,KAAK,QAAQ,OAAO,EAAE,kBAAkB,IAAI,KAAKlD,CAAG,EAAE,YAAA,GAAe;AAAA,EAC5E;AAAA,EAEQ,cAAcmD,GAAoBC,GAAmB;AAC3D,UAAMC,IAAO,IAAI,KAAKF,CAAU,GAC1BG,IAAO,IAAI,KAAKF,CAAS;AAC/B,WACEC,EAAK,eAAA,MAAqBC,EAAK,eAAA,KAC/BD,EAAK,YAAA,MAAkBC,EAAK,iBAC5BD,EAAK,WAAA,MAAiBC,EAAK,WAAA;AAAA,EAE/B;AAAA,EAEQ,oBAAoB;AAC1B,WAAI,OAAO,KAAK,OAAO,eAAgB,WAAiB,KACpD,KAAK,OAAO,eAAe,IAAU,KACrC,KAAK,OAAO,eAAe,IAAU,KAClC,KAAK,OAAA,KAAY,KAAK,OAAO;AAAA,EACtC;AAAA,EAEQ,eAAe;AACrB,QAAI,CAAC,KAAK,OAAO,aAAa,OAAO,YAAc;AACjD,aAAO;AAET,UAAMC,IAAM,UAAU,YAChBC,IAAO,UAA6D;AAC1E,WAAOD,MAAQ,OAAOC,MAAQ;AAAA,EAChC;AAAA,EAEQ,cAAcC,GAAkB;AACtC,QAAI;AACF,YAAMC,IAAS,IAAI,IAAID,CAAQ;AAC/B,UAAI,OAAO,SAAW,IAAa,QAAOA;AAC1C,YAAME,IAAa,IAAI,IAAI,OAAO,SAAS,IAAI;AAC/C,aAAID,EAAO,WAAWC,EAAW,UAC/BD,EAAO,SAAS,IACTA,EAAO,SAAA,KAETD;AAAA,IACT,QAAQ;AACN,aAAOA;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,4BAA4B;AAClC,IAAI,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,YAAY,OAAO,SAAW,QAG1D,OAAO,SAAS,SAAS,KAAK,YAChC,QAAQ,KAAK,uDAAuD;AAAA,MAClE,UAAU,KAAK;AAAA,MACf,QAAQ,OAAO,SAAS;AAAA,IAAA,CACzB,GAEC,KAAK,UAAU,CAAC,6EAA6E,KAAK,KAAK,MAAM,KAC/G,QAAQ,KAAK,oDAAoD,KAAK,MAAM;AAAA,EAEhF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,eAAe;AACrB,UAAMG,IAAS,KAAK,OAAO;AAC3B,QAAIA,GAAQ,SAAS,SAAS,OAAO,SAAW,IAAa;AAC7D,UAAMC,IAAWD,EAAO,UAElBE,IAAU,CAACC,MAAoB;AACnC,UAAI;AACF,cAAMC,IAAI;AAgBV,YAAI,OAAOA,EAAE,YAAa,YAAY;AACpC,UAAID,IAAU,KACZ,WAAW,MAAMD,EAAQC,IAAU,CAAC,GAAG,GAAG;AAE5C;AAAA,QACF;AACA,QAAAC,EAAE,SAAS,oBAAoB,GAAG,CAACC,GAAQC,MAAO;AAChD,cAAI,CAACA,KAAM,CAACD,GAAQ;AAClB,iBAAK,mBAAmB;AACxB;AAAA,UACF;AACA,cAAIA,EAAO,gBAAgB,IAAO;AAChC,iBAAK,mBAAmB;AACxB;AAAA,UACF;AACA,gBAAME,IAAWF,EAAO,SAAS,YAAY,CAAA;AAC7C,eAAK,mBAAmBJ,EAAS,MAAM,CAACO,MAAMD,EAAS,OAAOC,CAAC,CAAC,MAAM,EAAI;AAAA,QAC5E,CAAC;AAAA,MACH,QAAQ;AACN,aAAK,mBAAmB;AAAA,MAC1B;AAAA,IACF;AAEA,IAAAN,EAAQ,EAAE;AAAA,EACZ;AACF;AChhBO,MAAMO,EAA0C;AAAA,EACpC;AAAA,EACA;AAAA,EACA,8BAAc,IAAA;AAAA,EAE/B,YAAYnD,GAAoB;AAC9B,SAAK,SAASA,GACd,KAAK,WAAWA,EAAO,YAAYrC;AAAA,EACrC;AAAA,EAEA,MAAa,KAAKsB,GAAcC,GAAepB,GAAsC;AACnF,UAAMsF,IAAU,KAAK,aAAanE,GAAMC,GAAMpB,CAAO;AACrD,SAAK,QAAQ,IAAIsF,CAAO;AACxB,QAAI;AACF,YAAMA;AAAA,IACR,UAAA;AACE,WAAK,QAAQ,OAAOA,CAAO;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,MAAa,QAAQ;AACnB,IAAI,KAAK,QAAQ,SAAS,KAG1B,MAAM,QAAQ,WAAW,MAAM,KAAK,KAAK,OAAO,CAAC;AAAA,EACnD;AAAA,EAEA,MAAc,aAAanE,GAAcC,GAAepB,GAAsC;AAC5F,UAAMuF,IAAM,KAAK,QAAQ,KAAK,UAAUpE,CAAI,GACtCuC,IAAM,KAAK,OAAO,KAElB8B,IAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,CAAC9B,EAAI,WAAW,KAAK,IAAI,iBAAiB,mBAAmB,GAAGA;AAAA,IAAA;AAGlE,IAAI,KAAK,OAAO,SACd,QAAQ,IAAI,kBAAkB6B,GAAKnE,CAAI;AAGzC,UAAMqE,IAAO,KAAK,UAAUrE,CAAI;AAChC,QAAIpB,GAAS,gBAAgB,OAAO,YAAc,OAAe,OAAO,UAAU,cAAe,YAAY;AAC3G,YAAM0F,IAAO,IAAI,KAAK,CAACD,CAAI,GAAG,EAAE,MAAM,oBAAoB;AAE1D,UADa,UAAU,WAAWF,GAAKG,CAAI;AAEzC;AAAA,IAEJ;AAEA,QAAI,OAAO,SAAU;AACnB,YAAI,KAAK,OAAO,SACd,QAAQ,MAAM,uDAAuD,GAEjE,IAAI5F,EAAe,uDAAuD;AAGlF,UAAM6F,IAAY,KAAK,OAAO,sBAAsB,KAC9CC,IAAa,IAAI,gBAAA;AACvB,QAAIC,IAAW;AACf,UAAMC,IAAQ,WAAW,MAAM;AAC7B,MAAAD,IAAW,IACXD,EAAW,MAAA;AAAA,IACb,GAAGD,CAAS;AAEZ,QAAI;AACF,YAAMI,IAAW,MAAM,MAAMR,GAAK;AAAA,QAChC,QAAQ;AAAA,QACR,SAAAC;AAAA,QACA,MAAAC;AAAA,QACA,WAAW;AAAA,QACX,QAAQG,EAAW;AAAA,MAAA,CACpB;AACD,UAAI,CAACG,EAAS,IAAI;AAChB,cAAMC,IAAU,MAAM,KAAK,iBAAiBD,CAAQ,GAC9CnE,IAAezB,EAAgB4F,EAAS,QAAQ,IAAI,aAAa,CAAC;AACxE,cAAI,KAAK,OAAO,SACd,QAAQ,MAAM,2BAA2B;AAAA,UACvC,KAAAR;AAAA,UACA,QAAQQ,EAAS;AAAA,UACjB,YAAYA,EAAS;AAAA,UACrB,MAAMC;AAAA,UACN,cAAApE;AAAA,QAAA,CACD,GAEG,IAAI9B;AAAA,UACR,uCAAuCiG,EAAS,MAAM,KAAKC,CAAO;AAAA,UAClE,EAAE,QAAQD,EAAS,QAAQ,cAAAnE,EAAA;AAAA,QAAa;AAAA,MAE5C;AAAA,IACF,SAASD,GAAO;AAId,UAHI,KAAK,OAAO,SACd,QAAQ,MAAM,yBAAyB4D,GAAK5D,CAAK,GAE/CA,aAAiB7B;AACnB,cAAM6B;AAER,UAAIkE;AACF,cAAM,IAAI/F,EAAe,kCAAkC6F,CAAS,MAAM,EAAE,QAAQ,KAAK;AAE3F,YAAM5F,IAAU4B,aAAiB,QAAQA,EAAM,UAAU,OAAOA,CAAK;AACrE,YAAM,IAAI7B,EAAe,yBAAyBC,CAAO,EAAE;AAAA,IAC7D,UAAA;AACE,mBAAa+F,CAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,QAAQG,GAAkB9E,GAAc;AAC9C,UAAM+E,IAAqBD,EAAS,QAAQ,QAAQ,EAAE,GAChDE,IAAiBhF,EAAK,QAAQ,QAAQ,EAAE;AAC9C,WAAO,GAAG+E,CAAkB,IAAIC,CAAc;AAAA,EAChD;AAAA,EAEA,MAAc,iBAAiBJ,GAAoB;AACjD,QAAI;AAEF,cADa,MAAMA,EAAS,KAAA,GAChB,MAAM,GAAG,GAAG;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;ACtHO,MAAMK,EAA2C;AAAA,EACtD,YAAoBC,IAAwB,IAAI;AAA5B,SAAA,WAAAA;AAAA,EAA8B;AAAA,EAE3C,eAAe;AACpB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,iBAAiB;AACtB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,gBAAgB;AACrB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,iBAAiB;AACtB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,YAAY;AACjB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,eAAe;AACpB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,sBAAsB;AAC3B,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,oBAAoB;AACzB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,mBAA8C;AACnD,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEO,SAAe;AAAA,EAEtB;AAAA,EAEO,mBAAyB;AAAA,EAEhC;AAAA,EAEO,mBAAyB;AAAA,EAEhC;AAAA,EAEO,uBAA6B;AAAA,EAEpC;AAAA,EAEO,kBAAwB;AAAA,EAE/B;AACF;AC/CO,MAAMC,EAAS;AAAA,EACpB,YAA6BC,GAAkB;AAAlB,SAAA,SAAAA;AAAA,EAAmB;AAAA,EAEzC,MAAMlE,GAAmBjB,IAAuB,IAAI;AACzD,SAAK,OAAO,MAAMiB,GAAWjB,CAAI;AAAA,EACnC;AAAA,EAEO,WAAWiB,GAAmBjB,IAAuB,IAAI;AAC9D,SAAK,OAAO,WAAWiB,GAAWjB,CAAI;AAAA,EACxC;AAAA,EAEO,SAASyB,GAAgBC,IAAyB,IAAI;AAC3D,SAAK,OAAO,SAASD,GAAQC,CAAM;AAAA,EACrC;AAAA,EAEO,WAAW5C,GAAuB;AACvC,SAAK,OAAO,WAAWA,CAAM;AAAA,EAC/B;AAAA,EAEO,QAAQ;AACb,WAAO,KAAK,OAAO,MAAA;AAAA,EACrB;AAAA,EAEO,QAAQ;AACb,SAAK,OAAO,MAAA;AAAA,EACd;AAAA,EAEO,WAAW;AAChB,WAAO,KAAK,OAAO,SAAA;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,eAAmC;AACxC,WAAO,KAAK,OAAO,aAAA;AAAA,EACrB;AACF;AAEO,MAAMsG,IAAiB,CAACtE,GAAoBlC,IAA2B,OAAO;AACnF,QAAMyG,IACJzG,EAAQ,WACR,IAAIoG,EAAiB;AAAA,IACnB,YAAYpG,EAAQ,iBAAiB;AAAA,IACrC,SAASA,EAAQ,iBAAiB;AAAA,IAClC,YAAYA,EAAQ,iBAAiB;AAAA,IACrC,oBAAoBA,EAAQ,iBAAiB;AAAA,IAC7C,kBAAkBA,EAAQ,iBAAiB;AAAA,IAC3C,eAAeA,EAAQ,iBAAiB;AAAA,IACxC,aAAaA,EAAQ,iBAAiB;AAAA,IACtC,cAAcA,EAAQ,iBAAiB;AAAA,IACvC,gBAAgBA,EAAQ,iBAAiB;AAAA,EAAA,CAC1C,GAEGc,IAAYd,EAAQ,aAAa,IAAIqF,EAAcnD,CAAM,GACzDqE,IAAS,IAAItE,EAASC,GAAQ,EAAE,SAAAuE,GAAS,WAAA3F,GAAW;AAE1D,SAAO,IAAIwF,EAASC,CAAM;AAC5B;"}
|
|
@@ -2,10 +2,12 @@ import type { ConsentStatus, CrossDomainConfig, StorageData } from '../types';
|
|
|
2
2
|
import type { StorageAdapter } from '../core/adapters';
|
|
3
3
|
interface BrowserStorageOptions extends CrossDomainConfig {
|
|
4
4
|
storageKey?: string;
|
|
5
|
+
outboxKey?: string;
|
|
5
6
|
}
|
|
6
7
|
export declare class BrowserStorage implements StorageAdapter {
|
|
7
8
|
private data;
|
|
8
9
|
private readonly storageKey;
|
|
10
|
+
private readonly outboxKey;
|
|
9
11
|
private readonly options;
|
|
10
12
|
private bridgeFrame?;
|
|
11
13
|
private bridgeReady;
|
|
@@ -16,8 +18,17 @@ export declare class BrowserStorage implements StorageAdapter {
|
|
|
16
18
|
getCampaignId(): string | undefined;
|
|
17
19
|
getQueryParams(): Record<string, string> | undefined;
|
|
18
20
|
getConsentStatus(): ConsentStatus | undefined;
|
|
21
|
+
getUserId(): string | undefined;
|
|
22
|
+
getSessionId(): string | undefined;
|
|
23
|
+
getSessionStartedAt(): string | undefined;
|
|
24
|
+
getLastActivityAt(): string | undefined;
|
|
19
25
|
update(updates: Partial<StorageData>): void;
|
|
20
26
|
setConsentStatus(status: ConsentStatus): void;
|
|
27
|
+
clearAttribution(): void;
|
|
28
|
+
clearCookieVisitorId(): void;
|
|
29
|
+
rotateVisitorId(): void;
|
|
30
|
+
getOutbox(): unknown[];
|
|
31
|
+
setOutbox(items: unknown[]): void;
|
|
21
32
|
private load;
|
|
22
33
|
private save;
|
|
23
34
|
private getCookie;
|
|
@@ -28,5 +39,6 @@ export declare class BrowserStorage implements StorageAdapter {
|
|
|
28
39
|
private handleBridgeMessage;
|
|
29
40
|
private postBridgeMessage;
|
|
30
41
|
private isDomainAllowed;
|
|
42
|
+
private handleStorageEvent;
|
|
31
43
|
}
|
|
32
44
|
export {};
|
|
@@ -8,7 +8,15 @@ export declare class Mark {
|
|
|
8
8
|
private static lastPageviewHref;
|
|
9
9
|
private static emitAutoPageview;
|
|
10
10
|
private static pendingAttribution;
|
|
11
|
+
private static originalPushState;
|
|
12
|
+
private static originalReplaceState;
|
|
13
|
+
private static popstateHandler;
|
|
14
|
+
private static beaconDrainHandler;
|
|
15
|
+
private static visibilityHandler;
|
|
16
|
+
private static onlineHandler;
|
|
17
|
+
private static kickTimer;
|
|
11
18
|
static init(config: MarkConfig): MarkCore;
|
|
19
|
+
private static installLifecycleFlushing;
|
|
12
20
|
static track(eventName: string, data?: TrackEventData): void;
|
|
13
21
|
static identify(userId: string, traits?: IdentifyTraits): void;
|
|
14
22
|
static conversion(eventName: string, data?: TrackEventData): void;
|
|
@@ -20,9 +28,19 @@ export declare class Mark {
|
|
|
20
28
|
* when you do not have an authenticated user ID.
|
|
21
29
|
*/
|
|
22
30
|
static getVisitorId(): string | undefined;
|
|
31
|
+
static flush(): Promise<void>;
|
|
32
|
+
static reset(): void;
|
|
33
|
+
static getStats(): {
|
|
34
|
+
queued: number;
|
|
35
|
+
sent: number;
|
|
36
|
+
failed: number;
|
|
37
|
+
dropped: number;
|
|
38
|
+
};
|
|
39
|
+
static destroy(): void;
|
|
23
40
|
private static installPageviewTracking;
|
|
24
41
|
private static refreshAttribution;
|
|
25
42
|
private static flushPendingAttribution;
|
|
26
43
|
private static shouldPersistAttribution;
|
|
27
44
|
private static mergeAttributionUpdates;
|
|
45
|
+
private static installExtendedAutocapture;
|
|
28
46
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { TransportAdapter } from './adapters';
|
|
2
|
+
interface QueueItem {
|
|
3
|
+
path: string;
|
|
4
|
+
data: Record<string, unknown>;
|
|
5
|
+
attempts: number;
|
|
6
|
+
nextAttemptAt: number;
|
|
7
|
+
enqueuedAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DeliveryQueueOptions {
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
maxAttempts?: number;
|
|
12
|
+
baseBackoffMs?: number;
|
|
13
|
+
maxBackoffMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum age (ms) for persisted queue items before they are dropped on load
|
|
16
|
+
* or next process tick. Defaults to 48 hours.
|
|
17
|
+
*/
|
|
18
|
+
maxItemAgeMs?: number;
|
|
19
|
+
loadPersisted?: () => QueueItem[];
|
|
20
|
+
savePersisted?: (items: QueueItem[]) => void;
|
|
21
|
+
onError?: (error: unknown, event?: Record<string, unknown>) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare class DeliveryQueue {
|
|
24
|
+
private readonly transport;
|
|
25
|
+
private readonly queue;
|
|
26
|
+
private flushing;
|
|
27
|
+
private sent;
|
|
28
|
+
private failed;
|
|
29
|
+
private dropped;
|
|
30
|
+
private readonly maxAttempts;
|
|
31
|
+
private readonly baseBackoffMs;
|
|
32
|
+
private readonly maxBackoffMs;
|
|
33
|
+
private readonly maxItemAgeMs;
|
|
34
|
+
private readonly debug;
|
|
35
|
+
private readonly loadPersisted?;
|
|
36
|
+
private readonly savePersisted?;
|
|
37
|
+
private readonly onError?;
|
|
38
|
+
constructor(transport: TransportAdapter, options?: DeliveryQueueOptions);
|
|
39
|
+
enqueue(path: string, data: Record<string, unknown>): void;
|
|
40
|
+
flush(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Best-effort synchronous drain using sendBeacon. Intended for page unload;
|
|
43
|
+
* errors are swallowed because the tab is going away.
|
|
44
|
+
*/
|
|
45
|
+
drainViaBeacon(): void;
|
|
46
|
+
getStats(): {
|
|
47
|
+
queued: number;
|
|
48
|
+
sent: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
dropped: number;
|
|
51
|
+
};
|
|
52
|
+
clear(): void;
|
|
53
|
+
private persist;
|
|
54
|
+
private evictExpired;
|
|
55
|
+
private process;
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
@@ -3,9 +3,13 @@ import type { TransportAdapter } from './adapters';
|
|
|
3
3
|
export declare class HttpTransport implements TransportAdapter {
|
|
4
4
|
private readonly config;
|
|
5
5
|
private readonly endpoint;
|
|
6
|
+
private readonly pending;
|
|
6
7
|
constructor(config: MarkConfig);
|
|
7
|
-
send(path: string, data: unknown
|
|
8
|
+
send(path: string, data: unknown, options?: {
|
|
9
|
+
preferBeacon?: boolean;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
flush(): Promise<void>;
|
|
12
|
+
private sendInternal;
|
|
8
13
|
private joinUrl;
|
|
9
14
|
private readErrorSnippet;
|
|
10
|
-
private validateConfig;
|
|
11
15
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MarkConfig, TrackEventData, IdentifyTraits, ConsentStatus } from '../types';
|
|
1
|
+
import type { MarkConfig, TrackEventData, ConversionProperties, IdentifyTraits, ConsentStatus } from '../types';
|
|
2
2
|
import type { MarkDependencies } from './adapters';
|
|
3
3
|
export declare class MarkCore {
|
|
4
4
|
private readonly deps;
|
|
@@ -6,21 +6,65 @@ export declare class MarkCore {
|
|
|
6
6
|
private consentRequirement;
|
|
7
7
|
private siteId?;
|
|
8
8
|
private siteHost?;
|
|
9
|
+
private readonly queue;
|
|
10
|
+
private readonly sessionTimeoutMs;
|
|
11
|
+
private batchTimer;
|
|
12
|
+
private readonly batchedEvents;
|
|
13
|
+
private tcfCachedAllowed;
|
|
9
14
|
constructor(config: MarkConfig, deps: MarkDependencies);
|
|
15
|
+
/**
|
|
16
|
+
* Best-effort synchronous drain that dispatches all queued events via
|
|
17
|
+
* sendBeacon. Intended for use on page unload (visibilitychange=hidden,
|
|
18
|
+
* pagehide) where async fetch may be cancelled by the browser.
|
|
19
|
+
*/
|
|
20
|
+
drainViaBeacon(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Kicks the queue to retry any pending items now. Safe to call repeatedly;
|
|
23
|
+
* used by the browser wrapper in response to `online` events or periodic
|
|
24
|
+
* timers.
|
|
25
|
+
*/
|
|
26
|
+
kickQueue(): void;
|
|
10
27
|
track(eventName: string, data?: TrackEventData): boolean;
|
|
11
28
|
private trackInternal;
|
|
12
29
|
identify(userId: string, traits?: IdentifyTraits): void;
|
|
13
|
-
conversion(eventName: string, data?:
|
|
30
|
+
conversion(eventName: string, data?: ConversionProperties): boolean;
|
|
31
|
+
trackWithOptions(eventName: string, data?: TrackEventData, options?: {
|
|
32
|
+
preferBeacon?: boolean;
|
|
33
|
+
}): boolean;
|
|
14
34
|
/**
|
|
15
35
|
* Returns the current visitor ID from storage, if any.
|
|
16
36
|
* Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.
|
|
17
37
|
*/
|
|
18
38
|
getVisitorId(): string | undefined;
|
|
19
39
|
setConsent(status: ConsentStatus): void;
|
|
40
|
+
reset(): void;
|
|
41
|
+
flush(): Promise<void>;
|
|
42
|
+
getStats(): {
|
|
43
|
+
queued: number;
|
|
44
|
+
sent: number;
|
|
45
|
+
failed: number;
|
|
46
|
+
dropped: number;
|
|
47
|
+
};
|
|
20
48
|
private getIdentityFields;
|
|
21
49
|
private hasConsent;
|
|
22
50
|
private sanitizeTrackData;
|
|
23
51
|
private sanitizeIdentifyTraits;
|
|
24
52
|
private validateConfig;
|
|
25
53
|
private applyPageContext;
|
|
54
|
+
private enqueueBatch;
|
|
55
|
+
private flushBatch;
|
|
56
|
+
private createMessageId;
|
|
57
|
+
private ensureSession;
|
|
58
|
+
private crossedUtcDay;
|
|
59
|
+
private shouldSampleTrack;
|
|
60
|
+
private isDntBlocked;
|
|
61
|
+
private scrubReferrer;
|
|
62
|
+
private warnMisconfiguredSiteHost;
|
|
63
|
+
/**
|
|
64
|
+
* Subscribes to the IAB TCF v2 CMP via `__tcfapi('addEventListener', ...)`.
|
|
65
|
+
* Result is cached in `tcfCachedAllowed` so the synchronous `hasConsent()`
|
|
66
|
+
* path does not need to await the CMP. If the CMP is not yet present, we
|
|
67
|
+
* poll briefly (CMPs commonly load asynchronously) and give up after ~2s.
|
|
68
|
+
*/
|
|
69
|
+
private subscribeTcf;
|
|
26
70
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class TransportError extends Error {
|
|
2
|
+
readonly status?: number;
|
|
3
|
+
readonly retryAfterMs?: number;
|
|
4
|
+
constructor(message: string, options?: {
|
|
5
|
+
status?: number;
|
|
6
|
+
retryAfterMs?: number;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns true when a TransportError status should NOT be retried by the queue.
|
|
11
|
+
* 4xx client errors (other than 408 Request Timeout and 429 Too Many Requests)
|
|
12
|
+
* indicate a request problem that re-sending will not fix.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isNonRetriableStatus(status: number | undefined): boolean;
|
|
15
|
+
export declare function parseRetryAfter(headerValue: string | null | undefined): number | undefined;
|
|
@@ -2,15 +2,28 @@ import type { StorageData } from '../types';
|
|
|
2
2
|
import type { ConsentStatus } from '../types';
|
|
3
3
|
export interface StorageAdapter {
|
|
4
4
|
getVisitorId(): string | undefined;
|
|
5
|
+
getUserId?(): string | undefined;
|
|
6
|
+
getSessionId?(): string | undefined;
|
|
7
|
+
getSessionStartedAt?(): string | undefined;
|
|
8
|
+
getLastActivityAt?(): string | undefined;
|
|
5
9
|
getLastClickId(): string | undefined;
|
|
6
10
|
getCampaignId(): string | undefined;
|
|
7
11
|
getQueryParams(): Record<string, string> | undefined;
|
|
8
12
|
getConsentStatus(): ConsentStatus | undefined;
|
|
9
13
|
setConsentStatus(status: ConsentStatus): void;
|
|
10
14
|
update(updates: Partial<StorageData>): void;
|
|
15
|
+
clearAttribution?(): void;
|
|
16
|
+
clearCookieVisitorId?(): void;
|
|
17
|
+
rotateVisitorId?(): void;
|
|
18
|
+
onUpdate?(listener: (updates: Partial<StorageData>) => void): void;
|
|
19
|
+
getOutbox?(): unknown[];
|
|
20
|
+
setOutbox?(items: unknown[]): void;
|
|
11
21
|
}
|
|
12
22
|
export interface TransportAdapter {
|
|
13
|
-
send(path: string, data: unknown
|
|
23
|
+
send(path: string, data: unknown, options?: {
|
|
24
|
+
preferBeacon?: boolean;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
flush?(): Promise<void>;
|
|
14
27
|
}
|
|
15
28
|
export interface MarkDependencies {
|
|
16
29
|
storage: StorageAdapter;
|
|
@@ -11,7 +11,14 @@ export declare class StatelessStorage implements StorageAdapter {
|
|
|
11
11
|
getLastClickId(): string | undefined;
|
|
12
12
|
getCampaignId(): string | undefined;
|
|
13
13
|
getQueryParams(): Record<string, string> | undefined;
|
|
14
|
+
getUserId(): string | undefined;
|
|
15
|
+
getSessionId(): string | undefined;
|
|
16
|
+
getSessionStartedAt(): string | undefined;
|
|
17
|
+
getLastActivityAt(): string | undefined;
|
|
14
18
|
getConsentStatus(): ConsentStatus | undefined;
|
|
15
19
|
update(): void;
|
|
16
20
|
setConsentStatus(): void;
|
|
21
|
+
clearAttribution(): void;
|
|
22
|
+
clearCookieVisitorId(): void;
|
|
23
|
+
rotateVisitorId(): void;
|
|
17
24
|
}
|
|
@@ -14,6 +14,14 @@ export declare class NodeMark {
|
|
|
14
14
|
conversion(eventName: string, data?: TrackEventData): void;
|
|
15
15
|
identify(userId: string, traits?: IdentifyTraits): void;
|
|
16
16
|
setConsent(status: ConsentStatus): void;
|
|
17
|
+
flush(): Promise<void>;
|
|
18
|
+
reset(): void;
|
|
19
|
+
getStats(): {
|
|
20
|
+
queued: number;
|
|
21
|
+
sent: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
dropped: number;
|
|
24
|
+
};
|
|
17
25
|
/**
|
|
18
26
|
* Returns the visitor ID from the configured storage, if any.
|
|
19
27
|
* With default StatelessStorage, this is the value passed via `storageDefaults.visitor_id` when creating the client.
|
package/dist/types/types.d.ts
CHANGED
|
@@ -4,6 +4,19 @@ export interface AutocaptureConfig {
|
|
|
4
4
|
* Defaults to false.
|
|
5
5
|
*/
|
|
6
6
|
pageview?: boolean;
|
|
7
|
+
click?: boolean | {
|
|
8
|
+
selector?: string;
|
|
9
|
+
};
|
|
10
|
+
form_submit?: boolean;
|
|
11
|
+
outbound_link?: boolean;
|
|
12
|
+
scroll_depth?: boolean;
|
|
13
|
+
web_vitals?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface BatchingConfig {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
max_size?: number;
|
|
18
|
+
flush_interval_ms?: number;
|
|
19
|
+
endpoint_path?: string;
|
|
7
20
|
}
|
|
8
21
|
export interface MarkConfig {
|
|
9
22
|
/**
|
|
@@ -19,6 +32,18 @@ export interface MarkConfig {
|
|
|
19
32
|
* Enables verbose logging for debugging purposes.
|
|
20
33
|
*/
|
|
21
34
|
debug?: boolean;
|
|
35
|
+
honor_dnt?: boolean;
|
|
36
|
+
sample_rate?: number;
|
|
37
|
+
request_timeout_ms?: number;
|
|
38
|
+
session_timeout_ms?: number;
|
|
39
|
+
rotate_visitor_on_consent_change?: boolean;
|
|
40
|
+
batching?: BatchingConfig;
|
|
41
|
+
before_send?: (event: Record<string, unknown>) => Record<string, unknown> | null;
|
|
42
|
+
on_error?: (error: unknown, event?: Record<string, unknown>) => void;
|
|
43
|
+
consent_source?: {
|
|
44
|
+
type: 'tcf';
|
|
45
|
+
purposes: number[];
|
|
46
|
+
};
|
|
22
47
|
/**
|
|
23
48
|
* Optional cross-domain configuration.
|
|
24
49
|
*/
|
|
@@ -104,8 +129,16 @@ export interface TrackEventData {
|
|
|
104
129
|
* Overrides the site_host from Mark.init() if provided.
|
|
105
130
|
*/
|
|
106
131
|
site_host?: string;
|
|
132
|
+
user_id?: string;
|
|
133
|
+
session_id?: string;
|
|
107
134
|
[key: string]: JsonValue | undefined;
|
|
108
135
|
}
|
|
136
|
+
export type ConversionProperties = TrackEventData & {
|
|
137
|
+
value?: number;
|
|
138
|
+
currency?: string;
|
|
139
|
+
order_id?: string;
|
|
140
|
+
items?: Array<Record<string, JsonValue>>;
|
|
141
|
+
};
|
|
109
142
|
export interface IdentifyTraits {
|
|
110
143
|
/**
|
|
111
144
|
* User's email address. Will be hashed server-side for privacy.
|
|
@@ -130,6 +163,10 @@ export interface IdentifyTraits {
|
|
|
130
163
|
}
|
|
131
164
|
export interface StorageData {
|
|
132
165
|
visitor_id?: string;
|
|
166
|
+
user_id?: string;
|
|
167
|
+
session_id?: string;
|
|
168
|
+
session_started_at?: string;
|
|
169
|
+
last_activity_at?: string;
|
|
133
170
|
last_click_id?: string;
|
|
134
171
|
campaign_id?: string;
|
|
135
172
|
consent_status?: ConsentStatus;
|