Files
iot-device-management-frontend/packages/effects/request/src/request-client/modules/sse.ts

137 lines
3.6 KiB
TypeScript
Raw Normal View History

import type { AxiosRequestHeaders, InternalAxiosRequestConfig } from 'axios';
import type { RequestClient } from '../request-client';
import type { SseRequestOptions } from '../types';
/**
* SSE模块
*/
class SSE {
private client: RequestClient;
constructor(client: RequestClient) {
this.client = client;
}
public async postSSE(
url: string,
data?: any,
requestOptions?: SseRequestOptions,
) {
return this.requestSSE(url, data, {
...requestOptions,
method: 'POST',
});
}
/**
* SSE请求方法
* @param url - URL
* @param data -
* @param requestOptions - SSE请求选项
*/
public async requestSSE(
url: string,
data?: any,
requestOptions?: SseRequestOptions,
) {
const baseUrl = this.client.getBaseUrl() || '';
let axiosConfig: InternalAxiosRequestConfig<any> = {
url,
method: (requestOptions?.method as any) ?? 'GET',
headers: {} as AxiosRequestHeaders,
};
const requestInterceptors = this.client.instance.interceptors
.request as any;
if (
requestInterceptors.handlers &&
requestInterceptors.handlers.length > 0
) {
for (const handler of requestInterceptors.handlers) {
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,
method: axiosConfig.method,
headers: merged,
body: bodyInit,
};
const response = await fetch(safeJoinUrl(baseUrl, url), requestInit);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) {
throw new Error('No reader');
}
let isEnd = false;
while (!isEnd) {
const { done, value } = await reader.read();
if (done) {
isEnd = true;
decoder.decode(new Uint8Array(0), { stream: false });
requestOptions?.onEnd?.();
reader.releaseLock?.();
break;
}
const content = decoder.decode(value, { stream: true });
requestOptions?.onMessage?.(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 };