@proxima-io/storefront-core 0.3.0 → 0.8.2

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.
Files changed (178) hide show
  1. package/README.md +68 -17
  2. package/dist/addresses/address-book.d.ts +36 -0
  3. package/dist/addresses/address-book.d.ts.map +1 -0
  4. package/dist/addresses/address-book.js +62 -0
  5. package/dist/addresses/address-book.js.map +1 -0
  6. package/dist/analytics/analytics.d.ts +28 -0
  7. package/dist/analytics/analytics.d.ts.map +1 -0
  8. package/dist/analytics/analytics.js +124 -0
  9. package/dist/analytics/analytics.js.map +1 -0
  10. package/dist/analytics/attribution.d.ts +28 -0
  11. package/dist/analytics/attribution.d.ts.map +1 -0
  12. package/dist/analytics/attribution.js +116 -0
  13. package/dist/analytics/attribution.js.map +1 -0
  14. package/dist/analytics/session.d.ts +12 -0
  15. package/dist/analytics/session.d.ts.map +1 -0
  16. package/dist/analytics/session.js +62 -0
  17. package/dist/analytics/session.js.map +1 -0
  18. package/dist/analytics/trackers.d.ts +29 -0
  19. package/dist/analytics/trackers.d.ts.map +1 -0
  20. package/dist/analytics/trackers.js +30 -0
  21. package/dist/analytics/trackers.js.map +1 -0
  22. package/dist/api/endpoints.d.ts +70 -0
  23. package/dist/api/endpoints.d.ts.map +1 -0
  24. package/dist/api/endpoints.js +70 -0
  25. package/dist/api/endpoints.js.map +1 -0
  26. package/dist/api/index.d.ts +3 -0
  27. package/dist/api/index.d.ts.map +1 -0
  28. package/dist/api/index.js +3 -0
  29. package/dist/api/index.js.map +1 -0
  30. package/dist/api/storefront-client.d.ts +50 -0
  31. package/dist/api/storefront-client.d.ts.map +1 -0
  32. package/dist/api/storefront-client.js +123 -0
  33. package/dist/api/storefront-client.js.map +1 -0
  34. package/dist/buyer/auth.d.ts +105 -0
  35. package/dist/buyer/auth.d.ts.map +1 -0
  36. package/dist/buyer/auth.js +215 -0
  37. package/dist/buyer/auth.js.map +1 -0
  38. package/dist/cache/cache.d.ts +31 -0
  39. package/dist/cache/cache.d.ts.map +1 -0
  40. package/dist/cache/cache.js +71 -0
  41. package/dist/cache/cache.js.map +1 -0
  42. package/dist/campaign/countdown.d.ts +40 -0
  43. package/dist/campaign/countdown.d.ts.map +1 -0
  44. package/dist/campaign/countdown.js +71 -0
  45. package/dist/campaign/countdown.js.map +1 -0
  46. package/dist/cart/cart.d.ts +57 -0
  47. package/dist/cart/cart.d.ts.map +1 -0
  48. package/dist/cart/cart.js +64 -0
  49. package/dist/cart/cart.js.map +1 -0
  50. package/dist/catalog/listings.d.ts +87 -0
  51. package/dist/catalog/listings.d.ts.map +1 -0
  52. package/dist/catalog/listings.js +140 -0
  53. package/dist/catalog/listings.js.map +1 -0
  54. package/dist/cms/payment-methods.d.ts +13 -0
  55. package/dist/cms/payment-methods.d.ts.map +1 -0
  56. package/dist/cms/payment-methods.js +41 -0
  57. package/dist/cms/payment-methods.js.map +1 -0
  58. package/dist/cms/website.d.ts +76 -0
  59. package/dist/cms/website.d.ts.map +1 -0
  60. package/dist/cms/website.js +146 -0
  61. package/dist/cms/website.js.map +1 -0
  62. package/dist/cookie-consent/consent.d.ts +22 -0
  63. package/dist/cookie-consent/consent.d.ts.map +1 -0
  64. package/dist/cookie-consent/consent.js +93 -0
  65. package/dist/cookie-consent/consent.js.map +1 -0
  66. package/dist/index.d.ts +45 -1310
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +42 -1576
  69. package/dist/index.js.map +1 -1
  70. package/dist/internal/http.d.ts +5 -0
  71. package/dist/internal/http.d.ts.map +1 -0
  72. package/dist/internal/http.js +27 -0
  73. package/dist/internal/http.js.map +1 -0
  74. package/dist/orders/guest.d.ts +9 -0
  75. package/dist/orders/guest.d.ts.map +1 -0
  76. package/dist/orders/guest.js +30 -0
  77. package/dist/orders/guest.js.map +1 -0
  78. package/dist/orders/orders.d.ts +23 -0
  79. package/dist/orders/orders.d.ts.map +1 -0
  80. package/dist/orders/orders.js +33 -0
  81. package/dist/orders/orders.js.map +1 -0
  82. package/dist/seo/engine-url.d.ts +26 -0
  83. package/dist/seo/engine-url.d.ts.map +1 -0
  84. package/dist/seo/engine-url.js +111 -0
  85. package/dist/seo/engine-url.js.map +1 -0
  86. package/dist/seo/hreflang.d.ts +19 -0
  87. package/dist/seo/hreflang.d.ts.map +1 -0
  88. package/dist/seo/hreflang.js +52 -0
  89. package/dist/seo/hreflang.js.map +1 -0
  90. package/dist/seo/indexnow.d.ts +24 -0
  91. package/dist/seo/indexnow.d.ts.map +1 -0
  92. package/dist/seo/indexnow.js +50 -0
  93. package/dist/seo/indexnow.js.map +1 -0
  94. package/dist/seo/json-ld.d.ts +57 -0
  95. package/dist/seo/json-ld.d.ts.map +1 -0
  96. package/dist/seo/json-ld.js +180 -0
  97. package/dist/seo/json-ld.js.map +1 -0
  98. package/dist/seo/page-seo.d.ts +21 -0
  99. package/dist/seo/page-seo.d.ts.map +1 -0
  100. package/dist/seo/page-seo.js +68 -0
  101. package/dist/seo/page-seo.js.map +1 -0
  102. package/dist/seo/robots.d.ts +23 -0
  103. package/dist/seo/robots.d.ts.map +1 -0
  104. package/dist/seo/robots.js +35 -0
  105. package/dist/seo/robots.js.map +1 -0
  106. package/dist/seo/sitemap.d.ts +35 -0
  107. package/dist/seo/sitemap.d.ts.map +1 -0
  108. package/dist/seo/sitemap.js +186 -0
  109. package/dist/seo/sitemap.js.map +1 -0
  110. package/dist/server/process.d.ts +136 -0
  111. package/dist/server/process.d.ts.map +1 -0
  112. package/dist/server/process.js +143 -0
  113. package/dist/server/process.js.map +1 -0
  114. package/dist/types/address.d.ts +40 -0
  115. package/dist/types/address.d.ts.map +1 -0
  116. package/dist/types/address.js +2 -0
  117. package/dist/types/address.js.map +1 -0
  118. package/dist/types/analytics.d.ts +79 -0
  119. package/dist/types/analytics.d.ts.map +1 -0
  120. package/dist/types/analytics.js +2 -0
  121. package/dist/types/analytics.js.map +1 -0
  122. package/dist/types/business.d.ts +95 -0
  123. package/dist/types/business.d.ts.map +1 -0
  124. package/dist/types/business.js +2 -0
  125. package/dist/types/business.js.map +1 -0
  126. package/dist/types/buyer.d.ts +144 -0
  127. package/dist/types/buyer.d.ts.map +1 -0
  128. package/dist/types/buyer.js +45 -0
  129. package/dist/types/buyer.js.map +1 -0
  130. package/dist/types/campaign.d.ts +51 -0
  131. package/dist/types/campaign.d.ts.map +1 -0
  132. package/dist/types/campaign.js +2 -0
  133. package/dist/types/campaign.js.map +1 -0
  134. package/dist/types/cart.d.ts +40 -0
  135. package/dist/types/cart.d.ts.map +1 -0
  136. package/dist/types/cart.js +2 -0
  137. package/dist/types/cart.js.map +1 -0
  138. package/dist/types/catalog.d.ts +164 -0
  139. package/dist/types/catalog.d.ts.map +1 -0
  140. package/dist/types/catalog.js +2 -0
  141. package/dist/types/catalog.js.map +1 -0
  142. package/dist/types/cms.d.ts +200 -0
  143. package/dist/types/cms.d.ts.map +1 -0
  144. package/dist/types/cms.js +2 -0
  145. package/dist/types/cms.js.map +1 -0
  146. package/dist/types/cookie-consent.d.ts +18 -0
  147. package/dist/types/cookie-consent.d.ts.map +1 -0
  148. package/dist/types/cookie-consent.js +7 -0
  149. package/dist/types/cookie-consent.js.map +1 -0
  150. package/dist/types/guest-order.d.ts +16 -0
  151. package/dist/types/guest-order.d.ts.map +1 -0
  152. package/dist/types/guest-order.js +9 -0
  153. package/dist/types/guest-order.js.map +1 -0
  154. package/dist/types/listing.d.ts +14 -0
  155. package/dist/types/listing.d.ts.map +1 -0
  156. package/dist/types/listing.js +2 -0
  157. package/dist/types/listing.js.map +1 -0
  158. package/dist/types/order.d.ts +40 -0
  159. package/dist/types/order.d.ts.map +1 -0
  160. package/dist/types/order.js +2 -0
  161. package/dist/types/order.js.map +1 -0
  162. package/dist/types/seo.d.ts +96 -0
  163. package/dist/types/seo.d.ts.map +1 -0
  164. package/dist/types/seo.js +2 -0
  165. package/dist/types/seo.js.map +1 -0
  166. package/dist/types/server-env.d.ts +19 -0
  167. package/dist/types/server-env.d.ts.map +1 -0
  168. package/dist/types/server-env.js +10 -0
  169. package/dist/types/server-env.js.map +1 -0
  170. package/dist/types/wishlist.d.ts +10 -0
  171. package/dist/types/wishlist.d.ts.map +1 -0
  172. package/dist/types/wishlist.js +2 -0
  173. package/dist/types/wishlist.js.map +1 -0
  174. package/dist/wishlist/wishlist.d.ts +28 -0
  175. package/dist/wishlist/wishlist.d.ts.map +1 -0
  176. package/dist/wishlist/wishlist.js +42 -0
  177. package/dist/wishlist/wishlist.js.map +1 -0
  178. package/package.json +1 -1
