@crelora/mark 0.2.1 → 0.3.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/README.md +27 -4
- package/dist/browser.es.js +393 -222
- package/dist/browser.es.js.map +1 -1
- package/dist/browser.umd.js +1 -1
- package/dist/browser.umd.js.map +1 -1
- package/dist/node.cjs +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.es.js +125 -80
- package/dist/node.es.js.map +1 -1
- package/dist/types/browser/BrowserStorage.d.ts +9 -0
- package/dist/types/browser/Mark.d.ts +10 -3
- package/dist/types/browser/pageEngagement.d.ts +8 -0
- package/dist/types/browser/pageLifecycle.d.ts +15 -0
- package/dist/types/browser/pageMetrics.d.ts +5 -0
- package/dist/types/browser/pageScrollDepth.d.ts +4 -0
- package/dist/types/core/MarkCore.d.ts +11 -0
- package/dist/types/core/adapters.d.ts +1 -0
- package/dist/types/core/visitorId.d.ts +5 -0
- package/dist/types/node/StatelessStorage.d.ts +1 -0
- package/dist/types/node/index.d.ts +6 -0
- package/dist/types/types.d.ts +17 -0
- package/package.json +1 -1
package/dist/node.es.js.map
CHANGED
|
@@ -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 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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"}
|
|
1
|
+
{"version":3,"file":"node.es.js","sources":["../src/core/constants.ts","../src/core/TransportError.ts","../src/core/DeliveryQueue.ts","../src/core/visitorId.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","const MAX_VISITOR_ID_LENGTH = 256;\r\n\r\n/**\r\n * Normalizes and validates a trusted first-party visitor identifier.\r\n * Returns the normalized id, or undefined when the value is invalid.\r\n */\r\nexport function normalizeVisitorId(value: unknown): string | undefined {\r\n if (typeof value !== 'string') {\r\n return undefined;\r\n }\r\n const trimmed = value.trim();\r\n if (!trimmed || trimmed.length > MAX_VISITOR_ID_LENGTH) {\r\n return undefined;\r\n }\r\n if (trimmed.includes('@')) {\r\n return undefined;\r\n }\r\n if (/\\s/.test(trimmed)) {\r\n return undefined;\r\n }\r\n return trimmed;\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\nimport { normalizeVisitorId } from './visitorId';\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 this.applySessionFields(transformed);\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\r\n this.ensureSession();\r\n this.applySessionFields(transformed);\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 /**\r\n * Adopts or replaces the stored visitor id with a trusted first-party value.\r\n * Use when an external anonymous id (for example a platform client id) arrives\r\n * after init. Returns false when the id is invalid.\r\n */\r\n public setVisitorId(visitorId: string): boolean {\r\n const normalized = normalizeVisitorId(visitorId);\r\n if (!normalized) {\r\n if (this.config.debug) {\r\n console.warn('[Mark] setVisitorId called with an invalid visitor id.');\r\n }\r\n return false;\r\n }\r\n if (this.deps.storage.setVisitorId) {\r\n return this.deps.storage.setVisitorId(normalized);\r\n }\r\n if (this.deps.storage.getVisitorId() === normalized) {\r\n return true;\r\n }\r\n this.deps.storage.update({ visitor_id: normalized });\r\n return true;\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 const sessionStartedAt = this.deps.storage.getSessionStartedAt?.();\r\n if (sessionId && sessionStartedAt) {\r\n identity.session_started_at = sessionStartedAt;\r\n identity.session_elapsed_ms = Date.now() - Date.parse(sessionStartedAt);\r\n }\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 /**\r\n * Patches session fields after ensureSession when the first event in a session was built\r\n * before rotation created an id. Rotation events keep the previous session_id from identity.\r\n */\r\n private applySessionFields(payload: Record<string, unknown>) {\r\n if (!payload.session_id) {\r\n const sessionId = this.deps.storage.getSessionId?.();\r\n const sessionStartedAt = this.deps.storage.getSessionStartedAt?.();\r\n if (sessionId) payload.session_id = sessionId;\r\n if (sessionStartedAt) payload.session_started_at = sessionStartedAt;\r\n }\r\n const startedAt = payload.session_started_at;\r\n if (payload.session_id && typeof startedAt === 'string') {\r\n payload.session_elapsed_ms = Date.now() - Date.parse(startedAt);\r\n }\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 if (config.visitor_id !== undefined && !normalizeVisitorId(config.visitor_id)) {\r\n throw new Error('[Mark] `visitor_id` must be a non-empty string (max 256 characters).');\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';\nimport { normalizeVisitorId } from '../core/visitorId';\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 setVisitorId(visitorId: string): boolean {\n const normalized = normalizeVisitorId(visitorId);\n if (!normalized) {\n return false;\n }\n this.defaults.visitor_id = normalized;\n return true;\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 { normalizeVisitorId } from '../core/visitorId';\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 * Adopts or replaces the visitor id for this NodeMark instance.\n * With default StatelessStorage, updates the in-memory defaults used by\n * subsequent track/identify calls.\n */\n public setVisitorId(visitorId: string): boolean {\n return this.client.setVisitorId(visitorId);\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 seedVisitorId = normalizeVisitorId(config.visitor_id);\n const storage =\n options.storage ??\n new StatelessStorage({\n visitor_id: options.storageDefaults?.visitor_id ?? seedVisitorId,\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","MAX_VISITOR_ID_LENGTH","normalizeVisitorId","value","TRACK_RESERVED_FIELDS","IDENTIFY_RESERVED_FIELDS","MarkCore","config","deps","event","eventName","isConversion","sanitizedData","restData","payload","siteId","siteHost","transformed","userId","traits","visitorId","normalized","previous","eventValue","stored","clickId","campaignId","sessionId","storedQuery","inputQuery","query","identity","sessionStartedAt","startedAt","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","seedVisitorId","storage"],"mappings":"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"}
|
|
@@ -3,6 +3,10 @@ import type { StorageAdapter } from '../core/adapters';
|
|
|
3
3
|
interface BrowserStorageOptions extends CrossDomainConfig {
|
|
4
4
|
storageKey?: string;
|
|
5
5
|
outboxKey?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Trusted first-party visitor id to adopt when no id is stored yet.
|
|
8
|
+
*/
|
|
9
|
+
seed_visitor_id?: string;
|
|
6
10
|
}
|
|
7
11
|
export declare class BrowserStorage implements StorageAdapter {
|
|
8
12
|
private data;
|
|
@@ -29,6 +33,11 @@ export declare class BrowserStorage implements StorageAdapter {
|
|
|
29
33
|
clearAttribution(): void;
|
|
30
34
|
clearCookieVisitorId(): void;
|
|
31
35
|
rotateVisitorId(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Adopts or replaces the stored visitor id with a trusted first-party value.
|
|
38
|
+
* No-op when the id is invalid or already set to the same value.
|
|
39
|
+
*/
|
|
40
|
+
setVisitorId(visitorId: string): boolean;
|
|
32
41
|
getOutbox(): unknown[];
|
|
33
42
|
setOutbox(items: unknown[]): void;
|
|
34
43
|
private load;
|
|
@@ -8,9 +8,6 @@ export declare class Mark {
|
|
|
8
8
|
private static lastPageviewDedupeKey;
|
|
9
9
|
private static emitAutoPageview;
|
|
10
10
|
private static pendingAttribution;
|
|
11
|
-
private static originalPushState;
|
|
12
|
-
private static originalReplaceState;
|
|
13
|
-
private static popstateHandler;
|
|
14
11
|
private static beaconDrainHandler;
|
|
15
12
|
private static visibilityHandler;
|
|
16
13
|
private static onlineHandler;
|
|
@@ -28,6 +25,15 @@ export declare class Mark {
|
|
|
28
25
|
* when you do not have an authenticated user ID.
|
|
29
26
|
*/
|
|
30
27
|
static getVisitorId(): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Adopts or replaces the stored visitor id with a trusted first-party value.
|
|
30
|
+
* Updates in-memory storage, localStorage, and the visitor cookie immediately.
|
|
31
|
+
* Use when an external anonymous id (for example a platform client id) is
|
|
32
|
+
* available after init. Theme embeds and pixels should call this with the
|
|
33
|
+
* same source id. Returns false when the id is invalid or the SDK is not
|
|
34
|
+
* initialized.
|
|
35
|
+
*/
|
|
36
|
+
static setVisitorId(visitorId: string): boolean;
|
|
31
37
|
static flush(): Promise<void>;
|
|
32
38
|
/**
|
|
33
39
|
* Flags (or un-flags) the current visitor as internal traffic. Persists
|
|
@@ -53,6 +59,7 @@ export declare class Mark {
|
|
|
53
59
|
dropped: number;
|
|
54
60
|
};
|
|
55
61
|
static destroy(): void;
|
|
62
|
+
private static installPageLifecycleIfNeeded;
|
|
56
63
|
private static installPageviewTracking;
|
|
57
64
|
private static refreshAttribution;
|
|
58
65
|
private static flushPendingAttribution;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AutocaptureConfig } from '../types';
|
|
2
|
+
import type { MarkCore } from '../core/MarkCore';
|
|
3
|
+
export type PageEngagementOptions = {
|
|
4
|
+
minActiveMs: number;
|
|
5
|
+
useBeacon: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function resolvePageEngagementOptions(config: true | Exclude<AutocaptureConfig['page_engagement'], boolean | undefined>): PageEngagementOptions;
|
|
8
|
+
export declare function installPageEngagement(client: MarkCore, options: PageEngagementOptions): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type PageChangeContext = {
|
|
2
|
+
previousKey: string | null;
|
|
3
|
+
nextKey: string;
|
|
4
|
+
};
|
|
5
|
+
type PageChangeListener = (ctx: PageChangeContext) => void;
|
|
6
|
+
type ExitListener = () => void;
|
|
7
|
+
export declare function installPageLifecycle(options?: {
|
|
8
|
+
trackRouteChanges?: boolean;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function onBeforePageChange(listener: PageChangeListener): () => void;
|
|
11
|
+
export declare function onAfterPageChange(listener: PageChangeListener): () => void;
|
|
12
|
+
export declare function onPageExit(listener: ExitListener): () => void;
|
|
13
|
+
export declare function teardownPageLifecycle(): void;
|
|
14
|
+
export declare function isPageLifecycleInstalled(): boolean;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Shared per-logical-page scroll depth for scroll_depth events and page_engagement payloads. */
|
|
2
|
+
export declare function resetPageMetrics(): void;
|
|
3
|
+
export declare function getMaxScrollPercent(): number;
|
|
4
|
+
export declare function measureScrollDepthPercent(): number;
|
|
5
|
+
export declare function updateMaxScrollFromCurrentPosition(): number;
|
|
@@ -36,6 +36,12 @@ export declare class MarkCore {
|
|
|
36
36
|
* Used by browser/Node wrappers to expose a stable pseudonymous ID for server-side attribution.
|
|
37
37
|
*/
|
|
38
38
|
getVisitorId(): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Adopts or replaces the stored visitor id with a trusted first-party value.
|
|
41
|
+
* Use when an external anonymous id (for example a platform client id) arrives
|
|
42
|
+
* after init. Returns false when the id is invalid.
|
|
43
|
+
*/
|
|
44
|
+
setVisitorId(visitorId: string): boolean;
|
|
39
45
|
setConsent(status: ConsentStatus): void;
|
|
40
46
|
reset(): void;
|
|
41
47
|
/**
|
|
@@ -70,6 +76,11 @@ export declare class MarkCore {
|
|
|
70
76
|
*/
|
|
71
77
|
private applyInternalFlag;
|
|
72
78
|
private getIdentityFields;
|
|
79
|
+
/**
|
|
80
|
+
* Patches session fields after ensureSession when the first event in a session was built
|
|
81
|
+
* before rotation created an id. Rotation events keep the previous session_id from identity.
|
|
82
|
+
*/
|
|
83
|
+
private applySessionFields;
|
|
73
84
|
private hasConsent;
|
|
74
85
|
private sanitizeTrackData;
|
|
75
86
|
private sanitizeIdentifyTraits;
|
|
@@ -15,6 +15,7 @@ export interface StorageAdapter {
|
|
|
15
15
|
clearAttribution?(): void;
|
|
16
16
|
clearCookieVisitorId?(): void;
|
|
17
17
|
rotateVisitorId?(): void;
|
|
18
|
+
setVisitorId?(visitorId: string): boolean;
|
|
18
19
|
getInternal?(): boolean | undefined;
|
|
19
20
|
setInternal?(value: boolean): void;
|
|
20
21
|
onUpdate?(listener: (updates: Partial<StorageData>) => void): void;
|
|
@@ -21,6 +21,7 @@ export declare class StatelessStorage implements StorageAdapter {
|
|
|
21
21
|
clearAttribution(): void;
|
|
22
22
|
clearCookieVisitorId(): void;
|
|
23
23
|
rotateVisitorId(): void;
|
|
24
|
+
setVisitorId(visitorId: string): boolean;
|
|
24
25
|
getInternal(): boolean | undefined;
|
|
25
26
|
setInternal(value: boolean): void;
|
|
26
27
|
}
|
|
@@ -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
|
+
* Adopts or replaces the visitor id for this NodeMark instance.
|
|
33
|
+
* With default StatelessStorage, updates the in-memory defaults used by
|
|
34
|
+
* subsequent track/identify calls.
|
|
35
|
+
*/
|
|
36
|
+
setVisitorId(visitorId: string): boolean;
|
|
31
37
|
/**
|
|
32
38
|
* Marks the current request/visitor as internal traffic. When using the
|
|
33
39
|
* default StatelessStorage, the flag is scoped to this NodeMark instance.
|
package/dist/types/types.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export interface AutocaptureConfig {
|
|
|
10
10
|
form_submit?: boolean;
|
|
11
11
|
outbound_link?: boolean;
|
|
12
12
|
scroll_depth?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When enabled, emits one `page_engagement` summary per logical page when the user
|
|
15
|
+
* navigates away or unloads (not on tab background alone). Visible active time
|
|
16
|
+
* must meet `min_active_ms` (default 10000). Tab hide/show pauses the clock.
|
|
17
|
+
*/
|
|
18
|
+
page_engagement?: boolean | {
|
|
19
|
+
min_active_ms?: number;
|
|
20
|
+
use_beacon?: boolean;
|
|
21
|
+
};
|
|
13
22
|
web_vitals?: boolean;
|
|
14
23
|
}
|
|
15
24
|
export interface BatchingConfig {
|
|
@@ -81,6 +90,14 @@ export interface MarkConfig {
|
|
|
81
90
|
* If not provided, the SDK will use the host from page context (browser) or leave it undefined (Node).
|
|
82
91
|
*/
|
|
83
92
|
site_host?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Optional trusted first-party visitor identifier to adopt when no visitor id
|
|
95
|
+
* is stored yet (for example a platform client id). Ignored when storage
|
|
96
|
+
* already contains a visitor id. Use `setVisitorId()` when the id arrives
|
|
97
|
+
* after init. Only pass ids you control (first-party cookies, platform
|
|
98
|
+
* client ids) — not email addresses, order ids, or untrusted URL params.
|
|
99
|
+
*/
|
|
100
|
+
visitor_id?: string;
|
|
84
101
|
/**
|
|
85
102
|
* Additional query parameter keys to capture for attribution.
|
|
86
103
|
* Keys are matched case-insensitively and stored in lowercase.
|