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 {
    
  11.   Thenable,
    
  12.   PendingThenable,
    
  13.   FulfilledThenable,
    
  14.   RejectedThenable,
    
  15.   ReactCustomFormAction,
    
  16. } from 'shared/ReactTypes';
    
  17. 
    
  18. import {
    
  19.   REACT_ELEMENT_TYPE,
    
  20.   REACT_LAZY_TYPE,
    
  21.   REACT_PROVIDER_TYPE,
    
  22.   getIteratorFn,
    
  23. } from 'shared/ReactSymbols';
    
  24. 
    
  25. import {
    
  26.   describeObjectForErrorMessage,
    
  27.   isSimpleObject,
    
  28.   objectName,
    
  29. } from 'shared/ReactSerializationErrors';
    
  30. 
    
  31. import isArray from 'shared/isArray';
    
  32. import getPrototypeOf from 'shared/getPrototypeOf';
    
  33. 
    
  34. const ObjectPrototype = Object.prototype;
    
  35. 
    
  36. import {usedWithSSR} from './ReactFlightClientConfig';
    
  37. 
    
  38. type ReactJSONValue =
    
  39.   | string
    
  40.   | boolean
    
  41.   | number
    
  42.   | null
    
  43.   | $ReadOnlyArray<ReactJSONValue>
    
  44.   | ReactServerObject;
    
  45. 
    
  46. export opaque type ServerReference<T> = T;
    
  47. 
    
  48. export type CallServerCallback = <A, T>(id: any, args: A) => Promise<T>;
    
  49. 
    
  50. export type ServerReferenceId = any;
    
  51. 
    
  52. const knownServerReferences: WeakMap<
    
  53.   Function,
    
  54.   {id: ServerReferenceId, bound: null | Thenable<Array<any>>},
    
  55. > = new WeakMap();
    
  56. 
    
  57. // Serializable values
    
  58. export type ReactServerValue =
    
  59.   // References are passed by their value
    
  60.   | ServerReference<any>
    
  61.   // The rest are passed as is. Sub-types can be passed in but lose their
    
  62.   // subtype, so the receiver can only accept once of these.
    
  63.   | string
    
  64.   | boolean
    
  65.   | number
    
  66.   | symbol
    
  67.   | null
    
  68.   | void
    
  69.   | bigint
    
  70.   | Iterable<ReactServerValue>
    
  71.   | Array<ReactServerValue>
    
  72.   | Map<ReactServerValue, ReactServerValue>
    
  73.   | Set<ReactServerValue>
    
  74.   | Date
    
  75.   | ReactServerObject
    
  76.   | Promise<ReactServerValue>; // Thenable<ReactServerValue>
    
  77. 
    
  78. type ReactServerObject = {+[key: string]: ReactServerValue};
    
  79. 
    
  80. // function serializeByValueID(id: number): string {
    
  81. //   return '$' + id.toString(16);
    
  82. // }
    
  83. 
    
  84. function serializePromiseID(id: number): string {
    
  85.   return '$@' + id.toString(16);
    
  86. }
    
  87. 
    
  88. function serializeServerReferenceID(id: number): string {
    
  89.   return '$F' + id.toString(16);
    
  90. }
    
  91. 
    
  92. function serializeSymbolReference(name: string): string {
    
  93.   return '$S' + name;
    
  94. }
    
  95. 
    
  96. function serializeFormDataReference(id: number): string {
    
  97.   // Why K? F is "Function". D is "Date". What else?
    
  98.   return '$K' + id.toString(16);
    
  99. }
    
  100. 
    
  101. function serializeNumber(number: number): string | number {
    
  102.   if (Number.isFinite(number)) {
    
  103.     if (number === 0 && 1 / number === -Infinity) {
    
  104.       return '$-0';
    
  105.     } else {
    
  106.       return number;
    
  107.     }
    
  108.   } else {
    
  109.     if (number === Infinity) {
    
  110.       return '$Infinity';
    
  111.     } else if (number === -Infinity) {
    
  112.       return '$-Infinity';
    
  113.     } else {
    
  114.       return '$NaN';
    
  115.     }
    
  116.   }
    
  117. }
    
  118. 
    
  119. function serializeUndefined(): string {
    
  120.   return '$undefined';
    
  121. }
    
  122. 
    
  123. function serializeDateFromDateJSON(dateJSON: string): string {
    
  124.   // JSON.stringify automatically calls Date.prototype.toJSON which calls toISOString.
    
  125.   // We need only tack on a $D prefix.
    
  126.   return '$D' + dateJSON;
    
  127. }
    
  128. 
    
  129. function serializeBigInt(n: bigint): string {
    
  130.   return '$n' + n.toString(10);
    
  131. }
    
  132. 
    
  133. function serializeMapID(id: number): string {
    
  134.   return '$Q' + id.toString(16);
    
  135. }
    
  136. 
    
  137. function serializeSetID(id: number): string {
    
  138.   return '$W' + id.toString(16);
    
  139. }
    
  140. 
    
  141. function escapeStringValue(value: string): string {
    
  142.   if (value[0] === '$') {
    
  143.     // We need to escape $ prefixed strings since we use those to encode
    
  144.     // references to IDs and as special symbol values.
    
  145.     return '$' + value;
    
  146.   } else {
    
  147.     return value;
    
  148.   }
    
  149. }
    
  150. 
    
  151. export function processReply(
    
  152.   root: ReactServerValue,
    
  153.   formFieldPrefix: string,
    
  154.   resolve: (string | FormData) => void,
    
  155.   reject: (error: mixed) => void,
    
  156. ): void {
    
  157.   let nextPartId = 1;
    
  158.   let pendingParts = 0;
    
  159.   let formData: null | FormData = null;
    
  160. 
    
  161.   function resolveToJSON(
    
  162.     this:
    
  163.       | {+[key: string | number]: ReactServerValue}
    
  164.       | $ReadOnlyArray<ReactServerValue>,
    
  165.     key: string,
    
  166.     value: ReactServerValue,
    
  167.   ): ReactJSONValue {
    
  168.     const parent = this;
    
  169. 
    
  170.     // Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
    
  171.     if (__DEV__) {
    
  172.       // $FlowFixMe[incompatible-use]
    
  173.       const originalValue = parent[key];
    
  174.       if (
    
  175.         typeof originalValue === 'object' &&
    
  176.         originalValue !== value &&
    
  177.         !(originalValue instanceof Date)
    
  178.       ) {
    
  179.         if (objectName(originalValue) !== 'Object') {
    
  180.           console.error(
    
  181.             'Only plain objects can be passed to Server Functions from the Client. ' +
    
  182.               '%s objects are not supported.%s',
    
  183.             objectName(originalValue),
    
  184.             describeObjectForErrorMessage(parent, key),
    
  185.           );
    
  186.         } else {
    
  187.           console.error(
    
  188.             'Only plain objects can be passed to Server Functions from the Client. ' +
    
  189.               'Objects with toJSON methods are not supported. Convert it manually ' +
    
  190.               'to a simple value before passing it to props.%s',
    
  191.             describeObjectForErrorMessage(parent, key),
    
  192.           );
    
  193.         }
    
  194.       }
    
  195.     }
    
  196. 
    
  197.     if (value === null) {
    
  198.       return null;
    
  199.     }
    
  200. 
    
  201.     if (typeof value === 'object') {
    
  202.       // $FlowFixMe[method-unbinding]
    
  203.       if (typeof value.then === 'function') {
    
  204.         // We assume that any object with a .then property is a "Thenable" type,
    
  205.         // or a Promise type. Either of which can be represented by a Promise.
    
  206.         if (formData === null) {
    
  207.           // Upgrade to use FormData to allow us to stream this value.
    
  208.           formData = new FormData();
    
  209.         }
    
  210.         pendingParts++;
    
  211.         const promiseId = nextPartId++;
    
  212.         const thenable: Thenable<any> = (value: any);
    
  213.         thenable.then(
    
  214.           partValue => {
    
  215.             const partJSON = JSON.stringify(partValue, resolveToJSON);
    
  216.             // $FlowFixMe[incompatible-type] We know it's not null because we assigned it above.
    
  217.             const data: FormData = formData;
    
  218.             // eslint-disable-next-line react-internal/safe-string-coercion
    
  219.             data.append(formFieldPrefix + promiseId, partJSON);
    
  220.             pendingParts--;
    
  221.             if (pendingParts === 0) {
    
  222.               resolve(data);
    
  223.             }
    
  224.           },
    
  225.           reason => {
    
  226.             // In the future we could consider serializing this as an error
    
  227.             // that throws on the server instead.
    
  228.             reject(reason);
    
  229.           },
    
  230.         );
    
  231.         return serializePromiseID(promiseId);
    
  232.       }
    
  233.       if (isArray(value)) {
    
  234.         // $FlowFixMe[incompatible-return]
    
  235.         return value;
    
  236.       }
    
  237.       // TODO: Should we the Object.prototype.toString.call() to test for cross-realm objects?
    
  238.       if (value instanceof FormData) {
    
  239.         if (formData === null) {
    
  240.           // Upgrade to use FormData to allow us to use rich objects as its values.
    
  241.           formData = new FormData();
    
  242.         }
    
  243.         const data: FormData = formData;
    
  244.         const refId = nextPartId++;
    
  245.         // Copy all the form fields with a prefix for this reference.
    
  246.         // These must come first in the form order because we assume that all the
    
  247.         // fields are available before this is referenced.
    
  248.         const prefix = formFieldPrefix + refId + '_';
    
  249.         // $FlowFixMe[prop-missing]: FormData has forEach.
    
  250.         value.forEach((originalValue: string | File, originalKey: string) => {
    
  251.           data.append(prefix + originalKey, originalValue);
    
  252.         });
    
  253.         return serializeFormDataReference(refId);
    
  254.       }
    
  255.       if (value instanceof Map) {
    
  256.         const partJSON = JSON.stringify(Array.from(value), resolveToJSON);
    
  257.         if (formData === null) {
    
  258.           formData = new FormData();
    
  259.         }
    
  260.         const mapId = nextPartId++;
    
  261.         formData.append(formFieldPrefix + mapId, partJSON);
    
  262.         return serializeMapID(mapId);
    
  263.       }
    
  264.       if (value instanceof Set) {
    
  265.         const partJSON = JSON.stringify(Array.from(value), resolveToJSON);
    
  266.         if (formData === null) {
    
  267.           formData = new FormData();
    
  268.         }
    
  269.         const setId = nextPartId++;
    
  270.         formData.append(formFieldPrefix + setId, partJSON);
    
  271.         return serializeSetID(setId);
    
  272.       }
    
  273.       const iteratorFn = getIteratorFn(value);
    
  274.       if (iteratorFn) {
    
  275.         return Array.from((value: any));
    
  276.       }
    
  277. 
    
  278.       // Verify that this is a simple plain object.
    
  279.       const proto = getPrototypeOf(value);
    
  280.       if (
    
  281.         proto !== ObjectPrototype &&
    
  282.         (proto === null || getPrototypeOf(proto) !== null)
    
  283.       ) {
    
  284.         throw new Error(
    
  285.           'Only plain objects, and a few built-ins, can be passed to Server Actions. ' +
    
  286.             'Classes or null prototypes are not supported.',
    
  287.         );
    
  288.       }
    
  289.       if (__DEV__) {
    
  290.         if ((value: any).$$typeof === REACT_ELEMENT_TYPE) {
    
  291.           console.error(
    
  292.             'React Element cannot be passed to Server Functions from the Client.%s',
    
  293.             describeObjectForErrorMessage(parent, key),
    
  294.           );
    
  295.         } else if ((value: any).$$typeof === REACT_LAZY_TYPE) {
    
  296.           console.error(
    
  297.             'React Lazy cannot be passed to Server Functions from the Client.%s',
    
  298.             describeObjectForErrorMessage(parent, key),
    
  299.           );
    
  300.         } else if ((value: any).$$typeof === REACT_PROVIDER_TYPE) {
    
  301.           console.error(
    
  302.             'React Context Providers cannot be passed to Server Functions from the Client.%s',
    
  303.             describeObjectForErrorMessage(parent, key),
    
  304.           );
    
  305.         } else if (objectName(value) !== 'Object') {
    
  306.           console.error(
    
  307.             'Only plain objects can be passed to Server Functions from the Client. ' +
    
  308.               '%s objects are not supported.%s',
    
  309.             objectName(value),
    
  310.             describeObjectForErrorMessage(parent, key),
    
  311.           );
    
  312.         } else if (!isSimpleObject(value)) {
    
  313.           console.error(
    
  314.             'Only plain objects can be passed to Server Functions from the Client. ' +
    
  315.               'Classes or other objects with methods are not supported.%s',
    
  316.             describeObjectForErrorMessage(parent, key),
    
  317.           );
    
  318.         } else if (Object.getOwnPropertySymbols) {
    
  319.           const symbols = Object.getOwnPropertySymbols(value);
    
  320.           if (symbols.length > 0) {
    
  321.             console.error(
    
  322.               'Only plain objects can be passed to Server Functions from the Client. ' +
    
  323.                 'Objects with symbol properties like %s are not supported.%s',
    
  324.               symbols[0].description,
    
  325.               describeObjectForErrorMessage(parent, key),
    
  326.             );
    
  327.           }
    
  328.         }
    
  329.       }
    
  330. 
    
  331.       // $FlowFixMe[incompatible-return]
    
  332.       return value;
    
  333.     }
    
  334. 
    
  335.     if (typeof value === 'string') {
    
  336.       // TODO: Maybe too clever. If we support URL there's no similar trick.
    
  337.       if (value[value.length - 1] === 'Z') {
    
  338.         // Possibly a Date, whose toJSON automatically calls toISOString
    
  339.         // $FlowFixMe[incompatible-use]
    
  340.         const originalValue = parent[key];
    
  341.         if (originalValue instanceof Date) {
    
  342.           return serializeDateFromDateJSON(value);
    
  343.         }
    
  344.       }
    
  345. 
    
  346.       return escapeStringValue(value);
    
  347.     }
    
  348. 
    
  349.     if (typeof value === 'boolean') {
    
  350.       return value;
    
  351.     }
    
  352. 
    
  353.     if (typeof value === 'number') {
    
  354.       return serializeNumber(value);
    
  355.     }
    
  356. 
    
  357.     if (typeof value === 'undefined') {
    
  358.       return serializeUndefined();
    
  359.     }
    
  360. 
    
  361.     if (typeof value === 'function') {
    
  362.       const metaData = knownServerReferences.get(value);
    
  363.       if (metaData !== undefined) {
    
  364.         const metaDataJSON = JSON.stringify(metaData, resolveToJSON);
    
  365.         if (formData === null) {
    
  366.           // Upgrade to use FormData to allow us to stream this value.
    
  367.           formData = new FormData();
    
  368.         }
    
  369.         // The reference to this function came from the same client so we can pass it back.
    
  370.         const refId = nextPartId++;
    
  371.         // eslint-disable-next-line react-internal/safe-string-coercion
    
  372.         formData.set(formFieldPrefix + refId, metaDataJSON);
    
  373.         return serializeServerReferenceID(refId);
    
  374.       }
    
  375.       throw new Error(
    
  376.         'Client Functions cannot be passed directly to Server Functions. ' +
    
  377.           'Only Functions passed from the Server can be passed back again.',
    
  378.       );
    
  379.     }
    
  380. 
    
  381.     if (typeof value === 'symbol') {
    
  382.       // $FlowFixMe[incompatible-type] `description` might be undefined
    
  383.       const name: string = value.description;
    
  384.       if (Symbol.for(name) !== value) {
    
  385.         throw new Error(
    
  386.           'Only global symbols received from Symbol.for(...) can be passed to Server Functions. ' +
    
  387.             `The symbol Symbol.for(${
    
  388.               // $FlowFixMe[incompatible-type] `description` might be undefined
    
  389.               value.description
    
  390.             }) cannot be found among global symbols.`,
    
  391.         );
    
  392.       }
    
  393.       return serializeSymbolReference(name);
    
  394.     }
    
  395. 
    
  396.     if (typeof value === 'bigint') {
    
  397.       return serializeBigInt(value);
    
  398.     }
    
  399. 
    
  400.     throw new Error(
    
  401.       `Type ${typeof value} is not supported as an argument to a Server Function.`,
    
  402.     );
    
  403.   }
    
  404. 
    
  405.   // $FlowFixMe[incompatible-type] it's not going to be undefined because we'll encode it.
    
  406.   const json: string = JSON.stringify(root, resolveToJSON);
    
  407.   if (formData === null) {
    
  408.     // If it's a simple data structure, we just use plain JSON.
    
  409.     resolve(json);
    
  410.   } else {
    
  411.     // Otherwise, we use FormData to let us stream in the result.
    
  412.     formData.set(formFieldPrefix + '0', json);
    
  413.     if (pendingParts === 0) {
    
  414.       // $FlowFixMe[incompatible-call] this has already been refined.
    
  415.       resolve(formData);
    
  416.     }
    
  417.   }
    
  418. }
    
  419. 
    
  420. const boundCache: WeakMap<
    
  421.   {id: ServerReferenceId, bound: null | Thenable<Array<any>>},
    
  422.   Thenable<FormData>,
    
  423. > = new WeakMap();
    
  424. 
    
  425. function encodeFormData(reference: any): Thenable<FormData> {
    
  426.   let resolve, reject;
    
  427.   // We need to have a handle on the thenable so that we can synchronously set
    
  428.   // its status from processReply, when it can complete synchronously.
    
  429.   const thenable: Thenable<FormData> = new Promise((res, rej) => {
    
  430.     resolve = res;
    
  431.     reject = rej;
    
  432.   });
    
  433.   processReply(
    
  434.     reference,
    
  435.     '',
    
  436.     (body: string | FormData) => {
    
  437.       if (typeof body === 'string') {
    
  438.         const data = new FormData();
    
  439.         data.append('0', body);
    
  440.         body = data;
    
  441.       }
    
  442.       const fulfilled: FulfilledThenable<FormData> = (thenable: any);
    
  443.       fulfilled.status = 'fulfilled';
    
  444.       fulfilled.value = body;
    
  445.       resolve(body);
    
  446.     },
    
  447.     e => {
    
  448.       const rejected: RejectedThenable<FormData> = (thenable: any);
    
  449.       rejected.status = 'rejected';
    
  450.       rejected.reason = e;
    
  451.       reject(e);
    
  452.     },
    
  453.   );
    
  454.   return thenable;
    
  455. }
    
  456. 
    
  457. export function encodeFormAction(
    
  458.   this: any => Promise<any>,
    
  459.   identifierPrefix: string,
    
  460. ): ReactCustomFormAction {
    
  461.   const reference = knownServerReferences.get(this);
    
  462.   if (!reference) {
    
  463.     throw new Error(
    
  464.       'Tried to encode a Server Action from a different instance than the encoder is from. ' +
    
  465.         'This is a bug in React.',
    
  466.     );
    
  467.   }
    
  468.   let data: null | FormData = null;
    
  469.   let name;
    
  470.   const boundPromise = reference.bound;
    
  471.   if (boundPromise !== null) {
    
  472.     let thenable = boundCache.get(reference);
    
  473.     if (!thenable) {
    
  474.       thenable = encodeFormData(reference);
    
  475.       boundCache.set(reference, thenable);
    
  476.     }
    
  477.     if (thenable.status === 'rejected') {
    
  478.       throw thenable.reason;
    
  479.     } else if (thenable.status !== 'fulfilled') {
    
  480.       throw thenable;
    
  481.     }
    
  482.     const encodedFormData = thenable.value;
    
  483.     // This is hacky but we need the identifier prefix to be added to
    
  484.     // all fields but the suspense cache would break since we might get
    
  485.     // a new identifier each time. So we just append it at the end instead.
    
  486.     const prefixedData = new FormData();
    
  487.     // $FlowFixMe[prop-missing]
    
  488.     encodedFormData.forEach((value: string | File, key: string) => {
    
  489.       prefixedData.append('$ACTION_' + identifierPrefix + ':' + key, value);
    
  490.     });
    
  491.     data = prefixedData;
    
  492.     // We encode the name of the prefix containing the data.
    
  493.     name = '$ACTION_REF_' + identifierPrefix;
    
  494.   } else {
    
  495.     // This is the simple case so we can just encode the ID.
    
  496.     name = '$ACTION_ID_' + reference.id;
    
  497.   }
    
  498.   return {
    
  499.     name: name,
    
  500.     method: 'POST',
    
  501.     encType: 'multipart/form-data',
    
  502.     data: data,
    
  503.   };
    
  504. }
    
  505. 
    
  506. function isSignatureEqual(
    
  507.   this: any => Promise<any>,
    
  508.   referenceId: ServerReferenceId,
    
  509.   numberOfBoundArgs: number,
    
  510. ): boolean {
    
  511.   const reference = knownServerReferences.get(this);
    
  512.   if (!reference) {
    
  513.     throw new Error(
    
  514.       'Tried to encode a Server Action from a different instance than the encoder is from. ' +
    
  515.         'This is a bug in React.',
    
  516.     );
    
  517.   }
    
  518.   if (reference.id !== referenceId) {
    
  519.     // These are different functions.
    
  520.     return false;
    
  521.   }
    
  522.   // Now check if the number of bound arguments is the same.
    
  523.   const boundPromise = reference.bound;
    
  524.   if (boundPromise === null) {
    
  525.     // No bound arguments.
    
  526.     return numberOfBoundArgs === 0;
    
  527.   }
    
  528.   // Unwrap the bound arguments array by suspending, if necessary. As with
    
  529.   // encodeFormData, this means isSignatureEqual can only be called while React
    
  530.   // is rendering.
    
  531.   switch (boundPromise.status) {
    
  532.     case 'fulfilled': {
    
  533.       const boundArgs = boundPromise.value;
    
  534.       return boundArgs.length === numberOfBoundArgs;
    
  535.     }
    
  536.     case 'pending': {
    
  537.       throw boundPromise;
    
  538.     }
    
  539.     case 'rejected': {
    
  540.       throw boundPromise.reason;
    
  541.     }
    
  542.     default: {
    
  543.       if (typeof boundPromise.status === 'string') {
    
  544.         // Only instrument the thenable if the status if not defined.
    
  545.       } else {
    
  546.         const pendingThenable: PendingThenable<Array<any>> =
    
  547.           (boundPromise: any);
    
  548.         pendingThenable.status = 'pending';
    
  549.         pendingThenable.then(
    
  550.           (boundArgs: Array<any>) => {
    
  551.             const fulfilledThenable: FulfilledThenable<Array<any>> =
    
  552.               (boundPromise: any);
    
  553.             fulfilledThenable.status = 'fulfilled';
    
  554.             fulfilledThenable.value = boundArgs;
    
  555.           },
    
  556.           (error: mixed) => {
    
  557.             const rejectedThenable: RejectedThenable<number> =
    
  558.               (boundPromise: any);
    
  559.             rejectedThenable.status = 'rejected';
    
  560.             rejectedThenable.reason = error;
    
  561.           },
    
  562.         );
    
  563.       }
    
  564.       throw boundPromise;
    
  565.     }
    
  566.   }
    
  567. }
    
  568. 
    
  569. export function registerServerReference(
    
  570.   proxy: any,
    
  571.   reference: {id: ServerReferenceId, bound: null | Thenable<Array<any>>},
    
  572. ) {
    
  573.   // Expose encoder for use by SSR, as well as a special bind that can be used to
    
  574.   // keep server capabilities.
    
  575.   if (usedWithSSR) {
    
  576.     // Only expose this in builds that would actually use it. Not needed on the client.
    
  577.     Object.defineProperties((proxy: any), {
    
  578.       $$FORM_ACTION: {value: encodeFormAction},
    
  579.       $$IS_SIGNATURE_EQUAL: {value: isSignatureEqual},
    
  580.       bind: {value: bind},
    
  581.     });
    
  582.   }
    
  583.   knownServerReferences.set(proxy, reference);
    
  584. }
    
  585. 
    
  586. // $FlowFixMe[method-unbinding]
    
  587. const FunctionBind = Function.prototype.bind;
    
  588. // $FlowFixMe[method-unbinding]
    
  589. const ArraySlice = Array.prototype.slice;
    
  590. function bind(this: Function) {
    
  591.   // $FlowFixMe[unsupported-syntax]
    
  592.   const newFn = FunctionBind.apply(this, arguments);
    
  593.   const reference = knownServerReferences.get(this);
    
  594.   if (reference) {
    
  595.     const args = ArraySlice.call(arguments, 1);
    
  596.     let boundPromise = null;
    
  597.     if (reference.bound !== null) {
    
  598.       boundPromise = Promise.resolve((reference.bound: any)).then(boundArgs =>
    
  599.         boundArgs.concat(args),
    
  600.       );
    
  601.     } else {
    
  602.       boundPromise = Promise.resolve(args);
    
  603.     }
    
  604.     registerServerReference(newFn, {id: reference.id, bound: boundPromise});
    
  605.   }
    
  606.   return newFn;
    
  607. }
    
  608. 
    
  609. export function createServerReference<A: Iterable<any>, T>(
    
  610.   id: ServerReferenceId,
    
  611.   callServer: CallServerCallback,
    
  612. ): (...A) => Promise<T> {
    
  613.   const proxy = function (): Promise<T> {
    
  614.     // $FlowFixMe[method-unbinding]
    
  615.     const args = Array.prototype.slice.call(arguments);
    
  616.     return callServer(id, args);
    
  617.   };
    
  618.   registerServerReference(proxy, {id, bound: null});
    
  619.   return proxy;
    
  620. }