package/README.md CHANGED
@@ -188,6 +188,51 @@ const seo = buildPageSeo(composition.seo, website, website.locale, canonicalUrl)
188
188
  // seo.title, seo.description, seo.ogImage, seo.canonicalUrl, seo.robots, …
189
189
  ```
190
190
 
191
+ ### `buildCanonicalUrl` / `buildHreflangAlternates`
192
+
193
+ Helpers for locale-prefix routing (default locale omits prefix; others use `/{locale}{path}`):
194
+
195
+ ```ts
196
+ import { buildCanonicalUrl, buildHreflangAlternates } from "@proxima-io/storefront-core";
197
+
198
+ const canonical = buildCanonicalUrl(website.domain, locale, "/catalogo", website.default_locale);
199
+ const alternates = buildHreflangAlternates({
200
+ domain: website.domain,
201
+ localizedPaths: { es: "/catalogo", en: "/catalog" },
202
+ enabledLocales: website.enabled_locales,
203
+ defaultLocale: website.default_locale,
204
+ });
205
+ // → <link rel="canonical" /> + <link rel="alternate" hreflang="…" />
206
+ ```
207
+
208
+ `fetchProximaRender` accepts optional `locale` — forwarded as `?locale=` and `Accept-Language`.
209
+
210
+ ### `buildEnginePageUrl` / `buildEnginePageHreflangAlternates`
211
+
212
+ For engine pages (`product_detail`, `category_detail`, …) with `{slug}` templates:
213
+
214
+ ```ts
215
+ import {
216
+ buildEnginePageHreflangAlternates,
217
+ buildEnginePageUrl,
218
+ } from "@proxima-io/storefront-core";
219
+
220
+ const paths = { es: "/producto/{slug}", en: "/product/{slug}" };
221
+ const canonical = buildEnginePageUrl(domain, "en", paths, { slug: "g502" }, "es");
222
+ // → https://shop.test/en/product/g502
223
+
224
+ const alternates = buildEnginePageHreflangAlternates({
225
+ domain,
226
+ localizedPaths: paths,
227
+ enabledLocales: ["es", "en"],
228
+ defaultLocale: "es",
229
+ routeParams: { slug: "g502" },
230
+ });
231
+ ```
232
+
233
+ `generateSitemapXml` emits bilingual catalog URLs when `website.pages[]` includes
234
+ `localized_paths` for `product_detail`, `category_detail`, and `brand_detail`.
235
+
191
236
  ### JSON-LD builders (schema.org)
192
237
 
193
238
  ```ts
