Skip to content

Commit

Permalink
Fix bugs with thrown data headers and duplciated set-cookie headers (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
brophdawg11 authored Jan 23, 2025
1 parent 90d6e43 commit 8f42290
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 7 deletions.
6 changes: 6 additions & 0 deletions .changeset/rich-pans-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"react-router": patch
---

- Properly bubble headers as `errorHeaders` when throwing a `data()` result
- Avoid duplication of `Set-Cookie` headers could be duplicated if also returned from `headers`
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
55 changes: 54 additions & 1 deletion integration/headers-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { UNSAFE_ServerMode as ServerMode } from "react-router";
import { createFixture, js } from "./helpers/create-fixture.js";
import type { Fixture } from "./helpers/create-fixture.js";

test.describe.skip("headers export", () => {
test.describe("headers export", () => {
let ROOT_HEADER_KEY = "X-Test";
let ROOT_HEADER_VALUE = "SUCCESS";
let ACTION_HKEY = "X-Test-Action";
Expand Down Expand Up @@ -416,4 +416,57 @@ test.describe.skip("headers export", () => {
])
);
});

test("does not duplicate set-cookie headers also returned via headers() function", async () => {
let fixture = await createFixture(
{
files: {
"app/root.tsx": js`
import { Links, Meta, Outlet, Scripts } from "react-router";
export default function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
`,

"app/routes/_index.tsx": js`
export function headers({ loaderHeaders }) {
return loaderHeaders;
}
export function loader() {
return new Response(null, {
headers: {
"X-Test": "value",
"Set-Cookie": "cookie=yum"
}
})
}
export default function Index() {
return <div>Heyo!</div>
}
`,
},
},
ServerMode.Test
);
let response = await fixture.requestDocument("/");
expect([...response.headers.entries()]).toEqual([
["content-type", "text/html"],
["set-cookie", "cookie=yum"],
["x-test", "value"],
]);
});
});
20 changes: 15 additions & 5 deletions packages/react-router/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4867,15 +4867,25 @@ async function convertDataStrategyResultToDataResult(
type: ResultType.error,
error: result.data,
statusCode: result.init?.status,
headers: result.init?.headers
? new Headers(result.init.headers)
: undefined,
};
}

// Convert thrown data() to ErrorResponse instances
result = new ErrorResponseImpl(
result.init?.status || 500,
undefined,
result.data
);
return {
type: ResultType.error,
error: new ErrorResponseImpl(
result.init?.status || 500,
undefined,
result.data
),
statusCode: isRouteErrorResponse(result) ? result.status : undefined,
headers: result.init?.headers
? new Headers(result.init.headers)
: undefined,
};
}

return {
Expand Down
5 changes: 4 additions & 1 deletion packages/react-router/lib/server-runtime/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ function prependCookies(parentHeaders: Headers, childHeaders: Headers): void {

if (parentSetCookieString) {
let cookies = splitCookiesString(parentSetCookieString);
let childCookies = new Set(childHeaders.getSetCookie());
cookies.forEach((cookie) => {
childHeaders.append("Set-Cookie", cookie);
if (!childCookies.has(cookie)) {
childHeaders.append("Set-Cookie", cookie);
}
});
}
}

0 comments on commit 8f42290

Please sign in to comment.