File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -63,19 +63,29 @@ export function parseSSEFrames(buffer: string): {
6363 const frames : SSEFrame [ ] = [ ]
6464 let pos = 0
6565
66- // SSE frames are delimited by double newlines
67- let idx : number
68- while ( ( idx = buffer . indexOf ( '\n\n' , pos ) ) !== - 1 ) {
69- const rawFrame = buffer . slice ( pos , idx )
70- pos = idx + 2
66+ // SSE frames are delimited by an empty line. Support LF and CRLF streams.
67+ const frameDelimiter = / \r ? \n \r ? \n / g
68+ frameDelimiter . lastIndex = pos
69+
70+ let delimiterMatch : RegExpExecArray | null
71+ while ( ( delimiterMatch = frameDelimiter . exec ( buffer ) ) !== null ) {
72+ const frameEnd = delimiterMatch . index
73+ const rawFrame = buffer . slice ( pos , frameEnd )
74+ pos = frameEnd + delimiterMatch [ 0 ] . length
7175
7276 // Skip empty frames
7377 if ( ! rawFrame . trim ( ) ) continue
7478
7579 const frame : SSEFrame = { }
7680 let isComment = false
7781
78- for ( const line of rawFrame . split ( '\n' ) ) {
82+ for ( const rawLine of rawFrame . split ( '\n' ) ) {
83+ // Normalize CRLF lines in mixed-line-ending streams.
84+ const line =
85+ rawLine [ rawLine . length - 1 ] === '\r'
86+ ? rawLine . slice ( 0 , - 1 )
87+ : rawLine
88+
7989 if ( line . startsWith ( ':' ) ) {
8090 // SSE comment (e.g., `:keepalive`)
8191 isComment = true
Original file line number Diff line number Diff line change 1+ import { describe , expect , test } from 'bun:test'
2+ import { parseSSEFrames } from '../SSETransport.js'
3+
4+ describe ( 'parseSSEFrames' , ( ) => {
5+ test ( 'parses LF-delimited frames' , ( ) => {
6+ const input = 'event: client_event\ndata: {"ok":true}\n\n'
7+ const { frames, remaining } = parseSSEFrames ( input )
8+
9+ expect ( remaining ) . toBe ( '' )
10+ expect ( frames ) . toEqual ( [
11+ {
12+ event : 'client_event' ,
13+ data : '{"ok":true}' ,
14+ } ,
15+ ] )
16+ } )
17+
18+ test ( 'parses CRLF-delimited frames and strips trailing carriage returns' , ( ) => {
19+ const input =
20+ 'event: client_event\r\ndata: {"ok":true}\r\nid: 7\r\n\r\nevent: keepalive\r\ndata: ping\r\n\r\n'
21+ const { frames, remaining } = parseSSEFrames ( input )
22+
23+ expect ( remaining ) . toBe ( '' )
24+ expect ( frames ) . toEqual ( [
25+ {
26+ event : 'client_event' ,
27+ data : '{"ok":true}' ,
28+ id : '7' ,
29+ } ,
30+ {
31+ event : 'keepalive' ,
32+ data : 'ping' ,
33+ } ,
34+ ] )
35+ } )
36+
37+ test ( 'keeps incomplete trailing frame in remaining buffer for CRLF streams' , ( ) => {
38+ const input = 'event: client_event\r\ndata: {"ok":true}\r\n\r\ndata: {"tail":1}\r\n'
39+ const { frames, remaining } = parseSSEFrames ( input )
40+
41+ expect ( frames ) . toEqual ( [
42+ {
43+ event : 'client_event' ,
44+ data : '{"ok":true}' ,
45+ } ,
46+ ] )
47+ expect ( remaining ) . toBe ( 'data: {"tail":1}\r\n' )
48+ } )
49+ } )
You can’t perform that action at this time.
0 commit comments