Skip to content

Commit ba3b0cf

Browse files
authored
fix(rsc): client element as bound arg encryption (#905)
1 parent 6728273 commit ba3b0cf

4 files changed

Lines changed: 67 additions & 6 deletions

File tree

packages/rsc/examples/basic/src/routes/action-bind/server.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,18 @@ export function TestServerActionBindSimple() {
4444
let testServerActionBindClientState = 0;
4545

4646
export function TestServerActionBindClient() {
47+
// client element as server action bound argument
48+
const client = <ActionBindClient />;
49+
50+
const action = async () => {
51+
"use server";
52+
return client;
53+
};
54+
4755
return (
4856
<TestServerActionBindClientForm
4957
key={testServerActionBindClientState}
50-
action={async () => {
51-
"use server";
52-
return <ActionBindClient />;
53-
}}
58+
action={action}
5459
/>
5560
);
5661
}

packages/rsc/src/core/rsc.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { memoize, tinyassert } from "@hiogawa/utils";
2-
import type { BundlerConfig, ImportManifestEntry } from "../types";
2+
import type { BundlerConfig, ImportManifestEntry, ModuleMap } from "../types";
33
import {
4+
SERVER_DECODE_CLIENT_PREFIX,
45
SERVER_REFERENCE_PREFIX,
56
createReferenceCacheTag,
67
removeReferenceCacheTag,
78
setInternalRequire,
89
} from "./shared";
910

11+
// @ts-ignore
12+
import * as ReactServer from "react-server-dom-webpack/server.edge";
13+
1014
let init = false;
1115
let requireModule!: (id: string) => unknown;
1216

@@ -23,6 +27,24 @@ export function setRequireModule(options: {
2327
// need memoize to return stable promise from __webpack_require__
2428
(globalThis as any).__vite_rsc_server_require__ = memoize(requireModule);
2529

30+
(globalThis as any).__vite_rsc_server_decode_client__ = memoize(
31+
async (raw: string) => {
32+
// restore client reference on server for decoding.
33+
// learned from https://github.com/lazarv/react-server/blob/79e7acebc6f4a8c930ad8422e2a4a9fdacfcce9b/packages/react-server/server/module-loader.mjs#L19
34+
const { id, name } = JSON.parse(raw);
35+
const reference = ReactServer.registerClientReference(
36+
() => {
37+
throw new Error(
38+
`Unexpectedly client reference export '${name}' is called on server`,
39+
);
40+
},
41+
removeReferenceCacheTag(id),
42+
name,
43+
);
44+
return { [name]: reference };
45+
},
46+
);
47+
2648
setInternalRequire();
2749
}
2850

@@ -54,6 +76,29 @@ export function createServerManifest(): BundlerConfig {
5476
);
5577
}
5678

79+
export function createServerDecodeClientManifest(): ModuleMap {
80+
return new Proxy(
81+
{},
82+
{
83+
get(_target, id: string) {
84+
return new Proxy(
85+
{},
86+
{
87+
get(_target, name: string) {
88+
return {
89+
id: SERVER_DECODE_CLIENT_PREFIX + JSON.stringify({ id, name }),
90+
name,
91+
chunks: [],
92+
async: true,
93+
};
94+
},
95+
},
96+
);
97+
},
98+
},
99+
);
100+
}
101+
57102
export function createClientManifest(): BundlerConfig {
58103
const cacheTag = import.meta.env.DEV ? createReferenceCacheTag() : "";
59104

packages/rsc/src/core/shared.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// use special prefix to switch client/server reference loading inside __webpack_require__
22
export const SERVER_REFERENCE_PREFIX = "$$server:";
33

4+
export const SERVER_DECODE_CLIENT_PREFIX = "$$decode-client:";
5+
46
// cache bust memoized require promise during dev
57
export function createReferenceCacheTag(): string {
68
const cache = Math.random().toString(36).slice(2);
@@ -14,6 +16,10 @@ export function removeReferenceCacheTag(id: string): string {
1416
export function setInternalRequire(): void {
1517
// branch client and server require to support the case when ssr and rsc share the same global
1618
(globalThis as any).__webpack_require__ = (id: string) => {
19+
if (id.startsWith(SERVER_DECODE_CLIENT_PREFIX)) {
20+
id = id.slice(SERVER_DECODE_CLIENT_PREFIX.length);
21+
return (globalThis as any).__vite_rsc_server_decode_client__(id);
22+
}
1723
if (id.startsWith(SERVER_REFERENCE_PREFIX)) {
1824
id = id.slice(SERVER_REFERENCE_PREFIX.length);
1925
return (globalThis as any).__vite_rsc_server_require__(id);

packages/rsc/src/react/rsc.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import type { ReactFormState } from "react-dom/client";
33
import * as ReactClient from "react-server-dom-webpack/client.edge";
44
// @ts-ignore
55
import * as ReactServer from "react-server-dom-webpack/server.edge";
6-
import { createClientManifest, createServerManifest } from "../core/rsc";
6+
import {
7+
createClientManifest,
8+
createServerDecodeClientManifest,
9+
createServerManifest,
10+
} from "../core/rsc";
711

812
export { loadServerAction, setRequireModule } from "../core/rsc";
913

@@ -27,6 +31,7 @@ export function createFromReadableStream<T>(
2731
// https://github.com/facebook/react/pull/31300
2832
// https://github.com/vercel/next.js/pull/71527
2933
serverModuleMap: createServerManifest(),
34+
moduleMap: createServerDecodeClientManifest(),
3035
},
3136
...options,
3237
});

0 commit comments

Comments
 (0)