@behio/storefront-sdk 0.21.0 → 0.23.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/react.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _tanstack_react_query from '@tanstack/react-query';
3
3
  import { QueryClient } from '@tanstack/react-query';
4
- import { a as BehioStorefront, P as ProductsQuery, b as PaginatedResponse, c as ProductListItem, d as ProductDetail, C as Category, e as CategoryDetail, f as ProductLabel, F as FilterField, g as Cart, h as CustomerProfile, R as RegisterInput, i as CustomerAddress, A as AddressSuggestion, j as AddressDetail, O as OrderListItem, k as OrderDetail, l as OrderAccessRequestResponse, m as OrderAccessVerifyResponse, n as CheckoutInput, o as PageDetail, p as Page, S as ShopInfo, q as ShopScripts, r as ShopSeo, s as Bundle, t as CrossSellItem, u as ActivePromotion, G as GiftCardBalance, W as WishlistItem, v as ProductReviewsResponse, w as SubmitReviewInput, x as ReturnableOrder, y as ReturnStatus, z as ReturnRequest, D as SubmitReturnInput, E as CookieConsent, H as CookieConsentInput, Q as QuoteRequest, I as SubmitQuoteInput, J as BackInStockSubscription } from './client-hCuGQERQ.js';
5
- export { K as AddToCartInput, L as AuthTokens, M as BehioApiError, N as BundleItem, T as CartDiscount, U as CartItem, V as CheckoutAddress, X as FulfillmentStatus, Y as LoginInput, Z as MessageResponse, _ as OrderItem, $ as OrderStatus, a0 as PaymentStatus, a1 as ProductPrice, a2 as ProductReview, a3 as ProductVariant } from './client-hCuGQERQ.js';
4
+ import { a as BehioStorefront, P as ProductsQuery, b as PaginatedResponse, c as ProductListItem, d as ProductDetail, C as Category, e as CategoryDetail, f as ProductLabel, F as FilterField, g as Cart, h as CustomerProfile, R as RegisterInput, i as CustomerAddress, A as AddressSuggestion, j as AddressDetail, O as OrderListItem, k as OrderDetail, l as OrderAccessRequestResponse, m as OrderAccessVerifyResponse, n as CheckoutInput, o as PageDetail, p as Page, S as ShopInfo, q as ShopScripts, r as ShopSeo, s as Bundle, t as CrossSellItem, u as ActivePromotion, G as GiftCardBalance, W as WishlistItem, v as ProductReviewsResponse, w as SubmitReviewInput, x as ReturnableOrder, y as ReturnStatus, z as ReturnRequest, D as SubmitReturnInput, E as CookieConsent, H as CookieConsentInput, Q as QuoteRequest, I as SubmitQuoteInput, J as BackInStockSubscription } from './client-B0VFrsad.js';
5
+ export { K as AddToCartInput, L as AuthTokens, M as BehioApiError, N as BundleItem, T as CartDiscount, U as CartItem, V as CheckoutAddress, X as FulfillmentStatus, Y as LoginInput, Z as MessageResponse, _ as OrderItem, $ as OrderStatus, a0 as PaymentStatus, a1 as ProductPrice, a2 as ProductReview, a3 as ProductVariant } from './client-B0VFrsad.js';
6
6
  import * as _tanstack_query_core from '@tanstack/query-core';
7
- export { formatPrice } from './index.js';
7
+ export { EcommerceEventName, EcommerceItem, EcommercePayload, formatPrice, trackEcommerceEvent } from './index.js';
8
8
 
