@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.2

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.
Files changed (163) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/utils/constants.d.ts +28 -0
  6. package/dist/utils/constants.d.ts.map +1 -0
  7. package/dist/utils/constants.js +17 -0
  8. package/dist/utils/constants.js.map +1 -0
  9. package/dist/utils/cookieSanitizer.d.ts +87 -0
  10. package/dist/utils/cookieSanitizer.d.ts.map +1 -0
  11. package/dist/utils/cookieSanitizer.js +136 -0
  12. package/dist/utils/cookieSanitizer.js.map +1 -0
  13. package/dist/utils/operationRouter.d.ts +25 -0
  14. package/dist/utils/operationRouter.d.ts.map +1 -0
  15. package/dist/utils/operationRouter.js +120 -0
  16. package/dist/utils/operationRouter.js.map +1 -0
  17. package/dist/utils/types.d.ts +944 -0
  18. package/dist/utils/types.d.ts.map +1 -0
  19. package/dist/utils/types.js +25 -0
  20. package/dist/utils/types.js.map +1 -0
  21. package/dist/vtexClient.d.ts +173 -0
  22. package/dist/vtexClient.d.ts.map +1 -0
  23. package/dist/vtexClient.js +242 -0
  24. package/dist/vtexClient.js.map +1 -0
  25. package/package.json +16 -57
  26. package/src/index.ts +11 -29
  27. package/src/utils/constants.ts +2 -56
  28. package/src/utils/cookieSanitizer.ts +59 -59
  29. package/src/utils/operationRouter.ts +75 -65
  30. package/src/utils/types.ts +709 -1557
  31. package/src/vtexClient.ts +358 -347
  32. package/src/README.md +0 -6
  33. package/src/__conformance__/upstream.test.ts +0 -158
  34. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  35. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  36. package/src/__tests__/schemas.test.ts +0 -71
  37. package/src/actions/address.ts +0 -250
  38. package/src/actions/analytics/sendEvent.ts +0 -86
  39. package/src/actions/auth.ts +0 -333
  40. package/src/actions/checkout.ts +0 -659
  41. package/src/actions/index.ts +0 -11
  42. package/src/actions/masterData.ts +0 -168
  43. package/src/actions/misc.ts +0 -188
  44. package/src/actions/newsletter.ts +0 -105
  45. package/src/actions/orders.ts +0 -34
  46. package/src/actions/profile.ts +0 -201
  47. package/src/actions/session.ts +0 -85
  48. package/src/actions/trigger.ts +0 -43
  49. package/src/actions/wishlist.ts +0 -114
  50. package/src/client.ts +0 -724
  51. package/src/commerceLoaders.ts +0 -267
  52. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  53. package/src/hooks/__tests__/createCart.test.ts +0 -92
  54. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  55. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  56. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  57. package/src/hooks/cartQuery.ts +0 -204
  58. package/src/hooks/createCart.ts +0 -427
  59. package/src/hooks/createUseCart.ts +0 -360
  60. package/src/hooks/createUseUser.ts +0 -153
  61. package/src/hooks/createUseWishlist.ts +0 -242
  62. package/src/hooks/index.ts +0 -25
  63. package/src/hooks/useAutocomplete.ts +0 -83
  64. package/src/hooks/useCart.ts +0 -243
  65. package/src/hooks/useUser.ts +0 -78
  66. package/src/hooks/useWishlist.ts +0 -119
  67. package/src/invoke.ts +0 -235
  68. package/src/loaders/ProductDetailsPage.ts +0 -1
  69. package/src/loaders/ProductList.ts +0 -1
  70. package/src/loaders/ProductListingPage.ts +0 -1
  71. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  72. package/src/loaders/address.ts +0 -115
  73. package/src/loaders/autocomplete.ts +0 -56
  74. package/src/loaders/brands.ts +0 -44
  75. package/src/loaders/cart/attachments.ts +0 -53
  76. package/src/loaders/cart/full.ts +0 -67
  77. package/src/loaders/cart/gifts.ts +0 -43
  78. package/src/loaders/cart/orderFormId.ts +0 -31
  79. package/src/loaders/cart/shipping.test.ts +0 -125
  80. package/src/loaders/cart/shipping.ts +0 -136
  81. package/src/loaders/cart/summary.ts +0 -36
  82. package/src/loaders/cart.ts +0 -52
  83. package/src/loaders/catalog.ts +0 -163
  84. package/src/loaders/collections.ts +0 -55
  85. package/src/loaders/index.ts +0 -20
  86. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  87. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  88. package/src/loaders/intelligentSearch/productList.ts +0 -170
  89. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  90. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  91. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  92. package/src/loaders/legacy/productList.ts +0 -1
  93. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  94. package/src/loaders/legacy.ts +0 -710
  95. package/src/loaders/logistics.ts +0 -111
  96. package/src/loaders/minicart.ts +0 -78
  97. package/src/loaders/navbar.ts +0 -26
  98. package/src/loaders/orders.ts +0 -92
  99. package/src/loaders/pageType.ts +0 -68
  100. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  101. package/src/loaders/payment.ts +0 -97
  102. package/src/loaders/productListFull.ts +0 -157
  103. package/src/loaders/profile.ts +0 -138
  104. package/src/loaders/promotion.ts +0 -30
  105. package/src/loaders/search.ts +0 -124
  106. package/src/loaders/session.ts +0 -83
  107. package/src/loaders/user.ts +0 -87
  108. package/src/loaders/wishlist.ts +0 -89
  109. package/src/loaders/wishlistProducts.ts +0 -69
  110. package/src/loaders/workflow/products.ts +0 -62
  111. package/src/loaders/workflow.ts +0 -316
  112. package/src/logo.png +0 -0
  113. package/src/manifest.gen.ts +0 -89
  114. package/src/middleware.cacheHeaders.test.ts +0 -44
  115. package/src/middleware.ts +0 -240
  116. package/src/mod.ts +0 -173
  117. package/src/registry.ts +0 -9
  118. package/src/schemas.gen.ts +0 -3191
  119. package/src/schemas.ts +0 -30
  120. package/src/types.ts +0 -248
  121. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  122. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  123. package/src/utils/__tests__/fetch.test.ts +0 -80
  124. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  125. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  126. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  127. package/src/utils/__tests__/minicart.test.ts +0 -184
  128. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  129. package/src/utils/__tests__/resilience.test.ts +0 -205
  130. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  131. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  132. package/src/utils/__tests__/sitemap.test.ts +0 -185
  133. package/src/utils/__tests__/slugify.test.ts +0 -31
  134. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  135. package/src/utils/__tests__/transform.test.ts +0 -1111
  136. package/src/utils/accountLoaders.ts +0 -203
  137. package/src/utils/authHelpers.ts +0 -85
  138. package/src/utils/batch.ts +0 -18
  139. package/src/utils/buildOfferShelf.test.ts +0 -59
  140. package/src/utils/cartProjection.ts +0 -96
  141. package/src/utils/cookies.ts +0 -167
  142. package/src/utils/enrichment.ts +0 -556
  143. package/src/utils/fetch.ts +0 -111
  144. package/src/utils/fetchCache.ts +0 -55
  145. package/src/utils/index.ts +0 -19
  146. package/src/utils/instrumentedFetch.ts +0 -98
  147. package/src/utils/intelligentSearch.ts +0 -96
  148. package/src/utils/legacy.ts +0 -139
  149. package/src/utils/minicart.ts +0 -139
  150. package/src/utils/pickAndOmit.ts +0 -25
  151. package/src/utils/proxy.ts +0 -495
  152. package/src/utils/resilience.ts +0 -352
  153. package/src/utils/resourceRange.ts +0 -10
  154. package/src/utils/segment.ts +0 -152
  155. package/src/utils/similars.ts +0 -35
  156. package/src/utils/simulationCache.ts +0 -73
  157. package/src/utils/sitemap.ts +0 -283
  158. package/src/utils/slugCache.ts +0 -28
  159. package/src/utils/slugify.ts +0 -13
  160. package/src/utils/transform.ts +0 -1691
  161. package/src/utils/vtexId.ts +0 -151
  162. package/src/vtexClient.test.ts +0 -204
  163. package/tsconfig.json +0 -7
