@behio/storefront-sdk 1.4.0 → 1.6.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.
@@ -143,6 +143,7 @@ var BehioStorefront = class {
143
143
  this.timeout = _nullishCoalesce(config.timeout, () => ( 3e4));
144
144
  this.retries = _nullishCoalesce(config.retries, () => ( 1));
145
145
  this.retryDelay = _nullishCoalesce(config.retryDelay, () => ( 1e3));
146
+ this.visitorIp = config.visitorIp;
146
147
  this.catalog = new CatalogModule(this);
147
148
  this.auth = new AuthModule(this);
148
149
  this.cart = new CartModule(this);
@@ -174,6 +175,15 @@ var BehioStorefront = class {
174
175
  getAnalyticsVisitorId() {
175
176
  return this.analyticsVisitorId;
176
177
  }
178
+ /**
179
+ * Set (or clear with null) the real visitor IP forwarded as
180
+ * X-Behio-Visitor-Ip on server-side requests. See
181
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
182
+ * automatically for per-request clients.
183
+ */
184
+ setVisitorIp(ip) {
185
+ this.visitorIp = _nullishCoalesce(ip, () => ( void 0));
186
+ }
177
187
  /**
178
188
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
179
189
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -439,6 +449,13 @@ var BehioStorefront = class {
439
449
  if (this.analyticsVisitorId) {
440
450
  headers["X-Behio-Vid"] = this.analyticsVisitorId;
441
451
  }
452
+ if (this.visitorIp) {
453
+ try {
454
+ const ip = typeof this.visitorIp === "function" ? await this.visitorIp() : this.visitorIp;
455
+ if (ip) headers["X-Behio-Visitor-Ip"] = ip;
456
+ } catch (e4) {
457
+ }
458
+ }
442
459
  if (_optionalChain([options, 'optionalAccess', _14 => _14.headers])) {
443
460
  Object.assign(headers, options.headers);
444
461
  }
@@ -516,7 +533,7 @@ var BehioStorefront = class {
516
533
  ...options,
517
534
  _isRetryAfterRefresh: true
518
535
  });
519
- } catch (e4) {
536
+ } catch (e5) {
520
537
  this.emit("error", apiError);
521
538
  throw apiError;
522
539
  }
@@ -538,7 +555,7 @@ var BehioStorefront = class {
538
555
  data: responseData,
539
556
  headers: res.headers
540
557
  });
541
- } catch (e5) {
558
+ } catch (e6) {
542
559
  }
543
560
  }
544
561
  return responseData;
@@ -955,6 +972,29 @@ var CartModule = class {
955
972
  this.client.emit("cart:updated", res.data);
956
973
  return res;
957
974
  }
975
+ /**
976
+ * Switch the CART currency server-side. The backend re-prices every line in
977
+ * the new currency (no FX conversion: a product must have a price configured
978
+ * in that currency, otherwise the call fails with 400 listing the items and
979
+ * the cart stays unchanged). Creates an empty cart in that currency when
980
+ * none exists yet.
981
+ *
982
+ * This is the money-path counterpart of `client.setCurrency()` (which only
983
+ * affects catalog display). Call BOTH from a currency switcher:
984
+ *
985
+ * ```ts
986
+ * client.setCurrency("EUR"); // catalog prices
987
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
988
+ * ```
989
+ */
990
+ async setCurrency(currency) {
991
+ const res = await this.client.request("PUT", "/cart/currency", {
992
+ body: { currency }
993
+ });
994
+ if (res.error) return res;
995
+ this.client.emit("cart:updated", res.data);
996
+ return res;
997
+ }
958
998
  /** Update item quantity */
959
999
  async updateQuantity(itemId, quantity) {
960
1000
  const res = await this.client.request(
@@ -143,6 +143,7 @@ var BehioStorefront = class {
143
143
  this.timeout = config.timeout ?? 3e4;
144
144
  this.retries = config.retries ?? 1;
145
145
  this.retryDelay = config.retryDelay ?? 1e3;
146
+ this.visitorIp = config.visitorIp;
146
147
  this.catalog = new CatalogModule(this);
147
148
  this.auth = new AuthModule(this);
148
149
  this.cart = new CartModule(this);
@@ -174,6 +175,15 @@ var BehioStorefront = class {
174
175
  getAnalyticsVisitorId() {
175
176
  return this.analyticsVisitorId;
176
177
  }
178
+ /**
179
+ * Set (or clear with null) the real visitor IP forwarded as
180
+ * X-Behio-Visitor-Ip on server-side requests. See
181
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
182
+ * automatically for per-request clients.
183
+ */
184
+ setVisitorIp(ip) {
185
+ this.visitorIp = ip ?? void 0;
186
+ }
177
187
  /**
178
188
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
179
189
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -439,6 +449,13 @@ var BehioStorefront = class {
439
449
  if (this.analyticsVisitorId) {
440
450
  headers["X-Behio-Vid"] = this.analyticsVisitorId;
441
451
  }
452
+ if (this.visitorIp) {
453
+ try {
454
+ const ip = typeof this.visitorIp === "function" ? await this.visitorIp() : this.visitorIp;
455
+ if (ip) headers["X-Behio-Visitor-Ip"] = ip;
456
+ } catch {
457
+ }
458
+ }
442
459
  if (options?.headers) {
443
460
  Object.assign(headers, options.headers);
444
461
  }
@@ -955,6 +972,29 @@ var CartModule = class {
955
972
  this.client.emit("cart:updated", res.data);
956
973
  return res;
957
974
  }
975
+ /**
976
+ * Switch the CART currency server-side. The backend re-prices every line in
977
+ * the new currency (no FX conversion: a product must have a price configured
978
+ * in that currency, otherwise the call fails with 400 listing the items and
979
+ * the cart stays unchanged). Creates an empty cart in that currency when
980
+ * none exists yet.
981
+ *
982
+ * This is the money-path counterpart of `client.setCurrency()` (which only
983
+ * affects catalog display). Call BOTH from a currency switcher:
984
+ *
985
+ * ```ts
986
+ * client.setCurrency("EUR"); // catalog prices
987
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
988
+ * ```
989
+ */
990
+ async setCurrency(currency) {
991
+ const res = await this.client.request("PUT", "/cart/currency", {
992
+ body: { currency }
993
+ });
994
+ if (res.error) return res;
995
+ this.client.emit("cart:updated", res.data);
996
+ return res;
997
+ }
958
998
  /** Update item quantity */
959
999
  async updateQuantity(itemId, quantity) {
960
1000
  const res = await this.client.request(
@@ -17,6 +17,18 @@ interface BehioStorefrontConfig {
17
17
  currency?: string;
18
18
  /** Custom fetch implementation (for Node.js < 18 or testing) */
19
19
  fetch?: typeof fetch;
20
+ /**
21
+ * Real visitor IP for server-side rendering. Sent as X-Behio-Visitor-Ip so
22
+ * the backend rate-limits per visitor instead of per server: all SSR
23
+ * requests of one deployment share the server's IP, so without this header
24
+ * the whole shop shares one rate-limit bucket. The backend only trusts the
25
+ * header from allow-listed hosting IPs, so a browser sending it gains
26
+ * nothing. Accepts a fixed string or a resolver called per request (use a
27
+ * resolver for a shared catalog client that serves many concurrent
28
+ * requests). The Next.js adapter (`@behio/storefront-sdk/next`) fills this
29
+ * automatically from the incoming request headers.
30
+ */
31
+ visitorIp?: string | (() => string | null | undefined | Promise<string | null | undefined>);
20
32
  /** Request timeout in milliseconds. Default: 30000 (30s) */
21
33
  timeout?: number;
22
34
  /** Number of retries on network/5xx errors. Default: 1 */
@@ -2267,6 +2279,7 @@ declare class BehioStorefront {
2267
2279
  private responseInterceptors;
2268
2280
  private rateLimitRemaining;
2269
2281
  private rateLimitReset;
2282
+ private visitorIp?;
2270
2283
  constructor(config: BehioStorefrontConfig);
2271
2284
  readonly catalog: CatalogModule;
2272
2285
  readonly auth: AuthModule;
@@ -2293,6 +2306,13 @@ declare class BehioStorefront {
2293
2306
  setAnalyticsVisitorId(id: string | null): void;
2294
2307
  /** The consent-gated visitor id, if analytics consent was granted. */
2295
2308
  getAnalyticsVisitorId(): string | null;
2309
+ /**
2310
+ * Set (or clear with null) the real visitor IP forwarded as
2311
+ * X-Behio-Visitor-Ip on server-side requests. See
2312
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
2313
+ * automatically for per-request clients.
2314
+ */
2315
+ setVisitorIp(ip: string | null): void;
2296
2316
  /**
2297
2317
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
2298
2318
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -2596,6 +2616,22 @@ declare class CartModule {
2596
2616
  addItem(input: AddToCartInput): Promise<SdkResult<Cart & {
2597
2617
  newSessionToken?: string;
2598
2618
  }>>;
2619
+ /**
2620
+ * Switch the CART currency server-side. The backend re-prices every line in
2621
+ * the new currency (no FX conversion: a product must have a price configured
2622
+ * in that currency, otherwise the call fails with 400 listing the items and
2623
+ * the cart stays unchanged). Creates an empty cart in that currency when
2624
+ * none exists yet.
2625
+ *
2626
+ * This is the money-path counterpart of `client.setCurrency()` (which only
2627
+ * affects catalog display). Call BOTH from a currency switcher:
2628
+ *
2629
+ * ```ts
2630
+ * client.setCurrency("EUR"); // catalog prices
2631
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
2632
+ * ```
2633
+ */
2634
+ setCurrency(currency: string): Promise<SdkResult<Cart>>;
2599
2635
  /** Update item quantity */
2600
2636
  updateQuantity(itemId: string, quantity: number): Promise<SdkResult<Cart>>;
2601
2637
  /** Remove item from cart */
@@ -17,6 +17,18 @@ interface BehioStorefrontConfig {
17
17
  currency?: string;
18
18
  /** Custom fetch implementation (for Node.js < 18 or testing) */
19
19
  fetch?: typeof fetch;
20
+ /**
21
+ * Real visitor IP for server-side rendering. Sent as X-Behio-Visitor-Ip so
22
+ * the backend rate-limits per visitor instead of per server: all SSR
23
+ * requests of one deployment share the server's IP, so without this header
24
+ * the whole shop shares one rate-limit bucket. The backend only trusts the
25
+ * header from allow-listed hosting IPs, so a browser sending it gains
26
+ * nothing. Accepts a fixed string or a resolver called per request (use a
27
+ * resolver for a shared catalog client that serves many concurrent
28
+ * requests). The Next.js adapter (`@behio/storefront-sdk/next`) fills this
29
+ * automatically from the incoming request headers.
30
+ */
31
+ visitorIp?: string | (() => string | null | undefined | Promise<string | null | undefined>);
20
32
  /** Request timeout in milliseconds. Default: 30000 (30s) */
21
33
  timeout?: number;
22
34
  /** Number of retries on network/5xx errors. Default: 1 */
@@ -2267,6 +2279,7 @@ declare class BehioStorefront {
2267
2279
  private responseInterceptors;
2268
2280
  private rateLimitRemaining;
2269
2281
  private rateLimitReset;
2282
+ private visitorIp?;
2270
2283
  constructor(config: BehioStorefrontConfig);
2271
2284
  readonly catalog: CatalogModule;
2272
2285
  readonly auth: AuthModule;
@@ -2293,6 +2306,13 @@ declare class BehioStorefront {
2293
2306
  setAnalyticsVisitorId(id: string | null): void;
2294
2307
  /** The consent-gated visitor id, if analytics consent was granted. */
2295
2308
  getAnalyticsVisitorId(): string | null;
2309
+ /**
2310
+ * Set (or clear with null) the real visitor IP forwarded as
2311
+ * X-Behio-Visitor-Ip on server-side requests. See
2312
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
2313
+ * automatically for per-request clients.
2314
+ */
2315
+ setVisitorIp(ip: string | null): void;
2296
2316
  /**
2297
2317
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
2298
2318
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -2596,6 +2616,22 @@ declare class CartModule {
2596
2616
  addItem(input: AddToCartInput): Promise<SdkResult<Cart & {
2597
2617
  newSessionToken?: string;
2598
2618
  }>>;
2619
+ /**
2620
+ * Switch the CART currency server-side. The backend re-prices every line in
2621
+ * the new currency (no FX conversion: a product must have a price configured
2622
+ * in that currency, otherwise the call fails with 400 listing the items and
2623
+ * the cart stays unchanged). Creates an empty cart in that currency when
2624
+ * none exists yet.
2625
+ *
2626
+ * This is the money-path counterpart of `client.setCurrency()` (which only
2627
+ * affects catalog display). Call BOTH from a currency switcher:
2628
+ *
2629
+ * ```ts
2630
+ * client.setCurrency("EUR"); // catalog prices
2631
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
2632
+ * ```
2633
+ */
2634
+ setCurrency(currency: string): Promise<SdkResult<Cart>>;
2599
2635
  /** Update item quantity */
2600
2636
  updateQuantity(itemId: string, quantity: number): Promise<SdkResult<Cart>>;
2601
2637
  /** Remove item from cart */
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProductDetail, a as ProductVariant, B as BehioStorefront, S as SdkResult, C as CookieConsent } from './client-YJtbTLts.mjs';
2
- export { A as ActivePromotion, b as AddToCartInput, c as AddressDetail, d as AddressSuggestion, e as AddressType, f as AddressTypes, g as AuthTokens, h as BackInStockSubscription, i as BadgeTone, j as BehioApiError, k as BehioErrorCode, l as BehioEventHandler, m as BehioEventType, n as BehioNetworkError, o as BehioStorefrontConfig, p as Bundle, q as BundleItem, r as Cart, s as CartBundleLine, t as CartBundleLineItem, u as CartDiscount, v as CartItem, w as CartItemProduct, x as CartPromotion, y as Category, z as CategoryDetail, D as CertificateVerification, E as CheckoutAddress, F as CheckoutInput, G as CheckoutPaymentMethod, H as CheckoutSettings, I as CookieConsentInput, J as CourseAttachment, K as CourseCertificate, L as CourseComment, M as CourseCommentReply, N as CourseCommentsList, O as CourseDetail, Q as CourseLesson, R as CourseListItem, T as CourseModule, U as CoursePostedComment, V as CourseProgress, W as CourseTutorMessage, X as CourseTutorThread, Y as CrossSellItem, Z as CustomerAddress, _ as CustomerProfile, $ as DigitalDownload, a0 as DownloadUrl, a1 as Facet, a2 as FacetAvailability, a3 as FacetCategory, a4 as FacetLabel, a5 as FacetPriceRange, a6 as FacetRange, a7 as FacetRatingBucket, a8 as FacetValue, a9 as FacetsResponse, aa as FilterField, ab as FulfillmentStatus, ac as FulfillmentStatuses, ad as GiftCardBalance, ae as GiftCardPurchaseInput, af as GiftCardPurchaseResult, ag as GiftCardSummary, ah as LessonNote, ai as LessonQuiz, aj as LoginInput, ak as LoyaltyBalance, al as LoyaltyNextTier, am as LoyaltyProgram, an as LoyaltySummary, ao as LoyaltyTier, ap as LoyaltyTierPerks, aq as LoyaltyTransaction, ar as Menu, as as MenuItem, at as MenuItemRef, au as MenuItemType, av as MessageResponse, aw as NewsletterOptInDefault, ax as NewsletterSubscribeInput, ay as NewsletterSubscribeResult, az as NewsletterUnsubscribeResult, aA as OrderAccessRequestResponse, aB as OrderAccessVerifyResponse, aC as OrderDetail, aD as OrderItem, aE as OrderListItem, aF as OrderStatus, aG as OrderStatusHistory, aH as OrderStatuses, aI as OrderTracking, aJ as Page, aK as PageAttachment, aL as PageDetail, aM as PaginatedResponse, aN as PaymentStatus, aO as PaymentStatuses, aP as PickupPoint, aQ as PickupPointHours, aR as PickupPointsInput, aS as PriceDisplay, aT as ProductAvailability, aU as ProductGroup, aV as ProductLabel, aW as ProductListItem, aX as ProductMedia, aY as ProductMediaVariant, aZ as ProductParameter, a_ as ProductParameterGroup, a$ as ProductParametersResponse, b0 as ProductPrice, b1 as ProductPromotionSummary, b2 as ProductReview, b3 as ProductReviewsResponse, b4 as ProductSibling, b5 as ProductSort, b6 as ProductSortValue, b7 as ProductVolumePrice, b8 as ProductsQuery, b9 as QuizAnswerInput, ba as QuizAnswerResult, bb as QuizQuestion, bc as QuizResult, bd as QuoteItem, be as QuoteRequest, bf as RegisterInput, bg as RegisterResult, bh as RequestInterceptor, bi as RequestInterceptorConfig, bj as ResponseInterceptor, bk as ResponseInterceptorData, bl as ReturnRequest, bm as ReturnRequestItem, bn as ReturnStatus, bo as ReturnStatusItem, bp as ReturnableOrder, bq as ReturnableOrderItem, br as SdkError, bs as ShippingMethodSummary, bt as ShippingQuote, bu as ShippingQuoteInput, bv as ShopInfo, bw as ShopScript, bx as ShopScriptPlacement, by as ShopScriptType, bz as ShopScripts, bA as ShopSeo, bB as ShopSeoIdentity, bC as Sitemap, bD as SitemapEntry, bE as StockBehavior, bF as StockMode, bG as SubmitQuoteInput, bH as SubmitReturnInput, bI as SubmitReviewInput, bJ as Subscription, bK as SubscriptionAction, bL as SubscriptionFrequency, bM as SubscriptionItem, bN as SubscriptionStatus, bO as TaxBreakdownLine, bP as VariantAxis, bQ as VariantAxisValue, bR as WishlistItem, bS as err, bT as ok, bU as toSdkError } from './client-YJtbTLts.mjs';
1
+ import { P as ProductDetail, a as ProductVariant, B as BehioStorefront, S as SdkResult, C as CookieConsent } from './client-B9jHeWca.mjs';
2
+ export { A as ActivePromotion, b as AddToCartInput, c as AddressDetail, d as AddressSuggestion, e as AddressType, f as AddressTypes, g as AuthTokens, h as BackInStockSubscription, i as BadgeTone, j as BehioApiError, k as BehioErrorCode, l as BehioEventHandler, m as BehioEventType, n as BehioNetworkError, o as BehioStorefrontConfig, p as Bundle, q as BundleItem, r as Cart, s as CartBundleLine, t as CartBundleLineItem, u as CartDiscount, v as CartItem, w as CartItemProduct, x as CartPromotion, y as Category, z as CategoryDetail, D as CertificateVerification, E as CheckoutAddress, F as CheckoutInput, G as CheckoutPaymentMethod, H as CheckoutSettings, I as CookieConsentInput, J as CourseAttachment, K as CourseCertificate, L as CourseComment, M as CourseCommentReply, N as CourseCommentsList, O as CourseDetail, Q as CourseLesson, R as CourseListItem, T as CourseModule, U as CoursePostedComment, V as CourseProgress, W as CourseTutorMessage, X as CourseTutorThread, Y as CrossSellItem, Z as CustomerAddress, _ as CustomerProfile, $ as DigitalDownload, a0 as DownloadUrl, a1 as Facet, a2 as FacetAvailability, a3 as FacetCategory, a4 as FacetLabel, a5 as FacetPriceRange, a6 as FacetRange, a7 as FacetRatingBucket, a8 as FacetValue, a9 as FacetsResponse, aa as FilterField, ab as FulfillmentStatus, ac as FulfillmentStatuses, ad as GiftCardBalance, ae as GiftCardPurchaseInput, af as GiftCardPurchaseResult, ag as GiftCardSummary, ah as LessonNote, ai as LessonQuiz, aj as LoginInput, ak as LoyaltyBalance, al as LoyaltyNextTier, am as LoyaltyProgram, an as LoyaltySummary, ao as LoyaltyTier, ap as LoyaltyTierPerks, aq as LoyaltyTransaction, ar as Menu, as as MenuItem, at as MenuItemRef, au as MenuItemType, av as MessageResponse, aw as NewsletterOptInDefault, ax as NewsletterSubscribeInput, ay as NewsletterSubscribeResult, az as NewsletterUnsubscribeResult, aA as OrderAccessRequestResponse, aB as OrderAccessVerifyResponse, aC as OrderDetail, aD as OrderItem, aE as OrderListItem, aF as OrderStatus, aG as OrderStatusHistory, aH as OrderStatuses, aI as OrderTracking, aJ as Page, aK as PageAttachment, aL as PageDetail, aM as PaginatedResponse, aN as PaymentStatus, aO as PaymentStatuses, aP as PickupPoint, aQ as PickupPointHours, aR as PickupPointsInput, aS as PriceDisplay, aT as ProductAvailability, aU as ProductGroup, aV as ProductLabel, aW as ProductListItem, aX as ProductMedia, aY as ProductMediaVariant, aZ as ProductParameter, a_ as ProductParameterGroup, a$ as ProductParametersResponse, b0 as ProductPrice, b1 as ProductPromotionSummary, b2 as ProductReview, b3 as ProductReviewsResponse, b4 as ProductSibling, b5 as ProductSort, b6 as ProductSortValue, b7 as ProductVolumePrice, b8 as ProductsQuery, b9 as QuizAnswerInput, ba as QuizAnswerResult, bb as QuizQuestion, bc as QuizResult, bd as QuoteItem, be as QuoteRequest, bf as RegisterInput, bg as RegisterResult, bh as RequestInterceptor, bi as RequestInterceptorConfig, bj as ResponseInterceptor, bk as ResponseInterceptorData, bl as ReturnRequest, bm as ReturnRequestItem, bn as ReturnStatus, bo as ReturnStatusItem, bp as ReturnableOrder, bq as ReturnableOrderItem, br as SdkError, bs as ShippingMethodSummary, bt as ShippingQuote, bu as ShippingQuoteInput, bv as ShopInfo, bw as ShopScript, bx as ShopScriptPlacement, by as ShopScriptType, bz as ShopScripts, bA as ShopSeo, bB as ShopSeoIdentity, bC as Sitemap, bD as SitemapEntry, bE as StockBehavior, bF as StockMode, bG as SubmitQuoteInput, bH as SubmitReturnInput, bI as SubmitReviewInput, bJ as Subscription, bK as SubscriptionAction, bL as SubscriptionFrequency, bM as SubscriptionItem, bN as SubscriptionStatus, bO as TaxBreakdownLine, bP as VariantAxis, bQ as VariantAxisValue, bR as WishlistItem, bS as err, bT as ok, bU as toSdkError } from './client-B9jHeWca.mjs';
3
3
 
4
4
  /**
5
5
  * Format a price amount with currency using Intl.NumberFormat.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ProductDetail, a as ProductVariant, B as BehioStorefront, S as SdkResult, C as CookieConsent } from './client-YJtbTLts.js';
2
- export { A as ActivePromotion, b as AddToCartInput, c as AddressDetail, d as AddressSuggestion, e as AddressType, f as AddressTypes, g as AuthTokens, h as BackInStockSubscription, i as BadgeTone, j as BehioApiError, k as BehioErrorCode, l as BehioEventHandler, m as BehioEventType, n as BehioNetworkError, o as BehioStorefrontConfig, p as Bundle, q as BundleItem, r as Cart, s as CartBundleLine, t as CartBundleLineItem, u as CartDiscount, v as CartItem, w as CartItemProduct, x as CartPromotion, y as Category, z as CategoryDetail, D as CertificateVerification, E as CheckoutAddress, F as CheckoutInput, G as CheckoutPaymentMethod, H as CheckoutSettings, I as CookieConsentInput, J as CourseAttachment, K as CourseCertificate, L as CourseComment, M as CourseCommentReply, N as CourseCommentsList, O as CourseDetail, Q as CourseLesson, R as CourseListItem, T as CourseModule, U as CoursePostedComment, V as CourseProgress, W as CourseTutorMessage, X as CourseTutorThread, Y as CrossSellItem, Z as CustomerAddress, _ as CustomerProfile, $ as DigitalDownload, a0 as DownloadUrl, a1 as Facet, a2 as FacetAvailability, a3 as FacetCategory, a4 as FacetLabel, a5 as FacetPriceRange, a6 as FacetRange, a7 as FacetRatingBucket, a8 as FacetValue, a9 as FacetsResponse, aa as FilterField, ab as FulfillmentStatus, ac as FulfillmentStatuses, ad as GiftCardBalance, ae as GiftCardPurchaseInput, af as GiftCardPurchaseResult, ag as GiftCardSummary, ah as LessonNote, ai as LessonQuiz, aj as LoginInput, ak as LoyaltyBalance, al as LoyaltyNextTier, am as LoyaltyProgram, an as LoyaltySummary, ao as LoyaltyTier, ap as LoyaltyTierPerks, aq as LoyaltyTransaction, ar as Menu, as as MenuItem, at as MenuItemRef, au as MenuItemType, av as MessageResponse, aw as NewsletterOptInDefault, ax as NewsletterSubscribeInput, ay as NewsletterSubscribeResult, az as NewsletterUnsubscribeResult, aA as OrderAccessRequestResponse, aB as OrderAccessVerifyResponse, aC as OrderDetail, aD as OrderItem, aE as OrderListItem, aF as OrderStatus, aG as OrderStatusHistory, aH as OrderStatuses, aI as OrderTracking, aJ as Page, aK as PageAttachment, aL as PageDetail, aM as PaginatedResponse, aN as PaymentStatus, aO as PaymentStatuses, aP as PickupPoint, aQ as PickupPointHours, aR as PickupPointsInput, aS as PriceDisplay, aT as ProductAvailability, aU as ProductGroup, aV as ProductLabel, aW as ProductListItem, aX as ProductMedia, aY as ProductMediaVariant, aZ as ProductParameter, a_ as ProductParameterGroup, a$ as ProductParametersResponse, b0 as ProductPrice, b1 as ProductPromotionSummary, b2 as ProductReview, b3 as ProductReviewsResponse, b4 as ProductSibling, b5 as ProductSort, b6 as ProductSortValue, b7 as ProductVolumePrice, b8 as ProductsQuery, b9 as QuizAnswerInput, ba as QuizAnswerResult, bb as QuizQuestion, bc as QuizResult, bd as QuoteItem, be as QuoteRequest, bf as RegisterInput, bg as RegisterResult, bh as RequestInterceptor, bi as RequestInterceptorConfig, bj as ResponseInterceptor, bk as ResponseInterceptorData, bl as ReturnRequest, bm as ReturnRequestItem, bn as ReturnStatus, bo as ReturnStatusItem, bp as ReturnableOrder, bq as ReturnableOrderItem, br as SdkError, bs as ShippingMethodSummary, bt as ShippingQuote, bu as ShippingQuoteInput, bv as ShopInfo, bw as ShopScript, bx as ShopScriptPlacement, by as ShopScriptType, bz as ShopScripts, bA as ShopSeo, bB as ShopSeoIdentity, bC as Sitemap, bD as SitemapEntry, bE as StockBehavior, bF as StockMode, bG as SubmitQuoteInput, bH as SubmitReturnInput, bI as SubmitReviewInput, bJ as Subscription, bK as SubscriptionAction, bL as SubscriptionFrequency, bM as SubscriptionItem, bN as SubscriptionStatus, bO as TaxBreakdownLine, bP as VariantAxis, bQ as VariantAxisValue, bR as WishlistItem, bS as err, bT as ok, bU as toSdkError } from './client-YJtbTLts.js';
1
+ import { P as ProductDetail, a as ProductVariant, B as BehioStorefront, S as SdkResult, C as CookieConsent } from './client-B9jHeWca.js';
2
+ export { A as ActivePromotion, b as AddToCartInput, c as AddressDetail, d as AddressSuggestion, e as AddressType, f as AddressTypes, g as AuthTokens, h as BackInStockSubscription, i as BadgeTone, j as BehioApiError, k as BehioErrorCode, l as BehioEventHandler, m as BehioEventType, n as BehioNetworkError, o as BehioStorefrontConfig, p as Bundle, q as BundleItem, r as Cart, s as CartBundleLine, t as CartBundleLineItem, u as CartDiscount, v as CartItem, w as CartItemProduct, x as CartPromotion, y as Category, z as CategoryDetail, D as CertificateVerification, E as CheckoutAddress, F as CheckoutInput, G as CheckoutPaymentMethod, H as CheckoutSettings, I as CookieConsentInput, J as CourseAttachment, K as CourseCertificate, L as CourseComment, M as CourseCommentReply, N as CourseCommentsList, O as CourseDetail, Q as CourseLesson, R as CourseListItem, T as CourseModule, U as CoursePostedComment, V as CourseProgress, W as CourseTutorMessage, X as CourseTutorThread, Y as CrossSellItem, Z as CustomerAddress, _ as CustomerProfile, $ as DigitalDownload, a0 as DownloadUrl, a1 as Facet, a2 as FacetAvailability, a3 as FacetCategory, a4 as FacetLabel, a5 as FacetPriceRange, a6 as FacetRange, a7 as FacetRatingBucket, a8 as FacetValue, a9 as FacetsResponse, aa as FilterField, ab as FulfillmentStatus, ac as FulfillmentStatuses, ad as GiftCardBalance, ae as GiftCardPurchaseInput, af as GiftCardPurchaseResult, ag as GiftCardSummary, ah as LessonNote, ai as LessonQuiz, aj as LoginInput, ak as LoyaltyBalance, al as LoyaltyNextTier, am as LoyaltyProgram, an as LoyaltySummary, ao as LoyaltyTier, ap as LoyaltyTierPerks, aq as LoyaltyTransaction, ar as Menu, as as MenuItem, at as MenuItemRef, au as MenuItemType, av as MessageResponse, aw as NewsletterOptInDefault, ax as NewsletterSubscribeInput, ay as NewsletterSubscribeResult, az as NewsletterUnsubscribeResult, aA as OrderAccessRequestResponse, aB as OrderAccessVerifyResponse, aC as OrderDetail, aD as OrderItem, aE as OrderListItem, aF as OrderStatus, aG as OrderStatusHistory, aH as OrderStatuses, aI as OrderTracking, aJ as Page, aK as PageAttachment, aL as PageDetail, aM as PaginatedResponse, aN as PaymentStatus, aO as PaymentStatuses, aP as PickupPoint, aQ as PickupPointHours, aR as PickupPointsInput, aS as PriceDisplay, aT as ProductAvailability, aU as ProductGroup, aV as ProductLabel, aW as ProductListItem, aX as ProductMedia, aY as ProductMediaVariant, aZ as ProductParameter, a_ as ProductParameterGroup, a$ as ProductParametersResponse, b0 as ProductPrice, b1 as ProductPromotionSummary, b2 as ProductReview, b3 as ProductReviewsResponse, b4 as ProductSibling, b5 as ProductSort, b6 as ProductSortValue, b7 as ProductVolumePrice, b8 as ProductsQuery, b9 as QuizAnswerInput, ba as QuizAnswerResult, bb as QuizQuestion, bc as QuizResult, bd as QuoteItem, be as QuoteRequest, bf as RegisterInput, bg as RegisterResult, bh as RequestInterceptor, bi as RequestInterceptorConfig, bj as ResponseInterceptor, bk as ResponseInterceptorData, bl as ReturnRequest, bm as ReturnRequestItem, bn as ReturnStatus, bo as ReturnStatusItem, bp as ReturnableOrder, bq as ReturnableOrderItem, br as SdkError, bs as ShippingMethodSummary, bt as ShippingQuote, bu as ShippingQuoteInput, bv as ShopInfo, bw as ShopScript, bx as ShopScriptPlacement, by as ShopScriptType, bz as ShopScripts, bA as ShopSeo, bB as ShopSeoIdentity, bC as Sitemap, bD as SitemapEntry, bE as StockBehavior, bF as StockMode, bG as SubmitQuoteInput, bH as SubmitReturnInput, bI as SubmitReviewInput, bJ as Subscription, bK as SubscriptionAction, bL as SubscriptionFrequency, bM as SubscriptionItem, bN as SubscriptionStatus, bO as TaxBreakdownLine, bP as VariantAxis, bQ as VariantAxisValue, bR as WishlistItem, bS as err, bT as ok, bU as toSdkError } from './client-B9jHeWca.js';
3
3
 
4
4
  /**
5
5
  * Format a price amount with currency using Intl.NumberFormat.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
 
12
12
 
13
- var _chunkVI7OYYBDjs = require('./chunk-VI7OYYBD.js');
13
+ var _chunkTJ4TZ5FIjs = require('./chunk-TJ4TZ5FI.js');
14
14
 
15
15
  // src/react/utils/format-price.ts
16
16
  function formatPrice(amount, currency, locale) {
@@ -243,4 +243,4 @@ async function revokeAnalyticsConsent(client) {
243
243
 
244
244
 
245
245
 
246
- exports.AddressTypes = _chunkVI7OYYBDjs.AddressTypes; exports.BehioApiError = _chunkVI7OYYBDjs.BehioApiError; exports.BehioNetworkError = _chunkVI7OYYBDjs.BehioNetworkError; exports.BehioStorefront = _chunkVI7OYYBDjs.BehioStorefront; exports.FulfillmentStatuses = _chunkVI7OYYBDjs.FulfillmentStatuses; exports.OrderStatuses = _chunkVI7OYYBDjs.OrderStatuses; exports.PaymentStatuses = _chunkVI7OYYBDjs.PaymentStatuses; exports.ProductSort = _chunkVI7OYYBDjs.ProductSort; exports.VARIANT_QUERY_PARAM = VARIANT_QUERY_PARAM; exports.availableAxisValues = availableAxisValues; exports.buildVariantComparison = buildVariantComparison; exports.err = _chunkVI7OYYBDjs.err; exports.findVariantByAttributes = findVariantByAttributes; exports.formatPrice = formatPrice; exports.generateVisitorId = generateVisitorId; exports.getStoredVisitorId = getStoredVisitorId; exports.grantAnalyticsConsent = grantAnalyticsConsent; exports.ok = _chunkVI7OYYBDjs.ok; exports.resolveVariantContent = resolveVariantContent; exports.revokeAnalyticsConsent = revokeAnalyticsConsent; exports.toSdkError = _chunkVI7OYYBDjs.toSdkError; exports.trackEcommerceEvent = trackEcommerceEvent; exports.variantFromQuery = variantFromQuery; exports.variantHref = variantHref;
246
+ exports.AddressTypes = _chunkTJ4TZ5FIjs.AddressTypes; exports.BehioApiError = _chunkTJ4TZ5FIjs.BehioApiError; exports.BehioNetworkError = _chunkTJ4TZ5FIjs.BehioNetworkError; exports.BehioStorefront = _chunkTJ4TZ5FIjs.BehioStorefront; exports.FulfillmentStatuses = _chunkTJ4TZ5FIjs.FulfillmentStatuses; exports.OrderStatuses = _chunkTJ4TZ5FIjs.OrderStatuses; exports.PaymentStatuses = _chunkTJ4TZ5FIjs.PaymentStatuses; exports.ProductSort = _chunkTJ4TZ5FIjs.ProductSort; exports.VARIANT_QUERY_PARAM = VARIANT_QUERY_PARAM; exports.availableAxisValues = availableAxisValues; exports.buildVariantComparison = buildVariantComparison; exports.err = _chunkTJ4TZ5FIjs.err; exports.findVariantByAttributes = findVariantByAttributes; exports.formatPrice = formatPrice; exports.generateVisitorId = generateVisitorId; exports.getStoredVisitorId = getStoredVisitorId; exports.grantAnalyticsConsent = grantAnalyticsConsent; exports.ok = _chunkTJ4TZ5FIjs.ok; exports.resolveVariantContent = resolveVariantContent; exports.revokeAnalyticsConsent = revokeAnalyticsConsent; exports.toSdkError = _chunkTJ4TZ5FIjs.toSdkError; exports.trackEcommerceEvent = trackEcommerceEvent; exports.variantFromQuery = variantFromQuery; exports.variantHref = variantHref;
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  err,
11
11
  ok,
12
12
  toSdkError
13
- } from "./chunk-SULHQV5R.mjs";
13
+ } from "./chunk-TOSF3K7J.mjs";
14
14
 
15
15
  // src/react/utils/format-price.ts
16
16
  function formatPrice(amount, currency, locale) {
package/dist/next.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as BehioStorefrontConfig, B as BehioStorefront } from './client-YJtbTLts.mjs';
1
+ import { o as BehioStorefrontConfig, B as BehioStorefront } from './client-B9jHeWca.mjs';
2
2
 
3
3
  interface GetBehioOptions extends Partial<BehioStorefrontConfig> {
4
4
  /** Override the cart session cookie name (default: "behio_cart_session"). */
package/dist/next.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { o as BehioStorefrontConfig, B as BehioStorefront } from './client-YJtbTLts.js';
1
+ import { o as BehioStorefrontConfig, B as BehioStorefront } from './client-B9jHeWca.js';
2
2
 
3
3
  interface GetBehioOptions extends Partial<BehioStorefrontConfig> {
4
4
  /** Override the cart session cookie name (default: "behio_cart_session"). */
package/dist/next.js CHANGED
@@ -1,6 +1,6 @@
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 _chunkVI7OYYBDjs = require('./chunk-VI7OYYBD.js');
3
+ var _chunkTJ4TZ5FIjs = require('./chunk-TJ4TZ5FI.js');
4
4
 
5
5
  // src/next.ts
6
6
  var _headers = require('next/headers');
@@ -18,7 +18,7 @@ async function getBehio(options = {}) {
18
18
  const locale = _nullishCoalesce(options.locale, () => ( process.env.BEHIO_LOCALE));
19
19
  const currency = _nullishCoalesce(options.currency, () => ( process.env.BEHIO_CURRENCY));
20
20
  const cookieName = _nullishCoalesce(options.cartCookieName, () => ( CART_COOKIE_NAME));
21
- const client = new (0, _chunkVI7OYYBDjs.BehioStorefront)({
21
+ const client = new (0, _chunkTJ4TZ5FIjs.BehioStorefront)({
22
22
  apiKey,
23
23
  ...baseUrl ? { baseUrl } : {},
24
24
  ...locale ? { locale } : {},
@@ -30,9 +30,18 @@ async function getBehio(options = {}) {
30
30
  if (existing) {
31
31
  client.setCartSession(existing);
32
32
  }
33
+ if (!options.visitorIp) {
34
+ try {
35
+ const h = await _headers.headers.call(void 0, );
36
+ const xff = h.get("x-forwarded-for");
37
+ const ip = (_nullishCoalesce(_nullishCoalesce(_optionalChain([xff, 'optionalAccess', _4 => _4.split, 'call', _5 => _5(","), 'access', _6 => _6[0]]), () => ( h.get("x-real-ip"))), () => ( ""))).trim();
38
+ if (ip) client.setVisitorIp(ip);
39
+ } catch (e) {
40
+ }
41
+ }
33
42
  if (!currency) {
34
43
  const currencyCookieName = _nullishCoalesce(options.currencyCookieName, () => ( CURRENCY_COOKIE_NAME));
35
- const chosenCurrency = _optionalChain([cookieStore, 'access', _4 => _4.get, 'call', _5 => _5(currencyCookieName), 'optionalAccess', _6 => _6.value]);
44
+ const chosenCurrency = _optionalChain([cookieStore, 'access', _7 => _7.get, 'call', _8 => _8(currencyCookieName), 'optionalAccess', _9 => _9.value]);
36
45
  if (chosenCurrency) {
37
46
  client.setCurrency(chosenCurrency);
38
47
  }
@@ -48,14 +57,14 @@ async function getBehio(options = {}) {
48
57
  path: "/",
49
58
  maxAge: CART_COOKIE_MAX_AGE
50
59
  });
51
- } catch (e) {
60
+ } catch (e2) {
52
61
  }
53
62
  };
54
63
  client.clearCartSession = () => {
55
64
  originalClear();
56
65
  try {
57
66
  cookieStore.delete(cookieName);
58
- } catch (e2) {
67
+ } catch (e3) {
59
68
  }
60
69
  };
61
70
  return client;
package/dist/next.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  BehioStorefront
3
- } from "./chunk-SULHQV5R.mjs";
3
+ } from "./chunk-TOSF3K7J.mjs";
4
4
 
5
5
  // src/next.ts
6
- import { cookies } from "next/headers";
6
+ import { cookies, headers } from "next/headers";
7
7
  var CART_COOKIE_NAME = "behio_cart_session";
8
8
  var CURRENCY_COOKIE_NAME = "behio_currency";
9
9
  var CART_COOKIE_MAX_AGE = 60 * 60 * 24 * 30;
@@ -30,6 +30,15 @@ async function getBehio(options = {}) {
30
30
  if (existing) {
31
31
  client.setCartSession(existing);
32
32
  }
33
+ if (!options.visitorIp) {
34
+ try {
35
+ const h = await headers();
36
+ const xff = h.get("x-forwarded-for");
37
+ const ip = (xff?.split(",")[0] ?? h.get("x-real-ip") ?? "").trim();
38
+ if (ip) client.setVisitorIp(ip);
39
+ } catch {
40
+ }
41
+ }
33
42
  if (!currency) {
34
43
  const currencyCookieName = options.currencyCookieName ?? CURRENCY_COOKIE_NAME;
35
44
  const chosenCurrency = cookieStore.get(currencyCookieName)?.value;
package/dist/react.d.mts CHANGED
@@ -73,6 +73,18 @@ interface BehioStorefrontConfig {
73
73
  currency?: string;
74
74
  /** Custom fetch implementation (for Node.js < 18 or testing) */
75
75
  fetch?: typeof fetch;
76
+ /**
77
+ * Real visitor IP for server-side rendering. Sent as X-Behio-Visitor-Ip so
78
+ * the backend rate-limits per visitor instead of per server: all SSR
79
+ * requests of one deployment share the server's IP, so without this header
80
+ * the whole shop shares one rate-limit bucket. The backend only trusts the
81
+ * header from allow-listed hosting IPs, so a browser sending it gains
82
+ * nothing. Accepts a fixed string or a resolver called per request (use a
83
+ * resolver for a shared catalog client that serves many concurrent
84
+ * requests). The Next.js adapter (`@behio/storefront-sdk/next`) fills this
85
+ * automatically from the incoming request headers.
86
+ */
87
+ visitorIp?: string | (() => string | null | undefined | Promise<string | null | undefined>);
76
88
  /** Request timeout in milliseconds. Default: 30000 (30s) */
77
89
  timeout?: number;
78
90
  /** Number of retries on network/5xx errors. Default: 1 */
@@ -2311,6 +2323,7 @@ declare class BehioStorefront {
2311
2323
  private responseInterceptors;
2312
2324
  private rateLimitRemaining;
2313
2325
  private rateLimitReset;
2326
+ private visitorIp?;
2314
2327
  constructor(config: BehioStorefrontConfig);
2315
2328
  readonly catalog: CatalogModule;
2316
2329
  readonly auth: AuthModule;
@@ -2337,6 +2350,13 @@ declare class BehioStorefront {
2337
2350
  setAnalyticsVisitorId(id: string | null): void;
2338
2351
  /** The consent-gated visitor id, if analytics consent was granted. */
2339
2352
  getAnalyticsVisitorId(): string | null;
2353
+ /**
2354
+ * Set (or clear with null) the real visitor IP forwarded as
2355
+ * X-Behio-Visitor-Ip on server-side requests. See
2356
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
2357
+ * automatically for per-request clients.
2358
+ */
2359
+ setVisitorIp(ip: string | null): void;
2340
2360
  /**
2341
2361
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
2342
2362
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -2640,6 +2660,22 @@ declare class CartModule {
2640
2660
  addItem(input: AddToCartInput): Promise<SdkResult<Cart & {
2641
2661
  newSessionToken?: string;
2642
2662
  }>>;
2663
+ /**
2664
+ * Switch the CART currency server-side. The backend re-prices every line in
2665
+ * the new currency (no FX conversion: a product must have a price configured
2666
+ * in that currency, otherwise the call fails with 400 listing the items and
2667
+ * the cart stays unchanged). Creates an empty cart in that currency when
2668
+ * none exists yet.
2669
+ *
2670
+ * This is the money-path counterpart of `client.setCurrency()` (which only
2671
+ * affects catalog display). Call BOTH from a currency switcher:
2672
+ *
2673
+ * ```ts
2674
+ * client.setCurrency("EUR"); // catalog prices
2675
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
2676
+ * ```
2677
+ */
2678
+ setCurrency(currency: string): Promise<SdkResult<Cart>>;
2643
2679
  /** Update item quantity */
2644
2680
  updateQuantity(itemId: string, quantity: number): Promise<SdkResult<Cart>>;
2645
2681
  /** Remove item from cart */
package/dist/react.d.ts CHANGED
@@ -73,6 +73,18 @@ interface BehioStorefrontConfig {
73
73
  currency?: string;
74
74
  /** Custom fetch implementation (for Node.js < 18 or testing) */
75
75
  fetch?: typeof fetch;
76
+ /**
77
+ * Real visitor IP for server-side rendering. Sent as X-Behio-Visitor-Ip so
78
+ * the backend rate-limits per visitor instead of per server: all SSR
79
+ * requests of one deployment share the server's IP, so without this header
80
+ * the whole shop shares one rate-limit bucket. The backend only trusts the
81
+ * header from allow-listed hosting IPs, so a browser sending it gains
82
+ * nothing. Accepts a fixed string or a resolver called per request (use a
83
+ * resolver for a shared catalog client that serves many concurrent
84
+ * requests). The Next.js adapter (`@behio/storefront-sdk/next`) fills this
85
+ * automatically from the incoming request headers.
86
+ */
87
+ visitorIp?: string | (() => string | null | undefined | Promise<string | null | undefined>);
76
88
  /** Request timeout in milliseconds. Default: 30000 (30s) */
77
89
  timeout?: number;
78
90
  /** Number of retries on network/5xx errors. Default: 1 */
@@ -2311,6 +2323,7 @@ declare class BehioStorefront {
2311
2323
  private responseInterceptors;
2312
2324
  private rateLimitRemaining;
2313
2325
  private rateLimitReset;
2326
+ private visitorIp?;
2314
2327
  constructor(config: BehioStorefrontConfig);
2315
2328
  readonly catalog: CatalogModule;
2316
2329
  readonly auth: AuthModule;
@@ -2337,6 +2350,13 @@ declare class BehioStorefront {
2337
2350
  setAnalyticsVisitorId(id: string | null): void;
2338
2351
  /** The consent-gated visitor id, if analytics consent was granted. */
2339
2352
  getAnalyticsVisitorId(): string | null;
2353
+ /**
2354
+ * Set (or clear with null) the real visitor IP forwarded as
2355
+ * X-Behio-Visitor-Ip on server-side requests. See
2356
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
2357
+ * automatically for per-request clients.
2358
+ */
2359
+ setVisitorIp(ip: string | null): void;
2340
2360
  /**
2341
2361
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
2342
2362
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -2640,6 +2660,22 @@ declare class CartModule {
2640
2660
  addItem(input: AddToCartInput): Promise<SdkResult<Cart & {
2641
2661
  newSessionToken?: string;
2642
2662
  }>>;
2663
+ /**
2664
+ * Switch the CART currency server-side. The backend re-prices every line in
2665
+ * the new currency (no FX conversion: a product must have a price configured
2666
+ * in that currency, otherwise the call fails with 400 listing the items and
2667
+ * the cart stays unchanged). Creates an empty cart in that currency when
2668
+ * none exists yet.
2669
+ *
2670
+ * This is the money-path counterpart of `client.setCurrency()` (which only
2671
+ * affects catalog display). Call BOTH from a currency switcher:
2672
+ *
2673
+ * ```ts
2674
+ * client.setCurrency("EUR"); // catalog prices
2675
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
2676
+ * ```
2677
+ */
2678
+ setCurrency(currency: string): Promise<SdkResult<Cart>>;
2643
2679
  /** Update item quantity */
2644
2680
  updateQuantity(itemId: string, quantity: number): Promise<SdkResult<Cart>>;
2645
2681
  /** Remove item from cart */
package/dist/react.js CHANGED
@@ -215,6 +215,7 @@ var BehioStorefront = class {
215
215
  this.timeout = config.timeout ?? 3e4;
216
216
  this.retries = config.retries ?? 1;
217
217
  this.retryDelay = config.retryDelay ?? 1e3;
218
+ this.visitorIp = config.visitorIp;
218
219
  this.catalog = new CatalogModule(this);
219
220
  this.auth = new AuthModule(this);
220
221
  this.cart = new CartModule(this);
@@ -246,6 +247,15 @@ var BehioStorefront = class {
246
247
  getAnalyticsVisitorId() {
247
248
  return this.analyticsVisitorId;
248
249
  }
250
+ /**
251
+ * Set (or clear with null) the real visitor IP forwarded as
252
+ * X-Behio-Visitor-Ip on server-side requests. See
253
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
254
+ * automatically for per-request clients.
255
+ */
256
+ setVisitorIp(ip) {
257
+ this.visitorIp = ip ?? void 0;
258
+ }
249
259
  /**
250
260
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
251
261
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -511,6 +521,13 @@ var BehioStorefront = class {
511
521
  if (this.analyticsVisitorId) {
512
522
  headers["X-Behio-Vid"] = this.analyticsVisitorId;
513
523
  }
524
+ if (this.visitorIp) {
525
+ try {
526
+ const ip = typeof this.visitorIp === "function" ? await this.visitorIp() : this.visitorIp;
527
+ if (ip) headers["X-Behio-Visitor-Ip"] = ip;
528
+ } catch {
529
+ }
530
+ }
514
531
  if (options?.headers) {
515
532
  Object.assign(headers, options.headers);
516
533
  }
@@ -1027,6 +1044,29 @@ var CartModule = class {
1027
1044
  this.client.emit("cart:updated", res.data);
1028
1045
  return res;
1029
1046
  }
1047
+ /**
1048
+ * Switch the CART currency server-side. The backend re-prices every line in
1049
+ * the new currency (no FX conversion: a product must have a price configured
1050
+ * in that currency, otherwise the call fails with 400 listing the items and
1051
+ * the cart stays unchanged). Creates an empty cart in that currency when
1052
+ * none exists yet.
1053
+ *
1054
+ * This is the money-path counterpart of `client.setCurrency()` (which only
1055
+ * affects catalog display). Call BOTH from a currency switcher:
1056
+ *
1057
+ * ```ts
1058
+ * client.setCurrency("EUR"); // catalog prices
1059
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
1060
+ * ```
1061
+ */
1062
+ async setCurrency(currency) {
1063
+ const res = await this.client.request("PUT", "/cart/currency", {
1064
+ body: { currency }
1065
+ });
1066
+ if (res.error) return res;
1067
+ this.client.emit("cart:updated", res.data);
1068
+ return res;
1069
+ }
1030
1070
  /** Update item quantity */
1031
1071
  async updateQuantity(itemId, quantity) {
1032
1072
  const res = await this.client.request(
@@ -2025,9 +2065,16 @@ function BehioProvider({
2025
2065
  else cookieStorage.remove(currencyCookieName);
2026
2066
  }
2027
2067
  onCurrencyChange?.(next);
2028
- void qc.invalidateQueries({ queryKey: ["behio"] });
2068
+ const cartTarget = next ?? defaultCurrency;
2069
+ if (cartTarget && (client.getCartSession() || client.auth.isLoggedIn())) {
2070
+ void client.cart.setCurrency(cartTarget).finally(() => {
2071
+ void qc.invalidateQueries({ queryKey: ["behio"] });
2072
+ });
2073
+ } else {
2074
+ void qc.invalidateQueries({ queryKey: ["behio"] });
2075
+ }
2029
2076
  },
2030
- [client, qc, persistCurrency, currencyCookieName, onCurrencyChange]
2077
+ [client, qc, persistCurrency, currencyCookieName, onCurrencyChange, defaultCurrency]
2031
2078
  );
2032
2079
  const ctxValue = (0, import_react2.useMemo)(
2033
2080
  () => ({
package/dist/react.mjs CHANGED
@@ -112,6 +112,7 @@ var BehioStorefront = class {
112
112
  this.timeout = config.timeout ?? 3e4;
113
113
  this.retries = config.retries ?? 1;
114
114
  this.retryDelay = config.retryDelay ?? 1e3;
115
+ this.visitorIp = config.visitorIp;
115
116
  this.catalog = new CatalogModule(this);
116
117
  this.auth = new AuthModule(this);
117
118
  this.cart = new CartModule(this);
@@ -143,6 +144,15 @@ var BehioStorefront = class {
143
144
  getAnalyticsVisitorId() {
144
145
  return this.analyticsVisitorId;
145
146
  }
147
+ /**
148
+ * Set (or clear with null) the real visitor IP forwarded as
149
+ * X-Behio-Visitor-Ip on server-side requests. See
150
+ * `BehioStorefrontConfig.visitorIp` for why. The Next.js adapter calls this
151
+ * automatically for per-request clients.
152
+ */
153
+ setVisitorIp(ip) {
154
+ this.visitorIp = ip ?? void 0;
155
+ }
146
156
  /**
147
157
  * Fire-and-forget Behio Analytics ingest. Uses a bare keepalive fetch (not
148
158
  * the interceptor pipeline) so flushes on pagehide still land, and swallows
@@ -408,6 +418,13 @@ var BehioStorefront = class {
408
418
  if (this.analyticsVisitorId) {
409
419
  headers["X-Behio-Vid"] = this.analyticsVisitorId;
410
420
  }
421
+ if (this.visitorIp) {
422
+ try {
423
+ const ip = typeof this.visitorIp === "function" ? await this.visitorIp() : this.visitorIp;
424
+ if (ip) headers["X-Behio-Visitor-Ip"] = ip;
425
+ } catch {
426
+ }
427
+ }
411
428
  if (options?.headers) {
412
429
  Object.assign(headers, options.headers);
413
430
  }
@@ -924,6 +941,29 @@ var CartModule = class {
924
941
  this.client.emit("cart:updated", res.data);
925
942
  return res;
926
943
  }
944
+ /**
945
+ * Switch the CART currency server-side. The backend re-prices every line in
946
+ * the new currency (no FX conversion: a product must have a price configured
947
+ * in that currency, otherwise the call fails with 400 listing the items and
948
+ * the cart stays unchanged). Creates an empty cart in that currency when
949
+ * none exists yet.
950
+ *
951
+ * This is the money-path counterpart of `client.setCurrency()` (which only
952
+ * affects catalog display). Call BOTH from a currency switcher:
953
+ *
954
+ * ```ts
955
+ * client.setCurrency("EUR"); // catalog prices
956
+ * await client.cart.setCurrency("EUR"); // cart + checkout prices
957
+ * ```
958
+ */
959
+ async setCurrency(currency) {
960
+ const res = await this.client.request("PUT", "/cart/currency", {
961
+ body: { currency }
962
+ });
963
+ if (res.error) return res;
964
+ this.client.emit("cart:updated", res.data);
965
+ return res;
966
+ }
927
967
  /** Update item quantity */
928
968
  async updateQuantity(itemId, quantity) {
929
969
  const res = await this.client.request(
@@ -1922,9 +1962,16 @@ function BehioProvider({
1922
1962
  else cookieStorage.remove(currencyCookieName);
1923
1963
  }
1924
1964
  onCurrencyChange?.(next);
1925
- void qc.invalidateQueries({ queryKey: ["behio"] });
1965
+ const cartTarget = next ?? defaultCurrency;
1966
+ if (cartTarget && (client.getCartSession() || client.auth.isLoggedIn())) {
1967
+ void client.cart.setCurrency(cartTarget).finally(() => {
1968
+ void qc.invalidateQueries({ queryKey: ["behio"] });
1969
+ });
1970
+ } else {
1971
+ void qc.invalidateQueries({ queryKey: ["behio"] });
1972
+ }
1926
1973
  },
1927
- [client, qc, persistCurrency, currencyCookieName, onCurrencyChange]
1974
+ [client, qc, persistCurrency, currencyCookieName, onCurrencyChange, defaultCurrency]
1928
1975
  );
1929
1976
  const ctxValue = useMemo(
1930
1977
  () => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",