PageSourceSearch

https://www.solarsurvey.ai/attribution.js

js solarsurvey.ai collected 2026-10-05 14:22:25 UTC 5,613 bytes, 65 lines download raw bytes

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.