@webority/analytics 0.5.1 → 0.6.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/consent.js +108 -16
- package/src/tags.js +38 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
var WeborityAnalytics=(()=>{var
|
|
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);})();
|
package/package.json
CHANGED
package/src/consent.js
CHANGED
|
@@ -2,11 +2,14 @@ import { OPT_IN_COUNTRIES } from "./consent-regions.js";
|
|
|
2
2
|
|
|
3
3
|
export const CONSENT_COOKIE_NAME = "webority_cookie_consent";
|
|
4
4
|
export const CONSENT_CHANGE_EVENT = "webority-consent-change";
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_COOKIE_DAYS = 365;
|
|
6
6
|
|
|
7
7
|
const consentState = {
|
|
8
8
|
initialized: false,
|
|
9
9
|
requiresOptIn: true,
|
|
10
|
+
strictOptIn: false,
|
|
11
|
+
adStorage: true,
|
|
12
|
+
cookieDays: DEFAULT_COOKIE_DAYS,
|
|
10
13
|
cookieDomain: null,
|
|
11
14
|
saved: null,
|
|
12
15
|
};
|
|
@@ -18,19 +21,50 @@ const listeners = new Set();
|
|
|
18
21
|
* knows the visitor's country (requiresOptIn(countryCode)); guessing it in the browser misplaces
|
|
19
22
|
* VPN users. cookieDomain (".capnix.ai") shares one choice between the website and every portal
|
|
20
23
|
* subdomain; without it the choice stays on the current host.
|
|
24
|
+
*
|
|
25
|
+
* strictOptIn asks every visitor in every region: Consent Mode is denied everywhere until they
|
|
26
|
+
* choose, loadTags holds Tag Manager and Ahrefs until consent, and requiresOptIn may be left out.
|
|
27
|
+
* adStorage says whether marketing consent also grants Google's ad_* signals: true by default,
|
|
28
|
+
* false under strictOptIn. cookieDays is the saved choice's lifetime, 365 by default.
|
|
21
29
|
*/
|
|
22
30
|
export function initConsent(options) {
|
|
23
|
-
|
|
31
|
+
const strictOptIn = options?.strictOptIn ?? false;
|
|
32
|
+
if (typeof strictOptIn !== "boolean") {
|
|
33
|
+
throw new Error("@webority/analytics: initConsent strictOptIn must be true or false, or left out.");
|
|
34
|
+
}
|
|
35
|
+
if (strictOptIn) {
|
|
36
|
+
if (options.requiresOptIn !== undefined && options.requiresOptIn !== true) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"@webority/analytics: initConsent strictOptIn asks every visitor to opt in, so requiresOptIn must be true or left out.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
} else if (typeof options?.requiresOptIn !== "boolean") {
|
|
24
42
|
throw new Error("@webority/analytics: initConsent needs requiresOptIn (true or false), decided from the visitor's country.");
|
|
25
43
|
}
|
|
26
44
|
if (options.cookieDomain !== undefined && (typeof options.cookieDomain !== "string" || !options.cookieDomain.trim())) {
|
|
27
45
|
throw new Error('@webority/analytics: initConsent cookieDomain must be a domain such as ".example.com", or left out.');
|
|
28
46
|
}
|
|
47
|
+
if (options.cookieDays !== undefined && !(Number.isInteger(options.cookieDays) && options.cookieDays > 0)) {
|
|
48
|
+
throw new Error("@webority/analytics: initConsent cookieDays must be a whole number of days above zero, or left out.");
|
|
49
|
+
}
|
|
50
|
+
if (options.adStorage !== undefined && typeof options.adStorage !== "boolean") {
|
|
51
|
+
throw new Error("@webority/analytics: initConsent adStorage must be true or false, or left out.");
|
|
52
|
+
}
|
|
29
53
|
|
|
30
54
|
consentState.initialized = true;
|
|
31
|
-
consentState.
|
|
55
|
+
consentState.strictOptIn = strictOptIn;
|
|
56
|
+
consentState.requiresOptIn = strictOptIn || options.requiresOptIn;
|
|
57
|
+
consentState.adStorage = options.adStorage ?? !strictOptIn;
|
|
58
|
+
consentState.cookieDays = options.cookieDays ?? DEFAULT_COOKIE_DAYS;
|
|
32
59
|
consentState.cookieDomain = options.cookieDomain?.trim() ?? null;
|
|
33
|
-
|
|
60
|
+
const { saved, entryCount } = readConsentCookie();
|
|
61
|
+
consentState.saved = saved;
|
|
62
|
+
|
|
63
|
+
// A host-only cookie from before the domain was set sits beside the shared one, and
|
|
64
|
+
// browsers list the older first. Folding them into one stops an old choice resurfacing.
|
|
65
|
+
if (consentState.cookieDomain && entryCount > 1 && saved) {
|
|
66
|
+
writeConsentCookie(saved);
|
|
67
|
+
}
|
|
34
68
|
|
|
35
69
|
pushConsentDefaults();
|
|
36
70
|
if (consentState.saved) {
|
|
@@ -42,6 +76,14 @@ export function isConsentInitialized() {
|
|
|
42
76
|
return consentState.initialized;
|
|
43
77
|
}
|
|
44
78
|
|
|
79
|
+
export function isStrictOptIn() {
|
|
80
|
+
return consentState.strictOptIn;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isAdStorageEnabled() {
|
|
84
|
+
return consentState.adStorage;
|
|
85
|
+
}
|
|
86
|
+
|
|
45
87
|
/**
|
|
46
88
|
* The visitor's current consent. Before they choose (decided: false) every optional category
|
|
47
89
|
* follows the region default: denied where opt-in is required, granted everywhere else.
|
|
@@ -117,6 +159,19 @@ function gtag() {
|
|
|
117
159
|
|
|
118
160
|
// Defaults must reach dataLayer before the Tag Manager container loads, or its tags fire ungated.
|
|
119
161
|
function pushConsentDefaults() {
|
|
162
|
+
if (consentState.strictOptIn) {
|
|
163
|
+
gtag("consent", "default", {
|
|
164
|
+
ad_storage: "denied",
|
|
165
|
+
ad_user_data: "denied",
|
|
166
|
+
ad_personalization: "denied",
|
|
167
|
+
analytics_storage: "denied",
|
|
168
|
+
functionality_storage: "denied",
|
|
169
|
+
personalization_storage: "denied",
|
|
170
|
+
security_storage: "granted",
|
|
171
|
+
wait_for_update: 500,
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
120
175
|
gtag("consent", "default", {
|
|
121
176
|
ad_storage: "denied",
|
|
122
177
|
ad_user_data: "denied",
|
|
@@ -141,28 +196,30 @@ function pushConsentDefaults() {
|
|
|
141
196
|
|
|
142
197
|
function pushConsentUpdate(saved) {
|
|
143
198
|
const state = (granted) => (granted ? "granted" : "denied");
|
|
199
|
+
const ads = saved.marketing && consentState.adStorage;
|
|
144
200
|
gtag("consent", "update", {
|
|
145
201
|
analytics_storage: state(saved.analytics),
|
|
146
202
|
functionality_storage: state(saved.functional),
|
|
147
203
|
personalization_storage: state(saved.functional),
|
|
148
|
-
ad_storage: state(
|
|
149
|
-
ad_user_data: state(
|
|
150
|
-
ad_personalization: state(
|
|
204
|
+
ad_storage: state(ads),
|
|
205
|
+
ad_user_data: state(ads),
|
|
206
|
+
ad_personalization: state(ads),
|
|
151
207
|
});
|
|
152
208
|
}
|
|
153
209
|
|
|
154
210
|
function readConsentCookie() {
|
|
155
211
|
const prefix = `${CONSENT_COOKIE_NAME}=`;
|
|
156
|
-
const
|
|
212
|
+
const entries = (document.cookie || "")
|
|
157
213
|
.split(";")
|
|
158
214
|
.map((part) => part.trim())
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
215
|
+
.filter((part) => part.startsWith(prefix));
|
|
216
|
+
const choices = entries.map((entry) => parseConsentEntry(entry.slice(prefix.length))).filter(Boolean);
|
|
217
|
+
return { saved: newestChoice(choices), entryCount: entries.length };
|
|
218
|
+
}
|
|
163
219
|
|
|
220
|
+
function parseConsentEntry(value) {
|
|
164
221
|
try {
|
|
165
|
-
const parsed = JSON.parse(decodeURIComponent(
|
|
222
|
+
const parsed = JSON.parse(decodeURIComponent(value));
|
|
166
223
|
if (!parsed || typeof parsed !== "object") {
|
|
167
224
|
return null;
|
|
168
225
|
}
|
|
@@ -179,17 +236,52 @@ function readConsentCookie() {
|
|
|
179
236
|
}
|
|
180
237
|
}
|
|
181
238
|
|
|
239
|
+
// Without a readable timestamp on every choice, the last one wins: browsers list equal-path
|
|
240
|
+
// cookies oldest first.
|
|
241
|
+
function newestChoice(choices) {
|
|
242
|
+
if (choices.length === 0) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
const times = choices.map((choice) => (typeof choice.timestamp === "string" ? Date.parse(choice.timestamp) : Number.NaN));
|
|
246
|
+
if (times.some(Number.isNaN)) {
|
|
247
|
+
return choices.at(-1);
|
|
248
|
+
}
|
|
249
|
+
let newest = 0;
|
|
250
|
+
for (let index = 1; index < choices.length; index++) {
|
|
251
|
+
if (times[index] >= times[newest]) {
|
|
252
|
+
newest = index;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return choices[newest];
|
|
256
|
+
}
|
|
257
|
+
|
|
182
258
|
function writeConsentCookie(saved) {
|
|
183
|
-
|
|
184
|
-
const
|
|
259
|
+
// The lifetime runs from the choice, so folding an old choice onto the shared domain does not extend it.
|
|
260
|
+
const chosenAt = typeof saved.timestamp === "string" ? Date.parse(saved.timestamp) : Number.NaN;
|
|
261
|
+
const expiresAt = (Number.isNaN(chosenAt) ? Date.now() : chosenAt) + consentState.cookieDays * 24 * 60 * 60 * 1000;
|
|
185
262
|
const secure = window.location?.protocol === "https:" ? ";Secure" : "";
|
|
263
|
+
if (consentState.cookieDomain) {
|
|
264
|
+
// Expired before the shared write: a browser that treats the two as one cookie on the
|
|
265
|
+
// apex host then still ends up holding the shared one.
|
|
266
|
+
setCookie(`${CONSENT_COOKIE_NAME}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;max-age=0;path=/;SameSite=Lax${secure}`);
|
|
267
|
+
}
|
|
268
|
+
const domain = consentState.cookieDomain ? `;domain=${consentState.cookieDomain}` : "";
|
|
269
|
+
setCookie(
|
|
270
|
+
`${CONSENT_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(saved))};expires=${new Date(expiresAt).toUTCString()};path=/${domain};SameSite=Lax${secure}`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function setCookie(text) {
|
|
186
275
|
// biome-ignore lint/suspicious/noDocumentCookie: the Cookie Store API is missing from browsers our visitors use.
|
|
187
|
-
document.cookie =
|
|
276
|
+
document.cookie = text;
|
|
188
277
|
}
|
|
189
278
|
|
|
190
279
|
export function resetConsentForTests() {
|
|
191
280
|
consentState.initialized = false;
|
|
192
281
|
consentState.requiresOptIn = true;
|
|
282
|
+
consentState.strictOptIn = false;
|
|
283
|
+
consentState.adStorage = true;
|
|
284
|
+
consentState.cookieDays = DEFAULT_COOKIE_DAYS;
|
|
193
285
|
consentState.cookieDomain = null;
|
|
194
286
|
consentState.saved = null;
|
|
195
287
|
listeners.clear();
|
package/src/tags.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getConsent, isConsentInitialized, onConsentChange } from "./consent.js";
|
|
1
|
+
import { getConsent, isAdStorageEnabled, isConsentInitialized, isStrictOptIn, onConsentChange } from "./consent.js";
|
|
2
2
|
|
|
3
3
|
const INTERACTION_EVENTS = ["scroll", "mousemove", "touchstart", "keydown", "pointerdown"];
|
|
4
4
|
const DEFER_AFTER_LOAD_MS = 1500;
|
|
@@ -18,6 +18,11 @@ const tagState = {
|
|
|
18
18
|
* analytics consent, and a revoke takes effect from the next page. Ahrefs is cookieless and
|
|
19
19
|
* loads straight away. GA4 is deliberately absent: it runs inside the Tag Manager container,
|
|
20
20
|
* and loading it here as well counts every page view twice.
|
|
21
|
+
*
|
|
22
|
+
* Under initConsent's strictOptIn nothing loads before consent: Ahrefs waits for analytics, and
|
|
23
|
+
* Tag Manager for analytics, or for marketing when adStorage lets marketing grant ad signals,
|
|
24
|
+
* whether granted on this page or saved on an earlier one. Marketing alone without adStorage
|
|
25
|
+
* unlocks nothing in the container, and loading it would still let GA4 send cookieless pings.
|
|
21
26
|
*/
|
|
22
27
|
export function loadTags(options = {}) {
|
|
23
28
|
if (!isConsentInitialized()) {
|
|
@@ -29,17 +34,30 @@ export function loadTags(options = {}) {
|
|
|
29
34
|
tagState.loaded = true;
|
|
30
35
|
|
|
31
36
|
const { gtmId, clarityId, ahrefsKey } = options;
|
|
37
|
+
const strict = isStrictOptIn();
|
|
32
38
|
|
|
33
39
|
if (ahrefsKey) {
|
|
34
|
-
appendScript("https://analytics.ahrefs.com/analytics.js", { "data-key": ahrefsKey });
|
|
40
|
+
const loadAhrefs = () => appendScript("https://analytics.ahrefs.com/analytics.js", { "data-key": ahrefsKey });
|
|
41
|
+
if (strict) {
|
|
42
|
+
whenConsented((consent) => consent.analytics, loadAhrefs);
|
|
43
|
+
} else {
|
|
44
|
+
loadAhrefs();
|
|
45
|
+
}
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
if (gtmId) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
const loadTagManager = () =>
|
|
50
|
+
runAfterFirstInteraction(() => {
|
|
51
|
+
window.dataLayer = window.dataLayer || [];
|
|
52
|
+
window.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
53
|
+
appendScript(`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(gtmId)}`);
|
|
54
|
+
});
|
|
55
|
+
if (strict) {
|
|
56
|
+
const ads = isAdStorageEnabled();
|
|
57
|
+
whenConsented((consent) => consent.analytics || (consent.marketing && ads), loadTagManager);
|
|
58
|
+
} else {
|
|
59
|
+
loadTagManager();
|
|
60
|
+
}
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
if (clarityId) {
|
|
@@ -55,6 +73,19 @@ export function loadTags(options = {}) {
|
|
|
55
73
|
}
|
|
56
74
|
}
|
|
57
75
|
|
|
76
|
+
function whenConsented(granted, callback) {
|
|
77
|
+
if (granted(getConsent())) {
|
|
78
|
+
callback();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const unsubscribe = onConsentChange((consent) => {
|
|
82
|
+
if (granted(consent)) {
|
|
83
|
+
unsubscribe();
|
|
84
|
+
callback();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
function runAfterFirstInteraction(callback) {
|
|
59
90
|
let ran = false;
|
|
60
91
|
const run = () => {
|