main
js 248 lines 6.48 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 FindSourceMapURLCallback,
15 Response as FlightResponse,
16 } from 'react-client/src/ReactFlightClient';
17
18 import type {ReactServerValue} from 'react-client/src/ReactFlightReplyClient';
19
20 import type {
21 ServerConsumerModuleMap,
22 ModuleLoading,
23 ServerManifest,
24 } from 'react-client/src/ReactFlightClientConfig';
25
26 type ServerConsumerManifest = {
27 moduleMap: ServerConsumerModuleMap,
28 moduleLoading: ModuleLoading,
29 serverModuleMap: null | ServerManifest,
30 };
31
32 import {
33 createResponse,
34 createStreamState,
35 getRoot,
36 reportGlobalError,
37 processBinaryChunk,
38 close,
39 } from 'react-client/src/ReactFlightClient';
40
41 import {
42 processReply,
43 createServerReference as createServerReferenceImpl,
44 } from 'react-client/src/ReactFlightReplyClient';
45
46 export {registerServerReference} from 'react-client/src/ReactFlightReplyClient';
47
48 import type {TemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
49
50 export {createTemporaryReferenceSet} from 'react-client/src/ReactFlightTemporaryReferences';
51
52 export type {TemporaryReferenceSet};
53
54 function noServerCall() {
55 throw new Error(
56 'Server Functions cannot be called during initial render. ' +
57 'This would create a fetch waterfall. Try to use a Server Component ' +
58 'to pass data to Client Components instead.',
59 );
60 }
61
62 export function createServerReference<A: Iterable<any>, T>(
63 id: any,
64 callServer: any,
65 ): (...A) => Promise<T> {
66 return createServerReferenceImpl(id, noServerCall);
67 }
68
69 type EncodeFormActionCallback = <A>(
70 id: any,
71 args: Promise<A>,
72 ) => ReactCustomFormAction;
73
74 export type Options = {
75 serverConsumerManifest: ServerConsumerManifest,
76 nonce?: string,
77 encodeFormAction?: EncodeFormActionCallback,
78 temporaryReferences?: TemporaryReferenceSet,
79 unstable_allowPartialStream?: boolean,
80 findSourceMapURL?: FindSourceMapURLCallback,
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 options.serverConsumerManifest.moduleMap,
100 options.serverConsumerManifest.serverModuleMap,
101 options.serverConsumerManifest.moduleLoading,
102 noServerCall,
103 options.encodeFormAction,
104 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__ && options && options.findSourceMapURL
112 ? options.findSourceMapURL
113 : undefined,
114 __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
115 __DEV__ && options && options.environmentName
116 ? options.environmentName
117 : undefined,
118 __DEV__ && options && options.startTime != null
119 ? options.startTime
120 : undefined,
121 __DEV__ && options && options.endTime != null ? options.endTime : undefined,
122 debugChannel,
123 );
124 }
125
126 function startReadingFromStream(
127 response: FlightResponse,
128 stream: ReadableStream,
129 onDone: () => void,
130 debugValue: mixed,
131 ): void {
132 const streamState = createStreamState(response, debugValue);
133 const reader = stream.getReader();
134 function progress({
135 done,
136 value,
137 }: {
138 done: boolean,
139 value: ?any,
140 ...
141 }): void | Promise<void> {
142 if (done) {
143 return onDone();
144 }
145 const buffer: Uint8Array = value as any;
146 processBinaryChunk(response, streamState, buffer);
147 return reader.read().then(progress).catch(error);
148 }
149 function error(e: any) {
150 reportGlobalError(response, e);
151 }
152 reader.read().then(progress).catch(error);
153 }
154
155 function createFromReadableStream<T>(
156 stream: ReadableStream,
157 options: Options,
158 ): Thenable<T> {
159 const response: FlightResponse = createResponseFromOptions(options);
160
161 if (
162 __DEV__ &&
163 options &&
164 options.debugChannel &&
165 options.debugChannel.readable
166 ) {
167 let streamDoneCount = 0;
168 const handleDone = () => {
169 if (++streamDoneCount === 2) {
170 close(response);
171 }
172 };
173 startReadingFromStream(response, options.debugChannel.readable, handleDone);
174 startReadingFromStream(response, stream, handleDone, stream);
175 } else {
176 startReadingFromStream(
177 response,
178 stream,
179 close.bind(null, response),
180 stream,
181 );
182 }
183
184 return getRoot(response);
185 }
186
187 function createFromFetch<T>(
188 promiseForResponse: Promise<Response>,
189 options: Options,
190 ): Thenable<T> {
191 const response: FlightResponse = createResponseFromOptions(options);
192 promiseForResponse.then(
193 function (r) {
194 if (
195 __DEV__ &&
196 options &&
197 options.debugChannel &&
198 options.debugChannel.readable
199 ) {
200 let streamDoneCount = 0;
201 const handleDone = () => {
202 if (++streamDoneCount === 2) {
203 close(response);
204 }
205 };
206 startReadingFromStream(
207 response,
208 options.debugChannel.readable,
209 handleDone,
210 );
211 startReadingFromStream(response, r.body as any, handleDone, r);
212 } else {
213 startReadingFromStream(
214 response,
215 r.body as any,
216 close.bind(null, response),
217 r,
218 );
219 }
220 },
221 function (e) {
222 reportGlobalError(response, e);
223 },
224 );
225 return getRoot(response);
226 }
227
228 function encodeReply(
229 value: ReactServerValue,
230 options?: {temporaryReferences?: TemporaryReferenceSet, signal?: AbortSignal},
231 ): Promise<
232 string | URLSearchParams | FormData,
233 > /* We don't use URLSearchParams yet but maybe */ {
234 return new Promise((resolve, reject) => {
235 processReply(
236 value,
237 '',
238 options && options.temporaryReferences
239 ? options.temporaryReferences
240 : undefined,
241 resolve,
242 reject,
243 options ? options.signal : undefined,
244 );
245 });
246 }
247
248 export {createFromFetch, createFromReadableStream, encodeReply};