@hifilabs/pixel 0.1.4 → 0.1.6
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 +554 -0
- package/dist/browser.min.js +1 -0
- package/dist/index.js +600 -668
- package/package.json +10 -2
- package/dist/balance-pixel.js +0 -291
- package/dist/balance-pixel.min.js +0 -1
- package/dist/index.min.js +0 -1
- /package/dist/{index.esm.js → index.mjs} +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hifilabs/pixel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "BALANCE Pixel - Lightweight browser tracking script for artist fan analytics",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
|
-
"module": "./dist/index.
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.esm.d.ts",
|
|
8
|
+
"browser": "./dist/browser.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.esm.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
8
16
|
"publishConfig": {
|
|
9
17
|
"access": "restricted"
|
|
10
18
|
},
|
package/dist/balance-pixel.js
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
var BalancePixel = (() => {
|
|
2
|
-
// src/index.ts
|
|
3
|
-
(function() {
|
|
4
|
-
const currentScript = document.currentScript;
|
|
5
|
-
const artistId = currentScript?.dataset.artistId;
|
|
6
|
-
const useEmulator = currentScript?.dataset.emulator === "true";
|
|
7
|
-
const debug = currentScript?.dataset.debug === "true";
|
|
8
|
-
if (!artistId) {
|
|
9
|
-
console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const SESSION_KEY = "balance_session_id";
|
|
13
|
-
const SESSION_TIMESTAMP_KEY = "balance_session_timestamp";
|
|
14
|
-
const ATTRIBUTION_KEY = "balance_attribution";
|
|
15
|
-
const FAN_ID_KEY = "balance_fan_id_hash";
|
|
16
|
-
const CONSENT_STORAGE_KEY = "balance_consent";
|
|
17
|
-
const SESSION_DURATION = 30 * 60 * 1e3;
|
|
18
|
-
const API_ENDPOINT = useEmulator ? `http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint` : `https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint`;
|
|
19
|
-
let sessionId = null;
|
|
20
|
-
let fanIdHash = null;
|
|
21
|
-
let consent = null;
|
|
22
|
-
let attribution = {};
|
|
23
|
-
let eventQueue = [];
|
|
24
|
-
let flushTimer = null;
|
|
25
|
-
const log = (...args) => {
|
|
26
|
-
if (debug)
|
|
27
|
-
console.log("[BALANCE Pixel]", ...args);
|
|
28
|
-
};
|
|
29
|
-
function generateUUID() {
|
|
30
|
-
if (crypto && crypto.randomUUID) {
|
|
31
|
-
return crypto.randomUUID();
|
|
32
|
-
}
|
|
33
|
-
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
34
|
-
const r = Math.random() * 16 | 0;
|
|
35
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
36
|
-
return v.toString(16);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
function getOrCreateSession() {
|
|
40
|
-
try {
|
|
41
|
-
const stored = localStorage.getItem(SESSION_KEY);
|
|
42
|
-
const timestamp = localStorage.getItem(SESSION_TIMESTAMP_KEY);
|
|
43
|
-
if (stored && timestamp) {
|
|
44
|
-
const age = Date.now() - parseInt(timestamp, 10);
|
|
45
|
-
if (age < SESSION_DURATION) {
|
|
46
|
-
localStorage.setItem(SESSION_TIMESTAMP_KEY, Date.now().toString());
|
|
47
|
-
return stored;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const newId = generateUUID();
|
|
51
|
-
localStorage.setItem(SESSION_KEY, newId);
|
|
52
|
-
localStorage.setItem(SESSION_TIMESTAMP_KEY, Date.now().toString());
|
|
53
|
-
return newId;
|
|
54
|
-
} catch (e) {
|
|
55
|
-
return generateUUID();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function extractUTM() {
|
|
59
|
-
const params = new URLSearchParams(window.location.search);
|
|
60
|
-
const utm = {};
|
|
61
|
-
["source", "medium", "campaign", "content", "term"].forEach((param) => {
|
|
62
|
-
const value = params.get(`utm_${param}`);
|
|
63
|
-
if (value)
|
|
64
|
-
utm[`utm_${param}`] = value;
|
|
65
|
-
});
|
|
66
|
-
return utm;
|
|
67
|
-
}
|
|
68
|
-
function captureAttribution() {
|
|
69
|
-
try {
|
|
70
|
-
const stored = localStorage.getItem(ATTRIBUTION_KEY);
|
|
71
|
-
if (stored) {
|
|
72
|
-
attribution = JSON.parse(stored);
|
|
73
|
-
log("Loaded attribution:", attribution);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const utm = extractUTM();
|
|
77
|
-
if (Object.keys(utm).length > 0) {
|
|
78
|
-
attribution = utm;
|
|
79
|
-
localStorage.setItem(ATTRIBUTION_KEY, JSON.stringify(utm));
|
|
80
|
-
log("Captured attribution:", attribution);
|
|
81
|
-
}
|
|
82
|
-
} catch (e) {
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function loadFanId() {
|
|
86
|
-
try {
|
|
87
|
-
fanIdHash = localStorage.getItem(FAN_ID_KEY);
|
|
88
|
-
} catch (e) {
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function loadConsent() {
|
|
92
|
-
try {
|
|
93
|
-
const stored = localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
94
|
-
if (stored) {
|
|
95
|
-
const parsed = JSON.parse(stored);
|
|
96
|
-
consent = parsed.preferences || null;
|
|
97
|
-
log("Loaded consent:", consent);
|
|
98
|
-
}
|
|
99
|
-
} catch (e) {
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function setConsent(preferences) {
|
|
103
|
-
const previousConsent = consent;
|
|
104
|
-
consent = preferences;
|
|
105
|
-
try {
|
|
106
|
-
const storage = {
|
|
107
|
-
preferences,
|
|
108
|
-
method: "explicit",
|
|
109
|
-
version: 1
|
|
110
|
-
};
|
|
111
|
-
localStorage.setItem(CONSENT_STORAGE_KEY, JSON.stringify(storage));
|
|
112
|
-
log("Consent saved:", preferences);
|
|
113
|
-
} catch (e) {
|
|
114
|
-
console.error("[BALANCE Pixel] Could not save consent:", e);
|
|
115
|
-
}
|
|
116
|
-
const event = buildEvent({
|
|
117
|
-
event_name: "consent_updated",
|
|
118
|
-
metadata: {
|
|
119
|
-
consent_preferences: preferences,
|
|
120
|
-
consent_method: "explicit",
|
|
121
|
-
previous_consent: previousConsent || void 0
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
enqueueEvent(event);
|
|
125
|
-
}
|
|
126
|
-
function getConsent() {
|
|
127
|
-
return consent;
|
|
128
|
-
}
|
|
129
|
-
function hasConsent(type) {
|
|
130
|
-
return consent?.[type] === true;
|
|
131
|
-
}
|
|
132
|
-
async function hashEmail(email) {
|
|
133
|
-
const normalized = email.toLowerCase().trim();
|
|
134
|
-
const encoder = new TextEncoder();
|
|
135
|
-
const data = encoder.encode(normalized);
|
|
136
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
137
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
138
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
139
|
-
}
|
|
140
|
-
function buildEvent(partial) {
|
|
141
|
-
return {
|
|
142
|
-
artist_id: artistId,
|
|
143
|
-
fan_session_id: sessionId,
|
|
144
|
-
fan_id_hash: fanIdHash || void 0,
|
|
145
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
146
|
-
source_url: window.location.href,
|
|
147
|
-
referrer_url: document.referrer || void 0,
|
|
148
|
-
user_agent: navigator.userAgent,
|
|
149
|
-
...partial,
|
|
150
|
-
...attribution
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function enqueueEvent(event) {
|
|
154
|
-
const essentialEvents = ["identify", "consent_updated"];
|
|
155
|
-
const isEssential = essentialEvents.includes(event.event_name);
|
|
156
|
-
if (!isEssential && !hasConsent("analytics")) {
|
|
157
|
-
log(`Event '${event.event_name}' blocked - no analytics consent`);
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
eventQueue.push(event);
|
|
161
|
-
log("Event queued:", event.event_name, "(queue:", eventQueue.length, ")");
|
|
162
|
-
if (eventQueue.length >= 10) {
|
|
163
|
-
flush();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
async function flush() {
|
|
167
|
-
if (eventQueue.length === 0)
|
|
168
|
-
return;
|
|
169
|
-
const events = [...eventQueue];
|
|
170
|
-
eventQueue = [];
|
|
171
|
-
log("Flushing", events.length, "events to", API_ENDPOINT);
|
|
172
|
-
try {
|
|
173
|
-
const response = await fetch(API_ENDPOINT, {
|
|
174
|
-
method: "POST",
|
|
175
|
-
headers: { "Content-Type": "application/json" },
|
|
176
|
-
body: JSON.stringify({ events }),
|
|
177
|
-
keepalive: true
|
|
178
|
-
// Send even if page is closing
|
|
179
|
-
});
|
|
180
|
-
if (!response.ok) {
|
|
181
|
-
throw new Error(`HTTP ${response.status}`);
|
|
182
|
-
}
|
|
183
|
-
log("Events sent successfully");
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.error("[BALANCE Pixel] Failed to send events:", error);
|
|
186
|
-
if (eventQueue.length < 50) {
|
|
187
|
-
eventQueue.push(...events);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function startFlushTimer() {
|
|
192
|
-
if (flushTimer)
|
|
193
|
-
clearInterval(flushTimer);
|
|
194
|
-
flushTimer = window.setInterval(() => {
|
|
195
|
-
if (eventQueue.length > 0)
|
|
196
|
-
flush();
|
|
197
|
-
}, 5e3);
|
|
198
|
-
}
|
|
199
|
-
function trackPageView(options = {}) {
|
|
200
|
-
const event = buildEvent({
|
|
201
|
-
event_name: "pageview",
|
|
202
|
-
page_title: options.title || document.title,
|
|
203
|
-
source_url: options.url || window.location.href
|
|
204
|
-
});
|
|
205
|
-
enqueueEvent(event);
|
|
206
|
-
}
|
|
207
|
-
function track(eventName, properties = {}) {
|
|
208
|
-
const event = buildEvent({
|
|
209
|
-
event_name: "custom",
|
|
210
|
-
metadata: {
|
|
211
|
-
event_type: eventName,
|
|
212
|
-
...properties
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
enqueueEvent(event);
|
|
216
|
-
}
|
|
217
|
-
async function identify(email, traits = {}) {
|
|
218
|
-
try {
|
|
219
|
-
fanIdHash = await hashEmail(email);
|
|
220
|
-
localStorage.setItem(FAN_ID_KEY, fanIdHash);
|
|
221
|
-
log("Fan identified:", fanIdHash);
|
|
222
|
-
const event = buildEvent({
|
|
223
|
-
event_name: "identify",
|
|
224
|
-
fan_id_hash: fanIdHash,
|
|
225
|
-
metadata: {
|
|
226
|
-
email_sha256: fanIdHash,
|
|
227
|
-
traits,
|
|
228
|
-
consent_preferences: consent || void 0
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
enqueueEvent(event);
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error("[BALANCE Pixel] Failed to identify:", error);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
function purchase(revenue, currency = "USD", properties = {}) {
|
|
237
|
-
const event = buildEvent({
|
|
238
|
-
event_name: "purchase",
|
|
239
|
-
metadata: {
|
|
240
|
-
revenue,
|
|
241
|
-
currency,
|
|
242
|
-
...properties
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
enqueueEvent(event);
|
|
246
|
-
}
|
|
247
|
-
function init() {
|
|
248
|
-
sessionId = getOrCreateSession();
|
|
249
|
-
loadFanId();
|
|
250
|
-
loadConsent();
|
|
251
|
-
captureAttribution();
|
|
252
|
-
startFlushTimer();
|
|
253
|
-
log("Initialized", {
|
|
254
|
-
artistId,
|
|
255
|
-
sessionId,
|
|
256
|
-
fanIdHash,
|
|
257
|
-
consent,
|
|
258
|
-
useEmulator,
|
|
259
|
-
endpoint: API_ENDPOINT
|
|
260
|
-
});
|
|
261
|
-
trackPageView();
|
|
262
|
-
window.addEventListener("beforeunload", () => {
|
|
263
|
-
flush();
|
|
264
|
-
});
|
|
265
|
-
document.addEventListener("visibilitychange", () => {
|
|
266
|
-
if (document.visibilityState === "hidden") {
|
|
267
|
-
flush();
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
window.balance = {
|
|
272
|
-
track,
|
|
273
|
-
identify,
|
|
274
|
-
page: trackPageView,
|
|
275
|
-
purchase,
|
|
276
|
-
getSessionId: () => sessionId,
|
|
277
|
-
getFanIdHash: () => fanIdHash,
|
|
278
|
-
getAttribution: () => attribution,
|
|
279
|
-
setConsent,
|
|
280
|
-
getConsent,
|
|
281
|
-
hasConsent
|
|
282
|
-
};
|
|
283
|
-
if (document.readyState === "loading") {
|
|
284
|
-
document.addEventListener("DOMContentLoaded", init);
|
|
285
|
-
} else {
|
|
286
|
-
init();
|
|
287
|
-
}
|
|
288
|
-
log("Pixel script loaded");
|
|
289
|
-
})();
|
|
290
|
-
})();
|
|
291
|
-
//# sourceMappingURL=balance-pixel.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var BalancePixel=(()=>{(function(){let f=document.currentScript,m=f?.dataset.artistId,_=f?.dataset.emulator==="true",C=f?.dataset.debug==="true";if(!m){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let v="balance_session_id",p="balance_session_timestamp",x="balance_attribution",S="balance_fan_id_hash",I="balance_consent",P=30*60*1e3,h=_?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint",u=null,s=null,i=null,c={},a=[],y=null,r=(...e)=>{C&&console.log("[BALANCE Pixel]",...e)};function E(){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 N(){try{let e=localStorage.getItem(v),t=localStorage.getItem(p);if(e&&t&&Date.now()-parseInt(t,10)<P)return localStorage.setItem(p,Date.now().toString()),e;let n=E();return localStorage.setItem(v,n),localStorage.setItem(p,Date.now().toString()),n}catch{return E()}}function O(){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 T(){try{let e=localStorage.getItem(x);if(e){c=JSON.parse(e),r("Loaded attribution:",c);return}let t=O();Object.keys(t).length>0&&(c=t,localStorage.setItem(x,JSON.stringify(t)),r("Captured attribution:",c))}catch{}}function L(){try{s=localStorage.getItem(S)}catch{}}function R(){try{let e=localStorage.getItem(I);e&&(i=JSON.parse(e).preferences||null,r("Loaded consent:",i))}catch{}}function D(e){let t=i;i=e;try{let o={preferences:e,method:"explicit",version:1};localStorage.setItem(I,JSON.stringify(o)),r("Consent saved:",e)}catch(o){console.error("[BALANCE Pixel] Could not save consent:",o)}let n=l({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});d(n)}function U(){return i}function b(e){return i?.[e]===!0}async function k(e){let t=e.toLowerCase().trim(),o=new TextEncoder().encode(t),z=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(z)).map(J=>J.toString(16).padStart(2,"0")).join("")}function l(e){return{artist_id:m,fan_session_id:u,fan_id_hash:s||void 0,timestamp:new Date().toISOString(),source_url:window.location.href,referrer_url:document.referrer||void 0,user_agent:navigator.userAgent,...e,...c}}function d(e){if(!["identify","consent_updated"].includes(e.event_name)&&!b("analytics")){r(`Event '${e.event_name}' blocked - no analytics consent`);return}a.push(e),r("Event queued:",e.event_name,"(queue:",a.length,")"),a.length>=10&&g()}async function g(){if(a.length===0)return;let e=[...a];a=[],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){console.error("[BALANCE Pixel] Failed to send events:",t),a.length<50&&a.push(...e)}}function B(){y&&clearInterval(y),y=window.setInterval(()=>{a.length>0&&g()},5e3)}function w(e={}){let t=l({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});d(t)}function F(e,t={}){let n=l({event_name:"custom",metadata:{event_type:e,...t}});d(n)}async function H(e,t={}){try{s=await k(e),localStorage.setItem(S,s),r("Fan identified:",s);let n=l({event_name:"identify",fan_id_hash:s,metadata:{email_sha256:s,traits:t,consent_preferences:i||void 0}});d(n)}catch(n){console.error("[BALANCE Pixel] Failed to identify:",n)}}function M(e,t="USD",n={}){let o=l({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});d(o)}function A(){u=N(),L(),R(),T(),B(),r("Initialized",{artistId:m,sessionId:u,fanIdHash:s,consent:i,useEmulator:_,endpoint:h}),w(),window.addEventListener("beforeunload",()=>{g()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&g()})}window.balance={track:F,identify:H,page:w,purchase:M,getSessionId:()=>u,getFanIdHash:()=>s,getAttribution:()=>c,setConsent:D,getConsent:U,hasConsent:b},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",A):A(),r("Pixel script loaded")})();})();
|
package/dist/index.min.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var BalancePixel=(()=>{var Ae=Object.create;var T=Object.defineProperty;var ke=Object.getOwnPropertyDescriptor;var Pe=Object.getOwnPropertyNames;var Ee=Object.getPrototypeOf,Te=Object.prototype.hasOwnProperty;var Z=(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 Ce=(i,r)=>{for(var s in r)T(i,s,{get:r[s],enumerable:!0})},ee=(i,r,s,l)=>{if(r&&typeof r=="object"||typeof r=="function")for(let d of Pe(r))!Te.call(i,d)&&d!==s&&T(i,d,{get:()=>r[d],enumerable:!(l=ke(r,d))||l.enumerable});return i};var De=(i,r,s)=>(s=i!=null?Ae(Ee(i)):{},ee(r||!i||!i.__esModule?T(s,"default",{value:i,enumerable:!0}):s,i)),Oe=i=>ee(T({},"__esModule",{value:!0}),i);var Je={};Ce(Je,{BalanceAnalytics:()=>te,getAttribution:()=>ze,getConsent:()=>Me,getFanIdHash:()=>He,getSessionId:()=>Fe,hasConsent:()=>Ke,identify:()=>Re,page:()=>Ue,purchase:()=>Be,setConsent:()=>je,track:()=>Ne});var u=De(Z("react")),C=Z("next/navigation");function Le(){let i=(0,C.usePathname)(),r=(0,C.useSearchParams)(),s=(0,u.useRef)(!0),l=(0,u.useRef)(null);return(0,u.useEffect)(()=>{if(typeof window>"u"||!window.balance)return;let d=i+(r?.toString()||"");if(l.current===d)return;if(s.current&&(s.current=!1,window._balanceInitialPageviewFired)){l.current=d,console.log("[BalanceAnalytics] Skipping initial pageview (script already fired)");return}let v=window.location.href,_=document.title;l.current=d,window.balance.page({url:v,title:_})},[i,r]),null}function te(){return u.default.createElement(u.Suspense,{fallback:null},u.default.createElement(Le,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 l=document.currentScript,d=l?.dataset.artistId,v=l?.dataset.projectId,_=l?.dataset.emulator==="true",ne=l?.dataset.debug==="true",D=parseInt(l?.dataset.heartbeatInterval||"30000",10),z=l?.dataset.heartbeat!=="false",j=l?.dataset.source;function ie(){if(j)return j;let e=typeof window.dataLayer<"u"&&Array.isArray(window.dataLayer),t=typeof window.gtag=="function";return e&&t?"gtm":"pixel"}let A="pixel";if(!d){console.error("[BALANCE Pixel] Error: data-artist-id attribute is required");return}let M="session_id",O="session_timestamp",K="attribution",J="fan_id_hash",Y="balance_consent",re=60*60*1e3,L="balance_",N=_?"http://localhost:5001/artist-os-distro/us-central1/pixelEndpoint":"https://us-central1-artist-os-distro.cloudfunctions.net/pixelEndpoint",k=null,f=null,c=null,m={},g=[],R=null,p="session",w=null,ae=0,U=0,y=0,x=!0,oe=2*60*1e3,q=Date.now(),S=!1,a=(...e)=>{ne&&console.log("[BALANCE Pixel]",...e)};function G(){try{return p==="local"?localStorage:sessionStorage}catch{return null}}function P(e){let t=G();if(!t)return null;try{let n=L+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=G();if(n)try{n.setItem(L+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(L)&&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 $(){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 se(){try{let e=P(M),t=P(O);if(e&&t&&Date.now()-parseInt(t,10)<re)return I(O,Date.now().toString()),e;let n=$();return I(M,n),I(O,Date.now().toString()),n}catch{return $()}}function ce(){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 le(){try{let e=P(K);if(e){m=JSON.parse(e),a("Loaded attribution:",m);return}let t=ce();Object.keys(t).length>0&&(m=t,I(K,JSON.stringify(t)),a("Captured attribution:",m))}catch{}}function de(){try{f=P(J)}catch{}}function ue(){try{let e=localStorage.getItem(Y);e&&(c=JSON.parse(e).preferences||null,a("Loaded consent:",c))}catch{}}function fe(e){let t=c;c=e;try{let o={preferences:e,method:"explicit",version:1};localStorage.setItem(Y,JSON.stringify(o)),a("Consent saved:",e)}catch(o){console.error("[BALANCE Pixel] Could not save consent:",o)}e.analytics===!0&&B();let n=b({event_name:"consent_updated",metadata:{consent_preferences:e,consent_method:"explicit",previous_consent:t||void 0}});h(n)}function ge(){return c}function pe(e){return c?.[e]===!0}async function me(e){let t=e.toLowerCase().trim(),o=new TextEncoder().encode(t),H=await crypto.subtle.digest("SHA-256",o);return Array.from(new Uint8Array(H)).map(_e=>_e.toString(16).padStart(2,"0")).join("")}function b(e){let t=s(),n={artist_id:d,fan_session_id:k,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,tracking_source:A,...e,...m};return v&&!e.projectId&&(n.projectId=v),n}function h(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 we(){R&&clearInterval(R),R=window.setInterval(()=>{g.length>0&&E()},5e3)}function F(){y||(ae=Date.now()),y=Date.now(),x=!0,a("Active time tracking started/resumed")}function W(){y&&x&&(U+=Date.now()-y),x=!1,a("Active time tracking paused, accumulated:",U,"ms")}function ye(){let e=U;return x&&y&&(e+=Date.now()-y),e}function be(){q=Date.now(),S&&(S=!1,F(),a("User returned from idle"))}function V(){if(document.visibilityState==="hidden"){a("Skipping heartbeat - tab hidden");return}if(Date.now()-q>oe){S||(S=!0,W(),a("User idle - pausing heartbeat"));return}let e=ye(),t=Math.round(e/1e3),n=b({event_name:"engagement_heartbeat",metadata:{time_on_page_seconds:t,time_on_page_ms:e,heartbeat_interval_ms:D,is_active:x&&!S}});h(n),a("Heartbeat sent:",t,"seconds active")}function he(){if(!z){a('Heartbeat disabled via data-heartbeat="false"');return}w&&clearInterval(w),F(),w=window.setInterval(()=>{V()},D),a("Heartbeat started with interval:",D,"ms")}function ve(){w&&(clearInterval(w),w=null),z&&(V(),a("Heartbeat stopped, final time sent"))}function Q(e={}){let t=b({event_name:"pageview",page_title:e.title||document.title,source_url:e.url||window.location.href});h(t)}function xe(e,t={}){let n=b({event_name:"custom",metadata:{event_type:e,...t}});h(n)}async function Se(e,t={}){try{if(c&&c.analytics===!1){a("Identify skipped - user declined analytics consent");return}f=await me(e),c?.analytics===!0&&B(),I(J,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 H=b({event_name:"identify",fan_id_hash:f,metadata:{email_sha256:f,traits:t,consent_preferences:c||void 0,storage_tier:p}});h(H)}catch(n){console.error("[BALANCE Pixel] Failed to identify:",n)}}function Ie(e,t="USD",n={}){let o=b({event_name:"purchase",metadata:{revenue:e,currency:t,...n}});h(o)}function X(){A=ie(),a("Tracking source detected:",A),ue(),c?.analytics===!0?(p="local",a("Storage tier: local (analytics consent granted)")):(p="session",a("Storage tier: session (privacy by default)")),k=se(),de(),c||(c={analytics:!0,marketing:!0,personalization:!0,timestamp:new Date().toISOString()},a("Default consent enabled (all tracking):",c),B()),le(),we(),a("Initialized",{artistId:d,projectId:v||"(none - will track to all projects)",sessionId:k,fanIdHash:f,consent:c,storageTier:p,trackingSource:A,useEmulator:_,endpoint:N}),window._balanceInitialPageviewFired=!0,Q(),he(),["mousemove","keydown","scroll","touchstart"].forEach(e=>{document.addEventListener(e,be,{passive:!0})}),window.addEventListener("beforeunload",()=>{ve(),E()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?(W(),E()):F()})}window.balance={track:xe,identify:Se,page:Q,purchase:Ie,getSessionId:()=>k,getFanIdHash:()=>f,getAttribution:()=>m,setConsent:fe,getConsent:ge,hasConsent:pe},document.readyState==="loading"?document.addEventListener("DOMContentLoaded",X):X(),a("Pixel script loaded")})();var Ne=(i,r)=>{typeof window>"u"||(window.balance?window.balance.track(i,r):console.warn("[Balance Pixel] track() called before pixel initialized:",i))},Re=(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()),Ue=i=>{typeof window>"u"||(window.balance?window.balance.page(i):console.warn("[Balance Pixel] page() called before pixel initialized"))},Be=(i,r,s)=>{typeof window>"u"||(window.balance?window.balance.purchase(i,r,s):console.warn("[Balance Pixel] purchase() called before pixel initialized"))},Fe=()=>typeof window>"u"?null:window.balance?.getSessionId()??null,He=()=>typeof window>"u"?null:window.balance?.getFanIdHash()??null,ze=()=>typeof window>"u"?{}:window.balance?.getAttribution()??{},je=i=>{typeof window>"u"||(window.balance?window.balance.setConsent(i):console.warn("[Balance Pixel] setConsent() called before pixel initialized"))},Me=()=>typeof window>"u"?null:window.balance?.getConsent()??null,Ke=i=>typeof window>"u"?!1:window.balance?.hasConsent(i)??!1;return Oe(Je);})();
|
|
File without changes
|