9
9
  interface StorageAdapter {
10
10
  get(key: string): string | null;
@@ -504,6 +504,27 @@ interface StorefrontScriptsProps {
504
504
  */
505
505
  declare function StorefrontScripts({ visitorId: visitorIdProp }?: StorefrontScriptsProps): null;
506
506
 
507
+ /**
508
+ * Behio Analytics auto-tracker. Mount ONCE in the root layout (next to
509
+ * <StorefrontScripts/>).
510
+ *
511
+ * Identity is three-tiered:
512
+ * - anonymous: no consent needed — the server computes a daily-rotating hash,
513
+ * the client sends nothing identifying
514
+ * - consented: once the visitor grants analytics consent (the shop's consent
515
+ * banner, keyed by the existing `behio_visitor_id`), events carry that
516
+ * persistent first-party id — enabling returning-visitor metrics and the
517
+ * customer journey
518
+ * - customer: the backend links the id to the customer at checkout/login
519
+ *
520
+ * Captures: pageviews (load + SPA route changes), visibility-aware dwell time
521
+ * per page, click events on interactive elements, UTM + referrer on landing.
522
+ * Events are batched (flush on 20 events / 5 s / route change / pagehide) via
523
+ * keepalive fetch. Listens for the `behio:consent-changed` window event to
524
+ * pick up consent grants/revocations immediately.
525
+ */
526
+ declare function BehioAnalyticsTracker(): null;
527
+
507
528
  /** List all active bundles. */
508
529
  declare function useBundles(options?: {
509
530
  enabled?: boolean;
@@ -1028,4 +1049,4 @@ declare function useNotifyWhenAvailable(): _tanstack_react_query.UseMutationResu
1028
1049
  */
1029
1050
  declare function useBehioClient(): BehioStorefront;
1030
1051
 
1031
- export { ActivePromotion, BehioProvider, type BehioProviderProps, Bundle, Cart, Category, CategoryDetail, CheckoutInput, CookieConsent, CookieConsentInput, CrossSellItem, CurrencySwitcher, type CurrencySwitcherProps, type CurrencySwitcherRenderProps, CustomerAddress, CustomerProfile, FilterField, GiftCardBalance, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductReviewsResponse, ProductsQuery, QuoteRequest, RegisterInput, ReturnRequest, ShopInfo, ShopSeo, type StorageAdapter, StorefrontScripts, type StorefrontScriptsProps, SubmitQuoteInput, SubmitReturnInput, SubmitReviewInput, type UseAddressAutocompleteOptions, type UseAddressAutocompleteReturn, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCurrencyResult, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, type UseShopScriptsOptions, type UseShopSeoOptions, WishlistItem, cookieStorage, createMemoryStorage, detectStorage, localStorageAdapter, memoryStorage, useAddressAutocomplete, useAddresses, useAuth, useBehio, useBehioClient, useBundle, useBundles, useCart, useCartCount, useCategories, useCategory, useCheckout, useCookieConsent, useCrossSell, useCurrency, useCustomer, useFeatured, useFilters, useGiftCardBalance, useIsInWishlist, useLabels, useLookupReturnableOrder, useNotifyWhenAvailable, useOrder, useOrderAccess, useOrders, usePage, usePages, useProduct, useProductPromotions, useProductReviews, useProducts, useQuoteStatus, useReturnStatus, useSearch, useShopInfo, useShopScripts, useShopSeo, useSubmitQuote, useSubmitReturn, useSubmitReview, useWishlist };
1052
+ export { ActivePromotion, BehioAnalyticsTracker, BehioProvider, type BehioProviderProps, Bundle, Cart, Category, CategoryDetail, CheckoutInput, CookieConsent, CookieConsentInput, CrossSellItem, CurrencySwitcher, type CurrencySwitcherProps, type CurrencySwitcherRenderProps, CustomerAddress, CustomerProfile, FilterField, GiftCardBalance, OrderDetail, OrderListItem, Page, PageDetail, PaginatedResponse, ProductDetail, ProductLabel, ProductListItem, ProductReviewsResponse, ProductsQuery, QuoteRequest, RegisterInput, ReturnRequest, ShopInfo, ShopSeo, type StorageAdapter, StorefrontScripts, type StorefrontScriptsProps, SubmitQuoteInput, SubmitReturnInput, SubmitReviewInput, type UseAddressAutocompleteOptions, type UseAddressAutocompleteReturn, type UseAddressesOptions, type UseCartCountOptions, type UseCartOptions, type UseCategoriesOptions, type UseCategoryOptions, type UseCurrencyResult, type UseCustomerOptions, type UseFeaturedOptions, type UseFiltersOptions, type UseLabelsOptions, type UseOrderOptions, type UseOrdersOptions, type UsePageOptions, type UsePagesOptions, type UseProductOptions, type UseProductsOptions, type UseSearchOptions, type UseShopInfoOptions, type UseShopScriptsOptions, type UseShopSeoOptions, WishlistItem, cookieStorage, createMemoryStorage, detectStorage, localStorageAdapter, memoryStorage, useAddressAutocomplete, useAddresses, useAuth, useBehio, useBehioClient, useBundle, useBundles, useCart, useCartCount, useCategories, useCategory, useCheckout, useCookieConsent, useCrossSell, useCurrency, useCustomer, useFeatured, useFilters, useGiftCardBalance, useIsInWishlist, useLabels, useLookupReturnableOrder, useNotifyWhenAvailable, useOrder, useOrderAccess, useOrders, usePage, usePages, useProduct, useProductPromotions, useProductReviews, useProducts, useQuoteStatus, useReturnStatus, useSearch, useShopInfo, useShopScripts, useShopSeo, useSubmitQuote, useSubmitReturn, useSubmitReview, useWishlist };
package/dist/react.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
- var _chunkXWYDXBLGjs = require('./chunk-XWYDXBLG.js');
4
3
 
4
+ var _chunkJIAOZ5YWjs = require('./chunk-JIAOZ5YW.js');
5
5
 
6
- var _chunkBF4YVFHXjs = require('./chunk-BF4YVFHX.js');
6
+
7
+ var _chunk3RI2QBQOjs = require('./chunk-3RI2QBQO.js');
7
8
 
8
9
  // src/react/provider.tsx
9
10
  var _react = require('react');
@@ -133,7 +134,7 @@ function BehioProvider({
133
134
  const [activeCurrency, setActiveCurrency] = _react.useState.call(void 0, resolveInitialCurrency);
134
135
  const clientRef = _react.useRef.call(void 0, null);
135
136
  if (!clientRef.current) {
136
- clientRef.current = new (0, _chunkBF4YVFHXjs.BehioStorefront)({
137
+ clientRef.current = new (0, _chunk3RI2QBQOjs.BehioStorefront)({
137
138
  apiKey,
138
139
  baseUrl,
139
140
  ...shopDomain ? { shopDomain } : {},
@@ -1273,6 +1274,221 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
1273
1274
  return null;
1274
1275
  }
1275
1276
 
1277
+ // src/react/components/behio-analytics.tsx
1278
+
1279
+
1280
+ // src/react/hooks/use-behio-client.ts
1281
+ function useBehioClient() {
1282
+ return useBehio().client;
1283
+ }
1284
+
1285
+ // src/react/components/behio-analytics.tsx
1286
+ function BehioAnalyticsTracker() {
1287
+ const client = useBehioClient();
1288
+ _react.useEffect.call(void 0, () => {
1289
+ if (typeof window === "undefined") return;
1290
+ const w = window;
1291
+ if (w.__behioAnalytics) return;
1292
+ w.__behioAnalytics = true;
1293
+ const teardown = initTracker(client);
1294
+ return () => {
1295
+ w.__behioAnalytics = false;
1296
+ teardown();
1297
+ };
1298
+ }, [client]);
1299
+ return null;
1300
+ }
1301
+ var VISITOR_KEY2 = "behio_visitor_id";
1302
+ var FLUSH_MAX_EVENTS = 20;
1303
+ var FLUSH_INTERVAL_MS = 5e3;
1304
+ function initTracker(client) {
1305
+ const sessionId = getSessionId();
1306
+ let visitorId;
1307
+ let currentPath = window.location.pathname;
1308
+ let visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1309
+ let dwellAccumMs = 0;
1310
+ const refreshIdentity = async () => {
1311
+ try {
1312
+ const stored = _nullishCoalesce(localStorage.getItem(VISITOR_KEY2), () => ( void 0));
1313
+ if (!stored) {
1314
+ visitorId = void 0;
1315
+ client.setAnalyticsVisitorId(null);
1316
+ return;
1317
+ }
1318
+ const { data } = await client.consent.get(stored);
1319
+ const granted = Boolean(_optionalChain([data, 'optionalAccess', _71 => _71.analytics]));
1320
+ visitorId = granted ? stored : void 0;
1321
+ client.setAnalyticsVisitorId(granted ? stored : null);
1322
+ } catch (e7) {
1323
+ visitorId = void 0;
1324
+ }
1325
+ };
1326
+ void refreshIdentity();
1327
+ const onConsentChanged = () => void refreshIdentity();
1328
+ window.addEventListener("behio:consent-changed", onConsentChanged);
1329
+ let queue = [];
1330
+ let flushTimer = null;
1331
+ const flush = () => {
1332
+ if (flushTimer) {
1333
+ clearTimeout(flushTimer);
1334
+ flushTimer = null;
1335
+ }
1336
+ if (queue.length === 0) return;
1337
+ const events = queue;
1338
+ queue = [];
1339
+ void client.sendAnalyticsEvents({ sessionId, ...visitorId ? { visitorId } : {}, events });
1340
+ };
1341
+ const enqueue = (event) => {
1342
+ queue.push(event);
1343
+ if (queue.length >= FLUSH_MAX_EVENTS) {
1344
+ flush();
1345
+ return;
1346
+ }
1347
+ if (!flushTimer) flushTimer = setTimeout(flush, FLUSH_INTERVAL_MS);
1348
+ };
1349
+ const landing = utmFromSearch(window.location.search);
1350
+ enqueue({
1351
+ type: "pageview",
1352
+ path: currentPath,
1353
+ referrer: document.referrer || void 0,
1354
+ ...landing,
1355
+ ts: Date.now()
1356
+ });
1357
+ const settleDwell = () => {
1358
+ if (visibleSince != null) {
1359
+ dwellAccumMs += Date.now() - visibleSince;
1360
+ visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1361
+ }
1362
+ };
1363
+ const takeDwell = () => {
1364
+ settleDwell();
1365
+ const dwell = dwellAccumMs;
1366
+ dwellAccumMs = 0;
1367
+ return dwell;
1368
+ };
1369
+ const onRouteChange = () => {
1370
+ const nextPath = window.location.pathname;
1371
+ if (nextPath === currentPath) return;
1372
+ const dwellMs = takeDwell();
1373
+ if (dwellMs > 0) {
1374
+ enqueue({ type: "custom", name: "dwell", path: currentPath, dwellMs, ts: Date.now() });
1375
+ }
1376
+ currentPath = nextPath;
1377
+ visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1378
+ enqueue({
1379
+ type: "pageview",
1380
+ path: currentPath,
1381
+ ...utmFromSearch(window.location.search),
1382
+ ts: Date.now()
1383
+ });
1384
+ flush();
1385
+ };
1386
+ const onVisibility = () => {
1387
+ if (document.visibilityState === "hidden") {
1388
+ settleDwell();
1389
+ visibleSince = null;
1390
+ } else if (visibleSince == null) {
1391
+ visibleSince = Date.now();
1392
+ }
1393
+ };
1394
+ const onPageHide = () => {
1395
+ const dwellMs = takeDwell();
1396
+ if (dwellMs > 0) {
1397
+ enqueue({ type: "custom", name: "dwell", path: currentPath, dwellMs, ts: Date.now() });
1398
+ }
1399
+ flush();
1400
+ };
1401
+ const onClick = (ev) => {
1402
+ const target = ev.target;
1403
+ const el = _optionalChain([target, 'optionalAccess', _72 => _72.closest, 'optionalCall', _73 => _73("a,button,[role=button],[data-behio-event]")]);
1404
+ if (!el) return;
1405
+ const explicit = _nullishCoalesce(el.getAttribute("data-behio-event"), () => ( void 0));
1406
+ const text = (_nullishCoalesce(el.textContent, () => ( ""))).trim().replace(/\s+/g, " ").slice(0, 80) || void 0;
1407
+ const hrefRaw = _nullishCoalesce(el.getAttribute("href"), () => ( void 0));
1408
+ let href;
1409
+ if (hrefRaw) {
1410
+ try {
1411
+ const u = new URL(hrefRaw, window.location.origin);
1412
+ href = u.origin === window.location.origin ? u.pathname : u.hostname;
1413
+ } catch (e8) {
1414
+ href = void 0;
1415
+ }
1416
+ }
1417
+ enqueue({
1418
+ type: "custom",
1419
+ name: explicit || "click",
1420
+ path: currentPath,
1421
+ ts: Date.now(),
1422
+ props: {
1423
+ tag: el.tagName.toLowerCase(),
1424
+ ...text ? { text } : {},
1425
+ ...href ? { href } : {}
1426
+ }
1427
+ });
1428
+ };
1429
+ const w = window;
1430
+ w.__behioEcommerceSink = (event, payload) => {
1431
+ if (event === "purchase") return;
1432
+ enqueue({
1433
+ type: "ecommerce",
1434
+ name: event,
1435
+ path: currentPath,
1436
+ ts: Date.now(),
1437
+ ...payload.value != null ? { value: payload.value } : {},
1438
+ ...payload.currency ? { currency: payload.currency } : {},
1439
+ ...Array.isArray(payload.items) && payload.items.length > 0 ? { props: { items: payload.items.length } } : {}
1440
+ });
1441
+ };
1442
+ const origPush = history.pushState.bind(history);
1443
+ const origReplace = history.replaceState.bind(history);
1444
+ history.pushState = (...args) => {
1445
+ origPush(...args);
1446
+ onRouteChange();
1447
+ };
1448
+ history.replaceState = (...args) => {
1449
+ origReplace(...args);
1450
+ onRouteChange();
1451
+ };
1452
+ window.addEventListener("popstate", onRouteChange);
1453
+ document.addEventListener("visibilitychange", onVisibility);
1454
+ window.addEventListener("pagehide", onPageHide);
1455
+ document.addEventListener("click", onClick, { capture: true, passive: true });
1456
+ return () => {
1457
+ flush();
1458
+ delete w.__behioEcommerceSink;
1459
+ history.pushState = origPush;
1460
+ history.replaceState = origReplace;
1461
+ window.removeEventListener("popstate", onRouteChange);
1462
+ document.removeEventListener("visibilitychange", onVisibility);
1463
+ window.removeEventListener("pagehide", onPageHide);
1464
+ document.removeEventListener("click", onClick, { capture: true });
1465
+ window.removeEventListener("behio:consent-changed", onConsentChanged);
1466
+ };
1467
+ }
1468
+ function getSessionId() {
1469
+ try {
1470
+ const existing = sessionStorage.getItem("behio.sid");
1471
+ if (existing) return existing;
1472
+ const sid = `s-${Math.random().toString(36).slice(2, 12)}${Date.now().toString(36)}`;
1473
+ sessionStorage.setItem("behio.sid", sid);
1474
+ return sid;
1475
+ } catch (e9) {
1476
+ return `s-${Math.random().toString(36).slice(2, 12)}`;
1477
+ }
1478
+ }
1479
+ function utmFromSearch(search) {
1480
+ try {
1481
+ const p = new URLSearchParams(search);
1482
+ return {
1483
+ utmSource: _nullishCoalesce(p.get("utm_source"), () => ( void 0)),
1484
+ utmMedium: _nullishCoalesce(p.get("utm_medium"), () => ( void 0)),
1485
+ utmCampaign: _nullishCoalesce(p.get("utm_campaign"), () => ( void 0))
1486
+ };
1487
+ } catch (e10) {
1488
+ return {};
1489
+ }
1490
+ }
1491
+
1276
1492
  // src/react/hooks/use-bundles.ts
1277
1493
 
1278
1494
  function useBundles(options) {
@@ -1280,8 +1496,8 @@ function useBundles(options) {
1280
1496
  return _reactquery.useQuery.call(void 0, {
1281
1497
  queryKey: ["behio", "bundles"],
1282
1498
  queryFn: () => unwrap(client.catalog.getBundles()),
1283
- enabled: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _71 => _71.enabled]), () => ( true)),
1284
- initialData: _optionalChain([options, 'optionalAccess', _72 => _72.initialData])
1499
+ enabled: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _74 => _74.enabled]), () => ( true)),
1500
+ initialData: _optionalChain([options, 'optionalAccess', _75 => _75.initialData])
1285
1501
  });
1286
1502
  }
1287
1503
  function useBundle(slug, options) {
@@ -1289,8 +1505,8 @@ function useBundle(slug, options) {
1289
1505
  return _reactquery.useQuery.call(void 0, {
1290
1506
  queryKey: ["behio", "bundle", slug],
1291
1507
  queryFn: () => unwrap(client.catalog.getBundle(slug)),
1292
- enabled: Boolean(slug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _73 => _73.enabled]), () => ( true))),
1293
- initialData: _optionalChain([options, 'optionalAccess', _74 => _74.initialData])
1508
+ enabled: Boolean(slug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _76 => _76.enabled]), () => ( true))),
1509
+ initialData: _optionalChain([options, 'optionalAccess', _77 => _77.initialData])
1294
1510
  });
1295
1511
  }
