H1-H10 优化OCR实时体验闭环
这个提交包含在:
@@ -0,0 +1,81 @@
|
||||
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();
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户