Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/fetch/src/stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,32 @@ describe("streamSse", () => {
expect(results).toEqual([{ foo: "bar" }, { baz: 42 }]);
});

it("ignores SSE comment lines that share a chunk with data events", async () => {
// Servers and proxies send SSE comments (e.g. `: ping`) as keep-alives.
// When one arrives in the same chunk as the following data events, those
// events must still be yielded rather than silently dropped.
const stream = new Readable({
read() {
this.push(
': ping\n\ndata: {"foo": "bar"}\n\ndata: {"baz": 42}\n\ndata: [DONE]\n\n',
);
this.push(null); // End of stream
},
}) as any;
const response = {
status: 200,
body: stream,
text: async () => "",
} as unknown as Response;

const results = [];
for await (const data of streamSse(response)) {
results.push(data);
}

expect(results).toEqual([{ foo: "bar" }, { baz: 42 }]);
});

it("throws on malformed JSON", async () => {
const sseLines = ['data: {"foo": "bar"', "data:[DONE]"];
const response = createMockResponse(sseLines);
Expand Down
5 changes: 3 additions & 2 deletions packages/fetch/src/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ function parseSseLine(line: string): { done: boolean; data: any } {
if (line.startsWith("data:")) {
return { done: false, data: parseDataLine(line) };
}
if (line.startsWith(": ping")) {
return { done: true, data: undefined };
if (line.startsWith(":")) {
// Comment line (e.g. the `: ping` keep-alive), ignored per the SSE spec
return { done: false, data: undefined };
}
return { done: false, data: undefined };
}
Expand Down
Loading