1296
1512
 
@@ -1301,8 +1517,8 @@ function useCrossSell(productSlug, options) {
1301
1517
  return _reactquery.useQuery.call(void 0, {
1302
1518
  queryKey: ["behio", "cross-sell", productSlug],
1303
1519
  queryFn: () => unwrap(client.catalog.getCrossSell(productSlug)),
1304
- enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _75 => _75.enabled]), () => ( true))),
1305
- initialData: _optionalChain([options, 'optionalAccess', _76 => _76.initialData])
1520
+ enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _78 => _78.enabled]), () => ( true))),
1521
+ initialData: _optionalChain([options, 'optionalAccess', _79 => _79.initialData])
1306
1522
  });
1307
1523
  }
1308
1524
 
@@ -1313,8 +1529,8 @@ function useProductPromotions(productSlug, options) {
1313
1529
  return _reactquery.useQuery.call(void 0, {
1314
1530
  queryKey: ["behio", "product-promotions", productSlug],
1315
1531
  queryFn: () => unwrap(client.catalog.getProductPromotions(productSlug)),
1316
- enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _77 => _77.enabled]), () => ( true))),
1317
- refetchInterval: _optionalChain([options, 'optionalAccess', _78 => _78.refetchIntervalMs])
1532
+ enabled: Boolean(productSlug) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _80 => _80.enabled]), () => ( true))),
1533
+ refetchInterval: _optionalChain([options, 'optionalAccess', _81 => _81.refetchIntervalMs])
1318
1534
  });
