@decocms/apps 0.21.2 → 0.21.4
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 +75 -0
- package/commerce/components/Image.tsx +171 -126
- package/package.json +1 -1
- package/vtex/client.ts +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @decocms/apps
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@decocms/apps)
|
|
4
|
+
[](https://github.com/decocms/apps-start/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
Commerce integrations for [Deco](https://deco.cx) storefronts on **TanStack Start + React 19 + Cloudflare Workers**.
|
|
7
|
+
|
|
8
|
+
Provides VTEX and Shopify loaders, actions, hooks, and shared commerce types based on schema.org. Built on top of [`@decocms/start`](https://www.npmjs.com/package/@decocms/start).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @decocms/apps
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Integrations
|
|
17
|
+
|
|
18
|
+
### VTEX
|
|
19
|
+
|
|
20
|
+
Full VTEX Intelligent Search and Checkout integration.
|
|
21
|
+
|
|
22
|
+
| Import | Purpose |
|
|
23
|
+
|--------|---------|
|
|
24
|
+
| `@decocms/apps/vtex` | Configuration and setup |
|
|
25
|
+
| `@decocms/apps/vtex/client` | VTEX API client with SWR caching |
|
|
26
|
+
| `@decocms/apps/vtex/loaders/*` | Product, cart, search, catalog, session, wishlist |
|
|
27
|
+
| `@decocms/apps/vtex/actions/*` | Checkout, auth, newsletter, profile, wishlist |
|
|
28
|
+
| `@decocms/apps/vtex/hooks` | useCart, useUser, useWishlist, useAutocomplete |
|
|
29
|
+
| `@decocms/apps/vtex/inline-loaders/*` | PDP, PLP, product list, suggestions |
|
|
30
|
+
| `@decocms/apps/vtex/middleware` | Cookie propagation and session handling |
|
|
31
|
+
| `@decocms/apps/vtex/invoke` | Server function wrappers |
|
|
32
|
+
| `@decocms/apps/vtex/utils/*` | Transform, enrichment, segment, cookies |
|
|
33
|
+
|
|
34
|
+
### Shopify
|
|
35
|
+
|
|
36
|
+
Storefront API integration via GraphQL.
|
|
37
|
+
|
|
38
|
+
| Import | Purpose |
|
|
39
|
+
|--------|---------|
|
|
40
|
+
| `@decocms/apps/shopify` | Configuration and setup |
|
|
41
|
+
| `@decocms/apps/shopify/client` | Storefront GraphQL client |
|
|
42
|
+
| `@decocms/apps/shopify/loaders/*` | PDP, PLP, product list, cart, user |
|
|
43
|
+
| `@decocms/apps/shopify/actions/cart/*` | Add, update items, coupons |
|
|
44
|
+
| `@decocms/apps/shopify/actions/user/*` | Sign in, sign up |
|
|
45
|
+
| `@decocms/apps/shopify/utils/*` | Transform, cookies, GraphQL queries |
|
|
46
|
+
|
|
47
|
+
### Shared Commerce
|
|
48
|
+
|
|
49
|
+
Platform-agnostic types and utilities.
|
|
50
|
+
|
|
51
|
+
| Import | Purpose |
|
|
52
|
+
|--------|---------|
|
|
53
|
+
| `@decocms/apps/commerce/types` | schema.org Product, Offer, BreadcrumbList, etc. |
|
|
54
|
+
| `@decocms/apps/commerce/components/Image` | Optimized commerce image component |
|
|
55
|
+
| `@decocms/apps/commerce/components/JsonLd` | Structured data for SEO |
|
|
56
|
+
| `@decocms/apps/commerce/sdk/*` | useOffer, formatPrice, analytics, URL utils |
|
|
57
|
+
| `@decocms/apps/commerce/utils/*` | productToAnalyticsItem, canonical, stateByZip |
|
|
58
|
+
|
|
59
|
+
## Peer Dependencies
|
|
60
|
+
|
|
61
|
+
- `@decocms/start` >= 0.19.0
|
|
62
|
+
- `@tanstack/react-query` >= 5
|
|
63
|
+
- `react` >= 18
|
|
64
|
+
- `react-dom` >= 18
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run typecheck # tsc --noEmit
|
|
70
|
+
npm run check # typecheck + unused export detection
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
|
@@ -1,144 +1,201 @@
|
|
|
1
|
+
import type { ImgHTMLAttributes } from "react";
|
|
2
|
+
|
|
3
|
+
// -------------------------------------------------------------------------
|
|
4
|
+
// Known asset prefixes that get stripped to produce a relative src path
|
|
5
|
+
// -------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
const DECO_CACHE_URL = "https://assets.decocache.com/";
|
|
8
|
+
const S3_URL = "https://deco-sites-assets.s3.sa-east-1.amazonaws.com/";
|
|
9
|
+
|
|
10
|
+
// -------------------------------------------------------------------------
|
|
11
|
+
// Configurable CDN domain
|
|
12
|
+
// -------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
let imageCdnDomain = "decoims.com";
|
|
15
|
+
|
|
1
16
|
/**
|
|
2
|
-
*
|
|
17
|
+
* Register the image CDN domain used by `getOptimizedMediaUrl`.
|
|
18
|
+
* Call once in your site's setup.ts before any page loads.
|
|
3
19
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```tsx
|
|
10
|
-
* <Image
|
|
11
|
-
* src="https://store.vteximg.com.br/products/123.jpg"
|
|
12
|
-
* width={400}
|
|
13
|
-
* height={400}
|
|
14
|
-
* alt="Product name"
|
|
15
|
-
* cdn="vtex"
|
|
16
|
-
* />
|
|
17
|
-
* ```
|
|
20
|
+
* Available domains:
|
|
21
|
+
* - `decoims.com` (Cloudflare, default — best compression, same edge as Workers)
|
|
22
|
+
* - `deco-assets.edgedeco.com` (Azion IMS)
|
|
23
|
+
* - `deco-assets.decoazn.com` (Azion IMS, legacy)
|
|
18
24
|
*/
|
|
25
|
+
export function registerImageCdnDomain(domain: string) {
|
|
26
|
+
imageCdnDomain = domain.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
27
|
+
}
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
export function getImageCdnDomain(): string {
|
|
30
|
+
return imageCdnDomain;
|
|
31
|
+
}
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
// -------------------------------------------------------------------------
|
|
34
|
+
// Fit options & optimization types
|
|
35
|
+
// -------------------------------------------------------------------------
|
|
23
36
|
|
|
24
|
-
export
|
|
25
|
-
|
|
37
|
+
export type FitOptions = "contain" | "cover";
|
|
38
|
+
|
|
39
|
+
export const FACTORS = [1, 2];
|
|
40
|
+
|
|
41
|
+
interface OptimizationOptions {
|
|
42
|
+
originalSrc: string;
|
|
26
43
|
width: number;
|
|
27
|
-
height
|
|
28
|
-
|
|
29
|
-
cdn?: ImageCDN;
|
|
30
|
-
/**
|
|
31
|
-
* Responsive sizes descriptor.
|
|
32
|
-
* @default "(max-width: 768px) 100vw, 50vw"
|
|
33
|
-
*/
|
|
34
|
-
sizes?: string;
|
|
35
|
-
/**
|
|
36
|
-
* Multipliers for srcset generation.
|
|
37
|
-
* @default [1, 2]
|
|
38
|
-
*/
|
|
39
|
-
srcSetMultipliers?: number[];
|
|
40
|
-
/** Preload the image (adds fetchPriority="high"). */
|
|
41
|
-
preload?: boolean;
|
|
44
|
+
height?: number;
|
|
45
|
+
fit: FitOptions;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
// -------------------------------------------------------------------------
|
|
45
|
-
//
|
|
49
|
+
// Platform-specific URL optimizers (fallbacks when the CDN can handle
|
|
50
|
+
// the native platform's resize syntax directly)
|
|
46
51
|
// -------------------------------------------------------------------------
|
|
47
52
|
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`-${width}-${height}$2`,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
53
|
+
function optimizeVTEX(originalSrc: string, width: number, height?: number): string {
|
|
54
|
+
const src = new URL(originalSrc);
|
|
55
|
+
const [slash, arquivos, ids, rawId, ...rest] = src.pathname.split("/");
|
|
56
|
+
const [trueId] = rawId.split("-");
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
src.pathname = [
|
|
59
|
+
slash,
|
|
60
|
+
arquivos,
|
|
61
|
+
ids,
|
|
62
|
+
`${trueId}-${width}-${height ?? width}`,
|
|
63
|
+
...rest,
|
|
64
|
+
].join("/");
|
|
65
|
+
|
|
66
|
+
return src.href;
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
69
|
+
function optimizeShopify(originalSrc: string, width: number, height?: number): string {
|
|
70
|
+
const url = new URL(originalSrc);
|
|
71
|
+
url.searchParams.set("width", `${width}`);
|
|
72
|
+
if (height) url.searchParams.set("height", `${height}`);
|
|
73
|
+
url.searchParams.set("crop", "center");
|
|
74
|
+
return url.href;
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// -------------------------------------------------------------------------
|
|
78
|
+
// Core optimization function
|
|
79
|
+
// Ported from deco-cx/apps website/components/Image.tsx
|
|
80
|
+
// -------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds an optimized image URL.
|
|
84
|
+
*
|
|
85
|
+
* For Deco-hosted images (decocache / S3), strips the known prefix and
|
|
86
|
+
* routes through the Deco image CDN for edge resize + format conversion.
|
|
87
|
+
*
|
|
88
|
+
* For platform-specific images (VTEX, Shopify), rewrites the URL using
|
|
89
|
+
* the platform's native resize params — no CDN proxy needed.
|
|
90
|
+
*
|
|
91
|
+
* Data URIs are returned as-is.
|
|
92
|
+
*/
|
|
93
|
+
export function getOptimizedMediaUrl(opts: OptimizationOptions): string {
|
|
94
|
+
const { originalSrc, width, height, fit } = opts;
|
|
95
|
+
|
|
96
|
+
if (originalSrc.startsWith("data:")) {
|
|
97
|
+
return originalSrc;
|
|
76
98
|
}
|
|
77
|
-
return src;
|
|
78
|
-
}
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
if (
|
|
101
|
+
/(vteximg\.com\.br|vtexassets\.com|myvtex\.com)\/arquivos\/ids\/\d+/.test(originalSrc)
|
|
102
|
+
) {
|
|
103
|
+
return optimizeVTEX(originalSrc, width, height);
|
|
104
|
+
}
|
|
83
105
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
case "vtex": return vtexImageUrl(src, width, height);
|
|
87
|
-
case "shopify": return shopifyImageUrl(src, width);
|
|
88
|
-
case "deco": return decoImageUrl(src, width, height);
|
|
89
|
-
case "cloudflare": return cloudflareImageUrl(src, width, height);
|
|
90
|
-
default: return src;
|
|
106
|
+
if (originalSrc.startsWith("https://cdn.shopify.com")) {
|
|
107
|
+
return optimizeShopify(originalSrc, width, height);
|
|
91
108
|
}
|
|
109
|
+
|
|
110
|
+
const imageSource = originalSrc
|
|
111
|
+
.replace(DECO_CACHE_URL, "")
|
|
112
|
+
.replace(S3_URL, "")
|
|
113
|
+
.split("?")[0];
|
|
114
|
+
|
|
115
|
+
const params = new URLSearchParams();
|
|
116
|
+
params.set("fit", fit);
|
|
117
|
+
params.set("width", `${width}`);
|
|
118
|
+
if (height) params.set("height", `${height}`);
|
|
119
|
+
|
|
120
|
+
return `https://${imageCdnDomain}/image?${params}&src=${imageSource}`;
|
|
92
121
|
}
|
|
93
122
|
|
|
94
|
-
|
|
95
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Generates a srcset string with responsive multipliers.
|
|
125
|
+
*/
|
|
126
|
+
export function getSrcSet(
|
|
127
|
+
originalSrc: string,
|
|
96
128
|
width: number,
|
|
97
|
-
height
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
): string {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
height?: number,
|
|
130
|
+
fit?: FitOptions,
|
|
131
|
+
factors: number[] = FACTORS,
|
|
132
|
+
): string | undefined {
|
|
133
|
+
const entries: string[] = [];
|
|
134
|
+
|
|
135
|
+
for (const factor of factors) {
|
|
136
|
+
const w = Math.trunc(factor * width);
|
|
137
|
+
const h = height ? Math.trunc(factor * height) : undefined;
|
|
138
|
+
|
|
139
|
+
const src = getOptimizedMediaUrl({
|
|
140
|
+
originalSrc,
|
|
141
|
+
width: w,
|
|
142
|
+
height: h,
|
|
143
|
+
fit: fit ?? "cover",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (src) {
|
|
147
|
+
entries.push(`${src} ${w}w`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return entries.length > 0 ? entries.join(", ") : undefined;
|
|
108
152
|
}
|
|
109
153
|
|
|
110
154
|
// -------------------------------------------------------------------------
|
|
111
|
-
//
|
|
155
|
+
// Image component
|
|
112
156
|
// -------------------------------------------------------------------------
|
|
113
157
|
|
|
158
|
+
export interface ImageProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "width" | "height"> {
|
|
159
|
+
src: string;
|
|
160
|
+
/** @description Improves Web Vitals (CLS/LCP) */
|
|
161
|
+
width: number;
|
|
162
|
+
/** @description Improves Web Vitals (CLS/LCP) */
|
|
163
|
+
height?: number;
|
|
164
|
+
/** @description Object-fit */
|
|
165
|
+
fit?: FitOptions;
|
|
166
|
+
/** @description Preload the image (adds fetchPriority="high") */
|
|
167
|
+
preload?: boolean;
|
|
168
|
+
}
|
|
169
|
+
|
|
114
170
|
export function Image({
|
|
115
171
|
src,
|
|
116
172
|
width,
|
|
117
173
|
height,
|
|
118
|
-
|
|
119
|
-
sizes = "(max-width: 768px) 100vw, 50vw",
|
|
120
|
-
srcSetMultipliers = [1, 2],
|
|
174
|
+
fit = "cover",
|
|
121
175
|
preload,
|
|
122
176
|
loading,
|
|
123
177
|
decoding,
|
|
178
|
+
srcSet: srcSetProp,
|
|
179
|
+
sizes,
|
|
124
180
|
...rest
|
|
125
181
|
}: ImageProps) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
182
|
+
if (!height && typeof process !== "undefined") {
|
|
183
|
+
console.warn(`Missing height. This image will NOT be optimized: ${src}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const srcSet = srcSetProp ?? getSrcSet(src, width, height, fit);
|
|
130
187
|
|
|
131
188
|
return (
|
|
132
189
|
<img
|
|
133
|
-
|
|
190
|
+
{...rest}
|
|
191
|
+
src={src}
|
|
134
192
|
srcSet={srcSet}
|
|
135
|
-
sizes={srcSet ? sizes : undefined}
|
|
193
|
+
sizes={srcSet ? (sizes ?? "(max-width: 768px) 100vw, 50vw") : undefined}
|
|
136
194
|
width={width}
|
|
137
195
|
height={height}
|
|
138
196
|
loading={loading ?? (preload ? "eager" : "lazy")}
|
|
139
197
|
decoding={decoding ?? "async"}
|
|
140
198
|
fetchPriority={preload ? "high" : undefined}
|
|
141
|
-
{...rest}
|
|
142
199
|
/>
|
|
143
200
|
);
|
|
144
201
|
}
|
|
@@ -147,63 +204,51 @@ export function Image({
|
|
|
147
204
|
// Picture (responsive art direction)
|
|
148
205
|
// -------------------------------------------------------------------------
|
|
149
206
|
|
|
150
|
-
export interface
|
|
207
|
+
export interface PictureSourceProps {
|
|
151
208
|
src: string;
|
|
152
209
|
width: number;
|
|
153
|
-
height
|
|
210
|
+
height?: number;
|
|
154
211
|
media: string;
|
|
155
|
-
|
|
212
|
+
fit?: FitOptions;
|
|
156
213
|
}
|
|
157
214
|
|
|
158
215
|
export interface PictureProps extends Omit<ImageProps, "sizes"> {
|
|
159
|
-
sources:
|
|
216
|
+
sources: PictureSourceProps[];
|
|
160
217
|
}
|
|
161
218
|
|
|
162
|
-
/**
|
|
163
|
-
* Picture component for responsive art direction.
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* ```tsx
|
|
167
|
-
* <Picture
|
|
168
|
-
* sources={[
|
|
169
|
-
* { src: mobileSrc, width: 375, height: 200, media: "(max-width: 767px)", cdn: "vtex" },
|
|
170
|
-
* { src: desktopSrc, width: 1200, height: 400, media: "(min-width: 768px)", cdn: "vtex" },
|
|
171
|
-
* ]}
|
|
172
|
-
* src={desktopSrc}
|
|
173
|
-
* width={1200}
|
|
174
|
-
* height={400}
|
|
175
|
-
* alt="Banner"
|
|
176
|
-
* />
|
|
177
|
-
* ```
|
|
178
|
-
*/
|
|
179
219
|
export function Picture({
|
|
180
220
|
sources,
|
|
181
221
|
src,
|
|
182
222
|
width,
|
|
183
223
|
height,
|
|
184
|
-
|
|
224
|
+
fit = "cover",
|
|
185
225
|
preload,
|
|
186
226
|
...rest
|
|
187
227
|
}: PictureProps) {
|
|
188
228
|
return (
|
|
189
229
|
<picture>
|
|
190
|
-
{sources.map((source, i) =>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
230
|
+
{sources.map((source, i) => {
|
|
231
|
+
const srcSet = getSrcSet(source.src, source.width, source.height, source.fit ?? fit);
|
|
232
|
+
return (
|
|
233
|
+
<source
|
|
234
|
+
key={i}
|
|
235
|
+
srcSet={srcSet}
|
|
236
|
+
media={source.media}
|
|
237
|
+
width={source.width}
|
|
238
|
+
height={source.height}
|
|
239
|
+
/>
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
199
242
|
<Image
|
|
200
243
|
src={src}
|
|
201
244
|
width={width}
|
|
202
245
|
height={height}
|
|
203
|
-
|
|
246
|
+
fit={fit}
|
|
204
247
|
preload={preload}
|
|
205
248
|
{...rest}
|
|
206
249
|
/>
|
|
207
250
|
</picture>
|
|
208
251
|
);
|
|
209
252
|
}
|
|
253
|
+
|
|
254
|
+
export default Image;
|
package/package.json
CHANGED
package/vtex/client.ts
CHANGED
|
@@ -82,6 +82,8 @@ export interface VtexConfig {
|
|
|
82
82
|
|
|
83
83
|
let _config: VtexConfig | null = null;
|
|
84
84
|
let _fetch: typeof fetch = globalThis.fetch;
|
|
85
|
+
let _getCookieHeader: (() => string | undefined) | null = null;
|
|
86
|
+
let _forwardSetCookies: ((cookies: string[]) => void) | null = null;
|
|
85
87
|
|
|
86
88
|
export function configureVtex(config: VtexConfig) {
|
|
87
89
|
_config = config;
|
|
@@ -103,6 +105,37 @@ export function setVtexFetch(fetchFn: typeof fetch) {
|
|
|
103
105
|
_fetch = fetchFn;
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Register a provider that returns the Cookie header from the current request.
|
|
110
|
+
* Called automatically by vtexFetchWithCookies when no explicit cookieHeader
|
|
111
|
+
* is present in the request init — so checkout/session/auth actions
|
|
112
|
+
* transparently forward browser cookies to the VTEX API.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* import { getRequestHeader } from "@tanstack/react-start/server";
|
|
117
|
+
* setRequestCookieProvider(() => getRequestHeader("cookie") ?? undefined);
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function setRequestCookieProvider(fn: () => string | undefined) {
|
|
121
|
+
_getCookieHeader = fn;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register a callback that forwards VTEX Set-Cookie headers back to the browser.
|
|
126
|
+
* Called automatically by vtexFetchWithCookies after every response that
|
|
127
|
+
* carries Set-Cookie headers.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```ts
|
|
131
|
+
* import { setResponseHeader } from "@tanstack/react-start/server";
|
|
132
|
+
* setResponseCookieForwarder((cookies) => setResponseHeader("set-cookie", cookies));
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function setResponseCookieForwarder(fn: (cookies: string[]) => void) {
|
|
136
|
+
_forwardSetCookies = fn;
|
|
137
|
+
}
|
|
138
|
+
|
|
106
139
|
export function getVtexConfig(): VtexConfig {
|
|
107
140
|
if (!_config)
|
|
108
141
|
throw new Error("VTEX not configured. Call configureVtex() first.");
|
|
@@ -222,6 +255,20 @@ export async function vtexFetchWithCookies<T>(
|
|
|
222
255
|
path: string,
|
|
223
256
|
init?: RequestInit,
|
|
224
257
|
): Promise<VtexFetchResult<T>> {
|
|
258
|
+
// Auto-inject request cookies when no explicit cookie header is set
|
|
259
|
+
if (_getCookieHeader) {
|
|
260
|
+
const existingHeaders = init?.headers as Record<string, string> | undefined;
|
|
261
|
+
if (!existingHeaders?.["cookie"]) {
|
|
262
|
+
const cookies = _getCookieHeader();
|
|
263
|
+
if (cookies) {
|
|
264
|
+
init = {
|
|
265
|
+
...init,
|
|
266
|
+
headers: { ...existingHeaders, cookie: cookies },
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
225
272
|
const response = await vtexFetchResponse(path, init);
|
|
226
273
|
const data = (await response.json()) as T;
|
|
227
274
|
const setCookies: string[] = [];
|
|
@@ -234,6 +281,12 @@ export async function vtexFetchWithCookies<T>(
|
|
|
234
281
|
}
|
|
235
282
|
});
|
|
236
283
|
}
|
|
284
|
+
|
|
285
|
+
// Auto-forward Set-Cookie headers to the browser response
|
|
286
|
+
if (_forwardSetCookies && setCookies.length) {
|
|
287
|
+
_forwardSetCookies(setCookies);
|
|
288
|
+
}
|
|
289
|
+
|
|
237
290
|
return { data, setCookies };
|
|
238
291
|
}
|
|
239
292
|
|