@hifilabs/pixel 0.7.1 → 0.9.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/browser.js +222 -4
- package/dist/browser.min.js +6 -6
- package/dist/index.esm.d.ts +256 -0
- package/dist/index.js +1606 -359
- package/dist/index.mjs +1610 -359
- package/package.json +8 -2
package/dist/browser.js
CHANGED
|
@@ -8,7 +8,7 @@ var BalancePixel = (() => {
|
|
|
8
8
|
|
|
9
9
|
// src/browser.ts
|
|
10
10
|
(function() {
|
|
11
|
-
const PIXEL_VERSION = "0.
|
|
11
|
+
const PIXEL_VERSION = "0.9.0";
|
|
12
12
|
function parseUserAgent(ua) {
|
|
13
13
|
let device_type = "desktop";
|
|
14
14
|
if (/ipad|tablet|android(?!.*mobile)/i.test(ua))
|
|
@@ -74,6 +74,9 @@ var BalancePixel = (() => {
|
|
|
74
74
|
const consentStyle = currentScript?.dataset.consentStyle || "brutalist";
|
|
75
75
|
const consentPrimaryColor = currentScript?.dataset.primaryColor;
|
|
76
76
|
const consentBannerPosition = currentScript?.dataset.bannerPosition || "bottom";
|
|
77
|
+
const excludePatterns = currentScript?.dataset.excludePages?.split(",").filter(Boolean) || [];
|
|
78
|
+
const trackFileDownloads = currentScript?.dataset.trackFileDownloads === "true";
|
|
79
|
+
const hashRoutingEnabled = currentScript?.dataset.hashRouting === "true";
|
|
77
80
|
function detectTrackingSource() {
|
|
78
81
|
if (explicitSource)
|
|
79
82
|
return explicitSource;
|
|
@@ -99,7 +102,88 @@ var BalancePixel = (() => {
|
|
|
99
102
|
const CONSENT_REFRESH_THRESHOLD = 30 * 24 * 60 * 60 * 1e3;
|
|
100
103
|
const SESSION_DURATION = 60 * 60 * 1e3;
|
|
101
104
|
const STORAGE_PREFIX = "balance_";
|
|
102
|
-
|
|
105
|
+
function globToRegex(pattern) {
|
|
106
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
107
|
+
return new RegExp("^" + escaped + "$");
|
|
108
|
+
}
|
|
109
|
+
function isExcludedPage(url) {
|
|
110
|
+
if (excludePatterns.length === 0)
|
|
111
|
+
return false;
|
|
112
|
+
try {
|
|
113
|
+
const pathname = new URL(url, window.location.origin).pathname;
|
|
114
|
+
return excludePatterns.some((pattern) => {
|
|
115
|
+
const regex = globToRegex(pattern);
|
|
116
|
+
return regex.test(pathname);
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const DOWNLOAD_EXTENSIONS = [
|
|
123
|
+
".pdf",
|
|
124
|
+
".zip",
|
|
125
|
+
".tar",
|
|
126
|
+
".gz",
|
|
127
|
+
".rar",
|
|
128
|
+
".7z",
|
|
129
|
+
".doc",
|
|
130
|
+
".docx",
|
|
131
|
+
".xls",
|
|
132
|
+
".xlsx",
|
|
133
|
+
".ppt",
|
|
134
|
+
".pptx",
|
|
135
|
+
".mp3",
|
|
136
|
+
".wav",
|
|
137
|
+
".flac",
|
|
138
|
+
".aac",
|
|
139
|
+
".ogg",
|
|
140
|
+
".mp4",
|
|
141
|
+
".mov",
|
|
142
|
+
".avi",
|
|
143
|
+
".mkv",
|
|
144
|
+
".webm",
|
|
145
|
+
".exe",
|
|
146
|
+
".dmg",
|
|
147
|
+
".pkg",
|
|
148
|
+
".deb",
|
|
149
|
+
".rpm",
|
|
150
|
+
".csv",
|
|
151
|
+
".json",
|
|
152
|
+
".xml",
|
|
153
|
+
".txt"
|
|
154
|
+
];
|
|
155
|
+
function isFileDownload(url) {
|
|
156
|
+
try {
|
|
157
|
+
const pathname = new URL(url, window.location.origin).pathname.toLowerCase();
|
|
158
|
+
return DOWNLOAD_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function trackFileDownload(url, element) {
|
|
164
|
+
try {
|
|
165
|
+
const pathname = new URL(url, window.location.origin).pathname;
|
|
166
|
+
const fileName = pathname.split("/").pop() || "unknown";
|
|
167
|
+
const fileExtension = fileName.includes(".") ? fileName.split(".").pop() : "unknown";
|
|
168
|
+
log("File download tracked:", { url, fileName, fileExtension });
|
|
169
|
+
const event = buildEvent({
|
|
170
|
+
event_name: "custom",
|
|
171
|
+
metadata: {
|
|
172
|
+
event_type: "file_download",
|
|
173
|
+
file_url: url,
|
|
174
|
+
file_name: fileName,
|
|
175
|
+
file_extension: fileExtension,
|
|
176
|
+
link_text: element?.textContent?.trim().substring(0, 100) || ""
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
enqueueEvent(event);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
logError("Failed to track file download:", e);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const PIXEL_PROXY_URL = "https://balance-pixel-proxy.jassin.workers.dev";
|
|
185
|
+
const FIREBASE_DIRECT_URL = "https://us-central1-artist-os-distro.cloudfunctions.net/ingestEvents";
|
|
186
|
+
const API_ENDPOINT = customEndpoint ? customEndpoint : useEmulator ? `http://localhost:5001/artist-os-distro/us-central1/ingestEvents` : PIXEL_PROXY_URL;
|
|
103
187
|
let sessionId = null;
|
|
104
188
|
let visitorId = null;
|
|
105
189
|
let fanIdHash = null;
|
|
@@ -582,7 +666,23 @@ var BalancePixel = (() => {
|
|
|
582
666
|
}
|
|
583
667
|
log("Events sent successfully");
|
|
584
668
|
} catch (error) {
|
|
585
|
-
logError(" Failed to send events:", error);
|
|
669
|
+
logError(" Failed to send events, trying fallback:", error);
|
|
670
|
+
if (API_ENDPOINT !== FIREBASE_DIRECT_URL && !useEmulator) {
|
|
671
|
+
try {
|
|
672
|
+
const fallbackResponse = await fetch(FIREBASE_DIRECT_URL, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
headers: { "Content-Type": "application/json" },
|
|
675
|
+
body: JSON.stringify({ events }),
|
|
676
|
+
keepalive: true
|
|
677
|
+
});
|
|
678
|
+
if (fallbackResponse.ok) {
|
|
679
|
+
log("Events sent via fallback (no geo)");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
} catch (fallbackError) {
|
|
683
|
+
logError(" Fallback also failed:", fallbackError);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
586
686
|
if (eventQueue.length < 50) {
|
|
587
687
|
eventQueue.push(...events);
|
|
588
688
|
}
|
|
@@ -677,10 +777,15 @@ var BalancePixel = (() => {
|
|
|
677
777
|
}
|
|
678
778
|
}
|
|
679
779
|
function trackPageView(options = {}) {
|
|
780
|
+
const pageUrl = options.url || window.location.href;
|
|
781
|
+
if (isExcludedPage(pageUrl)) {
|
|
782
|
+
log("Page excluded from tracking:", pageUrl);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
680
785
|
const event = buildEvent({
|
|
681
786
|
event_name: "pageview",
|
|
682
787
|
page_title: options.title || document.title,
|
|
683
|
-
source_url:
|
|
788
|
+
source_url: pageUrl
|
|
684
789
|
});
|
|
685
790
|
enqueueEvent(event);
|
|
686
791
|
}
|
|
@@ -798,6 +903,119 @@ var BalancePixel = (() => {
|
|
|
798
903
|
["mousemove", "keydown", "scroll", "touchstart"].forEach((eventType) => {
|
|
799
904
|
document.addEventListener(eventType, resetIdleTimer, { passive: true });
|
|
800
905
|
});
|
|
906
|
+
const currentHost = window.location.hostname;
|
|
907
|
+
function detectPlatform(url) {
|
|
908
|
+
try {
|
|
909
|
+
const hostname = new URL(url).hostname.toLowerCase();
|
|
910
|
+
if (hostname.includes("spotify"))
|
|
911
|
+
return "spotify";
|
|
912
|
+
if (hostname.includes("apple") || hostname.includes("music.apple"))
|
|
913
|
+
return "apple_music";
|
|
914
|
+
if (hostname.includes("youtube") || hostname.includes("youtu.be"))
|
|
915
|
+
return "youtube";
|
|
916
|
+
if (hostname.includes("soundcloud"))
|
|
917
|
+
return "soundcloud";
|
|
918
|
+
if (hostname.includes("tidal"))
|
|
919
|
+
return "tidal";
|
|
920
|
+
if (hostname.includes("deezer"))
|
|
921
|
+
return "deezer";
|
|
922
|
+
if (hostname.includes("amazon") || hostname.includes("music.amazon"))
|
|
923
|
+
return "amazon_music";
|
|
924
|
+
if (hostname.includes("bandcamp"))
|
|
925
|
+
return "bandcamp";
|
|
926
|
+
if (hostname.includes("lnk.to") || hostname.includes("linkfire"))
|
|
927
|
+
return "linkfire";
|
|
928
|
+
if (hostname.includes("linktr.ee"))
|
|
929
|
+
return "linktree";
|
|
930
|
+
if (hostname.includes("shop") || hostname.includes("store") || hostname.includes("merch"))
|
|
931
|
+
return "shop";
|
|
932
|
+
if (hostname.includes("ticketmaster") || hostname.includes("eventbrite") || hostname.includes("dice.fm"))
|
|
933
|
+
return "tickets";
|
|
934
|
+
if (hostname.includes("instagram"))
|
|
935
|
+
return "instagram";
|
|
936
|
+
if (hostname.includes("twitter") || hostname.includes("x.com"))
|
|
937
|
+
return "twitter";
|
|
938
|
+
if (hostname.includes("tiktok"))
|
|
939
|
+
return "tiktok";
|
|
940
|
+
if (hostname.includes("facebook"))
|
|
941
|
+
return "facebook";
|
|
942
|
+
return "external";
|
|
943
|
+
} catch {
|
|
944
|
+
return "external";
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function isExternalLink(url) {
|
|
948
|
+
try {
|
|
949
|
+
const linkHost = new URL(url, window.location.origin).hostname;
|
|
950
|
+
return linkHost !== currentHost;
|
|
951
|
+
} catch {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function trackExternalClick(url, element) {
|
|
956
|
+
const platform = detectPlatform(url);
|
|
957
|
+
const linkText = element?.textContent?.trim().substring(0, 100) || "";
|
|
958
|
+
const linkId = element?.id || element?.getAttribute("data-link-id") || void 0;
|
|
959
|
+
log("External link clicked:", { url, platform, linkText });
|
|
960
|
+
track("page_link_click", {
|
|
961
|
+
link_url: url,
|
|
962
|
+
link_text: linkText,
|
|
963
|
+
link_id: linkId,
|
|
964
|
+
platform,
|
|
965
|
+
link_type: "external"
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
const originalWindowOpen = window.open;
|
|
969
|
+
window.open = function(url, target, features) {
|
|
970
|
+
if (url) {
|
|
971
|
+
const urlString = url.toString();
|
|
972
|
+
if (isExternalLink(urlString)) {
|
|
973
|
+
trackExternalClick(urlString);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return originalWindowOpen.call(window, url, target, features);
|
|
977
|
+
};
|
|
978
|
+
log("window.open interception enabled (always on)");
|
|
979
|
+
const autoTrackLinks = currentScript?.dataset.autoTrackLinks === "true";
|
|
980
|
+
if (autoTrackLinks) {
|
|
981
|
+
document.addEventListener("click", (e) => {
|
|
982
|
+
const target = e.target;
|
|
983
|
+
const anchor = target.closest("a");
|
|
984
|
+
if (anchor && anchor.href && isExternalLink(anchor.href)) {
|
|
985
|
+
if (trackFileDownloads && (isFileDownload(anchor.href) || anchor.hasAttribute("download"))) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
trackExternalClick(anchor.href, anchor);
|
|
989
|
+
}
|
|
990
|
+
}, { capture: true, passive: true });
|
|
991
|
+
log("External link click listener enabled (opt-in)");
|
|
992
|
+
}
|
|
993
|
+
if (trackFileDownloads) {
|
|
994
|
+
document.addEventListener("click", (e) => {
|
|
995
|
+
const target = e.target;
|
|
996
|
+
const anchor = target.closest("a");
|
|
997
|
+
if (anchor?.href && (isFileDownload(anchor.href) || anchor.hasAttribute("download"))) {
|
|
998
|
+
trackFileDownload(anchor.href, anchor);
|
|
999
|
+
}
|
|
1000
|
+
}, { capture: true, passive: true });
|
|
1001
|
+
log("File download tracking enabled");
|
|
1002
|
+
}
|
|
1003
|
+
if (hashRoutingEnabled) {
|
|
1004
|
+
let lastUrl = window.location.href;
|
|
1005
|
+
window.addEventListener("hashchange", () => {
|
|
1006
|
+
const newUrl = window.location.href;
|
|
1007
|
+
if (newUrl !== lastUrl) {
|
|
1008
|
+
lastUrl = newUrl;
|
|
1009
|
+
setTimeout(() => {
|
|
1010
|
+
trackPageView({
|
|
1011
|
+
title: document.title,
|
|
1012
|
+
url: newUrl
|
|
1013
|
+
});
|
|
1014
|
+
}, 0);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
log("Hash routing tracking enabled");
|
|
1018
|
+
}
|
|
801
1019
|
window.addEventListener("beforeunload", () => {
|
|
802
1020
|
stopHeartbeat();
|
|
803
1021
|
flush();
|
package/dist/browser.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var BalancePixel=(()=>{var
|
|
1
|
+
var BalancePixel=(()=>{var it=Object.defineProperty;var rt=(g,p,u)=>p in g?it(g,p,{enumerable:!0,configurable:!0,writable:!0,value:u}):g[p]=u;var F=(g,p,u)=>(rt(g,typeof p!="symbol"?p+"":p,u),u);(function(){let g="0.9.0";function p(e){let t="desktop";/ipad|tablet|android(?!.*mobile)/i.test(e)?t="tablet":/mobile|iphone|android.*mobile|blackberry|iemobile/i.test(e)&&(t="mobile");let n="Unknown";/edg/i.test(e)?n="Edge":/opr|opera/i.test(e)?n="Opera":/firefox/i.test(e)?n="Firefox":/chrome/i.test(e)?n="Chrome":/safari/i.test(e)&&(n="Safari");let r="Unknown";return/iphone|ipad/i.test(e)?r="iOS":/android/i.test(e)?r="Android":/windows/i.test(e)?r="Windows":/mac os/i.test(e)?r="macOS":/linux/i.test(e)?r="Linux":/cros/i.test(e)&&(r="ChromeOS"),{device_type:t,browser:n,os:r}}let u=null;function Ie(){if(!u)try{u=p(navigator.userAgent)}catch{u={device_type:"desktop",browser:"Unknown",os:"Unknown"}}return u}let l=document.currentScript,H=l?.dataset.artistId,M=l?.dataset.projectId,z=M?Se(M):void 0;function Se(e){return!e||e.startsWith("release_")||e.startsWith("merch_")||e.startsWith("link_")||e.startsWith("custom_")?e:`custom_${e}`}let j=l?.dataset.emulator==="true",ee=l?.dataset.debug==="true",$=parseInt(l?.dataset.heartbeatInterval||"30000",10),te=l?.dataset.heartbeat!=="false",ne=l?.dataset.source,ie=l?.dataset.endpoint,B=l?.dataset.consentUi==="true",Ee=l?.dataset.consentStyle||"brutalist",Ce=l?.dataset.primaryColor,Te=l?.dataset.bannerPosition||"bottom",re=l?.dataset.excludePages?.split(",").filter(Boolean)||[],oe=l?.dataset.trackFileDownloads==="true",Le=l?.dataset.hashRouting==="true";function De(){if(ne)return ne;let e=typeof window.dataLayer<"u"&&Array.isArray(window.dataLayer),t=typeof window.gtag=="function";return e&&t?"gtm":"pixel"}let P="pixel";if(!H){w(" Error: data-artist-id attribute is required");return}let ae="session_id",W="session_timestamp",se="attribution",ce="fan_id_hash",V="visitor_id",R="balance_consent",Pe=365*24*60*60*1e3,Re=30*24*60*60*1e3,Oe=60*60*1e3,x="balance_";function Ae(e){let t=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp("^"+t+"$")}function Ne(e){if(re.length===0)return!1;try{let t=new URL(e,window.location.origin).pathname;return re.some(n=>Ae(n).test(t))}catch{return!1}}let Ue=[".pdf",".zip",".tar",".gz",".rar",".7z",".doc",".docx",".xls",".xlsx",".ppt",".pptx",".mp3",".wav",".flac",".aac",".ogg",".mp4",".mov",".avi",".mkv",".webm",".exe",".dmg",".pkg",".deb",".rpm",".csv",".json",".xml",".txt"];function le(e){try{let t=new URL(e,window.location.origin).pathname.toLowerCase();return Ue.some(n=>t.endsWith(n))}catch{return!1}}function Fe(e,t){try{let r=new URL(e,window.location.origin).pathname.split("/").pop()||"unknown",f=r.includes(".")?r.split(".").pop():"unknown";i("File download tracked:",{url:e,fileName:r,fileExtension:f});let L=y({event_name:"custom",metadata:{event_type:"file_download",file_url:e,file_name:r,file_extension:f,link_text:t?.textContent?.trim().substring(0,100)||""}});v(L)}catch(n){w("Failed to track file download:",n)}}let He="https://balance-pixel-proxy.jassin.workers.dev",de="https://us-central1-artist-os-distro.cloudfunctions.net/ingestEvents",O=ie||(j?"http://localhost:5001/artist-os-distro/us-central1/ingestEvents":He),A=null,d=null,m=null,s=null,k={},h=[],q=null,b="session",_=null,Me=0,K=0,I=0,S=!0,ze=2*60*1e3,ue=Date.now(),E=!1,je=!ee,i=(...e)=>{ee&&console.log("[BLN]",...e)},w=(...e)=>{je||console.error("[BLN]",...e)},Y={base:`
|
|
2
2
|
:host {
|
|
3
3
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
4
4
|
position: fixed;
|
|
@@ -83,12 +83,12 @@ var BalancePixel=(()=>{var ze=Object.defineProperty;var Ve=(l,d,c)=>d in l?ze(l,
|
|
|
83
83
|
}
|
|
84
84
|
.btn.accept { background: #fff; color: #000; border-color: #fff; }
|
|
85
85
|
.btn:hover { opacity: 0.8; }
|
|
86
|
-
`};class
|
|
86
|
+
`};class $e{constructor(t){F(this,"container",null);F(this,"shadow",null);F(this,"config");if(this.config=t,this.hasStoredConsent()){i("ConsentManager: Consent already exists, not showing banner");return}this.container=document.createElement("div"),this.container.id="balance-consent-manager",this.shadow=this.container.attachShadow({mode:"closed"}),this.render(),document.body.appendChild(this.container),i("ConsentManager: Banner rendered")}hasStoredConsent(){try{return window._balanceConsentNeedsRefresh?!1:localStorage.getItem(R)!==null}catch{return!1}}render(){if(!this.shadow)return;let t=this.config.style||"brutalist",n=Y.base,r=Y[t]||Y.brutalist,f=this.config.position==="top"?"top: 0;":"bottom: 0;",L=this.config.primaryColor?`.btn.accept { background: ${this.config.primaryColor} !important; border-color: ${this.config.primaryColor} !important; color: #fff !important; }`:"";this.shadow.innerHTML=`
|
|
87
87
|
<style>
|
|
88
88
|
${n}
|
|
89
|
-
:host { ${
|
|
90
|
-
${
|
|
91
|
-
${
|
|
89
|
+
:host { ${f} }
|
|
90
|
+
${r}
|
|
91
|
+
${L}
|
|
92
92
|
</style>
|
|
93
93
|
<div class="banner" role="dialog" aria-label="Cookie consent" aria-modal="false">
|
|
94
94
|
<p class="text">
|
|
@@ -99,4 +99,4 @@ var BalancePixel=(()=>{var ze=Object.defineProperty;var Ve=(l,d,c)=>d in l?ze(l,
|
|
|
99
99
|
<button id="accept" class="btn accept">Accept</button>
|
|
100
100
|
</div>
|
|
101
101
|
</div>
|
|
102
|
-
`,this.shadow.getElementById("accept")?.addEventListener("click",()=>this.handleConsent(!0)),this.shadow.getElementById("decline")?.addEventListener("click",()=>this.handleConsent(!1))}handleConsent(t){window.balance?.setConsent&&window.balance.setConsent({analytics:t,marketing:t,personalization:t,timestamp:new Date().toISOString()}),t&&!window._balanceInitialPageviewFired&&(window._balanceInitialPageviewFired=!0,window.balance?.page&&window.balance.page(),W(),r("Initial pageview fired after consent granted")),this.remove()}remove(){this.container&&(this.container.remove(),this.container=null,this.shadow=null,r("ConsentManager: Banner removed"))}}function ne(){try{return g==="local"?localStorage:sessionStorage}catch{return null}}function T(e){let t=ne();if(!t)return null;try{let n=p+e,o=t.getItem(n);if(!o&&g==="session")try{o=localStorage.getItem(n)}catch{}return o}catch{return null}}function S(e,t){let n=ne();if(n)try{n.setItem(p+e,t)}catch{}}function re(){if(g!=="local"){r("Upgrading storage tier: session -> local");try{let e=[];for(let t=0;t<sessionStorage.length;t++){let n=sessionStorage.key(t);n?.startsWith(p)&&e.push(n)}for(let t of e){let n=sessionStorage.getItem(t);n&&localStorage.setItem(t,n)}for(let t of e)sessionStorage.removeItem(t);g="local",r(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){x(" Storage migration failed:",e)}}}function z(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function De(){try{let e=T(X),t=T(M);if(e&&t&&Date.now()-parseInt(t,10)<_e)return S(M,Date.now().toString()),e;let n=z();return S(X,n),S(M,Date.now().toString()),n}catch{return z()}}function Pe(){let e=new URLSearchParams(window.location.search),t={};return["source","medium","campaign","content","term"].forEach(n=>{let o=e.get(`utm_${n}`);o&&(t[`utm_${n}`]=o)}),t}function Ae(){try{let e=T(Z);if(e){m=JSON.parse(e),r("Loaded attribution:",m);return}let t=Pe();Object.keys(t).length>0&&(m=t,S(Z,JSON.stringify(t)),r("Captured attribution:",m))}catch{}}function Oe(){try{u=T(ee)}catch{}}function oe(){if(!i?.analytics)return null;try{let e=localStorage.getItem(p+F);if(e)return e;let t=z();return localStorage.setItem(p+F,t),r("Created persistent visitor ID:",t.substring(0,8)+"..."),t}catch{return null}}function Ne(){if(!i?.analytics){s=null;return}try{s=localStorage.getItem(p+F),s&&r("Loaded visitor ID:",s.substring(0,8)+"...")}catch{}}function Le(){try{let e=localStorage.getItem(C);if(e){let t=JSON.parse(e);if(t.expiresAt&&Date.now()>t.expiresAt){r("Consent expired - clearing stored consent"),localStorage.removeItem(C),i=null;return}if(t.expiresAt){let n=t.expiresAt-Date.now();n>0&&n<Se&&(r("Consent nearing expiration - will prompt for refresh"),window._balanceConsentNeedsRefresh=!0)}i=t.preferences||null,r("Loaded consent:",i)}}catch{}}function ie(e){let t=i;i=e;try{let o={preferences:e,method:"explicit",version:1,expiresAt:Date.now()+xe};localStorage.setItem(C,JSON.stringify(o)),r("Consent saved with TTL:",e),window._balanceConsentNeedsRefresh=!1}catch(o){x(" Could not save consent:",o)}e.analytics===!0&&(re(),s||(s=oe()));let n=y({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});v(n);try{window.dispatchEvent(new CustomEvent("balance:consent:updated",{detail:e})),r("DOM event balance:consent:updated dispatched")}catch{}}function Re(){return i}function Ue(e){return i?.[e]===!0}async function Me(e){let t=e.toLowerCase().trim(),o=new TextEncoder().encode(t),_=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(_)).map($e=>$e.toString(16).padStart(2,"0")).join("")}function y(e){let t=be(),n={artist_id:O,fan_session_id:E,visitor_id:s||void 0,fan_id_hash:u||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,device_type:t.device_type,browser:t.browser,os:t.os,tracking_source:k,...e,...m};return L&&!e.projectId&&(n.projectId=L),n}function v(e){f.push(e),r("Event queued:",e.event_name,"(queue:",f.length,")"),f.length>=10&&D()}async function D(){if(f.length===0)return;let e=[...f];f=[],r("Flushing",e.length,"events to",H);try{let t=await fetch(H,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!t.ok)throw new Error(`HTTP ${t.status}`);r("Events sent successfully")}catch(t){x(" Failed to send events:",t),f.length<50&&f.push(...e)}}function Fe(){j&&clearInterval(j),j=window.setInterval(()=>{f.length>0&&D()},5e3)}function V(){h||(ke=Date.now()),h=Date.now(),w=!0,r("Active time tracking started/resumed")}function ae(){h&&w&&(B+=Date.now()-h),w=!1,r("Active time tracking paused, accumulated:",B,"ms")}function He(){let e=B;return w&&h&&(e+=Date.now()-h),e}function je(){te=Date.now(),I&&(I=!1,V(),r("User returned from idle"))}function se(){if(document.visibilityState==="hidden"){r("Skipping heartbeat - tab hidden");return}if(Date.now()-te>Ce){I||(I=!0,ae(),r("User idle - pausing heartbeat"));return}let e=He(),t=Math.round(e/1e3),n=y({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:R,is_active:w&&!I}});v(n),r("Heartbeat sent:",t,"seconds active")}function W(){if(!J){r('Heartbeat disabled via data-heartbeat="false"');return}b&&clearInterval(b),V(),b=window.setInterval(()=>{se()},R),r("Heartbeat started with interval:",R,"ms")}function Be(){b&&(clearInterval(b),b=null),J&&(se(),r("Heartbeat stopped, final time sent"))}function P(e={}){let t=y({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});v(t)}function ce(e,t={}){let n=y({event_name:"custom",metadata:{event_type:e,...t}});v(n)}async function le(e,t={}){try{if(i&&i.analytics===!1){r("Identify skipped - user declined analytics consent");return}u=await Me(e),i?.analytics===!0&&re(),S(ee,u);let n=e.split("@"),o=n[0].charAt(0)+"***@"+(n[1]||"");r("Fan identified:",{name:t.name||"(no name)",email:o,hash:u.substring(0,16)+"...",traits:t,storageTier:g});let _=y({event_name:"identify",fan_id_hash:u,metadata:{email_sha256:u,email_display:o,traits:t,consent_preferences:i||void 0,storage_tier:g}});v(_)}catch(n){x(" Failed to identify:",n)}}function de(e,t="USD",n={}){let o=y({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});v(o)}function ue(){k=Ie(),r("Tracking source detected:",k),Le(),i?.analytics===!0?(g="local",r("Storage tier: local (analytics consent granted)")):(g="session",r("Storage tier: session (privacy by default)")),E=De(),Oe(),Ne(),i||r(U?"Consent UI enabled, waiting for explicit user consent (Tier 0 mode)":"No consent - operating in privacy-first mode (session storage only, limited tracking)"),i?.analytics&&!s&&(s=oe()),Ae(),Fe(),r("Initialized",{artistId:O,projectId:L||"(none - will track to all projects)",rawProjectId:N||"(none)",sessionId:E,visitorId:s?s.substring(0,8)+"...":null,fanIdHash:u,consent:i,storageTier:g,trackingSource:k,useEmulator:K,endpoint:H}),i?.analytics===!0?(window._balanceInitialPageviewFired=!0,P(),W(),r("Full tracking enabled (user consented)")):U?r("Tracking dormant - waiting for explicit consent via ConsentManager"):(window._balanceInitialPageviewFired=!0,P(),W(),r("Privacy-first tracking enabled (session-scoped, no persistent IDs)")),["mousemove","keydown","scroll","touchstart"].forEach(e=>{document.addEventListener(e,je,{passive:!0})}),window.addEventListener("beforeunload",()=>{Be(),D()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?(ae(),D()):V()})}if(window.balance?._version&&window.balance._version!==l){console.warn(`[BLN] Version conflict: ${window.balance._version} already loaded, skipping ${l}`);return}let q=window.balance?.q||[];function fe(){if(q.length>0){r("Processing",q.length,"queued commands");for(let e of q){let[t,...n]=e;switch(t){case"track":ce(n[0],n[1]);break;case"identify":le(n[0],n[1]);break;case"page":P(n[0]);break;case"purchase":de(n[0],n[1],n[2]);break;case"setConsent":ie(n[0]);break;default:r("Unknown queued command:",t)}}}}window.balance={track:ce,identify:le,page:P,purchase:de,getSessionId:()=>E,getVisitorId:()=>s,getFanIdHash:()=>u,getAttribution:()=>m,setConsent:ie,getConsent:Re,hasConsent:Ue,_version:l};function ge(){U&&!i&&new Te({style:ye,primaryColor:ve,position:we})}let pe=window.requestIdleCallback||(e=>setTimeout(e,1));document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{ue(),fe(),pe(()=>ge())}):(ue(),fe(),pe(()=>ge())),r("Pixel script loaded")})();})();
|
|
102
|
+
`,this.shadow.getElementById("accept")?.addEventListener("click",()=>this.handleConsent(!0)),this.shadow.getElementById("decline")?.addEventListener("click",()=>this.handleConsent(!1))}handleConsent(t){window.balance?.setConsent&&window.balance.setConsent({analytics:t,marketing:t,personalization:t,timestamp:new Date().toISOString()}),t&&!window._balanceInitialPageviewFired&&(window._balanceInitialPageviewFired=!0,window.balance?.page&&window.balance.page(),G(),i("Initial pageview fired after consent granted")),this.remove()}remove(){this.container&&(this.container.remove(),this.container=null,this.shadow=null,i("ConsentManager: Banner removed"))}}function fe(){try{return b==="local"?localStorage:sessionStorage}catch{return null}}function N(e){let t=fe();if(!t)return null;try{let n=x+e,r=t.getItem(n);if(!r&&b==="session")try{r=localStorage.getItem(n)}catch{}return r}catch{return null}}function C(e,t){let n=fe();if(n)try{n.setItem(x+e,t)}catch{}}function ge(){if(b!=="local"){i("Upgrading storage tier: session -> local");try{let e=[];for(let t=0;t<sessionStorage.length;t++){let n=sessionStorage.key(t);n?.startsWith(x)&&e.push(n)}for(let t of e){let n=sessionStorage.getItem(t);n&&localStorage.setItem(t,n)}for(let t of e)sessionStorage.removeItem(t);b="local",i(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){w(" Storage migration failed:",e)}}}function J(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function Be(){try{let e=N(ae),t=N(W);if(e&&t&&Date.now()-parseInt(t,10)<Oe)return C(W,Date.now().toString()),e;let n=J();return C(ae,n),C(W,Date.now().toString()),n}catch{return J()}}function We(){let e=new URLSearchParams(window.location.search),t={};return["source","medium","campaign","content","term"].forEach(n=>{let r=e.get(`utm_${n}`);r&&(t[`utm_${n}`]=r)}),t}function Ve(){try{let e=N(se);if(e){k=JSON.parse(e),i("Loaded attribution:",k);return}let t=We();Object.keys(t).length>0&&(k=t,C(se,JSON.stringify(t)),i("Captured attribution:",k))}catch{}}function qe(){try{m=N(ce)}catch{}}function pe(){if(!s?.analytics)return null;try{let e=localStorage.getItem(x+V);if(e)return e;let t=J();return localStorage.setItem(x+V,t),i("Created persistent visitor ID:",t.substring(0,8)+"..."),t}catch{return null}}function Ke(){if(!s?.analytics){d=null;return}try{d=localStorage.getItem(x+V),d&&i("Loaded visitor ID:",d.substring(0,8)+"...")}catch{}}function Ye(){try{let e=localStorage.getItem(R);if(e){let t=JSON.parse(e);if(t.expiresAt&&Date.now()>t.expiresAt){i("Consent expired - clearing stored consent"),localStorage.removeItem(R),s=null;return}if(t.expiresAt){let n=t.expiresAt-Date.now();n>0&&n<Re&&(i("Consent nearing expiration - will prompt for refresh"),window._balanceConsentNeedsRefresh=!0)}s=t.preferences||null,i("Loaded consent:",s)}}catch{}}function me(e){let t=s;s=e;try{let r={preferences:e,method:"explicit",version:1,expiresAt:Date.now()+Pe};localStorage.setItem(R,JSON.stringify(r)),i("Consent saved with TTL:",e),window._balanceConsentNeedsRefresh=!1}catch(r){w(" Could not save consent:",r)}e.analytics===!0&&(ge(),d||(d=pe()));let n=y({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});v(n);try{window.dispatchEvent(new CustomEvent("balance:consent:updated",{detail:e})),i("DOM event balance:consent:updated dispatched")}catch{}}function Je(){return s}function Xe(e){return s?.[e]===!0}async function Ge(e){let t=e.toLowerCase().trim(),r=new TextEncoder().encode(t),f=await crypto.subtle.digest("SHA-256",r);return Array.from(new Uint8Array(f)).map(a=>a.toString(16).padStart(2,"0")).join("")}function y(e){let t=Ie(),n={artist_id:H,fan_session_id:A,visitor_id:d||void 0,fan_id_hash:m||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,device_type:t.device_type,browser:t.browser,os:t.os,tracking_source:P,...e,...k};return z&&!e.projectId&&(n.projectId=z),n}function v(e){h.push(e),i("Event queued:",e.event_name,"(queue:",h.length,")"),h.length>=10&&U()}async function U(){if(h.length===0)return;let e=[...h];h=[],i("Flushing",e.length,"events to",O);try{let t=await fetch(O,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!t.ok)throw new Error(`HTTP ${t.status}`);i("Events sent successfully")}catch(t){if(w(" Failed to send events, trying fallback:",t),O!==de&&!j)try{if((await fetch(de,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0})).ok){i("Events sent via fallback (no geo)");return}}catch(n){w(" Fallback also failed:",n)}h.length<50&&h.push(...e)}}function Qe(){q&&clearInterval(q),q=window.setInterval(()=>{h.length>0&&U()},5e3)}function X(){I||(Me=Date.now()),I=Date.now(),S=!0,i("Active time tracking started/resumed")}function he(){I&&S&&(K+=Date.now()-I),S=!1,i("Active time tracking paused, accumulated:",K,"ms")}function Ze(){let e=K;return S&&I&&(e+=Date.now()-I),e}function et(){ue=Date.now(),E&&(E=!1,X(),i("User returned from idle"))}function be(){if(document.visibilityState==="hidden"){i("Skipping heartbeat - tab hidden");return}if(Date.now()-ue>ze){E||(E=!0,he(),i("User idle - pausing heartbeat"));return}let e=Ze(),t=Math.round(e/1e3),n=y({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:$,is_active:S&&!E}});v(n),i("Heartbeat sent:",t,"seconds active")}function G(){if(!te){i('Heartbeat disabled via data-heartbeat="false"');return}_&&clearInterval(_),X(),_=window.setInterval(()=>{be()},$),i("Heartbeat started with interval:",$,"ms")}function tt(){_&&(clearInterval(_),_=null),te&&(be(),i("Heartbeat stopped, final time sent"))}function T(e={}){let t=e.url||window.location.href;if(Ne(t)){i("Page excluded from tracking:",t);return}let n=y({event_name:"pageview",page_title:e.title||document.title,source_url:t});v(n)}function Q(e,t={}){let n=y({event_name:"custom",metadata:{event_type:e,...t}});v(n)}async function we(e,t={}){try{if(s&&s.analytics===!1){i("Identify skipped - user declined analytics consent");return}m=await Ge(e),s?.analytics===!0&&ge(),C(ce,m);let n=e.split("@"),r=n[0].charAt(0)+"***@"+(n[1]||"");i("Fan identified:",{name:t.name||"(no name)",email:r,hash:m.substring(0,16)+"...",traits:t,storageTier:b});let f=y({event_name:"identify",fan_id_hash:m,metadata:{email_sha256:m,email_display:r,traits:t,consent_preferences:s||void 0,storage_tier:b}});v(f)}catch(n){w(" Failed to identify:",n)}}function ye(e,t="USD",n={}){let r=y({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});v(r)}function ve(){P=De(),i("Tracking source detected:",P),Ye(),s?.analytics===!0?(b="local",i("Storage tier: local (analytics consent granted)")):(b="session",i("Storage tier: session (privacy by default)")),A=Be(),qe(),Ke(),s||i(B?"Consent UI enabled, waiting for explicit user consent (Tier 0 mode)":"No consent - operating in privacy-first mode (session storage only, limited tracking)"),s?.analytics&&!d&&(d=pe()),Ve(),Qe(),i("Initialized",{artistId:H,projectId:z||"(none - will track to all projects)",rawProjectId:M||"(none)",sessionId:A,visitorId:d?d.substring(0,8)+"...":null,fanIdHash:m,consent:s,storageTier:b,trackingSource:P,useEmulator:j,endpoint:O}),s?.analytics===!0?(window._balanceInitialPageviewFired=!0,T(),G(),i("Full tracking enabled (user consented)")):B?i("Tracking dormant - waiting for explicit consent via ConsentManager"):(window._balanceInitialPageviewFired=!0,T(),G(),i("Privacy-first tracking enabled (session-scoped, no persistent IDs)")),["mousemove","keydown","scroll","touchstart"].forEach(a=>{document.addEventListener(a,et,{passive:!0})});let e=window.location.hostname;function t(a){try{let o=new URL(a).hostname.toLowerCase();return o.includes("spotify")?"spotify":o.includes("apple")||o.includes("music.apple")?"apple_music":o.includes("youtube")||o.includes("youtu.be")?"youtube":o.includes("soundcloud")?"soundcloud":o.includes("tidal")?"tidal":o.includes("deezer")?"deezer":o.includes("amazon")||o.includes("music.amazon")?"amazon_music":o.includes("bandcamp")?"bandcamp":o.includes("lnk.to")||o.includes("linkfire")?"linkfire":o.includes("linktr.ee")?"linktree":o.includes("shop")||o.includes("store")||o.includes("merch")?"shop":o.includes("ticketmaster")||o.includes("eventbrite")||o.includes("dice.fm")?"tickets":o.includes("instagram")?"instagram":o.includes("twitter")||o.includes("x.com")?"twitter":o.includes("tiktok")?"tiktok":o.includes("facebook")?"facebook":"external"}catch{return"external"}}function n(a){try{return new URL(a,window.location.origin).hostname!==e}catch{return!1}}function r(a,o){let c=t(a),D=o?.textContent?.trim().substring(0,100)||"",nt=o?.id||o?.getAttribute("data-link-id")||void 0;i("External link clicked:",{url:a,platform:c,linkText:D}),Q("page_link_click",{link_url:a,link_text:D,link_id:nt,platform:c,link_type:"external"})}let f=window.open;if(window.open=function(a,o,c){if(a){let D=a.toString();n(D)&&r(D)}return f.call(window,a,o,c)},i("window.open interception enabled (always on)"),l?.dataset.autoTrackLinks==="true"&&(document.addEventListener("click",a=>{let c=a.target.closest("a");if(c&&c.href&&n(c.href)){if(oe&&(le(c.href)||c.hasAttribute("download")))return;r(c.href,c)}},{capture:!0,passive:!0}),i("External link click listener enabled (opt-in)")),oe&&(document.addEventListener("click",a=>{let c=a.target.closest("a");c?.href&&(le(c.href)||c.hasAttribute("download"))&&Fe(c.href,c)},{capture:!0,passive:!0}),i("File download tracking enabled")),Le){let a=window.location.href;window.addEventListener("hashchange",()=>{let o=window.location.href;o!==a&&(a=o,setTimeout(()=>{T({title:document.title,url:o})},0))}),i("Hash routing tracking enabled")}window.addEventListener("beforeunload",()=>{tt(),U()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?(he(),U()):X()})}if(window.balance?._version&&window.balance._version!==g){console.warn(`[BLN] Version conflict: ${window.balance._version} already loaded, skipping ${g}`);return}let Z=window.balance?.q||[];function xe(){if(Z.length>0){i("Processing",Z.length,"queued commands");for(let e of Z){let[t,...n]=e;switch(t){case"track":Q(n[0],n[1]);break;case"identify":we(n[0],n[1]);break;case"page":T(n[0]);break;case"purchase":ye(n[0],n[1],n[2]);break;case"setConsent":me(n[0]);break;default:i("Unknown queued command:",t)}}}}window.balance={track:Q,identify:we,page:T,purchase:ye,getSessionId:()=>A,getVisitorId:()=>d,getFanIdHash:()=>m,getAttribution:()=>k,setConsent:me,getConsent:Je,hasConsent:Xe,_version:g};function ke(){B&&!s&&new $e({style:Ee,primaryColor:Ce,position:Te})}let _e=window.requestIdleCallback||(e=>setTimeout(e,1));document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>{ve(),xe(),_e(()=>ke())}):(ve(),xe(),_e(()=>ke())),i("Pixel script loaded")})();})();
|
package/dist/index.esm.d.ts
CHANGED
|
@@ -617,3 +617,259 @@ export interface ConsentBridgeProps {
|
|
|
617
617
|
* props happens in your app code, not in the SDK.
|
|
618
618
|
*/
|
|
619
619
|
export declare function ConsentBridge(props: ConsentBridgeProps): null;
|
|
620
|
+
|
|
621
|
+
// ============================================
|
|
622
|
+
// ArtistOS - Unified Component (Recommended)
|
|
623
|
+
// ============================================
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Consent mode for ArtistOS
|
|
627
|
+
*/
|
|
628
|
+
export type ConsentMode = 'built-in' | 'c15t' | 'external' | 'disabled';
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Consent configuration for ArtistOS
|
|
632
|
+
*/
|
|
633
|
+
export interface ConsentConfig {
|
|
634
|
+
/**
|
|
635
|
+
* How consent is managed
|
|
636
|
+
* - 'built-in': Use ArtistOS built-in consent manager (default)
|
|
637
|
+
* - 'c15t': Use c15t consent manager (requires @c15t/react)
|
|
638
|
+
* - 'external': Consent managed by external system (use ConsentBridge)
|
|
639
|
+
* - 'disabled': No consent management
|
|
640
|
+
*/
|
|
641
|
+
mode?: ConsentMode;
|
|
642
|
+
/** Show consent banner */
|
|
643
|
+
showBanner?: boolean;
|
|
644
|
+
/** Delay before showing banner in ms */
|
|
645
|
+
bannerDelay?: number;
|
|
646
|
+
/** Show "Manage Preferences" button on banner */
|
|
647
|
+
showManageButton?: boolean;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Resolved config after merging props with env vars
|
|
652
|
+
*/
|
|
653
|
+
export interface ResolvedConfig {
|
|
654
|
+
artistId: string | undefined;
|
|
655
|
+
gtmId: string | undefined;
|
|
656
|
+
endpoint: string | undefined;
|
|
657
|
+
consent: Required<ConsentConfig>;
|
|
658
|
+
debug: boolean;
|
|
659
|
+
disabled: boolean;
|
|
660
|
+
scriptUrl: string;
|
|
661
|
+
useEmulator: boolean;
|
|
662
|
+
projectId: string | undefined;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Consent state from built-in consent manager
|
|
667
|
+
*/
|
|
668
|
+
export interface ConsentState {
|
|
669
|
+
/** Current consent preferences */
|
|
670
|
+
consent: ConsentPreferences | null;
|
|
671
|
+
/** Whether consent is still loading */
|
|
672
|
+
loading: boolean;
|
|
673
|
+
/** Whether user has made a consent choice */
|
|
674
|
+
hasConsented: boolean;
|
|
675
|
+
/** Whether to show the consent banner */
|
|
676
|
+
showBanner: boolean;
|
|
677
|
+
/** Set banner visibility */
|
|
678
|
+
setShowBanner: (show: boolean) => void;
|
|
679
|
+
/** Whether to show the consent dialog */
|
|
680
|
+
showDialog: boolean;
|
|
681
|
+
/** Set dialog visibility */
|
|
682
|
+
setShowDialog: (show: boolean) => void;
|
|
683
|
+
/** Accept all consent */
|
|
684
|
+
acceptAll: () => void;
|
|
685
|
+
/** Decline all consent */
|
|
686
|
+
declineAll: () => void;
|
|
687
|
+
/** Update specific consent preferences */
|
|
688
|
+
updateConsent: (prefs: ConsentPreferences) => void;
|
|
689
|
+
/** Check if specific consent type is granted */
|
|
690
|
+
hasConsent: (type: 'analytics' | 'marketing' | 'personalization') => boolean;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* ArtistOS Context Value
|
|
695
|
+
*/
|
|
696
|
+
export interface ArtistOSContextValue {
|
|
697
|
+
config: ResolvedConfig;
|
|
698
|
+
consentState: ConsentState;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* ArtistOS Props - Zero-config unified component
|
|
703
|
+
*/
|
|
704
|
+
export interface ArtistOSProps {
|
|
705
|
+
/** Artist ID (reads from NEXT_PUBLIC_ARTIST_ID if not provided) */
|
|
706
|
+
artistId?: string;
|
|
707
|
+
/** GTM container ID (reads from NEXT_PUBLIC_GTM_ID if not provided) */
|
|
708
|
+
gtmId?: string;
|
|
709
|
+
/** Pixel endpoint (reads from NEXT_PUBLIC_PIXEL_ENDPOINT if not provided) */
|
|
710
|
+
endpoint?: string;
|
|
711
|
+
/** Consent configuration */
|
|
712
|
+
consent?: ConsentConfig;
|
|
713
|
+
/** Enable debug logging */
|
|
714
|
+
debug?: boolean;
|
|
715
|
+
/** Disable tracking entirely */
|
|
716
|
+
disabled?: boolean;
|
|
717
|
+
/** Custom pixel script URL */
|
|
718
|
+
scriptUrl?: string;
|
|
719
|
+
/** Use Firebase emulator */
|
|
720
|
+
useEmulator?: boolean;
|
|
721
|
+
/** Project ID for multi-project tracking */
|
|
722
|
+
projectId?: string;
|
|
723
|
+
/** Optional children */
|
|
724
|
+
children?: React.ReactNode;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* ArtistOS - Unified component for pixel loading, consent management, and GTM integration
|
|
729
|
+
*
|
|
730
|
+
* Zero-config usage:
|
|
731
|
+
* ```tsx
|
|
732
|
+
* <ArtistOS />
|
|
733
|
+
* ```
|
|
734
|
+
*
|
|
735
|
+
* With explicit artistId:
|
|
736
|
+
* ```tsx
|
|
737
|
+
* <ArtistOS artistId="artist_123" />
|
|
738
|
+
* ```
|
|
739
|
+
*/
|
|
740
|
+
export declare function ArtistOS(props: ArtistOSProps): JSX.Element;
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Hook to access ArtistOS context
|
|
744
|
+
* @throws Error if used outside ArtistOS
|
|
745
|
+
*/
|
|
746
|
+
export declare function useArtistOS(): ArtistOSContextValue;
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Hook to optionally access ArtistOS context
|
|
750
|
+
* Returns null if not inside ArtistOS
|
|
751
|
+
*/
|
|
752
|
+
export declare function useArtistOSOptional(): ArtistOSContextValue | null;
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Built-in consent hook for ArtistOS
|
|
756
|
+
*/
|
|
757
|
+
export declare function useBuiltInConsent(config: Required<ConsentConfig>): ConsentState;
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @deprecated Use ArtistOS with consent={{ mode: 'c15t' }} instead
|
|
761
|
+
*/
|
|
762
|
+
export declare function useC15tConsent(config: Required<ConsentConfig>): ConsentState;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* ArtistOS with Built-in Consent (used internally by ArtistOS)
|
|
766
|
+
*/
|
|
767
|
+
export declare function ArtistOSWithBuiltIn(props: {
|
|
768
|
+
config: ResolvedConfig;
|
|
769
|
+
children?: React.ReactNode;
|
|
770
|
+
}): JSX.Element;
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* ArtistOS Core - No consent UI (used for external/disabled modes)
|
|
774
|
+
*/
|
|
775
|
+
export declare function ArtistOSCore(props: {
|
|
776
|
+
config: ResolvedConfig;
|
|
777
|
+
children?: React.ReactNode;
|
|
778
|
+
}): JSX.Element;
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* ArtistOS with c15t Integration
|
|
782
|
+
* Requires @c15t/react to be installed
|
|
783
|
+
*/
|
|
784
|
+
export declare function ArtistOSWithC15t(props: {
|
|
785
|
+
config: ResolvedConfig;
|
|
786
|
+
children?: React.ReactNode;
|
|
787
|
+
}): JSX.Element;
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* ArtistOS Context - for building custom integrations
|
|
791
|
+
*/
|
|
792
|
+
export declare const ArtistOSContext: React.Context<ArtistOSContextValue | null>;
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Pixel Script Loader component
|
|
796
|
+
*/
|
|
797
|
+
export declare function PixelScriptLoader(props: { config: ResolvedConfig }): JSX.Element | null;
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* GTM Script Loader component
|
|
801
|
+
*/
|
|
802
|
+
export declare function GTMScriptLoader(props: { config: ResolvedConfig }): JSX.Element | null;
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Consent UI Manager component
|
|
806
|
+
*/
|
|
807
|
+
export declare function ConsentUIManager(props: {
|
|
808
|
+
config: ResolvedConfig;
|
|
809
|
+
consentState: ConsentState;
|
|
810
|
+
}): JSX.Element | null;
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Hook to sync GTM consent with Balance consent
|
|
814
|
+
*/
|
|
815
|
+
export declare function useGTMConsentSync(
|
|
816
|
+
config: ResolvedConfig,
|
|
817
|
+
consentState: ConsentState
|
|
818
|
+
): void;
|
|
819
|
+
|
|
820
|
+
// ============================================
|
|
821
|
+
// c15t Integration Types
|
|
822
|
+
// ============================================
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* c15t Consent Manager interface (from @c15t/react)
|
|
826
|
+
*/
|
|
827
|
+
export interface C15tConsentManager {
|
|
828
|
+
consents: Record<string, boolean> | null;
|
|
829
|
+
hasConsented: () => boolean;
|
|
830
|
+
hasConsentFor: (category: string) => boolean;
|
|
831
|
+
setConsent: (category: string, value: boolean) => void;
|
|
832
|
+
saveConsents: (source?: string) => void;
|
|
833
|
+
acceptAll: () => void;
|
|
834
|
+
rejectAll: () => void;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Hook that bridges c15t consent to ArtistOS ConsentState
|
|
839
|
+
*/
|
|
840
|
+
export declare function useC15tConsentState(
|
|
841
|
+
c15tManager: C15tConsentManager,
|
|
842
|
+
config: Required<ConsentConfig>
|
|
843
|
+
): ConsentState;
|
|
844
|
+
|
|
845
|
+
// ============================================
|
|
846
|
+
// Consent Mapping Helpers
|
|
847
|
+
// ============================================
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Mapping from c15t consent categories to Balance consent types
|
|
851
|
+
*/
|
|
852
|
+
export declare const C15T_TO_BALANCE_MAP: {
|
|
853
|
+
measurement: 'analytics';
|
|
854
|
+
marketing: 'marketing';
|
|
855
|
+
experience: 'personalization';
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Mapping from Balance consent types to c15t categories
|
|
860
|
+
*/
|
|
861
|
+
export declare const BALANCE_TO_C15T_MAP: {
|
|
862
|
+
analytics: 'measurement';
|
|
863
|
+
marketing: 'marketing';
|
|
864
|
+
personalization: 'experience';
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Convert c15t consent state to Balance format
|
|
869
|
+
*/
|
|
870
|
+
export declare function toBalanceConsent(c15tConsents: Record<string, boolean>): ConsentPreferences;
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Convert Balance consent to c15t format
|
|
874
|
+
*/
|
|
875
|
+
export declare function fromBalanceConsent(balanceConsent: ConsentPreferences): Record<string, boolean>;
|