@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.
@@ -1 +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":"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"}
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 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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"}
@@ -22,6 +22,8 @@ export declare class BrowserStorage implements StorageAdapter {
22
22
  getSessionId(): string | undefined;
23
23
  getSessionStartedAt(): string | undefined;
24
24
  getLastActivityAt(): string | undefined;
25
+ getInternal(): boolean | undefined;
26
+ setInternal(value: boolean): void;
25
27
  update(updates: Partial<StorageData>): void;
26
28
  setConsentStatus(status: ConsentStatus): void;
27
29
  clearAttribution(): void;
@@ -29,6 +29,22 @@ export declare class Mark {
29
29
  */
30
30
  static getVisitorId(): string | undefined;
31
31
  static flush(): Promise<void>;
32
+ /**
33
+ * Flags (or un-flags) the current visitor as internal traffic. Persists
34
+ * across reloads via the SDK's browser storage. Cleared by `reset()` and by
35
+ * `setConsent('denied')`.
36
+ *
37
+ * The policy for deciding *when* to call this (URL query parameter, auth
38
+ * role, IP, feature flag, etc.) is intentionally left to the host app.
39
+ *
40
+ * @example
41
+ * // URL-based opt-in: call once during app bootstrap, after Mark.init()
42
+ * const params = new URLSearchParams(window.location.search);
43
+ * if (params.get('onelence_internal') === '1') Mark.setInternal(true);
44
+ * if (params.get('onelence_internal') === '0') Mark.setInternal(false);
45
+ */
46
+ static setInternal(value: boolean): void;
47
+ static getInternal(): boolean;
32
48
  static reset(): void;
33
49
  static getStats(): {
34
50
  queued: number;
@@ -38,6 +38,21 @@ export declare class MarkCore {
38
38
  getVisitorId(): string | undefined;
39
39
  setConsent(status: ConsentStatus): void;
40
40
  reset(): void;
41
+ /**
42
+ * Marks or unmarks the current visitor as internal traffic. While set, every
43
+ * subsequent event (track/identify/conversion/consent) is stamped with
44
+ * `is_internal: true`, so the backend can exclude it from customer-facing
45
+ * reports by default.
46
+ *
47
+ * The flag is persisted via the storage adapter (browser: localStorage) so
48
+ * it survives reloads, and is cleared by `reset()` and by
49
+ * `setConsent('denied')`.
50
+ */
51
+ setInternal(value: boolean): void;
52
+ /**
53
+ * Returns the currently persisted internal-traffic flag, if any.
54
+ */
55
+ getInternal(): boolean;
41
56
  flush(): Promise<void>;
42
57
  getStats(): {
43
58
  queued: number;
@@ -45,6 +60,15 @@ export declare class MarkCore {
45
60
  failed: number;
46
61
  dropped: number;
47
62
  };
63
+ /**
64
+ * Stamps `is_internal: true` on the payload when either:
65
+ * - the persistent visitor flag is set (via setInternal), or
66
+ * - the caller passed `is_internal: true` on this specific event.
67
+ *
68
+ * Explicit `is_internal: false` on a single event wins over the visitor flag
69
+ * so individual calls can opt out.
70
+ */
71
+ private applyInternalFlag;
48
72
  private getIdentityFields;
49
73
  private hasConsent;
50
74
  private sanitizeTrackData;
@@ -15,6 +15,8 @@ export interface StorageAdapter {
15
15
  clearAttribution?(): void;
16
16
  clearCookieVisitorId?(): void;
17
17
  rotateVisitorId?(): void;
18
+ getInternal?(): boolean | undefined;
19
+ setInternal?(value: boolean): void;
18
20
  onUpdate?(listener: (updates: Partial<StorageData>) => void): void;
19
21
  getOutbox?(): unknown[];
20
22
  setOutbox?(items: unknown[]): void;
@@ -21,4 +21,6 @@ export declare class StatelessStorage implements StorageAdapter {
21
21
  clearAttribution(): void;
22
22
  clearCookieVisitorId(): void;
23
23
  rotateVisitorId(): void;
24
+ getInternal(): boolean | undefined;
25
+ setInternal(value: boolean): void;
24
26
  }
@@ -28,6 +28,12 @@ export declare class NodeMark {
28
28
  * Use it to associate server-side events with the same visitor dimension as browser events.
29
29
  */
30
30
  getVisitorId(): string | undefined;
31
+ /**
32
+ * Marks the current request/visitor as internal traffic. When using the
33
+ * default StatelessStorage, the flag is scoped to this NodeMark instance.
34
+ */
35
+ setInternal(value: boolean): void;
36
+ getInternal(): boolean;
31
37
  }
32
38
  export declare const createNodeMark: (config: MarkConfig, options?: NodeMarkOptions) => NodeMark;
33
39
  export { StatelessStorage };
@@ -131,6 +131,13 @@ export interface TrackEventData {
131
131
  site_host?: string;
132
132
  user_id?: string;
133
133
  session_id?: string;
134
+ /**
135
+ * When true, stamps the event as internal traffic so the backend can exclude
136
+ * it from customer-facing reports by default. Can be set per-event here, or
137
+ * globally for the current visitor via `Mark.setInternal(true)` /
138
+ * `nodeMark.setInternal(true)`.
139
+ */
140
+ is_internal?: boolean;
134
141
  [key: string]: JsonValue | undefined;
135
142
  }
136
143
  export type ConversionProperties = TrackEventData & {
@@ -171,6 +178,11 @@ export interface StorageData {
171
178
  campaign_id?: string;
172
179
  consent_status?: ConsentStatus;
173
180
  query_params?: Record<string, string>;
181
+ /**
182
+ * When true, all subsequent events are stamped with `is_internal: true`.
183
+ * Set via the `setInternal()` method on the browser/Node client.
184
+ */
185
+ is_internal?: boolean;
174
186
  }
175
187
  export type DomainMode = 'single' | 'subdomain' | 'cross_domain';
176
188
  export interface CrossDomainConfig {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crelora/mark",
3
3
  "private": false,
4
- "version": "0.1.1",
4
+ "version": "0.2.0",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
7
7
  "main": "./dist/browser.umd.js",