@behio/storefront-sdk 1.4.0 → 1.5.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 = 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
  }
@@ -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;
@@ -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
@@ -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
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-LetSVGM1.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-LetSVGM1.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-LetSVGM1.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-LetSVGM1.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 _chunkOQK7PQYJjs = require('./chunk-OQK7PQYJ.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 = _chunkOQK7PQYJjs.AddressTypes; exports.BehioApiError = _chunkOQK7PQYJjs.BehioApiError; exports.BehioNetworkError = _chunkOQK7PQYJjs.BehioNetworkError; exports.BehioStorefront = _chunkOQK7PQYJjs.BehioStorefront; exports.FulfillmentStatuses = _chunkOQK7PQYJjs.FulfillmentStatuses; exports.OrderStatuses = _chunkOQK7PQYJjs.OrderStatuses; exports.PaymentStatuses = _chunkOQK7PQYJjs.PaymentStatuses; exports.ProductSort = _chunkOQK7PQYJjs.ProductSort; exports.VARIANT_QUERY_PARAM = VARIANT_QUERY_PARAM; exports.availableAxisValues = availableAxisValues; exports.buildVariantComparison = buildVariantComparison; exports.err = _chunkOQK7PQYJjs.err; exports.findVariantByAttributes = findVariantByAttributes; exports.formatPrice = formatPrice; exports.generateVisitorId = generateVisitorId; exports.getStoredVisitorId = getStoredVisitorId; exports.grantAnalyticsConsent = grantAnalyticsConsent; exports.ok = _chunkOQK7PQYJjs.ok; exports.resolveVariantContent = resolveVariantContent; exports.revokeAnalyticsConsent = revokeAnalyticsConsent; exports.toSdkError = _chunkOQK7PQYJjs.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-7CFBSRUZ.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-LetSVGM1.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-LetSVGM1.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 _chunkOQK7PQYJjs = require('./chunk-OQK7PQYJ.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, _chunkOQK7PQYJjs.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-7CFBSRUZ.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
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
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
  }
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@behio/storefront-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "TypeScript SDK for Behio Headless E-Shop — core client + React hooks",
5
5
  "author": "Behio",
6
6
  "license": "MIT",