lobe-chat
110 строк · 3.3 Кб
1/**
2* file copy from https://github.com/Azure/fetch-event-source/blob/45ac3cfffd30b05b79fbf95c21e67d4ef59aa56a/src/fetch.ts
3* and remove some code
4*/
5import { EventSourceMessage, getBytes, getLines, getMessages } from './parse';
6
7export const EventStreamContentType = 'text/event-stream';
8
9const LastEventId = 'last-event-id';
10
11// eslint-disable-next-line no-undef
12export interface FetchEventSourceInit extends RequestInit {
13/** The Fetch function to use. Defaults to window.fetch */
14fetch?: typeof fetch;
15
16/**
17* The request headers. FetchEventSource only supports the Record<string,string> format.
18*/
19headers?: Record<string, string>;
20
21/**
22* Called when a response finishes. If you don't expect the server to kill
23* the connection, you can throw an exception here and retry using onerror.
24*/
25onclose?: () => void;
26
27/**
28* Called when there is any error making the request / processing messages /
29* handling callbacks etc. Use this to control the retry strategy: if the
30* error is fatal, rethrow the error inside the callback to stop the entire
31* operation. Otherwise, you can return an interval (in milliseconds) after
32* which the request will automatically retry (with the last-event-id).
33* If this callback is not specified, or it returns undefined, fetchEventSource
34* will treat every error as retriable and will try again after 1 second.
35*/
36onerror?: (err: any) => number | null | undefined | void;
37
38/**
39* Called when a message is received. NOTE: Unlike the default browser
40* EventSource.onmessage, this callback is called for _all_ events,
41* even ones with a custom `event` field.
42*/
43onmessage?: (ev: EventSourceMessage) => void;
44
45/**
46* Called when a response is received. Use this to validate that the response
47* actually matches what you expect (and throw if it doesn't.) If not provided,
48* will default to a basic validation to ensure the content-type is text/event-stream.
49*/
50onopen: (response: Response) => Promise<void>;
51}
52
53export function fetchEventSource(
54// eslint-disable-next-line no-undef
55input: RequestInfo,
56{
57signal: inputSignal,
58headers: inputHeaders,
59onopen: inputOnOpen,
60onmessage,
61onclose,
62onerror,
63fetch: inputFetch,
64...rest
65}: FetchEventSourceInit,
66) {
67return new Promise<void>((resolve) => {
68// make a copy of the input headers since we may modify it below:
69const headers = { ...inputHeaders };
70if (!headers.accept) {
71headers.accept = EventStreamContentType;
72}
73
74const fetch = inputFetch ?? window.fetch;
75async function create() {
76try {
77const response = await fetch(input, {
78...rest,
79headers,
80signal: inputSignal,
81});
82
83await inputOnOpen(response);
84
85await getBytes(
86response.body!,
87getLines(
88getMessages((id) => {
89if (id) {
90// store the id and send it back on the next retry:
91headers[LastEventId] = id;
92} else {
93// don't send the last-event-id header anymore:
94delete headers[LastEventId];
95}
96}, onmessage),
97),
98);
99
100onclose?.();
101resolve();
102} catch (err) {
103onerror?.(err);
104resolve();
105}
106}
107
108create();
109});
110}
111