@@ -1,495 +0,0 @@
1
- /**
2
- * VTEX Proxy Utility.
3
- *
4
- * Proxies storefront requests for /checkout, /account, /api, /files, /arquivos
5
- * to the VTEX origin. Essential for checkout and My Account pages to work.
6
- *
7
- * Two flavors:
8
- * - `proxyToVtex()` — simple single-origin proxy (vtexcommercestable)
9
- * - `createVtexCheckoutProxy()` — production-grade dual-origin proxy with
10
- * proper cookie attribute preservation, non-ASCII sanitization, and
11
- * configurable origin routing (checkout UI vs API paths)
12
- *
13
- * Designed to be used with TanStack Start API routes or Cloudflare Worker
14
- * fetch handlers.
15
- */
16
-
17
- import { getVtexConfig, getVtexFetch, type VtexConfig, vtexHost } from "../client";
18
- import { proxySetCookie } from "./cookies";
19
-
20
- export interface VtexProxyOptions {
21
- /**
22
- * VTEX environment suffix.
23
- * @default "vtexcommercestable"
24
- */
25
- environment?: "vtexcommercestable" | "vtexcommercebeta";
26
-
27
- /**
28
- * Additional path prefixes to proxy beyond the defaults.
29
- * Example: ["/custom-api/"]
30
- */
31
- extraPaths?: string[];
32
-
33
- /**
34
- * Paths that should NOT be proxied even if they match a prefix.
35
- */
36
- excludePaths?: string[];
37
-
38
- /**
39
- * Whether to rewrite Set-Cookie domains to the storefront's domain.
40
- * @default true
41
- */
42
- rewriteCookieDomain?: boolean;
43
-
44
- /**
45
- * Custom headers to inject into every proxied request.
46
- */
47
- extraHeaders?: Record<string, string>;
48
-
49
- /**
50
- * Force `Cache-Control: private, no-store` + `CDN-Cache-Control: no-store`
51
- * on authenticated proxy responses (everything except static assets), so a
52
- * personalized VTEX response can never be cached and served across users.
53
- * @default true
54
- */
55
- hardenAuthenticatedCache?: boolean;
56
- }
57
-
58
- const DEFAULT_PROXY_PATHS = [
59
- "/checkout",
60
- "/checkout/",
61
- "/account",
62
- "/account/",
63
- "/api/",
64
- "/files/",
65
- "/arquivos/",
66
- "/checkout/changeToAnonymousUser/",
67
- "/_v/",
68
- "/no-cache/",
69
- "/graphql/",
70
- "/login",
71
- "/login/",
72
- "/logout",
73
- "/logout/",
74
- "/assets/vtex",
75
- "/_secure/account",
76
- "/XMLData/",
77
- ] as const;
78
-
79
- const HOP_BY_HOP_HEADERS = new Set([
80
- "connection",
81
- "keep-alive",
82
- "proxy-authenticate",
83
- "proxy-authorization",
84
- "te",
85
- "trailers",
86
- "transfer-encoding",
87
- "upgrade",
88
- ]);
89
-
90
- /**
91
- * Path prefixes whose proxied responses are safe to cache publicly — genuinely
92
- * public static assets (media files, VTEX store-theme bundles). Everything else
93
- * routed through the VTEX proxy is authenticated/personalized by nature
94
- * (account, checkout, /api/sessions, orders, graphql, VTEX ID) and MUST NOT be
95
- * cached by shared/edge layers.
96
- */
97
- const PUBLIC_PROXY_ASSET_PREFIXES = [
98
- "/files/",
99
- "/arquivos/",
100
- "/assets/vtex",
101
- "/XMLData/",
102
- ];
103
-
104
- function isPublicProxyAsset(pathname: string): boolean {
105
- return PUBLIC_PROXY_ASSET_PREFIXES.some((p) => pathname.startsWith(p));
106
- }
107
-
108
- /**
109
- * Force authenticated proxy responses to be non-cacheable on every shared layer.
110
- *
111
- * VTEX marks some endpoints `no-store` (e.g. `/api/sessions`) but not all
112
- * (`/account` comes back `public`), and the raw origin `Cache-Control` must
113
- * never let a personalized response be cached across users. Without this, a
114
- * logged-in user's response can be stored at the edge and served to another
115
- * user — the root cause of a cross-customer data leak (see decocms/blocks#412).
116
- * Static assets are exempt so store-theme/media caching is preserved.
117
- */
118
- function hardenProxyCacheHeaders(headers: Headers, pathname: string): void {
119
- if (isPublicProxyAsset(pathname)) return;
120
- headers.set("Cache-Control", "private, no-store, no-cache, must-revalidate");
121
- // CDN-Cache-Control takes precedence for Cloudflare's CDN layer.
122
- headers.set("CDN-Cache-Control", "no-store");
123
- headers.delete("Surrogate-Control");
124
- }
125
-
126
- /**
127
- * Returns all path prefixes that should be proxied to VTEX.
128
- */
129
- export function getVtexProxyPaths(options?: VtexProxyOptions): string[] {
130
- return [...DEFAULT_PROXY_PATHS, ...(options?.extraPaths ?? [])];
131
- }
132
-
133
- /**
134
- * Check if a request path should be proxied to VTEX.
135
- */
136
- export function shouldProxyToVtex(pathname: string, options?: VtexProxyOptions): boolean {
137
- const paths = getVtexProxyPaths(options);
138
- const excluded = options?.excludePaths ?? [];
139
-
140
- if (excluded.some((ex) => pathname.startsWith(ex))) return false;
141
- return paths.some((prefix) => pathname.startsWith(prefix));
142
- }
143
-
144
- function buildOriginUrl(request: Request, config: VtexConfig, environment: string): URL {
145
- const url = new URL(request.url);
146
- const originHost = vtexHost(environment, config);
147
- return new URL(`https://${originHost}${url.pathname}${url.search}`);
148
- }
149
-
150
- /**
151
- * Copy headers excluding hop-by-hop and Set-Cookie.
152
- *
153
- * Set-Cookie is excluded intentionally: Headers.forEach / .set() joins
154
- * multiple Set-Cookie values with ", " which corrupts cookies containing
155
- * commas (e.g. Expires dates). proxySetCookie handles Set-Cookie
156
- * separately using Headers.getSetCookie() for correct multi-cookie support.
157
- */
158
- function filterHeaders(headers: Headers): Headers {
159
- const filtered = new Headers();
160
- headers.forEach((value, key) => {
161
- const lower = key.toLowerCase();
162
- if (lower === "set-cookie") return;
163
- if (!HOP_BY_HOP_HEADERS.has(lower)) {
164
- filtered.set(key, value);
165
- }
166
- });
167
- return filtered;
168
- }
169
-
170
- /**
171
- * Proxy a request to VTEX origin.
172
- *
173
- * Forwards the request with all cookies and headers, rewrites
174
- * Set-Cookie domains on the response, and strips hop-by-hop headers.
175
- *
176
- * @example
177
- * ```ts
178
- * // In a TanStack Start API route or catch-all handler
179
- * if (shouldProxyToVtex(url.pathname)) {
180
- * return proxyToVtex(request);
181
- * }
182
- * ```
183
- */
184
- export async function proxyToVtex(request: Request, options?: VtexProxyOptions): Promise<Response> {
185
- const config = getVtexConfig();
186
- const environment = options?.environment ?? "vtexcommercestable";
187
-
188
- const originUrl = buildOriginUrl(request, config, environment);
189
- const forwardHeaders = filterHeaders(new Headers(request.headers));
190
-
191
- const requestUrl = new URL(request.url);
192
- forwardHeaders.set("origin", request.headers.get("origin") ?? requestUrl.origin);
193
- forwardHeaders.set("Host", originUrl.hostname);
194
- forwardHeaders.set("X-Forwarded-Host", requestUrl.host);
195
- forwardHeaders.set("X-Forwarded-Proto", "https");
196
-
197
- if (options?.extraHeaders) {
198
- for (const [k, v] of Object.entries(options.extraHeaders)) {
199
- forwardHeaders.set(k, v);
200
- }
201
- }
202
-
203
- if (typeof config.appKey === "string" && typeof config.appToken === "string") {
204
- forwardHeaders.set("X-VTEX-API-AppKey", config.appKey);
205
- forwardHeaders.set("X-VTEX-API-AppToken", config.appToken);
206
- }
207
-
208
- const init: RequestInit = {
209
- method: request.method,
210
- headers: forwardHeaders,
211
- redirect: "manual",
212
- };
213
-
214
- if (request.method !== "GET" && request.method !== "HEAD") {
215
- init.body = request.body;
216
- // @ts-expect-error -- needed for streaming body in Workers
217
- init.duplex = "half";
218
- }
219
-
220
- // Route through the configured VTEX fetch so traces / metrics / logs
221
- // see the proxied origin call. The URL router classifies the call
222
- // into the right `vtex.<area>.<op>` bucket (e.g. `vtex.checkout.*`,
223
- // `vtex.vtexid.logout`, `vtex.io.segment`) — no per-callsite hint
224
- // needed because we're a generic forwarder.
225
- const originResponse = await getVtexFetch()(originUrl.toString(), init);
226
-
227
- const responseHeaders = filterHeaders(new Headers(originResponse.headers));
228
-
229
- proxySetCookie(
230
- originResponse.headers,
231
- responseHeaders,
232
- options?.rewriteCookieDomain !== false ? requestUrl.origin : undefined,
233
- );
234
-
235
- if (originResponse.status >= 300 && originResponse.status < 400) {
236
- const location = originResponse.headers.get("location");
237
- if (location) {
238
- const originVtexHost = vtexHost(environment, config);
239
- const storefrontOrigin = requestUrl.origin;
240
- const vtexOrigin = `https://${originVtexHost}`;
241
- const rewritten = location.replace(vtexOrigin, storefrontOrigin);
242
- responseHeaders.set("location", rewritten);
243
- }
244
- }
245
-
246
- if (options?.hardenAuthenticatedCache !== false) {
247
- hardenProxyCacheHeaders(responseHeaders, requestUrl.pathname);
248
- }
249
-
250
- return new Response(originResponse.body, {
251
- status: originResponse.status,
252
- statusText: originResponse.statusText,
253
- headers: responseHeaders,
254
- });
255
- }
256
-
257
- // ---------------------------------------------------------------------------
258
- // Production-grade checkout proxy factory
259
- // ---------------------------------------------------------------------------
260
-
261
- export interface VtexCheckoutProxyConfig {
262
- /** VTEX account name (e.g. "acme"). */
263
- account: string;
264
-
265
- /**
266
- * Store's public checkout domain (e.g. "secure.acme.com.br").
267
- * Checkout UI, /files/, and /_v/private/graphql are routed here.
268
- */
269
- checkoutOrigin: string;
270
-
271
- /**
272
- * VTEX commerce-stable origin for API calls.
273
- * @default `https://{account}.vtexcommercestable.com.br`
274
- */
275
- apiOrigin?: string;
276
-
277
- /**
278
- * myvtex origin — used for redirect rewriting.
279
- * @default `https://{account}.myvtex.com`
280
- */
281
- myvtexOrigin?: string;
282
-
283
- /**
284
- * VTEX TLD — most accounts use `.com.br`, but some use `.com`.
285
- * @default "com.br"
286
- */
287
- domain?: string;
288
-
289
- /**
290
- * Extra paths on which to force-expire cookies.
291
- * Useful for logout: VTEX sends Max-Age=0 for auth cookies, but the
292
- * checkout orderForm cookie sometimes survives. This appends explicit
293
- * Set-Cookie: name=; Max-Age=0 entries.
294
- */
295
- expireCookiesOnPaths?: Array<{
296
- pathPrefix: string;
297
- cookies: string[];
298
- }>;
299
-
300
- /**
301
- * Optional HTML transform for checkout pages.
302
- * Receives the full HTML string and should return the modified version.
303
- */
304
- htmlTransform?: (html: string) => string;
305
-
306
- /**
307
- * Force `Cache-Control: private, no-store` + `CDN-Cache-Control: no-store`
308
- * on authenticated proxy responses (everything except static assets), so a
309
- * personalized VTEX response can never be cached and served across users.
310
- * @default true
311
- */
312
- hardenAuthenticatedCache?: boolean;
313
- }
314
-
315
- const CF_INTERNAL_HEADERS = new Set([
316
- "cf-connecting-ip",
317
- "cf-ipcountry",
318
- "cf-ray",
319
- "cf-visitor",
320
- "cf-ew-via",
321
- "cdn-loop",
322
- ]);
323
-
324
- const CHECKOUT_SKIP_HEADERS = new Set([
325
- ...HOP_BY_HOP_HEADERS,
326
- "set-cookie",
327
- ...CF_INTERNAL_HEADERS,
328
- ]);
329
-
330
- const toAscii = (v: string) => v.replace(/[^\x20-\x7E]/g, "");
331
-
332
- function filterHeadersStrict(headers: Headers): Headers {
333
- const filtered = new Headers();
334
- headers.forEach((value, key) => {
335
- if (CHECKOUT_SKIP_HEADERS.has(key.toLowerCase())) return;
336
- try {
337
- filtered.set(key, toAscii(value));
338
- } catch {
339
- // skip headers that still fail after sanitization
340
- }
341
- });
342
- return filtered;
343
- }
344
-
345
- /**
346
- * Rewrite Set-Cookie headers: only change the Domain attribute.
347
- * Unlike `proxySetCookie`, this preserves ALL attributes (Max-Age,
348
- * Expires, SameSite, etc.) which is critical for logout.
349
- */
350
- function rewriteSetCookieDomain(from: Headers, to: Headers, toHostname: string) {
351
- const raw: string[] =
352
- typeof from.getSetCookie === "function"
353
- ? from.getSetCookie()
354
- : (from.get("set-cookie") ?? "").split(/,(?=[^ ]+=)/).filter(Boolean);
355
-
356
- for (const cookie of raw) {
357
- const rewritten = cookie.replace(/Domain=[^;]*/i, `Domain=${toHostname}`);
358
- to.append("Set-Cookie", rewritten);
359
- }
360
- }
361
-
362
- /**
363
- * Creates a production-grade VTEX checkout proxy handler.
364
- *
365
- * Routes checkout UI pages to the store's public domain and API calls
366
- * to vtexcommercestable. Properly rewrites Set-Cookie domains (preserving
367
- * Max-Age/Expires), sanitizes non-ASCII headers, filters hop-by-hop and
368
- * CF-internal headers, and rewrites Location redirects.
369
- *
370
- * Returns a function compatible with `createDecoWorkerEntry`'s `proxyHandler`.
371
- *
372
- * @example
373
- * ```ts
374
- * const vtexProxy = createVtexCheckoutProxy({
375
- * account: "acme",
376
- * checkoutOrigin: "secure.acme.com.br",
377
- * expireCookiesOnPaths: [
378
- * { pathPrefix: "/api/vtexid/pub/logout", cookies: ["checkout.vtex.com"] },
379
- * ],
380
- * htmlTransform: (html) =>
381
- * html.replace("</head>", "<style>.body{min-height:100vh}</style></head>"),
382
- * });
383
- *
384
- * createDecoWorkerEntry(serverEntry, {
385
- * proxyHandler: async (request, url) => {
386
- * if (url.pathname === "/login") return null;
387
- * if (!shouldProxyToVtex(url.pathname)) return null;
388
- * return vtexProxy(request, url);
389
- * },
390
- * });
391
- * ```
392
- */
393
- export function createVtexCheckoutProxy(
394
- config: VtexCheckoutProxyConfig,
395
- ): (request: Request, url: URL) => Promise<Response> {
396
- const domain = config.domain ?? "com.br";
397
- const checkoutOrigin = config.checkoutOrigin.startsWith("https://")
398
- ? config.checkoutOrigin
399
- : `https://${config.checkoutOrigin}`;
400
- const apiOrigin = config.apiOrigin ?? `https://${config.account}.vtexcommercestable.${domain}`;
401
- const myvtexOrigin = config.myvtexOrigin ?? `https://${config.account}.myvtex.com`;
402
-
403
- function getOrigin(pathname: string, method: string): string {
404
- if (
405
- pathname.startsWith("/checkout") ||
406
- pathname.startsWith("/account") ||
407
- pathname.startsWith("/_secure/account") ||
408
- pathname.startsWith("/files/") ||
409
- pathname.startsWith("/_v/private/graphql")
410
- ) {
411
- return checkoutOrigin;
412
- }
413
- if (method !== "GET" && method !== "HEAD" && pathname.startsWith("/_v/")) {
414
- return checkoutOrigin;
415
- }
416
- return apiOrigin;
417
- }
418
-
419
- return async (request: Request, url: URL): Promise<Response> => {
420
- const origin = getOrigin(url.pathname, request.method);
421
- const originUrl = new URL(`${origin}${url.pathname}${url.search}`);
422
- const fwd = filterHeadersStrict(new Headers(request.headers));
423
-
424
- fwd.set("Host", originUrl.hostname);
425
- fwd.set("X-Forwarded-Host", url.host);
426
- fwd.set("X-Forwarded-Proto", "https");
427
- fwd.set("origin", request.headers.get("origin") ?? url.origin);
428
-
429
- const isCheckoutUI =
430
- url.pathname.startsWith("/checkout") || url.pathname.startsWith("/account");
431
- const isLogout = url.pathname.startsWith("/api/vtexid/pub/logout");
432
-
433
- const init: RequestInit = {
434
- method: request.method,
435
- headers: fwd,
436
- redirect: isCheckoutUI || isLogout ? "manual" : "follow",
437
- };
438
- if (request.method !== "GET" && request.method !== "HEAD") {
439
- init.body = request.body;
440
- // @ts-expect-error -- needed for streaming body in Workers
441
- init.duplex = "half";
442
- }
443
-
444
- const originRes = await getVtexFetch()(originUrl.toString(), init);
445
- const resHeaders = filterHeadersStrict(new Headers(originRes.headers));
446
- rewriteSetCookieDomain(originRes.headers, resHeaders, url.hostname);
447
-
448
- // Force-expire cookies on configured paths
449
- if (config.expireCookiesOnPaths) {
450
- for (const rule of config.expireCookiesOnPaths) {
451
- if (url.pathname.startsWith(rule.pathPrefix)) {
452
- for (const name of rule.cookies) {
453
- resHeaders.append("Set-Cookie", `${name}=; Path=/; Max-Age=0; Domain=${url.hostname}`);
454
- }
455
- }
456
- }
457
- }
458
-
459
- // Rewrite redirect Location headers from VTEX domains to storefront
460
- if (originRes.status >= 300 && originRes.status < 400) {
461
- const loc = originRes.headers.get("location");
462
- if (loc) {
463
- resHeaders.set(
464
- "location",
465
- loc
466
- .replace(checkoutOrigin, url.origin)
467
- .replace(apiOrigin, url.origin)
468
- .replace(myvtexOrigin, url.origin),
469
- );
470
- }
471
- }
472
-
473
- if (config.hardenAuthenticatedCache !== false) {
474
- hardenProxyCacheHeaders(resHeaders, url.pathname);
475
- }
476
-
477
- // HTML transform for checkout pages
478
- const ct = originRes.headers.get("content-type") ?? "";
479
- if (config.htmlTransform && ct.includes("text/html")) {
480
- const html = await originRes.text();
481
- const patched = config.htmlTransform(html);
482
- return new Response(patched, {
483
- status: originRes.status,
484
- statusText: originRes.statusText,
485
- headers: resHeaders,
486
- });
487
- }
488
-
489
- return new Response(originRes.body, {
490
- status: originRes.status,
491
- statusText: originRes.statusText,
492
- headers: resHeaders,
493
- });
494
- };
495
- }