@hifilabs/pixel 0.6.2 → 0.7.1
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 +73 -25
- package/dist/browser.min.js +5 -5
- package/dist/index.esm.d.ts +5 -0
- package/dist/index.js +102 -0
- package/dist/index.mjs +102 -0
- package/package.json +1 -1
package/dist/browser.js
CHANGED
|
@@ -8,6 +8,7 @@ var BalancePixel = (() => {
|
|
|
8
8
|
|
|
9
9
|
// src/browser.ts
|
|
10
10
|
(function() {
|
|
11
|
+
const PIXEL_VERSION = "0.7.0";
|
|
11
12
|
function parseUserAgent(ua) {
|
|
12
13
|
let device_type = "desktop";
|
|
13
14
|
if (/ipad|tablet|android(?!.*mobile)/i.test(ua))
|
|
@@ -85,7 +86,7 @@ var BalancePixel = (() => {
|
|
|
85
86
|
}
|
|
86
87
|
let trackingSource = "pixel";
|
|
87
88
|
if (!artistId) {
|
|
88
|
-
|
|
89
|
+
logError(" Error: data-artist-id attribute is required");
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
91
92
|
const SESSION_KEY = "session_id";
|
|
@@ -94,9 +95,11 @@ var BalancePixel = (() => {
|
|
|
94
95
|
const FAN_ID_KEY = "fan_id_hash";
|
|
95
96
|
const VISITOR_ID_KEY = "visitor_id";
|
|
96
97
|
const CONSENT_STORAGE_KEY = "balance_consent";
|
|
98
|
+
const CONSENT_TTL = 365 * 24 * 60 * 60 * 1e3;
|
|
99
|
+
const CONSENT_REFRESH_THRESHOLD = 30 * 24 * 60 * 60 * 1e3;
|
|
97
100
|
const SESSION_DURATION = 60 * 60 * 1e3;
|
|
98
101
|
const STORAGE_PREFIX = "balance_";
|
|
99
|
-
const API_ENDPOINT = customEndpoint ? customEndpoint : useEmulator ? `http://localhost:5001/artist-os-distro/us-central1/
|
|
102
|
+
const API_ENDPOINT = customEndpoint ? customEndpoint : useEmulator ? `http://localhost:5001/artist-os-distro/us-central1/ingestEvents` : `https://us-central1-artist-os-distro.cloudfunctions.net/ingestEvents`;
|
|
100
103
|
let sessionId = null;
|
|
101
104
|
let visitorId = null;
|
|
102
105
|
let fanIdHash = null;
|
|
@@ -113,9 +116,14 @@ var BalancePixel = (() => {
|
|
|
113
116
|
const IDLE_TIMEOUT = 2 * 60 * 1e3;
|
|
114
117
|
let lastActivityTime = Date.now();
|
|
115
118
|
let isIdle = false;
|
|
119
|
+
const SILENT_MODE = !debug;
|
|
116
120
|
const log = (...args) => {
|
|
117
121
|
if (debug)
|
|
118
|
-
console.log("[
|
|
122
|
+
console.log("[BLN]", ...args);
|
|
123
|
+
};
|
|
124
|
+
const logError = (...args) => {
|
|
125
|
+
if (!SILENT_MODE)
|
|
126
|
+
console.error("[BLN]", ...args);
|
|
119
127
|
};
|
|
120
128
|
const CONSENT_STYLES = {
|
|
121
129
|
base: `
|
|
@@ -227,6 +235,9 @@ var BalancePixel = (() => {
|
|
|
227
235
|
}
|
|
228
236
|
hasStoredConsent() {
|
|
229
237
|
try {
|
|
238
|
+
if (window._balanceConsentNeedsRefresh) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
230
241
|
return localStorage.getItem(CONSENT_STORAGE_KEY) !== null;
|
|
231
242
|
} catch {
|
|
232
243
|
return false;
|
|
@@ -269,6 +280,14 @@ var BalancePixel = (() => {
|
|
|
269
280
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
270
281
|
});
|
|
271
282
|
}
|
|
283
|
+
if (accepted && !window._balanceInitialPageviewFired) {
|
|
284
|
+
window._balanceInitialPageviewFired = true;
|
|
285
|
+
if (window.balance?.page) {
|
|
286
|
+
window.balance.page();
|
|
287
|
+
}
|
|
288
|
+
startHeartbeat();
|
|
289
|
+
log("Initial pageview fired after consent granted");
|
|
290
|
+
}
|
|
272
291
|
this.remove();
|
|
273
292
|
}
|
|
274
293
|
remove() {
|
|
@@ -338,7 +357,7 @@ var BalancePixel = (() => {
|
|
|
338
357
|
currentStorageTier = "local";
|
|
339
358
|
log(`Storage tier upgraded, migrated ${keysToMigrate.length} items`);
|
|
340
359
|
} catch (error) {
|
|
341
|
-
|
|
360
|
+
logError(" Storage migration failed:", error);
|
|
342
361
|
}
|
|
343
362
|
}
|
|
344
363
|
function generateUUID() {
|
|
@@ -438,6 +457,19 @@ var BalancePixel = (() => {
|
|
|
438
457
|
const stored = localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
439
458
|
if (stored) {
|
|
440
459
|
const parsed = JSON.parse(stored);
|
|
460
|
+
if (parsed.expiresAt && Date.now() > parsed.expiresAt) {
|
|
461
|
+
log("Consent expired - clearing stored consent");
|
|
462
|
+
localStorage.removeItem(CONSENT_STORAGE_KEY);
|
|
463
|
+
consent = null;
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (parsed.expiresAt) {
|
|
467
|
+
const timeUntilExpiry = parsed.expiresAt - Date.now();
|
|
468
|
+
if (timeUntilExpiry > 0 && timeUntilExpiry < CONSENT_REFRESH_THRESHOLD) {
|
|
469
|
+
log("Consent nearing expiration - will prompt for refresh");
|
|
470
|
+
window._balanceConsentNeedsRefresh = true;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
441
473
|
consent = parsed.preferences || null;
|
|
442
474
|
log("Loaded consent:", consent);
|
|
443
475
|
}
|
|
@@ -451,12 +483,15 @@ var BalancePixel = (() => {
|
|
|
451
483
|
const storage = {
|
|
452
484
|
preferences,
|
|
453
485
|
method: "explicit",
|
|
454
|
-
version: 1
|
|
486
|
+
version: 1,
|
|
487
|
+
expiresAt: Date.now() + CONSENT_TTL
|
|
488
|
+
// 12-month expiration
|
|
455
489
|
};
|
|
456
490
|
localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(storage));
|
|
457
|
-
log("Consent saved:", preferences);
|
|
491
|
+
log("Consent saved with TTL:", preferences);
|
|
492
|
+
window._balanceConsentNeedsRefresh = false;
|
|
458
493
|
} catch (e) {
|
|
459
|
-
|
|
494
|
+
logError(" Could not save consent:", e);
|
|
460
495
|
}
|
|
461
496
|
if (preferences.analytics === true) {
|
|
462
497
|
upgradeStorageTier();
|
|
@@ -547,7 +582,7 @@ var BalancePixel = (() => {
|
|
|
547
582
|
}
|
|
548
583
|
log("Events sent successfully");
|
|
549
584
|
} catch (error) {
|
|
550
|
-
|
|
585
|
+
logError(" Failed to send events:", error);
|
|
551
586
|
if (eventQueue.length < 50) {
|
|
552
587
|
eventQueue.push(...events);
|
|
553
588
|
}
|
|
@@ -683,9 +718,10 @@ var BalancePixel = (() => {
|
|
|
683
718
|
event_name: "identify",
|
|
684
719
|
fan_id_hash: fanIdHash,
|
|
685
720
|
metadata: {
|
|
686
|
-
email
|
|
687
|
-
// Pass email for display_name fallback (extracted prefix only stored)
|
|
721
|
+
// GDPR FIX: Never send plaintext email - only hashed identifier
|
|
688
722
|
email_sha256: fanIdHash,
|
|
723
|
+
email_display: maskedEmail,
|
|
724
|
+
// Safe for UI display (e.g., "j***@example.com")
|
|
689
725
|
traits,
|
|
690
726
|
consent_preferences: consent || void 0,
|
|
691
727
|
storage_tier: currentStorageTier
|
|
@@ -693,7 +729,7 @@ var BalancePixel = (() => {
|
|
|
693
729
|
});
|
|
694
730
|
enqueueEvent(event);
|
|
695
731
|
} catch (error) {
|
|
696
|
-
|
|
732
|
+
logError(" Failed to identify:", error);
|
|
697
733
|
}
|
|
698
734
|
}
|
|
699
735
|
function purchase(revenue, currency = "USD", properties = {}) {
|
|
@@ -725,14 +761,7 @@ var BalancePixel = (() => {
|
|
|
725
761
|
if (consentUIEnabled) {
|
|
726
762
|
log("Consent UI enabled, waiting for explicit user consent (Tier 0 mode)");
|
|
727
763
|
} else {
|
|
728
|
-
consent
|
|
729
|
-
analytics: true,
|
|
730
|
-
marketing: true,
|
|
731
|
-
personalization: true,
|
|
732
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
733
|
-
};
|
|
734
|
-
log("Default consent enabled (all tracking):", consent);
|
|
735
|
-
upgradeStorageTier();
|
|
764
|
+
log("No consent - operating in privacy-first mode (session storage only, limited tracking)");
|
|
736
765
|
}
|
|
737
766
|
}
|
|
738
767
|
if (consent?.analytics && !visitorId) {
|
|
@@ -753,9 +782,19 @@ var BalancePixel = (() => {
|
|
|
753
782
|
useEmulator,
|
|
754
783
|
endpoint: API_ENDPOINT
|
|
755
784
|
});
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
785
|
+
if (consent?.analytics === true) {
|
|
786
|
+
window._balanceInitialPageviewFired = true;
|
|
787
|
+
trackPageView();
|
|
788
|
+
startHeartbeat();
|
|
789
|
+
log("Full tracking enabled (user consented)");
|
|
790
|
+
} else if (consentUIEnabled) {
|
|
791
|
+
log("Tracking dormant - waiting for explicit consent via ConsentManager");
|
|
792
|
+
} else {
|
|
793
|
+
window._balanceInitialPageviewFired = true;
|
|
794
|
+
trackPageView();
|
|
795
|
+
startHeartbeat();
|
|
796
|
+
log("Privacy-first tracking enabled (session-scoped, no persistent IDs)");
|
|
797
|
+
}
|
|
759
798
|
["mousemove", "keydown", "scroll", "touchstart"].forEach((eventType) => {
|
|
760
799
|
document.addEventListener(eventType, resetIdleTimer, { passive: true });
|
|
761
800
|
});
|
|
@@ -772,6 +811,12 @@ var BalancePixel = (() => {
|
|
|
772
811
|
}
|
|
773
812
|
});
|
|
774
813
|
}
|
|
814
|
+
if (window.balance?._version && window.balance._version !== PIXEL_VERSION) {
|
|
815
|
+
console.warn(
|
|
816
|
+
`[BLN] Version conflict: ${window.balance._version} already loaded, skipping ${PIXEL_VERSION}`
|
|
817
|
+
);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
775
820
|
const existingQueue = window.balance?.q || [];
|
|
776
821
|
function processQueue() {
|
|
777
822
|
if (existingQueue.length > 0) {
|
|
@@ -811,7 +856,9 @@ var BalancePixel = (() => {
|
|
|
811
856
|
getAttribution: () => attribution,
|
|
812
857
|
setConsent,
|
|
813
858
|
getConsent,
|
|
814
|
-
hasConsent
|
|
859
|
+
hasConsent,
|
|
860
|
+
_version: PIXEL_VERSION
|
|
861
|
+
// Expose version for collision detection
|
|
815
862
|
};
|
|
816
863
|
function initConsentManager() {
|
|
817
864
|
if (consentUIEnabled && !consent) {
|
|
@@ -822,16 +869,17 @@ var BalancePixel = (() => {
|
|
|
822
869
|
});
|
|
823
870
|
}
|
|
824
871
|
}
|
|
872
|
+
const scheduleIdleWork = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
|
|
825
873
|
if (document.readyState === "loading") {
|
|
826
874
|
document.addEventListener("DOMContentLoaded", () => {
|
|
827
875
|
init();
|
|
828
876
|
processQueue();
|
|
829
|
-
initConsentManager();
|
|
877
|
+
scheduleIdleWork(() => initConsentManager());
|
|
830
878
|
});
|
|
831
879
|
} else {
|
|
832
880
|
init();
|
|
833
881
|
processQueue();
|
|
834
|
-
initConsentManager();
|
|
882
|
+
scheduleIdleWork(() => initConsentManager());
|
|
835
883
|
}
|
|
836
884
|
log("Pixel script loaded");
|
|
837
885
|
})();
|
package/dist/browser.min.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var BalancePixel=(()=>{var
|
|
1
|
+
var BalancePixel=(()=>{var ze=Object.defineProperty;var Ve=(l,d,c)=>d in l?ze(l,d,{enumerable:!0,configurable:!0,writable:!0,value:c}):l[d]=c;var A=(l,d,c)=>(Ve(l,typeof d!="symbol"?d+"":d,c),c);(function(){let l="0.7.0";function d(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 o="Unknown";return/iphone|ipad/i.test(e)?o="iOS":/android/i.test(e)?o="Android":/windows/i.test(e)?o="Windows":/mac os/i.test(e)?o="macOS":/linux/i.test(e)?o="Linux":/cros/i.test(e)&&(o="ChromeOS"),{device_type:t,browser:n,os:o}}let c=null;function be(){if(!c)try{c=d(navigator.userAgent)}catch{c={device_type:"desktop",browser:"Unknown",os:"Unknown"}}return c}let a=document.currentScript,O=a?.dataset.artistId,N=a?.dataset.projectId,L=N?he(N):void 0;function he(e){return!e||e.startsWith("release_")||e.startsWith("merch_")||e.startsWith("link_")||e.startsWith("custom_")?e:`custom_${e}`}let K=a?.dataset.emulator==="true",Y=a?.dataset.debug==="true",R=parseInt(a?.dataset.heartbeatInterval||"30000",10),J=a?.dataset.heartbeat!=="false",G=a?.dataset.source,Q=a?.dataset.endpoint,U=a?.dataset.consentUi==="true",ye=a?.dataset.consentStyle||"brutalist",ve=a?.dataset.primaryColor,we=a?.dataset.bannerPosition||"bottom";function Ie(){if(G)return G;let e=typeof window.dataLayer<"u"&&Array.isArray(window.dataLayer),t=typeof window.gtag=="function";return e&&t?"gtm":"pixel"}let k="pixel";if(!O){x(" Error: data-artist-id attribute is required");return}let X="session_id",M="session_timestamp",Z="attribution",ee="fan_id_hash",F="visitor_id",C="balance_consent",xe=365*24*60*60*1e3,Se=30*24*60*60*1e3,_e=60*60*1e3,p="balance_",H=Q||(K?"http://localhost:5001/artist-os-distro/us-central1/ingestEvents":"https://us-central1-artist-os-distro.cloudfunctions.net/ingestEvents"),E=null,s=null,u=null,i=null,m={},f=[],j=null,g="session",b=null,ke=0,B=0,h=0,w=!0,Ce=2*60*1e3,te=Date.now(),I=!1,Ee=!Y,r=(...e)=>{Y&&console.log("[BLN]",...e)},x=(...e)=>{Ee||console.error("[BLN]",...e)},$={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 Me=Object.defineProperty;var Be=(f,a,g)=>a in f?Me(f,
|
|
|
83
83
|
}
|
|
84
84
|
.btn.accept { background: #fff; color: #000; border-color: #fff; }
|
|
85
85
|
.btn:hover { opacity: 0.8; }
|
|
86
|
-
`};class
|
|
86
|
+
`};class Te{constructor(t){A(this,"container",null);A(this,"shadow",null);A(this,"config");if(this.config=t,this.hasStoredConsent()){r("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),r("ConsentManager: Banner rendered")}hasStoredConsent(){try{return window._balanceConsentNeedsRefresh?!1:localStorage.getItem(C)!==null}catch{return!1}}render(){if(!this.shadow)return;let t=this.config.style||"brutalist",n=$.base,o=$[t]||$.brutalist,_=this.config.position==="top"?"top: 0;":"bottom: 0;",me=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 { ${
|
|
89
|
+
:host { ${_} }
|
|
90
90
|
${o}
|
|
91
|
-
${
|
|
91
|
+
${me}
|
|
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 Me=Object.defineProperty;var Be=(f,a,g)=>a in f?Me(f,
|
|
|
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()}),this.remove()}remove(){this.container&&(this.container.remove(),this.container=null,this.shadow=null,r("ConsentManager: Banner removed"))}}function
|
|
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")})();})();
|
package/dist/index.esm.d.ts
CHANGED
|
@@ -53,7 +53,12 @@ export interface BalanceContextValue {
|
|
|
53
53
|
setConsent: (preferences: ConsentPreferences) => void;
|
|
54
54
|
getConsent: () => ConsentPreferences | null;
|
|
55
55
|
hasConsent: (type: 'analytics' | 'marketing' | 'personalization') => boolean;
|
|
56
|
+
/** True when the pixel script has loaded successfully */
|
|
56
57
|
isReady: boolean;
|
|
58
|
+
/** True if the pixel script failed to load */
|
|
59
|
+
isError: boolean;
|
|
60
|
+
/** Error object if script loading failed */
|
|
61
|
+
error: Error | null;
|
|
57
62
|
artistId: string;
|
|
58
63
|
projectId?: string;
|
|
59
64
|
endpoint?: string;
|
package/dist/index.js
CHANGED
|
@@ -100,6 +100,8 @@ var defaultContextValue = {
|
|
|
100
100
|
hasConsent: () => false,
|
|
101
101
|
// State - SSR-safe defaults
|
|
102
102
|
isReady: false,
|
|
103
|
+
isError: false,
|
|
104
|
+
error: null,
|
|
103
105
|
artistId: "",
|
|
104
106
|
projectId: void 0,
|
|
105
107
|
endpoint: void 0,
|
|
@@ -284,6 +286,8 @@ function BalanceProvider({
|
|
|
284
286
|
children
|
|
285
287
|
}) {
|
|
286
288
|
const [isReady, setIsReady] = (0, import_react4.useState)(false);
|
|
289
|
+
const [isError, setIsError] = (0, import_react4.useState)(false);
|
|
290
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
287
291
|
const [shouldLoadScript, setShouldLoadScript] = (0, import_react4.useState)(false);
|
|
288
292
|
(0, import_react4.useEffect)(() => {
|
|
289
293
|
ensureGlobalStub();
|
|
@@ -390,6 +394,8 @@ function BalanceProvider({
|
|
|
390
394
|
hasConsent: hasConsent2,
|
|
391
395
|
// State
|
|
392
396
|
isReady,
|
|
397
|
+
isError,
|
|
398
|
+
error,
|
|
393
399
|
artistId,
|
|
394
400
|
projectId,
|
|
395
401
|
endpoint,
|
|
@@ -406,6 +412,8 @@ function BalanceProvider({
|
|
|
406
412
|
getConsent2,
|
|
407
413
|
hasConsent2,
|
|
408
414
|
isReady,
|
|
415
|
+
isError,
|
|
416
|
+
error,
|
|
409
417
|
artistId,
|
|
410
418
|
projectId,
|
|
411
419
|
endpoint,
|
|
@@ -419,8 +427,17 @@ function BalanceProvider({
|
|
|
419
427
|
}, [debug]);
|
|
420
428
|
const handleScriptError = (0, import_react4.useCallback)((e) => {
|
|
421
429
|
console.error("[BalanceProvider] Failed to load pixel script:", e);
|
|
430
|
+
setIsError(true);
|
|
431
|
+
setError(e);
|
|
422
432
|
}, []);
|
|
423
433
|
const resolvedScriptUrl = scriptUrl ?? DEFAULT_SCRIPT_URL;
|
|
434
|
+
(0, import_react4.useEffect)(() => {
|
|
435
|
+
if (debug && !scriptUrl) {
|
|
436
|
+
console.warn(
|
|
437
|
+
'[BalanceProvider] Using default CDN URL. For production, consider hosting the pixel script locally:\n1. Copy index.js from artist-os-distro/apps/web/public/scripts/\n2. Place it in your public/scripts/index.js\n3. Set scriptUrl="/scripts/index.js"\n\nThis avoids external dependencies and CSP issues.'
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}, [debug, scriptUrl]);
|
|
424
441
|
const content = /* @__PURE__ */ import_react4.default.createElement(BalanceContext.Provider, { value: contextValue }, shouldLoadScript && /* @__PURE__ */ import_react4.default.createElement(
|
|
425
442
|
import_script2.default,
|
|
426
443
|
{
|
|
@@ -2880,6 +2897,17 @@ var import_react23 = require("react");
|
|
|
2880
2897
|
|
|
2881
2898
|
// src/storage/StorageManager.ts
|
|
2882
2899
|
var DEFAULT_PREFIX = "balance_";
|
|
2900
|
+
var TTL_DURATIONS = {
|
|
2901
|
+
/** Consent should be refreshed annually per GDPR best practices */
|
|
2902
|
+
CONSENT: 365 * 24 * 60 * 60 * 1e3,
|
|
2903
|
+
// 12 months
|
|
2904
|
+
/** Session IDs expire with the session anyway */
|
|
2905
|
+
SESSION: 30 * 60 * 1e3,
|
|
2906
|
+
// 30 minutes
|
|
2907
|
+
/** Visitor IDs can persist longer for returning user detection */
|
|
2908
|
+
VISITOR: 90 * 24 * 60 * 60 * 1e3
|
|
2909
|
+
// 90 days
|
|
2910
|
+
};
|
|
2883
2911
|
var StorageManager = class {
|
|
2884
2912
|
constructor(config) {
|
|
2885
2913
|
__publicField(this, "prefix");
|
|
@@ -3061,6 +3089,80 @@ var StorageManager = class {
|
|
|
3061
3089
|
} catch {
|
|
3062
3090
|
}
|
|
3063
3091
|
}
|
|
3092
|
+
/**
|
|
3093
|
+
* Set item with TTL (Time To Live)
|
|
3094
|
+
* The item will automatically expire after the specified duration
|
|
3095
|
+
*/
|
|
3096
|
+
setItemWithTTL(key, value, ttlMs) {
|
|
3097
|
+
const ttlWrapper = {
|
|
3098
|
+
value,
|
|
3099
|
+
expiresAt: Date.now() + ttlMs
|
|
3100
|
+
};
|
|
3101
|
+
this.setItem(key, JSON.stringify(ttlWrapper));
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Get item with TTL check
|
|
3105
|
+
* Returns null if the item has expired (and removes it)
|
|
3106
|
+
*/
|
|
3107
|
+
getItemWithTTL(key) {
|
|
3108
|
+
const raw = this.getItem(key);
|
|
3109
|
+
if (!raw)
|
|
3110
|
+
return null;
|
|
3111
|
+
try {
|
|
3112
|
+
const parsed = JSON.parse(raw);
|
|
3113
|
+
if (typeof parsed === "object" && "expiresAt" in parsed && "value" in parsed) {
|
|
3114
|
+
if (Date.now() > parsed.expiresAt) {
|
|
3115
|
+
this.removeItem(key);
|
|
3116
|
+
return null;
|
|
3117
|
+
}
|
|
3118
|
+
return parsed.value;
|
|
3119
|
+
}
|
|
3120
|
+
return raw;
|
|
3121
|
+
} catch {
|
|
3122
|
+
return raw;
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
/**
|
|
3126
|
+
* Set JSON item with TTL
|
|
3127
|
+
*/
|
|
3128
|
+
setJSONWithTTL(key, value, ttlMs) {
|
|
3129
|
+
try {
|
|
3130
|
+
this.setItemWithTTL(key, JSON.stringify(value), ttlMs);
|
|
3131
|
+
} catch {
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
/**
|
|
3135
|
+
* Get JSON item with TTL check
|
|
3136
|
+
*/
|
|
3137
|
+
getJSONWithTTL(key) {
|
|
3138
|
+
const value = this.getItemWithTTL(key);
|
|
3139
|
+
if (!value)
|
|
3140
|
+
return null;
|
|
3141
|
+
try {
|
|
3142
|
+
return JSON.parse(value);
|
|
3143
|
+
} catch {
|
|
3144
|
+
return null;
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
/**
|
|
3148
|
+
* Check if an item's TTL needs refresh (e.g., consent nearing expiration)
|
|
3149
|
+
* Returns true if the item will expire within the threshold
|
|
3150
|
+
*/
|
|
3151
|
+
needsTTLRefresh(key, refreshThresholdMs = 30 * 24 * 60 * 60 * 1e3) {
|
|
3152
|
+
const raw = this.getItem(key);
|
|
3153
|
+
if (!raw)
|
|
3154
|
+
return false;
|
|
3155
|
+
try {
|
|
3156
|
+
const parsed = JSON.parse(raw);
|
|
3157
|
+
if (typeof parsed === "object" && "expiresAt" in parsed) {
|
|
3158
|
+
const timeUntilExpiry = parsed.expiresAt - Date.now();
|
|
3159
|
+
return timeUntilExpiry > 0 && timeUntilExpiry < refreshThresholdMs;
|
|
3160
|
+
}
|
|
3161
|
+
return false;
|
|
3162
|
+
} catch {
|
|
3163
|
+
return false;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3064
3166
|
};
|
|
3065
3167
|
var storageManagerInstance = null;
|
|
3066
3168
|
function getStorageManager() {
|
package/dist/index.mjs
CHANGED
|
@@ -31,6 +31,8 @@ var defaultContextValue = {
|
|
|
31
31
|
hasConsent: () => false,
|
|
32
32
|
// State - SSR-safe defaults
|
|
33
33
|
isReady: false,
|
|
34
|
+
isError: false,
|
|
35
|
+
error: null,
|
|
34
36
|
artistId: "",
|
|
35
37
|
projectId: void 0,
|
|
36
38
|
endpoint: void 0,
|
|
@@ -215,6 +217,8 @@ function BalanceProvider({
|
|
|
215
217
|
children
|
|
216
218
|
}) {
|
|
217
219
|
const [isReady, setIsReady] = useState(false);
|
|
220
|
+
const [isError, setIsError] = useState(false);
|
|
221
|
+
const [error, setError] = useState(null);
|
|
218
222
|
const [shouldLoadScript, setShouldLoadScript] = useState(false);
|
|
219
223
|
useEffect2(() => {
|
|
220
224
|
ensureGlobalStub();
|
|
@@ -321,6 +325,8 @@ function BalanceProvider({
|
|
|
321
325
|
hasConsent: hasConsent2,
|
|
322
326
|
// State
|
|
323
327
|
isReady,
|
|
328
|
+
isError,
|
|
329
|
+
error,
|
|
324
330
|
artistId,
|
|
325
331
|
projectId,
|
|
326
332
|
endpoint,
|
|
@@ -337,6 +343,8 @@ function BalanceProvider({
|
|
|
337
343
|
getConsent2,
|
|
338
344
|
hasConsent2,
|
|
339
345
|
isReady,
|
|
346
|
+
isError,
|
|
347
|
+
error,
|
|
340
348
|
artistId,
|
|
341
349
|
projectId,
|
|
342
350
|
endpoint,
|
|
@@ -350,8 +358,17 @@ function BalanceProvider({
|
|
|
350
358
|
}, [debug]);
|
|
351
359
|
const handleScriptError = useCallback2((e) => {
|
|
352
360
|
console.error("[BalanceProvider] Failed to load pixel script:", e);
|
|
361
|
+
setIsError(true);
|
|
362
|
+
setError(e);
|
|
353
363
|
}, []);
|
|
354
364
|
const resolvedScriptUrl = scriptUrl ?? DEFAULT_SCRIPT_URL;
|
|
365
|
+
useEffect2(() => {
|
|
366
|
+
if (debug && !scriptUrl) {
|
|
367
|
+
console.warn(
|
|
368
|
+
'[BalanceProvider] Using default CDN URL. For production, consider hosting the pixel script locally:\n1. Copy index.js from artist-os-distro/apps/web/public/scripts/\n2. Place it in your public/scripts/index.js\n3. Set scriptUrl="/scripts/index.js"\n\nThis avoids external dependencies and CSP issues.'
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}, [debug, scriptUrl]);
|
|
355
372
|
const content = /* @__PURE__ */ React3.createElement(BalanceContext.Provider, { value: contextValue }, shouldLoadScript && /* @__PURE__ */ React3.createElement(
|
|
356
373
|
Script2,
|
|
357
374
|
{
|
|
@@ -2811,6 +2828,17 @@ import { useCallback as useCallback16, useEffect as useEffect9, useRef as useRef
|
|
|
2811
2828
|
|
|
2812
2829
|
// src/storage/StorageManager.ts
|
|
2813
2830
|
var DEFAULT_PREFIX = "balance_";
|
|
2831
|
+
var TTL_DURATIONS = {
|
|
2832
|
+
/** Consent should be refreshed annually per GDPR best practices */
|
|
2833
|
+
CONSENT: 365 * 24 * 60 * 60 * 1e3,
|
|
2834
|
+
// 12 months
|
|
2835
|
+
/** Session IDs expire with the session anyway */
|
|
2836
|
+
SESSION: 30 * 60 * 1e3,
|
|
2837
|
+
// 30 minutes
|
|
2838
|
+
/** Visitor IDs can persist longer for returning user detection */
|
|
2839
|
+
VISITOR: 90 * 24 * 60 * 60 * 1e3
|
|
2840
|
+
// 90 days
|
|
2841
|
+
};
|
|
2814
2842
|
var StorageManager = class {
|
|
2815
2843
|
constructor(config) {
|
|
2816
2844
|
__publicField(this, "prefix");
|
|
@@ -2992,6 +3020,80 @@ var StorageManager = class {
|
|
|
2992
3020
|
} catch {
|
|
2993
3021
|
}
|
|
2994
3022
|
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Set item with TTL (Time To Live)
|
|
3025
|
+
* The item will automatically expire after the specified duration
|
|
3026
|
+
*/
|
|
3027
|
+
setItemWithTTL(key, value, ttlMs) {
|
|
3028
|
+
const ttlWrapper = {
|
|
3029
|
+
value,
|
|
3030
|
+
expiresAt: Date.now() + ttlMs
|
|
3031
|
+
};
|
|
3032
|
+
this.setItem(key, JSON.stringify(ttlWrapper));
|
|
3033
|
+
}
|
|
3034
|
+
/**
|
|
3035
|
+
* Get item with TTL check
|
|
3036
|
+
* Returns null if the item has expired (and removes it)
|
|
3037
|
+
*/
|
|
3038
|
+
getItemWithTTL(key) {
|
|
3039
|
+
const raw = this.getItem(key);
|
|
3040
|
+
if (!raw)
|
|
3041
|
+
return null;
|
|
3042
|
+
try {
|
|
3043
|
+
const parsed = JSON.parse(raw);
|
|
3044
|
+
if (typeof parsed === "object" && "expiresAt" in parsed && "value" in parsed) {
|
|
3045
|
+
if (Date.now() > parsed.expiresAt) {
|
|
3046
|
+
this.removeItem(key);
|
|
3047
|
+
return null;
|
|
3048
|
+
}
|
|
3049
|
+
return parsed.value;
|
|
3050
|
+
}
|
|
3051
|
+
return raw;
|
|
3052
|
+
} catch {
|
|
3053
|
+
return raw;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Set JSON item with TTL
|
|
3058
|
+
*/
|
|
3059
|
+
setJSONWithTTL(key, value, ttlMs) {
|
|
3060
|
+
try {
|
|
3061
|
+
this.setItemWithTTL(key, JSON.stringify(value), ttlMs);
|
|
3062
|
+
} catch {
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
/**
|
|
3066
|
+
* Get JSON item with TTL check
|
|
3067
|
+
*/
|
|
3068
|
+
getJSONWithTTL(key) {
|
|
3069
|
+
const value = this.getItemWithTTL(key);
|
|
3070
|
+
if (!value)
|
|
3071
|
+
return null;
|
|
3072
|
+
try {
|
|
3073
|
+
return JSON.parse(value);
|
|
3074
|
+
} catch {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Check if an item's TTL needs refresh (e.g., consent nearing expiration)
|
|
3080
|
+
* Returns true if the item will expire within the threshold
|
|
3081
|
+
*/
|
|
3082
|
+
needsTTLRefresh(key, refreshThresholdMs = 30 * 24 * 60 * 60 * 1e3) {
|
|
3083
|
+
const raw = this.getItem(key);
|
|
3084
|
+
if (!raw)
|
|
3085
|
+
return false;
|
|
3086
|
+
try {
|
|
3087
|
+
const parsed = JSON.parse(raw);
|
|
3088
|
+
if (typeof parsed === "object" && "expiresAt" in parsed) {
|
|
3089
|
+
const timeUntilExpiry = parsed.expiresAt - Date.now();
|
|
3090
|
+
return timeUntilExpiry > 0 && timeUntilExpiry < refreshThresholdMs;
|
|
3091
|
+
}
|
|
3092
|
+
return false;
|
|
3093
|
+
} catch {
|
|
3094
|
+
return false;
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
2995
3097
|
};
|
|
2996
3098
|
var storageManagerInstance = null;
|
|
2997
3099
|
function getStorageManager() {
|