45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
/** Minimal Server-Sent Events reader: yields one {event, data} per blank-line-terminated record. */
|
|
export async function* readSse(body: ReadableStream<Uint8Array>): AsyncIterable<{ event: string; data: string }> {
|
|
const decoder = new TextDecoder();
|
|
let buf = "";
|
|
let event = "message";
|
|
let data: string[] = [];
|
|
const flushLine = function* (line: string): Generator<{ event: string; data: string }> {
|
|
if (line === "") {
|
|
if (data.length) yield { event, data: data.join("\n") };
|
|
event = "message";
|
|
data = [];
|
|
return;
|
|
}
|
|
if (line.startsWith(":")) return;
|
|
const i = line.indexOf(":");
|
|
const field = i === -1 ? line : line.slice(0, i);
|
|
let value = i === -1 ? "" : line.slice(i + 1);
|
|
if (value.startsWith(" ")) value = value.slice(1);
|
|
if (field === "event") event = value;
|
|
else if (field === "data") data.push(value);
|
|
};
|
|
for await (const chunk of body as unknown as AsyncIterable<Uint8Array>) {
|
|
buf += decoder.decode(chunk, { stream: true });
|
|
let nl: number;
|
|
while ((nl = buf.search(/\r\n|\n|\r/)) !== -1) {
|
|
const line = buf.slice(0, nl);
|
|
buf = buf.slice(nl + (buf.startsWith("\r\n", nl) ? 2 : 1));
|
|
yield* flushLine(line);
|
|
}
|
|
}
|
|
buf += decoder.decode();
|
|
if (buf) yield* flushLine(buf);
|
|
yield* flushLine("");
|
|
}
|
|
|
|
export async function errorMessage(res: Response): Promise<string> {
|
|
const text = await res.text().catch(() => "");
|
|
try {
|
|
const j = JSON.parse(text);
|
|
const m = j?.error?.message ?? j?.message ?? j?.error;
|
|
if (typeof m === "string") return `${res.status}: ${m}`;
|
|
} catch {}
|
|
return `${res.status}: ${text.slice(0, 300) || res.statusText}`;
|
|
}
|