1/* Shared attribution parsing. No network calls or contact data. */ 2(function(root,factory){var api=factory();if(typeof module==='object'&&module.exports)module.exports=api;else root.SSAI_ATTR=api;})(typeof window==='object'?window:this,function(){ 3 'use strict'; 4 var fields=['utm_source','utm_medium','utm_campaign','utm_term','utm_content','claim_id','asset_id','asset_variant']; 5 // Asset Claim IDs follow ssai-{claim}-{format}-{variant}, e.g. ssai-designfit-short-a. 6 var FULL_ID=/^ssai-[a-z0-9_]+-[a-z0-9_]+-[a-z0-9_]+$/i,FAMILY=/^ssai-[a-z0-9_]+$/i,FORMAT_VARIANT=/^[a-z0-9_]+-[a-z0-9_]+$/i; 7 // claim_id and asset_id from one touch: an explicit claim_id/asset_id param wins; otherwise a full 8 // ID carried in utm_content or utm_campaign, or utm_campaign=ssai-{claim} + utm_content={format}-{variant}. 9 // Legacy campaign names never become claim IDs. 10 function claimOf(t){ 11 if(!t||typeof t!=='object')return {claim_id:null,asset_id:null}; 12 var claim=t.claim?String(t.claim).trim().slice(0,200):'',asset=t.asset?String(t.asset).trim().slice(0,200):'',camp=String(t.camp||'').trim(),cont=String(t.cont||'').trim(); 13 if(!claim){ 14 if(FULL_ID.test(cont))claim=cont.toLowerCase(); 15 else if(FULL_ID.test(camp))claim=camp.toLowerCase(); 16 else if(FAMILY.test(camp)&&FORMAT_VARIANT.test(cont))claim=(camp+'-'+cont).toLowerCase(); 17 } 18 if(!asset&&FULL_ID.test(claim))asset=claim; 19 return {claim_id:claim||null,asset_id:asset||null}; 20 } 21 function earliest(journey,touches){ 22 var best=null;touches.forEach(function(t){var d=Date.parse(t&&t.t);if(isFinite(d)&&(best===null||d<best))best=d;}); 23 if(best===null&&journey&&journey.first_seen){var f=Date.parse(journey.first_seen);if(isFinite(f))best=f;} 24 return best===null?null:new Date(best).toISOString(); 25 } 26 function parse(search){ 27 var q=new URLSearchParams(search||''),values={},duplicates={}; 28 fields.concat(['gclid','wbraid','gbraid','li_fat_id','msclkid','fbclid','post_id','ssai_pid']).forEac
28h(function(key){ 29 var raw=q.getAll(key).map(function(v){return v.trim().slice(0,900);}).filter(Boolean); 30 var normalized=raw.map(function(v){return key==='utm_source'||key==='utm_medium'?v.toLowerCase():v;}); 31 var unique=normalized.filter(function(v,i,a){return a.indexOf(v)===i;}); 32 values[key]=unique.length===1?unique[0]:null; 33 if(raw.length>1)duplicates[key]={values:raw,ambiguous:unique.length>1}; 34 }); 35 return {values:values,duplicates:duplicates}; 36 } 37 function query(key){return parse(typeof location==='object'?location.search:'').values[key]||null;} 38 function firstTouch(journey){var touches=journey&&Array.isArray(journey.touches)?journey.touches:[];return touches.find(function(t){return t&&t.ch&&t.ch!=='direct'&&t.ch!=='unknown';})||touches[0]||null;} 39 function booking(journey,search){ 40 var parsed=parse(search),first=firstTouch(journey),touches=journey&&Array.isArray(journey.touches)?journey.touches.filter(function(t){return t&&typeof t==='object';}):[],last=touches[touches.length-1]||null,ids={}; 41 touches.forEach(function(t){Object.keys(t.ids||{}).forEach(function(k){ids[k]=t.ids[k];});}); 42 Object.keys(parsed.values).forEach(function(k){if(['gclid','wbraid','gbraid','li_fat_id','msclkid','fbclid'].indexOf(k)!==-1&&parsed.values[k])ids[k]=parsed.values[k];}); 43 var utm={};['source','medium','campaign','term','content'].forEach(function(k){var field={source:'ch',medium:'med',campaign:'camp',term:'term',content:'cont'}[k];utm[k]=first?first[field]||null:parsed.values['utm_'+k]||null;}); 44 // Claim: the first touch's, else the earliest touch carrying one, else this URL's. 45 var here={claim:parsed.values.claim_id,asset:parsed.values.asset_id,camp:parsed.values.utm_campaign,cont:parsed.values.utm_content},claim=claimOf(first); 46 if(!claim.claim_id)for(var i=0;i<touches.length&&!claim.claim_id;i++)claim=claimOf(touches[i]); 47 if(!claim.claim_id)claim=claimOf(here); 48 if(!claim.asset_id&&parsed.values.asset_id)claim.asset_id=parsed.values.asset_id; 49 return {utm:utm,gclid:ids.gclid||'',ids:ids,first:first,last:last,claim_id:claim.claim_id,asset_id:claim.asset_id,first_touch_at:earliest(journey,touches),asset_variant:first&&first.variant||parsed.values.asset_variant||null,duplicates:parsed.duplicates,touches:touches}; 50 } 51 function current(){var j=typeof window==='object'&&window.__ssaiJourney||null;try{if(!j)j=JSON.parse(localStorage.getItem('ssai_journey')||'null');}catch(e){}return booking(j,typeof location==='object'?location.search:'');} 52 function packed(data){ 53 var bits=[];if(data.utm.content)bits.push('asset:'+encodeURIComponent(data.utm.content)); 54 if(data.claim_id)bits.push('claim:'+encodeURIComponent(data.claim_id));if(data.asset_variant)bits.push('variant:'+encodeURIComponent(data.asset_variant)); 55 if(data.asset_id&&data.asset_id!==data.claim_id)bits.push('aid:'+encodeURIComponent(data.asset_id));if(data.first_touch_at)bits.push('ft:'+encodeURIComponent(data.first_touch_at)); 56 [['gclid','gclid'],['wbraid','wbraid'],['gbraid','gbraid'],['li_fat_id','li'],['msclkid','ms']].forEach(function(pair){if(data.ids[pair[0]])bits.push(pair[1]+':'+data.ids[pair[0]]);}); 57 return bits.join('|'); 58 } 59 function decorate(url,data){ 60 var u=new URL(url);['source','medium','campaign','term'].forEach(function(k){u.searchParams.delete('utm_'+k);if(data.utm[k])u.searchParams.set('utm_'+k,data.utm[k]);}); 61 u.searchParams.delete('utm_content');var content=packed(data);if(content)u.searchParams.set('utm_content',content); 62 return u.toString(); 63 } 64 return {parse:parse,query:query,firstTouch:firstTouch,claimOf:claimOf,booking:booking,current:current,packed:packed,decorate:decorate}; 65});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.