51 lines
1.8 KiB
JavaScript
51 lines
1.8 KiB
JavaScript
/** Reusable Server-Sent Events parser for browser and test environments. */
|
|
export class SseParseError extends Error {
|
|
constructor(message, details = {}) {
|
|
super(message);
|
|
this.name = "SseParseError";
|
|
Object.assign(this, details);
|
|
}
|
|
}
|
|
|
|
function parseEvent(block, {onComment, onRetry} = {}) {
|
|
if (!block) return null;
|
|
let event = "message";
|
|
const data = [];
|
|
let id;
|
|
let retry;
|
|
for (const line of block.split(/\r?\n/)) {
|
|
if (line.startsWith(":")) { onComment?.(line.slice(1).trimStart()); continue; }
|
|
const separator = line.indexOf(":");
|
|
const field = separator < 0 ? line : line.slice(0, separator);
|
|
const value = separator < 0 ? "" : line.slice(separator + 1).replace(/^ /, "");
|
|
if (field === "event") event = value;
|
|
else if (field === "data") data.push(value);
|
|
else if (field === "id" && !value.includes("\u0000")) id = value;
|
|
else if (field === "retry" && /^\d+$/.test(value)) { retry = Number(value); onRetry?.(retry); }
|
|
}
|
|
if (!data.length) return null;
|
|
return {event, data: data.join("\n"), ...(id !== undefined ? {id} : {}), ...(retry !== undefined ? {retry} : {})};
|
|
}
|
|
|
|
export function createSseParser(options = {}) {
|
|
let buffer = "";
|
|
return {
|
|
push(text) {
|
|
buffer += text;
|
|
const blocks = buffer.split(/\r?\n\r?\n/);
|
|
buffer = blocks.pop() || "";
|
|
return blocks.map(block => parseEvent(block, options)).filter(Boolean);
|
|
},
|
|
finish() {
|
|
const event = parseEvent(buffer, options);
|
|
buffer = "";
|
|
return event ? [event] : [];
|
|
}
|
|
};
|
|
}
|
|
|
|
export function parseSseData(event) {
|
|
try { return JSON.parse(event.data); }
|
|
catch (error) { throw new SseParseError("Malformed SSE JSON payload.", {cause: error, event}); }
|
|
}
|