UNPKG

tiny-utm-keeper

Version:

Zero-dependency, TypeScript-first UTM parameter tracker for first-touch/last-touch attribution. SSR-friendly.

190 lines (189 loc) 5.34 kB
import { getStoredUTMData, saveUTMData, clearUTMData, isBrowser, } from './storage.js'; import { extractUTMsFromURL, extractUTMsFromCurrentURL, appendUTMsToURL, hasUTMParams, utmParamsToObject, } from './utils.js'; /** * Default configuration */ const DEFAULT_CONFIG = { mode: 'first-touch', expirationDays: 30, storageKey: 'utm_keeper', autoCapture: true, }; /** * UTMKeeper class - Main API */ export class UTMKeeper { constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config, }; // Auto-capture on init if enabled if (this.config.autoCapture && isBrowser()) { this.capture(config.captureUrl); } } /** * Capture UTM parameters from URL */ capture(url) { if (!isBrowser()) { return false; } const utmParams = url ? extractUTMsFromURL(url) : extractUTMsFromCurrentURL(); if (!hasUTMParams(utmParams)) { return false; } // Check attribution mode if (this.config.mode === 'first-touch') { const existing = getStoredUTMData(this.config.storageKey); if (existing && hasUTMParams(existing.params)) { // Already have first-touch data, don't override return false; } } // Save UTM data (for both first-touch and last-touch) return saveUTMData(this.config.storageKey, utmParams, this.config.expirationDays); } /** * Get stored UTM parameters */ getUTMs() { if (!isBrowser()) { return null; } const data = getStoredUTMData(this.config.storageKey); return data ? data.params : null; } /** * Get stored UTM data with metadata */ getUTMData() { if (!isBrowser()) { return null; } return getStoredUTMData(this.config.storageKey); } /** * Clear stored UTM parameters */ clear() { return clearUTMData(this.config.storageKey); } /** * Append stored UTMs to a URL */ appendToURL(url) { const utms = this.getUTMs(); if (!hasUTMParams(utms)) { return url; } return appendUTMsToURL(url, utms); } /** * Get UTM parameters as object for API calls */ getUTMObject() { const utms = this.getUTMs(); if (!hasUTMParams(utms)) { return {}; } return utmParamsToObject(utms); } /** * Create a fetch wrapper that automatically injects UTMs */ createFetch() { const originalFetch = fetch; const self = this; return function utmFetch(input, init) { // Skip if explicitly disabled or not in browser if (!isBrowser() || init?.skipUTM) { const { skipUTM, ...cleanInit } = init || {}; return originalFetch(input, cleanInit); } const utms = self.getUTMs(); if (!hasUTMParams(utms)) { return originalFetch(input, init); } // Handle URL injection let modifiedInput = input; if (typeof input === 'string') { modifiedInput = appendUTMsToURL(input, utms); } else if (input instanceof URL) { modifiedInput = new URL(appendUTMsToURL(input.toString(), utms)); } else if (input instanceof Request) { modifiedInput = new Request(appendUTMsToURL(input.url, utms), input); } return originalFetch(modifiedInput, init); }; } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config, }; } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Check if UTMs are currently stored */ hasStoredUTMs() { return hasUTMParams(this.getUTMs()); } /** * Get attribution mode */ getMode() { return this.config.mode; } /** * Set attribution mode */ setMode(mode) { this.config.mode = mode; } } /** * Create a singleton instance for convenience */ let defaultInstance = null; /** * Initialize the default UTMKeeper instance */ export const init = (config = {}) => { defaultInstance = new UTMKeeper(config); return defaultInstance; }; /** * Get the default instance (creates one if it doesn't exist) */ export const getInstance = () => { if (!defaultInstance) { defaultInstance = new UTMKeeper(); } return defaultInstance; }; /** * Convenience functions using the default instance */ export const capture = (url) => getInstance().capture(url); export const getUTMs = () => getInstance().getUTMs(); export const getUTMData = () => getInstance().getUTMData(); export const clear = () => getInstance().clear(); export const appendToURL = (url) => getInstance().appendToURL(url); export const getUTMObject = () => getInstance().getUTMObject(); export const createFetch = () => getInstance().createFetch(); export const hasStoredUTMs = () => getInstance().hasStoredUTMs();