@decocms/apps-vtex 7.55.2 → 7.56.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/apps-vtex",
3
- "version": "7.55.2",
3
+ "version": "7.56.0",
4
4
  "type": "module",
5
5
  "description": "Deco commerce app: VTEX integration",
6
6
  "repository": {
@@ -51,10 +51,10 @@
51
51
  "lint:unused": "knip"
52
52
  },
53
53
  "dependencies": {
54
- "@decocms/blocks": "7.55.2",
55
- "@decocms/apps-commerce": "7.55.2",
56
- "@decocms/apps-website": "7.55.2",
57
- "@decocms/tanstack": "7.55.2"
54
+ "@decocms/blocks": "7.56.0",
55
+ "@decocms/apps-commerce": "7.56.0",
56
+ "@decocms/apps-website": "7.56.0",
57
+ "@decocms/tanstack": "7.56.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@tanstack/react-query": ">=5.0.0",
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { vtexMiddleware } from "./mod";
3
+
4
+ /**
5
+ * The VTEX app middleware wraps the framework's entire edge-cache layer, so it
6
+ * is the last writer of Cache-Control on every response — including cache HITs.
7
+ * These tests pin the two things that made that dangerous.
8
+ */
9
+
10
+ // A response as the framework's cache layer would hand it over: public headers
11
+ // resolved from the page's cache profile, plus the CDN header.
12
+ function cachedResponse(): Response {
13
+ return new Response("page", {
14
+ headers: {
15
+ "Cache-Control": "public, max-age=120, s-maxage=900, stale-while-revalidate=1800",
16
+ "CDN-Cache-Control": "public, max-age=900",
17
+ },
18
+ });
19
+ }
20
+
21
+ const next = async () => cachedResponse();
22
+
23
+ describe("vtexMiddleware cache headers", () => {
24
+ it("leaves the cache layer's headers alone for an anonymous request", async () => {
25
+ const res = await vtexMiddleware(new Request("https://store.com/"), next);
26
+ // Used to be downgraded to vtexCacheControl's generic `s-maxage=60`,
27
+ // throwing away the profile the cache layer had resolved.
28
+ expect(res.headers.get("Cache-Control")).toContain("s-maxage=900");
29
+ });
30
+
31
+ it("forces private headers and clears the CDN header when logged in", async () => {
32
+ const req = new Request("https://store.com/", {
33
+ // extractVtexContext treats any VtexIdclientAutCookie* as authenticated.
34
+ headers: { cookie: "VtexIdclientAutCookie_store=abc123" },
35
+ });
36
+ const res = await vtexMiddleware(req, next);
37
+
38
+ expect(res.headers.get("Cache-Control")).toContain("no-store");
39
+ // Cloudflare gives CDN-Cache-Control precedence, so leaving the public
40
+ // value behind would cache a personalized page at the CDN regardless of
41
+ // the private Cache-Control next to it.
42
+ expect(res.headers.get("CDN-Cache-Control")).toBeNull();
43
+ });
44
+ });
package/src/mod.ts CHANGED
@@ -96,10 +96,26 @@ export interface VtexState {
96
96
  // Middleware
97
97
  // -------------------------------------------------------------------------
98
98
 
99
- const vtexMiddleware: AppMiddleware = async (request, next) => {
99
+ export const vtexMiddleware: AppMiddleware = async (request, next) => {
100
100
  const ctx = extractVtexContext(request);
101
101
  const response = await next();
102
- response.headers.set("Cache-Control", vtexCacheControl(ctx));
102
+
103
+ // This middleware wraps the framework's whole edge-cache layer, so it is the
104
+ // LAST writer of Cache-Control — including on a cache HIT. It used to
105
+ // overwrite unconditionally, which downgraded a home page the cache layer had
106
+ // resolved as `s-maxage=900` to vtexCacheControl's generic `s-maxage=60`.
107
+ //
108
+ // Now it only speaks up for the case it actually knows better about: a
109
+ // personalized request, which must not be cached anywhere. And when it does,
110
+ // it clears CDN-Cache-Control too — otherwise a response can go out as
111
+ // `Cache-Control: private, no-store` alongside `CDN-Cache-Control: public,
112
+ // max-age=300`, and Cloudflare gives the CDN header precedence. Same pairing
113
+ // the worker's own bypasses use, and utils/proxy.ts's hardenProxyCacheHeaders.
114
+ if (ctx.isLoggedIn || ctx.hasCustomPricing) {
115
+ response.headers.set("Cache-Control", vtexCacheControl(ctx));
116
+ response.headers.delete("CDN-Cache-Control");
117
+ }
118
+
103
119
  propagateISCookies(ctx, response);
104
120
  return response;
105
121
  };