main
js 121 lines 4.04 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 const TEMPORARY_REFERENCE_TAG = Symbol.for('react.temporary.reference');
11
12 export opaque type TemporaryReferenceSet = WeakMap<
13 TemporaryReference<any>,
14 string,
15 >;
16
17 // eslint-disable-next-line no-unused-vars
18 export interface TemporaryReference<T> {}
19
20 export function createTemporaryReferenceSet(): TemporaryReferenceSet {
21 return new WeakMap();
22 }
23
24 export function isOpaqueTemporaryReference(reference: Object): boolean {
25 return reference.$$typeof === TEMPORARY_REFERENCE_TAG;
26 }
27
28 export function resolveTemporaryReference<T>(
29 temporaryReferences: TemporaryReferenceSet,
30 temporaryReference: TemporaryReference<T>,
31 ): void | string {
32 return temporaryReferences.get(temporaryReference);
33 }
34
35 const proxyHandlers: Proxy$traps<mixed> = {
36 get: function (
37 target: Function,
38 name: string | symbol,
39 receiver: Proxy<Function>,
40 ) {
41 switch (name) {
42 // These names are read by the Flight runtime if you end up using the exports object.
43 case '$$typeof':
44 // These names are a little too common. We should probably have a way to
45 // have the Flight runtime extract the inner target instead.
46 return target.$$typeof;
47 case 'name':
48 return undefined;
49 case 'displayName':
50 return undefined;
51 // We need to special case this because createElement reads it if we pass this
52 // reference.
53 case 'defaultProps':
54 return undefined;
55 // React looks for debugInfo on thenables.
56 case '_debugInfo':
57 return undefined;
58 // Avoid this attempting to be serialized.
59 case 'toJSON':
60 return undefined;
61 case Symbol.toPrimitive:
62 // $FlowFixMe[prop-missing]
63 return Object.prototype[Symbol.toPrimitive];
64 case Symbol.toStringTag:
65 // $FlowFixMe[prop-missing]
66 return Object.prototype[Symbol.toStringTag];
67 case 'Provider':
68 // Context.Provider === Context in React, so return the same reference.
69 // This allows server components to render <ClientContext.Provider>
70 // which will be serialized and executed on the client.
71 return receiver;
72 case 'then':
73 // Allow returning a temporary reference from an async function
74 // Unlike regular Client References, a Promise would never have been serialized as
75 // an opaque Temporary Reference, but instead would have been serialized as a
76 // Promise on the server and so doesn't hit this path. So we can assume this wasn't
77 // a Promise on the client.
78 return undefined;
79 }
80 throw new Error(
81 // eslint-disable-next-line react-internal/safe-string-coercion
82 `Cannot access ${String(name)} on the server. ` +
83 'You cannot dot into a temporary client reference from a server component. ' +
84 'You can only pass the value through to the client.',
85 );
86 },
87 set: function () {
88 throw new Error(
89 'Cannot assign to a temporary client reference from a server module.',
90 );
91 },
92 };
93
94 export function createTemporaryReference<T>(
95 temporaryReferences: TemporaryReferenceSet,
96 id: string,
97 ): TemporaryReference<T> {
98 const reference: TemporaryReference<any> = Object.defineProperties(
99 function () {
100 throw new Error(
101 `Attempted to call a temporary Client Reference from the server but it is on the client. ` +
102 `It's not possible to invoke a client function from the server, it can ` +
103 `only be rendered as a Component or passed to props of a Client Component.`,
104 );
105 } as any,
106 {
107 $$typeof: {value: TEMPORARY_REFERENCE_TAG},
108 },
109 );
110 const wrapper = new Proxy(reference, proxyHandlers);
111 registerTemporaryReference(temporaryReferences, wrapper, id);
112 return wrapper;
113 }
114
115 export function registerTemporaryReference(
116 temporaryReferences: TemporaryReferenceSet,
117 object: TemporaryReference<any>,
118 id: string,
119 ): void {
120 temporaryReferences.set(object, id);
121 }