@hifilabs/pixel 0.0.9 → 0.0.10

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/index.js CHANGED
@@ -86,11 +86,56 @@ var BalancePixel = (() => {
86
86
 
87
87
  // src/index.ts
88
88
  (function() {
89
+ function parseUserAgent(ua) {
90
+ let device_type = "desktop";
91
+ if (/ipad|tablet|android(?!.*mobile)/i.test(ua))
92
+ device_type = "tablet";
93
+ else if (/mobile|iphone|android.*mobile|blackberry|iemobile/i.test(ua))
94
+ device_type = "mobile";
95
+ let browser = "Unknown";
96
+ if (/edg/i.test(ua))
97
+ browser = "Edge";
98
+ else if (/opr|opera/i.test(ua))
99
+ browser = "Opera";
100
+ else if (/firefox/i.test(ua))
101
+ browser = "Firefox";
102
+ else if (/chrome/i.test(ua))
103
+ browser = "Chrome";
104
+ else if (/safari/i.test(ua))
105
+ browser = "Safari";
106
+ let os = "Unknown";
107
+ if (/iphone|ipad/i.test(ua))
108
+ os = "iOS";
109
+ else if (/android/i.test(ua))
110
+ os = "Android";
111
+ else if (/windows/i.test(ua))
112
+ os = "Windows";
113
+ else if (/mac os/i.test(ua))
114
+ os = "macOS";
115
+ else if (/linux/i.test(ua))
116
+ os = "Linux";
117
+ else if (/cros/i.test(ua))
118
+ os = "ChromeOS";
119
+ return { device_type, browser, os };
120
+ }
121
+ let cachedDeviceInfo = null;
122
+ function getDeviceInfo() {
123
+ if (!cachedDeviceInfo) {
124
+ try {
125
+ cachedDeviceInfo = parseUserAgent(navigator.userAgent);
126
+ } catch {
127
+ cachedDeviceInfo = { device_type: "desktop", browser: "Unknown", os: "Unknown" };
128
+ }
129
+ }
130
+ return cachedDeviceInfo;
131
+ }
89
132
  const currentScript = document.currentScript;
90
133
  const artistId = currentScript?.dataset.artistId;
91
134
  const projectId = currentScript?.dataset.projectId;
92
135
  const useEmulator = currentScript?.dataset.emulator === "true";
93
136
  const debug = currentScript?.dataset.debug === "true";
137
+ const heartbeatInterval = parseInt(currentScript?.dataset.heartbeatInterval || "30000", 10);
138
+ const heartbeatEnabled = currentScript?.dataset.heartbeat !== "false";
94
139
  if (!artistId) {
95
140
  console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");
96
141
  return;
@@ -110,6 +155,14 @@ var BalancePixel = (() => {
110
155
  let eventQueue = [];
111
156
  let flushTimer = null;
112
157
  let currentStorageTier = "session";
158
+ let heartbeatTimer = null;
159
+ let pageStartTime = 0;
160
+ let activeTime = 0;
161
+ let lastActiveTimestamp = 0;
162
+ let isPageVisible = true;
163
+ const IDLE_TIMEOUT = 2 * 60 * 1e3;
164
+ let lastActivityTime = Date.now();
165
+ let isIdle = false;
113
166
  const log = (...args) => {
114
167
  if (debug)
115
168
  console.log("[BALANCE Pixel]", ...args);
@@ -290,6 +343,7 @@ var BalancePixel = (() => {
290
343
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
291
344
  }
292
345
  function buildEvent(partial) {
346
+ const deviceInfo = getDeviceInfo();
293
347
  const base = {
294
348
  artist_id: artistId,
295
349
  fan_session_id: sessionId,
@@ -298,6 +352,10 @@ var BalancePixel = (() => {
298
352
  source_url: window.location.href,
299
353
  referrer_url: document.referrer || void 0,
300
354
  user_agent: navigator.userAgent,
355
+ // Device info (parsed client-side)
356
+ device_type: deviceInfo.device_type,
357
+ browser: deviceInfo.browser,
358
+ os: deviceInfo.os,
301
359
  ...partial,
302
360
  ...attribution
303
361
  };
@@ -346,6 +404,86 @@ var BalancePixel = (() => {
346
404
  flush();
347
405
  }, 5e3);
348
406
  }
407
+ function startActiveTimeTracking() {
408
+ if (!lastActiveTimestamp) {
409
+ pageStartTime = Date.now();
410
+ }
411
+ lastActiveTimestamp = Date.now();
412
+ isPageVisible = true;
413
+ log("Active time tracking started/resumed");
414
+ }
415
+ function pauseActiveTimeTracking() {
416
+ if (lastActiveTimestamp && isPageVisible) {
417
+ activeTime += Date.now() - lastActiveTimestamp;
418
+ }
419
+ isPageVisible = false;
420
+ log("Active time tracking paused, accumulated:", activeTime, "ms");
421
+ }
422
+ function getCumulativeActiveTime() {
423
+ let total = activeTime;
424
+ if (isPageVisible && lastActiveTimestamp) {
425
+ total += Date.now() - lastActiveTimestamp;
426
+ }
427
+ return total;
428
+ }
429
+ function resetIdleTimer() {
430
+ lastActivityTime = Date.now();
431
+ if (isIdle) {
432
+ isIdle = false;
433
+ startActiveTimeTracking();
434
+ log("User returned from idle");
435
+ }
436
+ }
437
+ function sendHeartbeat() {
438
+ if (document.visibilityState === "hidden") {
439
+ log("Skipping heartbeat - tab hidden");
440
+ return;
441
+ }
442
+ if (Date.now() - lastActivityTime > IDLE_TIMEOUT) {
443
+ if (!isIdle) {
444
+ isIdle = true;
445
+ pauseActiveTimeTracking();
446
+ log("User idle - pausing heartbeat");
447
+ }
448
+ return;
449
+ }
450
+ const timeOnPage = getCumulativeActiveTime();
451
+ const timeOnPageSeconds = Math.round(timeOnPage / 1e3);
452
+ const event = buildEvent({
453
+ event_name: "engagement_heartbeat",
454
+ metadata: {
455
+ time_on_page_seconds: timeOnPageSeconds,
456
+ time_on_page_ms: timeOnPage,
457
+ heartbeat_interval_ms: heartbeatInterval,
458
+ is_active: isPageVisible && !isIdle
459
+ }
460
+ });
461
+ enqueueEvent(event);
462
+ log("Heartbeat sent:", timeOnPageSeconds, "seconds active");
463
+ }
464
+ function startHeartbeat() {
465
+ if (!heartbeatEnabled) {
466
+ log('Heartbeat disabled via data-heartbeat="false"');
467
+ return;
468
+ }
469
+ if (heartbeatTimer)
470
+ clearInterval(heartbeatTimer);
471
+ startActiveTimeTracking();
472
+ heartbeatTimer = window.setInterval(() => {
473
+ sendHeartbeat();
474
+ }, heartbeatInterval);
475
+ log("Heartbeat started with interval:", heartbeatInterval, "ms");
476
+ }
477
+ function stopHeartbeat() {
478
+ if (heartbeatTimer) {
479
+ clearInterval(heartbeatTimer);
480
+ heartbeatTimer = null;
481
+ }
482
+ if (heartbeatEnabled) {
483
+ sendHeartbeat();
484
+ log("Heartbeat stopped, final time sent");
485
+ }
486
+ }
349
487
  function trackPageView(options = {}) {
350
488
  const event = buildEvent({
351
489
  event_name: "pageview",
@@ -445,12 +583,20 @@ var BalancePixel = (() => {
445
583
  });
446
584
  window._balanceInitialPageviewFired = true;
447
585
  trackPageView();
586
+ startHeartbeat();
587
+ ["mousemove", "keydown", "scroll", "touchstart"].forEach((eventType) => {
588
+ document.addEventListener(eventType, resetIdleTimer, { passive: true });
589
+ });
448
590
  window.addEventListener("beforeunload", () => {
591
+ stopHeartbeat();
449
592
  flush();
450
593
  });
451
594
  document.addEventListener("visibilitychange", () => {
452
595
  if (document.visibilityState === "hidden") {
596
+ pauseActiveTimeTracking();
453
597
  flush();
598
+ } else {
599
+ startActiveTimeTracking();
454
600
  }
455
601
  });
456
602
  }
package/dist/index.min.js CHANGED
@@ -1 +1 @@
1
- var BalancePixel=(()=>{var te=Object.create;var _=Object.defineProperty;var re=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var oe=Object.getPrototypeOf,ae=Object.prototype.hasOwnProperty;var z=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(i,o)=>(typeof require<"u"?require:i)[o]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var se=(t,i)=>{for(var o in i)_(t,o,{get:i[o],enumerable:!0})},U=(t,i,o,d)=>{if(i&&typeof i=="object"||typeof i=="function")for(let l of ie(i))!ae.call(t,l)&&l!==o&&_(t,l,{get:()=>i[l],enumerable:!(d=re(i,l))||d.enumerable});return t};var ce=(t,i,o)=>(o=t!=null?te(oe(t)):{},U(i||!t||!t.__esModule?_(o,"default",{value:t,enumerable:!0}):o,t)),le=t=>U(_({},"__esModule",{value:!0}),t);var Se={};se(Se,{BalanceAnalytics:()=>j,getAttribution:()=>ye,getConsent:()=>xe,getFanIdHash:()=>me,getSessionId:()=>we,hasConsent:()=>be,identify:()=>ge,page:()=>fe,purchase:()=>pe,setConsent:()=>he,track:()=>de});var u=ce(z("react")),P=z("next/navigation");function ue(){let t=(0,P.usePathname)(),i=(0,P.useSearchParams)(),o=(0,u.useRef)(!0),d=(0,u.useRef)(null);return(0,u.useEffect)(()=>{if(typeof window>"u"||!window.balance)return;let l=t+(i?.toString()||"");if(d.current===l)return;if(o.current&&(o.current=!1,window._balanceInitialPageviewFired)){d.current=l,console.log("[BalanceAnalytics] Skipping initial pageview (script already fired)");return}let b=window.location.href,m=document.title;d.current=l,window.balance.page({url:b,title:m})},[t,i]),null}function j(){return u.default.createElement(u.Suspense,{fallback:null},u.default.createElement(ue,null))}(function(){let t=document.currentScript,i=t?.dataset.artistId,o=t?.dataset.projectId,d=t?.dataset.emulator==="true",l=t?.dataset.debug==="true";if(!i){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let b="session_id",m="session_timestamp",N="attribution",R="fan_id_hash",O="balance_consent",H=60*60*1e3,E="balance_",A=d?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint",S=null,g=null,c=null,w={},f=[],C=null,p="session",s=(...e)=>{l&&console.log("[BALANCE Pixel]",...e)};function B(){try{return p==="local"?localStorage:sessionStorage}catch{return null}}function v(e){let n=B();if(!n)return null;try{let r=E+e,a=n.getItem(r);if(!a&&p==="session")try{a=localStorage.getItem(r)}catch{}return a}catch{return null}}function y(e,n){let r=B();if(r)try{r.setItem(E+e,n)}catch{}}function k(){if(p!=="local"){s("Upgrading storage tier: session -> local");try{let e=[];for(let n=0;n<sessionStorage.length;n++){let r=sessionStorage.key(n);r?.startsWith(E)&&e.push(r)}for(let n of e){let r=sessionStorage.getItem(n);r&&localStorage.setItem(n,r)}for(let n of e)sessionStorage.removeItem(n);p="local",s(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){console.error("[BALANCE Pixel] Storage migration failed:",e)}}}function F(){return crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let n=Math.random()*16|0;return(e==="x"?n:n&3|8).toString(16)})}function M(){try{let e=v(b),n=v(m);if(e&&n&&Date.now()-parseInt(n,10)<H)return y(m,Date.now().toString()),e;let r=F();return y(b,r),y(m,Date.now().toString()),r}catch{return F()}}function K(){let e=new URLSearchParams(window.location.search),n={};return["source","medium","campaign","content","term"].forEach(r=>{let a=e.get(`utm_${r}`);a&&(n[`utm_${r}`]=a)}),n}function J(){try{let e=v(N);if(e){w=JSON.parse(e),s("Loaded attribution:",w);return}let n=K();Object.keys(n).length>0&&(w=n,y(N,JSON.stringify(n)),s("Captured attribution:",w))}catch{}}function Y(){try{g=v(R)}catch{}}function q(){try{let e=localStorage.getItem(O);e&&(c=JSON.parse(e).preferences||null,s("Loaded consent:",c))}catch{}}function $(e){let n=c;c=e;try{let a={preferences:e,method:"explicit",version:1};localStorage.setItem(O,JSON.stringify(a)),s("Consent saved:",e)}catch(a){console.error("[BALANCE Pixel] Could not save consent:",a)}e.analytics===!0&&k();let r=h({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:n||void 0}});x(r)}function G(){return c}function W(e){return c?.[e]===!0}async function Q(e){let n=e.toLowerCase().trim(),a=new TextEncoder().encode(n),T=await crypto.subtle.digest("SHA-256",a);return Array.from(new Uint8Array(T)).map(ne=>ne.toString(16).padStart(2,"0")).join("")}function h(e){let n={artist_id:i,fan_session_id:S,fan_id_hash:g||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,...e,...w};return o&&!e.projectId&&(n.projectId=o),n}function x(e){f.push(e),s("Event queued:",e.event_name,"(queue:",f.length,")"),f.length>=10&&I()}async function I(){if(f.length===0)return;let e=[...f];f=[],s("Flushing",e.length,"events to",A);try{let n=await fetch(A,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!n.ok)throw new Error(`HTTP ${n.status}`);s("Events sent successfully")}catch(n){console.error("[BALANCE Pixel] Failed to send events:",n),f.length<50&&f.push(...e)}}function V(){C&&clearInterval(C),C=window.setInterval(()=>{f.length>0&&I()},5e3)}function L(e={}){let n=h({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});x(n)}function X(e,n={}){let r=h({event_name:"custom",metadata:{event_type:e,...n}});x(r)}async function Z(e,n={}){try{if(c&&c.analytics===!1){s("Identify skipped - user declined analytics consent");return}g=await Q(e),c?.analytics===!0&&k(),y(R,g);let r=e.split("@"),a=r[0].charAt(0)+"***@"+(r[1]||"");s("Fan identified:",{name:n.name||"(no name)",email:a,hash:g.substring(0,16)+"...",traits:n,storageTier:p});let T=h({event_name:"identify",fan_id_hash:g,metadata:{email_sha256:g,traits:n,consent_preferences:c||void 0,storage_tier:p}});x(T)}catch(r){console.error("[BALANCE Pixel] Failed to identify:",r)}}function ee(e,n="USD",r={}){let a=h({event_name:"purchase",metadata:{revenue:e,currency:n,...r}});x(a)}function D(){q(),c?.analytics===!0?(p="local",s("Storage tier: local (analytics consent granted)")):(p="session",s("Storage tier: session (privacy by default)")),S=M(),Y(),c||(c={analytics:!0,marketing:!0,personalization:!0,timestamp:new Date().toISOString()},s("Default consent enabled (all tracking):",c),k()),J(),V(),s("Initialized",{artistId:i,projectId:o||"(none - will track to all projects)",sessionId:S,fanIdHash:g,consent:c,storageTier:p,useEmulator:d,endpoint:A}),window._balanceInitialPageviewFired=!0,L(),window.addEventListener("beforeunload",()=>{I()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&I()})}window.balance={track:X,identify:Z,page:L,purchase:ee,getSessionId:()=>S,getFanIdHash:()=>g,getAttribution:()=>w,setConsent:$,getConsent:G,hasConsent:W},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",D):D(),s("Pixel script loaded")})();var de=(t,i)=>{typeof window>"u"||(window.balance?window.balance.track(t,i):console.warn("[Balance Pixel] track() called before pixel initialized:",t))},ge=(t,i)=>typeof window>"u"?Promise.resolve():window.balance?window.balance.identify(t,i):(console.warn("[Balance Pixel] identify() called before pixel initialized"),Promise.resolve()),fe=t=>{typeof window>"u"||(window.balance?window.balance.page(t):console.warn("[Balance Pixel] page() called before pixel initialized"))},pe=(t,i,o)=>{typeof window>"u"||(window.balance?window.balance.purchase(t,i,o):console.warn("[Balance Pixel] purchase() called before pixel initialized"))},we=()=>typeof window>"u"?null:window.balance?.getSessionId()??null,me=()=>typeof window>"u"?null:window.balance?.getFanIdHash()??null,ye=()=>typeof window>"u"?{}:window.balance?.getAttribution()??{},he=t=>{typeof window>"u"||(window.balance?window.balance.setConsent(t):console.warn("[Balance Pixel] setConsent() called before pixel initialized"))},xe=()=>typeof window>"u"?null:window.balance?.getConsent()??null,be=t=>typeof window>"u"?!1:window.balance?.hasConsent(t)??!1;return le(Se);})();
1
+ var BalancePixel=(()=>{var Se=Object.create;var k=Object.defineProperty;var Ie=Object.getOwnPropertyDescriptor;var _e=Object.getOwnPropertyNames;var Ae=Object.getPrototypeOf,Pe=Object.prototype.hasOwnProperty;var Q=(i=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(i,{get:(r,s)=>(typeof require<"u"?require:r)[s]}):i)(function(i){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+i+'" is not supported')});var Ee=(i,r)=>{for(var s in r)k(i,s,{get:r[s],enumerable:!0})},X=(i,r,s,c)=>{if(r&&typeof r=="object"||typeof r=="function")for(let d of _e(r))!Pe.call(i,d)&&d!==s&&k(i,d,{get:()=>r[d],enumerable:!(c=Ie(r,d))||c.enumerable});return i};var ke=(i,r,s)=>(s=i!=null?Se(Ae(i)):{},X(r||!i||!i.__esModule?k(s,"default",{value:i,enumerable:!0}):s,i)),Ce=i=>X(k({},"__esModule",{value:!0}),i);var je={};Ee(je,{BalanceAnalytics:()=>Z,getAttribution:()=>Le,getConsent:()=>He,getFanIdHash:()=>Be,getSessionId:()=>Ue,hasConsent:()=>ze,identify:()=>Oe,page:()=>Ne,purchase:()=>Re,setConsent:()=>Fe,track:()=>De});var u=ke(Q("react")),C=Q("next/navigation");function Te(){let i=(0,C.usePathname)(),r=(0,C.useSearchParams)(),s=(0,u.useRef)(!0),c=(0,u.useRef)(null);return(0,u.useEffect)(()=>{if(typeof window>"u"||!window.balance)return;let d=i+(r?.toString()||"");if(c.current===d)return;if(s.current&&(s.current=!1,window._balanceInitialPageviewFired)){c.current=d,console.log("[BalanceAnalytics] Skipping initial pageview (script already fired)");return}let v=window.location.href,_=document.title;c.current=d,window.balance.page({url:v,title:_})},[i,r]),null}function Z(){return u.default.createElement(u.Suspense,{fallback:null},u.default.createElement(Te,null))}(function(){function i(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 r=null;function s(){if(!r)try{r=i(navigator.userAgent)}catch{r={device_type:"desktop",browser:"Unknown",os:"Unknown"}}return r}let c=document.currentScript,d=c?.dataset.artistId,v=c?.dataset.projectId,_=c?.dataset.emulator==="true",ee=c?.dataset.debug==="true",T=parseInt(c?.dataset.heartbeatInterval||"30000",10),H=c?.dataset.heartbeat!=="false";if(!d){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let z="session_id",D="session_timestamp",j="attribution",M="fan_id_hash",K="balance_consent",te=60*60*1e3,O="balance_",N=_?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint",A=null,f=null,l=null,m={},g=[],R=null,p="session",w=null,ne=0,U=0,b=0,x=!0,ie=2*60*1e3,J=Date.now(),S=!1,a=(...e)=>{ee&&console.log("[BALANCE Pixel]",...e)};function Y(){try{return p==="local"?localStorage:sessionStorage}catch{return null}}function P(e){let t=Y();if(!t)return null;try{let n=O+e,o=t.getItem(n);if(!o&&p==="session")try{o=localStorage.getItem(n)}catch{}return o}catch{return null}}function I(e,t){let n=Y();if(n)try{n.setItem(O+e,t)}catch{}}function B(){if(p!=="local"){a("Upgrading storage tier: session -> local");try{let e=[];for(let t=0;t<sessionStorage.length;t++){let n=sessionStorage.key(t);n?.startsWith(O)&&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);p="local",a(`Storage tier upgraded, migrated ${e.length} items`)}catch(e){console.error("[BALANCE Pixel] Storage migration failed:",e)}}}function q(){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 re(){try{let e=P(z),t=P(D);if(e&&t&&Date.now()-parseInt(t,10)<te)return I(D,Date.now().toString()),e;let n=q();return I(z,n),I(D,Date.now().toString()),n}catch{return q()}}function ae(){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 oe(){try{let e=P(j);if(e){m=JSON.parse(e),a("Loaded attribution:",m);return}let t=ae();Object.keys(t).length>0&&(m=t,I(j,JSON.stringify(t)),a("Captured attribution:",m))}catch{}}function se(){try{f=P(M)}catch{}}function le(){try{let e=localStorage.getItem(K);e&&(l=JSON.parse(e).preferences||null,a("Loaded consent:",l))}catch{}}function ce(e){let t=l;l=e;try{let o={preferences:e,method:"explicit",version:1};localStorage.setItem(K,JSON.stringify(o)),a("Consent saved:",e)}catch(o){console.error("[BALANCE Pixel] Could not save consent:",o)}e.analytics===!0&&B();let n=h({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});y(n)}function de(){return l}function ue(e){return l?.[e]===!0}async function fe(e){let t=e.toLowerCase().trim(),o=new TextEncoder().encode(t),F=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(F)).map(xe=>xe.toString(16).padStart(2,"0")).join("")}function h(e){let t=s(),n={artist_id:d,fan_session_id:A,fan_id_hash:f||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,...e,...m};return v&&!e.projectId&&(n.projectId=v),n}function y(e){g.push(e),a("Event queued:",e.event_name,"(queue:",g.length,")"),g.length>=10&&E()}async function E(){if(g.length===0)return;let e=[...g];g=[],a("Flushing",e.length,"events to",N);try{let t=await fetch(N,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0});if(!t.ok)throw new Error(`HTTP ${t.status}`);a("Events sent successfully")}catch(t){console.error("[BALANCE Pixel] Failed to send events:",t),g.length<50&&g.push(...e)}}function ge(){R&&clearInterval(R),R=window.setInterval(()=>{g.length>0&&E()},5e3)}function L(){b||(ne=Date.now()),b=Date.now(),x=!0,a("Active time tracking started/resumed")}function $(){b&&x&&(U+=Date.now()-b),x=!1,a("Active time tracking paused, accumulated:",U,"ms")}function pe(){let e=U;return x&&b&&(e+=Date.now()-b),e}function me(){J=Date.now(),S&&(S=!1,L(),a("User returned from idle"))}function G(){if(document.visibilityState==="hidden"){a("Skipping heartbeat - tab hidden");return}if(Date.now()-J>ie){S||(S=!0,$(),a("User idle - pausing heartbeat"));return}let e=pe(),t=Math.round(e/1e3),n=h({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:T,is_active:x&&!S}});y(n),a("Heartbeat sent:",t,"seconds active")}function we(){if(!H){a('Heartbeat disabled via data-heartbeat="false"');return}w&&clearInterval(w),L(),w=window.setInterval(()=>{G()},T),a("Heartbeat started with interval:",T,"ms")}function be(){w&&(clearInterval(w),w=null),H&&(G(),a("Heartbeat stopped, final time sent"))}function W(e={}){let t=h({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});y(t)}function he(e,t={}){let n=h({event_name:"custom",metadata:{event_type:e,...t}});y(n)}async function ye(e,t={}){try{if(l&&l.analytics===!1){a("Identify skipped - user declined analytics consent");return}f=await fe(e),l?.analytics===!0&&B(),I(M,f);let n=e.split("@"),o=n[0].charAt(0)+"***@"+(n[1]||"");a("Fan identified:",{name:t.name||"(no name)",email:o,hash:f.substring(0,16)+"...",traits:t,storageTier:p});let F=h({event_name:"identify",fan_id_hash:f,metadata:{email_sha256:f,traits:t,consent_preferences:l||void 0,storage_tier:p}});y(F)}catch(n){console.error("[BALANCE Pixel] Failed to identify:",n)}}function ve(e,t="USD",n={}){let o=h({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});y(o)}function V(){le(),l?.analytics===!0?(p="local",a("Storage tier: local (analytics consent granted)")):(p="session",a("Storage tier: session (privacy by default)")),A=re(),se(),l||(l={analytics:!0,marketing:!0,personalization:!0,timestamp:new Date().toISOString()},a("Default consent enabled (all tracking):",l),B()),oe(),ge(),a("Initialized",{artistId:d,projectId:v||"(none - will track to all projects)",sessionId:A,fanIdHash:f,consent:l,storageTier:p,useEmulator:_,endpoint:N}),window._balanceInitialPageviewFired=!0,W(),we(),["mousemove","keydown","scroll","touchstart"].forEach(e=>{document.addEventListener(e,me,{passive:!0})}),window.addEventListener("beforeunload",()=>{be(),E()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?($(),E()):L()})}window.balance={track:he,identify:ye,page:W,purchase:ve,getSessionId:()=>A,getFanIdHash:()=>f,getAttribution:()=>m,setConsent:ce,getConsent:de,hasConsent:ue},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",V):V(),a("Pixel script loaded")})();var De=(i,r)=>{typeof window>"u"||(window.balance?window.balance.track(i,r):console.warn("[Balance Pixel] track() called before pixel initialized:",i))},Oe=(i,r)=>typeof window>"u"?Promise.resolve():window.balance?window.balance.identify(i,r):(console.warn("[Balance Pixel] identify() called before pixel initialized"),Promise.resolve()),Ne=i=>{typeof window>"u"||(window.balance?window.balance.page(i):console.warn("[Balance Pixel] page() called before pixel initialized"))},Re=(i,r,s)=>{typeof window>"u"||(window.balance?window.balance.purchase(i,r,s):console.warn("[Balance Pixel] purchase() called before pixel initialized"))},Ue=()=>typeof window>"u"?null:window.balance?.getSessionId()??null,Be=()=>typeof window>"u"?null:window.balance?.getFanIdHash()??null,Le=()=>typeof window>"u"?{}:window.balance?.getAttribution()??{},Fe=i=>{typeof window>"u"||(window.balance?window.balance.setConsent(i):console.warn("[Balance Pixel] setConsent() called before pixel initialized"))},He=()=>typeof window>"u"?null:window.balance?.getConsent()??null,ze=i=>typeof window>"u"?!1:window.balance?.hasConsent(i)??!1;return Ce(je);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hifilabs/pixel",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "BALANCE Pixel - Lightweight browser tracking script for artist fan analytics",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.esm.js",