@@ -401,8 +446,11 @@ BUYER_AUTH_ERRORS.VERIFY_TOKEN_INVALID // "VERIFY_TOKEN_INVALID"
401
446
  BUYER_AUTH_ERRORS.EMAIL_ALREADY_VERIFIED // "EMAIL_ALREADY_VERIFIED"
402
447
  BUYER_AUTH_ERRORS.EMAIL_TAKEN // "Email already registered in this store"
403
448
  BUYER_AUTH_ERRORS.MISSING_REQUIRED_FIELDS // "MISSING_REQUIRED_FIELDS"
449
+ BUYER_AUTH_ERRORS.CAPTCHA_REQUIRED // "CAPTCHA_REQUIRED" (Turnstile, HTTP 422)
404
450
  ```
405
451
 
452
+ **Helper:** `isCaptchaRequiredError(err)` — detecta `{ status: 422, data.detail: CAPTCHA_REQUIRED }` en login, registro y forgot-password.
453
+
406
454
  ---
407
455
 
408
456
  ## Carrito
@@ -586,29 +634,32 @@ BUYER_COOKIE_OPTIONS // { path: '/', httpOnly: true, sameSite: 'lax', maxAg
586
634
 
587
635
  ## Analytics
588
636
 
589
- Cliente client-side con queue automático y flush por batch.
637
+ Cliente singleton con batch, consent GDPR, sesión (`X-Session-ID`) y atribución first-touch (UTMs + click IDs).
638
+
639
+ **Documentación completa:** [`docs/13-analytics.md`](../../docs/13-analytics.md)
590
640
 
591
641
  ```ts
592
642
  import { analytics } from '@proxima-io/storefront-core';
593
643
 
594
- // En SiteLayout.astro <script> — llamar una sola vez:
595
- analytics.init({
596
- apiUrl: 'https://api.proxima.io',
597
- websiteId: 'uuid-website',
598
- businessId: 'uuid-negocio',
599
- locale: 'es',
600
- flushInterval: 3000, // ms (default: 3000)
601
- debug: false,
602
- });
644
+ analytics.init(
645
+ {
646
+ apiUrl: 'https://api.proxima.io',
647
+ websiteId: 'uuid-website',
648
+ businessId: 'uuid-negocio',
649
+ locale: 'es',
650
+ sessionId: 'uuid-cart-session-from-ssr',
651
+ },
652
+ { requireAnalyticsConsent: true },
653
+ );
603
654
 
604
- // En cualquier componente client-side:
605
- analytics.track('product_view', { product_slug: 'titan-mx-pro' });
655
+ analytics.track('product_view', { product_slug: 'titan-mx-pro', price: 199.9 });
606
656
  analytics.track('add_to_cart', { product_slug, variant_id: 42, price: 199.90 });
