|
@@ -36,9 +36,10 @@ class SSE {
|
|
|
requestOptions?: SseRequestOptions,
|
|
requestOptions?: SseRequestOptions,
|
|
|
) {
|
|
) {
|
|
|
const baseUrl = this.client.getBaseUrl() || '';
|
|
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,
|
|
headers: {} as AxiosRequestHeaders,
|
|
|
};
|
|
};
|
|
|
const requestInterceptors = this.client.instance.interceptors
|
|
const requestInterceptors = this.client.instance.interceptors
|
|
@@ -48,25 +49,45 @@ class SSE {
|
|
|
requestInterceptors.handlers.length > 0
|
|
requestInterceptors.handlers.length > 0
|
|
|
) {
|
|
) {
|
|
|
for (const handler of requestInterceptors.handlers) {
|
|
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 = {
|
|
const requestInit: RequestInit = {
|
|
|
...requestOptions,
|
|
...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) {
|
|
if (!response.ok) {
|
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
|
}
|
|
}
|
|
@@ -78,19 +99,38 @@ class SSE {
|
|
|
throw new Error('No reader');
|
|
throw new Error('No reader');
|
|
|
}
|
|
}
|
|
|
let isEnd = false;
|
|
let isEnd = false;
|
|
|
- let allMessage = '';
|
|
|
|
|
while (!isEnd) {
|
|
while (!isEnd) {
|
|
|
const { done, value } = await reader.read();
|
|
const { done, value } = await reader.read();
|
|
|
if (done) {
|
|
if (done) {
|
|
|
isEnd = true;
|
|
isEnd = true;
|
|
|
- requestOptions?.onEnd?.(allMessage);
|
|
|
|
|
|
|
+ decoder.decode(new Uint8Array(0), { stream: false });
|
|
|
|
|
+ requestOptions?.onEnd?.();
|
|
|
|
|
+ reader.releaseLock?.();
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
const content = decoder.decode(value, { stream: true });
|
|
const content = decoder.decode(value, { stream: true });
|
|
|
requestOptions?.onMessage?.(content);
|
|
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 };
|
|
export { SSE };
|