-
-
Save DavidKuennen/443121e692175d6fc145e1efb0284ec9 to your computer and use it in GitHub Desktop.
(function (context, trackingId, options) { | |
const history = context.history; | |
const doc = document; | |
const nav = navigator || {}; | |
const storage = localStorage; | |
const encode = encodeURIComponent; | |
const pushState = history.pushState; | |
const typeException = 'exception'; | |
const generateId = () => Math.random().toString(36); | |
const getId = () => { | |
if (!storage.cid) { | |
storage.cid = generateId() | |
} | |
return storage.cid; | |
}; | |
const serialize = (obj) => { | |
var str = []; | |
for (var p in obj) { | |
if (obj.hasOwnProperty(p)) { | |
if(obj[p] !== undefined) { | |
str.push(encode(p) + "=" + encode(obj[p])); | |
} | |
} | |
} | |
return str.join("&"); | |
}; | |
const track = ( | |
type, | |
eventCategory, | |
eventAction, | |
eventLabel, | |
eventValue, | |
exceptionDescription, | |
exceptionFatal | |
) => { | |
const url = 'https://www.google-analytics.com/collect'; | |
const data = serialize({ | |
v: '1', | |
ds: 'web', | |
aip: options.anonymizeIp ? 1 : undefined, | |
tid: trackingId, | |
cid: getId(), | |
t: type || 'pageview', | |
sd: options.colorDepth && screen.colorDepth ? `${screen.colorDepth}-bits` : undefined, | |
dr: doc.referrer || undefined, | |
dt: doc.title, | |
dl: doc.location.origin + doc.location.pathname + doc.location.search, | |
ul: options.language ? (nav.language || "").toLowerCase() : undefined, | |
de: options.characterSet ? doc.characterSet : undefined, | |
sr: options.screenSize ? `${(context.screen || {}).width}x${(context.screen || {}).height}` : undefined, | |
vp: options.screenSize && context.visualViewport ? `${(context.visualViewport || {}).width}x${(context.visualViewport || {}).height}` : undefined, | |
ec: eventCategory || undefined, | |
ea: eventAction || undefined, | |
el: eventLabel || undefined, | |
ev: eventValue || undefined, | |
exd: exceptionDescription || undefined, | |
exf: typeof exceptionFatal !== 'undefined' && !!exceptionFatal === false ? 0 : undefined, | |
}); | |
if(nav.sendBeacon) { | |
nav.sendBeacon(url, data) | |
} else { | |
var xhr = new XMLHttpRequest(); | |
xhr.open("POST", url, true); | |
xhr.send(data); | |
} | |
}; | |
const trackEvent = (category, action, label, value) => track('event', category, action, label, value); | |
const trackException = (description, fatal) => track(typeException, null, null, null, null, description, fatal); | |
history.pushState = function (state) { | |
if (typeof history.onpushstate == "function") { | |
history.onpushstate({ state: state }); | |
} | |
setTimeout(track, options.delay || 10); | |
return pushState.apply(history, arguments); | |
} | |
track(); | |
context.ma = { | |
trackEvent, | |
trackException | |
} | |
})(window, "XX-XXXXXXXXX-X", { | |
anonymizeIp: true, | |
colorDepth: true, | |
characterSet: true, | |
screenSize: true, | |
language: true | |
}); |
To all who follow this discussion, I am following up on my post above (which makes me think about how to make it).
I am currently working on Google Analytics 4 minimal snipped and I got already development version working and collecting traffic. Still need to do some adjustments and will publish it... so there will be an option 🙌
Hi All,
I think I made it. Here is my post (with code and reference to Gist) about A very Minimal Google Analytics 4 Snippet
Hi All, I think I made it. Here is my post (with code and reference to Gist) about A very Minimal Google Analytics 4 Snippet
Great, I just needed it. thank you very much for your research
Thank you @idarek!
A better minimal analytics for GA4 https://github.com/jahilldev/minimal-analytics
A better minimal analytics for GA4 https://github.com/jahilldev/minimal-analytics
It's a different approach. More options to choose from is better for users, but leave the interpretation of which one is better to people who will be using it.
A better minimal analytics for GA4 https://github.com/jahilldev/minimal-analytics
It's a different approach. More options to choose from is better for users, but leave the interpretation of which one is better to people who will be using it.
I am using it, and in my opinion, it is better. So, I don't know what your problem is.
I am using it, and in my opinion, it is better. So, I don't know what your problem is.
The only problem is the one that you creating. I appreciate that @jahilldev found a way to extend the functionality of my initial work to give users what they may need. You are the opposite. You are the first to complain about somebody's work and the valuable time that they put into it but you are the last person to offer any advice or even pointing into solution how to make it better.
The script that I come up with taught me a lot, especially since I am not a developer in that matter so that was all new for me and I appreciate that I made something useful and learn a lot. With @jahilldev implementation, we will be able to extend it and give users some alternatives that are hard to find. I will be looking forward to seeing with the @jahilldev approach how I can extend my way or achieve it.
A simple word thank you is enough and motivates you to work forward, but your kind of attitude does the opposite.
So thank you @someguy9 @wkingnet for appreciating my work and thank @jahilldev for your time to extend the functionality.
The top appreciation goes to @DavidKuennen who initiate it and make it popular, thanks to which we can work further with GA4, and @thyngster who provided Google Analytics 4 Measurement Protocol CheatSheet that allowed understanding some of the functionality.
I am using it, and in my opinion, it is better. So, I don't know what your problem is.
The only problem is the one that you creating. I appreciate that @jahilldev found a way to extend the functionality of my initial work to give users what they may need. You are the opposite. You are the first to complain about somebody's work and the valuable time that they put into it but you are the last person to offer any advice or even pointing into solution how to make it better.
The script that I come up with taught me a lot, especially since I am not a developer in that matter so that was all new for me and I appreciate that I made something useful and learn a lot. With @jahilldev implementation, we will be able to extend it and give users some alternatives that are hard to find. I will be looking forward to seeing with the @jahilldev approach how I can extend my way or achieve it.
A simple word thank you is enough and motivates you to work forward, but your kind of attitude does the opposite.
So thank you @someguy9 @wkingnet for appreciating my work and thank @jahilldev for your time to extend the functionality.
The top appreciation goes to @DavidKuennen who initiate it and make it popular, thanks to which we can work further with GA4, and @thyngster who provided Google Analytics 4 Measurement Protocol CheatSheet that allowed understanding some of the functionality.
You are the one who jumped into my comment when I didn't even mention your name, No? I will not entertain your butthurt feelings just coz you were stubborn to implement a feature that I needed, but that someone else picked up and acted on. I will not entertain your comments since this comment section is derailing from featuring the great work of David and his contribution to the community. Cheers!
"coz you were stubborn to implement a feature that I needed"
There is no I in a team.
Requesting a feature vs trying to find how to implement it are two different things.
As mentioned - zero input and plenty of complaints on others' work.
My lack of knowledge (that I admit) of how to implement some things doesn't mean that I don't want to implement them.
Wish you best
Haven't jumped on the GA4 train yet but how do you guys track events inline via onclick
? And is this supported with minimal analytics v4? I read there were some limits on how many unique parameters and length you can create? This be a problem just all new, I assume this is more to setup the types of events it is? So if you have parameters "conversion" can I then have evenCategry + eventAction + eventLabel?
As an example I used this (page not needed as this can normally be matched with the normal page value in GA):
onclick="ga('send', 'event', 'eventCategory', 'eventAction', 'eventLabel');"
onclick="ga('send', 'event', 'WhatTypeOfCta', 'WhereCtaIs', 'CtaPath');"
So anyone with a good article or who can share how inline onclick
event tracking work in new GA4 and we can use it with minimal analytics?
@wpsumo I haven't been able to find official documentation (it must exist, somewhere..) about doing this programmatically, only via GTM, which is obviously not ideal given the "minimal" goals of this.
After profiling the official GA4 library, I've discovered there's another default event type of click
, which you can use in conjunction with event params, e.g ?ep.custom_param=lorem
or if it's a number, ?epn.price=5
.
If you're using @minimal-analytics/ga4
, and you want a custom click event, you'll need to add an event handler to the element, something like this:
import { track } from '@minimal-analytics/ga4';
const button = document.querySelector('button'); // use relevant selector
button.addEventListener('click', () => track({ type: 'click', event: { 'epn.product_id': 12345 } }));
The above assumes you're using a node build tool or environment to generate your site. There currently isn't a way to access
track();
without this. Open to feature requests though...
I am in the process of implementing automatic anchor and button click tracking in @minimal-analytics/ga4
, which is another "out of the box" event provided by the official GA4 library. That will trigger a click event with a series of event params when a user clicks an anchor or button element.
Hope that helps!
@jahilldev Thanks for your answer, no not using any node environment. I run wordpress so php environment, I wish to be able to continue using inlined event similar to how I've used onclick
today as I have a better structure there than track all click via class or ID.
As you say I did not find anything online or in the official documentation regarding this. Hopefully it will come before 2023 :D and then it can be added into minimal GA4 as well. For now if you know a solution or find, please share.
I'm not a fan of tracking all links external or internal. In this case I prefer to write them static in the html while they are dynamic via php. So it looks more like below in structure.
The only custom params I need to create if I prefer is "converted" but not necessary the rest is values which should be unlimited. The problem I see if trying to do it inline. But I have not spent hours into GA4 as I haven't seen this being supported in the official documentation yet... so no point to trying GA4 out for me right now, waiting for it though.
Event:
Converted/CTA (Category - Static)
Which CTA on the page (Action - dynamic php)
Where did the CTA go/slug (Label - dynamic php)
@wpsumo One solution to this would be to add track
to the window in @minimal-analytics/ga4
, that way you'd be able to call the function in the same way you've been doing with Universal Analytics.
I'm not super keen about blanket window pollution, but maybe I could add a config option that would do this.
What do you think?
Hello @jahilldev, thanks for the contribution!
I have a question please, how to implement this minimal analytics GA4 on a WordPress site?
Do we copy the script and paste it into the website < head > ?
We are also using Cloudflare as CDN.
The steps are not clear here: https://github.com/jahilldev/minimal-analytics/tree/main/packages/ga4
Your help is highly appreciated.
Thank You!
Hey @CharbelNemnom,
Could you raise an issue on the repo? That way all of this info will be in one place for anyone else looking for a similar answer 👍
But yes, if you're using a site that doesn't make use of Node, either for page generation, or to build client assets, the easiest route to integrate is to copy the config object and script tag into the <head />
of your document.
It's be a (very) long time since I've made use of Wordpress, but your chosen template should provide a provision for this, without having to necessarily modify the underlying code of Wordpress.
If it doesn't, just find the head tag in the respective PHP file, and add the script.
Just for clarity, this script will get you started:
<script src="https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js" async></script>
<script>
window.minimalAnalytics = {
trackingId: 'G-XXXXX', // <-- replace with your GA4 property ID
autoTrack: true,
};
</script>
You'll need to swap G-XXXXX
with your account ID.
Let me know if you need anything else 👍
Thank you @jahilldev for your fast response, much appreciated!
The steps are clear now.
After I looked deeper, I copied the entire script from @idarek here: https://dariusz.wieckiewicz.org/en/minimal-google-analytics-4-snippet/#minimal-analytics-4---the-code and put it in WP_Head in WordPress and it works as expected after I cleared the cache.
I can see the Real-Time data in Google Analytics.
I have a couple of follow-up questions, please:
I can use the same approach that you provided above, but you are loading the script first from CDN with async and then passing the Tracking ID (Measurement ID) and auto track set to true.
From a performance perspective, which method is faster? Having the entire script loaded on my site similar to what @idarek described which will be cached with Cloudflare eventually in my case, or using cdn.jsdelivr.net?
<script src="https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js" async></script>
<script>
window.minimalAnalytics = {
trackingId: 'G-XXXXX', // <-- replace with your GA4 property ID
autoTrack: true,
};
</script>
Another question, what about Minimal Analytics 4 - masking (hiding) requests?
I tried the script of @idarek with Firefox in private mode and the tracking is not working. Because many people have Ad Blocker enabled, I need to track all visits.
Today, Cloudflare does NOT allow _redirects file, to set not typical redirect with code 200 (OK), working as a proxy URL.
Many Thanks!
@CharbelNemnom, no worries!
So we're talking at cross purposes, a little, here. @idarek's script is a different implementation to the repo you originally linked to. The only difference, really, is @minimal-analytics/ga4
has more in depth tracking, such as engagement time, download, click, etc.
As for performance, embedding a script directly on the page will always be marginally faster, given you're mitigating a network request. However, the CDN I provided is super fast, and we're talking 10s of milliseconds difference, here.
That said, you can achieve the exact same performance by just embedding the @minimal-analytics/ga4
script directly into the document, as @idarek is doing. There's no requirement on loading it from an external CDN.
Just copy the contents of the script below, and add to your page:
https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js
On the flip side, using a CDN ensures you have the latest version of the script loaded on your site; Any updates, feature improvements, or bug fixes that are made, will be automatically applied to your site.
Which method you use, is up to you really. Given the size of the script, and the fact it's loaded asynchronously from a fast co-located CDN, the performance difference in real world terms is negligible, really.
Hope that helps!
Many Thanks, @jahilldev for the clarification!
So I should copy the entire contents of the script below and add
<script> ..... </script>
, right?
https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js
Another question, what about Minimal Analytics 4 - masking (hiding) requests?
I tried the script with Firefox in private mode and the tracking is not working. Because many people have Ad Blocker enabled, I need to track all visits.
Today, Cloudflare does NOT allow _redirects file, to set not typical redirect with code 200 (OK), working as a proxy URL.
@CharbelNemnom, yes that's correct 👍
Minimal Analytics 4 - masking (hiding) requests
Yeh, so someone else wanted the ability to avoid AdBlocker too, so I added a proxy endpoint config that will allow you to do this.
You'll need to specify the following property:
window.minimalAnalytics = {
/*[...snip]*/
analyticsEndpoint: '/collect', // <-- your endpoint
};
https://github.com/jahilldev/minimal-analytics/tree/main/packages/ga4#endpoint
This will redirect all analytics calls to your specified endpoint, which if named correctly, will bypass AdBlocking logic / rules. You'll need to provision this endpoint yourself, however, and ensure traffic is proxied from there to the official GA4 API. I'm sure there are guides on how to do this in various environments floating around the interwebs.
Fundamentally though, it's just request forwarding, so should be fairly trivial 👍
Sorry, I got confused about what will be the final script that I want to put on my page including the endpoint to avoid AdBlocker without CDN.
Could you please confirm the below code?
<script>
window.minimalAnalytics = {
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var n=t();for(var o in n)("object"==typeof exports?exports:e)[o]=n[o]}}(this,(()=>(()=>{"use strict";var e={508:(e,t,n)=>{function o(e,t=300,n=0){return(...o)=>(clearTimeout(n),n=setTimeout(e,t,...o))}function i(e=16){return e=e>16?16:e,`${Math.floor(1e16*Math.random())}`.padStart(e,"0").substring(-1,e)}function s(e,t=16){let n=0;for(let t=0;t<e.length;t++)n=(n<<5)-n+e.charCodeAt(t),n&=n;return n=Math.abs(n),`${n}`.padStart(t,"0").substring(-1,t)}function r(){const e=document.body,t=window.pageYOffset||e.scrollTop,{scrollHeight:n,offsetHeight:o,clientHeight:i}=document.documentElement,s=Math.max(e.scrollHeight,n,e.offsetHeight,o,e.clientHeight,i)-window.innerHeight;return Math.floor(100*Math.abs(t/s))}function a(e,t){let n=e;for(;n&&(!(null==n?void 0:n.matches)||!(null==n?void 0:n.matches(t)));)n=null==n?void 0:n.parentNode;return n}function c(e){let t,n,o=!1;try{({hostname:t,pathname:n}=e&&new URL(e)||{})}catch(e){}return t&&(o=t!==window.location.host),{t:o,hostname:t,pathname:n}}n.r(t),n.d(t,{o:()=>u,i:()=>d,u:()=>o,l:()=>m,m:()=>f,g:()=>p,v:()=>s,p:()=>i,h:()=>r,_:()=>g,$:()=>v,S:()=>c,j:()=>a,I:()=>l});const u="clientId",l="sessionId",d="sessionCount";function f(){const{hostname:e,origin:t,pathname:n,search:o}=document.location,i=document.title;return{location:t+n+o,hostname:e,pathname:n,referrer:document.referrer,title:i}}function m(e=u){const t=i(),n=localStorage.getItem(e);return n||(localStorage.setItem(e,t),t)}function g(e=l){const t=i(),n=sessionStorage.getItem(e);return n||(sessionStorage.setItem(e,t),t)}function v(e){const t=localStorage.getItem(u)?void 0:"1",n=sessionStorage.getItem(l)?void 0:"1";let o=sessionStorage.getItem(d)||"1";return e&&(o=function(e=d){let t="1";const n=sessionStorage.getItem(e);return n&&(t=""+(+n+1)),sessionStorage.setItem(e,t),t}()),{firstVisit:t,sessionStart:n,sessionCount:o}}function p(e){return Array.isArray(e)?e.map((e=>e.map((e=>null==e?void 0:e.toString())))):Object.keys(e).map((t=>[t,`${e[t]}`]))}},209:(e,t)=>{Object.defineProperty(t,"M",{value:!0}),t.files=t.k=void 0;t.k={protocolVersion:"v",trackingId:"tid",pageId:"_p",language:"ul",clientId:"cid",firstVisit:"_fv",hitCount:"_s",sessionId:"sid",sessionCount:"sct",sessionEngagement:"seg",sessionStart:"_ss",debug:"_dbg",referrer:"dr",location:"dl",title:"dt",eventName:"en",eventParam:"ep",eventParamNumber:"epn",screenResolution:"sr",enagementTime:"_et"};t.files=["pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi","mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma"]}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var s=t[o]={exports:{}};return e[o](s,s.exports,n),s.exports}n.d=(e,t)=>{for(var o in t)n.D(t,o)&&!n.D(e,o)&&Object.defineProperty(e,o,{O:!0,get:t[o]})},n.D=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"M",{value:!0})};var o={};return(()=>{var e=o;Object.defineProperty(e,"M",{value:!0}),e.track=void 0;const t=n(508),i=n(209),s="undefined"!=typeof window,r=s&&window.minimalAnalytics?.defineGlobal,a=s&&window.minimalAnalytics?.autoTrack,c=["q","s","search","query","keyword"];let u,l,d,f=[[Date.now()]],m=!1;const g="page_view",v="scroll",p="click",w="view_search_results",h="user_engagement",y="file_download";function b(e,{type:n,event:o,debug:s}){const{location:r,referrer:a,title:u}=(0,t.m)(),{firstVisit:l,sessionStart:d,sessionCount:f}=(0,t.$)(!m),g=self.screen||{};let v=[[i.k.protocolVersion,"2"],[i.k.trackingId,e],[i.k.pageId,(0,t.p)()],[i.k.language,(navigator.language||"").toLowerCase()],[i.k.clientId,(0,t.l)()],[i.k.firstVisit,l],[i.k.hitCount,"1"],[i.k.sessionId,(0,t._)()],[i.k.sessionCount,f],[i.k.sessionEngagement,"1"],[i.k.sessionStart,d],[i.k.debug,s?"1":""],[i.k.referrer,a],[i.k.location,r],[i.k.title,u],[i.k.screenResolution,`${g.width}x${g.height}`]];return v=v.concat(function({type:e="",event:n}){const o=document.location.search,s=new URLSearchParams(o),r=c.some((e=>new RegExp(`[?|&]${e}=`,"g").test(o)))?w:e,a=c.find((e=>s.get(e)));let u=[[i.k.eventName,r],[`${i.k.eventParam}.search_term`,a||""]];return n&&(u=u.concat((0,t.g)(n))),u}({type:n,event:o})),v=v.filter((([,e])=>e)),new URLSearchParams(v)}function _(){return f.reduce(((e,[t,n=Date.now()])=>e+(n-t)),0).toString()}function $(e,n){const o=(0,t.j)(n.target,"a, button, input[type=submit], input[type=button]"),s=o?.tagName?.toLowerCase(),r="a"===s?"link":s,a=o?.getAttribute("href")||void 0,c=o?.getAttribute("download")||void 0||a,{t:u,hostname:l,pathname:d}=(0,t.S)(c),f="link"===r&&!u,[m]=c?.match(new RegExp(i.files.join("|"),"g"))||[],g=m?y:p,v=`${i.k.eventParam}.${r}`;if(!o||f&&!m)return;let w=[[`${v}_id`,o.id],[`${v}_classes`,o.className],[`${v}_name`,o?.getAttribute("name")?.trim()],[`${v}_text`,o.textContent?.trim()],[`${v}_value`,o?.getAttribute("value")?.trim()],[`${v}_url`,a],[`${v}_domain`,l],[`${i.k.eventParam}.outbound`,`${u}`],[i.k.enagementTime,_()]];m&&(w=w.concat([[`${i.k.eventParam}.file_name`,d||c],[`${i.k.eventParam}.file_extension`,m]])),D(e,{type:g,event:w})}function S(){const e=f.length-1,[,t]=f[e];t||f[e].push(Date.now())}function x(){const e=f.length-1,[,t]=f[e];t&&f.push([Date.now()])}function j(){const e=f.length-1,[,t]=f[e],n=["hidden","visible"].indexOf(document.visibilityState),o=Boolean(n);-1!==n&&(o?t&&f.push([Date.now()]):!t&&f[e].push(Date.now()))}const I=(0,t.u)((e=>{if((0,t.h)()<90)return;const n=[[`${i.k.eventParamNumber}.percent_scrolled`,90]];D(e,{type:v,event:n}),document.removeEventListener("scroll",l)}));function M(e){const t=[[i.k.enagementTime,_()]];D(e,{type:h,event:t})}function k(e){m||(u=$.bind(null,e),l=I.bind(null,e),d=M.bind(null,e),document.addEventListener("visibilitychange",j),document.addEventListener("scroll",l),document.addEventListener("click",u),window.addEventListener("blur",S),window.addEventListener("focus",x),window.addEventListener("beforeunload",d))}function D(...e){const[t,{type:n,event:o,debug:i}]=function(e){const t=window.minimalAnalytics?.trackingId,n="string"==typeof e[0]?e[0]:t,o="object"==typeof e[0]?e[0]:e[1]||{};return[n,{type:g,...o}]}(e);if(!t)return void console.error("GA4: Tracking ID is missing or undefined");const s=b(t,{type:n,event:o,debug:i}),r=window.minimalAnalytics?.analyticsEndpoint||"https://www.google-analytics.com/g/collect";navigator.sendBeacon(`${r}?${s}`),k(t),m=!0}e.track=D,r&&(window.track=D),a&&D()})(),o})()));
analyticsEndpoint: '/collect', // <-- What Should be here?
};
</script>
Where do I need to put the trackingId: 'G-XXXXX', // <-- replace with your GA4 property ID
Thank You!!!!
@CharbelNemnom No worries, see below:
<script>
window.minimalAnalytics = {
trackingId: 'G-XXXXX', // <-- replace with your GA4 property ID
autoTrack: true,
analyticsEndpoint: '/my/analytics/endpoint/can/be/called/anything'
};
// GA4 script v1.8.7
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var n=t();for(var o in n)("object"==typeof exports?exports:e)[o]=n[o]}}(this,(()=>(()=>{"use strict";var e={508:(e,t,n)=>{function o(e,t=300,n=0){return(...o)=>(clearTimeout(n),n=setTimeout(e,t,...o))}function i(e=16){return e=e>16?16:e,`${Math.floor(1e16*Math.random())}`.padStart(e,"0").substring(-1,e)}function s(e,t=16){let n=0;for(let t=0;t<e.length;t++)n=(n<<5)-n+e.charCodeAt(t),n&=n;return n=Math.abs(n),`${n}`.padStart(t,"0").substring(-1,t)}function r(){const e=document.body,t=window.pageYOffset||e.scrollTop,{scrollHeight:n,offsetHeight:o,clientHeight:i}=document.documentElement,s=Math.max(e.scrollHeight,n,e.offsetHeight,o,e.clientHeight,i)-window.innerHeight;return Math.floor(100*Math.abs(t/s))}function a(e,t){let n=e;for(;n&&(!(null==n?void 0:n.matches)||!(null==n?void 0:n.matches(t)));)n=null==n?void 0:n.parentNode;return n}function c(e){let t,n,o=!1;try{({hostname:t,pathname:n}=e&&new URL(e)||{})}catch(e){}return t&&(o=t!==window.location.host),{t:o,hostname:t,pathname:n}}n.r(t),n.d(t,{o:()=>u,i:()=>d,u:()=>o,l:()=>m,m:()=>f,g:()=>p,v:()=>s,p:()=>i,h:()=>r,_:()=>g,$:()=>v,S:()=>c,j:()=>a,I:()=>l});const u="clientId",l="sessionId",d="sessionCount";function f(){const{hostname:e,origin:t,pathname:n,search:o}=document.location,i=document.title;return{location:t+n+o,hostname:e,pathname:n,referrer:document.referrer,title:i}}function m(e=u){const t=i(),n=localStorage.getItem(e);return n||(localStorage.setItem(e,t),t)}function g(e=l){const t=i(),n=sessionStorage.getItem(e);return n||(sessionStorage.setItem(e,t),t)}function v(e){const t=localStorage.getItem(u)?void 0:"1",n=sessionStorage.getItem(l)?void 0:"1";let o=sessionStorage.getItem(d)||"1";return e&&(o=function(e=d){let t="1";const n=sessionStorage.getItem(e);return n&&(t=""+(+n+1)),sessionStorage.setItem(e,t),t}()),{firstVisit:t,sessionStart:n,sessionCount:o}}function p(e){return Array.isArray(e)?e.map((e=>e.map((e=>null==e?void 0:e.toString())))):Object.keys(e).map((t=>[t,`${e[t]}`]))}},209:(e,t)=>{Object.defineProperty(t,"M",{value:!0}),t.files=t.k=void 0;t.k={protocolVersion:"v",trackingId:"tid",pageId:"_p",language:"ul",clientId:"cid",firstVisit:"_fv",hitCount:"_s",sessionId:"sid",sessionCount:"sct",sessionEngagement:"seg",sessionStart:"_ss",debug:"_dbg",referrer:"dr",location:"dl",title:"dt",eventName:"en",eventParam:"ep",eventParamNumber:"epn",screenResolution:"sr",enagementTime:"_et"};t.files=["pdf|xlsx?|docx?|txt|rtf|csv|exe|key|pp(s|t|tx)|7z|pkg|rar|gz|zip|avi","mov|mp4|mpe?g|wmv|midi?|mp3|wav|wma"]}},t={};function n(o){var i=t[o];if(void 0!==i)return i.exports;var s=t[o]={exports:{}};return e[o](s,s.exports,n),s.exports}n.d=(e,t)=>{for(var o in t)n.D(t,o)&&!n.D(e,o)&&Object.defineProperty(e,o,{O:!0,get:t[o]})},n.D=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"M",{value:!0})};var o={};return(()=>{var e=o;Object.defineProperty(e,"M",{value:!0}),e.track=void 0;const t=n(508),i=n(209),s="undefined"!=typeof window,r=s&&window.minimalAnalytics?.defineGlobal,a=s&&window.minimalAnalytics?.autoTrack,c=["q","s","search","query","keyword"];let u,l,d,f=[[Date.now()]],m=!1;const g="page_view",v="scroll",p="click",w="view_search_results",h="user_engagement",y="file_download";function b(e,{type:n,event:o,debug:s}){const{location:r,referrer:a,title:u}=(0,t.m)(),{firstVisit:l,sessionStart:d,sessionCount:f}=(0,t.$)(!m),g=self.screen||{};let v=[[i.k.protocolVersion,"2"],[i.k.trackingId,e],[i.k.pageId,(0,t.p)()],[i.k.language,(navigator.language||"").toLowerCase()],[i.k.clientId,(0,t.l)()],[i.k.firstVisit,l],[i.k.hitCount,"1"],[i.k.sessionId,(0,t._)()],[i.k.sessionCount,f],[i.k.sessionEngagement,"1"],[i.k.sessionStart,d],[i.k.debug,s?"1":""],[i.k.referrer,a],[i.k.location,r],[i.k.title,u],[i.k.screenResolution,`${g.width}x${g.height}`]];return v=v.concat(function({type:e="",event:n}){const o=document.location.search,s=new URLSearchParams(o),r=c.some((e=>new RegExp(`[?|&]${e}=`,"g").test(o)))?w:e,a=c.find((e=>s.get(e)));let u=[[i.k.eventName,r],[`${i.k.eventParam}.search_term`,a||""]];return n&&(u=u.concat((0,t.g)(n))),u}({type:n,event:o})),v=v.filter((([,e])=>e)),new URLSearchParams(v)}function _(){return f.reduce(((e,[t,n=Date.now()])=>e+(n-t)),0).toString()}function $(e,n){const o=(0,t.j)(n.target,"a, button, input[type=submit], input[type=button]"),s=o?.tagName?.toLowerCase(),r="a"===s?"link":s,a=o?.getAttribute("href")||void 0,c=o?.getAttribute("download")||void 0||a,{t:u,hostname:l,pathname:d}=(0,t.S)(c),f="link"===r&&!u,[m]=c?.match(new RegExp(i.files.join("|"),"g"))||[],g=m?y:p,v=`${i.k.eventParam}.${r}`;if(!o||f&&!m)return;let w=[[`${v}_id`,o.id],[`${v}_classes`,o.className],[`${v}_name`,o?.getAttribute("name")?.trim()],[`${v}_text`,o.textContent?.trim()],[`${v}_value`,o?.getAttribute("value")?.trim()],[`${v}_url`,a],[`${v}_domain`,l],[`${i.k.eventParam}.outbound`,`${u}`],[i.k.enagementTime,_()]];m&&(w=w.concat([[`${i.k.eventParam}.file_name`,d||c],[`${i.k.eventParam}.file_extension`,m]])),D(e,{type:g,event:w})}function S(){const e=f.length-1,[,t]=f[e];t||f[e].push(Date.now())}function x(){const e=f.length-1,[,t]=f[e];t&&f.push([Date.now()])}function j(){const e=f.length-1,[,t]=f[e],n=["hidden","visible"].indexOf(document.visibilityState),o=Boolean(n);-1!==n&&(o?t&&f.push([Date.now()]):!t&&f[e].push(Date.now()))}const I=(0,t.u)((e=>{if((0,t.h)()<90)return;const n=[[`${i.k.eventParamNumber}.percent_scrolled`,90]];D(e,{type:v,event:n}),document.removeEventListener("scroll",l)}));function M(e){const t=[[i.k.enagementTime,_()]];D(e,{type:h,event:t})}function k(e){m||(u=$.bind(null,e),l=I.bind(null,e),d=M.bind(null,e),document.addEventListener("visibilitychange",j),document.addEventListener("scroll",l),document.addEventListener("click",u),window.addEventListener("blur",S),window.addEventListener("focus",x),window.addEventListener("beforeunload",d))}function D(...e){const[t,{type:n,event:o,debug:i}]=function(e){const t=window.minimalAnalytics?.trackingId,n="string"==typeof e[0]?e[0]:t,o="object"==typeof e[0]?e[0]:e[1]||{};return[n,{type:g,...o}]}(e);if(!t)return void console.error("GA4: Tracking ID is missing or undefined");const s=b(t,{type:n,event:o,debug:i}),r=window.minimalAnalytics?.analyticsEndpoint||"https://www.google-analytics.com/g/collect";navigator.sendBeacon(`${r}?${s}`),k(t),m=!0}e.track=D,r&&(window.track=D),a&&D()})(),o})()));
</script>
Many Thanks, @jahilldev, much appreciated!
The current version is v1.8.7, so in the future, I need to update it if you released a new version since I am hosting the minimal script locally.
The latest version will be always here, right? https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js
I still have one more challenge with analyticsEndpoint:
analyticsEndpoint: '/my/analytics/endpoint/can/be/called/anything'
My google search is failing, can you point me in the right direction, please?
Thank You!
No worries, happy to help!
The current version is v1.8.7, so in the future, I need to update it if you released a new version since I am hosting the minimal script locally.
The latest version will be always here, right? https://cdn.jsdelivr.net/npm/@minimal-analytics/ga4/dist/index.js
Yes that's correct 👍
You could watch the repo for releases, or setup a script that checks the CDN url above periodically for updates, and either notify you of a release, or better still, dynamically update the script for you (that's out of scope for now 😉)
I still have one more challenge with analyticsEndpoint
What you do here is really down to your environment. You'll need to create an endpoint, perhaps via Wordpress or even an edge function service. The analyticsEndpoint
config property above just tells the script to send all tracking data to that server, instead of directly to the GA4 API. This should bypass any AdBlocker's algorithm.
There are a few (not entirely applicable, but close enough!) articles I found from searching for:
GA4 bypass adblocker
Node:
https://www.freecodecamp.org/news/save-your-analytics-from-content-blockers-7ee08c6ec7ee/
Wordpress (CAOS, not sure about this one, but the diagram explains what's needed well):
https://daan.dev/blog/how-to/bypass-ad-blockers-caos/
Good luck!
Thank you @jahilldev,
Yes, I understood the logic behind creating the request.
I have created the Analytic Endpoint, but still, the Firefox Private Mode browser is not working.
If I access it from a normal browser, I can see real-time data so the endpoint that I created is working.
Any help is highly appreciated.
Thanks!
@CharbelNemnom It's difficult for me to help without knowing more context around exactly what you're doing.
Given "Firefox Private Mode" does its best to block tracking, it could be that something you're doing is triggering their logic somehow.
Anecdotally, I know people have achieved this by using the custom endpoint, but I haven't personally.
I'd suggest we continue this via an issue below, given this is a little off topic for this Gist.
https://github.com/jahilldev/minimal-analytics
If I have a minute, and you can provide some kind of repo outlining exactly what you're doing, I might be able to help, but can't guarantee anything, I'm afraid.
My initial suggestions:
- Attempt to find out exactly what conditions Firefox are using to constitute tracking
- Debug where in the code the request is being blocked, if at all
- Experiment with different endpoint names, given this is likely the primary condition they're using, in conjunction with known tracking domain names
Let me know how you get on (in a seperate issue, please!)
Thanks 👍
Thank you so much @jahilldev,
I have opened an issue on your repo: https://github.com/jahilldev/minimal-analytics
So we can continue troubleshooting there.
Many Thanks!
Hola a todos, creo que lo logré. Aquí está mi publicación (con código y referencia a Gist) sobre un fragmento muy mínimo de Google Analytics 4
Gracias....
@jahilldev Any process on inline event tracking like the onclick="ga('send', 'event', 'eventCategory', 'eventAction', 'eventLabel');"
Basically all I want is to do as you had in universal Event: ma.trackEvent('Category', 'Action', 'Label', 'Value')
How would it look for example on a link element.
<a href="https://example.com/link/">Visit</a>
Like
<a class="button" href="#" onclick="gtag('event', <action>, {
'event_category': <category>,
'event_label': <label>
});">
<span class="button_label">Start now
</span>
</a>
On this website there is in detail described URL+Payload for Google Analytics 4.
the URL changed from
/collect/
to/g/collect/
, thev
attribute to value2
etc.I tried to change some elements. Add missing ones (changed
t
toen
andpageview
topage_view
) and use a new tracking code (for testing G-EGREM0V9JQ) but without luck.Hope somebody better in JavaScript will be able to figure this out.
There is new bit
gtm
there and_p
that is not described and is the reason why collect is not sending its payload.Interesting is that when I send RAW url (changing cid and sid numbers)
https://www.google-analytics.com/g/collect?v=2&tid=G-EGREM0V9JQ>m=2oe3n1&_p=2086482769&sr=1920x1080&ul=en&cid=526699612.1648651312&_s=1&dl=https%3A%2F%[2F](http://2f/)%2F&dt=My%20Hugo%20Playground&sid=1648651312&sct=1&seg=1&en=page_view&_ss=1
It will collect traffic in GAv4 so there is a progress, but now need to put this together into the above code, hence need somebody to help with that.