(headers?: Record<string, string>)
| 112 | } |
| 113 | |
| 114 | private async connect(headers?: Record<string, string>): Promise<void> { |
| 115 | if (this.readyState === EventSource.CLOSED) { |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | this.controller = new AbortController(); |
| 120 | this.readyState = FetchEventSource.CONNECTING; |
| 121 | |
| 122 | try { |
| 123 | const response = await fetch(this.url, { |
| 124 | method: 'GET', |
| 125 | headers: { |
| 126 | Accept: 'text/event-stream', |
| 127 | // Don't send Cache-Control header - backend sets it on response |
| 128 | // Including it causes CORS issues |
| 129 | ...headers, |
| 130 | }, |
| 131 | signal: this.controller.signal, |
| 132 | credentials: this.withCredentials ? 'include' : 'same-origin', |
| 133 | }); |
| 134 | |
| 135 | if (!response.ok) { |
| 136 | throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`); |
| 137 | } |
| 138 | |
| 139 | if (!response.body) { |
| 140 | throw new Error('Response body is null'); |
| 141 | } |
| 142 | |
| 143 | this.readyState = FetchEventSource.OPEN; |
| 144 | |
| 145 | // Fire onopen event |
| 146 | if (this.onopenHandler) { |
| 147 | this.onopenHandler(new Event('open')); |
| 148 | } |
| 149 | this.fireEvent('open', new Event('open')); |
| 150 | |
| 151 | // Read the stream |
| 152 | this.reader = response.body.getReader(); |
| 153 | const decoder = new TextDecoder(); |
| 154 | let buffer = ''; |
| 155 | |
| 156 | while (this.readyState === FetchEventSource.OPEN) { |
| 157 | const { done, value } = await this.reader.read(); |
| 158 | |
| 159 | if (done) { |
| 160 | break; |
| 161 | } |
| 162 | |
| 163 | buffer += decoder.decode(value, { stream: true }); |
| 164 | const lines = buffer.split('\n'); |
| 165 | buffer = lines.pop() || ''; // Keep incomplete line in buffer |
| 166 | |
| 167 | let eventType = 'message'; |
| 168 | let eventData = ''; |
| 169 | let eventId = ''; |
| 170 | |
| 171 | for (const line of lines) { |
no test coverage detected