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 {ReactClientValue} from 'react-server/src/ReactFlightServer';
    
  11. 
    
  12. export type ServerReference<T: Function> = T & {
    
  13.   $$typeof: symbol,
    
  14.   $$id: string,
    
  15.   $$bound: null | Array<ReactClientValue>,
    
  16. };
    
  17. 
    
  18. // eslint-disable-next-line no-unused-vars
    
  19. export type ClientReference<T> = {
    
  20.   $$typeof: symbol,
    
  21.   $$id: string,
    
  22.   $$async: boolean,
    
  23. };
    
  24. 
    
  25. const CLIENT_REFERENCE_TAG = Symbol.for('react.client.reference');
    
  26. const SERVER_REFERENCE_TAG = Symbol.for('react.server.reference');
    
  27. 
    
  28. export function isClientReference(reference: Object): boolean {
    
  29.   return reference.$$typeof === CLIENT_REFERENCE_TAG;
    
  30. }
    
  31. 
    
  32. export function isServerReference(reference: Object): boolean {
    
  33.   return reference.$$typeof === SERVER_REFERENCE_TAG;
    
  34. }
    
  35. 
    
  36. export function registerClientReference<T>(
    
  37.   proxyImplementation: any,
    
  38.   id: string,
    
  39.   exportName: string,
    
  40. ): ClientReference<T> {
    
  41.   return registerClientReferenceImpl(
    
  42.     proxyImplementation,
    
  43.     id + '#' + exportName,
    
  44.     false,
    
  45.   );
    
  46. }
    
  47. 
    
  48. function registerClientReferenceImpl<T>(
    
  49.   proxyImplementation: any,
    
  50.   id: string,
    
  51.   async: boolean,
    
  52. ): ClientReference<T> {
    
  53.   return Object.defineProperties(proxyImplementation, {
    
  54.     $$typeof: {value: CLIENT_REFERENCE_TAG},
    
  55.     $$id: {value: id},
    
  56.     $$async: {value: async},
    
  57.   });
    
  58. }
    
  59. 
    
  60. // $FlowFixMe[method-unbinding]
    
  61. const FunctionBind = Function.prototype.bind;
    
  62. // $FlowFixMe[method-unbinding]
    
  63. const ArraySlice = Array.prototype.slice;
    
  64. function bind(this: ServerReference<any>) {
    
  65.   // $FlowFixMe[unsupported-syntax]
    
  66.   const newFn = FunctionBind.apply(this, arguments);
    
  67.   if (this.$$typeof === SERVER_REFERENCE_TAG) {
    
  68.     const args = ArraySlice.call(arguments, 1);
    
  69.     newFn.$$typeof = SERVER_REFERENCE_TAG;
    
  70.     newFn.$$id = this.$$id;
    
  71.     newFn.$$bound = this.$$bound ? this.$$bound.concat(args) : args;
    
  72.   }
    
  73.   return newFn;
    
  74. }
    
  75. 
    
  76. export function registerServerReference<T>(
    
  77.   reference: ServerReference<T>,
    
  78.   id: string,
    
  79.   exportName: null | string,
    
  80. ): ServerReference<T> {
    
  81.   return Object.defineProperties((reference: any), {
    
  82.     $$typeof: {value: SERVER_REFERENCE_TAG},
    
  83.     $$id: {value: exportName === null ? id : id + '#' + exportName},
    
  84.     $$bound: {value: null},
    
  85.     bind: {value: bind},
    
  86.   });
    
  87. }
    
  88. 
    
  89. const PROMISE_PROTOTYPE = Promise.prototype;
    
  90. 
    
  91. const deepProxyHandlers = {
    
  92.   get: function (target: Function, name: string, receiver: Proxy<Function>) {
    
  93.     switch (name) {
    
  94.       // These names are read by the Flight runtime if you end up using the exports object.
    
  95.       case '$$typeof':
    
  96.         // These names are a little too common. We should probably have a way to
    
  97.         // have the Flight runtime extract the inner target instead.
    
  98.         return target.$$typeof;
    
  99.       case '$$id':
    
  100.         return target.$$id;
    
  101.       case '$$async':
    
  102.         return target.$$async;
    
  103.       case 'name':
    
  104.         return target.name;
    
  105.       case 'displayName':
    
  106.         return undefined;
    
  107.       // We need to special case this because createElement reads it if we pass this
    
  108.       // reference.
    
  109.       case 'defaultProps':
    
  110.         return undefined;
    
  111.       // Avoid this attempting to be serialized.
    
  112.       case 'toJSON':
    
  113.         return undefined;
    
  114.       case Symbol.toPrimitive:
    
  115.         // $FlowFixMe[prop-missing]
    
  116.         return Object.prototype[Symbol.toPrimitive];
    
  117.       case 'Provider':
    
  118.         throw new Error(
    
  119.           `Cannot render a Client Context Provider on the Server. ` +
    
  120.             `Instead, you can export a Client Component wrapper ` +
    
  121.             `that itself renders a Client Context Provider.`,
    
  122.         );
    
  123.     }
    
  124.     // eslint-disable-next-line react-internal/safe-string-coercion
    
  125.     const expression = String(target.name) + '.' + String(name);
    
  126.     throw new Error(
    
  127.       `Cannot access ${expression} on the server. ` +
    
  128.         'You cannot dot into a client module from a server component. ' +
    
  129.         'You can only pass the imported name through.',
    
  130.     );
    
  131.   },
    
  132.   set: function () {
    
  133.     throw new Error('Cannot assign to a client module from a server module.');
    
  134.   },
    
  135. };
    
  136. 
    
  137. function getReference(target: Function, name: string): $FlowFixMe {
    
  138.   switch (name) {
    
  139.     // These names are read by the Flight runtime if you end up using the exports object.
    
  140.     case '$$typeof':
    
  141.       return target.$$typeof;
    
  142.     case '$$id':
    
  143.       return target.$$id;
    
  144.     case '$$async':
    
  145.       return target.$$async;
    
  146.     case 'name':
    
  147.       return target.name;
    
  148.     // We need to special case this because createElement reads it if we pass this
    
  149.     // reference.
    
  150.     case 'defaultProps':
    
  151.       return undefined;
    
  152.     // Avoid this attempting to be serialized.
    
  153.     case 'toJSON':
    
  154.       return undefined;
    
  155.     case Symbol.toPrimitive:
    
  156.       // $FlowFixMe[prop-missing]
    
  157.       return Object.prototype[Symbol.toPrimitive];
    
  158.     case '__esModule':
    
  159.       // Something is conditionally checking which export to use. We'll pretend to be
    
  160.       // an ESM compat module but then we'll check again on the client.
    
  161.       const moduleId = target.$$id;
    
  162.       target.default = registerClientReferenceImpl(
    
  163.         (function () {
    
  164.           throw new Error(
    
  165.             `Attempted to call the default export of ${moduleId} from the server ` +
    
  166.               `but it's on the client. It's not possible to invoke a client function from ` +
    
  167.               `the server, it can only be rendered as a Component or passed to props of a ` +
    
  168.               `Client Component.`,
    
  169.           );
    
  170.         }: any),
    
  171.         target.$$id + '#',
    
  172.         target.$$async,
    
  173.       );
    
  174.       return true;
    
  175.     case 'then':
    
  176.       if (target.then) {
    
  177.         // Use a cached value
    
  178.         return target.then;
    
  179.       }
    
  180.       if (!target.$$async) {
    
  181.         // If this module is expected to return a Promise (such as an AsyncModule) then
    
  182.         // we should resolve that with a client reference that unwraps the Promise on
    
  183.         // the client.
    
  184. 
    
  185.         const clientReference: ClientReference<any> =
    
  186.           registerClientReferenceImpl(({}: any), target.$$id, true);
    
  187.         const proxy = new Proxy(clientReference, proxyHandlers);
    
  188. 
    
  189.         // Treat this as a resolved Promise for React's use()
    
  190.         target.status = 'fulfilled';
    
  191.         target.value = proxy;
    
  192. 
    
  193.         const then = (target.then = registerClientReferenceImpl(
    
  194.           (function then(resolve, reject: any) {
    
  195.             // Expose to React.
    
  196.             return Promise.resolve(resolve(proxy));
    
  197.           }: any),
    
  198.           // If this is not used as a Promise but is treated as a reference to a `.then`
    
  199.           // export then we should treat it as a reference to that name.
    
  200.           target.$$id + '#then',
    
  201.           false,
    
  202.         ));
    
  203.         return then;
    
  204.       } else {
    
  205.         // Since typeof .then === 'function' is a feature test we'd continue recursing
    
  206.         // indefinitely if we return a function. Instead, we return an object reference
    
  207.         // if we check further.
    
  208.         return undefined;
    
  209.       }
    
  210.   }
    
  211.   let cachedReference = target[name];
    
  212.   if (!cachedReference) {
    
  213.     const reference: ClientReference<any> = registerClientReferenceImpl(
    
  214.       (function () {
    
  215.         throw new Error(
    
  216.           // eslint-disable-next-line react-internal/safe-string-coercion
    
  217.           `Attempted to call ${String(name)}() from the server but ${String(
    
  218.             name,
    
  219.           )} is on the client. ` +
    
  220.             `It's not possible to invoke a client function from the server, it can ` +
    
  221.             `only be rendered as a Component or passed to props of a Client Component.`,
    
  222.         );
    
  223.       }: any),
    
  224.       target.$$id + '#' + name,
    
  225.       target.$$async,
    
  226.     );
    
  227.     Object.defineProperty((reference: any), 'name', {value: name});
    
  228.     cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
    
  229.   }
    
  230.   return cachedReference;
    
  231. }
    
  232. 
    
  233. const proxyHandlers = {
    
  234.   get: function (
    
  235.     target: Function,
    
  236.     name: string,
    
  237.     receiver: Proxy<Function>,
    
  238.   ): $FlowFixMe {
    
  239.     return getReference(target, name);
    
  240.   },
    
  241.   getOwnPropertyDescriptor: function (
    
  242.     target: Function,
    
  243.     name: string,
    
  244.   ): $FlowFixMe {
    
  245.     let descriptor = Object.getOwnPropertyDescriptor(target, name);
    
  246.     if (!descriptor) {
    
  247.       descriptor = {
    
  248.         value: getReference(target, name),
    
  249.         writable: false,
    
  250.         configurable: false,
    
  251.         enumerable: false,
    
  252.       };
    
  253.       Object.defineProperty(target, name, descriptor);
    
  254.     }
    
  255.     return descriptor;
    
  256.   },
    
  257.   getPrototypeOf(target: Function): Object {
    
  258.     // Pretend to be a Promise in case anyone asks.
    
  259.     return PROMISE_PROTOTYPE;
    
  260.   },
    
  261.   set: function (): empty {
    
  262.     throw new Error('Cannot assign to a client module from a server module.');
    
  263.   },
    
  264. };
    
  265. 
    
  266. export function createClientModuleProxy<T>(
    
  267.   moduleId: string,
    
  268. ): ClientReference<T> {
    
  269.   const clientReference: ClientReference<T> = registerClientReferenceImpl(
    
  270.     ({}: any),
    
  271.     // Represents the whole Module object instead of a particular import.
    
  272.     moduleId,
    
  273.     false,
    
  274.   );
    
  275.   return new Proxy(clientReference, proxyHandlers);
    
  276. }