文件
hyb-oa/client/src/api/events.ts
T

82 行
1.9 KiB
TypeScript

export type AppEvent = {
id?: string;
type: string;
invoiceId?: string;
status?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
};
type SubscribeAppEventsOptions = {
accessToken: string;
onEvent: (event: AppEvent) => void;
onError?: (error: unknown) => void;
};
function parseSseMessage(message: string): AppEvent | null {
const dataLines = message
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice("data:".length).trimStart());
if (!dataLines.length) {
return null;
}
try {
return JSON.parse(dataLines.join("\n")) as AppEvent;
} catch {
return null;
}
}
export function subscribeAppEvents(options: SubscribeAppEventsOptions) {
const controller = new AbortController();
void (async () => {
try {
const response = await fetch("/api/events", {
method: "GET",
credentials: "include",
headers: {
authorization: `Bearer ${options.accessToken}`,
accept: "text/event-stream",
},
signal: controller.signal,
});
if (!response.ok || !response.body) {
throw new Error(`events_stream_unavailable:${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (!controller.signal.aborted) {
const { done, value } = await reader.read();
if (done) {
break;
}
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split(/\r?\n\r?\n/);
buffer = chunks.pop() ?? "";
for (const chunk of chunks) {
const event = parseSseMessage(chunk);
if (event) {
options.onEvent(event);
}
}
}
} catch (error) {
if (!controller.signal.aborted) {
options.onError?.(error);
}
}
})();
return () => controller.abort();
}