1319
1535
  }
1320
1536
 
@@ -1322,11 +1538,11 @@ function useProductPromotions(productSlug, options) {
1322
1538
 
1323
1539
  function useGiftCardBalance(code, options) {
1324
1540
  const { client } = useBehio();
1325
- const trimmed = _optionalChain([code, 'optionalAccess', _79 => _79.trim, 'call', _80 => _80()]);
1541
+ const trimmed = _optionalChain([code, 'optionalAccess', _82 => _82.trim, 'call', _83 => _83()]);
1326
1542
  return _reactquery.useQuery.call(void 0, {
1327
1543
  queryKey: ["behio", "gift-card-balance", trimmed],
1328
1544
  queryFn: () => unwrap(client.catalog.checkGiftCard(trimmed)),
1329
- enabled: Boolean(trimmed && trimmed.length >= 6) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _81 => _81.enabled]), () => ( true)))
1545
+ enabled: Boolean(trimmed && trimmed.length >= 6) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _84 => _84.enabled]), () => ( true)))
1330
1546
  });
1331
1547
  }
1332
1548
 
@@ -1338,7 +1554,7 @@ function useWishlist(options) {
1338
1554
  const query = _reactquery.useQuery.call(void 0, {
1339
1555
  queryKey: ["behio", "wishlist"],
1340
1556
  queryFn: () => unwrap(client.wishlist.get()),
1341
- enabled: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _82 => _82.enabled]), () => ( true))
1557
+ enabled: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _85 => _85.enabled]), () => ( true))
1342
1558
  });
