|
| 1 | +/** |
| 2 | + * Attribution data extracted from URL parameters and referrer |
| 3 | + */ |
| 4 | +export interface AttributionData { |
| 5 | + /** Campaign source (e.g., 'google', 'facebook') */ |
| 6 | + source?: string; |
| 7 | + /** Campaign medium (e.g., 'cpc', 'email') */ |
| 8 | + medium?: string; |
| 9 | + /** Campaign name */ |
| 10 | + campaign?: string; |
| 11 | + /** Campaign term (keywords) */ |
| 12 | + term?: string; |
| 13 | + /** Campaign content (A/B testing) */ |
| 14 | + content?: string; |
| 15 | + /** Referrer URL */ |
| 16 | + referrer?: string; |
| 17 | + /** Landing page URL */ |
| 18 | + landingPage?: string; |
| 19 | + /** First touch timestamp */ |
| 20 | + firstTouchTime?: number; |
| 21 | + /** Last touch timestamp */ |
| 22 | + lastTouchTime?: number; |
| 23 | + /** Custom attribution parameters */ |
| 24 | + custom?: Record<string, string>; |
| 25 | +} |
| 26 | + |
| 27 | +/** |
| 28 | + * Parse URL parameters for attribution data |
| 29 | + */ |
| 30 | +export function parseAttributionFromUrl(url?: string): AttributionData { |
| 31 | + const urlObj = typeof window !== 'undefined' && !url |
| 32 | + ? new URL(window.location.href) |
| 33 | + : url |
| 34 | + ? new URL(url, typeof window !== 'undefined' ? window.location.origin : 'https://example.com') |
| 35 | + : null; |
| 36 | + |
| 37 | + if (!urlObj) { |
| 38 | + return {}; |
| 39 | + } |
| 40 | + |
| 41 | + const params = urlObj.searchParams; |
| 42 | + const attribution: AttributionData = { |
| 43 | + landingPage: urlObj.href, |
| 44 | + firstTouchTime: Date.now(), |
| 45 | + lastTouchTime: Date.now(), |
| 46 | + custom: {}, |
| 47 | + }; |
| 48 | + |
| 49 | + // Standard UTM parameters |
| 50 | + const utmSource = params.get('utm_source'); |
| 51 | + const utmMedium = params.get('utm_medium'); |
| 52 | + const utmCampaign = params.get('utm_campaign'); |
| 53 | + const utmTerm = params.get('utm_term'); |
| 54 | + const utmContent = params.get('utm_content'); |
| 55 | + |
| 56 | + if (utmSource) attribution.source = utmSource; |
| 57 | + if (utmMedium) attribution.medium = utmMedium; |
| 58 | + if (utmCampaign) attribution.campaign = utmCampaign; |
| 59 | + if (utmTerm) attribution.term = utmTerm; |
| 60 | + if (utmContent) attribution.content = utmContent; |
| 61 | + |
| 62 | + // AppsFlyer parameters (af_*) |
| 63 | + const afSource = params.get('af_source') || params.get('pid'); |
| 64 | + const afMedium = params.get('af_medium') || params.get('c'); |
| 65 | + const afCampaign = params.get('af_campaign') || params.get('af_c'); |
| 66 | + const afAdset = params.get('af_adset') || params.get('af_adset_id'); |
| 67 | + const afAd = params.get('af_ad') || params.get('af_ad_id'); |
| 68 | + |
| 69 | + if (afSource && !attribution.source) attribution.source = afSource; |
| 70 | + if (afMedium && !attribution.medium) attribution.medium = afMedium; |
| 71 | + if (afCampaign && !attribution.campaign) attribution.campaign = afCampaign; |
| 72 | + if (afAdset) attribution.custom = { ...attribution.custom, af_adset: afAdset }; |
| 73 | + if (afAd) attribution.custom = { ...attribution.custom, af_ad: afAd }; |
| 74 | + |
| 75 | + // Adjust parameters (adjust_*) |
| 76 | + const adjustSource = params.get('adjust_source') || params.get('network'); |
| 77 | + const adjustCampaign = params.get('adjust_campaign') || params.get('campaign'); |
| 78 | + const adjustAdgroup = params.get('adjust_adgroup') || params.get('adgroup'); |
| 79 | + const adjustCreative = params.get('adjust_creative') || params.get('creative'); |
| 80 | + |
| 81 | + if (adjustSource && !attribution.source) attribution.source = adjustSource; |
| 82 | + if (adjustCampaign && !attribution.campaign) attribution.campaign = adjustCampaign; |
| 83 | + if (adjustAdgroup) attribution.custom = { ...attribution.custom, adjust_adgroup: adjustAdgroup }; |
| 84 | + if (adjustCreative) attribution.custom = { ...attribution.custom, adjust_creative: adjustCreative }; |
| 85 | + |
| 86 | + // Referrer |
| 87 | + if (typeof document !== 'undefined' && document.referrer) { |
| 88 | + try { |
| 89 | + const referrerUrl = new URL(document.referrer); |
| 90 | + attribution.referrer = referrerUrl.hostname; |
| 91 | + } catch { |
| 92 | + attribution.referrer = document.referrer; |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + // Collect any remaining custom parameters |
| 97 | + for (const [key, value] of params.entries()) { |
| 98 | + if ( |
| 99 | + !key.startsWith('utm_') && |
| 100 | + !key.startsWith('af_') && |
| 101 | + !key.startsWith('adjust_') && |
| 102 | + key !== 'pid' && |
| 103 | + key !== 'c' && |
| 104 | + key !== 'network' && |
| 105 | + key !== 'campaign' && |
| 106 | + key !== 'adgroup' && |
| 107 | + key !== 'creative' |
| 108 | + ) { |
| 109 | + attribution.custom = attribution.custom || {}; |
| 110 | + attribution.custom[key] = value; |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + return attribution; |
| 115 | +} |
| 116 | + |
| 117 | +/** |
| 118 | + * Merge new attribution data with existing data |
| 119 | + */ |
| 120 | +export function mergeAttributionData( |
| 121 | + existing: AttributionData | null, |
| 122 | + incoming: AttributionData, |
| 123 | +): AttributionData { |
| 124 | + if (!existing) { |
| 125 | + return incoming; |
| 126 | + } |
| 127 | + |
| 128 | + return { |
| 129 | + ...existing, |
| 130 | + // Keep first touch time from existing |
| 131 | + firstTouchTime: existing.firstTouchTime || incoming.firstTouchTime, |
| 132 | + // Update last touch time |
| 133 | + lastTouchTime: incoming.lastTouchTime || Date.now(), |
| 134 | + // Merge custom parameters |
| 135 | + custom: { |
| 136 | + ...existing.custom, |
| 137 | + ...incoming.custom, |
| 138 | + }, |
| 139 | + // Prefer existing source/medium/campaign unless incoming has values |
| 140 | + source: incoming.source || existing.source, |
| 141 | + medium: incoming.medium || existing.medium, |
| 142 | + campaign: incoming.campaign || existing.campaign, |
| 143 | + term: incoming.term || existing.term, |
| 144 | + content: incoming.content || existing.content, |
| 145 | + }; |
| 146 | +} |
| 147 | + |
0 commit comments