@hifilabs/pixel 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,6 +35,8 @@ var __publicField = (obj, key, value) => {
35
35
  var src_exports = {};
36
36
  __export(src_exports, {
37
37
  BalanceAnalytics: () => BalanceAnalytics,
38
+ BalanceContext: () => BalanceContext,
39
+ BalanceProvider: () => BalanceProvider,
38
40
  DEFAULT_GTM_CONSENT: () => DEFAULT_GTM_CONSENT,
39
41
  GTMProvider: () => GTMProvider,
40
42
  StorageManager: () => StorageManager,
@@ -50,21 +52,2721 @@ __export(src_exports, {
50
52
  purchase: () => purchase,
51
53
  setConsent: () => setConsent,
52
54
  track: () => track,
55
+ useBalance: () => useBalance,
56
+ useBalanceAB: () => useBalanceAB,
57
+ useBalanceAuth: () => useBalanceAuth,
58
+ useBalanceConsent: () => useBalanceConsent,
59
+ useBalanceEcommerce: () => useBalanceEcommerce,
60
+ useBalanceEngagement: () => useBalanceEngagement,
61
+ useBalanceError: () => useBalanceError,
62
+ useBalanceForm: () => useBalanceForm,
53
63
  useBalanceIdentify: () => useBalanceIdentify,
64
+ useBalanceLive: () => useBalanceLive,
65
+ useBalanceMedia: () => useBalanceMedia,
66
+ useBalanceNotification: () => useBalanceNotification,
67
+ useBalanceOptional: () => useBalanceOptional,
68
+ useBalanceReady: () => useBalanceReady,
69
+ useBalanceSearch: () => useBalanceSearch,
70
+ useBalanceSocial: () => useBalanceSocial,
71
+ useBalanceSubscription: () => useBalanceSubscription,
54
72
  useGTMConsent: () => useGTMConsent
55
73
  });
56
74
  module.exports = __toCommonJS(src_exports);
57
75
 
76
+ // src/react/BalanceContext.ts
77
+ var import_react = require("react");
78
+ var warnNoProvider = (method) => {
79
+ if (true) {
80
+ console.warn(
81
+ `[BalanceContext] Called '${method}()' without <BalanceProvider>. Wrap your app with <BalanceProvider artistId="..."> to enable tracking.`
82
+ );
83
+ }
84
+ };
85
+ var defaultContextValue = {
86
+ // Tracking methods - warn in dev if Provider missing
87
+ track: () => warnNoProvider("track"),
88
+ identify: () => warnNoProvider("identify"),
89
+ page: () => warnNoProvider("page"),
90
+ purchase: () => warnNoProvider("purchase"),
91
+ // Getter methods - return defaults (no warning needed)
92
+ getSessionId: () => null,
93
+ getFanIdHash: () => null,
94
+ getAttribution: () => ({}),
95
+ // Consent methods - warn in dev if Provider missing
96
+ setConsent: () => warnNoProvider("setConsent"),
97
+ getConsent: () => null,
98
+ hasConsent: () => false,
99
+ // State - SSR-safe defaults
100
+ isReady: false,
101
+ artistId: "",
102
+ projectId: void 0,
103
+ endpoint: void 0,
104
+ debug: false
105
+ };
106
+ var BalanceContext = (0, import_react.createContext)(defaultContextValue);
107
+ BalanceContext.displayName = "BalanceContext";
108
+
109
+ // src/react/BalanceProvider.tsx
110
+ var import_react4 = __toESM(require("react"));
111
+ var import_script2 = __toESM(require("next/script"));
112
+
113
+ // src/react/GTMProvider.tsx
114
+ var import_react3 = __toESM(require("react"));
115
+ var import_script = __toESM(require("next/script"));
116
+
117
+ // src/react/useGTMConsent.ts
118
+ var import_react2 = require("react");
119
+ function useGTMConsent(options = {}) {
120
+ const { pollInterval = 1e3, debug = false, enabled = true } = options;
121
+ const lastConsentRef = (0, import_react2.useRef)("");
122
+ const log = (0, import_react2.useCallback)(
123
+ (...args) => {
124
+ if (debug) {
125
+ console.log("[useGTMConsent]", ...args);
126
+ }
127
+ },
128
+ [debug]
129
+ );
130
+ const mapPixelConsentToGTM = (0, import_react2.useCallback)(() => {
131
+ if (typeof window === "undefined" || !window.balance) {
132
+ return {
133
+ ad_storage: "denied",
134
+ ad_user_data: "denied",
135
+ ad_personalization: "denied",
136
+ analytics_storage: "denied"
137
+ };
138
+ }
139
+ const hasAnalytics = window.balance.hasConsent("analytics");
140
+ const hasMarketing = window.balance.hasConsent("marketing");
141
+ const analyticsState = hasAnalytics ? "granted" : "denied";
142
+ const marketingState = hasMarketing ? "granted" : "denied";
143
+ return {
144
+ analytics_storage: analyticsState,
145
+ ad_storage: marketingState,
146
+ ad_user_data: marketingState,
147
+ ad_personalization: marketingState
148
+ };
149
+ }, []);
150
+ const updateGTMConsent = (0, import_react2.useCallback)(
151
+ (consentConfig) => {
152
+ if (typeof window === "undefined")
153
+ return;
154
+ window.dataLayer = window.dataLayer || [];
155
+ if (typeof window.gtag !== "function") {
156
+ window.gtag = function gtag(...args) {
157
+ window.dataLayer.push(args);
158
+ };
159
+ }
160
+ window.gtag("consent", "update", consentConfig);
161
+ log("Pushed consent update to GTM:", consentConfig);
162
+ },
163
+ [log]
164
+ );
165
+ const syncConsent = (0, import_react2.useCallback)(() => {
166
+ const currentConsent = mapPixelConsentToGTM();
167
+ const consentKey = JSON.stringify(currentConsent);
168
+ if (consentKey !== lastConsentRef.current) {
169
+ lastConsentRef.current = consentKey;
170
+ updateGTMConsent(currentConsent);
171
+ }
172
+ }, [mapPixelConsentToGTM, updateGTMConsent]);
173
+ (0, import_react2.useEffect)(() => {
174
+ if (typeof window === "undefined" || !enabled)
175
+ return;
176
+ syncConsent();
177
+ const intervalId = setInterval(syncConsent, pollInterval);
178
+ const handleStorageChange = (e) => {
179
+ if (e.key === "balance_consent") {
180
+ log("Consent changed in another tab, syncing...");
181
+ syncConsent();
182
+ }
183
+ };
184
+ window.addEventListener("storage", handleStorageChange);
185
+ return () => {
186
+ clearInterval(intervalId);
187
+ window.removeEventListener("storage", handleStorageChange);
188
+ };
189
+ }, [syncConsent, pollInterval, log, enabled]);
190
+ return {
191
+ /**
192
+ * Manually trigger a consent sync
193
+ */
194
+ syncConsent,
195
+ /**
196
+ * Get current consent config
197
+ */
198
+ getConsentConfig: mapPixelConsentToGTM
199
+ };
200
+ }
201
+
202
+ // src/types/gtm.ts
203
+ var DEFAULT_GTM_CONSENT = {
204
+ ad_storage: "denied",
205
+ ad_user_data: "denied",
206
+ ad_personalization: "denied",
207
+ analytics_storage: "denied"
208
+ };
209
+
210
+ // src/react/GTMProvider.tsx
211
+ function GTMProvider({ gtmId, children, debug = false }) {
212
+ const resolvedGtmId = gtmId || process.env.NEXT_PUBLIC_GTM_ID;
213
+ useGTMConsent({ debug, enabled: !!resolvedGtmId });
214
+ if (!resolvedGtmId) {
215
+ if (debug) {
216
+ console.log("[GTMProvider] No GTM ID configured, skipping GTM initialization");
217
+ }
218
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, children);
219
+ }
220
+ const consentJson = JSON.stringify(DEFAULT_GTM_CONSENT);
221
+ const consentScript = `
222
+ window.dataLayer = window.dataLayer || [];
223
+ function gtag(){dataLayer.push(arguments);}
224
+ window.gtag = gtag;
225
+ gtag('consent', 'default', ${consentJson});
226
+ gtag('set', 'wait_for_update', 500);
227
+ ${debug ? "console.log('[GTM] Default consent initialized (denied)');" : ""}
228
+ `;
229
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(
230
+ import_script.default,
231
+ {
232
+ id: "gtm-consent-default",
233
+ strategy: "beforeInteractive",
234
+ dangerouslySetInnerHTML: { __html: consentScript }
235
+ }
236
+ ), /* @__PURE__ */ import_react3.default.createElement(
237
+ import_script.default,
238
+ {
239
+ id: "gtm-script",
240
+ strategy: "afterInteractive",
241
+ dangerouslySetInnerHTML: {
242
+ __html: `
243
+ (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
244
+ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
245
+ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
246
+ 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
247
+ })(window,document,'script','dataLayer','${resolvedGtmId}');
248
+ `
249
+ }
250
+ }
251
+ ), /* @__PURE__ */ import_react3.default.createElement("noscript", null, /* @__PURE__ */ import_react3.default.createElement(
252
+ "iframe",
253
+ {
254
+ src: `https://www.googletagmanager.com/ns.html?id=${resolvedGtmId}`,
255
+ height: "0",
256
+ width: "0",
257
+ style: { display: "none", visibility: "hidden" },
258
+ title: "GTM"
259
+ }
260
+ )), children);
261
+ }
262
+
263
+ // src/react/BalanceProvider.tsx
264
+ var DEFAULT_SCRIPT_URL = "https://cdn.hifilabs.co/balance-pixel.js";
265
+ function ensureGlobalStub() {
266
+ if (typeof window === "undefined")
267
+ return;
268
+ if (!window.balance) {
269
+ window.balance = function(...args) {
270
+ (window.balance.q = window.balance.q || []).push(args);
271
+ };
272
+ }
273
+ }
274
+ function BalanceProvider({
275
+ artistId,
276
+ projectId,
277
+ debug = false,
278
+ gtmId,
279
+ scriptUrl,
280
+ endpoint,
281
+ useEmulator = false,
282
+ children
283
+ }) {
284
+ const [isReady, setIsReady] = (0, import_react4.useState)(false);
285
+ const [shouldLoadScript, setShouldLoadScript] = (0, import_react4.useState)(false);
286
+ (0, import_react4.useEffect)(() => {
287
+ ensureGlobalStub();
288
+ const existingScript = document.querySelector(
289
+ 'script[src*="balance-pixel"], script[data-artist-id]'
290
+ );
291
+ if (existingScript || window.balance?.version) {
292
+ if (debug) {
293
+ console.log("[BalanceProvider] Pixel already loaded externally");
294
+ }
295
+ setIsReady(true);
296
+ return;
297
+ }
298
+ setShouldLoadScript(true);
299
+ }, [debug]);
300
+ const track2 = (0, import_react4.useCallback)((event, properties) => {
301
+ if (typeof window === "undefined")
302
+ return;
303
+ ensureGlobalStub();
304
+ window.balance("track", event, properties || {});
305
+ }, []);
306
+ const identify2 = (0, import_react4.useCallback)((email, traits) => {
307
+ if (typeof window === "undefined")
308
+ return;
309
+ ensureGlobalStub();
310
+ window.balance("identify", email, traits || {});
311
+ }, []);
312
+ const page2 = (0, import_react4.useCallback)((options) => {
313
+ if (typeof window === "undefined")
314
+ return;
315
+ ensureGlobalStub();
316
+ window.balance("page", options || {});
317
+ }, []);
318
+ const purchase2 = (0, import_react4.useCallback)((amount, currency, properties) => {
319
+ if (typeof window === "undefined")
320
+ return;
321
+ ensureGlobalStub();
322
+ window.balance("purchase", amount, currency || "USD", properties || {});
323
+ }, []);
324
+ const getSessionId2 = (0, import_react4.useCallback)(() => {
325
+ if (typeof window === "undefined")
326
+ return null;
327
+ return window.balance?.getSessionId?.() ?? null;
328
+ }, []);
329
+ const getFanIdHash2 = (0, import_react4.useCallback)(() => {
330
+ if (typeof window === "undefined")
331
+ return null;
332
+ return window.balance?.getFanIdHash?.() ?? null;
333
+ }, []);
334
+ const getAttribution2 = (0, import_react4.useCallback)(() => {
335
+ if (typeof window === "undefined")
336
+ return {};
337
+ return window.balance?.getAttribution?.() ?? {};
338
+ }, []);
339
+ const setConsent2 = (0, import_react4.useCallback)((preferences) => {
340
+ if (typeof window === "undefined")
341
+ return;
342
+ ensureGlobalStub();
343
+ window.balance("setConsent", preferences);
344
+ }, []);
345
+ const getConsent2 = (0, import_react4.useCallback)(() => {
346
+ if (typeof window === "undefined")
347
+ return null;
348
+ return window.balance?.getConsent?.() ?? null;
349
+ }, []);
350
+ const hasConsent2 = (0, import_react4.useCallback)((type) => {
351
+ if (typeof window === "undefined")
352
+ return false;
353
+ return window.balance?.hasConsent?.(type) ?? false;
354
+ }, []);
355
+ const contextValue = (0, import_react4.useMemo)(() => ({
356
+ // Tracking methods (always usable via queue)
357
+ track: track2,
358
+ identify: identify2,
359
+ page: page2,
360
+ purchase: purchase2,
361
+ // Getter methods
362
+ getSessionId: getSessionId2,
363
+ getFanIdHash: getFanIdHash2,
364
+ getAttribution: getAttribution2,
365
+ // Consent methods
366
+ setConsent: setConsent2,
367
+ getConsent: getConsent2,
368
+ hasConsent: hasConsent2,
369
+ // State
370
+ isReady,
371
+ artistId,
372
+ projectId,
373
+ endpoint,
374
+ debug
375
+ }), [
376
+ track2,
377
+ identify2,
378
+ page2,
379
+ purchase2,
380
+ getSessionId2,
381
+ getFanIdHash2,
382
+ getAttribution2,
383
+ setConsent2,
384
+ getConsent2,
385
+ hasConsent2,
386
+ isReady,
387
+ artistId,
388
+ projectId,
389
+ endpoint,
390
+ debug
391
+ ]);
392
+ const handleScriptLoad = (0, import_react4.useCallback)(() => {
393
+ if (debug) {
394
+ console.log("[BalanceProvider] Pixel script loaded");
395
+ }
396
+ setIsReady(true);
397
+ }, [debug]);
398
+ const handleScriptError = (0, import_react4.useCallback)((e) => {
399
+ console.error("[BalanceProvider] Failed to load pixel script:", e);
400
+ }, []);
401
+ const resolvedScriptUrl = scriptUrl ?? DEFAULT_SCRIPT_URL;
402
+ const content = /* @__PURE__ */ import_react4.default.createElement(BalanceContext.Provider, { value: contextValue }, shouldLoadScript && /* @__PURE__ */ import_react4.default.createElement(
403
+ import_script2.default,
404
+ {
405
+ src: resolvedScriptUrl,
406
+ "data-artist-id": artistId,
407
+ "data-project-id": projectId,
408
+ "data-endpoint": endpoint,
409
+ "data-emulator": useEmulator ? "true" : void 0,
410
+ "data-debug": debug ? "true" : void 0,
411
+ strategy: "afterInteractive",
412
+ onLoad: handleScriptLoad,
413
+ onError: handleScriptError
414
+ }
415
+ ), children);
416
+ if (gtmId) {
417
+ return /* @__PURE__ */ import_react4.default.createElement(GTMProvider, { gtmId, debug }, content);
418
+ }
419
+ return content;
420
+ }
421
+
422
+ // src/react/useBalance.ts
423
+ var import_react5 = require("react");
424
+ function useBalance() {
425
+ const context = (0, import_react5.useContext)(BalanceContext);
426
+ if (!context.artistId) {
427
+ throw new Error(
428
+ '[useBalance] must be used within a BalanceProvider. Wrap your app with <BalanceProvider artistId="..."> or use useBalanceOptional() instead.'
429
+ );
430
+ }
431
+ return context;
432
+ }
433
+ function useBalanceOptional() {
434
+ const context = (0, import_react5.useContext)(BalanceContext);
435
+ if (!context.artistId) {
436
+ return null;
437
+ }
438
+ return context;
439
+ }
440
+
441
+ // src/react/useBalanceConsent.ts
442
+ var import_react6 = require("react");
443
+ function useBalanceConsent() {
444
+ const { setConsent: setConsent2, getConsent: getConsent2, hasConsent: hasConsent2 } = useBalance();
445
+ const acceptAll = (0, import_react6.useCallback)(() => {
446
+ setConsent2({
447
+ analytics: true,
448
+ marketing: true,
449
+ personalization: true,
450
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
451
+ });
452
+ }, [setConsent2]);
453
+ const declineAll = (0, import_react6.useCallback)(() => {
454
+ setConsent2({
455
+ analytics: false,
456
+ marketing: false,
457
+ personalization: false,
458
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
459
+ });
460
+ }, [setConsent2]);
461
+ const acceptAnalyticsOnly = (0, import_react6.useCallback)(() => {
462
+ setConsent2({
463
+ analytics: true,
464
+ marketing: false,
465
+ personalization: false,
466
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
467
+ });
468
+ }, [setConsent2]);
469
+ return {
470
+ setConsent: setConsent2,
471
+ getConsent: getConsent2,
472
+ hasConsent: hasConsent2,
473
+ acceptAll,
474
+ declineAll,
475
+ acceptAnalyticsOnly
476
+ };
477
+ }
478
+
479
+ // src/react/useBalanceReady.ts
480
+ var import_react7 = require("react");
481
+ function useBalanceReady() {
482
+ const context = (0, import_react7.useContext)(BalanceContext);
483
+ return context.isReady;
484
+ }
485
+
486
+ // src/react/useBalanceEcommerce.ts
487
+ var import_react8 = require("react");
488
+ function useBalanceEcommerce() {
489
+ const { track: track2, purchase: purchase2 } = useBalance();
490
+ const formatProduct = (0, import_react8.useCallback)((product, quantity) => ({
491
+ item_id: product.id,
492
+ item_name: product.name,
493
+ price: product.price,
494
+ item_category: product.category,
495
+ item_variant: product.variant,
496
+ item_brand: product.brand,
497
+ quantity: quantity ?? product.quantity ?? 1
498
+ }), []);
499
+ const formatProducts = (0, import_react8.useCallback)(
500
+ (products) => products.map((p, index) => ({
501
+ ...formatProduct(p),
502
+ index
503
+ })),
504
+ [formatProduct]
505
+ );
506
+ const calculateValue = (0, import_react8.useCallback)(
507
+ (items) => items.reduce((sum, item) => sum + item.price * (item.quantity || 1), 0),
508
+ []
509
+ );
510
+ const viewProduct = (0, import_react8.useCallback)((product) => {
511
+ track2("view_item", {
512
+ currency: "USD",
513
+ value: product.price,
514
+ items: [formatProduct(product)]
515
+ });
516
+ }, [track2, formatProduct]);
517
+ const viewProductList = (0, import_react8.useCallback)((listName, products) => {
518
+ track2("view_item_list", {
519
+ item_list_id: listName.toLowerCase().replace(/\s+/g, "_"),
520
+ item_list_name: listName,
521
+ items: formatProducts(products)
522
+ });
523
+ }, [track2, formatProducts]);
524
+ const selectProduct = (0, import_react8.useCallback)((product, listName) => {
525
+ track2("select_item", {
526
+ item_list_name: listName,
527
+ items: [formatProduct(product)]
528
+ });
529
+ }, [track2, formatProduct]);
530
+ const addToCart = (0, import_react8.useCallback)((product, quantity = 1) => {
531
+ track2("add_to_cart", {
532
+ currency: "USD",
533
+ value: product.price * quantity,
534
+ items: [formatProduct(product, quantity)]
535
+ });
536
+ }, [track2, formatProduct]);
537
+ const removeFromCart = (0, import_react8.useCallback)((product, quantity = 1) => {
538
+ track2("remove_from_cart", {
539
+ currency: "USD",
540
+ value: product.price * quantity,
541
+ items: [formatProduct(product, quantity)]
542
+ });
543
+ }, [track2, formatProduct]);
544
+ const viewCart = (0, import_react8.useCallback)((items, cartTotal) => {
545
+ track2("view_cart", {
546
+ currency: "USD",
547
+ value: cartTotal,
548
+ items: formatProducts(items)
549
+ });
550
+ }, [track2, formatProducts]);
551
+ const beginCheckout = (0, import_react8.useCallback)((items, total, currency = "USD") => {
552
+ track2("begin_checkout", {
553
+ currency,
554
+ value: total,
555
+ items: formatProducts(items)
556
+ });
557
+ }, [track2, formatProducts]);
558
+ const addShippingInfo = (0, import_react8.useCallback)((shippingTier, items) => {
559
+ track2("add_shipping_info", {
560
+ currency: "USD",
561
+ value: calculateValue(items),
562
+ shipping_tier: shippingTier,
563
+ items: formatProducts(items)
564
+ });
565
+ }, [track2, formatProducts, calculateValue]);
566
+ const addPaymentInfo = (0, import_react8.useCallback)((paymentType, items) => {
567
+ track2("add_payment_info", {
568
+ currency: "USD",
569
+ value: calculateValue(items),
570
+ payment_type: paymentType,
571
+ items: formatProducts(items)
572
+ });
573
+ }, [track2, formatProducts, calculateValue]);
574
+ const completePurchase = (0, import_react8.useCallback)((orderId, total, items, options = {}) => {
575
+ const { currency = "USD", tax, shipping, coupon } = options;
576
+ purchase2(total, currency, {
577
+ order_id: orderId,
578
+ tax,
579
+ shipping,
580
+ coupon,
581
+ items: formatProducts(items)
582
+ });
583
+ }, [purchase2, formatProducts]);
584
+ const refund = (0, import_react8.useCallback)((orderId, amount, items) => {
585
+ track2("refund", {
586
+ currency: "USD",
587
+ transaction_id: orderId,
588
+ value: amount,
589
+ items: items ? formatProducts(items) : void 0
590
+ });
591
+ }, [track2, formatProducts]);
592
+ return {
593
+ // Product discovery
594
+ viewProduct,
595
+ viewProductList,
596
+ selectProduct,
597
+ // Cart operations
598
+ addToCart,
599
+ removeFromCart,
600
+ viewCart,
601
+ // Checkout flow
602
+ beginCheckout,
603
+ addShippingInfo,
604
+ addPaymentInfo,
605
+ // Purchase & refund
606
+ completePurchase,
607
+ refund
608
+ };
609
+ }
610
+
611
+ // src/react/useBalanceMedia.ts
612
+ var import_react9 = require("react");
613
+ function useBalanceMedia() {
614
+ const { track: track2 } = useBalance();
615
+ const playStartRef = (0, import_react9.useRef)(null);
616
+ const totalListenTimeRef = (0, import_react9.useRef)(0);
617
+ const milestonesTrackedRef = (0, import_react9.useRef)(/* @__PURE__ */ new Set());
618
+ const currentMediaIdRef = (0, import_react9.useRef)(null);
619
+ const isPlayingRef = (0, import_react9.useRef)(false);
620
+ const formatMedia = (0, import_react9.useCallback)((media) => ({
621
+ media_id: media.mediaId,
622
+ title: media.title,
623
+ duration: media.duration,
624
+ type: media.type,
625
+ artist: media.artist,
626
+ album: media.album,
627
+ genre: media.genre,
628
+ playlist: media.playlist,
629
+ playlist_position: media.playlistPosition
630
+ }), []);
631
+ const getListenDuration = (0, import_react9.useCallback)(() => {
632
+ let duration = totalListenTimeRef.current;
633
+ if (isPlayingRef.current && playStartRef.current) {
634
+ duration += (Date.now() - playStartRef.current) / 1e3;
635
+ }
636
+ return Math.round(duration * 100) / 100;
637
+ }, []);
638
+ const reset = (0, import_react9.useCallback)(() => {
639
+ playStartRef.current = null;
640
+ totalListenTimeRef.current = 0;
641
+ milestonesTrackedRef.current.clear();
642
+ currentMediaIdRef.current = null;
643
+ isPlayingRef.current = false;
644
+ }, []);
645
+ const play = (0, import_react9.useCallback)((media) => {
646
+ if (currentMediaIdRef.current !== media.mediaId) {
647
+ reset();
648
+ currentMediaIdRef.current = media.mediaId;
649
+ }
650
+ playStartRef.current = Date.now();
651
+ isPlayingRef.current = true;
652
+ track2("media_play", {
653
+ ...formatMedia(media),
654
+ session_listen_time: totalListenTimeRef.current
655
+ });
656
+ }, [track2, formatMedia, reset]);
657
+ const pause = (0, import_react9.useCallback)((media, currentTime) => {
658
+ if (playStartRef.current && isPlayingRef.current) {
659
+ totalListenTimeRef.current += (Date.now() - playStartRef.current) / 1e3;
660
+ }
661
+ playStartRef.current = null;
662
+ isPlayingRef.current = false;
663
+ const percentPlayed = media.duration > 0 ? Math.round(currentTime / media.duration * 100) : 0;
664
+ track2("media_pause", {
665
+ ...formatMedia(media),
666
+ current_time: currentTime,
667
+ percent_played: percentPlayed,
668
+ session_listen_time: totalListenTimeRef.current
669
+ });
670
+ }, [track2, formatMedia]);
671
+ const complete = (0, import_react9.useCallback)((media) => {
672
+ if (playStartRef.current && isPlayingRef.current) {
673
+ totalListenTimeRef.current += (Date.now() - playStartRef.current) / 1e3;
674
+ }
675
+ isPlayingRef.current = false;
676
+ track2("media_complete", {
677
+ ...formatMedia(media),
678
+ session_listen_time: totalListenTimeRef.current
679
+ });
680
+ reset();
681
+ }, [track2, formatMedia, reset]);
682
+ const seek = (0, import_react9.useCallback)((media, fromTime, toTime) => {
683
+ track2("media_seek", {
684
+ ...formatMedia(media),
685
+ from_time: fromTime,
686
+ to_time: toTime,
687
+ seek_distance: Math.abs(toTime - fromTime),
688
+ seek_direction: toTime > fromTime ? "forward" : "backward"
689
+ });
690
+ }, [track2, formatMedia]);
691
+ const trackProgress = (0, import_react9.useCallback)((media, currentTime) => {
692
+ if (media.duration <= 0)
693
+ return;
694
+ const percentPlayed = currentTime / media.duration * 100;
695
+ const milestones = [25, 50, 75, 90];
696
+ milestones.forEach((milestone) => {
697
+ if (percentPlayed >= milestone && !milestonesTrackedRef.current.has(milestone)) {
698
+ milestonesTrackedRef.current.add(milestone);
699
+ track2("media_progress", {
700
+ ...formatMedia(media),
701
+ milestone,
702
+ current_time: currentTime,
703
+ session_listen_time: getListenDuration()
704
+ });
705
+ }
706
+ });
707
+ }, [track2, formatMedia, getListenDuration]);
708
+ const buffer = (0, import_react9.useCallback)((media, currentTime) => {
709
+ track2("media_buffer", {
710
+ ...formatMedia(media),
711
+ current_time: currentTime,
712
+ percent_played: media.duration > 0 ? Math.round(currentTime / media.duration * 100) : 0
713
+ });
714
+ }, [track2, formatMedia]);
715
+ const qualityChange = (0, import_react9.useCallback)((media, fromQuality, toQuality) => {
716
+ track2("media_quality_change", {
717
+ ...formatMedia(media),
718
+ from_quality: fromQuality,
719
+ to_quality: toQuality
720
+ });
721
+ }, [track2, formatMedia]);
722
+ const volumeChange = (0, import_react9.useCallback)((media, volume, muted) => {
723
+ track2("media_volume_change", {
724
+ ...formatMedia(media),
725
+ volume: Math.round(volume * 100),
726
+ muted
727
+ });
728
+ }, [track2, formatMedia]);
729
+ const speedChange = (0, import_react9.useCallback)((media, speed) => {
730
+ track2("media_speed_change", {
731
+ ...formatMedia(media),
732
+ playback_speed: speed
733
+ });
734
+ }, [track2, formatMedia]);
735
+ return {
736
+ // Core playback
737
+ play,
738
+ pause,
739
+ complete,
740
+ seek,
741
+ // Progress tracking
742
+ trackProgress,
743
+ // Quality & settings
744
+ buffer,
745
+ qualityChange,
746
+ volumeChange,
747
+ speedChange,
748
+ // Utilities
749
+ reset,
750
+ getListenDuration
751
+ };
752
+ }
753
+
754
+ // src/react/useBalanceEngagement.ts
755
+ var import_react10 = require("react");
756
+ function useBalanceEngagement(contentId, contentTitle) {
757
+ const { track: track2 } = useBalance();
758
+ const scrollMilestonesRef = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
759
+ const pageLoadTimeRef = (0, import_react10.useRef)(null);
760
+ const isTrackingTimeRef = (0, import_react10.useRef)(false);
761
+ const timeIntervalsRef = (0, import_react10.useRef)([30, 60, 120, 300]);
762
+ const timeTrackedRef = (0, import_react10.useRef)(/* @__PURE__ */ new Set());
763
+ const getBaseProps = (0, import_react10.useCallback)(() => ({
764
+ content_id: contentId,
765
+ content_title: contentTitle
766
+ }), [contentId, contentTitle]);
767
+ const getTimeOnPage = (0, import_react10.useCallback)(() => {
768
+ if (!pageLoadTimeRef.current)
769
+ return 0;
770
+ return Math.round((Date.now() - pageLoadTimeRef.current) / 1e3);
771
+ }, []);
772
+ const reset = (0, import_react10.useCallback)(() => {
773
+ scrollMilestonesRef.current.clear();
774
+ pageLoadTimeRef.current = null;
775
+ isTrackingTimeRef.current = false;
776
+ timeTrackedRef.current.clear();
777
+ }, []);
778
+ const share = (0, import_react10.useCallback)((platform, contentType) => {
779
+ track2("content_shared", {
780
+ ...getBaseProps(),
781
+ platform,
782
+ content_type: contentType,
783
+ time_on_page: getTimeOnPage()
784
+ });
785
+ }, [track2, getBaseProps, getTimeOnPage]);
786
+ const like = (0, import_react10.useCallback)((contentType) => {
787
+ track2("content_liked", {
788
+ ...getBaseProps(),
789
+ content_type: contentType,
790
+ time_on_page: getTimeOnPage()
791
+ });
792
+ }, [track2, getBaseProps, getTimeOnPage]);
793
+ const unlike = (0, import_react10.useCallback)((contentType) => {
794
+ track2("content_unliked", {
795
+ ...getBaseProps(),
796
+ content_type: contentType
797
+ });
798
+ }, [track2, getBaseProps]);
799
+ const comment = (0, import_react10.useCallback)((commentLength) => {
800
+ track2("comment_added", {
801
+ ...getBaseProps(),
802
+ comment_length: commentLength,
803
+ time_on_page: getTimeOnPage()
804
+ });
805
+ }, [track2, getBaseProps, getTimeOnPage]);
806
+ const save = (0, import_react10.useCallback)((contentType) => {
807
+ track2("content_saved", {
808
+ ...getBaseProps(),
809
+ content_type: contentType,
810
+ time_on_page: getTimeOnPage()
811
+ });
812
+ }, [track2, getBaseProps, getTimeOnPage]);
813
+ const trackScrollDepth = (0, import_react10.useCallback)((scrollPercent) => {
814
+ const milestones = [25, 50, 75, 100];
815
+ milestones.forEach((milestone) => {
816
+ if (scrollPercent >= milestone && !scrollMilestonesRef.current.has(milestone)) {
817
+ scrollMilestonesRef.current.add(milestone);
818
+ track2("scroll_depth", {
819
+ ...getBaseProps(),
820
+ depth: milestone,
821
+ time_on_page: getTimeOnPage()
822
+ });
823
+ }
824
+ });
825
+ }, [track2, getBaseProps, getTimeOnPage]);
826
+ const trackTimeOnPage = (0, import_react10.useCallback)(() => {
827
+ if (isTrackingTimeRef.current)
828
+ return;
829
+ pageLoadTimeRef.current = Date.now();
830
+ isTrackingTimeRef.current = true;
831
+ const intervalId = setInterval(() => {
832
+ const timeOnPage = getTimeOnPage();
833
+ timeIntervalsRef.current.forEach((milestone) => {
834
+ if (timeOnPage >= milestone && !timeTrackedRef.current.has(milestone)) {
835
+ timeTrackedRef.current.add(milestone);
836
+ track2("time_on_page", {
837
+ ...getBaseProps(),
838
+ seconds: milestone,
839
+ scroll_depth: Math.max(...Array.from(scrollMilestonesRef.current), 0)
840
+ });
841
+ }
842
+ });
843
+ const maxMilestone = Math.max(...timeIntervalsRef.current);
844
+ if (timeOnPage > maxMilestone) {
845
+ clearInterval(intervalId);
846
+ }
847
+ }, 5e3);
848
+ return () => clearInterval(intervalId);
849
+ }, [track2, getBaseProps, getTimeOnPage]);
850
+ const click = (0, import_react10.useCallback)((elementType, elementId) => {
851
+ track2("content_click", {
852
+ ...getBaseProps(),
853
+ element_type: elementType,
854
+ element_id: elementId,
855
+ time_on_page: getTimeOnPage()
856
+ });
857
+ }, [track2, getBaseProps, getTimeOnPage]);
858
+ const copy = (0, import_react10.useCallback)((textLength) => {
859
+ track2("content_copied", {
860
+ ...getBaseProps(),
861
+ text_length: textLength,
862
+ time_on_page: getTimeOnPage()
863
+ });
864
+ }, [track2, getBaseProps, getTimeOnPage]);
865
+ const download = (0, import_react10.useCallback)((fileName, fileType) => {
866
+ track2("content_downloaded", {
867
+ ...getBaseProps(),
868
+ file_name: fileName,
869
+ file_type: fileType,
870
+ time_on_page: getTimeOnPage()
871
+ });
872
+ }, [track2, getBaseProps, getTimeOnPage]);
873
+ const expand = (0, import_react10.useCallback)((sectionName) => {
874
+ track2("content_expanded", {
875
+ ...getBaseProps(),
876
+ section_name: sectionName,
877
+ time_on_page: getTimeOnPage()
878
+ });
879
+ }, [track2, getBaseProps, getTimeOnPage]);
880
+ (0, import_react10.useEffect)(() => {
881
+ return () => {
882
+ if (isTrackingTimeRef.current && pageLoadTimeRef.current) {
883
+ const finalTime = getTimeOnPage();
884
+ const maxScrollDepth = Math.max(...Array.from(scrollMilestonesRef.current), 0);
885
+ if (finalTime > 5) {
886
+ track2("content_exit", {
887
+ ...getBaseProps(),
888
+ time_on_page: finalTime,
889
+ max_scroll_depth: maxScrollDepth
890
+ });
891
+ }
892
+ }
893
+ };
894
+ }, [track2, getBaseProps, getTimeOnPage]);
895
+ return {
896
+ // Social engagement
897
+ share,
898
+ like,
899
+ unlike,
900
+ comment,
901
+ save,
902
+ // Scroll tracking
903
+ trackScrollDepth,
904
+ // Time tracking
905
+ trackTimeOnPage,
906
+ getTimeOnPage,
907
+ // Click & interaction
908
+ click,
909
+ copy,
910
+ download,
911
+ expand,
912
+ // Utilities
913
+ reset
914
+ };
915
+ }
916
+
917
+ // src/react/useBalanceForm.ts
918
+ var import_react11 = require("react");
919
+ function useBalanceForm(formId, formName) {
920
+ const { track: track2, identify: identify2 } = useBalance();
921
+ const startTimeRef = (0, import_react11.useRef)(null);
922
+ const fieldsRef = (0, import_react11.useRef)(/* @__PURE__ */ new Map());
923
+ const wasSubmittedRef = (0, import_react11.useRef)(false);
924
+ const hasStartedRef = (0, import_react11.useRef)(false);
925
+ const currentStepRef = (0, import_react11.useRef)(1);
926
+ const errorCountRef = (0, import_react11.useRef)(0);
927
+ const getBaseProps = (0, import_react11.useCallback)(() => ({
928
+ form_id: formId,
929
+ form_name: formName
930
+ }), [formId, formName]);
931
+ const getFormDuration = (0, import_react11.useCallback)(() => {
932
+ if (!startTimeRef.current)
933
+ return 0;
934
+ return Math.round((Date.now() - startTimeRef.current) / 1e3);
935
+ }, []);
936
+ const getFieldsCompleted = (0, import_react11.useCallback)(() => {
937
+ return Array.from(fieldsRef.current.values()).filter((f) => f.completed).length;
938
+ }, []);
939
+ const hasStarted = (0, import_react11.useCallback)(() => {
940
+ return hasStartedRef.current;
941
+ }, []);
942
+ const wasSubmitted = (0, import_react11.useCallback)(() => {
943
+ return wasSubmittedRef.current;
944
+ }, []);
945
+ const reset = (0, import_react11.useCallback)(() => {
946
+ startTimeRef.current = null;
947
+ fieldsRef.current.clear();
948
+ wasSubmittedRef.current = false;
949
+ hasStartedRef.current = false;
950
+ currentStepRef.current = 1;
951
+ errorCountRef.current = 0;
952
+ }, []);
953
+ const formStart = (0, import_react11.useCallback)(() => {
954
+ if (hasStartedRef.current)
955
+ return;
956
+ startTimeRef.current = Date.now();
957
+ hasStartedRef.current = true;
958
+ track2("form_started", {
959
+ ...getBaseProps()
960
+ });
961
+ }, [track2, getBaseProps]);
962
+ const formSubmit = (0, import_react11.useCallback)((email, additionalData) => {
963
+ wasSubmittedRef.current = true;
964
+ const duration = getFormDuration();
965
+ const fieldsCompleted = getFieldsCompleted();
966
+ const totalFields = fieldsRef.current.size;
967
+ if (email) {
968
+ identify2(email, {
969
+ source: formId,
970
+ form_name: formName,
971
+ ...additionalData
972
+ });
973
+ }
974
+ track2("form_submitted", {
975
+ ...getBaseProps(),
976
+ duration_seconds: duration,
977
+ fields_completed: fieldsCompleted,
978
+ total_fields: totalFields,
979
+ error_count: errorCountRef.current,
980
+ current_step: currentStepRef.current,
981
+ ...additionalData
982
+ });
983
+ }, [track2, identify2, getBaseProps, getFormDuration, getFieldsCompleted, formId, formName]);
984
+ const formAbandoned = (0, import_react11.useCallback)((reason) => {
985
+ if (!hasStartedRef.current || wasSubmittedRef.current)
986
+ return;
987
+ const duration = getFormDuration();
988
+ const fieldsCompleted = getFieldsCompleted();
989
+ const totalFields = fieldsRef.current.size;
990
+ let lastField;
991
+ let lastInteractionTime = 0;
992
+ fieldsRef.current.forEach((field, name) => {
993
+ const time = field.blurTime || field.focusTime;
994
+ if (time > lastInteractionTime) {
995
+ lastInteractionTime = time;
996
+ lastField = name;
997
+ }
998
+ });
999
+ track2("form_abandoned", {
1000
+ ...getBaseProps(),
1001
+ duration_seconds: duration,
1002
+ fields_completed: fieldsCompleted,
1003
+ total_fields: totalFields,
1004
+ last_field: lastField,
1005
+ current_step: currentStepRef.current,
1006
+ error_count: errorCountRef.current,
1007
+ reason
1008
+ });
1009
+ }, [track2, getBaseProps, getFormDuration, getFieldsCompleted]);
1010
+ const fieldFocus = (0, import_react11.useCallback)((fieldName, fieldType) => {
1011
+ if (!hasStartedRef.current) {
1012
+ formStart();
1013
+ }
1014
+ const existing = fieldsRef.current.get(fieldName);
1015
+ if (!existing) {
1016
+ fieldsRef.current.set(fieldName, {
1017
+ name: fieldName,
1018
+ type: fieldType,
1019
+ focusTime: Date.now(),
1020
+ completed: false,
1021
+ errorCount: 0
1022
+ });
1023
+ track2("field_focused", {
1024
+ ...getBaseProps(),
1025
+ field_name: fieldName,
1026
+ field_type: fieldType,
1027
+ field_order: fieldsRef.current.size,
1028
+ time_to_field: getFormDuration()
1029
+ });
1030
+ } else {
1031
+ existing.focusTime = Date.now();
1032
+ }
1033
+ }, [track2, getBaseProps, getFormDuration, formStart]);
1034
+ const fieldBlur = (0, import_react11.useCallback)((fieldName, fieldType, hasValue) => {
1035
+ const field = fieldsRef.current.get(fieldName);
1036
+ if (field) {
1037
+ field.blurTime = Date.now();
1038
+ field.completed = hasValue;
1039
+ const timeInField = field.blurTime - field.focusTime;
1040
+ track2("field_completed", {
1041
+ ...getBaseProps(),
1042
+ field_name: fieldName,
1043
+ field_type: fieldType,
1044
+ has_value: hasValue,
1045
+ time_in_field_ms: timeInField
1046
+ });
1047
+ }
1048
+ }, [track2, getBaseProps]);
1049
+ const fieldError = (0, import_react11.useCallback)((fieldName, errorMessage) => {
1050
+ errorCountRef.current += 1;
1051
+ const field = fieldsRef.current.get(fieldName);
1052
+ if (field) {
1053
+ field.errorCount += 1;
1054
+ }
1055
+ track2("field_error", {
1056
+ ...getBaseProps(),
1057
+ field_name: fieldName,
1058
+ error_message: errorMessage,
1059
+ total_errors: errorCountRef.current
1060
+ });
1061
+ }, [track2, getBaseProps]);
1062
+ const stepChange = (0, import_react11.useCallback)((fromStep, toStep, stepName) => {
1063
+ currentStepRef.current = toStep;
1064
+ track2("form_step_change", {
1065
+ ...getBaseProps(),
1066
+ from_step: fromStep,
1067
+ to_step: toStep,
1068
+ step_name: stepName,
1069
+ direction: toStep > fromStep ? "forward" : "backward",
1070
+ duration_seconds: getFormDuration(),
1071
+ fields_completed: getFieldsCompleted()
1072
+ });
1073
+ }, [track2, getBaseProps, getFormDuration, getFieldsCompleted]);
1074
+ return {
1075
+ // Form lifecycle
1076
+ formStart,
1077
+ formSubmit,
1078
+ formAbandoned,
1079
+ // Field interactions
1080
+ fieldFocus,
1081
+ fieldBlur,
1082
+ fieldError,
1083
+ // Multi-step
1084
+ stepChange,
1085
+ // Utilities
1086
+ getFormDuration,
1087
+ getFieldsCompleted,
1088
+ reset,
1089
+ hasStarted,
1090
+ wasSubmitted
1091
+ };
1092
+ }
1093
+
1094
+ // src/react/useBalanceSearch.ts
1095
+ var import_react12 = require("react");
1096
+ function useBalanceSearch(searchContext) {
1097
+ const { track: track2 } = useBalance();
1098
+ const searchCountRef = (0, import_react12.useRef)(0);
1099
+ const clickCountRef = (0, import_react12.useRef)(0);
1100
+ const filterCountRef = (0, import_react12.useRef)(0);
1101
+ const activeFiltersRef = (0, import_react12.useRef)(/* @__PURE__ */ new Map());
1102
+ const lastQueryRef = (0, import_react12.useRef)("");
1103
+ const getBaseProps = (0, import_react12.useCallback)(() => ({
1104
+ search_context: searchContext,
1105
+ session_searches: searchCountRef.current,
1106
+ session_clicks: clickCountRef.current
1107
+ }), [searchContext]);
1108
+ const getSearchStats = (0, import_react12.useCallback)(() => ({
1109
+ searches: searchCountRef.current,
1110
+ clicks: clickCountRef.current,
1111
+ filters: filterCountRef.current
1112
+ }), []);
1113
+ const reset = (0, import_react12.useCallback)(() => {
1114
+ searchCountRef.current = 0;
1115
+ clickCountRef.current = 0;
1116
+ filterCountRef.current = 0;
1117
+ activeFiltersRef.current.clear();
1118
+ lastQueryRef.current = "";
1119
+ }, []);
1120
+ const search = (0, import_react12.useCallback)((query, resultCount, searchType) => {
1121
+ searchCountRef.current += 1;
1122
+ lastQueryRef.current = query;
1123
+ track2("search", {
1124
+ ...getBaseProps(),
1125
+ search_term: query,
1126
+ search_type: searchType,
1127
+ result_count: resultCount,
1128
+ has_results: resultCount > 0,
1129
+ active_filters: Array.from(activeFiltersRef.current.values()),
1130
+ filter_count: activeFiltersRef.current.size
1131
+ });
1132
+ }, [track2, getBaseProps]);
1133
+ const selectResult = (0, import_react12.useCallback)((query, resultId, position, resultType) => {
1134
+ clickCountRef.current += 1;
1135
+ track2("search_result_click", {
1136
+ ...getBaseProps(),
1137
+ search_term: query,
1138
+ result_id: resultId,
1139
+ result_position: position,
1140
+ result_type: resultType
1141
+ });
1142
+ }, [track2, getBaseProps]);
1143
+ const noResults = (0, import_react12.useCallback)((query, searchType) => {
1144
+ searchCountRef.current += 1;
1145
+ lastQueryRef.current = query;
1146
+ track2("search_no_results", {
1147
+ ...getBaseProps(),
1148
+ search_term: query,
1149
+ search_type: searchType,
1150
+ active_filters: Array.from(activeFiltersRef.current.values()),
1151
+ filter_count: activeFiltersRef.current.size
1152
+ });
1153
+ }, [track2, getBaseProps]);
1154
+ const applyFilter = (0, import_react12.useCallback)((filter) => {
1155
+ filterCountRef.current += 1;
1156
+ const filterKey = `${filter.category}:${filter.value}`;
1157
+ activeFiltersRef.current.set(filterKey, filter);
1158
+ track2("search_filter_apply", {
1159
+ ...getBaseProps(),
1160
+ filter_category: filter.category,
1161
+ filter_value: filter.value,
1162
+ active_filter_count: activeFiltersRef.current.size,
1163
+ last_query: lastQueryRef.current
1164
+ });
1165
+ }, [track2, getBaseProps]);
1166
+ const removeFilter = (0, import_react12.useCallback)((filter) => {
1167
+ const filterKey = `${filter.category}:${filter.value}`;
1168
+ activeFiltersRef.current.delete(filterKey);
1169
+ track2("search_filter_remove", {
1170
+ ...getBaseProps(),
1171
+ filter_category: filter.category,
1172
+ filter_value: filter.value,
1173
+ active_filter_count: activeFiltersRef.current.size,
1174
+ last_query: lastQueryRef.current
1175
+ });
1176
+ }, [track2, getBaseProps]);
1177
+ const clearFilters = (0, import_react12.useCallback)(() => {
1178
+ const filterCount = activeFiltersRef.current.size;
1179
+ activeFiltersRef.current.clear();
1180
+ track2("search_filters_clear", {
1181
+ ...getBaseProps(),
1182
+ filters_cleared: filterCount,
1183
+ last_query: lastQueryRef.current
1184
+ });
1185
+ }, [track2, getBaseProps]);
1186
+ const showSuggestions = (0, import_react12.useCallback)((query, suggestions) => {
1187
+ track2("search_suggestions_shown", {
1188
+ ...getBaseProps(),
1189
+ query,
1190
+ suggestion_count: suggestions.length,
1191
+ suggestions: suggestions.slice(0, 5)
1192
+ // Limit to first 5
1193
+ });
1194
+ }, [track2, getBaseProps]);
1195
+ const selectSuggestion = (0, import_react12.useCallback)((originalQuery, suggestion, position) => {
1196
+ track2("search_suggestion_click", {
1197
+ ...getBaseProps(),
1198
+ original_query: originalQuery,
1199
+ selected_suggestion: suggestion,
1200
+ suggestion_position: position
1201
+ });
1202
+ }, [track2, getBaseProps]);
1203
+ const refineSearch = (0, import_react12.useCallback)((originalQuery, newQuery) => {
1204
+ track2("search_refined", {
1205
+ ...getBaseProps(),
1206
+ original_query: originalQuery,
1207
+ refined_query: newQuery,
1208
+ refinement_type: newQuery.includes(originalQuery) ? "extended" : "modified"
1209
+ });
1210
+ }, [track2, getBaseProps]);
1211
+ const loadMoreResults = (0, import_react12.useCallback)((query, page2, resultsLoaded) => {
1212
+ track2("search_load_more", {
1213
+ ...getBaseProps(),
1214
+ search_term: query,
1215
+ page: page2,
1216
+ total_results_loaded: resultsLoaded
1217
+ });
1218
+ }, [track2, getBaseProps]);
1219
+ const voiceSearch = (0, import_react12.useCallback)((transcript, resultCount) => {
1220
+ searchCountRef.current += 1;
1221
+ lastQueryRef.current = transcript;
1222
+ track2("voice_search", {
1223
+ ...getBaseProps(),
1224
+ transcript,
1225
+ result_count: resultCount,
1226
+ has_results: resultCount > 0
1227
+ });
1228
+ }, [track2, getBaseProps]);
1229
+ return {
1230
+ // Core search
1231
+ search,
1232
+ selectResult,
1233
+ noResults,
1234
+ // Filters
1235
+ applyFilter,
1236
+ removeFilter,
1237
+ clearFilters,
1238
+ // Suggestions
1239
+ showSuggestions,
1240
+ selectSuggestion,
1241
+ // Advanced
1242
+ refineSearch,
1243
+ loadMoreResults,
1244
+ voiceSearch,
1245
+ // Utilities
1246
+ getSearchStats,
1247
+ reset
1248
+ };
1249
+ }
1250
+
1251
+ // src/react/useBalanceNotification.ts
1252
+ var import_react13 = require("react");
1253
+ function useBalanceNotification() {
1254
+ const { track: track2 } = useBalance();
1255
+ const shownCountRef = (0, import_react13.useRef)(0);
1256
+ const clickedCountRef = (0, import_react13.useRef)(0);
1257
+ const dismissedCountRef = (0, import_react13.useRef)(0);
1258
+ const notificationsSeenRef = (0, import_react13.useRef)(/* @__PURE__ */ new Set());
1259
+ const getBaseProps = (0, import_react13.useCallback)(() => ({
1260
+ session_notifications_shown: shownCountRef.current,
1261
+ session_notifications_clicked: clickedCountRef.current
1262
+ }), []);
1263
+ const getStats = (0, import_react13.useCallback)(() => {
1264
+ const shown2 = shownCountRef.current;
1265
+ const clicked2 = clickedCountRef.current;
1266
+ const dismissed2 = dismissedCountRef.current;
1267
+ const ctr = shown2 > 0 ? Math.round(clicked2 / shown2 * 100) : 0;
1268
+ return { shown: shown2, clicked: clicked2, dismissed: dismissed2, ctr };
1269
+ }, []);
1270
+ const reset = (0, import_react13.useCallback)(() => {
1271
+ shownCountRef.current = 0;
1272
+ clickedCountRef.current = 0;
1273
+ dismissedCountRef.current = 0;
1274
+ notificationsSeenRef.current.clear();
1275
+ }, []);
1276
+ const shown = (0, import_react13.useCallback)((notificationId, type, campaign) => {
1277
+ if (notificationsSeenRef.current.has(notificationId))
1278
+ return;
1279
+ notificationsSeenRef.current.add(notificationId);
1280
+ shownCountRef.current += 1;
1281
+ track2("notification_shown", {
1282
+ ...getBaseProps(),
1283
+ notification_id: notificationId,
1284
+ notification_type: type,
1285
+ campaign
1286
+ });
1287
+ }, [track2, getBaseProps]);
1288
+ const clicked = (0, import_react13.useCallback)((notificationId, type, action) => {
1289
+ clickedCountRef.current += 1;
1290
+ track2("notification_clicked", {
1291
+ ...getBaseProps(),
1292
+ notification_id: notificationId,
1293
+ notification_type: type,
1294
+ action
1295
+ });
1296
+ }, [track2, getBaseProps]);
1297
+ const dismissed = (0, import_react13.useCallback)((notificationId, type, reason) => {
1298
+ dismissedCountRef.current += 1;
1299
+ track2("notification_dismissed", {
1300
+ ...getBaseProps(),
1301
+ notification_id: notificationId,
1302
+ notification_type: type,
1303
+ dismiss_reason: reason
1304
+ });
1305
+ }, [track2, getBaseProps]);
1306
+ const optIn = (0, import_react13.useCallback)((channel, source) => {
1307
+ track2("notification_opt_in", {
1308
+ ...getBaseProps(),
1309
+ channel,
1310
+ opt_in_source: source
1311
+ });
1312
+ }, [track2, getBaseProps]);
1313
+ const optOut = (0, import_react13.useCallback)((channel, reason) => {
1314
+ track2("notification_opt_out", {
1315
+ ...getBaseProps(),
1316
+ channel,
1317
+ opt_out_reason: reason
1318
+ });
1319
+ }, [track2, getBaseProps]);
1320
+ const permissionRequested = (0, import_react13.useCallback)((channel) => {
1321
+ track2("notification_permission_requested", {
1322
+ ...getBaseProps(),
1323
+ channel
1324
+ });
1325
+ }, [track2, getBaseProps]);
1326
+ const permissionDenied = (0, import_react13.useCallback)((channel) => {
1327
+ track2("notification_permission_denied", {
1328
+ ...getBaseProps(),
1329
+ channel
1330
+ });
1331
+ }, [track2, getBaseProps]);
1332
+ const preferenceUpdated = (0, import_react13.useCallback)((preferences) => {
1333
+ track2("notification_preferences_updated", {
1334
+ ...getBaseProps(),
1335
+ preferences: preferences.map((p) => ({
1336
+ channel: p.channel,
1337
+ enabled: p.enabled,
1338
+ frequency: p.frequency
1339
+ })),
1340
+ enabled_channels: preferences.filter((p) => p.enabled).map((p) => p.channel)
1341
+ });
1342
+ }, [track2, getBaseProps]);
1343
+ const emailOpened = (0, import_react13.useCallback)((emailId, campaign, subject) => {
1344
+ track2("email_opened", {
1345
+ ...getBaseProps(),
1346
+ email_id: emailId,
1347
+ campaign,
1348
+ subject
1349
+ });
1350
+ }, [track2, getBaseProps]);
1351
+ const emailLinkClicked = (0, import_react13.useCallback)((emailId, linkUrl, linkName) => {
1352
+ track2("email_link_clicked", {
1353
+ ...getBaseProps(),
1354
+ email_id: emailId,
1355
+ link_url: linkUrl,
1356
+ link_name: linkName
1357
+ });
1358
+ }, [track2, getBaseProps]);
1359
+ const emailUnsubscribed = (0, import_react13.useCallback)((emailId, reason, category) => {
1360
+ track2("email_unsubscribed", {
1361
+ ...getBaseProps(),
1362
+ email_id: emailId,
1363
+ unsubscribe_reason: reason,
1364
+ category
1365
+ });
1366
+ }, [track2, getBaseProps]);
1367
+ const pushReceived = (0, import_react13.useCallback)((notificationId, campaign) => {
1368
+ track2("push_received", {
1369
+ ...getBaseProps(),
1370
+ notification_id: notificationId,
1371
+ campaign
1372
+ });
1373
+ }, [track2, getBaseProps]);
1374
+ const inAppInteraction = (0, import_react13.useCallback)((messageId, interactionType, elementClicked) => {
1375
+ if (interactionType === "view")
1376
+ shownCountRef.current += 1;
1377
+ if (interactionType === "click")
1378
+ clickedCountRef.current += 1;
1379
+ if (interactionType === "dismiss")
1380
+ dismissedCountRef.current += 1;
1381
+ track2("in_app_message_interaction", {
1382
+ ...getBaseProps(),
1383
+ message_id: messageId,
1384
+ interaction_type: interactionType,
1385
+ element_clicked: elementClicked
1386
+ });
1387
+ }, [track2, getBaseProps]);
1388
+ return {
1389
+ // Core events
1390
+ shown,
1391
+ clicked,
1392
+ dismissed,
1393
+ // Permission & opt-in
1394
+ optIn,
1395
+ optOut,
1396
+ permissionRequested,
1397
+ permissionDenied,
1398
+ preferenceUpdated,
1399
+ // Email
1400
+ emailOpened,
1401
+ emailLinkClicked,
1402
+ emailUnsubscribed,
1403
+ // Push & in-app
1404
+ pushReceived,
1405
+ inAppInteraction,
1406
+ // Utilities
1407
+ getStats,
1408
+ reset
1409
+ };
1410
+ }
1411
+
1412
+ // src/react/useBalanceSocial.ts
1413
+ var import_react14 = require("react");
1414
+ function useBalanceSocial() {
1415
+ const { track: track2 } = useBalance();
1416
+ const followsRef = (0, import_react14.useRef)(0);
1417
+ const unfollowsRef = (0, import_react14.useRef)(0);
1418
+ const profileViewsRef = (0, import_react14.useRef)(0);
1419
+ const postsCreatedRef = (0, import_react14.useRef)(0);
1420
+ const viewedProfilesRef = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
1421
+ const getBaseProps = (0, import_react14.useCallback)(() => ({
1422
+ session_follows: followsRef.current,
1423
+ session_unfollows: unfollowsRef.current,
1424
+ session_profile_views: profileViewsRef.current
1425
+ }), []);
1426
+ const getStats = (0, import_react14.useCallback)(() => ({
1427
+ follows: followsRef.current,
1428
+ unfollows: unfollowsRef.current,
1429
+ profileViews: profileViewsRef.current,
1430
+ postsCreated: postsCreatedRef.current
1431
+ }), []);
1432
+ const reset = (0, import_react14.useCallback)(() => {
1433
+ followsRef.current = 0;
1434
+ unfollowsRef.current = 0;
1435
+ profileViewsRef.current = 0;
1436
+ postsCreatedRef.current = 0;
1437
+ viewedProfilesRef.current.clear();
1438
+ }, []);
1439
+ const viewProfile = (0, import_react14.useCallback)((entityId, entityType, source) => {
1440
+ const profileKey = `${entityType}:${entityId}`;
1441
+ const isFirstView = !viewedProfilesRef.current.has(profileKey);
1442
+ if (isFirstView) {
1443
+ viewedProfilesRef.current.add(profileKey);
1444
+ profileViewsRef.current += 1;
1445
+ }
1446
+ track2("profile_viewed", {
1447
+ ...getBaseProps(),
1448
+ entity_id: entityId,
1449
+ entity_type: entityType,
1450
+ source,
1451
+ is_first_view: isFirstView
1452
+ });
1453
+ }, [track2, getBaseProps]);
1454
+ const follow = (0, import_react14.useCallback)((entityId, entityType, source) => {
1455
+ followsRef.current += 1;
1456
+ track2("follow", {
1457
+ ...getBaseProps(),
1458
+ entity_id: entityId,
1459
+ entity_type: entityType,
1460
+ source
1461
+ });
1462
+ }, [track2, getBaseProps]);
1463
+ const unfollow = (0, import_react14.useCallback)((entityId, entityType, reason) => {
1464
+ unfollowsRef.current += 1;
1465
+ track2("unfollow", {
1466
+ ...getBaseProps(),
1467
+ entity_id: entityId,
1468
+ entity_type: entityType,
1469
+ reason
1470
+ });
1471
+ }, [track2, getBaseProps]);
1472
+ const block = (0, import_react14.useCallback)((entityId, entityType, reason) => {
1473
+ track2("block", {
1474
+ ...getBaseProps(),
1475
+ entity_id: entityId,
1476
+ entity_type: entityType,
1477
+ reason
1478
+ });
1479
+ }, [track2, getBaseProps]);
1480
+ const mute = (0, import_react14.useCallback)((entityId, entityType) => {
1481
+ track2("mute", {
1482
+ ...getBaseProps(),
1483
+ entity_id: entityId,
1484
+ entity_type: entityType
1485
+ });
1486
+ }, [track2, getBaseProps]);
1487
+ const report = (0, import_react14.useCallback)((entityId, entityType, reason, details) => {
1488
+ track2("report", {
1489
+ ...getBaseProps(),
1490
+ entity_id: entityId,
1491
+ entity_type: entityType,
1492
+ reason,
1493
+ details
1494
+ });
1495
+ }, [track2, getBaseProps]);
1496
+ const mention = (0, import_react14.useCallback)((mentionedId, mentionedType, context) => {
1497
+ track2("mention_sent", {
1498
+ ...getBaseProps(),
1499
+ mentioned_id: mentionedId,
1500
+ mentioned_type: mentionedType,
1501
+ context
1502
+ });
1503
+ }, [track2, getBaseProps]);
1504
+ const wasMentioned = (0, import_react14.useCallback)((byUserId, context, contentId) => {
1505
+ track2("mention_received", {
1506
+ ...getBaseProps(),
1507
+ by_user_id: byUserId,
1508
+ context,
1509
+ content_id: contentId
1510
+ });
1511
+ }, [track2, getBaseProps]);
1512
+ const inviteSent = (0, import_react14.useCallback)((channel, recipientCount) => {
1513
+ track2("invite_sent", {
1514
+ ...getBaseProps(),
1515
+ channel,
1516
+ recipient_count: recipientCount || 1
1517
+ });
1518
+ }, [track2, getBaseProps]);
1519
+ const inviteAccepted = (0, import_react14.useCallback)((inviteCode, inviterId) => {
1520
+ track2("invite_accepted", {
1521
+ ...getBaseProps(),
1522
+ invite_code: inviteCode,
1523
+ inviter_id: inviterId
1524
+ });
1525
+ }, [track2, getBaseProps]);
1526
+ const joinCommunity = (0, import_react14.useCallback)((communityId, communityName, source) => {
1527
+ track2("community_joined", {
1528
+ ...getBaseProps(),
1529
+ community_id: communityId,
1530
+ community_name: communityName,
1531
+ source
1532
+ });
1533
+ }, [track2, getBaseProps]);
1534
+ const leaveCommunity = (0, import_react14.useCallback)((communityId, communityName, reason) => {
1535
+ track2("community_left", {
1536
+ ...getBaseProps(),
1537
+ community_id: communityId,
1538
+ community_name: communityName,
1539
+ reason
1540
+ });
1541
+ }, [track2, getBaseProps]);
1542
+ const createPost = (0, import_react14.useCallback)((postId, postType, communityId) => {
1543
+ postsCreatedRef.current += 1;
1544
+ track2("post_created", {
1545
+ ...getBaseProps(),
1546
+ post_id: postId,
1547
+ post_type: postType,
1548
+ community_id: communityId
1549
+ });
1550
+ }, [track2, getBaseProps]);
1551
+ const replyToPost = (0, import_react14.useCallback)((postId, replyId, depth) => {
1552
+ track2("reply_created", {
1553
+ ...getBaseProps(),
1554
+ post_id: postId,
1555
+ reply_id: replyId,
1556
+ depth: depth || 1
1557
+ });
1558
+ }, [track2, getBaseProps]);
1559
+ const react = (0, import_react14.useCallback)((contentId, reactionType, contentType) => {
1560
+ track2("reaction", {
1561
+ ...getBaseProps(),
1562
+ content_id: contentId,
1563
+ reaction_type: reactionType,
1564
+ content_type: contentType
1565
+ });
1566
+ }, [track2, getBaseProps]);
1567
+ const editProfile = (0, import_react14.useCallback)((fieldsEdited) => {
1568
+ track2("profile_edited", {
1569
+ ...getBaseProps(),
1570
+ fields_edited: fieldsEdited,
1571
+ field_count: fieldsEdited.length
1572
+ });
1573
+ }, [track2, getBaseProps]);
1574
+ const changeAvatar = (0, import_react14.useCallback)((source) => {
1575
+ track2("avatar_changed", {
1576
+ ...getBaseProps(),
1577
+ source
1578
+ });
1579
+ }, [track2, getBaseProps]);
1580
+ const connectionRequest = (0, import_react14.useCallback)((targetId, targetType) => {
1581
+ track2("connection_requested", {
1582
+ ...getBaseProps(),
1583
+ target_id: targetId,
1584
+ target_type: targetType
1585
+ });
1586
+ }, [track2, getBaseProps]);
1587
+ const connectionAccepted = (0, import_react14.useCallback)((requesterId, requesterType) => {
1588
+ track2("connection_accepted", {
1589
+ ...getBaseProps(),
1590
+ requester_id: requesterId,
1591
+ requester_type: requesterType
1592
+ });
1593
+ }, [track2, getBaseProps]);
1594
+ return {
1595
+ // Profile & follow
1596
+ viewProfile,
1597
+ follow,
1598
+ unfollow,
1599
+ block,
1600
+ mute,
1601
+ report,
1602
+ // Mentions
1603
+ mention,
1604
+ wasMentioned,
1605
+ // Invites
1606
+ inviteSent,
1607
+ inviteAccepted,
1608
+ // Community
1609
+ joinCommunity,
1610
+ leaveCommunity,
1611
+ // Content
1612
+ createPost,
1613
+ replyToPost,
1614
+ react,
1615
+ // Profile management
1616
+ editProfile,
1617
+ changeAvatar,
1618
+ // Connections
1619
+ connectionRequest,
1620
+ connectionAccepted,
1621
+ // Utilities
1622
+ getStats,
1623
+ reset
1624
+ };
1625
+ }
1626
+
1627
+ // src/react/useBalanceAuth.ts
1628
+ var import_react15 = require("react");
1629
+ function useBalanceAuth() {
1630
+ const { track: track2, identify: identify2 } = useBalance();
1631
+ const loginAttemptsRef = (0, import_react15.useRef)(0);
1632
+ const loginSuccessesRef = (0, import_react15.useRef)(0);
1633
+ const loginFailuresRef = (0, import_react15.useRef)(0);
1634
+ const authStartTimeRef = (0, import_react15.useRef)(null);
1635
+ const getBaseProps = (0, import_react15.useCallback)(() => ({
1636
+ session_login_attempts: loginAttemptsRef.current,
1637
+ session_login_successes: loginSuccessesRef.current
1638
+ }), []);
1639
+ const getAuthDuration = (0, import_react15.useCallback)(() => {
1640
+ if (!authStartTimeRef.current)
1641
+ return 0;
1642
+ return Math.round((Date.now() - authStartTimeRef.current) / 1e3);
1643
+ }, []);
1644
+ const getStats = (0, import_react15.useCallback)(() => ({
1645
+ loginAttempts: loginAttemptsRef.current,
1646
+ loginSuccesses: loginSuccessesRef.current,
1647
+ loginFailures: loginFailuresRef.current
1648
+ }), []);
1649
+ const reset = (0, import_react15.useCallback)(() => {
1650
+ loginAttemptsRef.current = 0;
1651
+ loginSuccessesRef.current = 0;
1652
+ loginFailuresRef.current = 0;
1653
+ authStartTimeRef.current = null;
1654
+ }, []);
1655
+ const signUpStart = (0, import_react15.useCallback)((method, source) => {
1656
+ authStartTimeRef.current = Date.now();
1657
+ track2("sign_up_start", {
1658
+ ...getBaseProps(),
1659
+ method,
1660
+ source
1661
+ });
1662
+ }, [track2, getBaseProps]);
1663
+ const signUpSuccess = (0, import_react15.useCallback)((method, email) => {
1664
+ loginSuccessesRef.current += 1;
1665
+ if (email) {
1666
+ identify2(email, { auth_method: method, signup_date: (/* @__PURE__ */ new Date()).toISOString() });
1667
+ }
1668
+ track2("sign_up", {
1669
+ ...getBaseProps(),
1670
+ method,
1671
+ duration_seconds: getAuthDuration()
1672
+ });
1673
+ authStartTimeRef.current = null;
1674
+ }, [track2, identify2, getBaseProps, getAuthDuration]);
1675
+ const signUpFailed = (0, import_react15.useCallback)((method, errorCode, errorMessage) => {
1676
+ loginFailuresRef.current += 1;
1677
+ track2("sign_up_failed", {
1678
+ ...getBaseProps(),
1679
+ method,
1680
+ error_code: errorCode,
1681
+ error_message: errorMessage,
1682
+ duration_seconds: getAuthDuration()
1683
+ });
1684
+ }, [track2, getBaseProps, getAuthDuration]);
1685
+ const verificationSent = (0, import_react15.useCallback)((method) => {
1686
+ track2("verification_sent", {
1687
+ ...getBaseProps(),
1688
+ method
1689
+ });
1690
+ }, [track2, getBaseProps]);
1691
+ const verified = (0, import_react15.useCallback)((method) => {
1692
+ track2("verified", {
1693
+ ...getBaseProps(),
1694
+ method
1695
+ });
1696
+ }, [track2, getBaseProps]);
1697
+ const loginStart = (0, import_react15.useCallback)((method, source) => {
1698
+ loginAttemptsRef.current += 1;
1699
+ authStartTimeRef.current = Date.now();
1700
+ track2("login_start", {
1701
+ ...getBaseProps(),
1702
+ method,
1703
+ source
1704
+ });
1705
+ }, [track2, getBaseProps]);
1706
+ const loginSuccess = (0, import_react15.useCallback)((method, email) => {
1707
+ loginSuccessesRef.current += 1;
1708
+ if (email) {
1709
+ identify2(email, { auth_method: method, last_login: (/* @__PURE__ */ new Date()).toISOString() });
1710
+ }
1711
+ track2("login", {
1712
+ ...getBaseProps(),
1713
+ method,
1714
+ duration_seconds: getAuthDuration()
1715
+ });
1716
+ authStartTimeRef.current = null;
1717
+ }, [track2, identify2, getBaseProps, getAuthDuration]);
1718
+ const loginFailed = (0, import_react15.useCallback)((method, errorCode, errorMessage) => {
1719
+ loginFailuresRef.current += 1;
1720
+ track2("login_failed", {
1721
+ ...getBaseProps(),
1722
+ method,
1723
+ error_code: errorCode,
1724
+ error_message: errorMessage,
1725
+ duration_seconds: getAuthDuration()
1726
+ });
1727
+ }, [track2, getBaseProps, getAuthDuration]);
1728
+ const logout = (0, import_react15.useCallback)((reason = "user_initiated") => {
1729
+ track2("logout", {
1730
+ ...getBaseProps(),
1731
+ reason
1732
+ });
1733
+ }, [track2, getBaseProps]);
1734
+ const passwordResetRequested = (0, import_react15.useCallback)((method) => {
1735
+ track2("password_reset_requested", {
1736
+ ...getBaseProps(),
1737
+ method
1738
+ });
1739
+ }, [track2, getBaseProps]);
1740
+ const passwordResetCompleted = (0, import_react15.useCallback)(() => {
1741
+ track2("password_reset_completed", {
1742
+ ...getBaseProps()
1743
+ });
1744
+ }, [track2, getBaseProps]);
1745
+ const passwordChanged = (0, import_react15.useCallback)((source) => {
1746
+ track2("password_changed", {
1747
+ ...getBaseProps(),
1748
+ source
1749
+ });
1750
+ }, [track2, getBaseProps]);
1751
+ const mfaSetupStart = (0, import_react15.useCallback)((method) => {
1752
+ track2("mfa_setup_start", {
1753
+ ...getBaseProps(),
1754
+ method
1755
+ });
1756
+ }, [track2, getBaseProps]);
1757
+ const mfaSetupCompleted = (0, import_react15.useCallback)((method) => {
1758
+ track2("mfa_setup_completed", {
1759
+ ...getBaseProps(),
1760
+ method
1761
+ });
1762
+ }, [track2, getBaseProps]);
1763
+ const mfaChallenge = (0, import_react15.useCallback)((method) => {
1764
+ track2("mfa_challenge", {
1765
+ ...getBaseProps(),
1766
+ method
1767
+ });
1768
+ }, [track2, getBaseProps]);
1769
+ const mfaSuccess = (0, import_react15.useCallback)((method) => {
1770
+ track2("mfa_success", {
1771
+ ...getBaseProps(),
1772
+ method
1773
+ });
1774
+ }, [track2, getBaseProps]);
1775
+ const mfaFailed = (0, import_react15.useCallback)((method, reason) => {
1776
+ track2("mfa_failed", {
1777
+ ...getBaseProps(),
1778
+ method,
1779
+ reason
1780
+ });
1781
+ }, [track2, getBaseProps]);
1782
+ const mfaDisabled = (0, import_react15.useCallback)((method) => {
1783
+ track2("mfa_disabled", {
1784
+ ...getBaseProps(),
1785
+ method
1786
+ });
1787
+ }, [track2, getBaseProps]);
1788
+ const sessionStart = (0, import_react15.useCallback)((isNewUser, authMethod) => {
1789
+ track2("session_start", {
1790
+ ...getBaseProps(),
1791
+ is_new_user: isNewUser,
1792
+ auth_method: authMethod
1793
+ });
1794
+ }, [track2, getBaseProps]);
1795
+ const sessionResume = (0, import_react15.useCallback)(() => {
1796
+ track2("session_resume", {
1797
+ ...getBaseProps()
1798
+ });
1799
+ }, [track2, getBaseProps]);
1800
+ const sessionExpired = (0, import_react15.useCallback)((durationSeconds) => {
1801
+ track2("session_expired", {
1802
+ ...getBaseProps(),
1803
+ duration_seconds: durationSeconds
1804
+ });
1805
+ }, [track2, getBaseProps]);
1806
+ const sessionRefreshed = (0, import_react15.useCallback)(() => {
1807
+ track2("session_refreshed", {
1808
+ ...getBaseProps()
1809
+ });
1810
+ }, [track2, getBaseProps]);
1811
+ const accountDeletionRequested = (0, import_react15.useCallback)((reason) => {
1812
+ track2("account_deletion_requested", {
1813
+ ...getBaseProps(),
1814
+ reason
1815
+ });
1816
+ }, [track2, getBaseProps]);
1817
+ const accountDeleted = (0, import_react15.useCallback)((reason) => {
1818
+ track2("account_deleted", {
1819
+ ...getBaseProps(),
1820
+ reason
1821
+ });
1822
+ }, [track2, getBaseProps]);
1823
+ const accountLinked = (0, import_react15.useCallback)((method) => {
1824
+ track2("account_linked", {
1825
+ ...getBaseProps(),
1826
+ method
1827
+ });
1828
+ }, [track2, getBaseProps]);
1829
+ const accountUnlinked = (0, import_react15.useCallback)((method) => {
1830
+ track2("account_unlinked", {
1831
+ ...getBaseProps(),
1832
+ method
1833
+ });
1834
+ }, [track2, getBaseProps]);
1835
+ return {
1836
+ // Sign up
1837
+ signUpStart,
1838
+ signUpSuccess,
1839
+ signUpFailed,
1840
+ verificationSent,
1841
+ verified,
1842
+ // Login
1843
+ loginStart,
1844
+ loginSuccess,
1845
+ loginFailed,
1846
+ logout,
1847
+ // Password
1848
+ passwordResetRequested,
1849
+ passwordResetCompleted,
1850
+ passwordChanged,
1851
+ // MFA
1852
+ mfaSetupStart,
1853
+ mfaSetupCompleted,
1854
+ mfaChallenge,
1855
+ mfaSuccess,
1856
+ mfaFailed,
1857
+ mfaDisabled,
1858
+ // Session
1859
+ sessionStart,
1860
+ sessionResume,
1861
+ sessionExpired,
1862
+ sessionRefreshed,
1863
+ // Account
1864
+ accountDeletionRequested,
1865
+ accountDeleted,
1866
+ accountLinked,
1867
+ accountUnlinked,
1868
+ // Utilities
1869
+ getStats,
1870
+ reset
1871
+ };
1872
+ }
1873
+
1874
+ // src/react/useBalanceError.ts
1875
+ var import_react16 = require("react");
1876
+ function useBalanceError() {
1877
+ const { track: track2 } = useBalance();
1878
+ const errorCountRef = (0, import_react16.useRef)(0);
1879
+ const apiErrorCountRef = (0, import_react16.useRef)(0);
1880
+ const userReportCountRef = (0, import_react16.useRef)(0);
1881
+ const pageLoadTimesRef = (0, import_react16.useRef)([]);
1882
+ const measureStartTimesRef = (0, import_react16.useRef)(/* @__PURE__ */ new Map());
1883
+ const capturedErrorsRef = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
1884
+ const getBaseProps = (0, import_react16.useCallback)(() => ({
1885
+ session_errors: errorCountRef.current,
1886
+ session_api_errors: apiErrorCountRef.current,
1887
+ page_url: typeof window !== "undefined" ? window.location.href : void 0,
1888
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : void 0
1889
+ }), []);
1890
+ const getErrorFingerprint = (0, import_react16.useCallback)((error) => {
1891
+ return `${error.name}:${error.message}:${error.stack?.split("\n")[1] || ""}`;
1892
+ }, []);
1893
+ const getStats = (0, import_react16.useCallback)(() => {
1894
+ const times = pageLoadTimesRef.current;
1895
+ const avgPageLoad = times.length > 0 ? Math.round(times.reduce((a, b) => a + b, 0) / times.length) : null;
1896
+ return {
1897
+ errors: errorCountRef.current,
1898
+ apiErrors: apiErrorCountRef.current,
1899
+ userReports: userReportCountRef.current,
1900
+ avgPageLoad
1901
+ };
1902
+ }, []);
1903
+ const reset = (0, import_react16.useCallback)(() => {
1904
+ errorCountRef.current = 0;
1905
+ apiErrorCountRef.current = 0;
1906
+ userReportCountRef.current = 0;
1907
+ pageLoadTimesRef.current = [];
1908
+ measureStartTimesRef.current.clear();
1909
+ capturedErrorsRef.current.clear();
1910
+ }, []);
1911
+ const captureError = (0, import_react16.useCallback)((error, context, severity = "error") => {
1912
+ const fingerprint = getErrorFingerprint(error);
1913
+ if (capturedErrorsRef.current.has(fingerprint))
1914
+ return;
1915
+ capturedErrorsRef.current.add(fingerprint);
1916
+ errorCountRef.current += 1;
1917
+ track2("error_captured", {
1918
+ ...getBaseProps(),
1919
+ error_name: error.name,
1920
+ error_message: error.message,
1921
+ error_stack: error.stack?.slice(0, 1e3),
1922
+ // Limit stack trace size
1923
+ severity,
1924
+ ...context
1925
+ });
1926
+ }, [track2, getBaseProps, getErrorFingerprint]);
1927
+ const captureApiError = (0, import_react16.useCallback)((details, context) => {
1928
+ apiErrorCountRef.current += 1;
1929
+ track2("api_error", {
1930
+ ...getBaseProps(),
1931
+ api_url: details.url,
1932
+ api_method: details.method || "GET",
1933
+ status_code: details.statusCode,
1934
+ response_body: details.responseBody?.slice(0, 500),
1935
+ // Limit size
1936
+ duration_ms: details.durationMs,
1937
+ ...context
1938
+ });
1939
+ }, [track2, getBaseProps]);
1940
+ const captureUserReport = (0, import_react16.useCallback)((category, description, metadata) => {
1941
+ userReportCountRef.current += 1;
1942
+ track2("user_report", {
1943
+ ...getBaseProps(),
1944
+ report_category: category,
1945
+ report_description: description.slice(0, 1e3),
1946
+ ...metadata
1947
+ });
1948
+ }, [track2, getBaseProps]);
1949
+ const captureComponentError = (0, import_react16.useCallback)((error, componentName, componentStack) => {
1950
+ errorCountRef.current += 1;
1951
+ track2("component_error", {
1952
+ ...getBaseProps(),
1953
+ error_name: error.name,
1954
+ error_message: error.message,
1955
+ component_name: componentName,
1956
+ component_stack: componentStack?.slice(0, 1e3)
1957
+ });
1958
+ }, [track2, getBaseProps]);
1959
+ const captureUnhandledRejection = (0, import_react16.useCallback)((reason, promiseInfo) => {
1960
+ errorCountRef.current += 1;
1961
+ const message = reason instanceof Error ? reason.message : String(reason);
1962
+ track2("unhandled_rejection", {
1963
+ ...getBaseProps(),
1964
+ rejection_reason: message.slice(0, 500),
1965
+ promise_info: promiseInfo
1966
+ });
1967
+ }, [track2, getBaseProps]);
1968
+ const captureResourceError = (0, import_react16.useCallback)((resourceUrl, resourceType, details) => {
1969
+ track2("resource_error", {
1970
+ ...getBaseProps(),
1971
+ resource_url: resourceUrl,
1972
+ resource_type: resourceType,
1973
+ ...details
1974
+ });
1975
+ }, [track2, getBaseProps]);
1976
+ const trackPerformance = (0, import_react16.useCallback)((metricType, valueMs, context) => {
1977
+ if (metricType === "page_load") {
1978
+ pageLoadTimesRef.current.push(valueMs);
1979
+ }
1980
+ track2("performance_metric", {
1981
+ ...getBaseProps(),
1982
+ metric_type: metricType,
1983
+ value_ms: Math.round(valueMs),
1984
+ ...context
1985
+ });
1986
+ }, [track2, getBaseProps]);
1987
+ const trackWebVitals = (0, import_react16.useCallback)((vitals) => {
1988
+ track2("web_vitals", {
1989
+ ...getBaseProps(),
1990
+ lcp_ms: vitals.lcp ? Math.round(vitals.lcp) : void 0,
1991
+ fid_ms: vitals.fid ? Math.round(vitals.fid) : void 0,
1992
+ cls_score: vitals.cls,
1993
+ fcp_ms: vitals.fcp ? Math.round(vitals.fcp) : void 0,
1994
+ ttfb_ms: vitals.ttfb ? Math.round(vitals.ttfb) : void 0
1995
+ });
1996
+ }, [track2, getBaseProps]);
1997
+ const trackSlowInteraction = (0, import_react16.useCallback)((interactionType, durationMs, targetElement) => {
1998
+ track2("slow_interaction", {
1999
+ ...getBaseProps(),
2000
+ interaction_type: interactionType,
2001
+ duration_ms: Math.round(durationMs),
2002
+ target_element: targetElement
2003
+ });
2004
+ }, [track2, getBaseProps]);
2005
+ const startMeasure = (0, import_react16.useCallback)((measureName) => {
2006
+ measureStartTimesRef.current.set(measureName, performance.now());
2007
+ }, []);
2008
+ const endMeasure = (0, import_react16.useCallback)((measureName, metricType = "interaction") => {
2009
+ const startTime = measureStartTimesRef.current.get(measureName);
2010
+ if (!startTime)
2011
+ return null;
2012
+ const duration = performance.now() - startTime;
2013
+ measureStartTimesRef.current.delete(measureName);
2014
+ trackPerformance(metricType, duration, { measure_name: measureName });
2015
+ return Math.round(duration);
2016
+ }, [trackPerformance]);
2017
+ return {
2018
+ // Error capture
2019
+ captureError,
2020
+ captureApiError,
2021
+ captureUserReport,
2022
+ captureComponentError,
2023
+ captureUnhandledRejection,
2024
+ captureResourceError,
2025
+ // Performance
2026
+ trackPerformance,
2027
+ trackWebVitals,
2028
+ trackSlowInteraction,
2029
+ // Measurement
2030
+ startMeasure,
2031
+ endMeasure,
2032
+ // Utilities
2033
+ getStats,
2034
+ reset
2035
+ };
2036
+ }
2037
+
2038
+ // src/react/useBalanceAB.ts
2039
+ var import_react17 = require("react");
2040
+ function useBalanceAB() {
2041
+ const { track: track2 } = useBalance();
2042
+ const exposuresRef = (0, import_react17.useRef)(0);
2043
+ const conversionsRef = (0, import_react17.useRef)(0);
2044
+ const activeExperimentsRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
2045
+ const exposedExperimentsRef = (0, import_react17.useRef)(/* @__PURE__ */ new Set());
2046
+ const getBaseProps = (0, import_react17.useCallback)(() => ({
2047
+ session_exposures: exposuresRef.current,
2048
+ session_conversions: conversionsRef.current,
2049
+ active_experiment_count: activeExperimentsRef.current.size
2050
+ }), []);
2051
+ const getStorageKey = (0, import_react17.useCallback)((experimentId) => {
2052
+ return `balance_ab_${experimentId}`;
2053
+ }, []);
2054
+ const getVariant = (0, import_react17.useCallback)((experimentId) => {
2055
+ if (typeof window === "undefined")
2056
+ return null;
2057
+ try {
2058
+ const stored = localStorage.getItem(getStorageKey(experimentId));
2059
+ return stored;
2060
+ } catch {
2061
+ return null;
2062
+ }
2063
+ }, [getStorageKey]);
2064
+ const setVariant = (0, import_react17.useCallback)((experimentId, variant) => {
2065
+ if (typeof window === "undefined")
2066
+ return;
2067
+ try {
2068
+ localStorage.setItem(getStorageKey(experimentId), variant);
2069
+ activeExperimentsRef.current.set(experimentId, variant);
2070
+ } catch {
2071
+ }
2072
+ }, [getStorageKey]);
2073
+ const getActiveExperiments = (0, import_react17.useCallback)(() => {
2074
+ return new Map(activeExperimentsRef.current);
2075
+ }, []);
2076
+ const getStats = (0, import_react17.useCallback)(() => {
2077
+ const exposures = exposuresRef.current;
2078
+ const conversions = conversionsRef.current;
2079
+ const conversionRate = exposures > 0 ? Math.round(conversions / exposures * 1e4) / 100 : 0;
2080
+ return {
2081
+ exposures,
2082
+ conversions,
2083
+ activeExperiments: activeExperimentsRef.current.size,
2084
+ conversionRate
2085
+ };
2086
+ }, []);
2087
+ const reset = (0, import_react17.useCallback)(() => {
2088
+ exposuresRef.current = 0;
2089
+ conversionsRef.current = 0;
2090
+ activeExperimentsRef.current.clear();
2091
+ exposedExperimentsRef.current.clear();
2092
+ }, []);
2093
+ (0, import_react17.useEffect)(() => {
2094
+ if (typeof window === "undefined")
2095
+ return;
2096
+ try {
2097
+ for (let i = 0; i < localStorage.length; i++) {
2098
+ const key = localStorage.key(i);
2099
+ if (key?.startsWith("balance_ab_")) {
2100
+ const experimentId = key.replace("balance_ab_", "");
2101
+ const variant = localStorage.getItem(key);
2102
+ if (variant) {
2103
+ activeExperimentsRef.current.set(experimentId, variant);
2104
+ }
2105
+ }
2106
+ }
2107
+ } catch {
2108
+ }
2109
+ }, []);
2110
+ const trackExposure = (0, import_react17.useCallback)((experimentId, variant, context) => {
2111
+ const exposureKey = `${experimentId}:${variant}`;
2112
+ if (exposedExperimentsRef.current.has(exposureKey))
2113
+ return;
2114
+ exposedExperimentsRef.current.add(exposureKey);
2115
+ exposuresRef.current += 1;
2116
+ activeExperimentsRef.current.set(experimentId, variant);
2117
+ track2("experiment_exposure", {
2118
+ ...getBaseProps(),
2119
+ experiment_id: experimentId,
2120
+ variant,
2121
+ is_first_exposure: true,
2122
+ ...context
2123
+ });
2124
+ }, [track2, getBaseProps]);
2125
+ const trackConversion = (0, import_react17.useCallback)((experimentId, variant, conversionData) => {
2126
+ conversionsRef.current += 1;
2127
+ track2("experiment_conversion", {
2128
+ ...getBaseProps(),
2129
+ experiment_id: experimentId,
2130
+ variant,
2131
+ ...conversionData
2132
+ });
2133
+ }, [track2, getBaseProps]);
2134
+ const trackInteraction = (0, import_react17.useCallback)((experimentId, variant, interactionType, details) => {
2135
+ track2("experiment_interaction", {
2136
+ ...getBaseProps(),
2137
+ experiment_id: experimentId,
2138
+ variant,
2139
+ interaction_type: interactionType,
2140
+ ...details
2141
+ });
2142
+ }, [track2, getBaseProps]);
2143
+ const enrollUser = (0, import_react17.useCallback)((experimentId, variant, source = "random") => {
2144
+ setVariant(experimentId, variant);
2145
+ track2("experiment_enrolled", {
2146
+ ...getBaseProps(),
2147
+ experiment_id: experimentId,
2148
+ variant,
2149
+ enrollment_source: source
2150
+ });
2151
+ }, [track2, getBaseProps, setVariant]);
2152
+ const exitExperiment = (0, import_react17.useCallback)((experimentId, reason = "completed") => {
2153
+ const variant = activeExperimentsRef.current.get(experimentId);
2154
+ activeExperimentsRef.current.delete(experimentId);
2155
+ track2("experiment_exit", {
2156
+ ...getBaseProps(),
2157
+ experiment_id: experimentId,
2158
+ variant,
2159
+ exit_reason: reason
2160
+ });
2161
+ }, [track2, getBaseProps]);
2162
+ const trackFeatureFlag = (0, import_react17.useCallback)((flag) => {
2163
+ track2("feature_flag_evaluated", {
2164
+ ...getBaseProps(),
2165
+ flag_key: flag.key,
2166
+ flag_enabled: flag.enabled,
2167
+ flag_variant: flag.variant,
2168
+ flag_source: flag.source || "unknown"
2169
+ });
2170
+ }, [track2, getBaseProps]);
2171
+ const trackFeatureFlagToggle = (0, import_react17.useCallback)((flagKey, newValue, previousValue) => {
2172
+ track2("feature_flag_toggled", {
2173
+ ...getBaseProps(),
2174
+ flag_key: flagKey,
2175
+ new_value: newValue,
2176
+ previous_value: previousValue
2177
+ });
2178
+ }, [track2, getBaseProps]);
2179
+ const trackGoal = (0, import_react17.useCallback)((experimentId, goalName, value, context) => {
2180
+ const variant = activeExperimentsRef.current.get(experimentId);
2181
+ track2("experiment_goal", {
2182
+ ...getBaseProps(),
2183
+ experiment_id: experimentId,
2184
+ variant,
2185
+ goal_name: goalName,
2186
+ goal_value: value,
2187
+ ...context
2188
+ });
2189
+ }, [track2, getBaseProps]);
2190
+ const trackSegment = (0, import_react17.useCallback)((segmentId, segmentName, criteria) => {
2191
+ track2("segment_assigned", {
2192
+ ...getBaseProps(),
2193
+ segment_id: segmentId,
2194
+ segment_name: segmentName,
2195
+ ...criteria
2196
+ });
2197
+ }, [track2, getBaseProps]);
2198
+ return {
2199
+ // Experiment tracking
2200
+ trackExposure,
2201
+ trackConversion,
2202
+ trackInteraction,
2203
+ enrollUser,
2204
+ exitExperiment,
2205
+ // Feature flags
2206
+ trackFeatureFlag,
2207
+ trackFeatureFlagToggle,
2208
+ // Goals & segments
2209
+ trackGoal,
2210
+ trackSegment,
2211
+ // Variant management
2212
+ getVariant,
2213
+ setVariant,
2214
+ getActiveExperiments,
2215
+ // Utilities
2216
+ getStats,
2217
+ reset
2218
+ };
2219
+ }
2220
+
2221
+ // src/react/useBalanceLive.ts
2222
+ var import_react18 = require("react");
2223
+ function useBalanceLive() {
2224
+ const { track: track2 } = useBalance();
2225
+ const currentStreamRef = (0, import_react18.useRef)(null);
2226
+ const streamStartTimeRef = (0, import_react18.useRef)(null);
2227
+ const totalWatchTimeRef = (0, import_react18.useRef)(0);
2228
+ const messageCountRef = (0, import_react18.useRef)(0);
2229
+ const reactionCountRef = (0, import_react18.useRef)(0);
2230
+ const tipCountRef = (0, import_react18.useRef)(0);
2231
+ const lastMilestoneRef = (0, import_react18.useRef)(0);
2232
+ const watchTimeIntervalRef = (0, import_react18.useRef)(null);
2233
+ const getCurrentWatchTime = (0, import_react18.useCallback)(() => {
2234
+ if (!streamStartTimeRef.current)
2235
+ return 0;
2236
+ return Math.round((Date.now() - streamStartTimeRef.current) / 1e3);
2237
+ }, []);
2238
+ const getBaseProps = (0, import_react18.useCallback)(() => ({
2239
+ current_stream_id: currentStreamRef.current,
2240
+ session_watch_time_seconds: totalWatchTimeRef.current + getCurrentWatchTime(),
2241
+ session_messages: messageCountRef.current,
2242
+ session_reactions: reactionCountRef.current,
2243
+ session_tips: tipCountRef.current
2244
+ }), [getCurrentWatchTime]);
2245
+ const getStats = (0, import_react18.useCallback)(() => ({
2246
+ totalWatchTimeSeconds: totalWatchTimeRef.current + getCurrentWatchTime(),
2247
+ messagesent: messageCountRef.current,
2248
+ reactions: reactionCountRef.current,
2249
+ tips: tipCountRef.current,
2250
+ currentStreamId: currentStreamRef.current
2251
+ }), [getCurrentWatchTime]);
2252
+ const reset = (0, import_react18.useCallback)(() => {
2253
+ if (watchTimeIntervalRef.current) {
2254
+ clearInterval(watchTimeIntervalRef.current);
2255
+ watchTimeIntervalRef.current = null;
2256
+ }
2257
+ currentStreamRef.current = null;
2258
+ streamStartTimeRef.current = null;
2259
+ totalWatchTimeRef.current = 0;
2260
+ messageCountRef.current = 0;
2261
+ reactionCountRef.current = 0;
2262
+ tipCountRef.current = 0;
2263
+ lastMilestoneRef.current = 0;
2264
+ }, []);
2265
+ (0, import_react18.useEffect)(() => {
2266
+ return () => {
2267
+ if (watchTimeIntervalRef.current) {
2268
+ clearInterval(watchTimeIntervalRef.current);
2269
+ }
2270
+ };
2271
+ }, []);
2272
+ const joinStream = (0, import_react18.useCallback)((streamId, streamTitle, artistId, metadata) => {
2273
+ if (currentStreamRef.current && currentStreamRef.current !== streamId) {
2274
+ totalWatchTimeRef.current += getCurrentWatchTime();
2275
+ }
2276
+ currentStreamRef.current = streamId;
2277
+ streamStartTimeRef.current = Date.now();
2278
+ lastMilestoneRef.current = 0;
2279
+ track2("stream_joined", {
2280
+ ...getBaseProps(),
2281
+ stream_id: streamId,
2282
+ stream_title: streamTitle,
2283
+ artist_id: artistId,
2284
+ ...metadata
2285
+ });
2286
+ if (watchTimeIntervalRef.current) {
2287
+ clearInterval(watchTimeIntervalRef.current);
2288
+ }
2289
+ watchTimeIntervalRef.current = setInterval(() => {
2290
+ const minutes = Math.floor(getCurrentWatchTime() / 60);
2291
+ if (minutes > lastMilestoneRef.current && minutes % 5 === 0) {
2292
+ lastMilestoneRef.current = minutes;
2293
+ track2("viewer_milestone", {
2294
+ ...getBaseProps(),
2295
+ stream_id: streamId,
2296
+ minutes_watched: minutes
2297
+ });
2298
+ }
2299
+ }, 6e4);
2300
+ }, [track2, getBaseProps, getCurrentWatchTime]);
2301
+ const leaveStream = (0, import_react18.useCallback)((streamId, reason = "user") => {
2302
+ const watchTime = getCurrentWatchTime();
2303
+ totalWatchTimeRef.current += watchTime;
2304
+ if (watchTimeIntervalRef.current) {
2305
+ clearInterval(watchTimeIntervalRef.current);
2306
+ watchTimeIntervalRef.current = null;
2307
+ }
2308
+ track2("stream_left", {
2309
+ ...getBaseProps(),
2310
+ stream_id: streamId,
2311
+ watch_time_seconds: watchTime,
2312
+ leave_reason: reason
2313
+ });
2314
+ currentStreamRef.current = null;
2315
+ streamStartTimeRef.current = null;
2316
+ }, [track2, getBaseProps, getCurrentWatchTime]);
2317
+ const playbackStarted = (0, import_react18.useCallback)((streamId, quality, latencyMs) => {
2318
+ track2("stream_playback_started", {
2319
+ ...getBaseProps(),
2320
+ stream_id: streamId,
2321
+ quality,
2322
+ latency_ms: latencyMs
2323
+ });
2324
+ }, [track2, getBaseProps]);
2325
+ const buffering = (0, import_react18.useCallback)((streamId, durationMs, reason) => {
2326
+ track2("stream_buffering", {
2327
+ ...getBaseProps(),
2328
+ stream_id: streamId,
2329
+ buffer_duration_ms: durationMs,
2330
+ buffer_reason: reason
2331
+ });
2332
+ }, [track2, getBaseProps]);
2333
+ const qualityChanged = (0, import_react18.useCallback)((streamId, previousQuality, newQuality, isAutomatic) => {
2334
+ track2("stream_quality_changed", {
2335
+ ...getBaseProps(),
2336
+ stream_id: streamId,
2337
+ previous_quality: previousQuality,
2338
+ new_quality: newQuality,
2339
+ is_automatic: isAutomatic
2340
+ });
2341
+ }, [track2, getBaseProps]);
2342
+ const streamError = (0, import_react18.useCallback)((streamId, errorCode, errorMessage) => {
2343
+ track2("stream_error", {
2344
+ ...getBaseProps(),
2345
+ stream_id: streamId,
2346
+ error_code: errorCode,
2347
+ error_message: errorMessage
2348
+ });
2349
+ }, [track2, getBaseProps]);
2350
+ const sendChat = (0, import_react18.useCallback)((streamId, messageLength, hasEmoji) => {
2351
+ messageCountRef.current += 1;
2352
+ track2("stream_chat_sent", {
2353
+ ...getBaseProps(),
2354
+ stream_id: streamId,
2355
+ message_length: messageLength,
2356
+ has_emoji: hasEmoji
2357
+ });
2358
+ }, [track2, getBaseProps]);
2359
+ const react = (0, import_react18.useCallback)((streamId, reactionType) => {
2360
+ reactionCountRef.current += 1;
2361
+ track2("stream_reaction", {
2362
+ ...getBaseProps(),
2363
+ stream_id: streamId,
2364
+ reaction_type: reactionType
2365
+ });
2366
+ }, [track2, getBaseProps]);
2367
+ const tip = (0, import_react18.useCallback)((streamId, amount, currency, message) => {
2368
+ tipCountRef.current += 1;
2369
+ track2("stream_tip", {
2370
+ ...getBaseProps(),
2371
+ stream_id: streamId,
2372
+ tip_amount: amount,
2373
+ tip_currency: currency,
2374
+ has_message: !!message
2375
+ });
2376
+ }, [track2, getBaseProps]);
2377
+ const gift = (0, import_react18.useCallback)((streamId, giftType, recipientId, value) => {
2378
+ track2("stream_gift", {
2379
+ ...getBaseProps(),
2380
+ stream_id: streamId,
2381
+ gift_type: giftType,
2382
+ recipient_id: recipientId,
2383
+ gift_value: value
2384
+ });
2385
+ }, [track2, getBaseProps]);
2386
+ const pollVote = (0, import_react18.useCallback)((streamId, pollId, optionId) => {
2387
+ track2("stream_poll_vote", {
2388
+ ...getBaseProps(),
2389
+ stream_id: streamId,
2390
+ poll_id: pollId,
2391
+ option_id: optionId
2392
+ });
2393
+ }, [track2, getBaseProps]);
2394
+ const submitQuestion = (0, import_react18.useCallback)((streamId, questionId, isAnonymous) => {
2395
+ track2("stream_question_submitted", {
2396
+ ...getBaseProps(),
2397
+ stream_id: streamId,
2398
+ question_id: questionId,
2399
+ is_anonymous: isAnonymous
2400
+ });
2401
+ }, [track2, getBaseProps]);
2402
+ const share = (0, import_react18.useCallback)((streamId, platform) => {
2403
+ track2("stream_shared", {
2404
+ ...getBaseProps(),
2405
+ stream_id: streamId,
2406
+ share_platform: platform
2407
+ });
2408
+ }, [track2, getBaseProps]);
2409
+ const captureClip = (0, import_react18.useCallback)((streamId, clipDurationSeconds) => {
2410
+ track2("stream_clip_captured", {
2411
+ ...getBaseProps(),
2412
+ stream_id: streamId,
2413
+ clip_duration_seconds: clipDurationSeconds
2414
+ });
2415
+ }, [track2, getBaseProps]);
2416
+ const toggleFullscreen = (0, import_react18.useCallback)((streamId, isFullscreen) => {
2417
+ track2("stream_fullscreen_toggled", {
2418
+ ...getBaseProps(),
2419
+ stream_id: streamId,
2420
+ is_fullscreen: isFullscreen
2421
+ });
2422
+ }, [track2, getBaseProps]);
2423
+ const togglePiP = (0, import_react18.useCallback)((streamId, isPiP) => {
2424
+ track2("stream_pip_toggled", {
2425
+ ...getBaseProps(),
2426
+ stream_id: streamId,
2427
+ is_pip: isPiP
2428
+ });
2429
+ }, [track2, getBaseProps]);
2430
+ const volumeChanged = (0, import_react18.useCallback)((streamId, volume, isMuted) => {
2431
+ track2("stream_volume_changed", {
2432
+ ...getBaseProps(),
2433
+ stream_id: streamId,
2434
+ volume_level: Math.round(volume * 100),
2435
+ is_muted: isMuted
2436
+ });
2437
+ }, [track2, getBaseProps]);
2438
+ const viewerMilestone = (0, import_react18.useCallback)((streamId, minutesWatched) => {
2439
+ track2("viewer_milestone", {
2440
+ ...getBaseProps(),
2441
+ stream_id: streamId,
2442
+ minutes_watched: minutesWatched
2443
+ });
2444
+ }, [track2, getBaseProps]);
2445
+ return {
2446
+ // Stream lifecycle
2447
+ joinStream,
2448
+ leaveStream,
2449
+ playbackStarted,
2450
+ buffering,
2451
+ qualityChanged,
2452
+ streamError,
2453
+ // Viewer interactions
2454
+ sendChat,
2455
+ react,
2456
+ tip,
2457
+ gift,
2458
+ pollVote,
2459
+ submitQuestion,
2460
+ share,
2461
+ // Viewer controls
2462
+ captureClip,
2463
+ toggleFullscreen,
2464
+ togglePiP,
2465
+ volumeChanged,
2466
+ viewerMilestone,
2467
+ // Utilities
2468
+ getStats,
2469
+ reset
2470
+ };
2471
+ }
2472
+
2473
+ // src/react/useBalanceSubscription.ts
2474
+ var import_react19 = require("react");
2475
+ function useBalanceSubscription() {
2476
+ const { track: track2, purchase: purchase2 } = useBalance();
2477
+ const subscriptionCountRef = (0, import_react19.useRef)(0);
2478
+ const upgradeCountRef = (0, import_react19.useRef)(0);
2479
+ const downgradeCountRef = (0, import_react19.useRef)(0);
2480
+ const cancellationCountRef = (0, import_react19.useRef)(0);
2481
+ const totalRevenueRef = (0, import_react19.useRef)(0);
2482
+ const getBaseProps = (0, import_react19.useCallback)(() => ({
2483
+ session_subscriptions: subscriptionCountRef.current,
2484
+ session_upgrades: upgradeCountRef.current,
2485
+ session_cancellations: cancellationCountRef.current
2486
+ }), []);
2487
+ const getStats = (0, import_react19.useCallback)(() => ({
2488
+ subscriptions: subscriptionCountRef.current,
2489
+ upgrades: upgradeCountRef.current,
2490
+ downgrades: downgradeCountRef.current,
2491
+ cancellations: cancellationCountRef.current,
2492
+ totalRevenue: totalRevenueRef.current
2493
+ }), []);
2494
+ const reset = (0, import_react19.useCallback)(() => {
2495
+ subscriptionCountRef.current = 0;
2496
+ upgradeCountRef.current = 0;
2497
+ downgradeCountRef.current = 0;
2498
+ cancellationCountRef.current = 0;
2499
+ totalRevenueRef.current = 0;
2500
+ }, []);
2501
+ const subscribe = (0, import_react19.useCallback)((planId, price, currency, interval, metadata) => {
2502
+ subscriptionCountRef.current += 1;
2503
+ totalRevenueRef.current += price;
2504
+ purchase2(price, currency, {
2505
+ type: "subscription",
2506
+ plan_id: planId,
2507
+ interval
2508
+ });
2509
+ track2("subscription_started", {
2510
+ ...getBaseProps(),
2511
+ plan_id: planId,
2512
+ price,
2513
+ currency,
2514
+ billing_interval: interval,
2515
+ ...metadata
2516
+ });
2517
+ }, [track2, purchase2, getBaseProps]);
2518
+ const startTrial = (0, import_react19.useCallback)((planId, trialDays, willConvertTo) => {
2519
+ track2("trial_started", {
2520
+ ...getBaseProps(),
2521
+ plan_id: planId,
2522
+ trial_days: trialDays,
2523
+ will_convert_to: willConvertTo
2524
+ });
2525
+ }, [track2, getBaseProps]);
2526
+ const trialConverted = (0, import_react19.useCallback)((planId, price, currency) => {
2527
+ subscriptionCountRef.current += 1;
2528
+ totalRevenueRef.current += price;
2529
+ purchase2(price, currency, {
2530
+ type: "trial_conversion",
2531
+ plan_id: planId
2532
+ });
2533
+ track2("trial_converted", {
2534
+ ...getBaseProps(),
2535
+ plan_id: planId,
2536
+ price,
2537
+ currency
2538
+ });
2539
+ }, [track2, purchase2, getBaseProps]);
2540
+ const trialExpired = (0, import_react19.useCallback)((planId, reason) => {
2541
+ track2("trial_expired", {
2542
+ ...getBaseProps(),
2543
+ plan_id: planId,
2544
+ expiry_reason: reason
2545
+ });
2546
+ }, [track2, getBaseProps]);
2547
+ const upgrade = (0, import_react19.useCallback)((fromPlanId, toPlanId, priceDifference, currency) => {
2548
+ upgradeCountRef.current += 1;
2549
+ totalRevenueRef.current += priceDifference;
2550
+ track2("subscription_upgraded", {
2551
+ ...getBaseProps(),
2552
+ from_plan_id: fromPlanId,
2553
+ to_plan_id: toPlanId,
2554
+ price_difference: priceDifference,
2555
+ currency
2556
+ });
2557
+ }, [track2, getBaseProps]);
2558
+ const downgrade = (0, import_react19.useCallback)((fromPlanId, toPlanId, priceDifference, currency, reason) => {
2559
+ downgradeCountRef.current += 1;
2560
+ track2("subscription_downgraded", {
2561
+ ...getBaseProps(),
2562
+ from_plan_id: fromPlanId,
2563
+ to_plan_id: toPlanId,
2564
+ price_difference: priceDifference,
2565
+ currency,
2566
+ downgrade_reason: reason
2567
+ });
2568
+ }, [track2, getBaseProps]);
2569
+ const renewal = (0, import_react19.useCallback)((planId, price, currency, periodNumber) => {
2570
+ totalRevenueRef.current += price;
2571
+ purchase2(price, currency, {
2572
+ type: "renewal",
2573
+ plan_id: planId,
2574
+ period_number: periodNumber
2575
+ });
2576
+ track2("subscription_renewed", {
2577
+ ...getBaseProps(),
2578
+ plan_id: planId,
2579
+ price,
2580
+ currency,
2581
+ period_number: periodNumber
2582
+ });
2583
+ }, [track2, purchase2, getBaseProps]);
2584
+ const cancelInitiated = (0, import_react19.useCallback)((planId, reason, feedback) => {
2585
+ track2("cancellation_initiated", {
2586
+ ...getBaseProps(),
2587
+ plan_id: planId,
2588
+ cancellation_reason: reason,
2589
+ feedback: feedback?.slice(0, 500)
2590
+ });
2591
+ }, [track2, getBaseProps]);
2592
+ const cancel = (0, import_react19.useCallback)((planId, reason, wasInTrial, periodsCompleted) => {
2593
+ cancellationCountRef.current += 1;
2594
+ track2("subscription_canceled", {
2595
+ ...getBaseProps(),
2596
+ plan_id: planId,
2597
+ cancellation_reason: reason,
2598
+ was_in_trial: wasInTrial,
2599
+ periods_completed: periodsCompleted
2600
+ });
2601
+ }, [track2, getBaseProps]);
2602
+ const pause = (0, import_react19.useCallback)((planId, pauseDurationDays, reason) => {
2603
+ track2("subscription_paused", {
2604
+ ...getBaseProps(),
2605
+ plan_id: planId,
2606
+ pause_duration_days: pauseDurationDays,
2607
+ pause_reason: reason
2608
+ });
2609
+ }, [track2, getBaseProps]);
2610
+ const resume = (0, import_react19.useCallback)((planId, pausedDays) => {
2611
+ track2("subscription_resumed", {
2612
+ ...getBaseProps(),
2613
+ plan_id: planId,
2614
+ paused_days: pausedDays
2615
+ });
2616
+ }, [track2, getBaseProps]);
2617
+ const reactivate = (0, import_react19.useCallback)((planId, daysSinceCanceled, winbackOffer) => {
2618
+ subscriptionCountRef.current += 1;
2619
+ track2("subscription_reactivated", {
2620
+ ...getBaseProps(),
2621
+ plan_id: planId,
2622
+ days_since_canceled: daysSinceCanceled,
2623
+ winback_offer: winbackOffer
2624
+ });
2625
+ }, [track2, getBaseProps]);
2626
+ const addPaymentMethod = (0, import_react19.useCallback)((methodType, isDefault) => {
2627
+ track2("payment_method_added", {
2628
+ ...getBaseProps(),
2629
+ method_type: methodType,
2630
+ is_default: isDefault
2631
+ });
2632
+ }, [track2, getBaseProps]);
2633
+ const updatePaymentMethod = (0, import_react19.useCallback)((oldMethodType, newMethodType) => {
2634
+ track2("payment_method_updated", {
2635
+ ...getBaseProps(),
2636
+ old_method_type: oldMethodType,
2637
+ new_method_type: newMethodType
2638
+ });
2639
+ }, [track2, getBaseProps]);
2640
+ const removePaymentMethod = (0, import_react19.useCallback)((methodType) => {
2641
+ track2("payment_method_removed", {
2642
+ ...getBaseProps(),
2643
+ method_type: methodType
2644
+ });
2645
+ }, [track2, getBaseProps]);
2646
+ const paymentFailed = (0, import_react19.useCallback)((planId, amount, currency, failureReason, attemptNumber) => {
2647
+ track2("payment_failed", {
2648
+ ...getBaseProps(),
2649
+ plan_id: planId,
2650
+ amount,
2651
+ currency,
2652
+ failure_reason: failureReason,
2653
+ attempt_number: attemptNumber
2654
+ });
2655
+ }, [track2, getBaseProps]);
2656
+ const paymentRecovered = (0, import_react19.useCallback)((planId, amount, currency, daysOverdue) => {
2657
+ totalRevenueRef.current += amount;
2658
+ track2("payment_recovered", {
2659
+ ...getBaseProps(),
2660
+ plan_id: planId,
2661
+ amount,
2662
+ currency,
2663
+ days_overdue: daysOverdue
2664
+ });
2665
+ }, [track2, getBaseProps]);
2666
+ const refund = (0, import_react19.useCallback)((planId, amount, currency, reason, isPartial) => {
2667
+ totalRevenueRef.current -= amount;
2668
+ track2("refund_issued", {
2669
+ ...getBaseProps(),
2670
+ plan_id: planId,
2671
+ refund_amount: amount,
2672
+ currency,
2673
+ refund_reason: reason,
2674
+ is_partial: isPartial
2675
+ });
2676
+ }, [track2, getBaseProps]);
2677
+ const applyPromo = (0, import_react19.useCallback)((promoCode, discountPercent, discountAmount, currency) => {
2678
+ track2("promo_applied", {
2679
+ ...getBaseProps(),
2680
+ promo_code: promoCode,
2681
+ discount_percent: discountPercent,
2682
+ discount_amount: discountAmount,
2683
+ currency
2684
+ });
2685
+ }, [track2, getBaseProps]);
2686
+ const giftPurchased = (0, import_react19.useCallback)((planId, price, currency, recipientType) => {
2687
+ totalRevenueRef.current += price;
2688
+ purchase2(price, currency, {
2689
+ type: "gift_subscription",
2690
+ plan_id: planId
2691
+ });
2692
+ track2("gift_subscription_purchased", {
2693
+ ...getBaseProps(),
2694
+ plan_id: planId,
2695
+ price,
2696
+ currency,
2697
+ recipient_type: recipientType
2698
+ });
2699
+ }, [track2, purchase2, getBaseProps]);
2700
+ const giftRedeemed = (0, import_react19.useCallback)((planId, giftCode) => {
2701
+ subscriptionCountRef.current += 1;
2702
+ track2("gift_subscription_redeemed", {
2703
+ ...getBaseProps(),
2704
+ plan_id: planId,
2705
+ gift_code: giftCode
2706
+ });
2707
+ }, [track2, getBaseProps]);
2708
+ const viewBillingPage = (0, import_react19.useCallback)(() => {
2709
+ track2("billing_page_viewed", {
2710
+ ...getBaseProps()
2711
+ });
2712
+ }, [track2, getBaseProps]);
2713
+ const viewPricingPage = (0, import_react19.useCallback)((source) => {
2714
+ track2("pricing_page_viewed", {
2715
+ ...getBaseProps(),
2716
+ source
2717
+ });
2718
+ }, [track2, getBaseProps]);
2719
+ return {
2720
+ // Subscription lifecycle
2721
+ subscribe,
2722
+ startTrial,
2723
+ trialConverted,
2724
+ trialExpired,
2725
+ // Plan changes
2726
+ upgrade,
2727
+ downgrade,
2728
+ renewal,
2729
+ // Cancellation
2730
+ cancelInitiated,
2731
+ cancel,
2732
+ pause,
2733
+ resume,
2734
+ reactivate,
2735
+ // Payment methods
2736
+ addPaymentMethod,
2737
+ updatePaymentMethod,
2738
+ removePaymentMethod,
2739
+ // Payments
2740
+ paymentFailed,
2741
+ paymentRecovered,
2742
+ refund,
2743
+ // Promos & gifts
2744
+ applyPromo,
2745
+ giftPurchased,
2746
+ giftRedeemed,
2747
+ // Page views
2748
+ viewBillingPage,
2749
+ viewPricingPage,
2750
+ // Utilities
2751
+ getStats,
2752
+ reset
2753
+ };
2754
+ }
2755
+
58
2756
  // src/react/BalanceAnalytics.tsx
59
- var import_react = __toESM(require("react"));
2757
+ var import_react20 = __toESM(require("react"));
60
2758
  var import_navigation = require("next/navigation");
61
2759
  function BalanceAnalyticsInner() {
62
2760
  const pathname = (0, import_navigation.usePathname)();
63
2761
  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)
2762
+ const isFirstRender = (0, import_react20.useRef)(true);
2763
+ const lastTrackedPath = (0, import_react20.useRef)(null);
2764
+ const balance = useBalanceOptional();
2765
+ (0, import_react20.useEffect)(() => {
2766
+ if (typeof window === "undefined")
2767
+ return;
2768
+ const pageTracker = balance?.page ?? window.balance?.page;
2769
+ if (!pageTracker && !window.balance)
68
2770
  return;
69
2771
  const currentPath = pathname + (searchParams?.toString() || "");
70
2772
  if (lastTrackedPath.current === currentPath) {
@@ -81,16 +2783,22 @@ function BalanceAnalyticsInner() {
81
2783
  const url = window.location.href;
82
2784
  const title = document.title;
83
2785
  lastTrackedPath.current = currentPath;
84
- window.balance.page({ url, title });
85
- }, [pathname, searchParams]);
2786
+ if (balance?.page) {
2787
+ balance.page({ url, title });
2788
+ } else if (window.balance?.page) {
2789
+ window.balance.page({ url, title });
2790
+ } else if (window.balance) {
2791
+ window.balance("page", { url, title });
2792
+ }
2793
+ }, [pathname, searchParams, balance]);
86
2794
  return null;
87
2795
  }