1343
1559
  const addMutation = _reactquery.useMutation.call(void 0, {
1344
1560
  mutationFn: (productId) => unwrap(client.wishlist.add(productId)),
@@ -1370,9 +1586,9 @@ function useIsInWishlist(productId) {
1370
1586
  function useProductReviews(productId, options) {
1371
1587
  const { client } = useBehio();
1372
1588
  return _reactquery.useQuery.call(void 0, {
1373
- queryKey: ["behio", "reviews", productId, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _83 => _83.page]), () => ( 1))],
1374
- queryFn: () => unwrap(client.reviews.getProductReviews(productId, _optionalChain([options, 'optionalAccess', _84 => _84.page]), _optionalChain([options, 'optionalAccess', _85 => _85.limit]))),
1375
- enabled: Boolean(productId) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _86 => _86.enabled]), () => ( true)))
1589
+ queryKey: ["behio", "reviews", productId, _nullishCoalesce(_optionalChain([options, 'optionalAccess', _86 => _86.page]), () => ( 1))],
1590
+ queryFn: () => unwrap(client.reviews.getProductReviews(productId, _optionalChain([options, 'optionalAccess', _87 => _87.page]), _optionalChain([options, 'optionalAccess', _88 => _88.limit]))),
1591
+ enabled: Boolean(productId) && (_nullishCoalesce(_optionalChain([options, 'optionalAccess', _89 => _89.enabled]), () => ( true)))
1376
1592
  });
1377
1593
  }
1378
1594
  function useSubmitReview() {
@@ -1433,10 +1649,6 @@ function useNotifyWhenAvailable() {
1433
1649
  });
1434
1650
  }
1435
1651
 
