import type { AxiosRequestConfig } from 'axios';
|
import { accessSessionKey } from '../request';
|
import { SESSION_KEY } from '../request';
|
import { Local } from '../storage';
|
import { debounce } from 'lodash-es';
|
import { Logger } from '/@/model/logger/Logger';
|
|
export interface SSEOptions {
|
/** 重试延迟(ms) */
|
retryDelay?: number;
|
/** 超时时间(ms) */
|
timeout?: number;
|
/** 是否自动重连 */
|
autoReconnect?: boolean;
|
|
/** 请求头 */
|
headers?: Record<string, string>;
|
}
|
|
export interface SSEEventCallbacks {
|
/** 消息回调 */
|
onMessage?: (data: any) => void;
|
/** 错误回调 */
|
onError?: (error: Error) => void;
|
/** 连接打开回调 */
|
onOpen?: () => void;
|
/** 连接关闭回调 */
|
onClose?: () => void;
|
/** 重试回调 */
|
onRetry?: () => void;
|
}
|
|
export type MessageHandler = (data: any) => void;
|
|
export class SSEClient {
|
private eventSource: EventSource | null = null;
|
private reconnectTimeout: number | null = null;
|
private abortController: AbortController | null = null;
|
private messageHandlers: Set<MessageHandler> = new Set();
|
|
constructor(private url: string, private options: SSEOptions = {}, private callbacks: SSEEventCallbacks = {}) {
|
// 设置默认值
|
this.options = {
|
retryDelay: 1000,
|
autoReconnect: true,
|
timeout: 3000,
|
headers: {
|
[SESSION_KEY]: Local.get(accessSessionKey),
|
},
|
...options,
|
};
|
}
|
|
/**
|
* 订阅消息
|
*/
|
subscribe(handler: MessageHandler): void {
|
this.messageHandlers.add(handler);
|
}
|
|
/**
|
* 取消订阅消息
|
*/
|
unsubscribe(handler: MessageHandler): void {
|
this.messageHandlers.delete(handler);
|
}
|
|
/**
|
* 建立连接
|
*/
|
async connect(params?: Record<string, any>): Promise<void> {
|
try {
|
// 构建 URL 和参数
|
const queryString = params && Object.values(params).length ? `?${new URLSearchParams(params).toString()}` : '';
|
const fullUrl = `${this.url}${queryString}`;
|
|
// 创建 AbortController 用于超时控制
|
// this.abortController = new AbortController();
|
// 创建 EventSource 并添加 headers
|
this.eventSource = new EventSource(fullUrl);
|
|
// 绑定事件处理
|
this.bindEvents();
|
} catch (error) {
|
console.log('catch error');
|
}
|
}
|
|
/**
|
* 绑定事件处理
|
*/
|
private bindEvents(): void {
|
if (!this.eventSource) return;
|
|
this.eventSource.onopen = () => {
|
Logger.info('onopen:连接成功');
|
this.callbacks.onOpen?.();
|
};
|
|
this.eventSource.onmessage = (event) => {
|
Logger.info('onmessage:\n\n'+ event.data);
|
try {
|
const data = JSON.parse(event.data);
|
// 检查是否是结束标记
|
if (data === '[DONE]') {
|
this.disconnect();
|
return;
|
}
|
// 通知所有订阅者
|
this.messageHandlers.forEach(handler => handler(data));
|
// 调用原有的回调
|
this.callbacks.onMessage?.(data);
|
} catch (error) {
|
console.error('Failed to parse SSE data:', error);
|
this.callbacks.onError?.(new Error('Failed to parse SSE data'));
|
}
|
};
|
|
this.eventSource.onerror = async (error) => {
|
Logger.error('onerror:\n\n'+ error);
|
};
|
}
|
|
/**
|
* 错误处理
|
*/
|
private async handleError(error: any): Promise<void> {
|
this.callbacks.onError?.(error);
|
if (this.options.autoReconnect) {
|
this.callbacks.onRetry?.();
|
// 延迟重试
|
await new Promise((resolve) => {
|
this.reconnectTimeout = setTimeout(resolve, this.options.retryDelay);
|
});
|
await this.connect();
|
} else {
|
this.disconnect();
|
}
|
}
|
|
/**
|
* 断开连接
|
*/
|
disconnect(): void {
|
if (this.reconnectTimeout) {
|
clearTimeout(this.reconnectTimeout);
|
this.reconnectTimeout = null;
|
}
|
if (this.eventSource) {
|
this.eventSource.close();
|
this.eventSource = null;
|
}
|
|
// if (this.abortController) {
|
// this.abortController.abort();
|
// this.abortController = null;
|
// }
|
|
this.callbacks.onClose?.();
|
}
|
|
/**
|
* 是否已连接
|
*/
|
isConnected(): boolean {
|
return this.eventSource?.readyState === EventSource.OPEN;
|
}
|
|
/**
|
* 重新连接
|
*/
|
async reconnect(): Promise<void> {
|
this.disconnect();
|
await this.connect();
|
}
|
}
|