diff --git a/packages/csr/src/reactive-runtime.ts b/packages/csr/src/reactive-runtime.ts
index 12e7a62a..5bbbc186 100644
--- a/packages/csr/src/reactive-runtime.ts
+++ b/packages/csr/src/reactive-runtime.ts
@@ -4138,8 +4138,13 @@ export const REACTIVE_RUNTIME = String.raw`
}
function callServerFunction(component, functionName, args) {
+ var csrfMeta = document.querySelector('meta[name="wrnexus-csrf"]');
var csrfMatch = /(?:^|;\s*)wire-csrf=([^;]+)/.exec(document.cookie || "");
- var csrf = csrfMatch ? decodeURIComponent(csrfMatch[1]) : "";
+ var csrf = csrfMeta
+ ? csrfMeta.getAttribute("content") || ""
+ : csrfMatch
+ ? decodeURIComponent(csrfMatch[1])
+ : "";
return fetch("/__wrnexus/rpc", {
method: "POST",
credentials: "same-origin",
diff --git a/packages/csr/test/reactive.test.ts b/packages/csr/test/reactive.test.ts
index e0fe8683..964e982e 100644
--- a/packages/csr/test/reactive.test.ts
+++ b/packages/csr/test/reactive.test.ts
@@ -828,6 +828,34 @@ test("component server calls send the CSRF cookie in the RPC header", async () =
expect(new Headers(request?.headers).get("x-csrf-token")).toBe("rpc token");
});
+test("component server calls prefer the rendered CSRF token over stale cookies", async () => {
+ const win = mount(
+ `` +
+ `
` +
+ `` +
+ `
`,
+ );
+ Object.defineProperty(win.document, "cookie", {
+ configurable: true,
+ value: "wire-csrf=stale-token",
+ });
+ let request: RequestInit | undefined;
+ const globals = globalThis as Record;
+ const originalFetch = globals.fetch;
+ globals.fetch = async (_input: RequestInfo | URL, init?: RequestInit) => {
+ request = init;
+ return Response.json({ ok: true });
+ };
+ try {
+ (win.document.querySelector("button") as unknown as HTMLElement).click();
+ await new Promise((resolve) => setTimeout(resolve, 0));
+ } finally {
+ globals.fetch = originalFetch;
+ }
+
+ expect(new Headers(request?.headers).get("x-csrf-token")).toBe("current-token");
+});
+
test("development runtime warns when a component binding names a missing function", () => {
const win = new Window() as unknown as Window & Record;
win.document.body.innerHTML =
diff --git a/packages/dev-server/src/runtime.ts b/packages/dev-server/src/runtime.ts
index dc31eeb6..a6a96fa9 100644
--- a/packages/dev-server/src/runtime.ts
+++ b/packages/dev-server/src/runtime.ts
@@ -1873,6 +1873,7 @@ export function createHandlers(deps: RuntimeDeps): Handlers {
body: renderedBody,
scripts,
extraHead: [
+ ``,
preserve ? `` : "",
pwaEnabled ? `` : "",
pwaEnabled ? `` : "",
diff --git a/packages/dev-server/test/actions-runtime.test.ts b/packages/dev-server/test/actions-runtime.test.ts
index 4dbd26b1..163ce66b 100644
--- a/packages/dev-server/test/actions-runtime.test.ts
+++ b/packages/dev-server/test/actions-runtime.test.ts
@@ -51,6 +51,7 @@ test("server actions validate, enforce CSRF, invalidate, and progressively enhan
const page = await handlers.fetch(new Request("https://example.test/users"), server);
const html = await page!.text();
expect(html).toContain('name="_csrf"');
+ expect(html).toContain('name="wrnexus-csrf"');
expect(html).toContain("/__wrnexus/actions.js");
const cookie = page!.headers.get("set-cookie")!;
const token = /wire-csrf=([^;]+)/.exec(cookie)?.[1];