Skip to content

Commit 55e0b47

Browse files
committed
Only skip past the end boundary if there is a newline character
1 parent 90229eb commit 55e0b47

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,7 @@ export function processBinaryChunk(
904904
const buffer = response._buffer;
905905
const chunkLength = chunk.length;
906906
while (i < chunkLength) {
907-
let lastIdx = -1;
907+
let lastIdx;
908908
switch (rowState) {
909909
case ROW_ID: {
910910
const byte = chunk[i++];
@@ -950,29 +950,33 @@ export function processBinaryChunk(
950950
}
951951
case ROW_CHUNK_BY_LENGTH: {
952952
// We're looking for the remaining byte length
953-
if (i + rowLength <= chunk.length) {
954-
lastIdx = i + rowLength;
953+
lastIdx = i + rowLength;
954+
if (lastIdx > chunk.length) {
955+
lastIdx = -1;
955956
}
956957
break;
957958
}
958959
}
960+
const offset = chunk.byteOffset + i;
959961
if (lastIdx > -1) {
960962
// We found the last chunk of the row
961-
const offset = chunk.byteOffset + i;
962963
const length = lastIdx - i;
963964
const lastChunk = new Uint8Array(chunk.buffer, offset, length);
964965
processFullRow(response, rowID, rowTag, buffer, lastChunk);
965966
// Reset state machine for a new row
967+
i = lastIdx;
968+
if (rowState === ROW_CHUNK_BY_NEWLINE) {
969+
// If we're trailing by a newline we need to skip it.
970+
i++;
971+
}
966972
rowState = ROW_ID;
967973
rowTag = 0;
968974
rowID = 0;
969975
rowLength = 0;
970976
buffer.length = 0;
971-
i = lastIdx + 1;
972977
} else {
973978
// The rest of this row is in a future chunk. We stash the rest of the
974979
// current chunk until we can process the full row.
975-
const offset = chunk.byteOffset + i;
976980
const length = chunk.byteLength - i;
977981
const remainingSlice = new Uint8Array(chunk.buffer, offset, length);
978982
buffer.push(remainingSlice);

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,37 @@ describe('ReactFlightDOMEdge', () => {
4242
use = React.use;
4343
});
4444

45+
function passThrough(stream) {
46+
// Simulate more realistic network by splitting up and rejoining some chunks.
47+
// This lets us test that we don't accidentally rely on particular bounds of the chunks.
48+
return new ReadableStream({
49+
async start(controller) {
50+
const reader = stream.getReader();
51+
let prevChunk = new Uint8Array(0);
52+
function push() {
53+
reader.read().then(({done, value}) => {
54+
if (done) {
55+
controller.enqueue(prevChunk);
56+
controller.close();
57+
return;
58+
}
59+
const chunk = new Uint8Array(prevChunk.length + value.length);
60+
chunk.set(prevChunk, 0);
61+
chunk.set(value, prevChunk.length);
62+
if (chunk.length > 50) {
63+
controller.enqueue(chunk.subarray(0, chunk.length - 50));
64+
prevChunk = chunk.subarray(chunk.length - 50);
65+
} else {
66+
prevChunk = chunk;
67+
}
68+
push();
69+
});
70+
}
71+
push();
72+
},
73+
});
74+
}
75+
4576
async function readResult(stream) {
4677
const reader = stream.getReader();
4778
let result = '';
@@ -101,15 +132,17 @@ describe('ReactFlightDOMEdge', () => {
101132

102133
it('should encode long string in a compact format', async () => {
103134
const testString = '"\n\t'.repeat(500) + '🙃';
135+
const testString2 = 'hello'.repeat(400);
104136

105137
const stream = ReactServerDOMServer.renderToReadableStream({
106138
text: testString,
139+
text2: testString2,
107140
});
108-
const [stream1, stream2] = stream.tee();
141+
const [stream1, stream2] = passThrough(stream).tee();
109142

110143
const serializedContent = await readResult(stream1);
111144
// The content should be compact an unescaped
112-
expect(serializedContent.length).toBeLessThan(2000);
145+
expect(serializedContent.length).toBeLessThan(4000);
113146
expect(serializedContent).not.toContain('\\n');
114147
expect(serializedContent).not.toContain('\\t');
115148
expect(serializedContent).not.toContain('\\"');
@@ -118,5 +151,6 @@ describe('ReactFlightDOMEdge', () => {
118151
const result = await ReactServerDOMClient.createFromReadableStream(stream2);
119152
// Should still match the result when parsed
120153
expect(result.text).toBe(testString);
154+
expect(result.text2).toBe(testString2);
121155
});
122156
});

0 commit comments

Comments
 (0)