/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @noflow * @nolint * @preventMunge * @preserve-invariant-messages */ 'use strict'; if (__DEV__) { (function() { "use strict"; var ReactFlightDOMRelayClientIntegration = require("ReactFlightDOMRelayClientIntegration"); var React = require("react"); var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare function isArray(a) { return isArrayImpl(a); } function resolveModuleReference(bundlerConfig, moduleData) { return ReactFlightDOMRelayClientIntegration.resolveModuleReference( moduleData ); } function parseModelRecursively(response, parentObj, key, value) { if (typeof value === "string") { return parseModelString(response, parentObj, key, value); } if (typeof value === "object" && value !== null) { if (isArray(value)) { var parsedValue = []; for (var i = 0; i < value.length; i++) { parsedValue[i] = parseModelRecursively( response, value, "" + i, value[i] ); } return parseModelTuple(response, parsedValue); } else { var _parsedValue = {}; for (var innerKey in value) { _parsedValue[innerKey] = parseModelRecursively( response, value, innerKey, value[innerKey] ); } return _parsedValue; } } return value; } var dummy = {}; function parseModel(response, json) { return parseModelRecursively(response, dummy, "", json); } // ATTENTION // When adding new symbols to this file, // Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols' // The Symbol used to tag the ReactElement-like types. var REACT_ELEMENT_TYPE = Symbol.for("react.element"); var REACT_LAZY_TYPE = Symbol.for("react.lazy"); var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; var ContextRegistry = ReactSharedInternals.ContextRegistry; var PENDING = "pending"; var BLOCKED = "blocked"; var RESOLVED_MODEL = "resolved_model"; var RESOLVED_MODULE = "resolved_module"; var INITIALIZED = "fulfilled"; var ERRORED = "rejected"; function Chunk(status, value, reason, response) { this.status = status; this.value = value; this.reason = reason; this._response = response; } // We subclass Promise.prototype so that we get other methods like .catch Chunk.prototype = Object.create(Promise.prototype); // TODO: This doesn't return a new Promise chain unlike the real .then Chunk.prototype.then = function(resolve, reject) { var chunk = this; // If we have resolved content, we try to initialize it first which // might put us back into one of the other states. switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); break; case RESOLVED_MODULE: initializeModuleChunk(chunk); break; } // The status might have changed after initialization. switch (chunk.status) { case INITIALIZED: resolve(chunk.value); break; case PENDING: case BLOCKED: if (resolve) { if (chunk.value === null) { chunk.value = []; } chunk.value.push(resolve); } if (reject) { if (chunk.reason === null) { chunk.reason = []; } chunk.reason.push(reject); } break; default: reject(chunk.reason); break; } }; function readChunk(chunk) { // If we have resolved content, we try to initialize it first which // might put us back into one of the other states. switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); break; case RESOLVED_MODULE: initializeModuleChunk(chunk); break; } // The status might have changed after initialization. switch (chunk.status) { case INITIALIZED: return chunk.value; case PENDING: case BLOCKED: // eslint-disable-next-line no-throw-literal throw chunk; default: throw chunk.reason; } } function getRoot(response) { var chunk = getChunk(response, 0); return chunk; } function createPendingChunk(response) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(PENDING, null, null, response); } function createBlockedChunk(response) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(BLOCKED, null, null, response); } function createErrorChunk(response, error) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(ERRORED, null, error, response); } function createInitializedChunk(response, value) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(INITIALIZED, value, null, response); } function wakeChunk(listeners, value) { for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; listener(value); } } function wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners) { switch (chunk.status) { case INITIALIZED: wakeChunk(resolveListeners, chunk.value); break; case PENDING: case BLOCKED: chunk.value = resolveListeners; chunk.reason = rejectListeners; break; case ERRORED: if (rejectListeners) { wakeChunk(rejectListeners, chunk.reason); } break; } } function triggerErrorOnChunk(chunk, error) { if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // We already resolved. We didn't expect to see this. return; } var listeners = chunk.reason; var erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; if (listeners !== null) { wakeChunk(listeners, error); } } function createResolvedModelChunk(response, value) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(RESOLVED_MODEL, value, null, response); } function createResolvedModuleChunk(response, value) { // $FlowFixMe Flow doesn't support functions as constructors return new Chunk(RESOLVED_MODULE, value, null, response); } function resolveModelChunk(chunk, value) { if (chunk.status !== PENDING) { // We already resolved. We didn't expect to see this. return; } var resolveListeners = chunk.value; var rejectListeners = chunk.reason; var resolvedChunk = chunk; resolvedChunk.status = RESOLVED_MODEL; resolvedChunk.value = value; if (resolveListeners !== null) { // This is unfortunate that we're reading this eagerly if // we already have listeners attached since they might no // longer be rendered or might not be the highest pri. initializeModelChunk(resolvedChunk); // The status might have changed after initialization. wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners); } } function resolveModuleChunk(chunk, value) { if (chunk.status !== PENDING && chunk.status !== BLOCKED) { // We already resolved. We didn't expect to see this. return; } var resolveListeners = chunk.value; var rejectListeners = chunk.reason; var resolvedChunk = chunk; resolvedChunk.status = RESOLVED_MODULE; resolvedChunk.value = value; if (resolveListeners !== null) { initializeModuleChunk(resolvedChunk); wakeChunkIfInitialized(chunk, resolveListeners, rejectListeners); } } var initializingChunk = null; var initializingChunkBlockedModel = null; function initializeModelChunk(chunk) { var prevChunk = initializingChunk; var prevBlocked = initializingChunkBlockedModel; initializingChunk = chunk; initializingChunkBlockedModel = null; try { var _value = parseModel(chunk._response, chunk.value); if ( initializingChunkBlockedModel !== null && initializingChunkBlockedModel.deps > 0 ) { initializingChunkBlockedModel.value = _value; // We discovered new dependencies on modules that are not yet resolved. // We have to go the BLOCKED state until they're resolved. var blockedChunk = chunk; blockedChunk.status = BLOCKED; blockedChunk.value = null; blockedChunk.reason = null; } else { var initializedChunk = chunk; initializedChunk.status = INITIALIZED; initializedChunk.value = _value; } } catch (error) { var erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; } finally { initializingChunk = prevChunk; initializingChunkBlockedModel = prevBlocked; } } function initializeModuleChunk(chunk) { try { var _value2 = ReactFlightDOMRelayClientIntegration.requireModule( chunk.value ); var initializedChunk = chunk; initializedChunk.status = INITIALIZED; initializedChunk.value = _value2; } catch (error) { var erroredChunk = chunk; erroredChunk.status = ERRORED; erroredChunk.reason = error; } } // Report that any missing chunks in the model is now going to throw this // error upon read. Also notify any pending promises. function reportGlobalError(response, error) { response._chunks.forEach(function(chunk) { // If this chunk was already resolved or errored, it won't // trigger an error but if it wasn't then we need to // because we won't be getting any new data to resolve it. if (chunk.status === PENDING) { triggerErrorOnChunk(chunk, error); } }); } function createElement(type, key, props) { var element = { // This tag allows us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: type, key: key, ref: null, props: props, // Record the component responsible for creating this element. _owner: null }; { // We don't really need to add any of these but keeping them for good measure. // Unfortunately, _store is enumerable in jest matchers so for equality to // work, I need to keep it or make _store non-enumerable in the other file. element._store = {}; Object.defineProperty(element._store, "validated", { configurable: false, enumerable: false, writable: true, value: true // This element has already been validated on the server. }); Object.defineProperty(element, "_self", { configurable: false, enumerable: false, writable: false, value: null }); Object.defineProperty(element, "_source", { configurable: false, enumerable: false, writable: false, value: null }); } return element; } function createLazyChunkWrapper(chunk) { var lazyType = { $$typeof: REACT_LAZY_TYPE, _payload: chunk, _init: readChunk }; return lazyType; } function getChunk(response, id) { var chunks = response._chunks; var chunk = chunks.get(id); if (!chunk) { chunk = createPendingChunk(response); chunks.set(id, chunk); } return chunk; } function createModelResolver(chunk, parentObject, key) { var blocked; if (initializingChunkBlockedModel) { blocked = initializingChunkBlockedModel; blocked.deps++; } else { blocked = initializingChunkBlockedModel = { deps: 1, value: null }; } return function(value) { parentObject[key] = value; blocked.deps--; if (blocked.deps === 0) { if (chunk.status !== BLOCKED) { return; } var resolveListeners = chunk.value; var initializedChunk = chunk; initializedChunk.status = INITIALIZED; initializedChunk.value = blocked.value; if (resolveListeners !== null) { wakeChunk(resolveListeners, blocked.value); } } }; } function createModelReject(chunk) { return function(error) { return triggerErrorOnChunk(chunk, error); }; } function parseModelString(response, parentObject, key, value) { switch (value[0]) { case "$": { if (value === "$") { return REACT_ELEMENT_TYPE; } else if (value[1] === "$" || value[1] === "@") { // This was an escaped string value. return value.substring(1); } else { var id = parseInt(value.substring(1), 16); var chunk = getChunk(response, id); switch (chunk.status) { case RESOLVED_MODEL: initializeModelChunk(chunk); break; case RESOLVED_MODULE: initializeModuleChunk(chunk); break; } // The status might have changed after initialization. switch (chunk.status) { case INITIALIZED: return chunk.value; case PENDING: case BLOCKED: var parentChunk = initializingChunk; chunk.then( createModelResolver(parentChunk, parentObject, key), createModelReject(parentChunk) ); return null; default: throw chunk.reason; } } } case "@": { var _id = parseInt(value.substring(1), 16); var _chunk = getChunk(response, _id); // We create a React.lazy wrapper around any lazy values. // When passed into React, we'll know how to suspend on this. return createLazyChunkWrapper(_chunk); } } return value; } function parseModelTuple(response, value) { var tuple = value; if (tuple[0] === REACT_ELEMENT_TYPE) { // TODO: Consider having React just directly accept these arrays as elements. // Or even change the ReactElement type to be an array. return createElement(tuple[1], tuple[2], tuple[3]); } return value; } function createResponse(bundlerConfig) { var chunks = new Map(); var response = { _bundlerConfig: bundlerConfig, _chunks: chunks }; return response; } function resolveModel(response, id, model) { var chunks = response._chunks; var chunk = chunks.get(id); if (!chunk) { chunks.set(id, createResolvedModelChunk(response, model)); } else { resolveModelChunk(chunk, model); } } function resolveModule(response, id, model) { var chunks = response._chunks; var chunk = chunks.get(id); var moduleMetaData = parseModel(response, model); var moduleReference = resolveModuleReference( response._bundlerConfig, moduleMetaData ); // TODO: Add an option to encode modules that are lazy loaded. // For now we preload all modules as early as possible since it's likely // that we'll need them. var promise = ReactFlightDOMRelayClientIntegration.preloadModule( moduleReference ); if (promise) { var blockedChunk; if (!chunk) { // Technically, we should just treat promise as the chunk in this // case. Because it'll just behave as any other promise. blockedChunk = createBlockedChunk(response); chunks.set(id, blockedChunk); } else { // This can't actually happen because we don't have any forward // references to modules. blockedChunk = chunk; blockedChunk.status = BLOCKED; } promise.then( function() { return resolveModuleChunk(blockedChunk, moduleReference); }, function(error) { return triggerErrorOnChunk(blockedChunk, error); } ); } else { if (!chunk) { chunks.set(id, createResolvedModuleChunk(response, moduleReference)); } else { // This can't actually happen because we don't have any forward // references to modules. resolveModuleChunk(chunk, moduleReference); } } } function resolveSymbol(response, id, name) { var chunks = response._chunks; // We assume that we'll always emit the symbol before anything references it // to save a few bytes. chunks.set(id, createInitializedChunk(response, Symbol.for(name))); } function resolveErrorDev(response, id, digest, message, stack) { var error = new Error( message || "An error occurred in the Server Components render but no message was provided" ); error.stack = stack; error.digest = digest; var errorWithDigest = error; var chunks = response._chunks; var chunk = chunks.get(id); if (!chunk) { chunks.set(id, createErrorChunk(response, errorWithDigest)); } else { triggerErrorOnChunk(chunk, errorWithDigest); } } function close(response) { // In case there are any remaining unresolved chunks, they won't // be resolved now. So we need to issue an error to those. // Ideally we should be able to early bail out if we kept a // ref count of pending chunks. reportGlobalError(response, new Error("Connection closed.")); } function resolveRow(response, chunk) { if (chunk[0] === "J") { // $FlowFixMe unable to refine on array indices resolveModel(response, chunk[1], chunk[2]); } else if (chunk[0] === "M") { // $FlowFixMe unable to refine on array indices resolveModule(response, chunk[1], chunk[2]); } else if (chunk[0] === "S") { // $FlowFixMe: Flow doesn't support disjoint unions on tuples. resolveSymbol(response, chunk[1], chunk[2]); } else { { resolveErrorDev( response, chunk[1], // $FlowFixMe: Flow doesn't support disjoint unions on tuples. chunk[2].digest, // $FlowFixMe: Flow doesn't support disjoint unions on tuples. chunk[2].message || "", // $FlowFixMe: Flow doesn't support disjoint unions on tuples. chunk[2].stack || "" ); } } } exports.close = close; exports.createResponse = createResponse; exports.getRoot = getRoot; exports.resolveRow = resolveRow; })(); }