@blotoutio/providers-google-analytics-4-sdk 1.53.0 → 1.54.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/index.cjs.js +174 -8
- package/index.js +174 -8
- package/index.mjs +174 -8
- package/package.json +1 -1
package/index.cjs.js
CHANGED
|
@@ -57,6 +57,40 @@ const upsert = (map, key, update, createDefault) => {
|
|
|
57
57
|
return map.set(key, update(currentValue));
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Known ad-network click ID query parameters and the human-readable provider
|
|
62
|
+
* label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
|
|
63
|
+
* and by the analytics API to classify paid-media touch sessions.
|
|
64
|
+
*
|
|
65
|
+
* Keep this list in sync with `defaultParams` in queryParams.ts when new
|
|
66
|
+
* click ID providers are added.
|
|
67
|
+
*/
|
|
68
|
+
const CLICK_IDS = [
|
|
69
|
+
{ param: 'fbclid', label: 'Facebook' }, // Facebook Ads
|
|
70
|
+
{ param: 'gclid', label: 'Google' }, // Google Ads (click)
|
|
71
|
+
{ param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
|
|
72
|
+
{ param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
|
|
73
|
+
{ param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
|
|
74
|
+
{ param: 'ttclid', label: 'TikTok' }, // TikTok Ads
|
|
75
|
+
{ param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
|
|
76
|
+
{ param: 'epik', label: 'Pinterest' }, // Pinterest Ads
|
|
77
|
+
{ param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
|
|
78
|
+
{ param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
|
|
79
|
+
{ param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
|
|
80
|
+
{ param: 'aleid', label: 'AppLovin' }, // AppLovin
|
|
81
|
+
{ param: 'tabclid', label: 'Taboola' }, // Taboola
|
|
82
|
+
{ param: 'obclid', label: 'Outbrain' }, // Outbrain
|
|
83
|
+
{ param: 'trybe', label: 'Trybe' }, // Trybe
|
|
84
|
+
{ param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
|
|
85
|
+
{ param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
|
|
86
|
+
{ param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
|
|
87
|
+
{ param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
|
|
88
|
+
{ param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
|
|
89
|
+
{ param: '_raclid', label: 'Rumble' }, // Rumble Ads
|
|
90
|
+
{ param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
|
|
91
|
+
{ param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
|
|
92
|
+
];
|
|
93
|
+
|
|
60
94
|
const isPrimitive = (value) => value === null ||
|
|
61
95
|
typeof value === 'boolean' ||
|
|
62
96
|
Number.isFinite(value) ||
|
|
@@ -459,6 +493,127 @@ new Set([...isoCountries.keys(), ...usStates.keys()]);
|
|
|
459
493
|
*/
|
|
460
494
|
const jsonClone = (json) => tryParse(JSON.stringify(json), undefined);
|
|
461
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Exact utm_source normalization (lowercase key → canonical name).
|
|
498
|
+
*
|
|
499
|
+
* Use exact match when the key is short, generic, or must not accidentally
|
|
500
|
+
* absorb variant spellings (e.g. "impact" must not match "impact_radius").
|
|
501
|
+
*
|
|
502
|
+
* Ordering: Paid (search/social → ad networks → affiliates) →
|
|
503
|
+
* Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
|
|
504
|
+
*/
|
|
505
|
+
const UTM_SOURCE_EXACT = {
|
|
506
|
+
// ── Paid – Search & Social ────────────────────────────────────────────────
|
|
507
|
+
google: 'Google',
|
|
508
|
+
adwords: 'Google',
|
|
509
|
+
youtube: 'Google',
|
|
510
|
+
yt: 'Google',
|
|
511
|
+
meta: 'Facebook',
|
|
512
|
+
facebook: 'Facebook',
|
|
513
|
+
instagram: 'Facebook',
|
|
514
|
+
ig: 'Facebook',
|
|
515
|
+
igshopping: 'Facebook',
|
|
516
|
+
threads: 'Facebook',
|
|
517
|
+
twitter: 'Twitter',
|
|
518
|
+
snapchat: 'Snapchat',
|
|
519
|
+
pinterest: 'Pinterest',
|
|
520
|
+
bing: 'Bing',
|
|
521
|
+
microsoft: 'Bing',
|
|
522
|
+
tiktok: 'TikTok',
|
|
523
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
524
|
+
rtbhouse: 'RTB House',
|
|
525
|
+
applovin: 'AppLovin',
|
|
526
|
+
ttd: 'The Trade Desk',
|
|
527
|
+
amazondsp: 'Amazon DSP',
|
|
528
|
+
axon: 'Axon',
|
|
529
|
+
duel: 'Duel',
|
|
530
|
+
// ── Paid – Affiliates ────────────────────────────────────────────────────
|
|
531
|
+
awin: 'Awin',
|
|
532
|
+
'affiliate-cj': 'CJ Affiliate',
|
|
533
|
+
impact: 'Impact',
|
|
534
|
+
rakuten: 'Rakuten',
|
|
535
|
+
superfiliate: 'Superfiliate',
|
|
536
|
+
// ── Organic – AI & Discovery ─────────────────────────────────────────────
|
|
537
|
+
perplexity: 'Perplexity',
|
|
538
|
+
chatgpt: 'ChatGPT',
|
|
539
|
+
'chatgpt.com': 'ChatGPT',
|
|
540
|
+
openai: 'ChatGPT',
|
|
541
|
+
'copilot.com': 'Microsoft Copilot',
|
|
542
|
+
copilot: 'Microsoft Copilot',
|
|
543
|
+
applenews: 'Apple News',
|
|
544
|
+
whatsapp: 'WhatsApp',
|
|
545
|
+
podcast: 'Podcast',
|
|
546
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
547
|
+
mailchimp: 'Mailchimp',
|
|
548
|
+
omnisend: 'Omnisend',
|
|
549
|
+
iterable: 'Iterable',
|
|
550
|
+
listrak: 'Listrak',
|
|
551
|
+
sailthru: 'Sailthru',
|
|
552
|
+
// ── Retention – Push ─────────────────────────────────────────────────────
|
|
553
|
+
pushowl: 'PushOwl',
|
|
554
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
555
|
+
narvar: 'Narvar',
|
|
556
|
+
shop_app: 'Shop App',
|
|
557
|
+
salesforce: 'Salesforce',
|
|
558
|
+
yotpo: 'Yotpo',
|
|
559
|
+
};
|
|
560
|
+
/**
|
|
561
|
+
* Partial utm_source normalization (startsWith prefix check, lowercase).
|
|
562
|
+
*
|
|
563
|
+
* Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
|
|
564
|
+
*/
|
|
565
|
+
const UTM_SOURCE_PARTIAL = [
|
|
566
|
+
// ── Paid – Social ─────────────────────────────────────────────────────────
|
|
567
|
+
{ prefix: 'tiktok', name: 'TikTok' },
|
|
568
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
569
|
+
{ prefix: 'criteo', name: 'Criteo' },
|
|
570
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
571
|
+
{ prefix: 'klaviyo', name: 'Klaviyo' },
|
|
572
|
+
// ── Retention – SMS ──────────────────────────────────────────────────────
|
|
573
|
+
{ prefix: 'attentive', name: 'Attentive' },
|
|
574
|
+
{ prefix: 'postscript', name: 'Postscript' },
|
|
575
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
576
|
+
{ prefix: 'afterpay', name: 'Afterpay' },
|
|
577
|
+
{ prefix: 'klarna', name: 'Klarna' },
|
|
578
|
+
];
|
|
579
|
+
const ORGANIC_SEARCH_ENGINES = [
|
|
580
|
+
{ match: 'google', name: 'Google' },
|
|
581
|
+
{ match: 'bing', name: 'Bing' },
|
|
582
|
+
{ match: 'yahoo', name: 'Yahoo' },
|
|
583
|
+
{ match: 'duckduckgo', name: 'DuckDuckGo' },
|
|
584
|
+
{ match: 'baidu', name: 'Baidu' },
|
|
585
|
+
{ match: 'yandex', name: 'Yandex' },
|
|
586
|
+
{ match: 'brave', name: 'Brave' },
|
|
587
|
+
];
|
|
588
|
+
const KNOWN_REFERRAL_PLATFORMS = [
|
|
589
|
+
{ match: 'facebook', name: 'Facebook' },
|
|
590
|
+
{ match: 'instagram', name: 'Facebook' },
|
|
591
|
+
{ match: 'twitter', name: 'Twitter' },
|
|
592
|
+
{ match: 'x.com', name: 'Twitter' },
|
|
593
|
+
{ match: 'tiktok', name: 'TikTok' },
|
|
594
|
+
{ match: 'pinterest', name: 'Pinterest' },
|
|
595
|
+
{ match: 'linkedin', name: 'LinkedIn' },
|
|
596
|
+
{ match: 'youtube', name: 'YouTube' },
|
|
597
|
+
{ match: 'chatgpt', name: 'ChatGPT' },
|
|
598
|
+
{ match: 'claude', name: 'Claude' },
|
|
599
|
+
{ match: 'perplexity', name: 'Perplexity' },
|
|
600
|
+
{ match: 'shop.app', name: 'Shop App' },
|
|
601
|
+
{ match: 'amazon', name: 'Amazon' },
|
|
602
|
+
{ match: 'attentive', name: 'Attentive' },
|
|
603
|
+
];
|
|
604
|
+
const OFFLINE_TOUCH = 'Blotout_Offline';
|
|
605
|
+
new Set([
|
|
606
|
+
...CLICK_IDS.map((c) => c.label),
|
|
607
|
+
...Object.values(UTM_SOURCE_EXACT),
|
|
608
|
+
...UTM_SOURCE_PARTIAL.map((e) => e.name),
|
|
609
|
+
...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
|
|
610
|
+
...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
|
|
611
|
+
'Referral - Other',
|
|
612
|
+
'Direct Traffic',
|
|
613
|
+
'Other',
|
|
614
|
+
OFFLINE_TOUCH,
|
|
615
|
+
]);
|
|
616
|
+
|
|
462
617
|
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
463
618
|
const getGoogleConsentFromCategories = (categories) => {
|
|
464
619
|
const analyticsConsent = isCategoryConsented(categories, 'analytics');
|
|
@@ -514,7 +669,8 @@ const initGA4 = (ID, advancedConsentMode, consentSkip, consentData, executionCon
|
|
|
514
669
|
script.parentNode.insertBefore(element, script);
|
|
515
670
|
}
|
|
516
671
|
};
|
|
517
|
-
const init = ({ manifest, userId, executionContext, consentData, }) => {
|
|
672
|
+
const init = ({ manifest, userId, executionContext, consentData, pageUrl, }) => {
|
|
673
|
+
var _a;
|
|
518
674
|
if (!window ||
|
|
519
675
|
!manifest.variables ||
|
|
520
676
|
!manifest.variables['measurementId'] ||
|
|
@@ -522,19 +678,29 @@ const init = ({ manifest, userId, executionContext, consentData, }) => {
|
|
|
522
678
|
return;
|
|
523
679
|
}
|
|
524
680
|
window.dataLayer = window.dataLayer || [];
|
|
525
|
-
window.gtag =
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
681
|
+
window.gtag =
|
|
682
|
+
window.gtag ||
|
|
683
|
+
function gtag() {
|
|
684
|
+
// eslint-disable-next-line prefer-rest-params
|
|
685
|
+
window.dataLayer.push(arguments);
|
|
686
|
+
};
|
|
529
687
|
if (!window.google_tag_manager ||
|
|
530
688
|
!window.google_tag_manager[manifest.variables['measurementId']]) {
|
|
531
689
|
initGA4(manifest.variables['measurementId'], manifest.variables['advancedConsentMode'], manifest.variables['consentSkip'], consentData, executionContext);
|
|
532
690
|
}
|
|
533
691
|
if (window.gtag) {
|
|
534
|
-
|
|
692
|
+
const config = {
|
|
535
693
|
user_id: userId,
|
|
536
694
|
send_page_view: false,
|
|
537
|
-
}
|
|
695
|
+
};
|
|
696
|
+
// Override gtag's `dl` param so enhanced-measurement events (scroll, click,
|
|
697
|
+
// etc.) auto-fired by gtag use the top-frame URL instead of the iframe
|
|
698
|
+
// sandbox's document.location.
|
|
699
|
+
const resolvedPageUrl = pageUrl || ((_a = window.location) === null || _a === void 0 ? void 0 : _a.href);
|
|
700
|
+
if (resolvedPageUrl) {
|
|
701
|
+
config['page_location'] = resolvedPageUrl;
|
|
702
|
+
}
|
|
703
|
+
window.gtag('config', manifest.variables['measurementId'], config);
|
|
538
704
|
}
|
|
539
705
|
};
|
|
540
706
|
|
|
@@ -806,7 +972,7 @@ const tag = ({ data, eventName, manifestVariables, eventId, pageUrl, }) => {
|
|
|
806
972
|
}
|
|
807
973
|
return {
|
|
808
974
|
loaded: isLoaded,
|
|
809
|
-
sdkVersion: "1.
|
|
975
|
+
sdkVersion: "1.54.0" ,
|
|
810
976
|
};
|
|
811
977
|
};
|
|
812
978
|
|
package/index.js
CHANGED
|
@@ -58,6 +58,40 @@ var ProvidersGoogleAnalytics4Sdk = (function () {
|
|
|
58
58
|
return map.set(key, update(currentValue));
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Known ad-network click ID query parameters and the human-readable provider
|
|
63
|
+
* label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
|
|
64
|
+
* and by the analytics API to classify paid-media touch sessions.
|
|
65
|
+
*
|
|
66
|
+
* Keep this list in sync with `defaultParams` in queryParams.ts when new
|
|
67
|
+
* click ID providers are added.
|
|
68
|
+
*/
|
|
69
|
+
const CLICK_IDS = [
|
|
70
|
+
{ param: 'fbclid', label: 'Facebook' }, // Facebook Ads
|
|
71
|
+
{ param: 'gclid', label: 'Google' }, // Google Ads (click)
|
|
72
|
+
{ param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
|
|
73
|
+
{ param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
|
|
74
|
+
{ param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
|
|
75
|
+
{ param: 'ttclid', label: 'TikTok' }, // TikTok Ads
|
|
76
|
+
{ param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
|
|
77
|
+
{ param: 'epik', label: 'Pinterest' }, // Pinterest Ads
|
|
78
|
+
{ param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
|
|
79
|
+
{ param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
|
|
80
|
+
{ param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
|
|
81
|
+
{ param: 'aleid', label: 'AppLovin' }, // AppLovin
|
|
82
|
+
{ param: 'tabclid', label: 'Taboola' }, // Taboola
|
|
83
|
+
{ param: 'obclid', label: 'Outbrain' }, // Outbrain
|
|
84
|
+
{ param: 'trybe', label: 'Trybe' }, // Trybe
|
|
85
|
+
{ param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
|
|
86
|
+
{ param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
|
|
87
|
+
{ param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
|
|
88
|
+
{ param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
|
|
89
|
+
{ param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
|
|
90
|
+
{ param: '_raclid', label: 'Rumble' }, // Rumble Ads
|
|
91
|
+
{ param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
|
|
92
|
+
{ param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
|
|
93
|
+
];
|
|
94
|
+
|
|
61
95
|
const isPrimitive = (value) => value === null ||
|
|
62
96
|
typeof value === 'boolean' ||
|
|
63
97
|
Number.isFinite(value) ||
|
|
@@ -460,6 +494,127 @@ var ProvidersGoogleAnalytics4Sdk = (function () {
|
|
|
460
494
|
*/
|
|
461
495
|
const jsonClone = (json) => tryParse(JSON.stringify(json), undefined);
|
|
462
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Exact utm_source normalization (lowercase key → canonical name).
|
|
499
|
+
*
|
|
500
|
+
* Use exact match when the key is short, generic, or must not accidentally
|
|
501
|
+
* absorb variant spellings (e.g. "impact" must not match "impact_radius").
|
|
502
|
+
*
|
|
503
|
+
* Ordering: Paid (search/social → ad networks → affiliates) →
|
|
504
|
+
* Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
|
|
505
|
+
*/
|
|
506
|
+
const UTM_SOURCE_EXACT = {
|
|
507
|
+
// ── Paid – Search & Social ────────────────────────────────────────────────
|
|
508
|
+
google: 'Google',
|
|
509
|
+
adwords: 'Google',
|
|
510
|
+
youtube: 'Google',
|
|
511
|
+
yt: 'Google',
|
|
512
|
+
meta: 'Facebook',
|
|
513
|
+
facebook: 'Facebook',
|
|
514
|
+
instagram: 'Facebook',
|
|
515
|
+
ig: 'Facebook',
|
|
516
|
+
igshopping: 'Facebook',
|
|
517
|
+
threads: 'Facebook',
|
|
518
|
+
twitter: 'Twitter',
|
|
519
|
+
snapchat: 'Snapchat',
|
|
520
|
+
pinterest: 'Pinterest',
|
|
521
|
+
bing: 'Bing',
|
|
522
|
+
microsoft: 'Bing',
|
|
523
|
+
tiktok: 'TikTok',
|
|
524
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
525
|
+
rtbhouse: 'RTB House',
|
|
526
|
+
applovin: 'AppLovin',
|
|
527
|
+
ttd: 'The Trade Desk',
|
|
528
|
+
amazondsp: 'Amazon DSP',
|
|
529
|
+
axon: 'Axon',
|
|
530
|
+
duel: 'Duel',
|
|
531
|
+
// ── Paid – Affiliates ────────────────────────────────────────────────────
|
|
532
|
+
awin: 'Awin',
|
|
533
|
+
'affiliate-cj': 'CJ Affiliate',
|
|
534
|
+
impact: 'Impact',
|
|
535
|
+
rakuten: 'Rakuten',
|
|
536
|
+
superfiliate: 'Superfiliate',
|
|
537
|
+
// ── Organic – AI & Discovery ─────────────────────────────────────────────
|
|
538
|
+
perplexity: 'Perplexity',
|
|
539
|
+
chatgpt: 'ChatGPT',
|
|
540
|
+
'chatgpt.com': 'ChatGPT',
|
|
541
|
+
openai: 'ChatGPT',
|
|
542
|
+
'copilot.com': 'Microsoft Copilot',
|
|
543
|
+
copilot: 'Microsoft Copilot',
|
|
544
|
+
applenews: 'Apple News',
|
|
545
|
+
whatsapp: 'WhatsApp',
|
|
546
|
+
podcast: 'Podcast',
|
|
547
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
548
|
+
mailchimp: 'Mailchimp',
|
|
549
|
+
omnisend: 'Omnisend',
|
|
550
|
+
iterable: 'Iterable',
|
|
551
|
+
listrak: 'Listrak',
|
|
552
|
+
sailthru: 'Sailthru',
|
|
553
|
+
// ── Retention – Push ─────────────────────────────────────────────────────
|
|
554
|
+
pushowl: 'PushOwl',
|
|
555
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
556
|
+
narvar: 'Narvar',
|
|
557
|
+
shop_app: 'Shop App',
|
|
558
|
+
salesforce: 'Salesforce',
|
|
559
|
+
yotpo: 'Yotpo',
|
|
560
|
+
};
|
|
561
|
+
/**
|
|
562
|
+
* Partial utm_source normalization (startsWith prefix check, lowercase).
|
|
563
|
+
*
|
|
564
|
+
* Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
|
|
565
|
+
*/
|
|
566
|
+
const UTM_SOURCE_PARTIAL = [
|
|
567
|
+
// ── Paid – Social ─────────────────────────────────────────────────────────
|
|
568
|
+
{ prefix: 'tiktok', name: 'TikTok' },
|
|
569
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
570
|
+
{ prefix: 'criteo', name: 'Criteo' },
|
|
571
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
572
|
+
{ prefix: 'klaviyo', name: 'Klaviyo' },
|
|
573
|
+
// ── Retention – SMS ──────────────────────────────────────────────────────
|
|
574
|
+
{ prefix: 'attentive', name: 'Attentive' },
|
|
575
|
+
{ prefix: 'postscript', name: 'Postscript' },
|
|
576
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
577
|
+
{ prefix: 'afterpay', name: 'Afterpay' },
|
|
578
|
+
{ prefix: 'klarna', name: 'Klarna' },
|
|
579
|
+
];
|
|
580
|
+
const ORGANIC_SEARCH_ENGINES = [
|
|
581
|
+
{ match: 'google', name: 'Google' },
|
|
582
|
+
{ match: 'bing', name: 'Bing' },
|
|
583
|
+
{ match: 'yahoo', name: 'Yahoo' },
|
|
584
|
+
{ match: 'duckduckgo', name: 'DuckDuckGo' },
|
|
585
|
+
{ match: 'baidu', name: 'Baidu' },
|
|
586
|
+
{ match: 'yandex', name: 'Yandex' },
|
|
587
|
+
{ match: 'brave', name: 'Brave' },
|
|
588
|
+
];
|
|
589
|
+
const KNOWN_REFERRAL_PLATFORMS = [
|
|
590
|
+
{ match: 'facebook', name: 'Facebook' },
|
|
591
|
+
{ match: 'instagram', name: 'Facebook' },
|
|
592
|
+
{ match: 'twitter', name: 'Twitter' },
|
|
593
|
+
{ match: 'x.com', name: 'Twitter' },
|
|
594
|
+
{ match: 'tiktok', name: 'TikTok' },
|
|
595
|
+
{ match: 'pinterest', name: 'Pinterest' },
|
|
596
|
+
{ match: 'linkedin', name: 'LinkedIn' },
|
|
597
|
+
{ match: 'youtube', name: 'YouTube' },
|
|
598
|
+
{ match: 'chatgpt', name: 'ChatGPT' },
|
|
599
|
+
{ match: 'claude', name: 'Claude' },
|
|
600
|
+
{ match: 'perplexity', name: 'Perplexity' },
|
|
601
|
+
{ match: 'shop.app', name: 'Shop App' },
|
|
602
|
+
{ match: 'amazon', name: 'Amazon' },
|
|
603
|
+
{ match: 'attentive', name: 'Attentive' },
|
|
604
|
+
];
|
|
605
|
+
const OFFLINE_TOUCH = 'Blotout_Offline';
|
|
606
|
+
new Set([
|
|
607
|
+
...CLICK_IDS.map((c) => c.label),
|
|
608
|
+
...Object.values(UTM_SOURCE_EXACT),
|
|
609
|
+
...UTM_SOURCE_PARTIAL.map((e) => e.name),
|
|
610
|
+
...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
|
|
611
|
+
...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
|
|
612
|
+
'Referral - Other',
|
|
613
|
+
'Direct Traffic',
|
|
614
|
+
'Other',
|
|
615
|
+
OFFLINE_TOUCH,
|
|
616
|
+
]);
|
|
617
|
+
|
|
463
618
|
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
464
619
|
const getGoogleConsentFromCategories = (categories) => {
|
|
465
620
|
const analyticsConsent = isCategoryConsented(categories, 'analytics');
|
|
@@ -515,7 +670,8 @@ var ProvidersGoogleAnalytics4Sdk = (function () {
|
|
|
515
670
|
script.parentNode.insertBefore(element, script);
|
|
516
671
|
}
|
|
517
672
|
};
|
|
518
|
-
const init = ({ manifest, userId, executionContext, consentData, }) => {
|
|
673
|
+
const init = ({ manifest, userId, executionContext, consentData, pageUrl, }) => {
|
|
674
|
+
var _a;
|
|
519
675
|
if (!window ||
|
|
520
676
|
!manifest.variables ||
|
|
521
677
|
!manifest.variables['measurementId'] ||
|
|
@@ -523,19 +679,29 @@ var ProvidersGoogleAnalytics4Sdk = (function () {
|
|
|
523
679
|
return;
|
|
524
680
|
}
|
|
525
681
|
window.dataLayer = window.dataLayer || [];
|
|
526
|
-
window.gtag =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
682
|
+
window.gtag =
|
|
683
|
+
window.gtag ||
|
|
684
|
+
function gtag() {
|
|
685
|
+
// eslint-disable-next-line prefer-rest-params
|
|
686
|
+
window.dataLayer.push(arguments);
|
|
687
|
+
};
|
|
530
688
|
if (!window.google_tag_manager ||
|
|
531
689
|
!window.google_tag_manager[manifest.variables['measurementId']]) {
|
|
532
690
|
initGA4(manifest.variables['measurementId'], manifest.variables['advancedConsentMode'], manifest.variables['consentSkip'], consentData, executionContext);
|
|
533
691
|
}
|
|
534
692
|
if (window.gtag) {
|
|
535
|
-
|
|
693
|
+
const config = {
|
|
536
694
|
user_id: userId,
|
|
537
695
|
send_page_view: false,
|
|
538
|
-
}
|
|
696
|
+
};
|
|
697
|
+
// Override gtag's `dl` param so enhanced-measurement events (scroll, click,
|
|
698
|
+
// etc.) auto-fired by gtag use the top-frame URL instead of the iframe
|
|
699
|
+
// sandbox's document.location.
|
|
700
|
+
const resolvedPageUrl = pageUrl || ((_a = window.location) === null || _a === void 0 ? void 0 : _a.href);
|
|
701
|
+
if (resolvedPageUrl) {
|
|
702
|
+
config['page_location'] = resolvedPageUrl;
|
|
703
|
+
}
|
|
704
|
+
window.gtag('config', manifest.variables['measurementId'], config);
|
|
539
705
|
}
|
|
540
706
|
};
|
|
541
707
|
|
|
@@ -807,7 +973,7 @@ var ProvidersGoogleAnalytics4Sdk = (function () {
|
|
|
807
973
|
}
|
|
808
974
|
return {
|
|
809
975
|
loaded: isLoaded,
|
|
810
|
-
sdkVersion: "1.
|
|
976
|
+
sdkVersion: "1.54.0" ,
|
|
811
977
|
};
|
|
812
978
|
};
|
|
813
979
|
|
package/index.mjs
CHANGED
|
@@ -55,6 +55,40 @@ const upsert = (map, key, update, createDefault) => {
|
|
|
55
55
|
return map.set(key, update(currentValue));
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Known ad-network click ID query parameters and the human-readable provider
|
|
60
|
+
* label each one maps to. Used by the CDN worker to resolve `inSessionTouch`
|
|
61
|
+
* and by the analytics API to classify paid-media touch sessions.
|
|
62
|
+
*
|
|
63
|
+
* Keep this list in sync with `defaultParams` in queryParams.ts when new
|
|
64
|
+
* click ID providers are added.
|
|
65
|
+
*/
|
|
66
|
+
const CLICK_IDS = [
|
|
67
|
+
{ param: 'fbclid', label: 'Facebook' }, // Facebook Ads
|
|
68
|
+
{ param: 'gclid', label: 'Google' }, // Google Ads (click)
|
|
69
|
+
{ param: 'gbraid', label: 'Google' }, // Google Ads (iOS app)
|
|
70
|
+
{ param: 'wbraid', label: 'Google' }, // Google Ads (iOS web)
|
|
71
|
+
{ param: 'msclkid', label: 'Bing' }, // Microsoft Bing Ads
|
|
72
|
+
{ param: 'ttclid', label: 'TikTok' }, // TikTok Ads
|
|
73
|
+
{ param: 'ScCid', label: 'Snapchat' }, // Snapchat Ads
|
|
74
|
+
{ param: 'epik', label: 'Pinterest' }, // Pinterest Ads
|
|
75
|
+
{ param: 'li_fat_id', label: 'LinkedIn' }, // LinkedIn Ads
|
|
76
|
+
{ param: 'twclid', label: 'Twitter' }, // X (Twitter) Ads
|
|
77
|
+
{ param: 'rdt_cid', label: 'Reddit' }, // Reddit Ads
|
|
78
|
+
{ param: 'aleid', label: 'AppLovin' }, // AppLovin
|
|
79
|
+
{ param: 'tabclid', label: 'Taboola' }, // Taboola
|
|
80
|
+
{ param: 'obclid', label: 'Outbrain' }, // Outbrain
|
|
81
|
+
{ param: 'trybe', label: 'Trybe' }, // Trybe
|
|
82
|
+
{ param: '_kx', label: 'Klaviyo' }, // Klaviyo email campaigns
|
|
83
|
+
{ param: 'mc_eid', label: 'Mailchimp' }, // Mailchimp email campaigns
|
|
84
|
+
{ param: 'ttd_id', label: 'The Trade Desk' }, // The Trade Desk programmatic
|
|
85
|
+
{ param: 'evsclid', label: 'EvoSearch' }, // EvoSearch
|
|
86
|
+
{ param: 'li_did', label: 'Live Intent' }, // LiveIntent device-level ad
|
|
87
|
+
{ param: '_raclid', label: 'Rumble' }, // Rumble Ads
|
|
88
|
+
{ param: 'ref_id', label: 'StackAdapt' }, // StackAdapt programmatic
|
|
89
|
+
{ param: 'duel_a', label: 'Duel' }, // Duel referral/advocacy
|
|
90
|
+
];
|
|
91
|
+
|
|
58
92
|
const isPrimitive = (value) => value === null ||
|
|
59
93
|
typeof value === 'boolean' ||
|
|
60
94
|
Number.isFinite(value) ||
|
|
@@ -457,6 +491,127 @@ new Set([...isoCountries.keys(), ...usStates.keys()]);
|
|
|
457
491
|
*/
|
|
458
492
|
const jsonClone = (json) => tryParse(JSON.stringify(json), undefined);
|
|
459
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Exact utm_source normalization (lowercase key → canonical name).
|
|
496
|
+
*
|
|
497
|
+
* Use exact match when the key is short, generic, or must not accidentally
|
|
498
|
+
* absorb variant spellings (e.g. "impact" must not match "impact_radius").
|
|
499
|
+
*
|
|
500
|
+
* Ordering: Paid (search/social → ad networks → affiliates) →
|
|
501
|
+
* Organic (AI/discovery) → Retention (email → SMS → push → post-purchase)
|
|
502
|
+
*/
|
|
503
|
+
const UTM_SOURCE_EXACT = {
|
|
504
|
+
// ── Paid – Search & Social ────────────────────────────────────────────────
|
|
505
|
+
google: 'Google',
|
|
506
|
+
adwords: 'Google',
|
|
507
|
+
youtube: 'Google',
|
|
508
|
+
yt: 'Google',
|
|
509
|
+
meta: 'Facebook',
|
|
510
|
+
facebook: 'Facebook',
|
|
511
|
+
instagram: 'Facebook',
|
|
512
|
+
ig: 'Facebook',
|
|
513
|
+
igshopping: 'Facebook',
|
|
514
|
+
threads: 'Facebook',
|
|
515
|
+
twitter: 'Twitter',
|
|
516
|
+
snapchat: 'Snapchat',
|
|
517
|
+
pinterest: 'Pinterest',
|
|
518
|
+
bing: 'Bing',
|
|
519
|
+
microsoft: 'Bing',
|
|
520
|
+
tiktok: 'TikTok',
|
|
521
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
522
|
+
rtbhouse: 'RTB House',
|
|
523
|
+
applovin: 'AppLovin',
|
|
524
|
+
ttd: 'The Trade Desk',
|
|
525
|
+
amazondsp: 'Amazon DSP',
|
|
526
|
+
axon: 'Axon',
|
|
527
|
+
duel: 'Duel',
|
|
528
|
+
// ── Paid – Affiliates ────────────────────────────────────────────────────
|
|
529
|
+
awin: 'Awin',
|
|
530
|
+
'affiliate-cj': 'CJ Affiliate',
|
|
531
|
+
impact: 'Impact',
|
|
532
|
+
rakuten: 'Rakuten',
|
|
533
|
+
superfiliate: 'Superfiliate',
|
|
534
|
+
// ── Organic – AI & Discovery ─────────────────────────────────────────────
|
|
535
|
+
perplexity: 'Perplexity',
|
|
536
|
+
chatgpt: 'ChatGPT',
|
|
537
|
+
'chatgpt.com': 'ChatGPT',
|
|
538
|
+
openai: 'ChatGPT',
|
|
539
|
+
'copilot.com': 'Microsoft Copilot',
|
|
540
|
+
copilot: 'Microsoft Copilot',
|
|
541
|
+
applenews: 'Apple News',
|
|
542
|
+
whatsapp: 'WhatsApp',
|
|
543
|
+
podcast: 'Podcast',
|
|
544
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
545
|
+
mailchimp: 'Mailchimp',
|
|
546
|
+
omnisend: 'Omnisend',
|
|
547
|
+
iterable: 'Iterable',
|
|
548
|
+
listrak: 'Listrak',
|
|
549
|
+
sailthru: 'Sailthru',
|
|
550
|
+
// ── Retention – Push ─────────────────────────────────────────────────────
|
|
551
|
+
pushowl: 'PushOwl',
|
|
552
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
553
|
+
narvar: 'Narvar',
|
|
554
|
+
shop_app: 'Shop App',
|
|
555
|
+
salesforce: 'Salesforce',
|
|
556
|
+
yotpo: 'Yotpo',
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* Partial utm_source normalization (startsWith prefix check, lowercase).
|
|
560
|
+
*
|
|
561
|
+
* Checked after exact match so short exact keys (e.g. "ig", "yt") win first.
|
|
562
|
+
*/
|
|
563
|
+
const UTM_SOURCE_PARTIAL = [
|
|
564
|
+
// ── Paid – Social ─────────────────────────────────────────────────────────
|
|
565
|
+
{ prefix: 'tiktok', name: 'TikTok' },
|
|
566
|
+
// ── Paid – Ad Networks ───────────────────────────────────────────────────
|
|
567
|
+
{ prefix: 'criteo', name: 'Criteo' },
|
|
568
|
+
// ── Retention – Email ────────────────────────────────────────────────────
|
|
569
|
+
{ prefix: 'klaviyo', name: 'Klaviyo' },
|
|
570
|
+
// ── Retention – SMS ──────────────────────────────────────────────────────
|
|
571
|
+
{ prefix: 'attentive', name: 'Attentive' },
|
|
572
|
+
{ prefix: 'postscript', name: 'Postscript' },
|
|
573
|
+
// ── Retention – Post-purchase / Payment ──────────────────────────────────
|
|
574
|
+
{ prefix: 'afterpay', name: 'Afterpay' },
|
|
575
|
+
{ prefix: 'klarna', name: 'Klarna' },
|
|
576
|
+
];
|
|
577
|
+
const ORGANIC_SEARCH_ENGINES = [
|
|
578
|
+
{ match: 'google', name: 'Google' },
|
|
579
|
+
{ match: 'bing', name: 'Bing' },
|
|
580
|
+
{ match: 'yahoo', name: 'Yahoo' },
|
|
581
|
+
{ match: 'duckduckgo', name: 'DuckDuckGo' },
|
|
582
|
+
{ match: 'baidu', name: 'Baidu' },
|
|
583
|
+
{ match: 'yandex', name: 'Yandex' },
|
|
584
|
+
{ match: 'brave', name: 'Brave' },
|
|
585
|
+
];
|
|
586
|
+
const KNOWN_REFERRAL_PLATFORMS = [
|
|
587
|
+
{ match: 'facebook', name: 'Facebook' },
|
|
588
|
+
{ match: 'instagram', name: 'Facebook' },
|
|
589
|
+
{ match: 'twitter', name: 'Twitter' },
|
|
590
|
+
{ match: 'x.com', name: 'Twitter' },
|
|
591
|
+
{ match: 'tiktok', name: 'TikTok' },
|
|
592
|
+
{ match: 'pinterest', name: 'Pinterest' },
|
|
593
|
+
{ match: 'linkedin', name: 'LinkedIn' },
|
|
594
|
+
{ match: 'youtube', name: 'YouTube' },
|
|
595
|
+
{ match: 'chatgpt', name: 'ChatGPT' },
|
|
596
|
+
{ match: 'claude', name: 'Claude' },
|
|
597
|
+
{ match: 'perplexity', name: 'Perplexity' },
|
|
598
|
+
{ match: 'shop.app', name: 'Shop App' },
|
|
599
|
+
{ match: 'amazon', name: 'Amazon' },
|
|
600
|
+
{ match: 'attentive', name: 'Attentive' },
|
|
601
|
+
];
|
|
602
|
+
const OFFLINE_TOUCH = 'Blotout_Offline';
|
|
603
|
+
new Set([
|
|
604
|
+
...CLICK_IDS.map((c) => c.label),
|
|
605
|
+
...Object.values(UTM_SOURCE_EXACT),
|
|
606
|
+
...UTM_SOURCE_PARTIAL.map((e) => e.name),
|
|
607
|
+
...ORGANIC_SEARCH_ENGINES.map((e) => `Organic Search - ${e.name}`),
|
|
608
|
+
...KNOWN_REFERRAL_PLATFORMS.map((e) => `Referral - ${e.name}`),
|
|
609
|
+
'Referral - Other',
|
|
610
|
+
'Direct Traffic',
|
|
611
|
+
'Other',
|
|
612
|
+
OFFLINE_TOUCH,
|
|
613
|
+
]);
|
|
614
|
+
|
|
460
615
|
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
461
616
|
const getGoogleConsentFromCategories = (categories) => {
|
|
462
617
|
const analyticsConsent = isCategoryConsented(categories, 'analytics');
|
|
@@ -512,7 +667,8 @@ const initGA4 = (ID, advancedConsentMode, consentSkip, consentData, executionCon
|
|
|
512
667
|
script.parentNode.insertBefore(element, script);
|
|
513
668
|
}
|
|
514
669
|
};
|
|
515
|
-
const init = ({ manifest, userId, executionContext, consentData, }) => {
|
|
670
|
+
const init = ({ manifest, userId, executionContext, consentData, pageUrl, }) => {
|
|
671
|
+
var _a;
|
|
516
672
|
if (!window ||
|
|
517
673
|
!manifest.variables ||
|
|
518
674
|
!manifest.variables['measurementId'] ||
|
|
@@ -520,19 +676,29 @@ const init = ({ manifest, userId, executionContext, consentData, }) => {
|
|
|
520
676
|
return;
|
|
521
677
|
}
|
|
522
678
|
window.dataLayer = window.dataLayer || [];
|
|
523
|
-
window.gtag =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
679
|
+
window.gtag =
|
|
680
|
+
window.gtag ||
|
|
681
|
+
function gtag() {
|
|
682
|
+
// eslint-disable-next-line prefer-rest-params
|
|
683
|
+
window.dataLayer.push(arguments);
|
|
684
|
+
};
|
|
527
685
|
if (!window.google_tag_manager ||
|
|
528
686
|
!window.google_tag_manager[manifest.variables['measurementId']]) {
|
|
529
687
|
initGA4(manifest.variables['measurementId'], manifest.variables['advancedConsentMode'], manifest.variables['consentSkip'], consentData, executionContext);
|
|
530
688
|
}
|
|
531
689
|
if (window.gtag) {
|
|
532
|
-
|
|
690
|
+
const config = {
|
|
533
691
|
user_id: userId,
|
|
534
692
|
send_page_view: false,
|
|
535
|
-
}
|
|
693
|
+
};
|
|
694
|
+
// Override gtag's `dl` param so enhanced-measurement events (scroll, click,
|
|
695
|
+
// etc.) auto-fired by gtag use the top-frame URL instead of the iframe
|
|
696
|
+
// sandbox's document.location.
|
|
697
|
+
const resolvedPageUrl = pageUrl || ((_a = window.location) === null || _a === void 0 ? void 0 : _a.href);
|
|
698
|
+
if (resolvedPageUrl) {
|
|
699
|
+
config['page_location'] = resolvedPageUrl;
|
|
700
|
+
}
|
|
701
|
+
window.gtag('config', manifest.variables['measurementId'], config);
|
|
536
702
|
}
|
|
537
703
|
};
|
|
538
704
|
|
|
@@ -804,7 +970,7 @@ const tag = ({ data, eventName, manifestVariables, eventId, pageUrl, }) => {
|
|
|
804
970
|
}
|
|
805
971
|
return {
|
|
806
972
|
loaded: isLoaded,
|
|
807
|
-
sdkVersion: "1.
|
|
973
|
+
sdkVersion: "1.54.0" ,
|
|
808
974
|
};
|
|
809
975
|
};
|
|
810
976
|
|