Allow a nonce to be set on single fetch stream transfer inline scripts
Closes: #9359
- [x] Docs
- [ ] Tests - not sure of the best approach to add tests for this. I looked at adding one to
packages/remix-react/__tests__/components-test.tsxbut it requires setting acontext.serverHandoffStreamwhich seems non-trivial? Is there a better approach?
Testing Strategy:
I patched @remix-run/react in our application with these changes and saw that it fixed our CSP violations.
🦋 Changeset detected
Latest commit: 1087c22cc39556af9b9b3f9b8a39bd3968c51a15
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 16 packages
| Name | Type |
|---|---|
| @remix-run/react | Patch |
| @remix-run/testing | Patch |
| @remix-run/dev | Patch |
| create-remix | Patch |
| remix | Patch |
| @remix-run/architect | Patch |
| @remix-run/cloudflare | Patch |
| @remix-run/cloudflare-pages | Patch |
| @remix-run/cloudflare-workers | Patch |
| @remix-run/css-bundle | Patch |
| @remix-run/deno | Patch |
| @remix-run/eslint-config | Patch |
| @remix-run/express | Patch |
| @remix-run/node | Patch |
| @remix-run/serve | Patch |
| @remix-run/server-runtime | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
Thanks!
Yeah this stuff is best tested through our E2E tests - want to try something like this in integration/single-fetch-test.ts?
You can run it with pnpm test:integration single-fetch --project chromium
test("supports nonce on streaming script tags", async ({ page }) => {
let fixture = await createFixture({
config: {
future: {
unstable_singleFetch: true,
},
},
files: {
...files,
"app/entry.server.tsx": js`
import { PassThrough } from "node:stream";
import type { EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { renderToPipeableStream } from "react-dom/server";
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
return new Promise((resolve, reject) => {
const { pipe } = renderToPipeableStream(
<RemixServer context={remixContext} url={request.url} nonce="the-nonce" />,
{
onShellReady() {
const body = new PassThrough();
const stream = createReadableStreamFromReadable(body);
responseHeaders.set("Content-Type", "text/html");
resolve(
new Response(stream, {
headers: responseHeaders,
status: responseStatusCode,
})
);
pipe(body);
},
onShellError(error: unknown) {
reject(error);
},
onError(error: unknown) {
responseStatusCode = 500;
},
}
);
});
}
`,
},
});
let appFixture = await createAppFixture(fixture);
let app = new PlaywrightFixture(appFixture, page);
await app.goto("/data", true);
let scripts = await page.$$("script");
expect(scripts.length).toBe(6);
for (let s of scripts) {
// Do nonce expect() calls here for the scripts that should have it - which I think might just be
// any with `window.__remixContext.streamController` in the innterHTML?
console.log(await s.getAttribute("nonce"), await s.innerHTML());
}
});
@brophdawg11 thanks a lot for helping with the test case!
I found that <Scripts> also injects an inline script that references window.__remixContext.streamController. I opted to add the nonce to that as well, rather than trying to distinguish it from the scripts added by <StreamTransfer>, which I felt would probably make the test too brittle.