main
js 143 lines 3.85 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,
16 } from 'react-client/src/ReactFlightClient';
17
18 import type {
19 ServerConsumerModuleMap,
20 ModuleLoading,
21 ServerManifest,
22 } from 'react-client/src/ReactFlightClientConfig';
23
24 type ServerConsumerManifest = {
25 moduleMap: ServerConsumerModuleMap,
26 moduleLoading: ModuleLoading,
27 serverModuleMap: null | ServerManifest,
28 };
29
30 import type {Readable} from 'stream';
31
32 import {
33 createResponse,
34 createStreamState,
35 getRoot,
36 reportGlobalError,
37 processStringChunk,
38 processBinaryChunk,
39 close,
40 } from 'react-client/src/ReactFlightClient';
41
42 export * from './ReactFlightDOMClientEdge';
43
44 function noServerCall() {
45 throw new Error(
46 'Server Functions cannot be called during initial render. ' +
47 'This would create a fetch waterfall. Try to use a Server Component ' +
48 'to pass data to Client Components instead.',
49 );
50 }
51
52 type EncodeFormActionCallback = <A>(
53 id: any,
54 args: Promise<A>,
55 ) => ReactCustomFormAction;
56
57 export type Options = {
58 nonce?: string,
59 encodeFormAction?: EncodeFormActionCallback,
60 unstable_allowPartialStream?: boolean,
61 findSourceMapURL?: FindSourceMapURLCallback,
62 replayConsoleLogs?: boolean,
63 environmentName?: string,
64 startTime?: number,
65 endTime?: number,
66 // For the Node.js client we only support a single-direction debug channel.
67 debugChannel?: Readable,
68 };
69
70 function startReadingFromStream(
71 response: Response,
72 stream: Readable,
73 onEnd: () => void,
74 ): void {
75 const streamState = createStreamState(response, stream);
76
77 stream.on('data', chunk => {
78 if (typeof chunk === 'string') {
79 processStringChunk(response, streamState, chunk);
80 } else {
81 processBinaryChunk(response, streamState, chunk);
82 }
83 });
84
85 stream.on('error', error => {
86 reportGlobalError(response, error);
87 });
88
89 stream.on('end', onEnd);
90 }
91
92 function createFromNodeStream<T>(
93 stream: Readable,
94 serverConsumerManifest: ServerConsumerManifest,
95 options?: Options,
96 ): Thenable<T> {
97 const debugChannel: void | DebugChannel =
98 __DEV__ && options && options.debugChannel !== undefined
99 ? {hasReadable: true, callback: null}
100 : undefined;
101
102 const response: Response = createResponse(
103 serverConsumerManifest.moduleMap,
104 serverConsumerManifest.serverModuleMap,
105 serverConsumerManifest.moduleLoading,
106 noServerCall,
107 options ? options.encodeFormAction : undefined,
108 options && typeof options.nonce === 'string' ? options.nonce : undefined,
109 undefined, // TODO: If encodeReply is supported, this should support temporaryReferences
110 options && options.unstable_allowPartialStream
111 ? options.unstable_allowPartialStream
112 : false,
113 __DEV__ && options && options.findSourceMapURL
114 ? options.findSourceMapURL
115 : undefined,
116 __DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
117 __DEV__ && options && options.environmentName
118 ? options.environmentName
119 : undefined,
120 __DEV__ && options && options.startTime != null
121 ? options.startTime
122 : undefined,
123 __DEV__ && options && options.endTime != null ? options.endTime : undefined,
124 debugChannel,
125 );
126
127 if (__DEV__ && options && options.debugChannel) {
128 let streamEndedCount = 0;
129 const handleEnd = () => {
130 if (++streamEndedCount === 2) {
131 close(response);
132 }
133 };
134 startReadingFromStream(response, options.debugChannel, handleEnd);
135 startReadingFromStream(response, stream, handleEnd);
136 } else {
137 startReadingFromStream(response, stream, close.bind(null, response));
138 }
139
140 return getRoot(response);
141 }
142
143 export {createFromNodeStream};