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. const proxyHandlers = {
    
  138.   get: function (
    
  139.     target: Function,
    
  140.     name: string,
    
  141.     receiver: Proxy<Function>,
    
  142.   ): $FlowFixMe {
    
  143.     switch (name) {
    
  144.       // These names are read by the Flight runtime if you end up using the exports object.
    
  145.       case '$$typeof':
    
  146.         return target.$$typeof;
    
  147.       case '$$id':
    
  148.         return target.$$id;
    
  149.       case '$$async':
    
  150.         return target.$$async;
    
  151.       case 'name':
    
  152.         return target.name;
    
  153.       // We need to special case this because createElement reads it if we pass this
    
  154.       // reference.
    
  155.       case 'defaultProps':
    
  156.         return undefined;
    
  157.       // Avoid this attempting to be serialized.
    
  158.       case 'toJSON':
    
  159.         return undefined;
    
  160.       case Symbol.toPrimitive:
    
  161.         // $FlowFixMe[prop-missing]
    
  162.         return Object.prototype[Symbol.toPrimitive];
    
  163.       case '__esModule':
    
  164.         // Something is conditionally checking which export to use. We'll pretend to be
    
  165.         // an ESM compat module but then we'll check again on the client.
    
  166.         const moduleId = target.$$id;
    
  167.         target.default = registerClientReferenceImpl(
    
  168.           (function () {
    
  169.             throw new Error(
    
  170.               `Attempted to call the default export of ${moduleId} from the server ` +
    
  171.                 `but it's on the client. It's not possible to invoke a client function from ` +
    
  172.                 `the server, it can only be rendered as a Component or passed to props of a ` +
    
  173.                 `Client Component.`,
    
  174.             );
    
  175.           }: any),
    
  176.           target.$$id + '#',
    
  177.           target.$$async,
    
  178.         );
    
  179.         return true;
    
  180.       case 'then':
    
  181.         if (target.then) {
    
  182.           // Use a cached value
    
  183.           return target.then;
    
  184.         }
    
  185.         if (!target.$$async) {
    
  186.           // If this module is expected to return a Promise (such as an AsyncModule) then
    
  187.           // we should resolve that with a client reference that unwraps the Promise on
    
  188.           // the client.
    
  189. 
    
  190.           const clientReference: ClientReference<any> =
    
  191.             registerClientReferenceImpl(({}: any), target.$$id, true);
    
  192.           const proxy = new Proxy(clientReference, proxyHandlers);
    
  193. 
    
  194.           // Treat this as a resolved Promise for React's use()
    
  195.           target.status = 'fulfilled';
    
  196.           target.value = proxy;
    
  197. 
    
  198.           const then = (target.then = registerClientReferenceImpl(
    
  199.             (function then(resolve, reject: any) {
    
  200.               // Expose to React.
    
  201.               return Promise.resolve(resolve(proxy));
    
  202.             }: any),
    
  203.             // If this is not used as a Promise but is treated as a reference to a `.then`
    
  204.             // export then we should treat it as a reference to that name.
    
  205.             target.$$id + '#then',
    
  206.             false,
    
  207.           ));
    
  208.           return then;
    
  209.         } else {
    
  210.           // Since typeof .then === 'function' is a feature test we'd continue recursing
    
  211.           // indefinitely if we return a function. Instead, we return an object reference
    
  212.           // if we check further.
    
  213.           return undefined;
    
  214.         }
    
  215.     }
    
  216.     let cachedReference = target[name];
    
  217.     if (!cachedReference) {
    
  218.       const reference: ClientReference<any> = registerClientReferenceImpl(
    
  219.         (function () {
    
  220.           throw new Error(
    
  221.             // eslint-disable-next-line react-internal/safe-string-coercion
    
  222.             `Attempted to call ${String(name)}() from the server but ${String(
    
  223.               name,
    
  224.             )} is on the client. ` +
    
  225.               `It's not possible to invoke a client function from the server, it can ` +
    
  226.               `only be rendered as a Component or passed to props of a Client Component.`,
    
  227.           );
    
  228.         }: any),
    
  229.         target.$$id + '#' + name,
    
  230.         target.$$async,
    
  231.       );
    
  232.       Object.defineProperty((reference: any), 'name', {value: name});
    
  233.       cachedReference = target[name] = new Proxy(reference, deepProxyHandlers);
    
  234.     }
    
  235.     return cachedReference;
    
  236.   },
    
  237.   getPrototypeOf(target: Function): Object {
    
  238.     // Pretend to be a Promise in case anyone asks.
    
  239.     return PROMISE_PROTOTYPE;
    
  240.   },
    
  241.   set: function (): empty {
    
  242.     throw new Error('Cannot assign to a client module from a server module.');
    
  243.   },
    
  244. };
    
  245. 
    
  246. export function createClientModuleProxy<T>(
    
  247.   moduleId: string,
    
  248. ): ClientReference<T> {
    
  249.   const clientReference: ClientReference<T> = registerClientReferenceImpl(
    
  250.     ({}: any),
    
  251.     // Represents the whole Module object instead of a particular import.
    
  252.     moduleId,
    
  253.     false,
    
  254.   );
    
  255.   return new Proxy(clientReference, proxyHandlers);
    
  256. }