@decocms/start 5.4.1 → 5.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "5.4.1",
3
+ "version": "5.4.2",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,106 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { injectGeoCookies } from "./workerEntry";
3
+
4
+ function parseCookies(header: string): Record<string, string> {
5
+ return Object.fromEntries(
6
+ header.split("; ").map((c) => {
7
+ const [k, ...v] = c.split("=");
8
+ return [k, v.join("=")];
9
+ }),
10
+ );
11
+ }
12
+
13
+ function makeRequest(
14
+ cf: Record<string, string> | undefined,
15
+ headers: Record<string, string> = {},
16
+ ): Request {
17
+ const req = new Request("https://example.com/", { headers });
18
+ if (cf) {
19
+ Object.defineProperty(req, "cf", { value: cf, configurable: true });
20
+ }
21
+ return req;
22
+ }
23
+
24
+ describe("injectGeoCookies", () => {
25
+ it("strips cf-region from the outgoing Request headers while preserving the value in __cf_geo_region cookie", () => {
26
+ const req = makeRequest(
27
+ { region: "São Paulo", country: "BR" },
28
+ { "cf-region": "São Paulo", "cf-ipcountry": "BR" },
29
+ );
30
+
31
+ const out = injectGeoCookies(req);
32
+
33
+ expect(out.headers.get("cf-region")).toBeNull();
34
+ // ASCII CF headers (cf-ipcountry) are still forwarded
35
+ expect(out.headers.get("cf-ipcountry")).toBe("BR");
36
+ // Geo data is preserved as cookies for matchers
37
+ const cookies = parseCookies(out.headers.get("cookie") ?? "");
38
+ expect(cookies.__cf_geo_region).toBe(encodeURIComponent("São Paulo"));
39
+ expect(cookies.__cf_geo_country).toBe("BR");
40
+ });
41
+
42
+ it("strips cf-ipcity from the outgoing Request headers while preserving the value in __cf_geo_city cookie", () => {
43
+ const req = makeRequest(
44
+ { city: "Brasília", country: "BR" },
45
+ { "cf-ipcity": "Brasília" },
46
+ );
47
+
48
+ const out = injectGeoCookies(req);
49
+
50
+ expect(out.headers.get("cf-ipcity")).toBeNull();
51
+ const cookies = parseCookies(out.headers.get("cookie") ?? "");
52
+ expect(cookies.__cf_geo_city).toBe(encodeURIComponent("Brasília"));
53
+ });
54
+
55
+ it("returns the original request unchanged when there is no cf object", () => {
56
+ const req = makeRequest(undefined, { "cf-region": "São Paulo" });
57
+
58
+ const out = injectGeoCookies(req);
59
+
60
+ // Without cf, we don't build cookies, and we return the original request
61
+ // untouched (so the cf-region header is still present — but that's the
62
+ // caller's pre-existing state, not something we re-introduced).
63
+ expect(out).toBe(req);
64
+ });
65
+
66
+ it("returns the original request unchanged when cf has no relevant geo fields", () => {
67
+ const req = makeRequest({ asn: "12345" }, { "cf-region": "São Paulo" });
68
+
69
+ const out = injectGeoCookies(req);
70
+
71
+ expect(out).toBe(req);
72
+ });
73
+
74
+ it("preserves a pre-existing cookie header", () => {
75
+ const req = makeRequest(
76
+ { region: "São Paulo" },
77
+ { cookie: "vtex_segment=abc; another=xyz" },
78
+ );
79
+
80
+ const out = injectGeoCookies(req);
81
+
82
+ const raw = out.headers.get("cookie") ?? "";
83
+ expect(raw).toContain("vtex_segment=abc");
84
+ expect(raw).toContain("another=xyz");
85
+ expect(raw).toContain("__cf_geo_region=");
86
+ });
87
+
88
+ it("forwards non-geo headers untouched", () => {
89
+ const req = makeRequest(
90
+ { region: "Paraná" },
91
+ {
92
+ "user-agent": "test-agent",
93
+ accept: "*/*",
94
+ "x-custom": "value",
95
+ "cf-ray": "9ff5b26cf9bc067a",
96
+ },
97
+ );
98
+
99
+ const out = injectGeoCookies(req);
100
+
101
+ expect(out.headers.get("user-agent")).toBe("test-agent");
102
+ expect(out.headers.get("accept")).toBe("*/*");
103
+ expect(out.headers.get("x-custom")).toBe("value");
104
+ expect(out.headers.get("cf-ray")).toBe("9ff5b26cf9bc067a");
105
+ });
106
+ });
@@ -443,7 +443,27 @@ export function injectGeoCookies(request: Request): Request {
443
443
 
444
444
  const existing = request.headers.get("cookie") ?? "";
445
445
  const combined = existing ? `${existing}; ${parts.join("; ")}` : parts.join("; ");
446
- const headers = new Headers(request.headers);
446
+
447
+ // Strip CF geo headers that carry non-ASCII values (cf-region: "São Paulo",
448
+ // cf-ipcity: "Brasília", etc.) before building the new Request. The geo
449
+ // data is preserved in the __cf_geo_* cookies we just built, so callers
450
+ // downstream lose no information.
451
+ //
452
+ // Without this strip, the Workers runtime emits a warning on every
453
+ // request because the new Request inherits these UTF-8 headers from the
454
+ // inbound request:
455
+ //
456
+ // "A header value for "cf-region" contains non-ASCII characters: "..."
457
+ //
458
+ // and the warning is logged once per non-ASCII header — for a Brazilian
459
+ // storefront with cities/states full of accents that means ~2 warns per
460
+ // request × every request that hits the worker.
461
+ const headers = new Headers();
462
+ for (const [key, value] of request.headers.entries()) {
463
+ const lk = key.toLowerCase();
464
+ if (lk === "cf-region" || lk === "cf-ipcity") continue;
465
+ headers.set(key, value);
466
+ }
447
467
  headers.set("cookie", combined);
448
468
 
449
469
  return new Request(request, { headers });