|
|
|
@ -36,9 +36,10 @@ class SSE { |
|
|
|
requestOptions?: SseRequestOptions, |
|
|
|
) { |
|
|
|
const baseUrl = this.client.getBaseUrl() || ''; |
|
|
|
const hasUrlSplit = baseUrl.endsWith('/') && url.startsWith('/'); |
|
|
|
|
|
|
|
const axiosConfig: InternalAxiosRequestConfig = { |
|
|
|
let axiosConfig: InternalAxiosRequestConfig<any> = { |
|
|
|
url, |
|
|
|
method: (requestOptions?.method as any) ?? 'GET', |
|
|
|
headers: {} as AxiosRequestHeaders, |
|
|
|
}; |
|
|
|
const requestInterceptors = this.client.instance.interceptors |
|
|
|
@ -48,25 +49,45 @@ class SSE { |
|
|
|
requestInterceptors.handlers.length > 0 |
|
|
|
) { |
|
|
|
for (const handler of requestInterceptors.handlers) { |
|
|
|
if (handler.fulfilled) { |
|
|
|
await handler.fulfilled(axiosConfig); |
|
|
|
if (typeof handler?.fulfilled === 'function') { |
|
|
|
const next = await handler.fulfilled(axiosConfig as any); |
|
|
|
if (next) axiosConfig = next as InternalAxiosRequestConfig<any>; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const merged = new Headers(); |
|
|
|
Object.entries( |
|
|
|
(axiosConfig.headers ?? {}) as Record<string, string>, |
|
|
|
).forEach(([k, v]) => merged.set(k, String(v))); |
|
|
|
if (requestOptions?.headers) { |
|
|
|
new Headers(requestOptions.headers).forEach((v, k) => merged.set(k, v)); |
|
|
|
} |
|
|
|
if (!merged.has('accept')) { |
|
|
|
merged.set('accept', 'text/event-stream'); |
|
|
|
} |
|
|
|
|
|
|
|
let bodyInit = requestOptions?.body ?? data; |
|
|
|
const ct = (merged.get('content-type') || '').toLowerCase(); |
|
|
|
if ( |
|
|
|
bodyInit && |
|
|
|
typeof bodyInit === 'object' && |
|
|
|
!ArrayBuffer.isView(bodyInit as any) && |
|
|
|
!(bodyInit instanceof ArrayBuffer) && |
|
|
|
!(bodyInit instanceof Blob) && |
|
|
|
!(bodyInit instanceof FormData) && |
|
|
|
ct.includes('application/json') |
|
|
|
) { |
|
|
|
bodyInit = JSON.stringify(bodyInit); |
|
|
|
} |
|
|
|
const requestInit: RequestInit = { |
|
|
|
...requestOptions, |
|
|
|
body: data, |
|
|
|
headers: { |
|
|
|
...(axiosConfig.headers as Record<string, string>), |
|
|
|
...requestOptions?.headers, |
|
|
|
}, |
|
|
|
method: axiosConfig.method, |
|
|
|
headers: merged, |
|
|
|
body: bodyInit, |
|
|
|
}; |
|
|
|
|
|
|
|
const response = await fetch( |
|
|
|
`${baseUrl}${hasUrlSplit ? '' : '/'}${url}`, |
|
|
|
requestInit, |
|
|
|
); |
|
|
|
const response = await fetch(safeJoinUrl(baseUrl, url), requestInit); |
|
|
|
if (!response.ok) { |
|
|
|
throw new Error(`HTTP error! status: ${response.status}`); |
|
|
|
} |
|
|
|
@ -78,19 +99,38 @@ class SSE { |
|
|
|
throw new Error('No reader'); |
|
|
|
} |
|
|
|
let isEnd = false; |
|
|
|
let allMessage = ''; |
|
|
|
while (!isEnd) { |
|
|
|
const { done, value } = await reader.read(); |
|
|
|
if (done) { |
|
|
|
isEnd = true; |
|
|
|
requestOptions?.onEnd?.(allMessage); |
|
|
|
decoder.decode(new Uint8Array(0), { stream: false }); |
|
|
|
requestOptions?.onEnd?.(); |
|
|
|
reader.releaseLock?.(); |
|
|
|
break; |
|
|
|
} |
|
|
|
const content = decoder.decode(value, { stream: true }); |
|
|
|
requestOptions?.onMessage?.(content); |
|
|
|
allMessage += content; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function safeJoinUrl(baseUrl: string | undefined, url: string): string { |
|
|
|
if (!baseUrl) { |
|
|
|
return url; // 没有 baseUrl,直接返回 url
|
|
|
|
} |
|
|
|
|
|
|
|
// 如果 url 本身就是绝对地址,直接返回
|
|
|
|
if (/^https?:\/\//i.test(url)) { |
|
|
|
return url; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果 baseUrl 是完整 URL,就用 new URL
|
|
|
|
if (/^https?:\/\//i.test(baseUrl)) { |
|
|
|
return new URL(url, baseUrl).toString(); |
|
|
|
} |
|
|
|
|
|
|
|
// 否则,当作路径拼接
|
|
|
|
return `${baseUrl.replace(/\/+$/, '')}/${url.replace(/^\/+/, '')}`; |
|
|
|
} |
|
|
|
|
|
|
|
export { SSE }; |
|
|
|
|