@lime-bundles/react 4.0.0 → 4.1.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 +43 -0
- package/dist/index.cjs +44 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +49 -8
- package/dist/index.js.map +1 -1
- package/docs/css-variables.md +3 -0
- package/package.json +2 -2
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
|
@@ -80,14 +80,29 @@ var import_core3 = require("@lime-bundles/core");
|
|
|
80
80
|
// src/fetchBundleData.ts
|
|
81
81
|
var import_core = require("@lime-bundles/core");
|
|
82
82
|
async function fetchBundleData(options) {
|
|
83
|
-
const
|
|
83
|
+
const result = await fetchBundleDataWithWarnings(options);
|
|
84
|
+
if (!result.bundle) {
|
|
85
|
+
throw new import_core.BundleParseError(
|
|
86
|
+
`Bundle hidden by market gate: ${options.bundleGid}`,
|
|
87
|
+
"not_found"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return result.bundle;
|
|
91
|
+
}
|
|
92
|
+
async function fetchBundleDataWithWarnings(options) {
|
|
93
|
+
const config = {
|
|
84
94
|
shopDomain: options.shopDomain,
|
|
85
95
|
accessToken: options.storefrontAccessToken,
|
|
86
96
|
buyerIp: options.buyerIp,
|
|
87
|
-
apiVersion: options.apiVersion
|
|
88
|
-
|
|
97
|
+
apiVersion: options.apiVersion,
|
|
98
|
+
country: options.country,
|
|
99
|
+
language: options.language,
|
|
100
|
+
buyer: options.buyer
|
|
101
|
+
};
|
|
102
|
+
const client = (0, import_core.createStorefrontClient)(config);
|
|
103
|
+
const query = (0, import_core.hasInContext)(config) ? (0, import_core.withInContext)(import_core.BUNDLE_METAOBJECT_QUERY) : import_core.BUNDLE_METAOBJECT_QUERY;
|
|
89
104
|
const data = await client.query(
|
|
90
|
-
|
|
105
|
+
query,
|
|
91
106
|
{ id: options.bundleGid },
|
|
92
107
|
{ signal: options.signal }
|
|
93
108
|
);
|
|
@@ -97,10 +112,24 @@ async function fetchBundleData(options) {
|
|
|
97
112
|
"not_found"
|
|
98
113
|
);
|
|
99
114
|
}
|
|
100
|
-
|
|
115
|
+
const bundle = (0, import_core.parseMetaobjectBundleStrict)(
|
|
101
116
|
data.metaobject.id,
|
|
102
117
|
data.metaobject.fields
|
|
103
118
|
);
|
|
119
|
+
if (bundle.marketVisibility === "specific" && (!options.marketId || !bundle.marketIds.includes(options.marketId))) {
|
|
120
|
+
return {
|
|
121
|
+
bundle: null,
|
|
122
|
+
warnings: [
|
|
123
|
+
{
|
|
124
|
+
bundleGid: options.bundleGid,
|
|
125
|
+
reason: "market_mismatch",
|
|
126
|
+
currentMarketId: options.marketId ?? null,
|
|
127
|
+
allowedMarketIds: bundle.marketIds
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { bundle, warnings: [] };
|
|
104
133
|
}
|
|
105
134
|
|
|
106
135
|
// src/fetchShopCustomCss.ts
|
|
@@ -146,7 +175,11 @@ function useBundleData(options) {
|
|
|
146
175
|
shopDomain: options.shopDomain,
|
|
147
176
|
storefrontAccessToken: options.storefrontAccessToken,
|
|
148
177
|
bundleGid: options.bundleGid,
|
|
149
|
-
signal: controller.signal
|
|
178
|
+
signal: controller.signal,
|
|
179
|
+
country: options.country,
|
|
180
|
+
language: options.language,
|
|
181
|
+
buyer: options.buyer,
|
|
182
|
+
marketId: options.marketId
|
|
150
183
|
}).then((bundle) => {
|
|
151
184
|
if (controller.signal.aborted) return;
|
|
152
185
|
setState({ status: "success", bundle, error: null });
|
|
@@ -173,7 +206,11 @@ function useBundleData(options) {
|
|
|
173
206
|
}, [
|
|
174
207
|
options.shopDomain,
|
|
175
208
|
options.storefrontAccessToken,
|
|
176
|
-
options.bundleGid
|
|
209
|
+
options.bundleGid,
|
|
210
|
+
options.country,
|
|
211
|
+
options.language,
|
|
212
|
+
options.buyer,
|
|
213
|
+
options.marketId
|
|
177
214
|
]);
|
|
178
215
|
return state;
|
|
179
216
|
}
|