@decocms/apps-vtex 8.0.0 → 8.1.0-next.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/package.json +5 -5
- package/src/__conformance__/upstream.test.ts +158 -0
- package/src/__tests__/client-set-cookie-forward.test.ts +4 -4
- package/src/client.ts +61 -4
- package/src/index.ts +17 -2
- package/src/loaders/__tests__/legacyProductList.test.ts +157 -0
- package/src/loaders/autocomplete.ts +2 -4
- package/src/loaders/intelligentSearch/productDetailsPage.ts +2 -4
- package/src/loaders/intelligentSearch/productList.ts +21 -5
- package/src/loaders/intelligentSearch/productListingPage.ts +2 -3
- package/src/loaders/intelligentSearch/suggestions.ts +2 -4
- package/src/loaders/legacy/relatedProductsLoader.ts +2 -4
- package/src/loaders/legacy.ts +77 -2
- package/src/loaders/productListFull.ts +2 -4
- package/src/loaders/workflow/products.ts +2 -4
- package/src/middleware.cacheHeaders.test.ts +44 -0
- package/src/mod.ts +18 -2
- package/src/utils/__tests__/cookieSanitizer.test.ts +1 -1
- package/src/utils/__tests__/storefrontBaseUrl.test.ts +61 -0
- package/src/utils/__tests__/transform.test.ts +349 -7
- package/src/utils/buildOfferShelf.test.ts +59 -0
- package/src/utils/enrichment.ts +3 -7
- package/src/utils/fetch.ts +5 -1
- package/src/utils/fetchCache.ts +24 -168
- package/src/utils/instrumentedFetch.ts +2 -1
- package/src/utils/minicart.ts +1 -1
- package/src/utils/operationRouter.ts +3 -9
- package/src/utils/proxy.ts +64 -4
- package/src/utils/similars.ts +2 -4
- package/src/utils/sitemap.ts +3 -2
- package/src/utils/transform.ts +222 -53
- package/src/utils/vtexId.ts +15 -2
- package/src/vtexClient.test.ts +204 -0
- package/src/vtexClient.ts +433 -0
package/src/utils/fetchCache.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SWR
|
|
2
|
+
* VTEX SWR fetch cache — a thin binding over the shared, instrumented
|
|
3
|
+
* `createFetchCache` in `@decocms/blocks/sdk/fetchCache`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The implementation (in-flight dedup, stale-while-revalidate, stale-if-error,
|
|
6
|
+
* inflight backstop) now lives ONCE in the framework so every commerce app
|
|
7
|
+
* shares it and emits `deco.cache.requests{layer="swr"}` automatically. This
|
|
8
|
+
* module just wires VTEX's tuning constants (`./constants`) and the
|
|
9
|
+
* `provider: "vtex"` label into a single module-level instance, preserving the
|
|
10
|
+
* public surface (`fetchWithCache` / `clearFetchCache` / `getFetchCacheStats` /
|
|
11
|
+
* `FetchCacheOptions`) that `client.ts` and existing tests depend on.
|
|
8
12
|
*/
|
|
9
13
|
|
|
14
|
+
import {
|
|
15
|
+
createFetchCache,
|
|
16
|
+
type FetchCacheOptions as SharedFetchCacheOptions,
|
|
17
|
+
} from "@decocms/blocks/sdk/fetchCache";
|
|
10
18
|
import {
|
|
11
19
|
FETCH_CACHE_FRESH_TTL_MS,
|
|
12
20
|
FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
@@ -14,186 +22,34 @@ import {
|
|
|
14
22
|
FETCH_CACHE_STALE_IF_ERROR_MS,
|
|
15
23
|
} from "./constants";
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
body: unknown;
|
|
19
|
-
status: number;
|
|
20
|
-
createdAt: number;
|
|
21
|
-
refreshing: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function freshTtlForStatus(status: number): number {
|
|
25
|
-
if (status >= 200 && status < 300) return FETCH_CACHE_FRESH_TTL_MS.success;
|
|
26
|
-
if (status === 404) return FETCH_CACHE_FRESH_TTL_MS.notFound;
|
|
27
|
-
if (status >= 500) return FETCH_CACHE_FRESH_TTL_MS.serverError;
|
|
28
|
-
return 0;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const store = new Map<string, CacheEntry>();
|
|
32
|
-
const inflight = new Map<string, Promise<CacheEntry>>();
|
|
33
|
-
|
|
34
|
-
function evictIfNeeded() {
|
|
35
|
-
if (store.size <= FETCH_CACHE_MAX_ENTRIES) return;
|
|
36
|
-
const sorted = [...store.entries()].sort((a, b) => a[1].createdAt - b[1].createdAt);
|
|
37
|
-
const toRemove = sorted.slice(0, store.size - FETCH_CACHE_MAX_ENTRIES);
|
|
38
|
-
for (const [key] of toRemove) store.delete(key);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Race a Promise against a timeout so callers' `.finally()` always runs.
|
|
43
|
-
* Critical for evicting the inflight Map entry when a `fetch()` hangs —
|
|
44
|
-
* without this, a never-settling Promise leaks the Map slot forever and
|
|
45
|
-
* every subsequent request for the same key joins the zombie Promise.
|
|
46
|
-
*/
|
|
47
|
-
function withTimeout<T>(work: Promise<T>, ms: number, label: string): Promise<T> {
|
|
48
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
49
|
-
const timeout = new Promise<never>((_, reject) => {
|
|
50
|
-
timer = setTimeout(() => {
|
|
51
|
-
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
52
|
-
}, ms);
|
|
53
|
-
});
|
|
54
|
-
return Promise.race([work, timeout]).finally(() => {
|
|
55
|
-
clearTimeout(timer);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function executeFetch(url: string, doFetch: () => Promise<Response>): Promise<CacheEntry> {
|
|
60
|
-
// Single attempt on purpose. The resilience layer (`createResilientFetch`,
|
|
61
|
-
// wired as the VTEX fetch's baseFetch) owns retries, backoff+jitter, the
|
|
62
|
-
// per-host retry budget, and the circuit breaker. Retrying here would:
|
|
63
|
-
// - double-retry network errors (resilience 3× × fetchCache 3× = up to 9
|
|
64
|
-
// upstream calls per logical request — the retry storm the budget
|
|
65
|
-
// prevents), and
|
|
66
|
-
// - re-enter the breaker on every 5xx retry, opening it ~3× too fast.
|
|
67
|
-
const response = await doFetch();
|
|
25
|
+
export type FetchCacheOptions = SharedFetchCacheOptions;
|
|
68
26
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
status: response.status,
|
|
77
|
-
createdAt: Date.now(),
|
|
78
|
-
refreshing: false,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface FetchCacheOptions {
|
|
83
|
-
/**
|
|
84
|
-
* Custom TTL in ms. If provided, overrides status-based TTL.
|
|
85
|
-
*/
|
|
86
|
-
ttl?: number;
|
|
87
|
-
/**
|
|
88
|
-
* Stale-if-error window in ms. How long past the freshness TTL a last-good
|
|
89
|
-
* entry may still be served while the origin is failing. Defaults to
|
|
90
|
-
* {@link FETCH_CACHE_STALE_IF_ERROR_MS} (24h). Set to 0 to disable stale
|
|
91
|
-
* serving.
|
|
92
|
-
*/
|
|
93
|
-
sieMs?: number;
|
|
94
|
-
}
|
|
27
|
+
const cache = createFetchCache({
|
|
28
|
+
provider: "vtex",
|
|
29
|
+
maxEntries: FETCH_CACHE_MAX_ENTRIES,
|
|
30
|
+
freshTtlMs: FETCH_CACHE_FRESH_TTL_MS,
|
|
31
|
+
staleIfErrorMs: FETCH_CACHE_STALE_IF_ERROR_MS,
|
|
32
|
+
inflightBackstopMs: FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
33
|
+
});
|
|
95
34
|
|
|
96
35
|
/**
|
|
97
36
|
* Wrap a GET fetch call with SWR caching and in-flight dedup.
|
|
98
37
|
*
|
|
99
38
|
* Returns `null` for non-2xx responses that are cached (e.g. 404).
|
|
100
39
|
* 5xx responses throw so the caller can handle them explicitly.
|
|
101
|
-
*
|
|
102
|
-
* @param cacheKey - Unique key (typically the full URL)
|
|
103
|
-
* @param doFetch - The actual fetch call to execute
|
|
104
|
-
* @param opts - Optional overrides
|
|
105
|
-
* @returns Parsed JSON body, or null for cacheable error responses (e.g. 404)
|
|
106
40
|
*/
|
|
107
41
|
export function fetchWithCache<T>(
|
|
108
42
|
cacheKey: string,
|
|
109
43
|
doFetch: () => Promise<Response>,
|
|
110
44
|
opts?: FetchCacheOptions,
|
|
111
45
|
): Promise<T | null> {
|
|
112
|
-
|
|
113
|
-
const entry = store.get(cacheKey);
|
|
114
|
-
|
|
115
|
-
if (entry) {
|
|
116
|
-
const maxAge = opts?.ttl ?? freshTtlForStatus(entry.status);
|
|
117
|
-
const age = now - entry.createdAt;
|
|
118
|
-
const isStale = age > maxAge;
|
|
119
|
-
|
|
120
|
-
if (!isStale) return Promise.resolve(entry.body as T | null);
|
|
121
|
-
|
|
122
|
-
// Beyond the stale-if-error window the last-good entry is too old to keep
|
|
123
|
-
// serving during an outage: drop it and fall through to a foreground
|
|
124
|
-
// refetch (cold path). On a healthy origin this is never reached because
|
|
125
|
-
// the background refresh below keeps resetting `createdAt`.
|
|
126
|
-
const sieMs = opts?.sieMs ?? FETCH_CACHE_STALE_IF_ERROR_MS;
|
|
127
|
-
const tooStale = age > maxAge + sieMs;
|
|
128
|
-
|
|
129
|
-
if (!tooStale) {
|
|
130
|
-
if (!entry.refreshing) {
|
|
131
|
-
entry.refreshing = true;
|
|
132
|
-
// Background refresh: no retry — stale data is already being served.
|
|
133
|
-
// Timeout guards against a hung VTEX response leaving `refreshing`
|
|
134
|
-
// stuck true forever (which would silently disable revalidation).
|
|
135
|
-
withTimeout(
|
|
136
|
-
executeFetch(cacheKey, doFetch),
|
|
137
|
-
FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
138
|
-
`fetchCache stale-refresh ${cacheKey}`,
|
|
139
|
-
)
|
|
140
|
-
.then((fresh) => {
|
|
141
|
-
const ttl = opts?.ttl ?? freshTtlForStatus(fresh.status);
|
|
142
|
-
const existingWasSuccess = entry.status >= 200 && entry.status < 300;
|
|
143
|
-
const freshIsError = fresh.status >= 400;
|
|
144
|
-
const wouldDowngrade = existingWasSuccess && freshIsError;
|
|
145
|
-
if (ttl > 0 && !wouldDowngrade) {
|
|
146
|
-
store.set(cacheKey, fresh);
|
|
147
|
-
} else {
|
|
148
|
-
entry.refreshing = false;
|
|
149
|
-
}
|
|
150
|
-
})
|
|
151
|
-
.catch(() => {
|
|
152
|
-
entry.refreshing = false;
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// Serve last-good while it is within the SIE window (stale-if-error).
|
|
156
|
-
return Promise.resolve(entry.body as T | null);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
store.delete(cacheKey);
|
|
160
|
-
// fall through to the cold path below with the dead entry removed
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const existing = inflight.get(cacheKey);
|
|
164
|
-
if (existing) return existing.then((e) => e.body as T | null);
|
|
165
|
-
|
|
166
|
-
// Wrap with a timeout so the `.finally()` below always runs and evicts
|
|
167
|
-
// the inflight slot — even if `executeFetch` never settles. See the
|
|
168
|
-
// FETCH_CACHE_INFLIGHT_BACKSTOP_MS doc comment (constants.ts) for the leak
|
|
169
|
-
// this guards against.
|
|
170
|
-
const promise = withTimeout(
|
|
171
|
-
executeFetch(cacheKey, doFetch),
|
|
172
|
-
FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
173
|
-
`fetchCache ${cacheKey}`,
|
|
174
|
-
)
|
|
175
|
-
.then((fresh) => {
|
|
176
|
-
const ttl = opts?.ttl ?? freshTtlForStatus(fresh.status);
|
|
177
|
-
if (ttl > 0) {
|
|
178
|
-
store.set(cacheKey, fresh);
|
|
179
|
-
evictIfNeeded();
|
|
180
|
-
}
|
|
181
|
-
return fresh;
|
|
182
|
-
})
|
|
183
|
-
.finally(() => inflight.delete(cacheKey));
|
|
184
|
-
|
|
185
|
-
inflight.set(cacheKey, promise);
|
|
186
|
-
return promise.then((e) => e.body as T | null);
|
|
46
|
+
return cache.fetchWithCache<T>(cacheKey, doFetch, opts);
|
|
187
47
|
}
|
|
188
48
|
|
|
189
49
|
export function clearFetchCache() {
|
|
190
|
-
|
|
191
|
-
inflight.clear();
|
|
50
|
+
cache.clear();
|
|
192
51
|
}
|
|
193
52
|
|
|
194
53
|
export function getFetchCacheStats() {
|
|
195
|
-
return
|
|
196
|
-
entries: store.size,
|
|
197
|
-
inflight: inflight.size,
|
|
198
|
-
};
|
|
54
|
+
return cache.getStats();
|
|
199
55
|
}
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* behavior.
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
|
+
import type { FetchFn } from "@decocms/blocks/sdk/fetchTimeout";
|
|
33
34
|
import {
|
|
34
35
|
createInstrumentedFetch,
|
|
35
36
|
type InstrumentedFetch,
|
|
@@ -45,7 +46,7 @@ export interface CreateVtexFetchOptions {
|
|
|
45
46
|
* or routes through a proxy) to preserve its behavior while adding
|
|
46
47
|
* the VTEX instrumentation layer on top.
|
|
47
48
|
*/
|
|
48
|
-
baseFetch?:
|
|
49
|
+
baseFetch?: FetchFn;
|
|
49
50
|
/**
|
|
50
51
|
* Disable the `http.client.request.duration` histogram emission for
|
|
51
52
|
* VTEX calls. The framework's span and structured logs still emit.
|
package/src/utils/minicart.ts
CHANGED
|
@@ -71,7 +71,7 @@ function vtexItemToMinicartItem(item: OrderFormItem, index: number, coupon?: str
|
|
|
71
71
|
|
|
72
72
|
return {
|
|
73
73
|
// AnalyticsItem identifier — VTEX uses productId; sites map to numeric SKU
|
|
74
|
-
// when needed via `Number(item.item_id)` (see
|
|
74
|
+
// when needed via `Number(item.item_id)` (see a production Minicart).
|
|
75
75
|
item_id: item.id,
|
|
76
76
|
item_group_id: item.productId,
|
|
77
77
|
item_name: item.name ?? item.skuName ?? "",
|
|
@@ -118,16 +118,10 @@ const MATCHERS: ReadonlyArray<Matcher> = [
|
|
|
118
118
|
* });
|
|
119
119
|
* ```
|
|
120
120
|
*/
|
|
121
|
+
import { extractPathname } from "@decocms/blocks/sdk/urlUtils";
|
|
122
|
+
|
|
121
123
|
export function vtexOperationRouter(url: string, method: string): string | undefined {
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
pathname = new URL(url).pathname;
|
|
125
|
-
} catch {
|
|
126
|
-
const qs = url.indexOf("?");
|
|
127
|
-
const hash = url.indexOf("#");
|
|
128
|
-
const end = [qs, hash].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
129
|
-
pathname = end === undefined ? url : url.slice(0, end);
|
|
130
|
-
}
|
|
124
|
+
const pathname = extractPathname(url);
|
|
131
125
|
|
|
132
126
|
const upperMethod = method.toUpperCase();
|
|
133
127
|
for (const { pattern, operation } of MATCHERS) {
|
package/src/utils/proxy.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface VtexProxyOptions {
|
|
|
45
45
|
* Custom headers to inject into every proxied request.
|
|
46
46
|
*/
|
|
47
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;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
const DEFAULT_PROXY_PATHS = [
|
|
@@ -79,6 +87,42 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
79
87
|
"upgrade",
|
|
80
88
|
]);
|
|
81
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
|
+
|
|
82
126
|
/**
|
|
83
127
|
* Returns all path prefixes that should be proxied to VTEX.
|
|
84
128
|
*/
|
|
@@ -199,6 +243,10 @@ export async function proxyToVtex(request: Request, options?: VtexProxyOptions):
|
|
|
199
243
|
}
|
|
200
244
|
}
|
|
201
245
|
|
|
246
|
+
if (options?.hardenAuthenticatedCache !== false) {
|
|
247
|
+
hardenProxyCacheHeaders(responseHeaders, requestUrl.pathname);
|
|
248
|
+
}
|
|
249
|
+
|
|
202
250
|
return new Response(originResponse.body, {
|
|
203
251
|
status: originResponse.status,
|
|
204
252
|
statusText: originResponse.statusText,
|
|
@@ -211,11 +259,11 @@ export async function proxyToVtex(request: Request, options?: VtexProxyOptions):
|
|
|
211
259
|
// ---------------------------------------------------------------------------
|
|
212
260
|
|
|
213
261
|
export interface VtexCheckoutProxyConfig {
|
|
214
|
-
/** VTEX account name (e.g. "
|
|
262
|
+
/** VTEX account name (e.g. "acme"). */
|
|
215
263
|
account: string;
|
|
216
264
|
|
|
217
265
|
/**
|
|
218
|
-
* Store's public checkout domain (e.g. "secure.
|
|
266
|
+
* Store's public checkout domain (e.g. "secure.acme.com.br").
|
|
219
267
|
* Checkout UI, /files/, and /_v/private/graphql are routed here.
|
|
220
268
|
*/
|
|
221
269
|
checkoutOrigin: string;
|
|
@@ -254,6 +302,14 @@ export interface VtexCheckoutProxyConfig {
|
|
|
254
302
|
* Receives the full HTML string and should return the modified version.
|
|
255
303
|
*/
|
|
256
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;
|
|
257
313
|
}
|
|
258
314
|
|
|
259
315
|
const CF_INTERNAL_HEADERS = new Set([
|
|
@@ -316,8 +372,8 @@ function rewriteSetCookieDomain(from: Headers, to: Headers, toHostname: string)
|
|
|
316
372
|
* @example
|
|
317
373
|
* ```ts
|
|
318
374
|
* const vtexProxy = createVtexCheckoutProxy({
|
|
319
|
-
* account: "
|
|
320
|
-
* checkoutOrigin: "secure.
|
|
375
|
+
* account: "acme",
|
|
376
|
+
* checkoutOrigin: "secure.acme.com.br",
|
|
321
377
|
* expireCookiesOnPaths: [
|
|
322
378
|
* { pathPrefix: "/api/vtexid/pub/logout", cookies: ["checkout.vtex.com"] },
|
|
323
379
|
* ],
|
|
@@ -414,6 +470,10 @@ export function createVtexCheckoutProxy(
|
|
|
414
470
|
}
|
|
415
471
|
}
|
|
416
472
|
|
|
473
|
+
if (config.hardenAuthenticatedCache !== false) {
|
|
474
|
+
hardenProxyCacheHeaders(resHeaders, url.pathname);
|
|
475
|
+
}
|
|
476
|
+
|
|
417
477
|
// HTML transform for checkout pages
|
|
418
478
|
const ct = originRes.headers.get("content-type") ?? "";
|
|
419
479
|
if (config.htmlTransform && ct.includes("text/html")) {
|
package/src/utils/similars.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Product } from "@decocms/apps-commerce/types";
|
|
2
|
-
import { getVtexConfig, vtexFetch } from "../client";
|
|
2
|
+
import { getVtexConfig, storefrontBaseUrl, vtexFetch } from "../client";
|
|
3
3
|
import { pickSku, toProduct } from "./transform";
|
|
4
4
|
import type { LegacyProduct } from "./types";
|
|
5
5
|
|
|
@@ -18,9 +18,7 @@ export const withIsSimilarTo = async (product: Product): Promise<Product> => {
|
|
|
18
18
|
if (!rawSimilars?.length) return product;
|
|
19
19
|
|
|
20
20
|
const config = getVtexConfig();
|
|
21
|
-
const baseUrl = config
|
|
22
|
-
? `https://${config.publicUrl}`
|
|
23
|
-
: `https://${config.account}.vtexcommercestable.${config.domain ?? "com.br"}`;
|
|
21
|
+
const baseUrl = storefrontBaseUrl(config);
|
|
24
22
|
|
|
25
23
|
const similars = rawSimilars.map((p) => {
|
|
26
24
|
const sku = pickSku(p);
|
package/src/utils/sitemap.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* needs to expose VTEX's existing crawl tree to the public hostname.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { type FetchFn, withFetchTimeout } from "@decocms/blocks/sdk/fetchTimeout";
|
|
14
15
|
import { getVtexConfig, vtexFetchResponse, vtexHost } from "../client";
|
|
15
16
|
|
|
16
17
|
export interface SitemapEntry {
|
|
@@ -185,7 +186,7 @@ export interface VtexSitemapProxyConfig {
|
|
|
185
186
|
* Optional fetch override — primarily for tests. Defaults to the
|
|
186
187
|
* platform `fetch`.
|
|
187
188
|
*/
|
|
188
|
-
fetchImpl?:
|
|
189
|
+
fetchImpl?: FetchFn;
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
const DEFAULT_SITEMAP_CACHE_CONTROL = "public, s-maxage=3600, stale-while-revalidate=86400";
|
|
@@ -238,7 +239,7 @@ export function createVtexSitemapProxy(
|
|
|
238
239
|
const environment = config.environment ?? "vtexcommercestable";
|
|
239
240
|
const cacheControl = config.cacheControl ?? DEFAULT_SITEMAP_CACHE_CONTROL;
|
|
240
241
|
const extraSitemaps = config.extraSitemaps ?? [];
|
|
241
|
-
const fetchImpl = config.fetchImpl ??
|
|
242
|
+
const fetchImpl = config.fetchImpl ?? withFetchTimeout();
|
|
242
243
|
|
|
243
244
|
return async (_request: Request, url: URL): Promise<Response | null> => {
|
|
244
245
|
if (!isVtexSitemapPath(url.pathname)) return null;
|