607
657
  analytics.track('order_completed', { order_id: 'ord_123', order_total: 399.80 });
608
- analytics.track('search', { query: 'zapatillas', results_count: 24 });
609
658
  ```
610
659
 
611
- - `page_view` se dispara automáticamente al iniciar y en cada `astro:page-load`.
612
- - Los eventos se encolan y se envían en batch a `POST /api/v1/store/events`.
613
- - Al cerrar la pestaña (`visibilitychange: hidden`) se usa `navigator.sendBeacon`.
614
- - Es seguro llamar `analytics.track()` antes de `analytics.init()` — los eventos se reproducen al inicializar.
660
+ - Endpoint: **`POST /api/v1/storefront/events`** con headers `X-Business-ID` + `X-Session-ID`.
661
+ - `page_view` automático en init y `astro:page-load`.
662
+ - Flush periódico + `fetch(..., { keepalive: true })` al ocultar la pestaña.
663
+ - `captureSessionAttribution()` / `getSessionAttribution()` — ver doc § Marketing attribution.
664
+ - Helpers de consent: `acceptAllCookieConsent()`, `isAnalyticsConsentGranted()`, etc.
665
+ - Catálogo completo de eventos (`checkout_started`, `filter_applied`, `promotion_view`, …) en la doc.
@@ -0,0 +1,36 @@
1
+ import type { ProximaApiConfig, ProximaWebsiteResponse } from '../types/cms.js';
2
+ import type { AddressInput, CustomerAddress, UbigeoResult } from '../types/address.js';
3
+ /** Fetch all saved addresses for the authenticated customer. */
4
+ export declare function fetchCustomerAddresses(config: Pick<ProximaApiConfig, "baseUrl">, website: Pick<ProximaWebsiteResponse, "business_id">, params: {
5
+ token: string;
6
+ }): Promise<CustomerAddress[]>;
7
+ /** Save a new address to the customer's address book. */
8
+ export declare function createCustomerAddress(config: Pick<ProximaApiConfig, "baseUrl">, website: Pick<ProximaWebsiteResponse, "business_id">, params: {
9
+ token: string;
10
+ address: AddressInput;
11
+ }): Promise<CustomerAddress>;
12
+ /** Partially update a saved address. Only the fields included in `address` are changed. */
13
+ export declare function updateCustomerAddress(config: Pick<ProximaApiConfig, "baseUrl">, website: Pick<ProximaWebsiteResponse, "business_id">, params: {
14
+ token: string;
15
+ addressId: number;
16
+ address: Partial<AddressInput>;
17
+ }): Promise<CustomerAddress>;
18
+ /** Delete a saved address. Throws if the address does not exist. */
19
+ export declare function deleteCustomerAddress(config: Pick<ProximaApiConfig, "baseUrl">, website: Pick<ProximaWebsiteResponse, "business_id">, params: {
20
+ token: string;
21
+ addressId: number;
22
+ }): Promise<void>;
23
+ /** Mark an address as the customer's default. The previous default is unset automatically. */
24
+ export declare function setDefaultAddress(config: Pick<ProximaApiConfig, "baseUrl">, website: Pick<ProximaWebsiteResponse, "business_id">, params: {
25
+ token: string;
26
+ addressId: number;
27
+ }): Promise<CustomerAddress>;
28
+ /**
29
+ * Search Peruvian ubigeo codes (department/province/district) by name.
30
+ * Use this to power the address form's location selector.
31
+ * Returns an empty array on error instead of throwing.
32
+ */
33
+ export declare function searchUbigeo(config: Pick<ProximaApiConfig, "baseUrl">, params: {
34
+ q: string;
35
+ }): Promise<UbigeoResult[]>;
36
+ //# sourceMappingURL=address-book.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-book.d.ts","sourceRoot":"","sources":["../../src/addresses/address-book.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAMvF,gEAAgE;AAChE,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,EACpD,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACxB,OAAO,CAAC,eAAe,EAAE,CAAC,CAO5B;AAED,yDAAyD;AACzD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,EACpD,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAC/C,OAAO,CAAC,eAAe,CAAC,CAW1B;AAED,2FAA2F;AAC3F,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,EACpD,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAA;CAAE,GAC3E,OAAO,CAAC,eAAe,CAAC,CAW1B;AAED,oEAAoE;AACpE,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,EACpD,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,8FAA8F;AAC9F,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,OAAO,EAAE,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC,EACpD,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,eAAe,CAAC,CAW1B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,CAAC,EACzC,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GACpB,OAAO,CAAC,YAAY,EAAE,CAAC,CAMzB"}
@@ -0,0 +1,62 @@
1
+ import { StorefrontEndpoints, createStorefrontClient } from '../api/index.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Address Book
4
+ // ---------------------------------------------------------------------------
5
+ /** Fetch all saved addresses for the authenticated customer. */
6
+ export async function fetchCustomerAddresses(config, website, params) {
7
+ const client = createStorefrontClient(config);
8
+ return client.get(StorefrontEndpoints.buyer.addresses(), {
9
+ businessId: website.business_id,
10
+ token: params.token,
11
+ failPrefix: 'fetchCustomerAddresses failed',
12
+ });
13
+ }
14
+ /** Save a new address to the customer's address book. */
15
+ export async function createCustomerAddress(config, website, params) {
16
+ const client = createStorefrontClient(config);
17
+ return client.post(StorefrontEndpoints.buyer.addresses(), params.address, {
18
+ businessId: website.business_id,
19
+ token: params.token,
20
+ failLabel: 'createCustomerAddress failed',
21
+ });
22
+ }
23
+ /** Partially update a saved address. Only the fields included in `address` are changed. */
24
+ export async function updateCustomerAddress(config, website, params) {
25
+ const client = createStorefrontClient(config);
26
+ return client.patch(StorefrontEndpoints.buyer.address(params.addressId), params.address, {
27
+ businessId: website.business_id,
28
+ token: params.token,
29
+ failLabel: 'updateCustomerAddress failed',
30
+ });
31
+ }
32
+ /** Delete a saved address. Throws if the address does not exist. */
33
+ export async function deleteCustomerAddress(config, website, params) {
34
+ const client = createStorefrontClient(config);
35
+ await client.delete(StorefrontEndpoints.buyer.address(params.addressId), {
36
+ businessId: website.business_id,
37
+ token: params.token,
38
+ failPrefix: 'deleteCustomerAddress failed',
39
+ });
40
+ }
41
+ /** Mark an address as the customer's default. The previous default is unset automatically. */
42
+ export async function setDefaultAddress(config, website, params) {
43
+ const client = createStorefrontClient(config);
44
+ return client.post(StorefrontEndpoints.buyer.defaultAddress(params.addressId), undefined, {
45
+ businessId: website.business_id,
46
+ token: params.token,
47
+ failPrefix: 'setDefaultAddress failed',
48
+ });
49
+ }
50
+ /**
51
+ * Search Peruvian ubigeo codes (department/province/district) by name.
52
+ * Use this to power the address form's location selector.
53
+ * Returns an empty array on error instead of throwing.
54
+ */
55
+ export async function searchUbigeo(config, params) {
56
+ const client = createStorefrontClient(config);
57
+ return client.get(StorefrontEndpoints.catalog.ubigeos(), {
58
+ query: { q: params.q },
59
+ fallback: [],
60
+ });
61
+ }
62
+ //# sourceMappingURL=address-book.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address-book.js","sourceRoot":"","sources":["../../src/addresses/address-book.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAI9E,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAE9E,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAyC,EACzC,OAAoD,EACpD,MAAyB;IAEzB,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAoB,mBAAmB,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE;QAC1E,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,+BAA+B;KAC5C,CAAC,CAAC;AACL,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAyC,EACzC,OAAoD,EACpD,MAAgD;IAEhD,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,IAAI,CAChB,mBAAmB,CAAC,KAAK,CAAC,SAAS,EAAE,EACrC,MAAM,CAAC,OAAO,EACd;QACE,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,8BAA8B;KAC1C,CACF,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAyC,EACzC,OAAoD,EACpD,MAA4E;IAE5E,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,KAAK,CACjB,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EACnD,MAAM,CAAC,OAAO,EACd;QACE,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,8BAA8B;KAC1C,CACF,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAyC,EACzC,OAAoD,EACpD,MAA4C;IAE5C,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;QACvE,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,8BAA8B;KAC3C,CAAC,CAAC;AACL,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAyC,EACzC,OAAoD,EACpD,MAA4C;IAE5C,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,IAAI,CAChB,mBAAmB,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,EAC1D,SAAS,EACT;QACE,UAAU,EAAE,OAAO,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,UAAU,EAAE,0BAA0B;KACvC,CACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAyC,EACzC,MAAqB;IAErB,MAAM,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,OAAO,MAAM,CAAC,GAAG,CAAiB,mBAAmB,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE;QACvE,KAAK,EAAE,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE;QACtB,QAAQ,EAAE,EAAE;KACb,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { StorefrontAnalyticsConfig, StorefrontEventPayload, StorefrontEventType } from '../types/analytics.js';
2
+ export interface StorefrontAnalyticsInitOptions {
3
+ /**
4
+ * When true, `init()` buffers until the shopper grants analytics consent
5
+ * (`acceptAllCookieConsent()`). Pair with a cookie banner UI.
6
+ */
7
+ requireAnalyticsConsent?: boolean;
8
+ }
9
+ declare class ProximaAnalytics {
10
+ private config;
11
+ private pendingConfig;
12
+ private queue;
13
+ private preInitQueue;
14
+ private timer;
15
+ private initialized;
16
+ private consentRequired;
17
+ init(config: StorefrontAnalyticsConfig, options?: StorefrontAnalyticsInitOptions): void;
18
+ /** Resume init after the shopper grants analytics consent. */
19
+ resumeFromConsent(): void;
20
+ private start;
21
+ track(type: StorefrontEventType, payload?: StorefrontEventPayload): void;
22
+ flush(beacon?: boolean): void;
23
+ destroy(): void;
24
+ }
25
+ /** Singleton analytics client. Call `analytics.init()` once from SiteLayout. */
26
+ export declare const analytics: ProximaAnalytics;
27
+ export {};
28
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/analytics/analytics.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,yBAAyB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEpH,MAAM,WAAW,8BAA8B;IAC7C;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAYD,cAAM,gBAAgB;IACpB,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,aAAa,CAA0C;IAC/D,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,YAAY,CAA4D;IAChF,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAS;IAEhC,IAAI,CAAC,MAAM,EAAE,yBAAyB,EAAE,OAAO,CAAC,EAAE,8BAA8B,GAAG,IAAI;IAUvF,8DAA8D;IAC9D,iBAAiB,IAAI,IAAI;IASzB,OAAO,CAAC,KAAK;IAyBb,KAAK,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,GAAE,sBAA2B,GAAG,IAAI;IAwB5E,KAAK,CAAC,MAAM,UAAQ,GAAG,IAAI;IAqB3B,OAAO,IAAI,IAAI;CAUhB;AAED,gFAAgF;AAChF,eAAO,MAAM,SAAS,kBAAyB,CAAC"}
@@ -0,0 +1,124 @@
1
+ import { StorefrontEndpoints } from '../api/endpoints.js';
2
+ import { isAnalyticsConsentGranted, onCookieConsentChanged } from '../cookie-consent/consent.js';
3
+ import { getSessionAttribution, captureSessionAttribution } from './attribution.js';
4
+ import { resolveAnalyticsSessionId } from './session.js';
5
+ class ProximaAnalytics {
6
+ config = null;
7
+ pendingConfig = null;
8
+ queue = [];
9
+ preInitQueue = [];
10
+ timer = null;
11
+ initialized = false;
12
+ consentRequired = false;
13
+ init(config, options) {
14
+ if (typeof window === 'undefined')
15
+ return;
16
+ if (options?.requireAnalyticsConsent) {
17
+ this.consentRequired = true;
18
+ this.pendingConfig = config;
19
+ if (!isAnalyticsConsentGranted())
20
+ return;
21
+ }
22
+ this.start(config);
23
+ }
24
+ /** Resume init after the shopper grants analytics consent. */
25
+ resumeFromConsent() {
26
+ if (typeof window === 'undefined')
27
+ return;
28
+ if (this.initialized)
29
+ return;
30
+ if (!this.consentRequired || !isAnalyticsConsentGranted())
31
+ return;
32
+ const config = this.pendingConfig ?? this.config;
33
+ if (!config)
34
+ return;
35
+ this.start(config);
36
+ }
37
+ start(config) {
38
+ if (typeof window === 'undefined')
39
+ return;
40
+ this.config = config;
41
+ this.pendingConfig = null;
42
+ if (this.initialized)
43
+ return;
44
+ this.initialized = true;
45
+ captureSessionAttribution();
46
+ for (const [type, payload] of this.preInitQueue.splice(0)) {
47
+ this.track(type, payload);
48
+ }
49
+ const firePageView = () => this.track('page_view');
50
+ firePageView();
51
+ document.addEventListener('astro:page-load', firePageView);
52
+ const interval = config.flushInterval ?? 3000;
53
+ this.timer = setInterval(() => this.flush(), interval);
54
+ document.addEventListener('visibilitychange', () => {
55
+ if (document.visibilityState === 'hidden')
56
+ this.flush(true);
57
+ });
58
+ }
59
+ track(type, payload = {}) {
60
+ if (typeof window === 'undefined')
61
+ return;
62
+ if (!this.config) {
63
+ this.preInitQueue.push([type, payload]);
64
+ return;
65
+ }
66
+ const attribution = getSessionAttribution();
67
+ const hasAttribution = Object.keys(attribution).length > 0;
68
+ const mergedPayload = hasAttribution
69
+ ? { ...payload, attribution }
70
+ : payload;
71
+ const event = {
72
+ event_type: type,
73
+ occurred_at: new Date().toISOString(),
74
+ website_id: this.config.websiteId,
75
+ path: window.location.pathname,
76
+ referrer: document.referrer || undefined,
77
+ locale: this.config.locale,
78
+ payload: mergedPayload,
79
+ };
80
+ this.queue.push(event);
81
+ if (this.config.debug)
82
+ console.debug('[proxima:analytics] queued', event);
83
+ }
84
+ flush(beacon = false) {
85
+ if (!this.config || this.queue.length === 0)
86
+ return;
87
+ const batch = this.queue.splice(0);
88
+ const url = `${this.config.apiUrl.replace(/\/$/, '')}${StorefrontEndpoints.analytics.events()}`;
89
+ const body = JSON.stringify({ events: batch });
90
+ const sessionId = resolveAnalyticsSessionId(this.config);
91
+ const headers = {
92
+ 'Content-Type': 'application/json',
93
+ 'X-Business-ID': this.config.businessId,
94
+ 'X-Session-ID': sessionId,
95
+ };
96
+ if (this.config.debug) {
97
+ console.debug(`[proxima:analytics] flushing ${batch.length} event(s) → ${url}`);
98
+ }
99
+ void fetch(url, { method: 'POST', headers, body, keepalive: true }).catch(() => {
100
+ if (beacon)
101
+ this.queue.unshift(...batch);
102
+ });
103
+ }
104
+ destroy() {
105
+ if (this.timer !== null)
106
+ clearInterval(this.timer);
107
+ this.timer = null;
108
+ this.initialized = false;
109
+ this.consentRequired = false;
110
+ this.pendingConfig = null;
111
+ this.config = null;
112
+ this.queue = [];
113
+ this.preInitQueue = [];
114
+ }
115
+ }
116
+ /** Singleton analytics client. Call `analytics.init()` once from SiteLayout. */
117
+ export const analytics = new ProximaAnalytics();
118
+ if (typeof window !== 'undefined') {
119
+ onCookieConsentChanged((state) => {
120
+ if (state.analytics)
121
+ analytics.resumeFromConsent();
122
+ });
123
+ }
124
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../src/analytics/analytics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /** Session-scoped first-touch marketing attribution (UTMs + ad click IDs). */
2
+ export declare const ATTRIBUTION_STORAGE_KEY = "pxa_session_attribution";
3
+ export interface SessionAttribution {
4
+ utm_source?: string;
5
+ utm_medium?: string;
6
+ utm_campaign?: string;
7
+ utm_content?: string;
8
+ utm_term?: string;
9
+ gclid?: string;
10
+ fbclid?: string;
11
+ msclkid?: string;
12
+ /** Path + query of the landing hit in this tab session. */
13
+ landing_path?: string;
14
+ /** document.referrer on the landing hit. */
15
+ landing_referrer?: string;
16
+ }
17
+ /** Best-effort channel guess when paid UTMs are absent (organic/social/direct). */
18
+ export declare function inferAttributionFromReferrer(referrer: string): Partial<SessionAttribution>;
19
+ /**
20
+ * Capture first-touch attribution for the current browser tab session.
21
+ * UTMs / click IDs from the landing URL win; subsequent navigations keep the snapshot.
22
+ */
23
+ export declare function captureSessionAttribution(): SessionAttribution;
24
+ /** Returns the stored session attribution (capturing on first call). */
25
+ export declare function getSessionAttribution(): SessionAttribution;
26
+ /** Test helper — clears stored attribution. */
27
+ export declare function clearSessionAttribution(): void;
28
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../src/analytics/attribution.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,4BAA4B,CAAC;AAEjE,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA+CD,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAkB1F;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,IAAI,kBAAkB,CAuB9D;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,IAAI,kBAAkB,CAE1D;AAED,+CAA+C;AAC/C,wBAAgB,uBAAuB,IAAI,IAAI,CAO9C"}
@@ -0,0 +1,116 @@
1
+ /** Session-scoped first-touch marketing attribution (UTMs + ad click IDs). */
2
+ export const ATTRIBUTION_STORAGE_KEY = 'pxa_session_attribution';
3
+ const UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
4
+ const CLICK_ID_KEYS = ['gclid', 'fbclid', 'msclkid'];
5
+ const MAX_VALUE_LEN = 256;
6
+ function trimValue(raw) {
7
+ const value = raw?.trim();
8
+ if (!value)
9
+ return undefined;
10
+ return value.length > MAX_VALUE_LEN ? value.slice(0, MAX_VALUE_LEN) : value;
11
+ }
12
+ function readStored() {
13
+ if (typeof window === 'undefined')
14
+ return null;
15
+ try {
16
+ const raw = window.localStorage?.getItem(ATTRIBUTION_STORAGE_KEY);
17
+ if (!raw)
18
+ return null;
19
+ const parsed = JSON.parse(raw);
20
+ return parsed && typeof parsed === 'object' ? parsed : null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function writeStored(value) {
27
+ if (typeof window === 'undefined' || !window.localStorage)
28
+ return;
29
+ try {
30
+ window.localStorage.setItem(ATTRIBUTION_STORAGE_KEY, JSON.stringify(value));
31
+ }
32
+ catch {
33
+ /* private mode */
34
+ }
35
+ }
36
+ function readFromSearchParams(search) {
37
+ const out = {};
38
+ for (const key of UTM_KEYS) {
39
+ const value = trimValue(search.get(key));
40
+ if (value)
41
+ out[key] = value;
42
+ }
43
+ for (const key of CLICK_ID_KEYS) {
44
+ const value = trimValue(search.get(key));
45
+ if (value)
46
+ out[key] = value;
47
+ }
48
+ return out;
49
+ }
50
+ /** Best-effort channel guess when paid UTMs are absent (organic/social/direct). */
51
+ export function inferAttributionFromReferrer(referrer) {
52
+ if (!referrer.trim())
53
+ return {};
54
+ try {
55
+ const host = new URL(referrer).hostname.replace(/^www\./i, '').toLowerCase();
56
+ if (host === 't.co' || host.endsWith('.twitter.com') || host === 'twitter.com' || host === 'x.com') {
57
+ return { utm_source: 'twitter', utm_medium: 'social' };
58
+ }
59
+ if (host.includes('google.'))
60
+ return { utm_source: 'google', utm_medium: 'organic' };
61
+ if (host.includes('facebook.com') || host.includes('instagram.com') || host.includes('l.facebook.com')) {
62
+ return { utm_source: 'facebook', utm_medium: 'social' };
63
+ }
64
+ if (host.includes('bing.com'))
65
+ return { utm_source: 'bing', utm_medium: 'organic' };
66
+ if (host.includes('tiktok.com'))
67
+ return { utm_source: 'tiktok', utm_medium: 'social' };
68
+ if (host.includes('youtube.com'))
69
+ return { utm_source: 'youtube', utm_medium: 'social' };
70
+ return { utm_source: host, utm_medium: 'referral' };
71
+ }
72
+ catch {
73
+ return {};
74
+ }
75
+ }
76
+ /**
77
+ * Capture first-touch attribution for the current browser tab session.
78
+ * UTMs / click IDs from the landing URL win; subsequent navigations keep the snapshot.
79
+ */
80
+ export function captureSessionAttribution() {
81
+ if (typeof window === 'undefined')
82
+ return {};
83
+ const stored = readStored();
84
+ if (stored)
85
+ return stored;
86
+ const href = window.location.href;
87
+ const url = new URL(href);
88
+ const fromUrl = readFromSearchParams(url.searchParams);
89
+ const landingReferrer = document.referrer?.trim() || undefined;
90
+ const attribution = {
91
+ landing_path: `${url.pathname}${url.search}`,
92
+ landing_referrer: landingReferrer,
93
+ ...fromUrl,
94
+ };
95
+ if (!attribution.utm_source && landingReferrer) {
96
+ Object.assign(attribution, inferAttributionFromReferrer(landingReferrer));
97
+ }
98
+ writeStored(attribution);
99
+ return attribution;
100
+ }
101
+ /** Returns the stored session attribution (capturing on first call). */
102
+ export function getSessionAttribution() {
103
+ return captureSessionAttribution();
104
+ }
105
+ /** Test helper — clears stored attribution. */
106
+ export function clearSessionAttribution() {
107
+ if (typeof window === 'undefined')
108
+ return;
109
+ try {
110
+ window.localStorage.removeItem(ATTRIBUTION_STORAGE_KEY);
111
+ }
112
+ catch {
113
+ /* ignore */
114
+ }
115
+ }
116
+ //# sourceMappingURL=attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.js","sourceRoot":"","sources":["../../src/analytics/attribution.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { StorefrontAnalyticsConfig } from '../types/analytics.js';
2
+ export declare const ANALYTICS_SESSION_STORAGE_KEY = "pxa_analytics_session";
3
+ export interface ResolveAnalyticsSessionIdOptions {
4
+ cookieName?: string;
5
+ storageKey?: string;
6
+ }
7
+ /**
8
+ * Resolve the browser session id sent as `X-Session-ID` on analytics batches.
9
+ * Priority: config.sessionId → config.getSessionId() → readable cart cookie → localStorage UUID.
10
+ */
11
+ export declare function resolveAnalyticsSessionId(config?: Pick<StorefrontAnalyticsConfig, 'sessionId' | 'getSessionId' | 'cartSessionCookieName'>, options?: ResolveAnalyticsSessionIdOptions): string;
12
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/analytics/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAEvE,eAAO,MAAM,6BAA6B,0BAA0B,CAAC;AAsCrE,MAAM,WAAW,gCAAgC;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,CAAC,EAAE,IAAI,CAAC,yBAAyB,EAAE,WAAW,GAAG,cAAc,GAAG,uBAAuB,CAAC,EAChG,OAAO,CAAC,EAAE,gCAAgC,GACzC,MAAM,CAuBR"}
@@ -0,0 +1,62 @@
1
+ export const ANALYTICS_SESSION_STORAGE_KEY = 'pxa_analytics_session';
2
+ const DEFAULT_CART_SESSION_COOKIE = 'cart_session_id';
3
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
+ function isBrowser() {
5
+ return typeof window !== 'undefined';
6
+ }
7
+ function readCookie(name) {
8
+ if (!isBrowser())
9
+ return null;
10
+ const prefix = `${name}=`;
11
+ for (const part of document.cookie.split(';')) {
12
+ const trimmed = part.trim();
13
+ if (trimmed.startsWith(prefix)) {
14
+ return decodeURIComponent(trimmed.slice(prefix.length));
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ function isUuid(value) {
20
+ return UUID_RE.test(value);
21
+ }
22
+ function createSessionId() {
23
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
24
+ return crypto.randomUUID();
25
+ }
26
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
27
+ const r = (Math.random() * 16) | 0;
28
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
29
+ return v.toString(16);
30
+ });
31
+ }
32
+ /**
33
+ * Resolve the browser session id sent as `X-Session-ID` on analytics batches.
34
+ * Priority: config.sessionId → config.getSessionId() → readable cart cookie → localStorage UUID.
35
+ */
36
+ export function resolveAnalyticsSessionId(config, options) {
37
+ const explicit = config?.sessionId?.trim();
38
+ if (explicit && isUuid(explicit))
39
+ return explicit;
40
+ const fromGetter = config?.getSessionId?.()?.trim();
41
+ if (fromGetter && isUuid(fromGetter))
42
+ return fromGetter;
43
+ const cookieName = config?.cartSessionCookieName ?? options?.cookieName ?? DEFAULT_CART_SESSION_COOKIE;
44
+ const fromCookie = readCookie(cookieName)?.trim();
45
+ if (fromCookie && isUuid(fromCookie))
46
+ return fromCookie;
47
+ if (!isBrowser())
48
+ return createSessionId();
49
+ const storageKey = options?.storageKey ?? ANALYTICS_SESSION_STORAGE_KEY;
50
+ try {
51
+ const stored = localStorage.getItem(storageKey)?.trim();
52
+ if (stored && isUuid(stored))
53
+ return stored;
54
+ const created = createSessionId();
55
+ localStorage.setItem(storageKey, created);
56
+ return created;
57
+ }
58
+ catch {
59
+ return createSessionId();
60
+ }
61
+ }
62
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/analytics/session.ts"],"names":[],"mappings":"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"}