@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/dist/index.js
CHANGED
|
@@ -1,711 +1,643 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
-
};
|
|
19
|
-
var __copyProps = (to, from, except, desc) => {
|
|
20
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
-
for (let key of __getOwnPropNames(from))
|
|
22
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
-
}
|
|
25
|
-
return to;
|
|
26
|
-
};
|
|
27
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
28
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
29
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
30
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
31
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
32
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
33
|
-
mod
|
|
34
|
-
));
|
|
35
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
-
|
|
37
|
-
// src/index.ts
|
|
38
|
-
var src_exports = {};
|
|
39
|
-
__export(src_exports, {
|
|
40
|
-
BalanceAnalytics: () => BalanceAnalytics,
|
|
41
|
-
getAttribution: () => getAttribution,
|
|
42
|
-
getConsent: () => getConsent,
|
|
43
|
-
getFanIdHash: () => getFanIdHash,
|
|
44
|
-
getSessionId: () => getSessionId,
|
|
45
|
-
hasConsent: () => hasConsent,
|
|
46
|
-
identify: () => identify,
|
|
47
|
-
page: () => page,
|
|
48
|
-
purchase: () => purchase,
|
|
49
|
-
setConsent: () => setConsent,
|
|
50
|
-
track: () => track
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// src/react/BalanceAnalytics.tsx
|
|
54
|
-
var import_react = __toESM(__require("react"));
|
|
55
|
-
var import_navigation = __require("next/navigation");
|
|
56
|
-
function BalanceAnalyticsInner() {
|
|
57
|
-
const pathname = (0, import_navigation.usePathname)();
|
|
58
|
-
const searchParams = (0, import_navigation.useSearchParams)();
|
|
59
|
-
const isFirstRender = (0, import_react.useRef)(true);
|
|
60
|
-
const lastTrackedPath = (0, import_react.useRef)(null);
|
|
61
|
-
(0, import_react.useEffect)(() => {
|
|
62
|
-
if (typeof window === "undefined" || !window.balance)
|
|
63
|
-
return;
|
|
64
|
-
const currentPath = pathname + (searchParams?.toString() || "");
|
|
65
|
-
if (lastTrackedPath.current === currentPath) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (isFirstRender.current) {
|
|
69
|
-
isFirstRender.current = false;
|
|
70
|
-
if (window._balanceInitialPageviewFired) {
|
|
71
|
-
lastTrackedPath.current = currentPath;
|
|
72
|
-
console.log("[BalanceAnalytics] Skipping initial pageview (script already fired)");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
const url = window.location.href;
|
|
77
|
-
const title = document.title;
|
|
78
|
-
lastTrackedPath.current = currentPath;
|
|
79
|
-
window.balance.page({ url, title });
|
|
80
|
-
}, [pathname, searchParams]);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
function BalanceAnalytics() {
|
|
84
|
-
return /* @__PURE__ */ import_react.default.createElement(import_react.Suspense, { fallback: null }, /* @__PURE__ */ import_react.default.createElement(BalanceAnalyticsInner, null));
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
85
17
|
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var __publicField = (obj, key, value) => {
|
|
30
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
86
33
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
34
|
+
// src/index.ts
|
|
35
|
+
var src_exports = {};
|
|
36
|
+
__export(src_exports, {
|
|
37
|
+
BalanceAnalytics: () => BalanceAnalytics,
|
|
38
|
+
DEFAULT_GTM_CONSENT: () => DEFAULT_GTM_CONSENT,
|
|
39
|
+
GTMProvider: () => GTMProvider,
|
|
40
|
+
StorageManager: () => StorageManager,
|
|
41
|
+
getAttribution: () => getAttribution,
|
|
42
|
+
getConsent: () => getConsent,
|
|
43
|
+
getFanIdHash: () => getFanIdHash,
|
|
44
|
+
getSessionId: () => getSessionId,
|
|
45
|
+
getStorageManager: () => getStorageManager,
|
|
46
|
+
hasConsent: () => hasConsent,
|
|
47
|
+
identify: () => identify,
|
|
48
|
+
initStorageWithConsent: () => initStorageWithConsent,
|
|
49
|
+
page: () => page,
|
|
50
|
+
purchase: () => purchase,
|
|
51
|
+
setConsent: () => setConsent,
|
|
52
|
+
track: () => track,
|
|
53
|
+
useBalanceIdentify: () => useBalanceIdentify,
|
|
54
|
+
useGTMConsent: () => useGTMConsent
|
|
55
|
+
});
|
|
56
|
+
module.exports = __toCommonJS(src_exports);
|
|
57
|
+
|
|
58
|
+
// src/react/BalanceAnalytics.tsx
|
|
59
|
+
var import_react = __toESM(require("react"));
|
|
60
|
+
var import_navigation = require("next/navigation");
|
|
61
|
+
function BalanceAnalyticsInner() {
|
|
62
|
+
const pathname = (0, import_navigation.usePathname)();
|
|
63
|
+
const searchParams = (0, import_navigation.useSearchParams)();
|
|
64
|
+
const isFirstRender = (0, import_react.useRef)(true);
|
|
65
|
+
const lastTrackedPath = (0, import_react.useRef)(null);
|
|
66
|
+
(0, import_react.useEffect)(() => {
|
|
67
|
+
if (typeof window === "undefined" || !window.balance)
|
|
68
|
+
return;
|
|
69
|
+
const currentPath = pathname + (searchParams?.toString() || "");
|
|
70
|
+
if (lastTrackedPath.current === currentPath) {
|
|
71
|
+
return;
|
|
120
72
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
cachedDeviceInfo = { device_type: "desktop", browser: "Unknown", os: "Unknown" };
|
|
128
|
-
}
|
|
73
|
+
if (isFirstRender.current) {
|
|
74
|
+
isFirstRender.current = false;
|
|
75
|
+
if (window._balanceInitialPageviewFired) {
|
|
76
|
+
lastTrackedPath.current = currentPath;
|
|
77
|
+
console.log("[BalanceAnalytics] Skipping initial pageview (script already fired)");
|
|
78
|
+
return;
|
|
129
79
|
}
|
|
130
|
-
return cachedDeviceInfo;
|
|
131
80
|
}
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
81
|
+
const url = window.location.href;
|
|
82
|
+
const title = document.title;
|
|
83
|
+
lastTrackedPath.current = currentPath;
|
|
84
|
+
window.balance.page({ url, title });
|
|
85
|
+
}, [pathname, searchParams]);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
function BalanceAnalytics() {
|
|
89
|
+
return /* @__PURE__ */ import_react.default.createElement(import_react.Suspense, { fallback: null }, /* @__PURE__ */ import_react.default.createElement(BalanceAnalyticsInner, null));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/react/useBalanceIdentify.ts
|
|
93
|
+
var import_react2 = require("react");
|
|
94
|
+
|
|
95
|
+
// src/storage/StorageManager.ts
|
|
96
|
+
var DEFAULT_PREFIX = "balance_";
|
|
97
|
+
var StorageManager = class {
|
|
98
|
+
constructor(config) {
|
|
99
|
+
__publicField(this, "prefix");
|
|
100
|
+
__publicField(this, "currentTier");
|
|
101
|
+
this.prefix = config?.prefix ?? DEFAULT_PREFIX;
|
|
102
|
+
this.currentTier = config?.tier ?? "session";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the appropriate storage based on current tier
|
|
106
|
+
*/
|
|
107
|
+
getStorage() {
|
|
108
|
+
if (typeof window === "undefined")
|
|
109
|
+
return null;
|
|
110
|
+
try {
|
|
111
|
+
return this.currentTier === "local" ? localStorage : sessionStorage;
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
149
114
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get current storage tier
|
|
118
|
+
*/
|
|
119
|
+
getTier() {
|
|
120
|
+
return this.currentTier;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Upgrade storage tier (e.g., when user gives consent)
|
|
124
|
+
* Automatically migrates data from sessionStorage to localStorage
|
|
125
|
+
*/
|
|
126
|
+
upgradeTier(newTier) {
|
|
127
|
+
if (typeof window === "undefined")
|
|
153
128
|
return;
|
|
129
|
+
if (this.currentTier === newTier)
|
|
130
|
+
return;
|
|
131
|
+
const oldTier = this.currentTier;
|
|
132
|
+
if (oldTier === "session" && newTier === "local") {
|
|
133
|
+
this.migrateToLocal();
|
|
154
134
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
let heartbeatTimer = null;
|
|
171
|
-
let pageStartTime = 0;
|
|
172
|
-
let activeTime = 0;
|
|
173
|
-
let lastActiveTimestamp = 0;
|
|
174
|
-
let isPageVisible = true;
|
|
175
|
-
const IDLE_TIMEOUT = 2 * 60 * 1e3;
|
|
176
|
-
let lastActivityTime = Date.now();
|
|
177
|
-
let isIdle = false;
|
|
178
|
-
const log = (...args) => {
|
|
179
|
-
if (debug)
|
|
180
|
-
console.log("[BALANCE Pixel]", ...args);
|
|
181
|
-
};
|
|
182
|
-
function getStorage() {
|
|
183
|
-
try {
|
|
184
|
-
return currentStorageTier === "local" ? localStorage : sessionStorage;
|
|
185
|
-
} catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
135
|
+
this.currentTier = newTier;
|
|
136
|
+
console.log(`[StorageManager] Upgraded tier: ${oldTier} -> ${newTier}`);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Downgrade storage tier (e.g., when user revokes consent)
|
|
140
|
+
* Clears localStorage data and falls back to sessionStorage
|
|
141
|
+
*/
|
|
142
|
+
downgradeTier(newTier) {
|
|
143
|
+
if (typeof window === "undefined")
|
|
144
|
+
return;
|
|
145
|
+
if (this.currentTier === newTier)
|
|
146
|
+
return;
|
|
147
|
+
const oldTier = this.currentTier;
|
|
148
|
+
if (oldTier === "local" && newTier === "session") {
|
|
149
|
+
this.clearLocalStorage();
|
|
188
150
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
151
|
+
this.currentTier = newTier;
|
|
152
|
+
console.log(`[StorageManager] Downgraded tier: ${oldTier} -> ${newTier}`);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Migrate all prefixed data from sessionStorage to localStorage
|
|
156
|
+
*/
|
|
157
|
+
migrateToLocal() {
|
|
158
|
+
if (typeof window === "undefined")
|
|
159
|
+
return;
|
|
160
|
+
try {
|
|
161
|
+
const keysToMigrate = [];
|
|
162
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
163
|
+
const key = sessionStorage.key(i);
|
|
164
|
+
if (key?.startsWith(this.prefix)) {
|
|
165
|
+
keysToMigrate.push(key);
|
|
201
166
|
}
|
|
202
|
-
return value;
|
|
203
|
-
} catch {
|
|
204
|
-
return null;
|
|
205
167
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
storage.setItem(STORAGE_PREFIX + key, value);
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
function upgradeStorageTier() {
|
|
217
|
-
if (currentStorageTier === "local")
|
|
218
|
-
return;
|
|
219
|
-
log("Upgrading storage tier: session -> local");
|
|
220
|
-
try {
|
|
221
|
-
const keysToMigrate = [];
|
|
222
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
223
|
-
const key = sessionStorage.key(i);
|
|
224
|
-
if (key?.startsWith(STORAGE_PREFIX)) {
|
|
225
|
-
keysToMigrate.push(key);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (const key of keysToMigrate) {
|
|
229
|
-
const value = sessionStorage.getItem(key);
|
|
230
|
-
if (value) {
|
|
231
|
-
localStorage.setItem(key, value);
|
|
232
|
-
}
|
|
168
|
+
for (const key of keysToMigrate) {
|
|
169
|
+
const value = sessionStorage.getItem(key);
|
|
170
|
+
if (value) {
|
|
171
|
+
localStorage.setItem(key, value);
|
|
172
|
+
console.log(`[StorageManager] Migrated: ${key}`);
|
|
233
173
|
}
|
|
234
|
-
for (const key of keysToMigrate) {
|
|
235
|
-
sessionStorage.removeItem(key);
|
|
236
|
-
}
|
|
237
|
-
currentStorageTier = "local";
|
|
238
|
-
log(`Storage tier upgraded, migrated ${keysToMigrate.length} items`);
|
|
239
|
-
} catch (error) {
|
|
240
|
-
console.error("[BALANCE Pixel] Storage migration failed:", error);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
function generateUUID() {
|
|
244
|
-
if (crypto && crypto.randomUUID) {
|
|
245
|
-
return crypto.randomUUID();
|
|
246
174
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
250
|
-
return v.toString(16);
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
function getOrCreateSession() {
|
|
254
|
-
try {
|
|
255
|
-
const stored = storageGet(SESSION_KEY);
|
|
256
|
-
const timestamp = storageGet(SESSION_TIMESTAMP_KEY);
|
|
257
|
-
if (stored && timestamp) {
|
|
258
|
-
const age = Date.now() - parseInt(timestamp, 10);
|
|
259
|
-
if (age < SESSION_DURATION) {
|
|
260
|
-
storageSet(SESSION_TIMESTAMP_KEY, Date.now().toString());
|
|
261
|
-
return stored;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const newId = generateUUID();
|
|
265
|
-
storageSet(SESSION_KEY, newId);
|
|
266
|
-
storageSet(SESSION_TIMESTAMP_KEY, Date.now().toString());
|
|
267
|
-
return newId;
|
|
268
|
-
} catch (e) {
|
|
269
|
-
return generateUUID();
|
|
175
|
+
for (const key of keysToMigrate) {
|
|
176
|
+
sessionStorage.removeItem(key);
|
|
270
177
|
}
|
|
178
|
+
console.log(`[StorageManager] Migration complete: ${keysToMigrate.length} items`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error("[StorageManager] Migration failed:", error);
|
|
271
181
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (stored) {
|
|
286
|
-
attribution = JSON.parse(stored);
|
|
287
|
-
log("Loaded attribution:", attribution);
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const utm = extractUTM();
|
|
291
|
-
if (Object.keys(utm).length > 0) {
|
|
292
|
-
attribution = utm;
|
|
293
|
-
storageSet(ATTRIBUTION_KEY, JSON.stringify(utm));
|
|
294
|
-
log("Captured attribution:", attribution);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Clear all prefixed data from localStorage
|
|
185
|
+
*/
|
|
186
|
+
clearLocalStorage() {
|
|
187
|
+
if (typeof window === "undefined")
|
|
188
|
+
return;
|
|
189
|
+
try {
|
|
190
|
+
const keysToRemove = [];
|
|
191
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
192
|
+
const key = localStorage.key(i);
|
|
193
|
+
if (key?.startsWith(this.prefix)) {
|
|
194
|
+
keysToRemove.push(key);
|
|
295
195
|
}
|
|
296
|
-
} catch (e) {
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
function loadFanId() {
|
|
300
|
-
try {
|
|
301
|
-
fanIdHash = storageGet(FAN_ID_KEY);
|
|
302
|
-
} catch (e) {
|
|
303
196
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
try {
|
|
307
|
-
const stored = localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
308
|
-
if (stored) {
|
|
309
|
-
const parsed = JSON.parse(stored);
|
|
310
|
-
consent = parsed.preferences || null;
|
|
311
|
-
log("Loaded consent:", consent);
|
|
312
|
-
}
|
|
313
|
-
} catch (e) {
|
|
197
|
+
for (const key of keysToRemove) {
|
|
198
|
+
localStorage.removeItem(key);
|
|
314
199
|
}
|
|
200
|
+
console.log(`[StorageManager] Cleared ${keysToRemove.length} items from localStorage`);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error("[StorageManager] Clear failed:", error);
|
|
315
203
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
upgradeStorageTier();
|
|
332
|
-
}
|
|
333
|
-
const event = buildEvent({
|
|
334
|
-
event_name: "consent_updated",
|
|
335
|
-
metadata: {
|
|
336
|
-
consent_preferences: preferences,
|
|
337
|
-
consent_method: "explicit",
|
|
338
|
-
previous_consent: previousConsent || void 0
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get item from current storage tier
|
|
207
|
+
*/
|
|
208
|
+
getItem(key) {
|
|
209
|
+
const storage = this.getStorage();
|
|
210
|
+
if (!storage)
|
|
211
|
+
return null;
|
|
212
|
+
try {
|
|
213
|
+
const fullKey = this.prefix + key;
|
|
214
|
+
let value = storage.getItem(fullKey);
|
|
215
|
+
if (!value && this.currentTier === "session") {
|
|
216
|
+
try {
|
|
217
|
+
value = localStorage.getItem(fullKey);
|
|
218
|
+
} catch {
|
|
339
219
|
}
|
|
340
|
-
});
|
|
341
|
-
enqueueEvent(event);
|
|
342
|
-
}
|
|
343
|
-
function getConsent2() {
|
|
344
|
-
return consent;
|
|
345
|
-
}
|
|
346
|
-
function hasConsent2(type) {
|
|
347
|
-
return consent?.[type] === true;
|
|
348
|
-
}
|
|
349
|
-
async function hashEmail(email) {
|
|
350
|
-
const normalized = email.toLowerCase().trim();
|
|
351
|
-
const encoder = new TextEncoder();
|
|
352
|
-
const data = encoder.encode(normalized);
|
|
353
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
354
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
355
|
-
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
356
|
-
}
|
|
357
|
-
function buildEvent(partial) {
|
|
358
|
-
const deviceInfo = getDeviceInfo();
|
|
359
|
-
const base = {
|
|
360
|
-
artist_id: artistId,
|
|
361
|
-
fan_session_id: sessionId,
|
|
362
|
-
fan_id_hash: fanIdHash || void 0,
|
|
363
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
364
|
-
source_url: window.location.href,
|
|
365
|
-
referrer_url: document.referrer || void 0,
|
|
366
|
-
user_agent: navigator.userAgent,
|
|
367
|
-
// Device info (parsed client-side)
|
|
368
|
-
device_type: deviceInfo.device_type,
|
|
369
|
-
browser: deviceInfo.browser,
|
|
370
|
-
os: deviceInfo.os,
|
|
371
|
-
// Tracking source (gtm, pixel, etc.) - enables filtering in artistHQ
|
|
372
|
-
tracking_source: trackingSource,
|
|
373
|
-
...partial,
|
|
374
|
-
...attribution
|
|
375
|
-
};
|
|
376
|
-
if (projectId && !partial.projectId) {
|
|
377
|
-
base.projectId = projectId;
|
|
378
|
-
}
|
|
379
|
-
return base;
|
|
380
|
-
}
|
|
381
|
-
function enqueueEvent(event) {
|
|
382
|
-
eventQueue.push(event);
|
|
383
|
-
log("Event queued:", event.event_name, "(queue:", eventQueue.length, ")");
|
|
384
|
-
if (eventQueue.length >= 10) {
|
|
385
|
-
flush();
|
|
386
220
|
}
|
|
221
|
+
return value;
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
387
224
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
});
|
|
402
|
-
if (!response.ok) {
|
|
403
|
-
throw new Error(`HTTP ${response.status}`);
|
|
404
|
-
}
|
|
405
|
-
log("Events sent successfully");
|
|
406
|
-
} catch (error) {
|
|
407
|
-
console.error("[BALANCE Pixel] Failed to send events:", error);
|
|
408
|
-
if (eventQueue.length < 50) {
|
|
409
|
-
eventQueue.push(...events);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Set item in current storage tier
|
|
228
|
+
*/
|
|
229
|
+
setItem(key, value) {
|
|
230
|
+
const storage = this.getStorage();
|
|
231
|
+
if (!storage)
|
|
232
|
+
return;
|
|
233
|
+
try {
|
|
234
|
+
const fullKey = this.prefix + key;
|
|
235
|
+
storage.setItem(fullKey, value);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
console.error("[StorageManager] setItem failed:", error);
|
|
412
238
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Remove item from all storage tiers
|
|
242
|
+
*/
|
|
243
|
+
removeItem(key) {
|
|
244
|
+
if (typeof window === "undefined")
|
|
245
|
+
return;
|
|
246
|
+
const fullKey = this.prefix + key;
|
|
247
|
+
try {
|
|
248
|
+
sessionStorage.removeItem(fullKey);
|
|
249
|
+
} catch {
|
|
420
250
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
lastActiveTimestamp = Date.now();
|
|
426
|
-
isPageVisible = true;
|
|
427
|
-
log("Active time tracking started/resumed");
|
|
251
|
+
try {
|
|
252
|
+
localStorage.removeItem(fullKey);
|
|
253
|
+
} catch {
|
|
428
254
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get JSON-parsed item
|
|
258
|
+
*/
|
|
259
|
+
getJSON(key) {
|
|
260
|
+
const value = this.getItem(key);
|
|
261
|
+
if (!value)
|
|
262
|
+
return null;
|
|
263
|
+
try {
|
|
264
|
+
return JSON.parse(value);
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
435
267
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Set JSON-stringified item
|
|
271
|
+
*/
|
|
272
|
+
setJSON(key, value) {
|
|
273
|
+
try {
|
|
274
|
+
this.setItem(key, JSON.stringify(value));
|
|
275
|
+
} catch {
|
|
442
276
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
var storageManagerInstance = null;
|
|
280
|
+
function getStorageManager() {
|
|
281
|
+
if (!storageManagerInstance) {
|
|
282
|
+
storageManagerInstance = new StorageManager();
|
|
283
|
+
}
|
|
284
|
+
return storageManagerInstance;
|
|
285
|
+
}
|
|
286
|
+
function initStorageWithConsent(hasAnalyticsConsent) {
|
|
287
|
+
const manager = getStorageManager();
|
|
288
|
+
if (hasAnalyticsConsent) {
|
|
289
|
+
manager.upgradeTier("local");
|
|
290
|
+
} else {
|
|
291
|
+
manager.downgradeTier("session");
|
|
292
|
+
}
|
|
293
|
+
return manager;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/react/useBalanceIdentify.ts
|
|
297
|
+
var PENDING_IDENTIFY_KEY = "pending_identify";
|
|
298
|
+
var MAX_PENDING_AGE_MS = 30 * 60 * 1e3;
|
|
299
|
+
function checkAnalyticsConsent() {
|
|
300
|
+
if (typeof window === "undefined")
|
|
301
|
+
return false;
|
|
302
|
+
try {
|
|
303
|
+
if (window.balance?.hasConsent) {
|
|
304
|
+
return window.balance.hasConsent("analytics");
|
|
305
|
+
}
|
|
306
|
+
const stored = localStorage.getItem("balance_consent");
|
|
307
|
+
if (stored) {
|
|
308
|
+
const consent = JSON.parse(stored);
|
|
309
|
+
return consent?.preferences?.analytics !== false;
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
} catch {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function useBalanceIdentify() {
|
|
317
|
+
const pollIntervalRef = (0, import_react2.useRef)(null);
|
|
318
|
+
const hasProcessedPending = (0, import_react2.useRef)(false);
|
|
319
|
+
const storageManager = typeof window !== "undefined" ? getStorageManager() : null;
|
|
320
|
+
const getPendingIdentify = (0, import_react2.useCallback)(() => {
|
|
321
|
+
if (!storageManager)
|
|
322
|
+
return null;
|
|
323
|
+
const pending = storageManager.getJSON(PENDING_IDENTIFY_KEY);
|
|
324
|
+
if (!pending)
|
|
325
|
+
return null;
|
|
326
|
+
if (Date.now() - pending.timestamp > MAX_PENDING_AGE_MS) {
|
|
327
|
+
storageManager.removeItem(PENDING_IDENTIFY_KEY);
|
|
328
|
+
return null;
|
|
450
329
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
is_active: isPageVisible && !isIdle
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
enqueueEvent(event);
|
|
476
|
-
log("Heartbeat sent:", timeOnPageSeconds, "seconds active");
|
|
330
|
+
return pending;
|
|
331
|
+
}, [storageManager]);
|
|
332
|
+
const setPendingIdentify = (0, import_react2.useCallback)((email, traits) => {
|
|
333
|
+
if (!storageManager)
|
|
334
|
+
return;
|
|
335
|
+
storageManager.setJSON(PENDING_IDENTIFY_KEY, {
|
|
336
|
+
email,
|
|
337
|
+
traits,
|
|
338
|
+
timestamp: Date.now()
|
|
339
|
+
});
|
|
340
|
+
}, [storageManager]);
|
|
341
|
+
const clearPendingIdentify = (0, import_react2.useCallback)(() => {
|
|
342
|
+
if (!storageManager)
|
|
343
|
+
return;
|
|
344
|
+
storageManager.removeItem(PENDING_IDENTIFY_KEY);
|
|
345
|
+
}, [storageManager]);
|
|
346
|
+
const identify2 = (0, import_react2.useCallback)((email, traits) => {
|
|
347
|
+
const hasConsent2 = checkAnalyticsConsent();
|
|
348
|
+
if (!hasConsent2) {
|
|
349
|
+
console.log("[useBalanceIdentify] Skipping identify - user declined analytics consent");
|
|
350
|
+
return false;
|
|
477
351
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
352
|
+
if (storageManager) {
|
|
353
|
+
initStorageWithConsent(hasConsent2);
|
|
354
|
+
}
|
|
355
|
+
if (typeof window !== "undefined" && window.balance?.identify) {
|
|
356
|
+
window.balance.identify(email, traits);
|
|
357
|
+
console.log("[useBalanceIdentify] User identified:", email.split("@")[0] + "***");
|
|
358
|
+
clearPendingIdentify();
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
console.log("[useBalanceIdentify] Pixel not ready, queueing identify");
|
|
362
|
+
setPendingIdentify(email, traits);
|
|
363
|
+
return false;
|
|
364
|
+
}, [storageManager, clearPendingIdentify, setPendingIdentify]);
|
|
365
|
+
const processPendingIdentify = (0, import_react2.useCallback)(() => {
|
|
366
|
+
if (hasProcessedPending.current)
|
|
367
|
+
return;
|
|
368
|
+
const pending = getPendingIdentify();
|
|
369
|
+
if (!pending)
|
|
370
|
+
return;
|
|
371
|
+
if (typeof window !== "undefined" && window.balance?.identify) {
|
|
372
|
+
const hasConsent2 = checkAnalyticsConsent();
|
|
373
|
+
if (!hasConsent2) {
|
|
374
|
+
console.log("[useBalanceIdentify] Clearing pending identify - user declined analytics consent");
|
|
375
|
+
clearPendingIdentify();
|
|
376
|
+
hasProcessedPending.current = true;
|
|
481
377
|
return;
|
|
482
378
|
}
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
startActiveTimeTracking();
|
|
486
|
-
heartbeatTimer = window.setInterval(() => {
|
|
487
|
-
sendHeartbeat();
|
|
488
|
-
}, heartbeatInterval);
|
|
489
|
-
log("Heartbeat started with interval:", heartbeatInterval, "ms");
|
|
490
|
-
}
|
|
491
|
-
function stopHeartbeat() {
|
|
492
|
-
if (heartbeatTimer) {
|
|
493
|
-
clearInterval(heartbeatTimer);
|
|
494
|
-
heartbeatTimer = null;
|
|
495
|
-
}
|
|
496
|
-
if (heartbeatEnabled) {
|
|
497
|
-
sendHeartbeat();
|
|
498
|
-
log("Heartbeat stopped, final time sent");
|
|
379
|
+
if (storageManager) {
|
|
380
|
+
initStorageWithConsent(hasConsent2);
|
|
499
381
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
});
|
|
517
|
-
enqueueEvent(event);
|
|
518
|
-
}
|
|
519
|
-
async function identify2(email, traits = {}) {
|
|
520
|
-
try {
|
|
521
|
-
if (consent && consent.analytics === false) {
|
|
522
|
-
log("Identify skipped - user declined analytics consent");
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
fanIdHash = await hashEmail(email);
|
|
526
|
-
if (consent?.analytics === true) {
|
|
527
|
-
upgradeStorageTier();
|
|
528
|
-
}
|
|
529
|
-
storageSet(FAN_ID_KEY, fanIdHash);
|
|
530
|
-
const emailParts = email.split("@");
|
|
531
|
-
const maskedEmail = emailParts[0].charAt(0) + "***@" + (emailParts[1] || "");
|
|
532
|
-
log("Fan identified:", {
|
|
533
|
-
name: traits.name || "(no name)",
|
|
534
|
-
email: maskedEmail,
|
|
535
|
-
hash: fanIdHash.substring(0, 16) + "...",
|
|
536
|
-
traits,
|
|
537
|
-
storageTier: currentStorageTier
|
|
538
|
-
});
|
|
539
|
-
const event = buildEvent({
|
|
540
|
-
event_name: "identify",
|
|
541
|
-
fan_id_hash: fanIdHash,
|
|
542
|
-
metadata: {
|
|
543
|
-
email_sha256: fanIdHash,
|
|
544
|
-
traits,
|
|
545
|
-
consent_preferences: consent || void 0,
|
|
546
|
-
storage_tier: currentStorageTier
|
|
382
|
+
console.log("[useBalanceIdentify] Processing pending identify");
|
|
383
|
+
window.balance.identify(pending.email, pending.traits);
|
|
384
|
+
clearPendingIdentify();
|
|
385
|
+
hasProcessedPending.current = true;
|
|
386
|
+
}
|
|
387
|
+
}, [getPendingIdentify, clearPendingIdentify, storageManager]);
|
|
388
|
+
(0, import_react2.useEffect)(() => {
|
|
389
|
+
processPendingIdentify();
|
|
390
|
+
const pending = getPendingIdentify();
|
|
391
|
+
if (pending && !hasProcessedPending.current) {
|
|
392
|
+
pollIntervalRef.current = setInterval(() => {
|
|
393
|
+
processPendingIdentify();
|
|
394
|
+
if (hasProcessedPending.current || !getPendingIdentify()) {
|
|
395
|
+
if (pollIntervalRef.current) {
|
|
396
|
+
clearInterval(pollIntervalRef.current);
|
|
397
|
+
pollIntervalRef.current = null;
|
|
547
398
|
}
|
|
548
|
-
});
|
|
549
|
-
enqueueEvent(event);
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error("[BALANCE Pixel] Failed to identify:", error);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
function purchase2(revenue, currency = "USD", properties = {}) {
|
|
555
|
-
const event = buildEvent({
|
|
556
|
-
event_name: "purchase",
|
|
557
|
-
metadata: {
|
|
558
|
-
revenue,
|
|
559
|
-
currency,
|
|
560
|
-
...properties
|
|
561
399
|
}
|
|
562
|
-
});
|
|
563
|
-
enqueueEvent(event);
|
|
400
|
+
}, 500);
|
|
564
401
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
if (consent?.analytics === true) {
|
|
570
|
-
currentStorageTier = "local";
|
|
571
|
-
log("Storage tier: local (analytics consent granted)");
|
|
572
|
-
} else {
|
|
573
|
-
currentStorageTier = "session";
|
|
574
|
-
log("Storage tier: session (privacy by default)");
|
|
402
|
+
return () => {
|
|
403
|
+
if (pollIntervalRef.current) {
|
|
404
|
+
clearInterval(pollIntervalRef.current);
|
|
405
|
+
pollIntervalRef.current = null;
|
|
575
406
|
}
|
|
576
|
-
sessionId = getOrCreateSession();
|
|
577
|
-
loadFanId();
|
|
578
|
-
if (!consent) {
|
|
579
|
-
consent = {
|
|
580
|
-
analytics: true,
|
|
581
|
-
marketing: true,
|
|
582
|
-
personalization: true,
|
|
583
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
584
|
-
};
|
|
585
|
-
log("Default consent enabled (all tracking):", consent);
|
|
586
|
-
upgradeStorageTier();
|
|
587
|
-
}
|
|
588
|
-
captureAttribution();
|
|
589
|
-
startFlushTimer();
|
|
590
|
-
log("Initialized", {
|
|
591
|
-
artistId,
|
|
592
|
-
projectId: projectId || "(none - will track to all projects)",
|
|
593
|
-
sessionId,
|
|
594
|
-
fanIdHash,
|
|
595
|
-
consent,
|
|
596
|
-
storageTier: currentStorageTier,
|
|
597
|
-
trackingSource,
|
|
598
|
-
useEmulator,
|
|
599
|
-
endpoint: API_ENDPOINT
|
|
600
|
-
});
|
|
601
|
-
window._balanceInitialPageviewFired = true;
|
|
602
|
-
trackPageView();
|
|
603
|
-
startHeartbeat();
|
|
604
|
-
["mousemove", "keydown", "scroll", "touchstart"].forEach((eventType) => {
|
|
605
|
-
document.addEventListener(eventType, resetIdleTimer, { passive: true });
|
|
606
|
-
});
|
|
607
|
-
window.addEventListener("beforeunload", () => {
|
|
608
|
-
stopHeartbeat();
|
|
609
|
-
flush();
|
|
610
|
-
});
|
|
611
|
-
document.addEventListener("visibilitychange", () => {
|
|
612
|
-
if (document.visibilityState === "hidden") {
|
|
613
|
-
pauseActiveTimeTracking();
|
|
614
|
-
flush();
|
|
615
|
-
} else {
|
|
616
|
-
startActiveTimeTracking();
|
|
617
|
-
}
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
window.balance = {
|
|
621
|
-
track: track2,
|
|
622
|
-
identify: identify2,
|
|
623
|
-
page: trackPageView,
|
|
624
|
-
purchase: purchase2,
|
|
625
|
-
getSessionId: () => sessionId,
|
|
626
|
-
getFanIdHash: () => fanIdHash,
|
|
627
|
-
getAttribution: () => attribution,
|
|
628
|
-
setConsent: setConsent2,
|
|
629
|
-
getConsent: getConsent2,
|
|
630
|
-
hasConsent: hasConsent2
|
|
631
407
|
};
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
window.balance.track(eventName, properties);
|
|
644
|
-
} else {
|
|
645
|
-
console.warn("[Balance Pixel] track() called before pixel initialized:", eventName);
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
var identify = (email, traits) => {
|
|
649
|
-
if (typeof window === "undefined")
|
|
650
|
-
return Promise.resolve();
|
|
651
|
-
if (window.balance) {
|
|
652
|
-
return window.balance.identify(email, traits);
|
|
653
|
-
} else {
|
|
654
|
-
console.warn("[Balance Pixel] identify() called before pixel initialized");
|
|
655
|
-
return Promise.resolve();
|
|
656
|
-
}
|
|
408
|
+
}, [processPendingIdentify, getPendingIdentify]);
|
|
409
|
+
return {
|
|
410
|
+
/**
|
|
411
|
+
* Identify a user with BALANCE pixel.
|
|
412
|
+
* Handles consent checks, queuing, and storage tier automatically.
|
|
413
|
+
*/
|
|
414
|
+
identify: identify2,
|
|
415
|
+
/**
|
|
416
|
+
* Check if analytics consent is currently granted
|
|
417
|
+
*/
|
|
418
|
+
hasAnalyticsConsent: checkAnalyticsConsent
|
|
657
419
|
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/react/GTMProvider.tsx
|
|
423
|
+
var import_react4 = __toESM(require("react"));
|
|
424
|
+
var import_script = __toESM(require("next/script"));
|
|
425
|
+
|
|
426
|
+
// src/react/useGTMConsent.ts
|
|
427
|
+
var import_react3 = require("react");
|
|
428
|
+
function useGTMConsent(options = {}) {
|
|
429
|
+
const { pollInterval = 1e3, debug = false } = options;
|
|
430
|
+
const lastConsentRef = (0, import_react3.useRef)("");
|
|
431
|
+
const log = (0, import_react3.useCallback)(
|
|
432
|
+
(...args) => {
|
|
433
|
+
if (debug) {
|
|
434
|
+
console.log("[useGTMConsent]", ...args);
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
[debug]
|
|
438
|
+
);
|
|
439
|
+
const mapPixelConsentToGTM = (0, import_react3.useCallback)(() => {
|
|
440
|
+
if (typeof window === "undefined" || !window.balance) {
|
|
441
|
+
return {
|
|
442
|
+
ad_storage: "denied",
|
|
443
|
+
ad_user_data: "denied",
|
|
444
|
+
ad_personalization: "denied",
|
|
445
|
+
analytics_storage: "denied"
|
|
446
|
+
};
|
|
665
447
|
}
|
|
666
|
-
|
|
667
|
-
|
|
448
|
+
const hasAnalytics = window.balance.hasConsent("analytics");
|
|
449
|
+
const hasMarketing = window.balance.hasConsent("marketing");
|
|
450
|
+
const analyticsState = hasAnalytics ? "granted" : "denied";
|
|
451
|
+
const marketingState = hasMarketing ? "granted" : "denied";
|
|
452
|
+
return {
|
|
453
|
+
analytics_storage: analyticsState,
|
|
454
|
+
ad_storage: marketingState,
|
|
455
|
+
ad_user_data: marketingState,
|
|
456
|
+
ad_personalization: marketingState
|
|
457
|
+
};
|
|
458
|
+
}, []);
|
|
459
|
+
const updateGTMConsent = (0, import_react3.useCallback)(
|
|
460
|
+
(consentConfig) => {
|
|
461
|
+
if (typeof window === "undefined")
|
|
462
|
+
return;
|
|
463
|
+
window.dataLayer = window.dataLayer || [];
|
|
464
|
+
if (typeof window.gtag !== "function") {
|
|
465
|
+
window.gtag = function gtag(...args) {
|
|
466
|
+
window.dataLayer.push(args);
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
window.gtag("consent", "update", consentConfig);
|
|
470
|
+
log("Pushed consent update to GTM:", consentConfig);
|
|
471
|
+
},
|
|
472
|
+
[log]
|
|
473
|
+
);
|
|
474
|
+
const syncConsent = (0, import_react3.useCallback)(() => {
|
|
475
|
+
const currentConsent = mapPixelConsentToGTM();
|
|
476
|
+
const consentKey = JSON.stringify(currentConsent);
|
|
477
|
+
if (consentKey !== lastConsentRef.current) {
|
|
478
|
+
lastConsentRef.current = consentKey;
|
|
479
|
+
updateGTMConsent(currentConsent);
|
|
480
|
+
}
|
|
481
|
+
}, [mapPixelConsentToGTM, updateGTMConsent]);
|
|
482
|
+
(0, import_react3.useEffect)(() => {
|
|
668
483
|
if (typeof window === "undefined")
|
|
669
484
|
return;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
return
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
485
|
+
syncConsent();
|
|
486
|
+
const intervalId = setInterval(syncConsent, pollInterval);
|
|
487
|
+
const handleStorageChange = (e) => {
|
|
488
|
+
if (e.key === "balance_consent") {
|
|
489
|
+
log("Consent changed in another tab, syncing...");
|
|
490
|
+
syncConsent();
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
window.addEventListener("storage", handleStorageChange);
|
|
494
|
+
return () => {
|
|
495
|
+
clearInterval(intervalId);
|
|
496
|
+
window.removeEventListener("storage", handleStorageChange);
|
|
497
|
+
};
|
|
498
|
+
}, [syncConsent, pollInterval, log]);
|
|
499
|
+
return {
|
|
500
|
+
/**
|
|
501
|
+
* Manually trigger a consent sync
|
|
502
|
+
*/
|
|
503
|
+
syncConsent,
|
|
504
|
+
/**
|
|
505
|
+
* Get current consent config
|
|
506
|
+
*/
|
|
507
|
+
getConsentConfig: mapPixelConsentToGTM
|
|
690
508
|
};
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/types/gtm.ts
|
|
512
|
+
var DEFAULT_GTM_CONSENT = {
|
|
513
|
+
ad_storage: "denied",
|
|
514
|
+
ad_user_data: "denied",
|
|
515
|
+
ad_personalization: "denied",
|
|
516
|
+
analytics_storage: "denied"
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// src/react/GTMProvider.tsx
|
|
520
|
+
function GTMProvider({ gtmId, children, debug = false }) {
|
|
521
|
+
const resolvedGtmId = gtmId || process.env.NEXT_PUBLIC_GTM_ID;
|
|
522
|
+
useGTMConsent({ debug });
|
|
523
|
+
if (!resolvedGtmId) {
|
|
524
|
+
if (debug) {
|
|
525
|
+
console.log("[GTMProvider] No GTM ID configured, skipping GTM initialization");
|
|
526
|
+
}
|
|
527
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, children);
|
|
528
|
+
}
|
|
529
|
+
const consentJson = JSON.stringify(DEFAULT_GTM_CONSENT);
|
|
530
|
+
const consentScript = `
|
|
531
|
+
window.dataLayer = window.dataLayer || [];
|
|
532
|
+
function gtag(){dataLayer.push(arguments);}
|
|
533
|
+
window.gtag = gtag;
|
|
534
|
+
gtag('consent', 'default', ${consentJson});
|
|
535
|
+
gtag('set', 'wait_for_update', 500);
|
|
536
|
+
${debug ? "console.log('[GTM] Default consent initialized (denied)');" : ""}
|
|
537
|
+
`;
|
|
538
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(
|
|
539
|
+
import_script.default,
|
|
540
|
+
{
|
|
541
|
+
id: "gtm-consent-default",
|
|
542
|
+
strategy: "beforeInteractive",
|
|
543
|
+
dangerouslySetInnerHTML: { __html: consentScript }
|
|
544
|
+
}
|
|
545
|
+
), /* @__PURE__ */ import_react4.default.createElement(
|
|
546
|
+
import_script.default,
|
|
547
|
+
{
|
|
548
|
+
id: "gtm-script",
|
|
549
|
+
strategy: "afterInteractive",
|
|
550
|
+
dangerouslySetInnerHTML: {
|
|
551
|
+
__html: `
|
|
552
|
+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
553
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
554
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|
555
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
|
556
|
+
})(window,document,'script','dataLayer','${resolvedGtmId}');
|
|
557
|
+
`
|
|
558
|
+
}
|
|
698
559
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
560
|
+
), /* @__PURE__ */ import_react4.default.createElement("noscript", null, /* @__PURE__ */ import_react4.default.createElement(
|
|
561
|
+
"iframe",
|
|
562
|
+
{
|
|
563
|
+
src: `https://www.googletagmanager.com/ns.html?id=${resolvedGtmId}`,
|
|
564
|
+
height: "0",
|
|
565
|
+
width: "0",
|
|
566
|
+
style: { display: "none", visibility: "hidden" },
|
|
567
|
+
title: "GTM"
|
|
568
|
+
}
|
|
569
|
+
)), children);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/index.esm.ts
|
|
573
|
+
var track = (eventName, properties) => {
|
|
574
|
+
if (typeof window === "undefined")
|
|
575
|
+
return;
|
|
576
|
+
if (window.balance) {
|
|
577
|
+
window.balance.track(eventName, properties);
|
|
578
|
+
} else {
|
|
579
|
+
console.warn("[Balance Pixel] track() called before pixel initialized:", eventName);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
var identify = (email, traits) => {
|
|
583
|
+
if (typeof window === "undefined")
|
|
584
|
+
return Promise.resolve();
|
|
585
|
+
if (window.balance) {
|
|
586
|
+
return window.balance.identify(email, traits);
|
|
587
|
+
} else {
|
|
588
|
+
console.warn("[Balance Pixel] identify() called before pixel initialized");
|
|
589
|
+
return Promise.resolve();
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
var page = (options) => {
|
|
593
|
+
if (typeof window === "undefined")
|
|
594
|
+
return;
|
|
595
|
+
if (window.balance) {
|
|
596
|
+
window.balance.page(options);
|
|
597
|
+
} else {
|
|
598
|
+
console.warn("[Balance Pixel] page() called before pixel initialized");
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
var purchase = (revenue, currency, properties) => {
|
|
602
|
+
if (typeof window === "undefined")
|
|
603
|
+
return;
|
|
604
|
+
if (window.balance) {
|
|
605
|
+
window.balance.purchase(revenue, currency, properties);
|
|
606
|
+
} else {
|
|
607
|
+
console.warn("[Balance Pixel] purchase() called before pixel initialized");
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var getSessionId = () => {
|
|
611
|
+
if (typeof window === "undefined")
|
|
612
|
+
return null;
|
|
613
|
+
return window.balance?.getSessionId() ?? null;
|
|
614
|
+
};
|
|
615
|
+
var getFanIdHash = () => {
|
|
616
|
+
if (typeof window === "undefined")
|
|
617
|
+
return null;
|
|
618
|
+
return window.balance?.getFanIdHash() ?? null;
|
|
619
|
+
};
|
|
620
|
+
var getAttribution = () => {
|
|
621
|
+
if (typeof window === "undefined")
|
|
622
|
+
return {};
|
|
623
|
+
return window.balance?.getAttribution() ?? {};
|
|
624
|
+
};
|
|
625
|
+
var setConsent = (preferences) => {
|
|
626
|
+
if (typeof window === "undefined")
|
|
627
|
+
return;
|
|
628
|
+
if (window.balance) {
|
|
629
|
+
window.balance.setConsent(preferences);
|
|
630
|
+
} else {
|
|
631
|
+
console.warn("[Balance Pixel] setConsent() called before pixel initialized");
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var getConsent = () => {
|
|
635
|
+
if (typeof window === "undefined")
|
|
636
|
+
return null;
|
|
637
|
+
return window.balance?.getConsent() ?? null;
|
|
638
|
+
};
|
|
639
|
+
var hasConsent = (type) => {
|
|
640
|
+
if (typeof window === "undefined")
|
|
641
|
+
return false;
|
|
642
|
+
return window.balance?.hasConsent(type) ?? false;
|
|
643
|
+
};
|