@webority/analytics 0.6.0 → 0.8.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/dist/webority-analytics-site.global.js +1 -1
- package/package.json +1 -1
- package/src/analytics.js +19 -0
- package/src/attribution.js +18 -0
- package/src/channels.js +193 -0
- package/src/consent.js +9 -0
- package/src/index.js +6 -2
- package/src/site-events.js +35 -0
- package/src/site.js +3 -0
- package/src/tags.js +15 -2
- package/src/vocabulary.js +16 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
var WeborityAnalytics=(()=>{var y=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var V=(t,e)=>{for(var n in e)y(t,n,{get:e[n],enumerable:!0})},Q=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of J(e))!X.call(t,i)&&i!==n&&y(t,i,{get:()=>e[i],enumerable:!(o=Y(e,i))||o.enumerable});return t};var W=t=>Q(y({},"__esModule",{value:!0}),t);var yt={};V(yt,{CONSENT_CHANGE_EVENT:()=>_,CONSENT_COOKIE_NAME:()=>l,captureAttribution:()=>$,getAttribution:()=>j,getConsent:()=>s,initConsent:()=>A,loadTags:()=>B,onConsentChange:()=>c,requiresOptIn:()=>k,saveConsent:()=>R});var w=Object.freeze(["AT","BE","BG","HR","CY","CZ","DK","EE","FI","FR","DE","GR","HU","IE","IT","LV","LT","LU","MT","NL","PL","PT","RO","SK","SI","ES","SE","IS","LI","NO","GB","CH"]);function k(t){let e=typeof t=="string"?t.trim().toUpperCase():"";return e.length!==2||e==="XX"||e==="T1"?!0:w.includes(e)}var l="webority_cookie_consent",_="webority-consent-change",L=365,r={initialized:!1,requiresOptIn:!0,strictOptIn:!1,adStorage:!0,cookieDays:L,cookieDomain:null,saved:null},C=new Set;function A(t){let e=t?.strictOptIn??!1;if(typeof e!="boolean")throw new Error("@webority/analytics: initConsent strictOptIn must be true or false, or left out.");if(e){if(t.requiresOptIn!==void 0&&t.requiresOptIn!==!0)throw new Error("@webority/analytics: initConsent strictOptIn asks every visitor to opt in, so requiresOptIn must be true or left out.")}else if(typeof t?.requiresOptIn!="boolean")throw new Error("@webority/analytics: initConsent needs requiresOptIn (true or false), decided from the visitor's country.");if(t.cookieDomain!==void 0&&(typeof t.cookieDomain!="string"||!t.cookieDomain.trim()))throw new Error('@webority/analytics: initConsent cookieDomain must be a domain such as ".example.com", or left out.');if(t.cookieDays!==void 0&&!(Number.isInteger(t.cookieDays)&&t.cookieDays>0))throw new Error("@webority/analytics: initConsent cookieDays must be a whole number of days above zero, or left out.");if(t.adStorage!==void 0&&typeof t.adStorage!="boolean")throw new Error("@webority/analytics: initConsent adStorage must be true or false, or left out.");r.initialized=!0,r.strictOptIn=e,r.requiresOptIn=e||t.requiresOptIn,r.adStorage=t.adStorage??!e,r.cookieDays=t.cookieDays??L,r.cookieDomain=t.cookieDomain?.trim()??null;let{saved:n,entryCount:o}=et();r.saved=n,r.cookieDomain&&o>1&&n&&q(n),tt(),r.saved&&U(r.saved)}function f(){return r.initialized}function v(){return r.strictOptIn}function x(){return r.adStorage}function s(){z("getConsent");let t=r.saved;if(t)return{essential:!0,analytics:t.analytics,functional:t.functional,marketing:t.marketing,decided:!0};let e=!r.requiresOptIn;return{essential:!0,analytics:e,functional:e,marketing:e,decided:!1}}function R(t){z("saveConsent");let e={essential:!0,analytics:t?.analytics===!0,functional:t?.functional===!0,marketing:t?.marketing===!0,timestamp:new Date().toISOString()};q(e),r.saved=e,U(e),Z(s())}function c(t){return C.add(t),()=>C.delete(t)}function Z(t){for(let e of C)try{e(t)}catch(n){queueMicrotask(()=>{throw n})}typeof window?.dispatchEvent=="function"&&typeof CustomEvent=="function"&&window.dispatchEvent(new CustomEvent(_,{detail:t}))}function z(t){if(!r.initialized)throw new Error(`@webority/analytics: call initConsent before ${t}.`)}function d(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function tt(){if(r.strictOptIn){d("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500});return}d("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500,region:[...w]}),d("consent","default",{ad_storage:"granted",ad_user_data:"granted",ad_personalization:"granted",analytics_storage:"granted",functionality_storage:"granted",personalization_storage:"granted",security_storage:"granted"})}function U(t){let e=o=>o?"granted":"denied",n=t.marketing&&r.adStorage;d("consent","update",{analytics_storage:e(t.analytics),functionality_storage:e(t.functional),personalization_storage:e(t.functional),ad_storage:e(n),ad_user_data:e(n),ad_personalization:e(n)})}function et(){let t=`${l}=`,e=(document.cookie||"").split(";").map(o=>o.trim()).filter(o=>o.startsWith(t)),n=e.map(o=>nt(o.slice(t.length))).filter(Boolean);return{saved:ot(n),entryCount:e.length}}function nt(t){try{let e=JSON.parse(decodeURIComponent(t));return!e||typeof e!="object"?null:{essential:!0,analytics:e.analytics===!0,functional:e.functional===!0,marketing:e.marketing===!0,timestamp:e.timestamp??null}}catch{return null}}function ot(t){if(t.length===0)return null;let e=t.map(o=>typeof o.timestamp=="string"?Date.parse(o.timestamp):Number.NaN);if(e.some(Number.isNaN))return t.at(-1);let n=0;for(let o=1;o<t.length;o++)e[o]>=e[n]&&(n=o);return t[n]}function q(t){let e=typeof t.timestamp=="string"?Date.parse(t.timestamp):Number.NaN,n=(Number.isNaN(e)?Date.now():e)+r.cookieDays*24*60*60*1e3,o=window.location?.protocol==="https:"?";Secure":"";r.cookieDomain&&D(`${l}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/;SameSite=Lax${o}`);let i=r.cookieDomain?`;domain=${r.cookieDomain}`:"";D(`${l}=${encodeURIComponent(JSON.stringify(t))};expires=${new Date(n).toUTCString()};path=/${i};SameSite=Lax${o}`)}function D(t){document.cookie=t}var p="webority_attribution",rt=2160*60*60*1e3,it=200,I=500,at=Object.freeze({utm_source:"utmSource",utm_medium:"utmMedium",utm_campaign:"utmCampaign",utm_content:"utmContent",utm_term:"utmTerm",gclid:"gclid",gbraid:"gbraid",wbraid:"wbraid",fbclid:"fbclid",msclkid:"msclkid",li_fat_id:"liFatId"}),F=!1;function $(){K("captureAttribution"),ut();let t=st();if(!t)return;let e=T(h());G(h(),{firstTouch:e?.firstTouch??t,lastTouch:t})}function j(){return K("getAttribution"),T(h())}function st(){let t=new URLSearchParams(window.location.search),e={};for(let[o,i]of Object.entries(at)){let a=m(t.get(o),it);a&&(e[i]=a)}let n=m(t.get("referrer"),I)||ct();return n&&(e.referrer=n),Object.keys(e).length===0?null:(e.landingPage=m(window.location.pathname,I),e.capturedDateTimeUtc=new Date().toISOString(),e)}function ct(){let t=document.referrer;if(!t)return"";try{return new URL(t).hostname===window.location.hostname?"":m(t,I)}catch{return""}}function ut(){F||(F=!0,c(t=>{let e=t.marketing?S():E(),n=t.marketing?E():S(),o=T(e);lt(e),o&&G(n,o)}))}function h(){return s().marketing?E():S()}function E(){try{return window.localStorage}catch{return null}}function S(){try{return window.sessionStorage}catch{return null}}function T(t){try{let e=t?.getItem(p);if(!e)return null;let n=JSON.parse(e),o=M(n?.firstTouch),i=M(n?.lastTouch);return!o&&!i?(t.removeItem(p),null):{firstTouch:o??i,lastTouch:i??o}}catch{return null}}function G(t,e){try{t?.setItem(p,JSON.stringify(e))}catch{}}function lt(t){try{t?.removeItem(p)}catch{}}function M(t){if(!t||typeof t!="object")return null;let e=Date.parse(t.capturedDateTimeUtc);return Number.isFinite(e)&&Date.now()-e<=rt?t:null}function m(t,e){let n=String(t??"").trim();return n?n.slice(0,e):""}function K(t){if(!f())throw new Error(`@webority/analytics: call initConsent before ${t}.`)}var dt=["scroll","mousemove","touchstart","keydown","pointerdown"],P=1500,ft=3e3,mt=100,u={loaded:!1,clarityId:null,clarityLoaded:!1};function B(t={}){if(!f())throw new Error("@webority/analytics: call initConsent before loadTags.");if(u.loaded)return;u.loaded=!0;let{gtmId:e,clarityId:n,ahrefsKey:o}=t,i=v();if(o){let a=()=>b("https://analytics.ahrefs.com/analytics.js",{"data-key":o});i?H(g=>g.analytics,a):a()}if(e){let a=()=>pt(()=>{window.dataLayer=window.dataLayer||[],window.dataLayer.push({"gtm.start":Date.now(),event:"gtm.js"}),b(`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(e)}`)});if(i){let g=x();H(N=>N.analytics||N.marketing&&g,a)}else a()}n&&(u.clarityId=n,s().analytics&>(),c(a=>{a.analytics&&O()}))}function H(t,e){if(t(s())){e();return}let n=c(o=>{t(o)&&(n(),e())})}function pt(t){let e=!1,n=()=>{e||(e=!0,t())};for(let o of dt)window.addEventListener(o,n,{once:!0,passive:!0});document.readyState==="complete"?setTimeout(n,P):window.addEventListener("load",()=>setTimeout(n,P),{once:!0})}function gt(){let t=()=>{window.scrollY>mt&&(window.removeEventListener("scroll",t),O())};window.addEventListener("scroll",t,{passive:!0}),setTimeout(O,ft)}function O(){u.clarityLoaded||!u.clarityId||(u.clarityLoaded=!0,window.clarity=window.clarity||function(){window.clarity.q=window.clarity.q||[],window.clarity.q.push(arguments)},b(`https://www.clarity.ms/tag/${encodeURIComponent(u.clarityId)}`,{crossorigin:"anonymous"}))}function b(t,e={}){let n=document.createElement("script");n.async=!0,n.src=t;for(let[o,i]of Object.entries(e))n.setAttribute(o,i);document.head.appendChild(n)}return W(yt);})();
|
|
1
|
+
var WeborityAnalytics=(()=>{var w=Object.defineProperty;var at=Object.getOwnPropertyDescriptor;var st=Object.getOwnPropertyNames;var ct=Object.prototype.hasOwnProperty;var ut=(t,e)=>{for(var n in e)w(t,n,{get:e[n],enumerable:!0})},dt=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of st(e))!ct.call(t,r)&&r!==n&&w(t,r,{get:()=>e[r],enumerable:!(o=at(e,r))||o.enumerable});return t};var lt=t=>dt(w({},"__esModule",{value:!0}),t);var Ft={};ut(Ft,{CONSENT_CHANGE_EVENT:()=>N,CONSENT_COOKIE_NAME:()=>_,captureAttribution:()=>Q,channels:()=>s,events:()=>a,getAttribution:()=>I,getConsent:()=>d,initConsent:()=>$,loadTags:()=>rt,onConsentChange:()=>f,requiresOptIn:()=>B,resolveAiSource:()=>S,resolveChannel:()=>p,saveConsent:()=>j,trackSiteEvent:()=>et});var ft=["google","googleads","adwords","youtube"],mt=["meta","facebook","fb","instagram","ig","messenger"],pt=["linkedin","lnkd"],gt=["bing","microsoft","microsoftads","msn"],M=Object.freeze([["chatgpt",["chatgpt.com","chat.openai.com"]],["perplexity",["perplexity.ai"]],["gemini",["gemini.google.com"]],["copilot",["copilot.microsoft.com"]],["claude",["claude.ai"]],["you",["you.com"]],["phind",["phind.com"]],["deepseek",["deepseek.com"]],["grok",["grok.com"]],["meta_ai",["meta.ai"]],["poe",["poe.com"]]]),_t=["bing.com","duckduckgo.com","search.yahoo.com","ecosia.org","search.brave.com"],Ct=["google","yandex"],St=["facebook.com","fb.com","instagram.com","linkedin.com","lnkd.in","twitter.com","t.co","x.com","reddit.com","pinterest.com","youtube.com","youtu.be","tiktok.com","whatsapp.com","threads.net","t.me","telegram.org"],s=Object.freeze({DIRECT:"direct",GOOGLE:"google",META:"meta",LINKEDIN:"linkedin",MICROSOFT:"microsoft",OTHER:"other",AI_ASSISTANT:"ai_assistant",ORGANIC_SEARCH:"organic_search",ORGANIC_SOCIAL:"organic_social",REFERRAL:"referral"});function p(t){if(!t)return s.DIRECT;let e=u(t.utmSource);if(e){let r=C(e);if(ft.includes(r))return s.GOOGLE;if(mt.includes(r))return s.META;if(pt.includes(r))return s.LINKEDIN;if(gt.includes(r))return s.MICROSOFT}if(u(t.fbclid))return s.META;if(u(t.gclid)||u(t.gbraid)||u(t.wbraid))return s.GOOGLE;if(u(t.liFatId))return s.LINKEDIN;if(u(t.msclkid))return s.MICROSOFT;if(P(e))return s.AI_ASSISTANT;if(e||u(t.utmMedium)||u(t.utmCampaign)||u(t.utmContent)||u(t.utmTerm))return s.OTHER;let o=F(t.referrer);return o?G(o)?s.AI_ASSISTANT:Et(o)?s.ORGANIC_SEARCH:St.some(r=>z(o,r))?s.ORGANIC_SOCIAL:s.REFERRAL:s.DIRECT}function S(t){if(!t)return"";let e=P(u(t.utmSource));if(e)return e;let n=F(t.referrer);return n?G(n):""}function P(t){if(!t)return"";let e=C(t);for(let[n,o]of M)if(C(n)===e||o.some(r=>C(r)===e))return n;return""}function G(t){for(let[e,n]of M)if(n.some(o=>z(t,o)))return e;return""}function Et(t){return _t.includes(t)?!0:Ct.some(e=>{if(!t.startsWith(`${e}.`))return!1;let n=t.slice(e.length+1).split(".");return n.length===1||n.length===2&&(n[0]==="co"||n[0]==="com")})}function z(t,e){return t===e||t.endsWith(`.${e}`)}function F(t){let e=u(t);if(!e)return"";try{let n=new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`https://${e}`).hostname.toLowerCase();return n.startsWith("www.")?n.slice(4):n}catch{return""}}function C(t){return String(t).toLowerCase().replace(/[^\p{L}\p{N}]/gu,"")}function u(t){return String(t??"").trim()}var O=Object.freeze(["AT","BE","BG","HR","CY","CZ","DK","EE","FI","FR","DE","GR","HU","IE","IT","LV","LT","LU","MT","NL","PL","PT","RO","SK","SI","ES","SE","IS","LI","NO","GB","CH"]);function B(t){let e=typeof t=="string"?t.trim().toUpperCase():"";return e.length!==2||e==="XX"||e==="T1"?!0:O.includes(e)}var _="webority_cookie_consent",N="webority-consent-change",K=365,i={initialized:!1,requiresOptIn:!0,strictOptIn:!1,adStorage:!0,cookieDays:K,cookieDomain:null,saved:null},A=new Set;function $(t){let e=t?.strictOptIn??!1;if(typeof e!="boolean")throw new Error("@webority/analytics: initConsent strictOptIn must be true or false, or left out.");if(e){if(t.requiresOptIn!==void 0&&t.requiresOptIn!==!0)throw new Error("@webority/analytics: initConsent strictOptIn asks every visitor to opt in, so requiresOptIn must be true or left out.")}else if(typeof t?.requiresOptIn!="boolean")throw new Error("@webority/analytics: initConsent needs requiresOptIn (true or false), decided from the visitor's country.");if(t.cookieDomain!==void 0&&(typeof t.cookieDomain!="string"||!t.cookieDomain.trim()))throw new Error('@webority/analytics: initConsent cookieDomain must be a domain such as ".example.com", or left out.');if(t.cookieDays!==void 0&&!(Number.isInteger(t.cookieDays)&&t.cookieDays>0))throw new Error("@webority/analytics: initConsent cookieDays must be a whole number of days above zero, or left out.");if(t.adStorage!==void 0&&typeof t.adStorage!="boolean")throw new Error("@webority/analytics: initConsent adStorage must be true or false, or left out.");i.initialized=!0,i.strictOptIn=e,i.requiresOptIn=e||t.requiresOptIn,i.adStorage=t.adStorage??!e,i.cookieDays=t.cookieDays??K,i.cookieDomain=t.cookieDomain?.trim()??null;let{saved:n,entryCount:o}=wt();i.saved=n,i.cookieDomain&&o>1&&n&&V(n),yt(),It(),i.saved&&W(i.saved)}function E(){return i.initialized}function q(){return i.strictOptIn}function h(){return i.adStorage}function d(){Y("getConsent");let t=i.saved;if(t)return{essential:!0,analytics:t.analytics,functional:t.functional,marketing:t.marketing,decided:!0};let e=!i.requiresOptIn;return{essential:!0,analytics:e,functional:e,marketing:e,decided:!1}}function j(t){Y("saveConsent");let e={essential:!0,analytics:t?.analytics===!0,functional:t?.functional===!0,marketing:t?.marketing===!0,timestamp:new Date().toISOString()};V(e),i.saved=e,W(e),Tt(d())}function f(t){return A.add(t),()=>A.delete(t)}function Tt(t){for(let e of A)try{e(t)}catch(n){queueMicrotask(()=>{throw n})}typeof window?.dispatchEvent=="function"&&typeof CustomEvent=="function"&&window.dispatchEvent(new CustomEvent(N,{detail:t}))}function Y(t){if(!i.initialized)throw new Error(`@webority/analytics: call initConsent before ${t}.`)}function g(){window.dataLayer=window.dataLayer||[],window.dataLayer.push(arguments)}function yt(){if(i.strictOptIn){g("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500});return}g("consent","default",{ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500,region:[...O]}),g("consent","default",{ad_storage:"granted",ad_user_data:"granted",ad_personalization:"granted",analytics_storage:"granted",functionality_storage:"granted",personalization_storage:"granted",security_storage:"granted"})}function It(){g("set","ads_data_redaction",!0),g("set","url_passthrough",!0)}function W(t){let e=o=>o?"granted":"denied",n=t.marketing&&i.adStorage;g("consent","update",{analytics_storage:e(t.analytics),functionality_storage:e(t.functional),personalization_storage:e(t.functional),ad_storage:e(n),ad_user_data:e(n),ad_personalization:e(n)})}function wt(){let t=`${_}=`,e=(document.cookie||"").split(";").map(o=>o.trim()).filter(o=>o.startsWith(t)),n=e.map(o=>Ot(o.slice(t.length))).filter(Boolean);return{saved:At(n),entryCount:e.length}}function Ot(t){try{let e=JSON.parse(decodeURIComponent(t));return!e||typeof e!="object"?null:{essential:!0,analytics:e.analytics===!0,functional:e.functional===!0,marketing:e.marketing===!0,timestamp:e.timestamp??null}}catch{return null}}function At(t){if(t.length===0)return null;let e=t.map(o=>typeof o.timestamp=="string"?Date.parse(o.timestamp):Number.NaN);if(e.some(Number.isNaN))return t.at(-1);let n=0;for(let o=1;o<t.length;o++)e[o]>=e[n]&&(n=o);return t[n]}function V(t){let e=typeof t.timestamp=="string"?Date.parse(t.timestamp):Number.NaN,n=(Number.isNaN(e)?Date.now():e)+i.cookieDays*24*60*60*1e3,o=window.location?.protocol==="https:"?";Secure":"";i.cookieDomain&&H(`${_}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/;SameSite=Lax${o}`);let r=i.cookieDomain?`;domain=${i.cookieDomain}`:"";H(`${_}=${encodeURIComponent(JSON.stringify(t))};expires=${new Date(n).toUTCString()};path=/${r};SameSite=Lax${o}`)}function H(t){document.cookie=t}var y="webority_attribution",Nt=2160*60*60*1e3,ht=200,D=500,Dt=Object.freeze({utm_source:"utmSource",utm_medium:"utmMedium",utm_campaign:"utmCampaign",utm_content:"utmContent",utm_term:"utmTerm",gclid:"gclid",gbraid:"gbraid",wbraid:"wbraid",fbclid:"fbclid",msclkid:"msclkid",li_fat_id:"liFatId"}),J=!1;function Q(){tt("captureAttribution"),kt();let t=bt();if(!t)return;let e=k(L());Z(L(),{firstTouch:e?.firstTouch??t,lastTouch:t}),Lt(t)}function Lt(t){let e={event:"acquisition_touch",acquisition_channel:p(t)};t.aiSource&&(e.ai_source=t.aiSource),window.dataLayer=window.dataLayer||[],window.dataLayer.push(e)}function I(){return tt("getAttribution"),k(L())}function bt(){let t=new URLSearchParams(window.location.search),e={};for(let[r,c]of Object.entries(Dt)){let m=T(t.get(r),ht);m&&(e[c]=m)}let n=T(t.get("referrer"),D)||Rt();if(n&&(e.referrer=n),Object.keys(e).length===0)return null;let o=S(e);return o&&(e.aiSource=o),e.landingPage=T(window.location.pathname,D),e.capturedDateTimeUtc=new Date().toISOString(),e}function Rt(){let t=document.referrer;if(!t)return"";try{return new URL(t).hostname===window.location.hostname?"":T(t,D)}catch{return""}}function kt(){J||(J=!0,f(t=>{let e=t.marketing?R():b(),n=t.marketing?b():R(),o=k(e);vt(e),o&&Z(n,o)}))}function L(){return d().marketing?b():R()}function b(){try{return window.localStorage}catch{return null}}function R(){try{return window.sessionStorage}catch{return null}}function k(t){try{let e=t?.getItem(y);if(!e)return null;let n=JSON.parse(e),o=X(n?.firstTouch),r=X(n?.lastTouch);return!o&&!r?(t.removeItem(y),null):{firstTouch:o??r,lastTouch:r??o}}catch{return null}}function Z(t,e){try{t?.setItem(y,JSON.stringify(e))}catch{}}function vt(t){try{t?.removeItem(y)}catch{}}function X(t){if(!t||typeof t!="object")return null;let e=Date.parse(t.capturedDateTimeUtc);return Number.isFinite(e)&&Date.now()-e<=Nt?t:null}function T(t,e){let n=String(t??"").trim();return n?n.slice(0,e):""}function tt(t){if(!E())throw new Error(`@webority/analytics: call initConsent before ${t}.`)}var a=Object.freeze({SIGNUP_STARTED:"signup_started",SIGNUP_COMPLETED:"signup_completed",LOGIN_SUCCEEDED:"login_succeeded",ONBOARDING_STARTED:"onboarding_started",ONBOARDING_STEP_COMPLETED:"onboarding_step_completed",ONBOARDING_COMPLETED:"onboarding_completed",SUBSCRIPTION_ACTIVATED:"subscription_activated",SUBSCRIPTION_CANCELLED:"subscription_cancelled",PAYMENT_SUCCEEDED:"payment_succeeded",PAYMENT_FAILED:"payment_failed",FEEDBACK_SUBMITTED:"feedback_submitted",LEAD_SUBMITTED:"lead_submitted",WHATSAPP_CLICK:"whatsapp_click",CALCULATOR_USE:"calculator_use",PHONE_CLICK:"phone_click",DEMO_BOOKED:"demo_booked",CONTACT_SUBMITTED:"contact_submitted",TRIAL_START_CLICK:"trial_start_click",SAMPLE_REPORT_DOWNLOAD:"sample_report_download"}),Vt=Object.freeze({signupStarted:a.SIGNUP_STARTED,signupCompleted:a.SIGNUP_COMPLETED,loginSucceeded:a.LOGIN_SUCCEEDED,onboardingStarted:a.ONBOARDING_STARTED,onboardingStepCompleted:a.ONBOARDING_STEP_COMPLETED,onboardingCompleted:a.ONBOARDING_COMPLETED,subscriptionActivated:a.SUBSCRIPTION_ACTIVATED,subscriptionCancelled:a.SUBSCRIPTION_CANCELLED,paymentSucceeded:a.PAYMENT_SUCCEEDED,paymentFailed:a.PAYMENT_FAILED,feedbackSubmitted:a.FEEDBACK_SUBMITTED,leadSubmitted:a.LEAD_SUBMITTED,whatsappClick:a.WHATSAPP_CLICK,calculatorUse:a.CALCULATOR_USE,phoneClick:a.PHONE_CLICK,demoBooked:a.DEMO_BOOKED,contactSubmitted:a.CONTACT_SUBMITTED,trialStartClick:a.TRIAL_START_CLICK,sampleReportDownload:a.SAMPLE_REPORT_DOWNLOAD});var Ut=new Set(Object.values(a));function et(t,e={}){if(!Ut.has(t)||!d().analytics)return!1;let n=I(),o={...e,event:t};for(let[r,c]of[["first",n?.firstTouch],["last",n?.lastTouch]])c&&(o[`${r}_touch_channel`]=p(c),c.aiSource&&(o[`${r}_touch_ai_source`]=c.aiSource));return window.dataLayer=window.dataLayer||[],window.dataLayer.push(o),!0}var xt=["scroll","mousemove","touchstart","keydown","pointerdown"],nt=1500,Mt=3e3,Pt=100,l={loaded:!1,clarityId:null,clarityLoaded:!1};function rt(t={}){if(!E())throw new Error("@webority/analytics: call initConsent before loadTags.");if(l.loaded)return;l.loaded=!0;let{gtmId:e,clarityId:n,ahrefsKey:o}=t,r=q();if(o){let c=()=>U("https://analytics.ahrefs.com/analytics.js",{"data-key":o});r?ot(m=>m.analytics,c):c()}if(e){let c=()=>Gt(()=>{window.dataLayer=window.dataLayer||[],window.dataLayer.push({"gtm.start":Date.now(),event:"gtm.js"}),U(`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(e)}`)});if(r){let m=h();ot(x=>x.analytics||x.marketing&&m,c)}else c()}n&&(l.clarityId=n,d().analytics&&zt(),f(c=>{c.analytics&&!l.clarityLoaded?v():l.clarityLoaded&&it(c)}))}function ot(t,e){if(t(d())){e();return}let n=f(o=>{t(o)&&(n(),e())})}function Gt(t){let e=!1,n=()=>{e||(e=!0,t())};for(let o of xt)window.addEventListener(o,n,{once:!0,passive:!0});document.readyState==="complete"?setTimeout(n,nt):window.addEventListener("load",()=>setTimeout(n,nt),{once:!0})}function zt(){let t=()=>{window.scrollY>Pt&&(window.removeEventListener("scroll",t),v())};window.addEventListener("scroll",t,{passive:!0}),setTimeout(v,Mt)}function v(){l.clarityLoaded||!l.clarityId||(l.clarityLoaded=!0,window.clarity=window.clarity||function(){window.clarity.q=window.clarity.q||[],window.clarity.q.push(arguments)},it(d()),U(`https://www.clarity.ms/tag/${encodeURIComponent(l.clarityId)}`,{crossorigin:"anonymous"}))}function it(t){window.clarity("consentv2",{ad_Storage:t.marketing&&h()?"granted":"denied",analytics_Storage:t.analytics?"granted":"denied"})}function U(t,e={}){let n=document.createElement("script");n.async=!0,n.src=t;for(let[o,r]of Object.entries(e))n.setAttribute(o,r);document.head.appendChild(n)}return lt(Ft);})();
|
package/package.json
CHANGED
package/src/analytics.js
CHANGED
|
@@ -104,6 +104,25 @@ export function identify(publicId, props = {}) {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// PostHog group analytics (a paid add-on). Same gate as identify: nothing is sent without consent.
|
|
108
|
+
export function group(type, key, props = {}) {
|
|
109
|
+
if (!isActive()) {
|
|
110
|
+
logInactive("group", { type, key, ...props });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
ensurePosthogInitialized()
|
|
115
|
+
.then((posthog) => {
|
|
116
|
+
if (!posthog || !hasConsent()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
posthog.group(type, key, props);
|
|
120
|
+
})
|
|
121
|
+
.catch(() => {
|
|
122
|
+
// Load failure is retried on the next call. Do not surface it as an unhandled rejection.
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
107
126
|
export function resetIdentity() {
|
|
108
127
|
if (!state.posthogInstance) {
|
|
109
128
|
return;
|
package/src/attribution.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveAiSource, resolveChannel } from "./channels.js";
|
|
1
2
|
import { getConsent, isConsentInitialized, onConsentChange } from "./consent.js";
|
|
2
3
|
|
|
3
4
|
export const ATTRIBUTION_STORAGE_KEY = "webority_attribution";
|
|
@@ -40,6 +41,18 @@ export function captureAttribution() {
|
|
|
40
41
|
|
|
41
42
|
const current = readFrom(activeStore());
|
|
42
43
|
writeTo(activeStore(), { firstTouch: current?.firstTouch ?? touch, lastTouch: touch });
|
|
44
|
+
pushLanding(touch);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Lets a Tag Manager trigger on "acquisition_touch" and GA4 segment by ai_source and channel.
|
|
48
|
+
// Consent Mode still decides what the container's tags may store.
|
|
49
|
+
function pushLanding(touch) {
|
|
50
|
+
const landing = { event: "acquisition_touch", acquisition_channel: resolveChannel(touch) };
|
|
51
|
+
if (touch.aiSource) {
|
|
52
|
+
landing.ai_source = touch.aiSource;
|
|
53
|
+
}
|
|
54
|
+
window.dataLayer = window.dataLayer || [];
|
|
55
|
+
window.dataLayer.push(landing);
|
|
43
56
|
}
|
|
44
57
|
|
|
45
58
|
/** { firstTouch, lastTouch }, or null when no campaign or referral visit is on record. */
|
|
@@ -69,6 +82,11 @@ function touchFromLocation() {
|
|
|
69
82
|
return null;
|
|
70
83
|
}
|
|
71
84
|
|
|
85
|
+
const aiSource = resolveAiSource(touch);
|
|
86
|
+
if (aiSource) {
|
|
87
|
+
touch.aiSource = aiSource;
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
// The path only: a query string can carry personal data a visitor typed into a form.
|
|
73
91
|
touch.landingPage = clip(window.location.pathname, MAX_URL_LENGTH);
|
|
74
92
|
touch.capturedDateTimeUtc = new Date().toISOString();
|
package/src/channels.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// The browser half of Webority.Analytics' AcquisitionChannelResolver. Keep the two tables in step.
|
|
2
|
+
const GOOGLE_SOURCES = ["google", "googleads", "adwords", "youtube"];
|
|
3
|
+
const META_SOURCES = ["meta", "facebook", "fb", "instagram", "ig", "messenger"];
|
|
4
|
+
const LINKEDIN_SOURCES = ["linkedin", "lnkd"];
|
|
5
|
+
const MICROSOFT_SOURCES = ["bing", "microsoft", "microsoftads", "msn"];
|
|
6
|
+
|
|
7
|
+
const AI_ASSISTANTS = Object.freeze([
|
|
8
|
+
["chatgpt", ["chatgpt.com", "chat.openai.com"]],
|
|
9
|
+
["perplexity", ["perplexity.ai"]],
|
|
10
|
+
["gemini", ["gemini.google.com"]],
|
|
11
|
+
["copilot", ["copilot.microsoft.com"]],
|
|
12
|
+
["claude", ["claude.ai"]],
|
|
13
|
+
["you", ["you.com"]],
|
|
14
|
+
["phind", ["phind.com"]],
|
|
15
|
+
["deepseek", ["deepseek.com"]],
|
|
16
|
+
["grok", ["grok.com"]],
|
|
17
|
+
["meta_ai", ["meta.ai"]],
|
|
18
|
+
["poe", ["poe.com"]],
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const SEARCH_HOSTS = ["bing.com", "duckduckgo.com", "search.yahoo.com", "ecosia.org", "search.brave.com"];
|
|
22
|
+
const SEARCH_BRANDS_WITH_COUNTRY_DOMAIN = ["google", "yandex"];
|
|
23
|
+
|
|
24
|
+
const SOCIAL_HOSTS = [
|
|
25
|
+
"facebook.com",
|
|
26
|
+
"fb.com",
|
|
27
|
+
"instagram.com",
|
|
28
|
+
"linkedin.com",
|
|
29
|
+
"lnkd.in",
|
|
30
|
+
"twitter.com",
|
|
31
|
+
"t.co",
|
|
32
|
+
"x.com",
|
|
33
|
+
"reddit.com",
|
|
34
|
+
"pinterest.com",
|
|
35
|
+
"youtube.com",
|
|
36
|
+
"youtu.be",
|
|
37
|
+
"tiktok.com",
|
|
38
|
+
"whatsapp.com",
|
|
39
|
+
"threads.net",
|
|
40
|
+
"t.me",
|
|
41
|
+
"telegram.org",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export const channels = Object.freeze({
|
|
45
|
+
DIRECT: "direct",
|
|
46
|
+
GOOGLE: "google",
|
|
47
|
+
META: "meta",
|
|
48
|
+
LINKEDIN: "linkedin",
|
|
49
|
+
MICROSOFT: "microsoft",
|
|
50
|
+
OTHER: "other",
|
|
51
|
+
AI_ASSISTANT: "ai_assistant",
|
|
52
|
+
ORGANIC_SEARCH: "organic_search",
|
|
53
|
+
ORGANIC_SOCIAL: "organic_social",
|
|
54
|
+
REFERRAL: "referral",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The channel of a touch, as captureAttribution stores it. utm_source wins, then click ids; an
|
|
59
|
+
* untagged visit is classified by its referrer's host.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveChannel(touch) {
|
|
62
|
+
if (!touch) {
|
|
63
|
+
return channels.DIRECT;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const source = clean(touch.utmSource);
|
|
67
|
+
if (source) {
|
|
68
|
+
const key = lettersAndDigits(source);
|
|
69
|
+
if (GOOGLE_SOURCES.includes(key)) {
|
|
70
|
+
return channels.GOOGLE;
|
|
71
|
+
}
|
|
72
|
+
if (META_SOURCES.includes(key)) {
|
|
73
|
+
return channels.META;
|
|
74
|
+
}
|
|
75
|
+
if (LINKEDIN_SOURCES.includes(key)) {
|
|
76
|
+
return channels.LINKEDIN;
|
|
77
|
+
}
|
|
78
|
+
if (MICROSOFT_SOURCES.includes(key)) {
|
|
79
|
+
return channels.MICROSOFT;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (clean(touch.fbclid)) {
|
|
84
|
+
return channels.META;
|
|
85
|
+
}
|
|
86
|
+
if (clean(touch.gclid) || clean(touch.gbraid) || clean(touch.wbraid)) {
|
|
87
|
+
return channels.GOOGLE;
|
|
88
|
+
}
|
|
89
|
+
if (clean(touch.liFatId)) {
|
|
90
|
+
return channels.LINKEDIN;
|
|
91
|
+
}
|
|
92
|
+
if (clean(touch.msclkid)) {
|
|
93
|
+
return channels.MICROSOFT;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (assistantFromSource(source)) {
|
|
97
|
+
return channels.AI_ASSISTANT;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const tagged = source || clean(touch.utmMedium) || clean(touch.utmCampaign) || clean(touch.utmContent) || clean(touch.utmTerm);
|
|
101
|
+
if (tagged) {
|
|
102
|
+
return channels.OTHER;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const host = referrerHost(touch.referrer);
|
|
106
|
+
if (!host) {
|
|
107
|
+
return channels.DIRECT;
|
|
108
|
+
}
|
|
109
|
+
if (assistantFromHost(host)) {
|
|
110
|
+
return channels.AI_ASSISTANT;
|
|
111
|
+
}
|
|
112
|
+
if (isSearchHost(host)) {
|
|
113
|
+
return channels.ORGANIC_SEARCH;
|
|
114
|
+
}
|
|
115
|
+
return SOCIAL_HOSTS.some((social) => hostIs(host, social)) ? channels.ORGANIC_SOCIAL : channels.REFERRAL;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The AI assistant ("chatgpt", "perplexity", "gemini", "copilot", "claude"...) behind a touch, or "". */
|
|
119
|
+
export function resolveAiSource(touch) {
|
|
120
|
+
if (!touch) {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
const fromSource = assistantFromSource(clean(touch.utmSource));
|
|
124
|
+
if (fromSource) {
|
|
125
|
+
return fromSource;
|
|
126
|
+
}
|
|
127
|
+
const host = referrerHost(touch.referrer);
|
|
128
|
+
return host ? assistantFromHost(host) : "";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assistantFromSource(source) {
|
|
132
|
+
if (!source) {
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
const key = lettersAndDigits(source);
|
|
136
|
+
for (const [name, hosts] of AI_ASSISTANTS) {
|
|
137
|
+
if (lettersAndDigits(name) === key || hosts.some((host) => lettersAndDigits(host) === key)) {
|
|
138
|
+
return name;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function assistantFromHost(host) {
|
|
145
|
+
for (const [name, hosts] of AI_ASSISTANTS) {
|
|
146
|
+
if (hosts.some((assistantHost) => hostIs(host, assistantHost))) {
|
|
147
|
+
return name;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// google.com, google.co.in, yandex.ru, bing.com, search.yahoo.com: the search front end itself, never
|
|
154
|
+
// another product of the brand (docs.google.com, mail.google.com). The caller has stripped "www.".
|
|
155
|
+
function isSearchHost(host) {
|
|
156
|
+
if (SEARCH_HOSTS.includes(host)) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return SEARCH_BRANDS_WITH_COUNTRY_DOMAIN.some((brand) => {
|
|
160
|
+
if (!host.startsWith(`${brand}.`)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const domain = host.slice(brand.length + 1).split(".");
|
|
164
|
+
return domain.length === 1 || (domain.length === 2 && (domain[0] === "co" || domain[0] === "com"));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function hostIs(host, domain) {
|
|
169
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function referrerHost(referrer) {
|
|
173
|
+
const value = clean(referrer);
|
|
174
|
+
if (!value) {
|
|
175
|
+
return "";
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const host = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`).hostname.toLowerCase();
|
|
179
|
+
return host.startsWith("www.") ? host.slice(4) : host;
|
|
180
|
+
} catch {
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function lettersAndDigits(value) {
|
|
186
|
+
return String(value)
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.replace(/[^\p{L}\p{N}]/gu, "");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function clean(value) {
|
|
192
|
+
return String(value ?? "").trim();
|
|
193
|
+
}
|
package/src/consent.js
CHANGED
|
@@ -67,6 +67,7 @@ export function initConsent(options) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
pushConsentDefaults();
|
|
70
|
+
pushAdSignalSettings();
|
|
70
71
|
if (consentState.saved) {
|
|
71
72
|
pushConsentUpdate(consentState.saved);
|
|
72
73
|
}
|
|
@@ -194,6 +195,14 @@ function pushConsentDefaults() {
|
|
|
194
195
|
});
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
// Both apply only while ad_storage is denied, so they are safe to set for every visitor: Google
|
|
199
|
+
// redacts ad click identifiers in requests, and url_passthrough keeps gclid and friends on the
|
|
200
|
+
// URLs we link to, so a denied visitor's click is not lost between pages.
|
|
201
|
+
function pushAdSignalSettings() {
|
|
202
|
+
gtag("set", "ads_data_redaction", true);
|
|
203
|
+
gtag("set", "url_passthrough", true);
|
|
204
|
+
}
|
|
205
|
+
|
|
197
206
|
function pushConsentUpdate(saved) {
|
|
198
207
|
const state = (granted) => (granted ? "granted" : "denied");
|
|
199
208
|
const ads = saved.marketing && consentState.adStorage;
|
package/src/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
export { identify, initAnalytics, registerEvents, resetIdentity, trackEvent } from "./analytics.js";
|
|
1
|
+
export { group, identify, initAnalytics, registerEvents, resetIdentity, trackEvent } from "./analytics.js";
|
|
2
2
|
export {
|
|
3
3
|
CONSENT_CHANGE_EVENT,
|
|
4
4
|
CONSENT_COOKIE_NAME,
|
|
5
5
|
captureAttribution,
|
|
6
|
+
channels,
|
|
7
|
+
events,
|
|
6
8
|
getAttribution,
|
|
7
9
|
getConsent,
|
|
8
10
|
initConsent,
|
|
9
11
|
loadTags,
|
|
10
12
|
onConsentChange,
|
|
11
13
|
requiresOptIn,
|
|
14
|
+
resolveAiSource,
|
|
15
|
+
resolveChannel,
|
|
12
16
|
saveConsent,
|
|
17
|
+
trackSiteEvent,
|
|
13
18
|
} from "./site.js";
|
|
14
19
|
export { track } from "./track-helpers.js";
|
|
15
|
-
export { events } from "./vocabulary.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getAttribution } from "./attribution.js";
|
|
2
|
+
import { resolveChannel } from "./channels.js";
|
|
3
|
+
import { getConsent } from "./consent.js";
|
|
4
|
+
import { events } from "./vocabulary.js";
|
|
5
|
+
|
|
6
|
+
const CATALOGUE = new Set(Object.values(events));
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pushes a catalogue event (lead_submitted, whatsapp_click, calculator_use, phone_click...) to
|
|
10
|
+
* dataLayer for a Tag Manager trigger, carrying the visitor's first and last acquisition channel
|
|
11
|
+
* and ai_source. Nothing is pushed until the visitor has granted analytics consent, the same gate
|
|
12
|
+
* PostHog capture follows; a name outside the catalogue is dropped. Returns whether it was pushed.
|
|
13
|
+
*/
|
|
14
|
+
export function trackSiteEvent(name, props = {}) {
|
|
15
|
+
if (!CATALOGUE.has(name) || !getConsent().analytics) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const attribution = getAttribution();
|
|
20
|
+
const payload = { ...props, event: name };
|
|
21
|
+
for (const [prefix, touch] of [
|
|
22
|
+
["first", attribution?.firstTouch],
|
|
23
|
+
["last", attribution?.lastTouch],
|
|
24
|
+
]) {
|
|
25
|
+
if (touch) {
|
|
26
|
+
payload[`${prefix}_touch_channel`] = resolveChannel(touch);
|
|
27
|
+
if (touch.aiSource) {
|
|
28
|
+
payload[`${prefix}_touch_ai_source`] = touch.aiSource;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
window.dataLayer = window.dataLayer || [];
|
|
33
|
+
window.dataLayer.push(payload);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
package/src/site.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Public websites: consent, marketing tags and attribution, without PostHog. The main entry
|
|
2
2
|
// re-exports all of this for portals that also capture product events.
|
|
3
3
|
export { captureAttribution, getAttribution } from "./attribution.js";
|
|
4
|
+
export { channels, resolveAiSource, resolveChannel } from "./channels.js";
|
|
4
5
|
export { CONSENT_CHANGE_EVENT, CONSENT_COOKIE_NAME, getConsent, initConsent, onConsentChange, saveConsent } from "./consent.js";
|
|
5
6
|
export { requiresOptIn } from "./consent-regions.js";
|
|
7
|
+
export { trackSiteEvent } from "./site-events.js";
|
|
6
8
|
export { loadTags } from "./tags.js";
|
|
9
|
+
export { events } from "./vocabulary.js";
|
package/src/tags.js
CHANGED
|
@@ -15,7 +15,8 @@ const tagState = {
|
|
|
15
15
|
* Loads the marketing tags. Tag Manager waits for the first interaction, or 1.5s after load,
|
|
16
16
|
* so its download never competes with the page's largest paint; Consent Mode (set by
|
|
17
17
|
* initConsent) gates what its tags may do. Clarity records sessions, so it loads only with
|
|
18
|
-
* analytics consent, and
|
|
18
|
+
* analytics consent, and tells Clarity the visitor's choice (consentv2) when it loads and on every
|
|
19
|
+
* change after; a revoke stops its recording from the next page. Ahrefs is cookieless and
|
|
19
20
|
* loads straight away. GA4 is deliberately absent: it runs inside the Tag Manager container,
|
|
20
21
|
* and loading it here as well counts every page view twice.
|
|
21
22
|
*
|
|
@@ -66,8 +67,10 @@ export function loadTags(options = {}) {
|
|
|
66
67
|
scheduleClarity();
|
|
67
68
|
}
|
|
68
69
|
onConsentChange((consent) => {
|
|
69
|
-
if (consent.analytics) {
|
|
70
|
+
if (consent.analytics && !tagState.clarityLoaded) {
|
|
70
71
|
loadClarity();
|
|
72
|
+
} else if (tagState.clarityLoaded) {
|
|
73
|
+
sendClarityConsent(consent);
|
|
71
74
|
}
|
|
72
75
|
});
|
|
73
76
|
}
|
|
@@ -131,9 +134,19 @@ function loadClarity() {
|
|
|
131
134
|
// biome-ignore lint/complexity/noArguments: Clarity's stub queues the Arguments object, as its own snippet does.
|
|
132
135
|
window.clarity.q.push(arguments);
|
|
133
136
|
};
|
|
137
|
+
sendClarityConsent(getConsent());
|
|
134
138
|
appendScript(`https://www.clarity.ms/tag/${encodeURIComponent(tagState.clarityId)}`, { crossorigin: "anonymous" });
|
|
135
139
|
}
|
|
136
140
|
|
|
141
|
+
// Clarity's consent API v2 (required for EEA, UK and Switzerland visitors): the signal rides the
|
|
142
|
+
// stub's queue, so it is replayed in order once the script arrives.
|
|
143
|
+
function sendClarityConsent(consent) {
|
|
144
|
+
window.clarity("consentv2", {
|
|
145
|
+
ad_Storage: consent.marketing && isAdStorageEnabled() ? "granted" : "denied",
|
|
146
|
+
analytics_Storage: consent.analytics ? "granted" : "denied",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
137
150
|
function appendScript(src, attributes = {}) {
|
|
138
151
|
const script = document.createElement("script");
|
|
139
152
|
script.async = true;
|
package/src/vocabulary.js
CHANGED
|
@@ -10,6 +10,14 @@ export const events = Object.freeze({
|
|
|
10
10
|
PAYMENT_SUCCEEDED: "payment_succeeded",
|
|
11
11
|
PAYMENT_FAILED: "payment_failed",
|
|
12
12
|
FEEDBACK_SUBMITTED: "feedback_submitted",
|
|
13
|
+
LEAD_SUBMITTED: "lead_submitted",
|
|
14
|
+
WHATSAPP_CLICK: "whatsapp_click",
|
|
15
|
+
CALCULATOR_USE: "calculator_use",
|
|
16
|
+
PHONE_CLICK: "phone_click",
|
|
17
|
+
DEMO_BOOKED: "demo_booked",
|
|
18
|
+
CONTACT_SUBMITTED: "contact_submitted",
|
|
19
|
+
TRIAL_START_CLICK: "trial_start_click",
|
|
20
|
+
SAMPLE_REPORT_DOWNLOAD: "sample_report_download",
|
|
13
21
|
});
|
|
14
22
|
|
|
15
23
|
export const trackHelperNames = Object.freeze({
|
|
@@ -24,6 +32,14 @@ export const trackHelperNames = Object.freeze({
|
|
|
24
32
|
paymentSucceeded: events.PAYMENT_SUCCEEDED,
|
|
25
33
|
paymentFailed: events.PAYMENT_FAILED,
|
|
26
34
|
feedbackSubmitted: events.FEEDBACK_SUBMITTED,
|
|
35
|
+
leadSubmitted: events.LEAD_SUBMITTED,
|
|
36
|
+
whatsappClick: events.WHATSAPP_CLICK,
|
|
37
|
+
calculatorUse: events.CALCULATOR_USE,
|
|
38
|
+
phoneClick: events.PHONE_CLICK,
|
|
39
|
+
demoBooked: events.DEMO_BOOKED,
|
|
40
|
+
contactSubmitted: events.CONTACT_SUBMITTED,
|
|
41
|
+
trialStartClick: events.TRIAL_START_CLICK,
|
|
42
|
+
sampleReportDownload: events.SAMPLE_REPORT_DOWNLOAD,
|
|
27
43
|
});
|
|
28
44
|
|
|
29
45
|
export const EVENT_NAME_PATTERN = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/;
|