88
2796
  function BalanceAnalytics() {
89
- return /* @__PURE__ */ import_react.default.createElement(import_react.Suspense, { fallback: null }, /* @__PURE__ */ import_react.default.createElement(BalanceAnalyticsInner, null));
2797
+ return /* @__PURE__ */ import_react20.default.createElement(import_react20.Suspense, { fallback: null }, /* @__PURE__ */ import_react20.default.createElement(BalanceAnalyticsInner, null));
90
2798
  }
91
2799
 
92
2800
  // src/react/useBalanceIdentify.ts
93
- var import_react2 = require("react");
2801
+ var import_react21 = require("react");
94
2802
 
95
2803
  // src/storage/StorageManager.ts
96
2804
  var DEFAULT_PREFIX = "balance_";
@@ -314,10 +3022,10 @@ function checkAnalyticsConsent() {
314
3022
  }
315
3023
  }
316
3024
  function useBalanceIdentify() {
317
- const pollIntervalRef = (0, import_react2.useRef)(null);
318
- const hasProcessedPending = (0, import_react2.useRef)(false);
3025
+ const pollIntervalRef = (0, import_react21.useRef)(null);
3026
+ const hasProcessedPending = (0, import_react21.useRef)(false);
319
3027
  const storageManager = typeof window !== "undefined" ? getStorageManager() : null;
320
- const getPendingIdentify = (0, import_react2.useCallback)(() => {
3028
+ const getPendingIdentify = (0, import_react21.useCallback)(() => {
321
3029
  if (!storageManager)
322
3030
  return null;
323
3031
  const pending = storageManager.getJSON(PENDING_IDENTIFY_KEY);
@@ -329,7 +3037,7 @@ function useBalanceIdentify() {
329
3037
  }
330
3038
  return pending;
331
3039
  }, [storageManager]);
332
- const setPendingIdentify = (0, import_react2.useCallback)((email, traits) => {
3040
+ const setPendingIdentify = (0, import_react21.useCallback)((email, traits) => {
333
3041
  if (!storageManager)
334
3042
  return;
335
3043
  storageManager.setJSON(PENDING_IDENTIFY_KEY, {
@@ -338,12 +3046,12 @@ function useBalanceIdentify() {
338
3046
  timestamp: Date.now()
339
3047
  });
340
3048
  }, [storageManager]);
341
- const clearPendingIdentify = (0, import_react2.useCallback)(() => {
3049
+ const clearPendingIdentify = (0, import_react21.useCallback)(() => {
342
3050
  if (!storageManager)
343
3051
  return;
344
3052
  storageManager.removeItem(PENDING_IDENTIFY_KEY);
345
3053
  }, [storageManager]);
346
- const identify2 = (0, import_react2.useCallback)((email, traits) => {
3054
+ const identify2 = (0, import_react21.useCallback)((email, traits) => {
347
3055
  const hasConsent2 = checkAnalyticsConsent();
348
3056
  if (!hasConsent2) {
349
3057
  console.log("[useBalanceIdentify] Skipping identify - user declined analytics consent");
@@ -362,7 +3070,7 @@ function useBalanceIdentify() {
362
3070
  setPendingIdentify(email, traits);
363
3071
  return false;
364
3072
  }, [storageManager, clearPendingIdentify, setPendingIdentify]);
365
- const processPendingIdentify = (0, import_react2.useCallback)(() => {
3073
+ const processPendingIdentify = (0, import_react21.useCallback)(() => {
366
3074
  if (hasProcessedPending.current)
367
3075
  return;
368
3076
  const pending = getPendingIdentify();
@@ -385,7 +3093,7 @@ function useBalanceIdentify() {
385
3093
  hasProcessedPending.current = true;
386
3094
  }
387
3095
  }, [getPendingIdentify, clearPendingIdentify, storageManager]);
388
- (0, import_react2.useEffect)(() => {
3096
+ (0, import_react21.useEffect)(() => {
389
3097
  processPendingIdentify();
390
3098
  const pending = getPendingIdentify();
391
3099
  if (pending && !hasProcessedPending.current) {
@@ -419,156 +3127,6 @@ function useBalanceIdentify() {
419
3127
  };
420
3128
  }
421
3129
 
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
- };
447
- }
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)(() => {
483
- if (typeof window === "undefined")
484
- return;
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
508
- };
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
- }
559
- }
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
3130
  // src/index.esm.ts
573
3131
  var track = (eventName, properties) => {
574
3132
  if (typeof window === "undefined")