@crelora/mark 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.cjs","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":"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"}
1
+ {"version":3,"file":"node.cjs","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 this.applyInternalFlag(payload, sanitizedData.is_internal);\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 this.applyInternalFlag(payload);\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 this.deps.storage.setInternal?.(false);\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 this.applyInternalFlag(payload);\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 is_internal: undefined,\r\n });\r\n this.deps.storage.rotateVisitorId?.();\r\n }\r\n\r\n /**\r\n * Marks or unmarks the current visitor as internal traffic. While set, every\r\n * subsequent event (track/identify/conversion/consent) is stamped with\r\n * `is_internal: true`, so the backend can exclude it from customer-facing\r\n * reports by default.\r\n *\r\n * The flag is persisted via the storage adapter (browser: localStorage) so\r\n * it survives reloads, and is cleared by `reset()` and by\r\n * `setConsent('denied')`.\r\n */\r\n public setInternal(value: boolean) {\r\n this.deps.storage.setInternal?.(Boolean(value));\r\n }\r\n\r\n /**\r\n * Returns the currently persisted internal-traffic flag, if any.\r\n */\r\n public getInternal(): boolean {\r\n return Boolean(this.deps.storage.getInternal?.());\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 /**\r\n * Stamps `is_internal: true` on the payload when either:\r\n * - the persistent visitor flag is set (via setInternal), or\r\n * - the caller passed `is_internal: true` on this specific event.\r\n *\r\n * Explicit `is_internal: false` on a single event wins over the visitor flag\r\n * so individual calls can opt out.\r\n */\r\n private applyInternalFlag(payload: Record<string, unknown>, eventValue?: boolean) {\r\n if (eventValue === false) {\r\n delete payload.is_internal;\r\n return;\r\n }\r\n const stored = this.deps.storage.getInternal?.() === true;\r\n if (eventValue === true || stored) {\r\n payload.is_internal = true;\r\n } else {\r\n delete payload.is_internal;\r\n }\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 public getInternal(): boolean | undefined {\n return this.defaults.is_internal;\n }\n\n public setInternal(value: boolean): void {\n this.defaults.is_internal = value ? true : undefined;\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 /**\n * Marks the current request/visitor as internal traffic. When using the\n * default StatelessStorage, the flag is scoped to this NodeMark instance.\n */\n public setInternal(value: boolean) {\n this.client.setInternal(value);\n }\n\n public getInternal(): boolean {\n return this.client.getInternal();\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 is_internal: options.storageDefaults?.is_internal,\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","value","eventValue","stored","visitorId","clickId","campaignId","sessionId","storedQuery","inputQuery","query","identity","state","sanitized","key","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":"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"}
package/dist/node.es.js CHANGED
@@ -22,10 +22,10 @@ function y(a) {
22
22
  return i > 0 ? i : 0;
23
23
  }
24
24
  }