1436
- // src/react/hooks/use-behio-client.ts
1437
- function useBehioClient() {
1438
- return useBehio().client;
1439
- }
1440
1652
 
1441
1653
 
1442
1654
 
@@ -1489,4 +1701,5 @@ function useBehioClient() {
1489
1701
 
1490
1702
 
1491
1703
 
1492
- exports.BehioProvider = BehioProvider; exports.CurrencySwitcher = CurrencySwitcher; exports.StorefrontScripts = StorefrontScripts; exports.cookieStorage = cookieStorage; exports.createMemoryStorage = createMemoryStorage; exports.detectStorage = detectStorage; exports.formatPrice = _chunkXWYDXBLGjs.formatPrice; exports.localStorageAdapter = localStorageAdapter; exports.memoryStorage = memoryStorage; exports.useAddressAutocomplete = useAddressAutocomplete; exports.useAddresses = useAddresses; exports.useAuth = useAuth; exports.useBehio = useBehio; exports.useBehioClient = useBehioClient; exports.useBundle = useBundle; exports.useBundles = useBundles; exports.useCart = useCart; exports.useCartCount = useCartCount; exports.useCategories = useCategories; exports.useCategory = useCategory; exports.useCheckout = useCheckout; exports.useCookieConsent = useCookieConsent; exports.useCrossSell = useCrossSell; exports.useCurrency = useCurrency; exports.useCustomer = useCustomer; exports.useFeatured = useFeatured; exports.useFilters = useFilters; exports.useGiftCardBalance = useGiftCardBalance; exports.useIsInWishlist = useIsInWishlist; exports.useLabels = useLabels; exports.useLookupReturnableOrder = useLookupReturnableOrder; exports.useNotifyWhenAvailable = useNotifyWhenAvailable; exports.useOrder = useOrder; exports.useOrderAccess = useOrderAccess; exports.useOrders = useOrders; exports.usePage = usePage; exports.usePages = usePages; exports.useProduct = useProduct; exports.useProductPromotions = useProductPromotions; exports.useProductReviews = useProductReviews; exports.useProducts = useProducts; exports.useQuoteStatus = useQuoteStatus; exports.useReturnStatus = useReturnStatus; exports.useSearch = useSearch; exports.useShopInfo = useShopInfo; exports.useShopScripts = useShopScripts; exports.useShopSeo = useShopSeo; exports.useSubmitQuote = useSubmitQuote; exports.useSubmitReturn = useSubmitReturn; exports.useSubmitReview = useSubmitReview; exports.useWishlist = useWishlist;
1704
+
1705
+ exports.BehioAnalyticsTracker = BehioAnalyticsTracker; exports.BehioProvider = BehioProvider; exports.CurrencySwitcher = CurrencySwitcher; exports.StorefrontScripts = StorefrontScripts; exports.cookieStorage = cookieStorage; exports.createMemoryStorage = createMemoryStorage; exports.detectStorage = detectStorage; exports.formatPrice = _chunkJIAOZ5YWjs.formatPrice; exports.localStorageAdapter = localStorageAdapter; exports.memoryStorage = memoryStorage; exports.trackEcommerceEvent = _chunkJIAOZ5YWjs.trackEcommerceEvent; exports.useAddressAutocomplete = useAddressAutocomplete; exports.useAddresses = useAddresses; exports.useAuth = useAuth; exports.useBehio = useBehio; exports.useBehioClient = useBehioClient; exports.useBundle = useBundle; exports.useBundles = useBundles; exports.useCart = useCart; exports.useCartCount = useCartCount; exports.useCategories = useCategories; exports.useCategory = useCategory; exports.useCheckout = useCheckout; exports.useCookieConsent = useCookieConsent; exports.useCrossSell = useCrossSell; exports.useCurrency = useCurrency; exports.useCustomer = useCustomer; exports.useFeatured = useFeatured; exports.useFilters = useFilters; exports.useGiftCardBalance = useGiftCardBalance; exports.useIsInWishlist = useIsInWishlist; exports.useLabels = useLabels; exports.useLookupReturnableOrder = useLookupReturnableOrder; exports.useNotifyWhenAvailable = useNotifyWhenAvailable; exports.useOrder = useOrder; exports.useOrderAccess = useOrderAccess; exports.useOrders = useOrders; exports.usePage = usePage; exports.usePages = usePages; exports.useProduct = useProduct; exports.useProductPromotions = useProductPromotions; exports.useProductReviews = useProductReviews; exports.useProducts = useProducts; exports.useQuoteStatus = useQuoteStatus; exports.useReturnStatus = useReturnStatus; exports.useSearch = useSearch; exports.useShopInfo = useShopInfo; exports.useShopScripts = useShopScripts; exports.useShopSeo = useShopSeo; exports.useSubmitQuote = useSubmitQuote; exports.useSubmitReturn = useSubmitReturn; exports.useSubmitReview = useSubmitReview; exports.useWishlist = useWishlist;
package/dist/react.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import {
2
- formatPrice
3
- } from "./chunk-YJ45WIQZ.mjs";
2
+ formatPrice,
3
+ trackEcommerceEvent
4
+ } from "./chunk-ZOZAJG6T.mjs";
4
5
  import {
5
6
  BehioStorefront
6
- } from "./chunk-PZED7VMQ.mjs";
7
+ } from "./chunk-2CD4CPEV.mjs";
7
8
 
