@decocms/start 1.3.1 → 1.3.3
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 +1 -1
- package/src/routes/cmsRoute.ts +7 -0
- package/src/sdk/workerEntry.ts +237 -20
package/package.json
CHANGED
package/src/routes/cmsRoute.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
getRequest,
|
|
28
28
|
getRequestHeader,
|
|
29
29
|
getRequestUrl,
|
|
30
|
+
setResponseHeader,
|
|
30
31
|
} from "@tanstack/react-start/server";
|
|
31
32
|
import { createElement } from "react";
|
|
32
33
|
import { preloadSectionComponents } from "../cms/registry";
|
|
@@ -227,6 +228,12 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
|
|
|
227
228
|
headers: originRequest.headers,
|
|
228
229
|
});
|
|
229
230
|
const enriched = await runSingleSectionLoader(section, request);
|
|
231
|
+
|
|
232
|
+
// Signal to the worker entry that this response is safe to edge-cache.
|
|
233
|
+
// Without this header, POST _serverFn responses are passed through
|
|
234
|
+
// without caching (checkout actions, invoke mutations, etc.).
|
|
235
|
+
setResponseHeader("X-Deco-Cacheable", "true");
|
|
236
|
+
|
|
230
237
|
return normalizeUrlsInObject(enriched);
|
|
231
238
|
});
|
|
232
239
|
|
package/src/sdk/workerEntry.ts
CHANGED
|
@@ -295,6 +295,63 @@ export interface DecoWorkerEntryOptions {
|
|
|
295
295
|
* @default true
|
|
296
296
|
*/
|
|
297
297
|
autoInjectGeoCookies?: boolean;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Cookie names considered "safe" for caching — these are public/anonymous
|
|
301
|
+
* cookies that do not carry per-user session or auth data.
|
|
302
|
+
*
|
|
303
|
+
* When a response contains ONLY safe cookies, it is still eligible for
|
|
304
|
+
* Cache API storage. The safe cookies are stripped from the cached copy
|
|
305
|
+
* but kept on the response served to the current user.
|
|
306
|
+
*
|
|
307
|
+
* If the response contains ANY cookie NOT in this list, the response
|
|
308
|
+
* bypasses caching entirely (existing behavior).
|
|
309
|
+
*
|
|
310
|
+
* @default DEFAULT_SAFE_COOKIES (vtex_is_session, vtex_is_anonymous, vtex_segment, _deco_bucket)
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* createDecoWorkerEntry(serverEntry, {
|
|
315
|
+
* safeCookies: [
|
|
316
|
+
* ...DEFAULT_SAFE_COOKIES,
|
|
317
|
+
* "my_custom_analytics_cookie",
|
|
318
|
+
* ],
|
|
319
|
+
* });
|
|
320
|
+
* ```
|
|
321
|
+
*/
|
|
322
|
+
safeCookies?: string[];
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Additional static paths (beyond fingerprinted assets) that should
|
|
326
|
+
* receive long-lived immutable cache headers.
|
|
327
|
+
*
|
|
328
|
+
* Useful for non-fingerprinted resources like fonts that live at
|
|
329
|
+
* stable URLs (e.g., `/fonts/Lato-Regular.woff2`).
|
|
330
|
+
*
|
|
331
|
+
* @default ["/fonts/"]
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* createDecoWorkerEntry(serverEntry, {
|
|
336
|
+
* staticPaths: ["/fonts/", "/static/", "/images/icons/"],
|
|
337
|
+
* });
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
staticPaths?: string[];
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* CDN-Cache-Control header strategy.
|
|
344
|
+
*
|
|
345
|
+
* - `"no-store"` (default): CDN never caches; every request invokes the Worker.
|
|
346
|
+
* Correct when segment-based cache keys differ from the original URL.
|
|
347
|
+
* - `"match-profile"`: Set CDN-Cache-Control to a short TTL matching the
|
|
348
|
+
* profile's edge.fresh value. Only safe when you are NOT using segment-based
|
|
349
|
+
* cache keys (i.e., no `buildSegment` and `deviceSpecificKeys: false`).
|
|
350
|
+
* - A function: Return a CDN-Cache-Control value per profile, or `null` for no-store.
|
|
351
|
+
*
|
|
352
|
+
* @default "no-store"
|
|
353
|
+
*/
|
|
354
|
+
cdnCacheControl?: "no-store" | "match-profile" | ((profile: CacheProfileName) => string | null);
|
|
298
355
|
}
|
|
299
356
|
|
|
300
357
|
// ---------------------------------------------------------------------------
|
|
@@ -381,6 +438,109 @@ export const DEFAULT_SECURITY_HEADERS: Record<string, string> = {
|
|
|
381
438
|
|
|
382
439
|
const DEFAULT_BYPASS_PATHS = ["/_build", "/deco/", "/live/", "/.decofile"];
|
|
383
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Cookie names that are safe for caching — they carry anonymous/public
|
|
443
|
+
* segment data, not per-user auth tokens.
|
|
444
|
+
*
|
|
445
|
+
* VTEX Intelligent Search sets `vtex_is_session` and `vtex_is_anonymous`
|
|
446
|
+
* on every response. `vtex_segment` encodes the sales channel.
|
|
447
|
+
* `_deco_bucket` is the A/B test cohort cookie.
|
|
448
|
+
*/
|
|
449
|
+
export const DEFAULT_SAFE_COOKIES: string[] = [
|
|
450
|
+
"vtex_is_session",
|
|
451
|
+
"vtex_is_anonymous",
|
|
452
|
+
"vtex_segment",
|
|
453
|
+
"_deco_bucket",
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
const DEFAULT_STATIC_PATHS = ["/fonts/"];
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Parse Set-Cookie header values and return cookie names.
|
|
460
|
+
*/
|
|
461
|
+
function parseCookieNames(response: Response): string[] {
|
|
462
|
+
const names: string[] = [];
|
|
463
|
+
// getSetCookie() returns individual Set-Cookie values (available in Workers runtime)
|
|
464
|
+
const setCookies = (response.headers as any).getSetCookie?.() as string[] | undefined;
|
|
465
|
+
if (setCookies) {
|
|
466
|
+
for (const sc of setCookies) {
|
|
467
|
+
const eqIdx = sc.indexOf("=");
|
|
468
|
+
if (eqIdx > 0) names.push(sc.slice(0, eqIdx).trim());
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
// Fallback: parse from combined header (less reliable but covers edge cases)
|
|
472
|
+
const combined = response.headers.get("set-cookie") ?? "";
|
|
473
|
+
for (const part of combined.split(",")) {
|
|
474
|
+
const eqIdx = part.indexOf("=");
|
|
475
|
+
if (eqIdx > 0) {
|
|
476
|
+
const name = part.slice(0, eqIdx).trim();
|
|
477
|
+
// Skip attributes like "Expires=..." that appear after semicolons
|
|
478
|
+
if (!name.includes(";") && name.length > 0) names.push(name);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return names;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Check if ALL cookies in a response are in the safe list.
|
|
487
|
+
* Returns true if the response has no cookies or only safe cookies.
|
|
488
|
+
*/
|
|
489
|
+
function hasOnlySafeCookies(response: Response, safeCookieSet: Set<string>): boolean {
|
|
490
|
+
if (!response.headers.has("set-cookie")) return true;
|
|
491
|
+
const names = parseCookieNames(response);
|
|
492
|
+
if (names.length === 0) return true;
|
|
493
|
+
return names.every((name) => safeCookieSet.has(name));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Clone a response, stripping Set-Cookie headers that match the safe list.
|
|
498
|
+
* Uses response.clone() to preserve the original body for the served response.
|
|
499
|
+
* The returned copy is intended for cache storage only.
|
|
500
|
+
*/
|
|
501
|
+
function stripSafeCookiesForCache(response: Response, safeCookieSet: Set<string>): Response {
|
|
502
|
+
const clone = response.clone();
|
|
503
|
+
const setCookies = (response.headers as any).getSetCookie?.() as string[] | undefined;
|
|
504
|
+
if (!setCookies || setCookies.length === 0) return clone;
|
|
505
|
+
|
|
506
|
+
// Remove all Set-Cookie headers, then re-add only unsafe ones
|
|
507
|
+
clone.headers.delete("set-cookie");
|
|
508
|
+
for (const sc of setCookies) {
|
|
509
|
+
const eqIdx = sc.indexOf("=");
|
|
510
|
+
const name = eqIdx > 0 ? sc.slice(0, eqIdx).trim() : "";
|
|
511
|
+
if (name && !safeCookieSet.has(name)) {
|
|
512
|
+
clone.headers.append("set-cookie", sc);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return clone;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Deduplicate Set-Cookie headers — keep only the LAST occurrence of
|
|
520
|
+
* each cookie name. Multiple layers (VTEX middleware, invoke handlers,
|
|
521
|
+
* etc.) may independently append the same cookie.
|
|
522
|
+
*/
|
|
523
|
+
function deduplicateSetCookies(response: Response): void {
|
|
524
|
+
const setCookies = (response.headers as any).getSetCookie?.() as string[] | undefined;
|
|
525
|
+
if (!setCookies || setCookies.length <= 1) return;
|
|
526
|
+
|
|
527
|
+
// Build map: cookie name → last Set-Cookie value
|
|
528
|
+
const seen = new Map<string, string>();
|
|
529
|
+
for (const sc of setCookies) {
|
|
530
|
+
const eqIdx = sc.indexOf("=");
|
|
531
|
+
const name = eqIdx > 0 ? sc.slice(0, eqIdx).trim() : sc;
|
|
532
|
+
seen.set(name, sc);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// If no duplicates, nothing to do
|
|
536
|
+
if (seen.size === setCookies.length) return;
|
|
537
|
+
|
|
538
|
+
response.headers.delete("set-cookie");
|
|
539
|
+
for (const sc of seen.values()) {
|
|
540
|
+
response.headers.append("set-cookie", sc);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
384
544
|
const FINGERPRINTED_ASSET_RE = /(?:\/_build)?\/assets\/.*-[a-zA-Z0-9_-]{8,}\.\w+$/;
|
|
385
545
|
|
|
386
546
|
const IMMUTABLE_HEADERS: Record<string, string> = {
|
|
@@ -430,8 +590,13 @@ export function createDecoWorkerEntry(
|
|
|
430
590
|
securityHeaders: securityHeadersOpt,
|
|
431
591
|
csp: cspOpt,
|
|
432
592
|
autoInjectGeoCookies: geoOpt = true,
|
|
593
|
+
safeCookies: safeCookiesOpt = DEFAULT_SAFE_COOKIES,
|
|
594
|
+
staticPaths: staticPathsOpt = DEFAULT_STATIC_PATHS,
|
|
595
|
+
cdnCacheControl: cdnCacheControlOpt = "no-store",
|
|
433
596
|
} = options;
|
|
434
597
|
|
|
598
|
+
const safeCookieSet = new Set(safeCookiesOpt);
|
|
599
|
+
|
|
435
600
|
// Build the final security headers map (merged defaults + custom + CSP)
|
|
436
601
|
const secHeaders: Record<string, string> | null = (() => {
|
|
437
602
|
if (securityHeadersOpt === false) return null;
|
|
@@ -465,7 +630,9 @@ export function createDecoWorkerEntry(
|
|
|
465
630
|
}
|
|
466
631
|
|
|
467
632
|
function isStaticAsset(pathname: string): boolean {
|
|
468
|
-
|
|
633
|
+
if (fingerprintedAssetPattern.test(pathname)) return true;
|
|
634
|
+
// Non-fingerprinted static paths (e.g., /fonts/)
|
|
635
|
+
return staticPathsOpt.some((sp) => pathname.startsWith(sp));
|
|
469
636
|
}
|
|
470
637
|
|
|
471
638
|
function isCacheable(request: Request, url: URL): boolean {
|
|
@@ -765,6 +932,10 @@ export function createDecoWorkerEntry(
|
|
|
765
932
|
return handleRequest(request, env, ctx);
|
|
766
933
|
});
|
|
767
934
|
|
|
935
|
+
// Deduplicate Set-Cookie headers — multiple layers (VTEX middleware,
|
|
936
|
+
// invoke handlers, etc.) may independently append the same cookie.
|
|
937
|
+
deduplicateSetCookies(response);
|
|
938
|
+
|
|
768
939
|
return applySecurityHeaders(response);
|
|
769
940
|
},
|
|
770
941
|
};
|
|
@@ -935,12 +1106,18 @@ export function createDecoWorkerEntry(
|
|
|
935
1106
|
try {
|
|
936
1107
|
const bgReq = new Request(request, { body, method: "POST" });
|
|
937
1108
|
const bgOrigin = await serverEntry.fetch(bgReq, env, ctx);
|
|
938
|
-
if (
|
|
1109
|
+
if (
|
|
1110
|
+
bgOrigin.status === 200 &&
|
|
1111
|
+
bgOrigin.headers.get("X-Deco-Cacheable") === "true" &&
|
|
1112
|
+
!bgOrigin.headers.has("set-cookie") &&
|
|
1113
|
+
serverFnCache
|
|
1114
|
+
) {
|
|
939
1115
|
const ttl = sfnEdge.fresh + Math.max(sfnEdge.swr, sfnEdge.sie);
|
|
940
1116
|
const toStore = bgOrigin.clone();
|
|
941
1117
|
toStore.headers.set("Cache-Control", `public, max-age=${ttl}`);
|
|
942
1118
|
toStore.headers.set("X-Deco-Stored-At", String(Date.now()));
|
|
943
1119
|
toStore.headers.delete("CDN-Cache-Control");
|
|
1120
|
+
toStore.headers.delete("X-Deco-Cacheable");
|
|
944
1121
|
await serverFnCache.put(sfnCacheKey, toStore);
|
|
945
1122
|
}
|
|
946
1123
|
} catch { /* background revalidation failed */ }
|
|
@@ -960,29 +1137,39 @@ export function createDecoWorkerEntry(
|
|
|
960
1137
|
const originReq = new Request(request, { body, method: "POST" });
|
|
961
1138
|
const origin = await serverEntry.fetch(originReq, env, ctx);
|
|
962
1139
|
|
|
963
|
-
//
|
|
964
|
-
|
|
1140
|
+
// Only cache responses explicitly marked as cacheable by the handler
|
|
1141
|
+
// (loadDeferredSection sets X-Deco-Cacheable: true). Checkout actions,
|
|
1142
|
+
// invoke mutations, and other server functions are passed through.
|
|
1143
|
+
const isCacheableResponse =
|
|
1144
|
+
origin.headers.get("X-Deco-Cacheable") === "true" &&
|
|
1145
|
+
!origin.headers.has("set-cookie") &&
|
|
1146
|
+
origin.status === 200;
|
|
1147
|
+
|
|
1148
|
+
if (!isCacheableResponse) {
|
|
965
1149
|
const resp = new Response(origin.body, origin);
|
|
966
|
-
resp.headers.
|
|
967
|
-
resp.headers.delete("CDN-Cache-Control");
|
|
1150
|
+
resp.headers.delete("X-Deco-Cacheable");
|
|
968
1151
|
resp.headers.set("X-Cache", "BYPASS");
|
|
969
|
-
resp.headers.set("X-Cache-Reason", "set-cookie")
|
|
1152
|
+
resp.headers.set("X-Cache-Reason", origin.headers.has("set-cookie")
|
|
1153
|
+
? "set-cookie"
|
|
1154
|
+
: "not-cacheable");
|
|
970
1155
|
return resp;
|
|
971
1156
|
}
|
|
972
1157
|
|
|
973
1158
|
// Store in edge cache
|
|
974
|
-
if (
|
|
1159
|
+
if (serverFnCache) {
|
|
975
1160
|
try {
|
|
976
1161
|
const ttl = sfnEdge.fresh + Math.max(sfnEdge.swr, sfnEdge.sie);
|
|
977
1162
|
const toStore = origin.clone();
|
|
978
1163
|
toStore.headers.set("Cache-Control", `public, max-age=${ttl}`);
|
|
979
1164
|
toStore.headers.set("X-Deco-Stored-At", String(Date.now()));
|
|
980
1165
|
toStore.headers.delete("CDN-Cache-Control");
|
|
1166
|
+
toStore.headers.delete("X-Deco-Cacheable");
|
|
981
1167
|
ctx.waitUntil(serverFnCache.put(sfnCacheKey, toStore));
|
|
982
1168
|
} catch { /* Cache API unavailable */ }
|
|
983
1169
|
}
|
|
984
1170
|
|
|
985
1171
|
const resp = new Response(origin.body, origin);
|
|
1172
|
+
resp.headers.delete("X-Deco-Cacheable");
|
|
986
1173
|
const hdrs = cacheHeaders(sfnProfile);
|
|
987
1174
|
for (const [k, v] of Object.entries(hdrs)) resp.headers.set(k, v);
|
|
988
1175
|
resp.headers.set("X-Cache", "MISS");
|
|
@@ -1006,13 +1193,14 @@ export function createDecoWorkerEntry(
|
|
|
1006
1193
|
|
|
1007
1194
|
const resp = new Response(origin.body, origin);
|
|
1008
1195
|
|
|
1009
|
-
// Responses with Set-Cookie carry per-user tokens —
|
|
1010
|
-
// them with public cache headers
|
|
1011
|
-
|
|
1196
|
+
// Responses with private Set-Cookie headers carry per-user tokens —
|
|
1197
|
+
// never expose them with public cache headers.
|
|
1198
|
+
// Safe/public cookies (e.g., vtex_is_session) are allowed through.
|
|
1199
|
+
if (origin.headers.has("set-cookie") && !hasOnlySafeCookies(origin, safeCookieSet)) {
|
|
1012
1200
|
resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
|
1013
1201
|
resp.headers.delete("CDN-Cache-Control");
|
|
1014
1202
|
resp.headers.set("X-Cache", "BYPASS");
|
|
1015
|
-
resp.headers.set("X-Cache-Reason", "set-cookie");
|
|
1203
|
+
resp.headers.set("X-Cache-Reason", "private-set-cookie");
|
|
1016
1204
|
return resp;
|
|
1017
1205
|
}
|
|
1018
1206
|
|
|
@@ -1057,7 +1245,22 @@ export function createDecoWorkerEntry(
|
|
|
1057
1245
|
const out = new Response(resp.body, resp);
|
|
1058
1246
|
const hdrs = cacheHeaders(profile);
|
|
1059
1247
|
for (const [k, v] of Object.entries(hdrs)) out.headers.set(k, v);
|
|
1060
|
-
|
|
1248
|
+
|
|
1249
|
+
// CDN-Cache-Control: controls Cloudflare's automatic CDN layer
|
|
1250
|
+
// (separate from Cache API which the worker manages directly).
|
|
1251
|
+
if (cdnCacheControlOpt === "no-store") {
|
|
1252
|
+
out.headers.set("CDN-Cache-Control", "no-store");
|
|
1253
|
+
} else if (cdnCacheControlOpt === "match-profile") {
|
|
1254
|
+
if (edgeConfig.isPublic && edgeConfig.fresh > 0) {
|
|
1255
|
+
out.headers.set("CDN-Cache-Control", `public, max-age=${edgeConfig.fresh}`);
|
|
1256
|
+
} else {
|
|
1257
|
+
out.headers.set("CDN-Cache-Control", "no-store");
|
|
1258
|
+
}
|
|
1259
|
+
} else if (typeof cdnCacheControlOpt === "function") {
|
|
1260
|
+
const val = cdnCacheControlOpt(profile);
|
|
1261
|
+
out.headers.set("CDN-Cache-Control", val ?? "no-store");
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1061
1264
|
out.headers.set("X-Cache", xCache);
|
|
1062
1265
|
out.headers.set("X-Cache-Profile", profile);
|
|
1063
1266
|
if (segment) out.headers.set("X-Cache-Segment", hashSegment(segment));
|
|
@@ -1089,8 +1292,15 @@ export function createDecoWorkerEntry(
|
|
|
1089
1292
|
function revalidateInBackground() {
|
|
1090
1293
|
ctx.waitUntil(
|
|
1091
1294
|
Promise.resolve(serverEntry.fetch(request, env, ctx)).then((origin) => {
|
|
1092
|
-
if (origin.status === 200
|
|
1093
|
-
|
|
1295
|
+
if (origin.status === 200) {
|
|
1296
|
+
// Only cache if response has no cookies or only safe cookies.
|
|
1297
|
+
// Strip safe cookies from the cached copy.
|
|
1298
|
+
if (hasOnlySafeCookies(origin, safeCookieSet)) {
|
|
1299
|
+
const cleanOrigin = origin.headers.has("set-cookie")
|
|
1300
|
+
? stripSafeCookiesForCache(origin, safeCookieSet)
|
|
1301
|
+
: origin;
|
|
1302
|
+
storeInCache(cleanOrigin);
|
|
1303
|
+
}
|
|
1094
1304
|
}
|
|
1095
1305
|
}).catch(() => {
|
|
1096
1306
|
// Background revalidation failed — stale entry stays until SIE expires
|
|
@@ -1170,14 +1380,16 @@ export function createDecoWorkerEntry(
|
|
|
1170
1380
|
return resp;
|
|
1171
1381
|
}
|
|
1172
1382
|
|
|
1173
|
-
// Responses with Set-Cookie must never be cached —
|
|
1174
|
-
// per-user session/auth tokens that would leak to other users.
|
|
1175
|
-
|
|
1383
|
+
// Responses with private Set-Cookie headers must never be cached —
|
|
1384
|
+
// they carry per-user session/auth tokens that would leak to other users.
|
|
1385
|
+
// Safe/public cookies (IS session, segment, etc.) are stripped from the
|
|
1386
|
+
// cached copy but kept on the response served to the current user.
|
|
1387
|
+
if (origin.headers.has("set-cookie") && !hasOnlySafeCookies(origin, safeCookieSet)) {
|
|
1176
1388
|
const resp = new Response(origin.body, origin);
|
|
1177
1389
|
resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
|
1178
1390
|
resp.headers.delete("CDN-Cache-Control");
|
|
1179
1391
|
resp.headers.set("X-Cache", "BYPASS");
|
|
1180
|
-
resp.headers.set("X-Cache-Reason", "set-cookie");
|
|
1392
|
+
resp.headers.set("X-Cache-Reason", "private-set-cookie");
|
|
1181
1393
|
appendResourceHints(resp);
|
|
1182
1394
|
return resp;
|
|
1183
1395
|
}
|
|
@@ -1197,7 +1409,12 @@ export function createDecoWorkerEntry(
|
|
|
1197
1409
|
// dressResponse() calls new Response(resp.body, resp) which locks
|
|
1198
1410
|
// the ReadableStream. Calling clone() on a locked body corrupts
|
|
1199
1411
|
// the stream in Workers runtime, causing Error 1101.
|
|
1200
|
-
|
|
1412
|
+
// Strip safe cookies from the cached copy so they don't leak
|
|
1413
|
+
// to other users, but the current user still gets them.
|
|
1414
|
+
const cacheOrigin = origin.headers.has("set-cookie")
|
|
1415
|
+
? stripSafeCookiesForCache(origin, safeCookieSet)
|
|
1416
|
+
: origin;
|
|
1417
|
+
storeInCache(cacheOrigin);
|
|
1201
1418
|
return dressResponse(origin, "MISS");
|
|
1202
1419
|
}
|
|
1203
1420
|
}
|