25
- const w = 5, b = 300, k = 15e3, v = 2880 * 60 * 1e3;
26
- class M {
25
+ const w = 5, b = 300, I = 15e3, k = 2880 * 60 * 1e3;
26
+ class v {
27
27
  constructor(t, e = {}) {
28
- this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? b, this.maxBackoffMs = e.maxBackoffMs ?? k, this.maxItemAgeMs = e.maxItemAgeMs ?? v, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
28
+ this.transport = t, this.maxAttempts = e.maxAttempts ?? w, this.baseBackoffMs = e.baseBackoffMs ?? b, this.maxBackoffMs = e.maxBackoffMs ?? I, this.maxItemAgeMs = e.maxItemAgeMs ?? k, this.debug = e.debug ?? !1, this.loadPersisted = e.loadPersisted, this.savePersisted = e.savePersisted, this.onError = e.onError;
29
29
  const s = this.loadPersisted?.() ?? [];
30
30
  if (s.length > 0) {
31
31
  const i = Date.now();
@@ -147,7 +147,7 @@ class M {
147
147
  }
148
148
  }
149
149
  }
150
- const I = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), q = /* @__PURE__ */ new Set([
150
+ const M = /* @__PURE__ */ new Set(["event_name", "user_id", "consent_state", "source", "is_conversion"]), q = /* @__PURE__ */ new Set([
151
151
  "user_id",
152
152
  "visitor_id",
153
153
  "click_id",
@@ -162,7 +162,7 @@ class A {
162
162
  endpoint: t.endpoint ?? p,
163
163
  ...t,
164
164
  include_page_context: t.include_page_context ?? !0
165
- }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new M(this.deps.transport, {
165
+ }, this.consentRequirement = t.require_consent ?? !1, this.siteId = t.site_id, this.siteHost = t.site_host, this.sessionTimeoutMs = t.session_timeout_ms ?? 1800 * 1e3, this.queue = new v(this.deps.transport, {
166
166
  debug: this.config.debug,
167
167
  loadPersisted: () => this.deps.storage.getOutbox?.() ?? [],
168
168
  savePersisted: (s) => this.deps.storage.setOutbox?.(s),
@@ -214,7 +214,7 @@ class A {
214
214
  };
215
215
  s && (r.is_conversion = !0);
216
216
  const h = o.site_id ?? this.siteId, d = o.site_host ?? this.siteHost;
217
- h && (r.site_id = h), d && (r.site_host = d), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !d && r.site && (r.site_host = r.site));
217
+ h && (r.site_id = h), d && (r.site_host = d), this.config.include_page_context && typeof window < "u" && (this.applyPageContext(r), !d && r.site && (r.site_host = r.site)), this.applyInternalFlag(r, o.is_internal);
218
218
  const u = this.config.before_send ? this.config.before_send(r) : r;
219
219
  return u ? (this.ensureSession(), this.config.batching?.enabled && !s && !i?.preferBeacon ? (this.enqueueBatch(u), !0) : (this.queue.enqueue("/event", { ...u, __prefer_beacon: i?.preferBeacon === !0 }), !0)) : !0;
220
220
  }
@@ -234,7 +234,7 @@ class A {
234
234
  ...this.sanitizeIdentifyTraits(e),
235
235
  ...this.getIdentityFields()
236
236
  };
237
- this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost);
237
+ this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost), this.applyInternalFlag(s);
238
238
  const i = this.config.before_send ? this.config.before_send(s) : s;
239
239
  i && this.queue.enqueue("/identify", i);
240
240
  }
@@ -253,14 +253,14 @@ class A {
253
253
  }
254
254
  setConsent(t) {
255
255
  const e = this.deps.storage.getConsentStatus();
256
- this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.()) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
256
+ this.deps.storage.setConsentStatus(t), t === "denied" ? (this.deps.storage.clearAttribution?.(), this.deps.storage.clearCookieVisitorId?.(), this.deps.storage.setInternal?.(!1)) : t === "granted" && e === "denied" && this.config.rotate_visitor_on_consent_change && this.deps.storage.rotateVisitorId?.();
257
257
  const s = {
258
258
  visitor_id: this.deps.storage.getVisitorId(),
259
259
  consent_state: t,
260
260
  source: "sdk",
261
261
  message_id: this.createMessageId()
262
262
  };
263
- this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost);
263
+ this.siteId && (s.site_id = this.siteId), this.siteHost && (s.site_host = this.siteHost), this.applyInternalFlag(s);
264
264
  const i = this.config.before_send ? this.config.before_send(s) : s;
265
265
  i && this.queue.enqueue("/consent", i);
266
266
  }
@@ -272,15 +272,51 @@ class A {
272
272
  query_params: void 0,
273
273
  session_id: void 0,
274
274
  session_started_at: void 0,
275
- last_activity_at: void 0
275
+ last_activity_at: void 0,
276
+ is_internal: void 0
276
277
  }), this.deps.storage.rotateVisitorId?.();
277
278
  }
