Skip to content

Commit 5511bf5

Browse files
committed
fix: handle CLRF and CR line endings in SSE parser
1 parent 271a0a2 commit 5511bf5

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

packages/openapi-ts/src/plugins/@hey-api/client-core/__tests__/serverSentEvents.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,4 +629,64 @@ describe('createSseClient', () => {
629629
expect(validator).not.toHaveBeenCalled();
630630
expect(transformer).not.toHaveBeenCalled();
631631
});
632+
633+
it('handles CRLF line endings', async () => {
634+
fetchMock.mockResolvedValue({
635+
body: makeStream(['id: 1\r\nevent: test\r\ndata: {"foo":"bar"}\r\n\r\n']),
636+
ok: true,
637+
});
638+
639+
const onEvent = vi.fn();
640+
const { stream } = createSseClient({
641+
onSseEvent: onEvent,
642+
url: 'http://localhost/sse',
643+
});
644+
645+
const result: any[] = [];
646+
for await (const ev of stream) result.push(ev);
647+
648+
expect(result).toEqual([{ foo: 'bar' }]);
649+
expect(onEvent).toHaveBeenCalledWith({
650+
data: { foo: 'bar' },
651+
event: 'test',
652+
id: '1',
653+
retry: 3000,
654+
});
655+
});
656+
657+
it('handles CR-only line endings', async () => {
658+
fetchMock.mockResolvedValue({
659+
body: makeStream(['id: 2\revent: test\rdata: {"baz":"qux"}\r\r']),
660+
ok: true,
661+
});
662+
663+
const onEvent = vi.fn();
664+
const { stream } = createSseClient({
665+
onSseEvent: onEvent,
666+
url: 'http://localhost/sse',
667+
});
668+
669+
const result: any[] = [];
670+
for await (const ev of stream) result.push(ev);
671+
672+
expect(result).toEqual([{ baz: 'qux' }]);
673+
expect(onEvent).toHaveBeenCalledWith({
674+
data: { baz: 'qux' },
675+
event: 'test',
676+
id: '2',
677+
retry: 3000,
678+
});
679+
});
680+
681+
it('handles mixed line endings in a single stream', async () => {
682+
fetchMock.mockResolvedValue({
683+
body: makeStream(['data: 1\n\n', 'data: 2\r\n\r\n', 'data: 3\r\r']),
684+
ok: true,
685+
});
686+
687+
const { stream } = createSseClient({ url: 'http://localhost/sse' });
688+
const result: any[] = [];
689+
for await (const ev of stream) result.push(ev);
690+
expect(result).toEqual([1, 2, 3]);
691+
});
632692
});

packages/openapi-ts/src/plugins/@hey-api/client-core/bundle/serverSentEvents.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ export const createSseClient = <TData = unknown>({
167167
const { done, value } = await reader.read();
168168
if (done) break;
169169
buffer += value;
170+
// Normalize line endings: CRLF -> LF, then CR -> LF
171+
buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
170172

171173
const chunks = buffer.split('\n\n');
172174
buffer = chunks.pop() ?? '';

0 commit comments

Comments
 (0)