|
|
@@ -11,6 +11,10 @@ export class WebSocketManager {
|
|
|
private messageHandlers: Map<string, (message: Message) => void> = new Map();
|
|
|
private pingInterval: ReturnType<typeof setInterval> | null = null;
|
|
|
private readonly PING_INTERVAL = 30000; // 30秒
|
|
|
+ private pendingMessages: Partial<Message>[] = [];
|
|
|
+ private bufferTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
+ private readonly BUFFER_TIMEOUT_MS = 2000;
|
|
|
+ private readonly MAX_BUFFERED_MESSAGES = 40;
|
|
|
|
|
|
connect(url: string, onConnectionChange?: (connected: boolean) => void): void {
|
|
|
this.ws = new ReconnectingWebSocket(url, [], {
|
|
|
@@ -26,6 +30,7 @@ export class WebSocketManager {
|
|
|
// 记录WebSocket连接建立时间
|
|
|
performanceLogger.recordWebSocketConnected();
|
|
|
this.startHeartbeat();
|
|
|
+ this.flushBufferedMessages();
|
|
|
if (onConnectionChange) {
|
|
|
onConnectionChange(true);
|
|
|
}
|
|
|
@@ -71,12 +76,18 @@ export class WebSocketManager {
|
|
|
this.messageHandlers.delete(id);
|
|
|
}
|
|
|
|
|
|
- send(message: Partial<Message>): void {
|
|
|
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
|
- console.warn(`[WebSocket] [${message.requestId?.substring(0, 8) || ''}] 发送失败: WebSocket未连接`, message.type);
|
|
|
+ private isOpen(): boolean {
|
|
|
+ return !!this.ws && this.ws.readyState === WebSocket.OPEN;
|
|
|
+ }
|
|
|
+
|
|
|
+ isReady(): boolean {
|
|
|
+ return this.isOpen();
|
|
|
+ }
|
|
|
+
|
|
|
+ private doSend(message: Partial<Message>): void {
|
|
|
+ if (!this.ws) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
const fullMessage = {
|
|
|
...message,
|
|
|
timestamp: Date.now(),
|
|
|
@@ -90,6 +101,63 @@ export class WebSocketManager {
|
|
|
this.ws.send(JSON.stringify(fullMessage));
|
|
|
}
|
|
|
|
|
|
+ private bufferMessage(message: Partial<Message>): void {
|
|
|
+ if (this.pendingMessages.length >= this.MAX_BUFFERED_MESSAGES) {
|
|
|
+ console.warn(
|
|
|
+ `[WebSocket] [${message.requestId?.substring(0, 8) || ''}] 缓冲区已满,丢弃消息: ${message.type}`
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.pendingMessages.push(message);
|
|
|
+ if (!this.bufferTimer) {
|
|
|
+ this.bufferTimer = setTimeout(() => {
|
|
|
+ if (this.pendingMessages.length > 0) {
|
|
|
+ console.warn('[WebSocket] 缓冲等待超时,清空未发送音频消息');
|
|
|
+ this.pendingMessages = [];
|
|
|
+ }
|
|
|
+ this.bufferTimer = null;
|
|
|
+ }, this.BUFFER_TIMEOUT_MS);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private flushBufferedMessages(): void {
|
|
|
+ if (!this.isOpen() || this.pendingMessages.length === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const bufferedMessages = [...this.pendingMessages];
|
|
|
+ this.pendingMessages = [];
|
|
|
+ if (this.bufferTimer) {
|
|
|
+ clearTimeout(this.bufferTimer);
|
|
|
+ this.bufferTimer = null;
|
|
|
+ }
|
|
|
+ for (const bufferedMessage of bufferedMessages) {
|
|
|
+ this.doSend(bufferedMessage);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private shouldBuffer(messageType: Message['type'] | undefined): boolean {
|
|
|
+ return messageType === 'audio_chunk' || messageType === 'audio_end';
|
|
|
+ }
|
|
|
+
|
|
|
+ send(message: Partial<Message>): void {
|
|
|
+ if (!this.isOpen()) {
|
|
|
+ if (this.shouldBuffer(message.type)) {
|
|
|
+ console.warn(
|
|
|
+ `[WebSocket] [${message.requestId?.substring(0, 8) || ''}] 连接未就绪,暂存消息: ${message.type}`
|
|
|
+ );
|
|
|
+ this.bufferMessage(message);
|
|
|
+ } else {
|
|
|
+ console.warn(
|
|
|
+ `[WebSocket] [${message.requestId?.substring(0, 8) || ''}] 发送失败: WebSocket未连接`,
|
|
|
+ message.type
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.doSend(message);
|
|
|
+ }
|
|
|
+
|
|
|
sendAudioChunk(requestId: string, data: string, chunkIndex: number): void {
|
|
|
this.send({
|
|
|
type: 'audio_chunk',
|
|
|
@@ -136,6 +204,11 @@ export class WebSocketManager {
|
|
|
|
|
|
disconnect(): void {
|
|
|
this.stopHeartbeat();
|
|
|
+ if (this.bufferTimer) {
|
|
|
+ clearTimeout(this.bufferTimer);
|
|
|
+ this.bufferTimer = null;
|
|
|
+ }
|
|
|
+ this.pendingMessages = [];
|
|
|
if (this.ws) {
|
|
|
this.ws.close();
|
|
|
this.ws = null;
|