mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
76a6dbcb9a
* Encode Symbols as special rows that can be referenced by models
If a symbol was extracted from Symbol.for(...) then we can reliably
recreate the same symbol on the client.
S123:"react.suspense"
M456:{mySymbol: '$123'}
This doesn't suffer from the XSS problem because you have to write actual
code to create one of these symbols. That problem is only a problem because
values pass through common other usages of JSON which are not secure.
Since React encodes its built-ins as symbols, we can now use them as long
as its props are serializable. Like Suspense.
* Refactor resolution to avoid memo hack
Going through createElement isn't quite equivalent for ref and key in props.
* Reuse symbol ids that have already been written earlier in the stream
134 lines
3.7 KiB
JavaScript
134 lines
3.7 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {Response} from './ReactFlightClientHostConfigStream';
|
|
|
|
import {
|
|
resolveModule,
|
|
resolveModel,
|
|
resolveSymbol,
|
|
resolveError,
|
|
createResponse as createResponseBase,
|
|
parseModelString,
|
|
parseModelTuple,
|
|
} from './ReactFlightClient';
|
|
|
|
import {
|
|
readPartialStringChunk,
|
|
readFinalStringChunk,
|
|
supportsBinaryStreams,
|
|
createStringDecoder,
|
|
} from './ReactFlightClientHostConfig';
|
|
|
|
export type {Response};
|
|
|
|
function processFullRow(response: Response, row: string): void {
|
|
if (row === '') {
|
|
return;
|
|
}
|
|
const tag = row[0];
|
|
// When tags that are not text are added, check them here before
|
|
// parsing the row as text.
|
|
// switch (tag) {
|
|
// }
|
|
const colon = row.indexOf(':', 1);
|
|
const id = parseInt(row.substring(1, colon), 16);
|
|
const text = row.substring(colon + 1);
|
|
switch (tag) {
|
|
case 'J': {
|
|
resolveModel(response, id, text);
|
|
return;
|
|
}
|
|
case 'M': {
|
|
resolveModule(response, id, text);
|
|
return;
|
|
}
|
|
case 'S': {
|
|
resolveSymbol(response, id, JSON.parse(text));
|
|
return;
|
|
}
|
|
case 'E': {
|
|
const errorInfo = JSON.parse(text);
|
|
resolveError(response, id, errorInfo.message, errorInfo.stack);
|
|
return;
|
|
}
|
|
default: {
|
|
throw new Error(
|
|
"Error parsing the data. It's probably an error code or network corruption.",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function processStringChunk(
|
|
response: Response,
|
|
chunk: string,
|
|
offset: number,
|
|
): void {
|
|
let linebreak = chunk.indexOf('\n', offset);
|
|
while (linebreak > -1) {
|
|
const fullrow = response._partialRow + chunk.substring(offset, linebreak);
|
|
processFullRow(response, fullrow);
|
|
response._partialRow = '';
|
|
offset = linebreak + 1;
|
|
linebreak = chunk.indexOf('\n', offset);
|
|
}
|
|
response._partialRow += chunk.substring(offset);
|
|
}
|
|
|
|
export function processBinaryChunk(
|
|
response: Response,
|
|
chunk: Uint8Array,
|
|
): void {
|
|
if (!supportsBinaryStreams) {
|
|
throw new Error("This environment don't support binary chunks.");
|
|
}
|
|
const stringDecoder = response._stringDecoder;
|
|
let linebreak = chunk.indexOf(10); // newline
|
|
while (linebreak > -1) {
|
|
const fullrow =
|
|
response._partialRow +
|
|
readFinalStringChunk(stringDecoder, chunk.subarray(0, linebreak));
|
|
processFullRow(response, fullrow);
|
|
response._partialRow = '';
|
|
chunk = chunk.subarray(linebreak + 1);
|
|
linebreak = chunk.indexOf(10); // newline
|
|
}
|
|
response._partialRow += readPartialStringChunk(stringDecoder, chunk);
|
|
}
|
|
|
|
function createFromJSONCallback(response: Response) {
|
|
return function(key: string, value: JSONValue) {
|
|
if (typeof value === 'string') {
|
|
// We can't use .bind here because we need the "this" value.
|
|
return parseModelString(response, this, value);
|
|
}
|
|
if (typeof value === 'object' && value !== null) {
|
|
return parseModelTuple(response, value);
|
|
}
|
|
return value;
|
|
};
|
|
}
|
|
|
|
export function createResponse(): Response {
|
|
// NOTE: CHECK THE COMPILER OUTPUT EACH TIME YOU CHANGE THIS.
|
|
// It should be inlined to one object literal but minor changes can break it.
|
|
const stringDecoder = supportsBinaryStreams ? createStringDecoder() : null;
|
|
const response: any = createResponseBase();
|
|
response._partialRow = '';
|
|
if (supportsBinaryStreams) {
|
|
response._stringDecoder = stringDecoder;
|
|
}
|
|
// Don't inline this call because it causes closure to outline the call above.
|
|
response._fromJSON = createFromJSONCallback(response);
|
|
return response;
|
|
}
|
|
|
|
export {reportGlobalError, close} from './ReactFlightClient';
|