@lime-bundles/react 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,6 +113,49 @@ useEffect(() => {
113
113
 
114
114
  Full CSS variable reference: [css-variables.md](./docs/css-variables.md).
115
115
 
116
+ ## Markets & B2B
117
+
118
+ `<FixedBundle>`, `<VolumeBundle>`, `<MixMatchBundle>`, and `useBundleData` accept four optional props for Shopify Markets and B2B catalogs:
119
+
120
+ - **`country`** — ISO-3166 alpha-2 (e.g. `"US"`). Passed as Storefront `@inContext(country:)`. Drives Markets pricing and currency.
121
+ - **`language`** — ISO-639-1 (e.g. `"EN"`). Passed as `@inContext(language:)`.
122
+ - **`buyer`** — B2B buyer identity. `BuyerInput` object or `() => Promise<BuyerInput>` callback.
123
+ - **`marketId`** — Current visitor's Market GID. When the bundle is `marketVisibility: "specific"`, it's hidden unless this market is in its allow-list.
124
+
125
+ ```tsx
126
+ <FixedBundle
127
+ shopDomain="acme.myshopify.com"
128
+ storefrontAccessToken={token}
129
+ bundleGid={gid}
130
+ onAddToCart={addToCart}
131
+ country="US"
132
+ language="EN"
133
+ buyer={async () => fetchBuyerToken()} // resolved per request
134
+ marketId="gid://shopify/Market/1"
135
+ />
136
+ ```
137
+
138
+ **Security — B2B `customerAccessToken`:**
139
+
140
+ - Prefer the callback form (`() => Promise<BuyerInput>`) so the token doesn't sit in static prop trees, React DevTools snapshots, or Sentry/LogRocket traces.
141
+ - Never pass via URL query parameters (leaks to Referer headers and server logs).
142
+ - Never store in localStorage shared with third-party scripts.
143
+ - The Customer Account API PKCE flow is the canonical source. See https://shopify.dev/docs/api/customer/latest.
144
+
145
+ **Caching:**
146
+
147
+ Buyer-contextual responses MUST be `Cache-Control: private` — never share across users. `@lime-bundles/core` exports `getCacheKey()` for SWR / TanStack Query consumers; it hashes the buyer token (never includes the raw value) so per-buyer caches don't leak tokens.
148
+
149
+ ```tsx
150
+ import { getCacheKey } from "@lime-bundles/core";
151
+ useQuery({
152
+ queryKey: getCacheKey("Bundle", { id }, { shopDomain, country, buyer }),
153
+ queryFn: () => fetchBundleData({ ... }),
154
+ });
155
+ ```
156
+
157
+ **Filtering vs throwing:** `fetchBundleData` throws `BundleParseError` when the bundle is hidden by the market gate. Use `fetchBundleDataWithWarnings` instead to receive `{ bundle, warnings }` and decide whether to render alternative content.
158
+
116
159
  ## Version policy
117
160
 
118
161
  `BundleComponentProps`, `UseBundleDataResult`, and `UseBundlesForProductResult` are locked at v2.0.0. All three packages (`core`, `react`, `widget`) bump majors together.
package/dist/index.cjs CHANGED
@@ -30,7 +30,6 @@ __export(index_exports, {
30
30
  VariantDropdown: () => VariantDropdown,
31
31
  VolumeBundle: () => VolumeBundle,
32
32
  WIDGET_CONFIG_DEFAULTS: () => import_core13.WIDGET_CONFIG_DEFAULTS,
33
- applyABVariantB: () => import_core13.applyABVariantB,
34
33
  applyWidgetConfigVars: () => import_core13.applyWidgetConfigVars,
35
34
  calculateDiscount: () => import_core13.calculateDiscount,
36
35
  calculateTierSavings: () => import_core13.calculateTierSavings,
@@ -40,12 +39,13 @@ __export(index_exports, {
40
39
  fetchBundleData: () => fetchBundleData,
41
40
  fetchBundlesForProduct: () => import_core13.fetchBundlesForProduct,
42
41
  fetchShopCustomCss: () => fetchShopCustomCss,
42
+ fnv1a: () => import_core13.fnv1a,
43
43
  formatCents: () => import_core13.formatCents,
44
44
  formatCountdown: () => import_core13.formatCountdown,
45
45
  formatMoney: () => import_core13.formatMoney,
46
46
  formatUnitPrice: () => import_core13.formatUnitPrice,
47
- getABTestAssignment: () => import_core13.getABTestAssignment,
48
47
  getActiveTier: () => import_core13.getActiveTier,
48
+ getLinkGroupAssignment: () => import_core13.getLinkGroupAssignment,
49
49
  hasConsent: () => import_core13.hasConsent,
50
50
  injectCustomCss: () => import_core13.injectCustomCss,
51
51
  mergeWidgetConfig: () => import_core13.mergeWidgetConfig,
@@ -54,6 +54,7 @@ __export(index_exports, {
54
54
  parseMetaobjectBundle: () => import_core13.parseMetaobjectBundle,
55
55
  parseMetaobjectBundleStrict: () => import_core13.parseMetaobjectBundleStrict,
56
56
  percentageDiscountUnit: () => import_core13.percentageDiscountUnit,
57
+ pickVariantFromWeights: () => import_core13.pickVariantFromWeights,
57
58
  reportAddToCart: () => import_core13.reportAddToCart,
58
59
  reportImpression: () => import_core13.reportImpression,
59
60
  sanitizeCustomCss: () => import_core13.sanitizeCustomCss,
@@ -80,14 +81,29 @@ var import_core3 = require("@lime-bundles/core");
80
81
  // src/fetchBundleData.ts
81
82
  var import_core = require("@lime-bundles/core");
82
83
  async function fetchBundleData(options) {
83
- const client = (0, import_core.createStorefrontClient)({
84
+ const result = await fetchBundleDataWithWarnings(options);
85
+ if (!result.bundle) {
86
+ throw new import_core.BundleParseError(
87
+ `Bundle hidden by market gate: ${options.bundleGid}`,
88
+ "not_found"
89
+ );
90
+ }
91
+ return result.bundle;
92
+ }
93
+ async function fetchBundleDataWithWarnings(options) {
94
+ const config = {
84
95
  shopDomain: options.shopDomain,
85
96
  accessToken: options.storefrontAccessToken,
86
97
  buyerIp: options.buyerIp,
87
- apiVersion: options.apiVersion
88
- });
98
+ apiVersion: options.apiVersion,
99
+ country: options.country,
100
+ language: options.language,
101
+ buyer: options.buyer
102
+ };
103
+ const client = (0, import_core.createStorefrontClient)(config);
104
+ const query = (0, import_core.hasInContext)(config) ? (0, import_core.withInContext)(import_core.BUNDLE_METAOBJECT_QUERY) : import_core.BUNDLE_METAOBJECT_QUERY;
89
105
  const data = await client.query(
90
- import_core.BUNDLE_METAOBJECT_QUERY,
106
+ query,
91
107
  { id: options.bundleGid },
92
108
  { signal: options.signal }
93
109
  );
@@ -97,10 +113,24 @@ async function fetchBundleData(options) {
97
113
  "not_found"
98
114
  );
99
115
  }
100
- return (0, import_core.parseMetaobjectBundleStrict)(
116
+ const bundle = (0, import_core.parseMetaobjectBundleStrict)(
101
117
  data.metaobject.id,
102
118
  data.metaobject.fields
103
119
  );
120
+ if (bundle.marketVisibility === "specific" && (!options.marketId || !bundle.marketIds.includes(options.marketId))) {
121
+ return {
122
+ bundle: null,
123
+ warnings: [
124
+ {
125
+ bundleGid: options.bundleGid,
126
+ reason: "market_mismatch",
127
+ currentMarketId: options.marketId ?? null,
128
+ allowedMarketIds: bundle.marketIds
129
+ }
130
+ ]
131
+ };
132
+ }
133
+ return { bundle, warnings: [] };
104
134
  }
105
135
 
106
136
  // src/fetchShopCustomCss.ts
@@ -146,7 +176,11 @@ function useBundleData(options) {
146
176
  shopDomain: options.shopDomain,
147
177
  storefrontAccessToken: options.storefrontAccessToken,
148
178
  bundleGid: options.bundleGid,
149
- signal: controller.signal
179
+ signal: controller.signal,
180
+ country: options.country,
181
+ language: options.language,
182
+ buyer: options.buyer,
183
+ marketId: options.marketId
150
184
  }).then((bundle) => {
151
185
  if (controller.signal.aborted) return;
152
186
  setState({ status: "success", bundle, error: null });
@@ -173,7 +207,11 @@ function useBundleData(options) {
173
207
  }, [
174
208
  options.shopDomain,
175
209
  options.storefrontAccessToken,
176
- options.bundleGid
210
+ options.bundleGid,
211
+ options.country,
212
+ options.language,
213
+ options.buyer,
214
+ options.marketId
177
215
  ]);
178
216
  return state;
179
217
  }
@@ -193,7 +231,7 @@ function useAnalytics(options) {
193
231
  );
194
232
  (0, import_react2.useEffect)(() => {
195
233
  impressionFiredRef.current = false;
196
- }, [options.bundleGid, options.abVariant]);
234
+ }, [options.bundleGid]);
197
235
  (0, import_react2.useEffect)(() => {
198
236
  if (options.enabled === false) return;
199
237
  if (impressionFiredRef.current) return;
@@ -204,8 +242,7 @@ function useAnalytics(options) {
204
242
  (0, import_core4.reportImpression)(config, {
205
243
  bundleGid: options.bundleGid,
206
244
  bundleType: options.bundleType,
207
- abTestId: options.abTestId,
208
- abVariant: options.abVariant
245
+ linkGroupId: options.linkGroupId
209
246
  });
210
247
  });
211
248
  return cleanup;
@@ -215,8 +252,7 @@ function useAnalytics(options) {
215
252
  options.enabled,
216
253
  options.bundleGid,
217
254
  options.bundleType,
218
- options.abTestId,
219
- options.abVariant
255
+ options.linkGroupId
220
256
  ]);
221
257
  const trackAddToCart = (0, import_react2.useCallback)(
222
258
  (event) => {
@@ -227,8 +263,7 @@ function useAnalytics(options) {
227
263
  productId: event.productId,
228
264
  quantity: event.quantity,
229
265
  totalPrice: event.totalPrice,
230
- abTestId: options.abTestId,
231
- abVariant: options.abVariant
266
+ linkGroupId: options.linkGroupId
232
267
  });
233
268
  },
234
269
  [
@@ -236,8 +271,7 @@ function useAnalytics(options) {
236
271
  options.enabled,
237
272
  options.bundleGid,
238
273
  options.bundleType,
239
- options.abTestId,
240
- options.abVariant
274
+ options.linkGroupId
241
275
  ]
242
276
  );
243
277
  return { elementRef, trackAddToCart };
@@ -1515,7 +1549,6 @@ var import_core13 = require("@lime-bundles/core");
1515
1549
  VariantDropdown,
1516
1550
  VolumeBundle,
1517
1551
  WIDGET_CONFIG_DEFAULTS,
1518
- applyABVariantB,
1519
1552
  applyWidgetConfigVars,
1520
1553
  calculateDiscount,
1521
1554
  calculateTierSavings,
@@ -1525,12 +1558,13 @@ var import_core13 = require("@lime-bundles/core");
1525
1558
  fetchBundleData,
1526
1559
  fetchBundlesForProduct,
1527
1560
  fetchShopCustomCss,
1561
+ fnv1a,
1528
1562
  formatCents,
1529
1563
  formatCountdown,
1530
1564
  formatMoney,
1531
1565
  formatUnitPrice,
1532
- getABTestAssignment,
1533
1566
  getActiveTier,
1567
+ getLinkGroupAssignment,
1534
1568
  hasConsent,
1535
1569
  injectCustomCss,
1536
1570
  mergeWidgetConfig,
@@ -1539,6 +1573,7 @@ var import_core13 = require("@lime-bundles/core");
1539
1573
  parseMetaobjectBundle,
1540
1574
  parseMetaobjectBundleStrict,
1541
1575
  percentageDiscountUnit,
1576
+ pickVariantFromWeights,
1542
1577
  reportAddToCart,
1543
1578
  reportImpression,
1544
1579
  sanitizeCustomCss,