main
js 244 lines 6.39 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Thenable, ReactCustomFormAction} from 'shared/ReactTypes.js';
11
12 import type {
13 DebugChannel,
14 Response as FlightResponse,
15 } from 'react-client/src/ReactFlightClient';
16 import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
17
18 import {
19 createResponse,
20 createStreamState,
21 getRoot,
22 reportGlobalError,
23 processBinaryChunk,
24 close,
25 } from 'react-client/src/ReactFlightClient';
26
27 import {
28 processReply,
29 createServerReference as createServerReferenceImpl,
30 } from 'react-client/src/ReactFlightReplyClient';
31
32 export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
33
34 import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
35
36 export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
37 export type {TemporaryReferenceSet};
38
39 function findSourceMapURL(filename: string, environmentName: string) {
40 const devServer = parcelRequire.meta.devServer;
41 if (devServer != null) {
42 const qs = new URLSearchParams();
43 qs.set('filename', filename);
44 qs.set('env', environmentName);
45 return devServer + '/__parcel_source_map?' + qs.toString();
46 }
47 return null;
48 }
49
50 function noServerCall() {
51 throw new Error(
52 'Server Functions cannot be called during initial render. ' +
53 'This would create a fetch waterfall. Try to use a Server Component ' +
54 'to pass data to Client Components instead.',
55 );
56 }
57
58 export function createServerReference<A: Iterable<any>, T>(
59 id: string,
60 exportName: string,
61 ): (...A) => Promise<T> {
62 return createServerReferenceImpl(
63 id + '#' + exportName,
64 noServerCall,
65 undefined,
66 findSourceMapURL,
67 exportName,
68 );
69 }
70
71 type EncodeFormActionCallback = <A>(
72 id: any,
73 args: Promise<A>,
74 ) => ReactCustomFormAction;
75
76 export type Options = {
77 nonce?: string,
78 encodeFormAction?: EncodeFormActionCallback,
79 temporaryReferences?: TemporaryReferenceSet,
80 unstable_allowPartialStream?: boolean,
81 replayConsoleLogs?: boolean,
82 environmentName?: string,
83 startTime?: number,
84 endTime?: number,
85 // For the Edge client we only support a single-direction debug channel.
86 debugChannel?: {readable?: ReadableStream, ...},
87 };
88
89 function createResponseFromOptions(options?: Options) {
90 const debugChannel: void | DebugChannel =
91 __DEV__ && options && options.debugChannel !== undefined
92 ? {
93 hasReadable: options.debugChannel.readable !== undefined,
94 callback: null,
95 }
96 : undefined;
97
98 return createResponse(
99 null, // bundlerConfig
100 null, // serverReferenceConfig
101 null, // moduleLoading
102 noServerCall,
103 options ? options.encodeFormAction : undefined,
104 options && typeof options.nonce === 'string' ? options.nonce : undefined,
105 options && options.temporaryReferences
106 ? options.temporaryReferences
107 : undefined,
108 options && options.unstable_allowPartialStream
109 ? options.unstable_allowPartialStream
110 : false,
111 __DEV__ ? findSourceMapURL : undefined,
112 __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
113 __DEV__ && options && options.environmentName
114 ? options.environmentName
115 : undefined,
116 __DEV__ && options && options.startTime != null
117 ? options.startTime
118 : undefined,
119 __DEV__ && options && options.endTime != null ? options.endTime : undefined,
120 debugChannel,
121 );
122 }
123
124 function startReadingFromStream(
125 response: FlightResponse,
126 stream: ReadableStream,
127 onDone: () => void,
128 debugValue: mixed,
129 ): void {
130 const streamState = createStreamState(response, debugValue);
131 const reader = stream.getReader();
132 function progress({
133 done,
134 value,
135 }: {
136 done: boolean,
137 value: ?any,
138 ...
139 }): void | Promise<void> {
140 if (done) {
141 return onDone();
142 }
143 const buffer: Uint8Array = value as any;
144 processBinaryChunk(response, streamState, buffer);
145 return reader.read().then(progress).catch(error);
146 }
147 function error(e: any) {
148 reportGlobalError(response, e);
149 }
150 reader.read().then(progress).catch(error);
151 }
152
153 export function createFromReadableStream<T>(
154 stream: ReadableStream,
155 options?: Options,
156 ): Thenable<T> {
157 const response: FlightResponse = createResponseFromOptions(options);
158
159 if (
160 __DEV__ &&
161 options &&
162 options.debugChannel &&
163 options.debugChannel.readable
164 ) {
165 let streamDoneCount = 0;
166 const handleDone = () => {
167 if (++streamDoneCount === 2) {
168 close(response);
169 }
170 };
171 startReadingFromStream(response, options.debugChannel.readable, handleDone);
172 startReadingFromStream(response, stream, handleDone, stream);
173 } else {
174 startReadingFromStream(
175 response,
176 stream,
177 close.bind(null, response),
178 stream,
179 );
180 }
181
182 return getRoot(response);
183 }
184
185 export function createFromFetch<T>(
186 promiseForResponse: Promise<Response>,
187 options?: Options,
188 ): Thenable<T> {
189 const response: FlightResponse = createResponseFromOptions(options);
190 promiseForResponse.then(
191 function (r) {
192 if (
193 __DEV__ &&
194 options &&
195 options.debugChannel &&
196 options.debugChannel.readable
197 ) {
198 let streamDoneCount = 0;
199 const handleDone = () => {
200 if (++streamDoneCount === 2) {
201 close(response);
202 }
203 };
204 startReadingFromStream(
205 response,
206 options.debugChannel.readable,
207 handleDone,
208 );
209 startReadingFromStream(response, r.body as any, handleDone, r);
210 } else {
211 startReadingFromStream(
212 response,
213 r.body as any,
214 close.bind(null, response),
215 r,
216 );
217 }
218 },
219 function (e) {
220 reportGlobalError(response, e);
221 },
222 );
223 return getRoot(response);
224 }
225
226 export function encodeReply(
227 value: ReactServerValue,
228 options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
229 ): Promise<
230 string | URLSearchParams | FormData,
231 > /* We don't use URLSearchParams yet but maybe */ {
232 return new Promise((resolve, reject) => {
233 processReply(
234 value,
235 '',
236 options && options.temporaryReferences
237 ? options.temporaryReferences
238 : undefined,
239 resolve,
240 reject,
241 options ? options.signal : undefined,
242 );
243 });
244 }