279
+ /**
280
+ * Marks or unmarks the current visitor as internal traffic. While set, every
281
+ * subsequent event (track/identify/conversion/consent) is stamped with
282
+ * `is_internal: true`, so the backend can exclude it from customer-facing
283
+ * reports by default.
284
+ *
285
+ * The flag is persisted via the storage adapter (browser: localStorage) so
286
+ * it survives reloads, and is cleared by `reset()` and by
287
+ * `setConsent('denied')`.
288
+ */
289
+ setInternal(t) {
290
+ this.deps.storage.setInternal?.(!!t);
291
+ }
292
+ /**
293
+ * Returns the currently persisted internal-traffic flag, if any.
294
+ */
295
+ getInternal() {
296
+ return !!this.deps.storage.getInternal?.();
297
+ }
278
298
  flush() {
279
299
  return this.flushBatch(), this.queue.flush();
280
300
  }
281
301
  getStats() {
282
302
  return this.queue.getStats();
283
303
  }
304
+ /**
305
+ * Stamps `is_internal: true` on the payload when either:
306
+ * - the persistent visitor flag is set (via setInternal), or
307
+ * - the caller passed `is_internal: true` on this specific event.
308
+ *
309
+ * Explicit `is_internal: false` on a single event wins over the visitor flag
310
+ * so individual calls can opt out.
311
+ */
312
+ applyInternalFlag(t, e) {
313
+ if (e === !1) {
314
+ delete t.is_internal;
315
+ return;
316
+ }
317
+ const s = this.deps.storage.getInternal?.() === !0;
318
+ e === !0 || s ? t.is_internal = !0 : delete t.is_internal;
319
+ }
284
320
  getIdentityFields(t) {
285
321
  const e = t?.visitor_id ?? this.deps.storage.getVisitorId(), s = t?.user_id ?? this.deps.storage.getUserId?.(), i = t?.click_id ?? this.deps.storage.getLastClickId(), o = t?.campaign_id ?? this.deps.storage.getCampaignId(), n = t?.session_id ?? this.deps.storage.getSessionId?.(), r = this.deps.storage.getQueryParams() ?? {}, h = t?.query ?? {}, d = { ...r, ...h }, u = {};
286
322
  return e && (u.visitor_id = e), s && (u.user_id = s), i && (u.click_id = i), o && (u.campaign_id = o), n && (u.session_id = n), Object.keys(d).length > 0 && (u.query = d), u;
@@ -296,7 +332,7 @@ class A {
296
332
  sanitizeTrackData(t) {
297
333
  const e = {};
298
334
  for (const [s, i] of Object.entries(t))
299
- I.has(s) || (e[s] = i);
335
+ M.has(s) || (e[s] = i);
300
336
  return e;
301
337
  }
302
338
  sanitizeIdentifyTraits(t) {
@@ -544,6 +580,12 @@ class S {
544
580
  }
545
581
  rotateVisitorId() {
546
582
  }
583
+ getInternal() {
584
+ return this.defaults.is_internal;
585
+ }
586
+ setInternal(t) {
587
+ this.defaults.is_internal = t ? !0 : void 0;
588
+ }
547
589
  }
548
590
  class T {
549
591
  constructor(t) {
@@ -578,6 +620,16 @@ class T {
578
620
  getVisitorId() {
579
621
  return this.client.getVisitorId();
580
622
  }
623
+ /**
624
+ * Marks the current request/visitor as internal traffic. When using the
625
+ * default StatelessStorage, the flag is scoped to this NodeMark instance.
626
+ */
627
+ setInternal(t) {
628
+ this.client.setInternal(t);
629
+ }
630
+ getInternal() {
631
+ return this.client.getInternal();
632
+ }
581
633
  }
582
634
  const E = (a, t = {}) => {
583
635
  const e = t.storage ?? new S({
@@ -589,7 +641,8 @@ const E = (a, t = {}) => {
589
641
  last_click_id: t.storageDefaults?.last_click_id,
590
642
  campaign_id: t.storageDefaults?.campaign_id,
591
643
  query_params: t.storageDefaults?.query_params,
592
- consent_status: t.storageDefaults?.consent_status
644
+ consent_status: t.storageDefaults?.consent_status,
645
+ is_internal: t.storageDefaults?.is_internal
593
646
  }), s = t.transport ?? new D(a), i = new A(a, { storage: e, transport: s });
594
647
  return new T(i);
595
648
  };