1+ import { logger } from "./log" ;
2+ import type { SSEStreamingApi } from "hono/streaming" ;
3+ import type { EventSource } from "eventsource" ;
4+
5+ /**
6+ * FakeEventSource provides a minimal implementation of an SSE stream
7+ * that handles events for the inline client driver
8+ */
9+ export class FakeEventSource {
10+ url = "http://internal-sse-endpoint" ;
11+ readyState = 1 ; // OPEN
12+ withCredentials = false ;
13+
14+ // Event handlers
15+ onopen : ( ( this : EventSource , ev : Event ) => any ) | null = null ;
16+ onmessage : ( ( this : EventSource , ev : MessageEvent ) => any ) | null = null ;
17+ onerror : ( ( this : EventSource , ev : Event ) => any ) | null = null ;
18+
19+ // Private event listeners
20+ #listeners: Record < string , Set < EventListener > > = {
21+ open : new Set ( ) ,
22+ message : new Set ( ) ,
23+ error : new Set ( ) ,
24+ close : new Set ( )
25+ } ;
26+
27+ // Stream that will be passed to the handler
28+ #stream: SSEStreamingApi ;
29+ #onCloseCallback: ( ) => Promise < void > ;
30+
31+ /**
32+ * Creates a new FakeEventSource
33+ */
34+ constructor ( onCloseCallback : ( ) => Promise < void > ) {
35+ this . #onCloseCallback = onCloseCallback ;
36+
37+ this . #stream = this . #createStreamApi( ) ;
38+
39+ // Trigger open event on next tick
40+ setTimeout ( ( ) => {
41+ if ( this . readyState === 1 ) {
42+ this . #dispatchEvent( 'open' ) ;
43+ }
44+ } , 0 ) ;
45+
46+ logger ( ) . debug ( "FakeEventSource created" ) ;
47+ }
48+
49+ // Creates the SSE streaming API implementation
50+ #createStreamApi( ) : SSEStreamingApi {
51+ // Create self-reference for closures
52+ const self = this ;
53+
54+ const streamApi : SSEStreamingApi = {
55+ write : async ( input ) => {
56+ const data = typeof input === "string" ? input : new TextDecoder ( ) . decode ( input ) ;
57+ self . #dispatchEvent( 'message' , { data } ) ;
58+ return streamApi ;
59+ } ,
60+
61+ writeln : async ( input : string ) => {
62+ await streamApi . write ( input + "\n" ) ;
63+ return streamApi ;
64+ } ,
65+
66+ writeSSE : async ( message : { data : string | Promise < string > , event ?: string , id ?: string , retry ?: number } ) : Promise < void > => {
67+ const data = await message . data ;
68+
69+ if ( message . event ) {
70+ self . #dispatchEvent( message . event , { data } ) ;
71+ } else {
72+ self . #dispatchEvent( 'message' , { data } ) ;
73+ }
74+ } ,
75+
76+ sleep : async ( ms : number ) => {
77+ await new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
78+ return streamApi ;
79+ } ,
80+
81+ close : async ( ) => {
82+ self . close ( ) ;
83+ } ,
84+
85+ pipe : async ( _body : ReadableStream ) => {
86+ // No-op implementation
87+ } ,
88+
89+ onAbort : async ( cb : ( ) => void ) => {
90+ self . addEventListener ( "error" , ( ) => {
91+ cb ( ) ;
92+ } ) ;
93+ return streamApi ;
94+ } ,
95+
96+ abort : async ( ) => {
97+ self . #dispatchEvent( 'error' ) ;
98+ return streamApi ;
99+ } ,
100+
101+ // Additional required properties
102+ get responseReadable ( ) {
103+ return null as unknown as ReadableStream ;
104+ } ,
105+
106+ get aborted ( ) {
107+ return self . readyState === 2 ; // CLOSED
108+ } ,
109+
110+ get closed ( ) {
111+ return self . readyState === 2 ; // CLOSED
112+ }
113+ } ;
114+
115+ return streamApi ;
116+ }
117+
118+ /**
119+ * Closes the connection
120+ */
121+ close ( ) : void {
122+ if ( this . readyState === 2 ) { // CLOSED
123+ return ;
124+ }
125+
126+ logger ( ) . debug ( "closing FakeEventSource" ) ;
127+ this . readyState = 2 ; // CLOSED
128+
129+ // Call the close callback
130+ this . #onCloseCallback( ) . catch ( err => {
131+ logger ( ) . error ( "error in onClose callback" , { error : err } ) ;
132+ } ) ;
133+
134+ // Dispatch close event
135+ this . #dispatchEvent( 'close' ) ;
136+ }
137+
138+ /**
139+ * Get the stream API to pass to the handler
140+ */
141+ getStream ( ) : SSEStreamingApi {
142+ return this . #stream;
143+ }
144+
145+ // Implementation of EventTarget-like interface
146+ addEventListener ( type : string , listener : EventListener ) : void {
147+ if ( ! this . #listeners[ type ] ) {
148+ this . #listeners[ type ] = new Set ( ) ;
149+ }
150+ this . #listeners[ type ] . add ( listener ) ;
151+
152+ // Map to onX properties as well
153+ if ( type === "open" && typeof listener === "function" && ! this . onopen ) {
154+ this . onopen = listener as any ;
155+ } else if ( type === "message" && typeof listener === "function" && ! this . onmessage ) {
156+ this . onmessage = listener as any ;
157+ } else if ( type === "error" && typeof listener === "function" && ! this . onerror ) {
158+ this . onerror = listener as any ;
159+ }
160+ }
161+
162+ removeEventListener ( type : string , listener : EventListener ) : void {
163+ if ( this . #listeners[ type ] ) {
164+ this . #listeners[ type ] . delete ( listener ) ;
165+ }
166+
167+ // Unset onX property if it matches
168+ if ( type === "open" && this . onopen === listener ) {
169+ this . onopen = null ;
170+ } else if ( type === "message" && this . onmessage === listener ) {
171+ this . onmessage = null ;
172+ } else if ( type === "error" && this . onerror === listener ) {
173+ this . onerror = null ;
174+ }
175+ }
176+
177+ // Internal method to dispatch events
178+ #dispatchEvent( type : string , detail ?: Record < string , any > ) : void {
179+ // Create appropriate event
180+ let event : Event ;
181+ if ( type === 'message' || detail ) {
182+ event = new MessageEvent ( type , detail ) ;
183+ } else {
184+ event = new Event ( type ) ;
185+ }
186+
187+ // Call specific handler
188+ if ( type === 'open' && this . onopen ) {
189+ try {
190+ this . onopen . call ( this as any , event ) ;
191+ } catch ( err ) {
192+ logger ( ) . error ( "error in onopen handler" , { error : err } ) ;
193+ }
194+ } else if ( type === 'message' && this . onmessage ) {
195+ try {
196+ this . onmessage . call ( this as any , event as MessageEvent ) ;
197+ } catch ( err ) {
198+ logger ( ) . error ( "error in onmessage handler" , { error : err } ) ;
199+ }
200+ } else if ( type === 'error' && this . onerror ) {
201+ try {
202+ this . onerror . call ( this as any , event ) ;
203+ } catch ( err ) {
204+ logger ( ) . error ( "error in onerror handler" , { error : err } ) ;
205+ }
206+ }
207+
208+ // Call all listeners
209+ if ( this . #listeners[ type ] ) {
210+ for ( const listener of this . #listeners[ type ] ) {
211+ try {
212+ listener . call ( this , event ) ;
213+ } catch ( err ) {
214+ logger ( ) . error ( `error in ${ type } event listener` , { error : err } ) ;
215+ }
216+ }
217+ }
218+ }
219+ }
0 commit comments