@hifilabs/pixel 0.6.2 → 0.7.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 +73 -25
- package/dist/browser.min.js +5 -5
- package/dist/index.js +85 -0
- package/dist/index.mjs +85 -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.js
CHANGED
|
@@ -2880,6 +2880,17 @@ var import_react23 = require("react");
|
|
|
2880
2880
|
|
|
2881
2881
|
// src/storage/StorageManager.ts
|
|
2882
2882
|
var DEFAULT_PREFIX = "balance_";
|
|
2883
|
+
var TTL_DURATIONS = {
|
|
2884
|
+
/** Consent should be refreshed annually per GDPR best practices */
|
|
2885
|
+
CONSENT: 365 * 24 * 60 * 60 * 1e3,
|
|
2886
|
+
// 12 months
|
|
2887
|
+
/** Session IDs expire with the session anyway */
|
|
2888
|
+
SESSION: 30 * 60 * 1e3,
|
|
2889
|
+
// 30 minutes
|
|
2890
|
+
/** Visitor IDs can persist longer for returning user detection */
|
|
2891
|
+
VISITOR: 90 * 24 * 60 * 60 * 1e3
|
|
2892
|
+
// 90 days
|
|
2893
|
+
};
|
|
2883
2894
|
var StorageManager = class {
|
|
2884
2895
|
constructor(config) {
|
|
2885
2896
|
__publicField(this, "prefix");
|
|
@@ -3061,6 +3072,80 @@ var StorageManager = class {
|
|
|
3061
3072
|
} catch {
|
|
3062
3073
|
}
|
|
3063
3074
|
}
|
|
3075
|
+
/**
|
|
3076
|
+
* Set item with TTL (Time To Live)
|
|
3077
|
+
* The item will automatically expire after the specified duration
|
|
3078
|
+
*/
|
|
3079
|
+
setItemWithTTL(key, value, ttlMs) {
|
|
3080
|
+
const ttlWrapper = {
|
|
3081
|
+
value,
|
|
3082
|
+
expiresAt: Date.now() + ttlMs
|
|
3083
|
+
};
|
|
3084
|
+
this.setItem(key, JSON.stringify(ttlWrapper));
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Get item with TTL check
|
|
3088
|
+
* Returns null if the item has expired (and removes it)
|
|
3089
|
+
*/
|
|
3090
|
+
getItemWithTTL(key) {
|
|
3091
|
+
const raw = this.getItem(key);
|
|
3092
|
+
if (!raw)
|
|
3093
|
+
return null;
|
|
3094
|
+
try {
|
|
3095
|
+
const parsed = JSON.parse(raw);
|
|
3096
|
+
if (typeof parsed === "object" && "expiresAt" in parsed && "value" in parsed) {
|
|
3097
|
+
if (Date.now() > parsed.expiresAt) {
|
|
3098
|
+
this.removeItem(key);
|
|
3099
|
+
return null;
|
|
3100
|
+
}
|
|
3101
|
+
return parsed.value;
|
|
3102
|
+
}
|
|
3103
|
+
return raw;
|
|
3104
|
+
} catch {
|
|
3105
|
+
return raw;
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
/**
|
|
3109
|
+
* Set JSON item with TTL
|
|
3110
|
+
*/
|
|
3111
|
+
setJSONWithTTL(key, value, ttlMs) {
|
|
3112
|
+
try {
|
|
3113
|
+
this.setItemWithTTL(key, JSON.stringify(value), ttlMs);
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Get JSON item with TTL check
|
|
3119
|
+
*/
|
|
3120
|
+
getJSONWithTTL(key) {
|
|
3121
|
+
const value = this.getItemWithTTL(key);
|
|
3122
|
+
if (!value)
|
|
3123
|
+
return null;
|
|
3124
|
+
try {
|
|
3125
|
+
return JSON.parse(value);
|
|
3126
|
+
} catch {
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
/**
|
|
3131
|
+
* Check if an item's TTL needs refresh (e.g., consent nearing expiration)
|
|
3132
|
+
* Returns true if the item will expire within the threshold
|
|
3133
|
+
*/
|
|
3134
|
+
needsTTLRefresh(key, refreshThresholdMs = 30 * 24 * 60 * 60 * 1e3) {
|
|
3135
|
+
const raw = this.getItem(key);
|
|
3136
|
+
if (!raw)
|
|
3137
|
+
return false;
|
|
3138
|
+
try {
|
|
3139
|
+
const parsed = JSON.parse(raw);
|
|
3140
|
+
if (typeof parsed === "object" && "expiresAt" in parsed) {
|
|
3141
|
+
const timeUntilExpiry = parsed.expiresAt - Date.now();
|
|
3142
|
+
return timeUntilExpiry > 0 && timeUntilExpiry < refreshThresholdMs;
|
|
3143
|
+
}
|
|
3144
|
+
return false;
|
|
3145
|
+
} catch {
|
|
3146
|
+
return false;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3064
3149
|
};
|
|
3065
3150
|
var storageManagerInstance = null;
|
|
3066
3151
|
function getStorageManager() {
|
package/dist/index.mjs
CHANGED
|
@@ -2811,6 +2811,17 @@ import { useCallback as useCallback16, useEffect as useEffect9, useRef as useRef
|
|
|
2811
2811
|
|
|
2812
2812
|
// src/storage/StorageManager.ts
|
|
2813
2813
|
var DEFAULT_PREFIX = "balance_";
|
|
2814
|
+
var TTL_DURATIONS = {
|
|
2815
|
+
/** Consent should be refreshed annually per GDPR best practices */
|
|
2816
|
+
CONSENT: 365 * 24 * 60 * 60 * 1e3,
|
|
2817
|
+
// 12 months
|
|
2818
|
+
/** Session IDs expire with the session anyway */
|
|
2819
|
+
SESSION: 30 * 60 * 1e3,
|
|
2820
|
+
// 30 minutes
|
|
2821
|
+
/** Visitor IDs can persist longer for returning user detection */
|
|
2822
|
+
VISITOR: 90 * 24 * 60 * 60 * 1e3
|
|
2823
|
+
// 90 days
|
|
2824
|
+
};
|
|
2814
2825
|
var StorageManager = class {
|
|
2815
2826
|
constructor(config) {
|
|
2816
2827
|
__publicField(this, "prefix");
|
|
@@ -2992,6 +3003,80 @@ var StorageManager = class {
|
|
|
2992
3003
|
} catch {
|
|
2993
3004
|
}
|
|
2994
3005
|
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Set item with TTL (Time To Live)
|
|
3008
|
+
* The item will automatically expire after the specified duration
|
|
3009
|
+
*/
|
|
3010
|
+
setItemWithTTL(key, value, ttlMs) {
|
|
3011
|
+
const ttlWrapper = {
|
|
3012
|
+
value,
|
|
3013
|
+
expiresAt: Date.now() + ttlMs
|
|
3014
|
+
};
|
|
3015
|
+
this.setItem(key, JSON.stringify(ttlWrapper));
|
|
3016
|
+
}
|
|
3017
|
+
/**
|
|
3018
|
+
* Get item with TTL check
|
|
3019
|
+
* Returns null if the item has expired (and removes it)
|
|
3020
|
+
*/
|
|
3021
|
+
getItemWithTTL(key) {
|
|
3022
|
+
const raw = this.getItem(key);
|
|
3023
|
+
if (!raw)
|
|
3024
|
+
return null;
|
|
3025
|
+
try {
|
|
3026
|
+
const parsed = JSON.parse(raw);
|
|
3027
|
+
if (typeof parsed === "object" && "expiresAt" in parsed && "value" in parsed) {
|
|
3028
|
+
if (Date.now() > parsed.expiresAt) {
|
|
3029
|
+
this.removeItem(key);
|
|
3030
|
+
return null;
|
|
3031
|
+
}
|
|
3032
|
+
return parsed.value;
|
|
3033
|
+
}
|
|
3034
|
+
return raw;
|
|
3035
|
+
} catch {
|
|
3036
|
+
return raw;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* Set JSON item with TTL
|
|
3041
|
+
*/
|
|
3042
|
+
setJSONWithTTL(key, value, ttlMs) {
|
|
3043
|
+
try {
|
|
3044
|
+
this.setItemWithTTL(key, JSON.stringify(value), ttlMs);
|
|
3045
|
+
} catch {
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Get JSON item with TTL check
|
|
3050
|
+
*/
|
|
3051
|
+
getJSONWithTTL(key) {
|
|
3052
|
+
const value = this.getItemWithTTL(key);
|
|
3053
|
+
if (!value)
|
|
3054
|
+
return null;
|
|
3055
|
+
try {
|
|
3056
|
+
return JSON.parse(value);
|
|
3057
|
+
} catch {
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* Check if an item's TTL needs refresh (e.g., consent nearing expiration)
|
|
3063
|
+
* Returns true if the item will expire within the threshold
|
|
3064
|
+
*/
|
|
3065
|
+
needsTTLRefresh(key, refreshThresholdMs = 30 * 24 * 60 * 60 * 1e3) {
|
|
3066
|
+
const raw = this.getItem(key);
|
|
3067
|
+
if (!raw)
|
|
3068
|
+
return false;
|
|
3069
|
+
try {
|
|
3070
|
+
const parsed = JSON.parse(raw);
|
|
3071
|
+
if (typeof parsed === "object" && "expiresAt" in parsed) {
|
|
3072
|
+
const timeUntilExpiry = parsed.expiresAt - Date.now();
|
|
3073
|
+
return timeUntilExpiry > 0 && timeUntilExpiry < refreshThresholdMs;
|
|
3074
|
+
}
|
|
3075
|
+
return false;
|
|
3076
|
+
} catch {
|
|
3077
|
+
return false;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2995
3080
|
};
|
|
2996
3081
|
var storageManagerInstance = null;
|
|
2997
3082
|
function getStorageManager() {
|