66 type HttpRequestResolverExtras ,
77 type HttpRequestParsedResult ,
88} from './handlers/HttpHandler'
9+ import type { ResponseResolutionContext } from '~/core/utils/executeHandlers'
910import type { Path , PathParams } from './utils/matching/matchRequestUrl'
1011import { delay } from './delay'
1112import { getTimestamp } from './utils/logging/getTimestamp'
@@ -67,47 +68,32 @@ export const sse: ServerSentEventRequestHandler = (path, resolver) => {
6768 return new ServerSentEventHandler ( path , resolver )
6869}
6970
71+ const SSE_RESPONSE_INIT : ResponseInit = {
72+ headers : {
73+ 'content-type' : 'text/event-stream' ,
74+ 'cache-control' : 'no-cache' ,
75+ connection : 'keep-alive' ,
76+ } ,
77+ }
78+
7079class ServerSentEventHandler <
7180 EventMap extends EventMapConstraint ,
7281> extends HttpHandler {
82+ #emitter: Emitter < ServerSentEventClientEventMap >
83+
7384 constructor ( path : Path , resolver : ServerSentEventResolver < EventMap , any > ) {
7485 invariant (
7586 typeof EventSource !== 'undefined' ,
7687 'Failed to construct a Server-Sent Event handler for path "%s": the EventSource API is not supported in this environment' ,
7788 path ,
7889 )
7990
80- const clientEmitter = new Emitter < ServerSentEventClientEventMap > ( )
81-
8291 super ( 'GET' , path , async ( info ) => {
83- const responseInit : ResponseInit = {
84- headers : {
85- 'content-type' : 'text/event-stream' ,
86- 'cache-control' : 'no-cache' ,
87- connection : 'keep-alive' ,
88- } ,
89- }
90-
91- /**
92- * @note Log the intercepted request early.
93- * Normally, the `this.log()` method is called when the handler returns a response.
94- * For SSE, call that method earlier so the logs are in correct order.
95- */
96- await super . log ( {
97- request : info . request ,
98- /**
99- * @note Construct a placeholder response since SSE response
100- * is being streamed and cannot be cloned/consumed for logging.
101- */
102- response : new Response ( '[streaming]' , responseInit ) ,
103- } )
104- this . #attachClientLogger( info . request , clientEmitter )
105-
10692 const stream = new ReadableStream ( {
107- async start ( controller ) {
93+ start : async ( controller ) => {
10894 const client = new ServerSentEventClient < EventMap > ( {
10995 controller,
110- emitter : clientEmitter ,
96+ emitter : this . #emitter ,
11197 } )
11298 const server = new ServerSentEventServer ( {
11399 request : info . request ,
@@ -122,19 +108,42 @@ class ServerSentEventHandler<
122108 } ,
123109 } )
124110
125- return new Response ( stream , responseInit )
111+ return new Response ( stream , SSE_RESPONSE_INIT )
126112 } )
113+
114+ this . #emitter = new Emitter < ServerSentEventClientEventMap > ( )
127115 }
128116
129117 async predicate ( args : {
130118 request : Request
131119 parsedResult : HttpRequestParsedResult
120+ resolutionContext ?: ResponseResolutionContext
132121 } ) {
133122 if ( args . request . headers . get ( 'accept' ) !== 'text/event-stream' ) {
134123 return false
135124 }
136125
137- return super . predicate ( args )
126+ const matches = await super . predicate ( args )
127+
128+ if ( matches && ! args . resolutionContext ?. quiet ) {
129+ /**
130+ * @note Log the intercepted request early.
131+ * Normally, the `this.log()` method is called when the handler returns a response.
132+ * For SSE, call that method earlier so the logs are in correct order.
133+ */
134+ await super . log ( {
135+ request : args . request ,
136+ /**
137+ * @note Construct a placeholder response since SSE response
138+ * is being streamed and cannot be cloned/consumed for logging.
139+ */
140+ response : new Response ( '[streaming]' , SSE_RESPONSE_INIT ) ,
141+ } )
142+
143+ this . #attachClientLogger( args . request , this . #emitter)
144+ }
145+
146+ return matches
138147 }
139148
140149 async log ( _args : { request : Request ; response : Response } ) : Promise < void > {
0 commit comments