···11+---
22+'@urql/core': patch
33+---
44+55+Fix `fetchSource` not text-decoding response chunks as streams, which could cause UTF-8 decoding to break.
+23-18
packages/core/src/internal/fetchSource.ts
···2626 * The implementation in this file needs to make certain accommodations for:
2727 * - The Web Fetch API
2828 * - Non-browser or polyfill Fetch APIs
2929- * - Node.js-like Fetch implementations (see `toString` below)
2929+ * - Node.js-like Fetch implementations
3030 *
3131 * GraphQL over SSE has a reference implementation, which supports non-HTTP/2
3232 * modes and is a faithful implementation of the spec.
···4747import type { Operation, OperationResult, ExecutionResult } from '../types';
4848import { makeResult, makeErrorResult, mergeResultPatch } from '../utils';
49495050-const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
5150const boundaryHeaderRe = /boundary="?([^=";]+)"?/i;
5251const eventStreamRe = /data: ?([^\n]+)/;
53525453type ChunkData = Buffer | Uint8Array;
55545656-// NOTE: We're avoiding referencing the `Buffer` global here to prevent
5757-// auto-polyfilling in Webpack
5858-const toString = (input: Buffer | ArrayBuffer): string =>
5959- input.constructor.name === 'Buffer'
6060- ? (input as Buffer).toString()
6161- : decoder!.decode(input as ArrayBuffer);
6262-6363-async function* streamBody(response: Response): AsyncIterableIterator<string> {
5555+async function* streamBody(
5656+ response: Response
5757+): AsyncIterableIterator<ChunkData> {
6458 if (response.body![Symbol.asyncIterator]) {
6565- for await (const chunk of response.body! as any)
6666- yield toString(chunk as ChunkData);
5959+ for await (const chunk of response.body! as any) yield chunk as ChunkData;
6760 } else {
6861 const reader = response.body!.getReader();
6962 let result: ReadableStreamReadResult<ChunkData>;
7063 try {
7171- while (!(result = await reader.read()).done) yield toString(result.value);
6464+ while (!(result = await reader.read()).done) yield result.value;
7265 } finally {
7366 reader.cancel();
7467 }
7568 }
7669}
77707878-async function* split(
7979- chunks: AsyncIterableIterator<string>,
7171+async function* streamToBoundedChunks(
7272+ chunks: AsyncIterableIterator<ChunkData>,
8073 boundary: string
8174): AsyncIterableIterator<string> {
7575+ const decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : null;
8276 let buffer = '';
8377 let boundaryIndex: number;
8478 for await (const chunk of chunks) {
8585- buffer += chunk;
7979+ // NOTE: We're avoiding referencing the `Buffer` global here to prevent
8080+ // auto-polyfilling in Webpack
8181+ buffer +=
8282+ chunk.constructor.name === 'Buffer'
8383+ ? (chunk as Buffer).toString()
8484+ : decoder!.decode(chunk as ArrayBuffer, { stream: true });
8685 while ((boundaryIndex = buffer.indexOf(boundary)) > -1) {
8786 yield buffer.slice(0, boundaryIndex);
8887 buffer = buffer.slice(boundaryIndex + boundary.length);
···10099 response: Response
101100): AsyncIterableIterator<ExecutionResult> {
102101 let payload: any;
103103- for await (const chunk of split(streamBody(response), '\n\n')) {
102102+ for await (const chunk of streamToBoundedChunks(
103103+ streamBody(response),
104104+ '\n\n'
105105+ )) {
104106 const match = chunk.match(eventStreamRe);
105107 if (match) {
106108 const chunk = match[1];
···125127 const boundary = '--' + (boundaryHeader ? boundaryHeader[1] : '-');
126128 let isPreamble = true;
127129 let payload: any;
128128- for await (let chunk of split(streamBody(response), '\r\n' + boundary)) {
130130+ for await (let chunk of streamToBoundedChunks(
131131+ streamBody(response),
132132+ '\r\n' + boundary
133133+ )) {
129134 if (isPreamble) {
130135 isPreamble = false;
131136 const preambleIndex = chunk.indexOf(boundary);