|
|
@@ -13,6 +13,7 @@ import {
|
|
|
} from '../store/slices/websocketSlice';
|
|
|
import { setError } from '../store/slices/appSlice';
|
|
|
import { WebSocketMessage } from '../types';
|
|
|
+import { browserCompat } from '../utils/browserCompat';
|
|
|
|
|
|
interface UseWebSocketOptions {
|
|
|
url?: string;
|
|
|
@@ -22,6 +23,60 @@ interface UseWebSocketOptions {
|
|
|
onError?: (error: Error) => void;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 获取WebSocket连接URL,支持自动协议检测
|
|
|
+ * @param customUrl 自定义URL
|
|
|
+ * @returns WebSocket连接URL
|
|
|
+ */
|
|
|
+const getWebSocketUrl = (customUrl?: string): string => {
|
|
|
+ let baseUrl: string;
|
|
|
+
|
|
|
+ if (customUrl) {
|
|
|
+ baseUrl = customUrl;
|
|
|
+ } else {
|
|
|
+ // 从环境变量获取配置
|
|
|
+ const envUrl = process.env.REACT_APP_WS_URL;
|
|
|
+ if (envUrl) {
|
|
|
+ // 如果环境变量中使用了IP地址,但当前页面是localhost,则使用localhost替换
|
|
|
+ if (window.location.hostname === 'localhost' && envUrl.includes('://192.168.')) {
|
|
|
+ const urlParts = envUrl.split('://');
|
|
|
+ const protocolPart = urlParts[0];
|
|
|
+ const addressPart = urlParts[1];
|
|
|
+ const portMatch = addressPart.match(/:(\d+)$/);
|
|
|
+ const port = portMatch ? portMatch[1] : (protocolPart === 'wss' ? '8766' : '8765');
|
|
|
+ baseUrl = `${protocolPart}://localhost:${port}`;
|
|
|
+ console.log('[WebSocket] 检测到localhost环境,将IP地址替换为localhost:', baseUrl);
|
|
|
+ } else {
|
|
|
+ baseUrl = envUrl;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 自动协议检测
|
|
|
+ const isHttps = window.location.protocol === 'https:';
|
|
|
+ const protocol = isHttps ? 'wss:' : 'ws:';
|
|
|
+ const hostname = window.location.hostname;
|
|
|
+
|
|
|
+ // 根据协议选择端口
|
|
|
+ const defaultPort = isHttps ? '8766' : '8765';
|
|
|
+ const port = process.env.REACT_APP_WS_PORT || defaultPort;
|
|
|
+
|
|
|
+ baseUrl = `${protocol}//${hostname}:${port}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用浏览器兼容性管理器处理URL
|
|
|
+ const compatibleUrl = browserCompat.getCompatibleWebSocketUrl(baseUrl);
|
|
|
+
|
|
|
+ console.log('WebSocket自动协议检测:', {
|
|
|
+ pageProtocol: window.location.protocol,
|
|
|
+ hostname: window.location.hostname,
|
|
|
+ baseUrl,
|
|
|
+ compatibleUrl,
|
|
|
+ browserInfo: browserCompat.getBrowserInfo()
|
|
|
+ });
|
|
|
+
|
|
|
+ return compatibleUrl;
|
|
|
+};
|
|
|
+
|
|
|
export const useWebSocket = (options: UseWebSocketOptions = {}) => {
|
|
|
const dispatch = useDispatch();
|
|
|
const { isConnected, connectionStatus, reconnectAttempts, session } = useSelector(
|
|
|
@@ -30,7 +85,6 @@ export const useWebSocket = (options: UseWebSocketOptions = {}) => {
|
|
|
|
|
|
const wsRef = useRef<WebSocketService | null>(null);
|
|
|
const chunkCounterRef = useRef(0);
|
|
|
- const defaultUrl = process.env.REACT_APP_WS_URL || 'ws://localhost:8765';
|
|
|
const connectingRef = useRef(false); // 防止重复连接
|
|
|
const errorTimeoutRef = useRef<NodeJS.Timeout | null>(null); // 错误提示延迟
|
|
|
|
|
|
@@ -42,7 +96,7 @@ export const useWebSocket = (options: UseWebSocketOptions = {}) => {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- const wsUrl = url || options.url || defaultUrl;
|
|
|
+ const wsUrl = getWebSocketUrl(url || options.url);
|
|
|
|
|
|
try {
|
|
|
connectingRef.current = true;
|
|
|
@@ -54,6 +108,11 @@ export const useWebSocket = (options: UseWebSocketOptions = {}) => {
|
|
|
}
|
|
|
|
|
|
// 设置事件监听器
|
|
|
+ // 设置音频响应回调
|
|
|
+ if (options.onAudioResponse) {
|
|
|
+ wsRef.current.onAudioResponse(options.onAudioResponse);
|
|
|
+ console.log('[useWebSocket] 音频响应回调已设置');
|
|
|
+ }
|
|
|
wsRef.current.onOpen(() => {
|
|
|
console.log('useWebSocket: WebSocket连接已建立,设置状态为已连接');
|
|
|
connectingRef.current = false; // 重置连接状态
|
|
|
@@ -93,82 +152,69 @@ export const useWebSocket = (options: UseWebSocketOptions = {}) => {
|
|
|
|
|
|
wsRef.current.onMessage((data) => {
|
|
|
if (data instanceof ArrayBuffer) {
|
|
|
- // 处理音频响应
|
|
|
- options.onAudioResponse?.(data);
|
|
|
+ // 处理音频数据
|
|
|
+ chunkCounterRef.current++;
|
|
|
+ console.log(`[useWebSocket] 收到音频chunk ${chunkCounterRef.current}:`, data.byteLength, 'bytes');
|
|
|
+ options.onMessage?.(data);
|
|
|
} else {
|
|
|
- // 处理其他消息
|
|
|
+ // 处理文本消息
|
|
|
try {
|
|
|
- const message: WebSocketMessage = JSON.parse(data as string);
|
|
|
- handleMessage(message);
|
|
|
+ const message: WebSocketMessage = JSON.parse(data);
|
|
|
+ console.log('[useWebSocket] 收到消息:', message);
|
|
|
+
|
|
|
+ switch (message.type) {
|
|
|
+ case 'session_init_response':
|
|
|
+ if (message.data?.session_id) {
|
|
|
+ dispatch(setSession(message.data.session_id));
|
|
|
+ console.log('[useWebSocket] 会话已初始化:', message.data.session_id);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'latency_response':
|
|
|
+ if (typeof message.data?.latency === 'number') {
|
|
|
+ dispatch(setLatency(message.data.latency));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'session_init':
|
|
|
+ console.log('[useWebSocket] 收到会话初始化响应:', message);
|
|
|
+ if (message.session_id) {
|
|
|
+ dispatch(setSession({
|
|
|
+ id: message.session_id,
|
|
|
+ userId: message.user_id,
|
|
|
+ language: message.language || 'zh-CN',
|
|
|
+ startTime: Date.now(),
|
|
|
+ lastActivity: Date.now(),
|
|
|
+ isActive: true,
|
|
|
+ }));
|
|
|
+ console.log('[useWebSocket] 会话状态已更新到Redux');
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ options.onMessage?.(data);
|
|
|
+ break;
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
- // 直接传递原始数据
|
|
|
+ console.error('[useWebSocket] 解析消息失败:', error, data);
|
|
|
options.onMessage?.(data);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 设置音频响应回调
|
|
|
- if (options.onAudioResponse) {
|
|
|
- wsRef.current.onAudioResponse(options.onAudioResponse);
|
|
|
- }
|
|
|
-
|
|
|
+ // 尝试连接
|
|
|
await wsRef.current.connect(wsUrl);
|
|
|
+ console.log(`[useWebSocket] 成功连接到 ${wsUrl}`);
|
|
|
|
|
|
} catch (error) {
|
|
|
- console.error('WebSocket连接失败:', error);
|
|
|
connectingRef.current = false; // 重置连接状态
|
|
|
-
|
|
|
- // 延迟显示错误,给重连机会
|
|
|
- if (errorTimeoutRef.current) {
|
|
|
- clearTimeout(errorTimeoutRef.current);
|
|
|
- }
|
|
|
-
|
|
|
- errorTimeoutRef.current = setTimeout(() => {
|
|
|
- if (!isConnected) {
|
|
|
- dispatch(setError(error instanceof Error ? error.message : 'WebSocket连接失败'));
|
|
|
- dispatch(setConnected(false));
|
|
|
- }
|
|
|
- }, 3000); // 3秒延迟
|
|
|
+ console.error(`[useWebSocket] 连接失败:`, error);
|
|
|
+ dispatch(setError(`连接失败: ${error instanceof Error ? error.message : '未知错误'}`));
|
|
|
+ throw error;
|
|
|
}
|
|
|
- }, [options, defaultUrl, dispatch, isConnected]);
|
|
|
-
|
|
|
- // 处理WebSocket消息
|
|
|
- const handleMessage = useCallback((message: WebSocketMessage) => {
|
|
|
- console.log('[useWebSocket] 处理消息:', message.type, message);
|
|
|
-
|
|
|
- switch (message.type) {
|
|
|
- case 'session_init':
|
|
|
- console.log('[useWebSocket] 收到会话初始化响应:', message);
|
|
|
- if (message.session_id) {
|
|
|
- dispatch(setSession({
|
|
|
- id: message.session_id,
|
|
|
- userId: message.user_id,
|
|
|
- language: message.language || 'zh-CN',
|
|
|
- startTime: Date.now(),
|
|
|
- lastActivity: Date.now(),
|
|
|
- isActive: true,
|
|
|
- }));
|
|
|
- console.log('[useWebSocket] 会话状态已更新到Redux');
|
|
|
- }
|
|
|
- break;
|
|
|
+ }, [dispatch, options, isConnected]);
|
|
|
|
|
|
- case 'pong':
|
|
|
- if (message.timestamp) {
|
|
|
- const latency = Date.now() - message.timestamp;
|
|
|
- dispatch(setLatency(latency));
|
|
|
- }
|
|
|
- break;
|
|
|
-
|
|
|
- case 'error':
|
|
|
- console.error('[useWebSocket] 收到错误消息:', message);
|
|
|
- dispatch(setError(message.message || '服务器错误'));
|
|
|
- break;
|
|
|
-
|
|
|
- default:
|
|
|
- console.log('[useWebSocket] 未处理的消息类型:', message.type);
|
|
|
- options.onMessage?.(JSON.stringify(message));
|
|
|
- }
|
|
|
- }, [dispatch, options]);
|
|
|
+ // 处理WebSocket消息的逻辑已集成到WebSocketManager中
|
|
|
|
|
|
// 断开连接
|
|
|
const disconnect = useCallback(() => {
|