399 lines
13 KiB
JavaScript
Raw Normal View History

2025-06-10 07:19:47 +08:00
/**
* MCP Feedback Enhanced - WebSocket 管理模組
* =========================================
*
* 處理 WebSocket 連接訊息傳遞和重連邏輯
*/
(function() {
'use strict';
// 確保命名空間和依賴存在
window.MCPFeedback = window.MCPFeedback || {};
const Utils = window.MCPFeedback.Utils;
/**
* WebSocket 管理器建構函數
*/
function WebSocketManager(options) {
options = options || {};
2025-06-13 05:48:08 +08:00
2025-06-10 07:19:47 +08:00
this.websocket = null;
this.isConnected = false;
this.connectionReady = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || Utils.CONSTANTS.MAX_RECONNECT_ATTEMPTS;
this.reconnectDelay = options.reconnectDelay || Utils.CONSTANTS.DEFAULT_RECONNECT_DELAY;
this.heartbeatInterval = null;
this.heartbeatFrequency = options.heartbeatFrequency || Utils.CONSTANTS.DEFAULT_HEARTBEAT_FREQUENCY;
2025-06-13 05:48:08 +08:00
2025-06-10 07:19:47 +08:00
// 事件回調
this.onOpen = options.onOpen || null;
this.onMessage = options.onMessage || null;
this.onClose = options.onClose || null;
this.onError = options.onError || null;
this.onConnectionStatusChange = options.onConnectionStatusChange || null;
2025-06-13 05:48:08 +08:00
2025-06-10 07:19:47 +08:00
// 標籤頁管理器引用
this.tabManager = options.tabManager || null;
2025-06-13 05:48:08 +08:00
// 連線監控器引用
this.connectionMonitor = options.connectionMonitor || null;
2025-06-10 07:19:47 +08:00
// 待處理的提交
this.pendingSubmission = null;
this.sessionUpdatePending = false;
}
/**
* 建立 WebSocket 連接
*/
WebSocketManager.prototype.connect = function() {
if (!Utils.isWebSocketSupported()) {
console.error('❌ 瀏覽器不支援 WebSocket');
return;
}
// 確保 WebSocket URL 格式正確
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
const wsUrl = protocol + '//' + host + '/ws';
console.log('嘗試連接 WebSocket:', wsUrl);
2025-06-13 10:33:24 +08:00
const connectingMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.connecting') : '連接中...';
this.updateConnectionStatus('connecting', connectingMessage);
2025-06-10 07:19:47 +08:00
try {
// 如果已有連接,先關閉
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
this.websocket = new WebSocket(wsUrl);
this.setupWebSocketEvents();
} catch (error) {
console.error('WebSocket 連接失敗:', error);
2025-06-13 10:33:24 +08:00
const connectionFailedMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.connectionFailed') : '連接失敗';
this.updateConnectionStatus('error', connectionFailedMessage);
2025-06-10 07:19:47 +08:00
}
};
/**
* 設置 WebSocket 事件監聽器
*/
WebSocketManager.prototype.setupWebSocketEvents = function() {
const self = this;
this.websocket.onopen = function() {
self.handleOpen();
};
this.websocket.onmessage = function(event) {
self.handleMessage(event);
};
this.websocket.onclose = function(event) {
self.handleClose(event);
};
this.websocket.onerror = function(error) {
self.handleError(error);
};
};
/**
* 處理連接開啟
*/
WebSocketManager.prototype.handleOpen = function() {
this.isConnected = true;
this.connectionReady = false; // 等待連接確認
2025-06-13 10:33:24 +08:00
const connectedMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.connected') : '已連接';
this.updateConnectionStatus('connected', connectedMessage);
2025-06-10 07:19:47 +08:00
console.log('WebSocket 連接已建立');
// 重置重連計數器和延遲
this.reconnectAttempts = 0;
this.reconnectDelay = Utils.CONSTANTS.DEFAULT_RECONNECT_DELAY;
2025-06-13 05:48:08 +08:00
// 通知連線監控器
if (this.connectionMonitor) {
this.connectionMonitor.startMonitoring();
}
2025-06-10 07:19:47 +08:00
// 開始心跳
this.startHeartbeat();
// 請求會話狀態
this.requestSessionStatus();
// 調用外部回調
if (this.onOpen) {
this.onOpen();
}
};
/**
* 處理訊息接收
*/
WebSocketManager.prototype.handleMessage = function(event) {
try {
const data = Utils.safeJsonParse(event.data, null);
if (data) {
2025-06-13 05:48:08 +08:00
// 記錄訊息到監控器
if (this.connectionMonitor) {
this.connectionMonitor.recordMessage();
}
2025-06-10 07:19:47 +08:00
this.processMessage(data);
2025-06-13 05:48:08 +08:00
2025-06-10 07:19:47 +08:00
// 調用外部回調
if (this.onMessage) {
this.onMessage(data);
}
}
} catch (error) {
console.error('解析 WebSocket 訊息失敗:', error);
}
};
/**
* 處理連接關閉
*/
WebSocketManager.prototype.handleClose = function(event) {
this.isConnected = false;
this.connectionReady = false;
console.log('WebSocket 連接已關閉, code:', event.code, 'reason:', event.reason);
// 停止心跳
this.stopHeartbeat();
2025-06-13 05:48:08 +08:00
// 通知連線監控器
if (this.connectionMonitor) {
this.connectionMonitor.stopMonitoring();
}
2025-06-10 07:19:47 +08:00
// 處理不同的關閉原因
if (event.code === 4004) {
2025-06-13 10:33:24 +08:00
const noActiveSessionMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.noActiveSession') : '沒有活躍會話';
this.updateConnectionStatus('disconnected', noActiveSessionMessage);
2025-06-10 07:19:47 +08:00
} else {
2025-06-13 10:33:24 +08:00
const disconnectedMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.disconnected') : '已斷開';
this.updateConnectionStatus('disconnected', disconnectedMessage);
2025-06-10 07:19:47 +08:00
this.handleReconnection(event);
}
// 調用外部回調
if (this.onClose) {
this.onClose(event);
}
};
/**
* 處理連接錯誤
*/
WebSocketManager.prototype.handleError = function(error) {
console.error('WebSocket 錯誤:', error);
2025-06-13 10:33:24 +08:00
const connectionErrorMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.connectionError') : '連接錯誤';
this.updateConnectionStatus('error', connectionErrorMessage);
2025-06-10 07:19:47 +08:00
// 調用外部回調
if (this.onError) {
this.onError(error);
}
};
/**
* 處理重連邏輯
*/
WebSocketManager.prototype.handleReconnection = function(event) {
// 會話更新導致的正常關閉,立即重連
if (event.code === 1000 && event.reason === '會話更新') {
console.log('🔄 會話更新導致的連接關閉,立即重連...');
this.sessionUpdatePending = true;
const self = this;
setTimeout(function() {
self.connect();
}, 200);
}
// 只有在非正常關閉時才重連
else if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 15000);
console.log(this.reconnectDelay / 1000 + '秒後嘗試重連... (第' + this.reconnectAttempts + '次)');
2025-06-13 05:48:08 +08:00
// 更新狀態為重連中
2025-06-13 10:33:24 +08:00
const reconnectingTemplate = window.i18nManager ? window.i18nManager.t('connectionMonitor.reconnecting') : '重連中... (第{attempt}次)';
const reconnectingMessage = reconnectingTemplate.replace('{attempt}', this.reconnectAttempts);
this.updateConnectionStatus('reconnecting', reconnectingMessage);
2025-06-13 05:48:08 +08:00
2025-06-10 07:19:47 +08:00
const self = this;
setTimeout(function() {
console.log('🔄 開始重連 WebSocket... (第' + self.reconnectAttempts + '次)');
self.connect();
}, this.reconnectDelay);
} else if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('❌ 達到最大重連次數,停止重連');
2025-06-13 10:33:24 +08:00
const maxReconnectMessage = window.i18nManager ? window.i18nManager.t('connectionMonitor.maxReconnectReached') : 'WebSocket 連接失敗,請刷新頁面重試';
Utils.showMessage(maxReconnectMessage, Utils.CONSTANTS.MESSAGE_ERROR);
2025-06-10 07:19:47 +08:00
}
};
/**
* 處理訊息
*/
WebSocketManager.prototype.processMessage = function(data) {
console.log('收到 WebSocket 訊息:', data);
switch (data.type) {
case 'connection_established':
console.log('WebSocket 連接確認');
this.connectionReady = true;
this.handleConnectionReady();
break;
case 'heartbeat_response':
this.handleHeartbeatResponse();
2025-06-13 05:48:08 +08:00
// 記錄 pong 時間到監控器
if (this.connectionMonitor) {
this.connectionMonitor.recordPong();
}
2025-06-10 07:19:47 +08:00
break;
default:
// 其他訊息類型由外部處理
break;
}
};
/**
* 處理連接就緒
*/
WebSocketManager.prototype.handleConnectionReady = function() {
// 如果有待提交的內容,現在可以提交了
if (this.pendingSubmission) {
console.log('🔄 連接就緒,提交待處理的內容');
const self = this;
setTimeout(function() {
if (self.pendingSubmission) {
self.send(self.pendingSubmission);
self.pendingSubmission = null;
}
}, 100);
}
};
/**
* 處理心跳回應
*/
WebSocketManager.prototype.handleHeartbeatResponse = function() {
if (this.tabManager) {
this.tabManager.updateLastActivity();
}
};
/**
* 發送訊息
*/
WebSocketManager.prototype.send = function(data) {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
try {
this.websocket.send(JSON.stringify(data));
return true;
} catch (error) {
console.error('發送 WebSocket 訊息失敗:', error);
return false;
}
} else {
console.warn('WebSocket 未連接,無法發送訊息');
return false;
}
};
/**
* 請求會話狀態
*/
WebSocketManager.prototype.requestSessionStatus = function() {
this.send({
type: 'get_status'
});
};
/**
* 開始心跳
*/
WebSocketManager.prototype.startHeartbeat = function() {
this.stopHeartbeat();
const self = this;
this.heartbeatInterval = setInterval(function() {
if (self.websocket && self.websocket.readyState === WebSocket.OPEN) {
2025-06-13 05:48:08 +08:00
// 記錄 ping 時間到監控器
if (self.connectionMonitor) {
self.connectionMonitor.recordPing();
}
2025-06-10 07:19:47 +08:00
self.send({
type: 'heartbeat',
tabId: self.tabManager ? self.tabManager.getTabId() : null,
timestamp: Date.now()
});
}
}, this.heartbeatFrequency);
console.log('💓 WebSocket 心跳已啟動,頻率: ' + this.heartbeatFrequency + 'ms');
};
/**
* 停止心跳
*/
WebSocketManager.prototype.stopHeartbeat = function() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
console.log('💔 WebSocket 心跳已停止');
}
};
/**
* 更新連接狀態
*/
WebSocketManager.prototype.updateConnectionStatus = function(status, text) {
if (this.onConnectionStatusChange) {
this.onConnectionStatusChange(status, text);
}
};
/**
* 設置待處理的提交
*/
WebSocketManager.prototype.setPendingSubmission = function(data) {
this.pendingSubmission = data;
};
/**
* 檢查是否已連接且就緒
*/
WebSocketManager.prototype.isReady = function() {
return this.isConnected && this.connectionReady;
};
/**
* 關閉連接
*/
WebSocketManager.prototype.close = function() {
this.stopHeartbeat();
if (this.websocket) {
this.websocket.close();
this.websocket = null;
}
this.isConnected = false;
this.connectionReady = false;
};
// 將 WebSocketManager 加入命名空間
window.MCPFeedback.WebSocketManager = WebSocketManager;
console.log('✅ WebSocketManager 模組載入完成');
})();