8
9
  // src/react/provider.tsx
9
10
  import { useRef, useEffect, useMemo, useState, useCallback } from "react";
@@ -1273,6 +1274,221 @@ function StorefrontScripts({ visitorId: visitorIdProp } = {}) {
1273
1274
  return null;
1274
1275
  }
1275
1276
 
1277
+ // src/react/components/behio-analytics.tsx
1278
+ import { useEffect as useEffect5 } from "react";
1279
+
1280
+ // src/react/hooks/use-behio-client.ts
1281
+ function useBehioClient() {
1282
+ return useBehio().client;
1283
+ }
1284
+
1285
+ // src/react/components/behio-analytics.tsx
1286
+ function BehioAnalyticsTracker() {
1287
+ const client = useBehioClient();
1288
+ useEffect5(() => {
1289
+ if (typeof window === "undefined") return;
1290
+ const w = window;
1291
+ if (w.__behioAnalytics) return;
1292
+ w.__behioAnalytics = true;
1293
+ const teardown = initTracker(client);
1294
+ return () => {
1295
+ w.__behioAnalytics = false;
1296
+ teardown();
1297
+ };
1298
+ }, [client]);
1299
+ return null;
1300
+ }
1301
+ var VISITOR_KEY2 = "behio_visitor_id";
1302
+ var FLUSH_MAX_EVENTS = 20;
1303
+ var FLUSH_INTERVAL_MS = 5e3;
1304
+ function initTracker(client) {
1305
+ const sessionId = getSessionId();
1306
+ let visitorId;
1307
+ let currentPath = window.location.pathname;
1308
+ let visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1309
+ let dwellAccumMs = 0;
1310
+ const refreshIdentity = async () => {
1311
+ try {
1312
+ const stored = localStorage.getItem(VISITOR_KEY2) ?? void 0;
1313
+ if (!stored) {
1314
+ visitorId = void 0;
1315
+ client.setAnalyticsVisitorId(null);
1316
+ return;
1317
+ }
1318
+ const { data } = await client.consent.get(stored);
1319
+ const granted = Boolean(data?.analytics);
1320
+ visitorId = granted ? stored : void 0;
1321
+ client.setAnalyticsVisitorId(granted ? stored : null);
1322
+ } catch {
1323
+ visitorId = void 0;
1324
+ }
1325
+ };
1326
+ void refreshIdentity();
1327
+ const onConsentChanged = () => void refreshIdentity();
1328
+ window.addEventListener("behio:consent-changed", onConsentChanged);
1329
+ let queue = [];
1330
+ let flushTimer = null;
1331
+ const flush = () => {
1332
+ if (flushTimer) {
1333
+ clearTimeout(flushTimer);
1334
+ flushTimer = null;
1335
+ }
1336
+ if (queue.length === 0) return;
1337
+ const events = queue;
1338
+ queue = [];
1339
+ void client.sendAnalyticsEvents({ sessionId, ...visitorId ? { visitorId } : {}, events });
1340
+ };
1341
+ const enqueue = (event) => {
1342
+ queue.push(event);
1343
+ if (queue.length >= FLUSH_MAX_EVENTS) {
1344
+ flush();
1345
+ return;
1346
+ }
1347
+ if (!flushTimer) flushTimer = setTimeout(flush, FLUSH_INTERVAL_MS);
1348
+ };
1349
+ const landing = utmFromSearch(window.location.search);
1350
+ enqueue({
1351
+ type: "pageview",
1352
+ path: currentPath,
1353
+ referrer: document.referrer || void 0,
1354
+ ...landing,
1355
+ ts: Date.now()
1356
+ });
1357
+ const settleDwell = () => {
1358
+ if (visibleSince != null) {
1359
+ dwellAccumMs += Date.now() - visibleSince;
1360
+ visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1361
+ }
1362
+ };
1363
+ const takeDwell = () => {
1364
+ settleDwell();
1365
+ const dwell = dwellAccumMs;
1366
+ dwellAccumMs = 0;
1367
+ return dwell;
1368
+ };
1369
+ const onRouteChange = () => {
1370
+ const nextPath = window.location.pathname;
1371
+ if (nextPath === currentPath) return;
1372
+ const dwellMs = takeDwell();
1373
+ if (dwellMs > 0) {
1374
+ enqueue({ type: "custom", name: "dwell", path: currentPath, dwellMs, ts: Date.now() });
1375
+ }
1376
+ currentPath = nextPath;
1377
+ visibleSince = document.visibilityState === "visible" ? Date.now() : null;
1378
+ enqueue({
1379
+ type: "pageview",
1380
+ path: currentPath,
1381
+ ...utmFromSearch(window.location.search),
1382
+ ts: Date.now()
1383
+ });
1384
+ flush();
1385
+ };
1386
+ const onVisibility = () => {
1387
+ if (document.visibilityState === "hidden") {
1388
+ settleDwell();
1389
+ visibleSince = null;
1390
+ } else if (visibleSince == null) {
1391
+ visibleSince = Date.now();
1392
+ }
1393
+ };
1394
+ const onPageHide = () => {
1395
+ const dwellMs = takeDwell();
1396
+ if (dwellMs > 0) {
1397
+ enqueue({ type: "custom", name: "dwell", path: currentPath, dwellMs, ts: Date.now() });
1398
+ }
1399
+ flush();
1400
+ };
1401
+ const onClick = (ev) => {
1402
+ const target = ev.target;
1403
+ const el = target?.closest?.("a,button,[role=button],[data-behio-event]");
1404
+ if (!el) return;
1405
+ const explicit = el.getAttribute("data-behio-event") ?? void 0;
1406
+ const text = (el.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 80) || void 0;
1407
+ const hrefRaw = el.getAttribute("href") ?? void 0;
1408
+ let href;
1409
+ if (hrefRaw) {
1410
+ try {
1411
+ const u = new URL(hrefRaw, window.location.origin);
1412
+ href = u.origin === window.location.origin ? u.pathname : u.hostname;
1413
+ } catch {
1414
+ href = void 0;
1415
+ }
1416
+ }
1417
+ enqueue({
1418
+ type: "custom",
1419
+ name: explicit || "click",
1420
+ path: currentPath,
1421
+ ts: Date.now(),
1422
+ props: {
1423
+ tag: el.tagName.toLowerCase(),
1424
+ ...text ? { text } : {},
1425
+ ...href ? { href } : {}
1426
+ }
1427
+ });
1428
+ };
1429
+ const w = window;
1430
+ w.__behioEcommerceSink = (event, payload) => {
1431
+ if (event === "purchase") return;
1432
+ enqueue({
1433
+ type: "ecommerce",
1434
+ name: event,
1435
+ path: currentPath,
1436
+ ts: Date.now(),
1437
+ ...payload.value != null ? { value: payload.value } : {},
1438
+ ...payload.currency ? { currency: payload.currency } : {},
1439
+ ...Array.isArray(payload.items) && payload.items.length > 0 ? { props: { items: payload.items.length } } : {}
1440
+ });
1441
+ };
1442
+ const origPush = history.pushState.bind(history);
1443
+ const origReplace = history.replaceState.bind(history);
1444
+ history.pushState = (...args) => {
1445
+ origPush(...args);
1446
+ onRouteChange();
1447
+ };
1448
+ history.replaceState = (...args) => {
1449
+ origReplace(...args);
1450
+ onRouteChange();
1451
+ };
1452
+ window.addEventListener("popstate", onRouteChange);
1453
+ document.addEventListener("visibilitychange", onVisibility);
1454
+ window.addEventListener("pagehide", onPageHide);
1455
+ document.addEventListener("click", onClick, { capture: true, passive: true });
1456
+ return () => {
1457
+ flush();
1458
+ delete w.__behioEcommerceSink;
1459
+ history.pushState = origPush;
1460
+ history.replaceState = origReplace;
1461
+ window.removeEventListener("popstate", onRouteChange);
1462
+ document.removeEventListener("visibilitychange", onVisibility);
1463
+ window.removeEventListener("pagehide", onPageHide);
1464
+ document.removeEventListener("click", onClick, { capture: true });
1465
+ window.removeEventListener("behio:consent-changed", onConsentChanged);
1466
+ };
1467
+ }
1468
+ function getSessionId() {
1469
+ try {
1470
+ const existing = sessionStorage.getItem("behio.sid");
1471
+ if (existing) return existing;
1472
+ const sid = `s-${Math.random().toString(36).slice(2, 12)}${Date.now().toString(36)}`;
1473
+ sessionStorage.setItem("behio.sid", sid);
1474
+ return sid;
1475
+ } catch {
1476
+ return `s-${Math.random().toString(36).slice(2, 12)}`;
1477
+ }
1478
+ }
1479
+ function utmFromSearch(search) {
1480
+ try {
1481
+ const p = new URLSearchParams(search);
1482
+ return {
1483
+ utmSource: p.get("utm_source") ?? void 0,
1484
+ utmMedium: p.get("utm_medium") ?? void 0,
1485
+ utmCampaign: p.get("utm_campaign") ?? void 0
1486
+ };
1487
+ } catch {
1488
+ return {};
1489
+ }
1490
+ }
1491
+
1276
1492
  // src/react/hooks/use-bundles.ts
1277
1493
  import { useQuery as useQuery19 } from "@tanstack/react-query";
1278
1494
  function useBundles(options) {
@@ -1432,12 +1648,8 @@ function useNotifyWhenAvailable() {
1432
1648
  mutationFn: ({ productId, email }) => unwrap(client.catalog.notifyWhenAvailable(productId, email))
1433
1649
  });
1434
1650
  }
1435
-
1436
- // src/react/hooks/use-behio-client.ts
1437
- function useBehioClient() {
1438
- return useBehio().client;
1439
- }
1440
1651
  export {
1652
+ BehioAnalyticsTracker,
1441
1653
  BehioProvider,
1442
1654
  CurrencySwitcher,
1443
1655
  StorefrontScripts,
@@ -1447,6 +1659,7 @@ export {
1447
1659
  formatPrice,
1448
1660
  localStorageAdapter,
1449
1661
  memoryStorage,
1662
+ trackEcommerceEvent,
1450
1663
  useAddressAutocomplete,
1451
1664
  useAddresses,
1452
1665
  useAuth,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",