@yoyo-org/progressive-json
Version:
Stream and render JSON data as it arrives - perfect for AI responses, large datasets, and real-time updates
80 lines (79 loc) • 2.79 kB
JavaScript
import { assert } from "./utils/assert";
export async function fetchSSE(url, processor, options) {
const eventSource = new EventSource(url, {
withCredentials: true,
});
// If we need to pass auth headers, we'll need to use a polyfill or fetch API
// Native EventSource doesn't support custom headers
if (options === null || options === void 0 ? void 0 : options.headers) {
return fetchSSEWithHeaders(url, processor, options);
}
eventSource.onmessage = (event) => {
try {
const data = new TextEncoder().encode(event.data + "\n");
processor.processChunk(data);
}
catch (error) {
processor.onStreamError(error);
}
};
eventSource.onerror = (error) => {
eventSource.close();
processor.onStreamError(new Error("SSE connection error"));
};
eventSource.addEventListener("end", () => {
eventSource.close();
processor.onStreamComplete(processor.getStore());
});
// Handle abort signal
if (options === null || options === void 0 ? void 0 : options.signal) {
options.signal.addEventListener("abort", () => {
eventSource.close();
});
}
return eventSource;
}
// Fallback implementation using fetch API for custom headers
async function fetchSSEWithHeaders(url, processor, options) {
const response = await fetch(url, {
headers: {
...options.headers,
"Accept": "text/event-stream",
},
signal: options.signal,
});
assert(response.ok, `Failed to fetch from ${url}: ${response.statusText}`);
assert(response.body, "No response body received");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
try {
while (true) {
const { value, done } = await reader.read();
if (done)
break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") {
processor.onStreamComplete(processor.getStore());
return;
}
try {
const chunk = new TextEncoder().encode(data + "\n");
processor.processChunk(chunk);
}
catch (error) {
processor.onStreamError(error);
}
}
}
}
}
finally {
reader.releaseLock();
}
}