mirror of
https://github.com/MatrixSeven/file-transfer-go.git
synced 2026-02-22 23:04:45 +08:00
- 使用更宽松的条件检查连接状态,确保文件列表和文件请求的发送时机 - 添加清除发送方数据的逻辑,当接收方离开房间时重置文件传输状态 - 在文件传输业务中设置断开连接回调,确保在连接断开时清理相关数据 - 更新数据通道和P2P连接状态的处理,增强连接状态的监控和反馈
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
import { useCallback, useEffect, useRef } from 'react';
|
||
import { IWebConnectStateManager } from '../state/useWebConnectStateManager';
|
||
import { DataHandler, IRegisterEventHandler, IWebMessage, MessageHandler, Role, WebRTCDataChannelManager } from '../types';
|
||
|
||
|
||
|
||
/**
|
||
* WebRTC 数据通道管理 Hook
|
||
* 负责数据通道的创建和管理,处理数据通道消息的发送和接收
|
||
*/
|
||
export function useWebRTCDataChannelManager(
|
||
stateManager: IWebConnectStateManager
|
||
): WebRTCDataChannelManager & IRegisterEventHandler {
|
||
const dcRef = useRef<RTCDataChannel | null>(null);
|
||
const stateManagerRef = useRef(stateManager);
|
||
stateManagerRef.current = stateManager;
|
||
|
||
// 多通道消息处理器
|
||
const messageHandlers = useRef<Map<string, MessageHandler>>(new Map());
|
||
const dataHandlers = useRef<Map<string, DataHandler>>(new Map());
|
||
|
||
|
||
// 创建数据通道
|
||
const createDataChannel = useCallback((
|
||
pc: RTCPeerConnection,
|
||
role: Role,
|
||
isReconnect: boolean = false
|
||
) => {
|
||
console.log('[DataChannelManager] 创建数据通道...', { role, isReconnect });
|
||
|
||
// 如果已经存在数据通道,先关闭它
|
||
if (dcRef.current) {
|
||
console.log('[DataChannelManager] 关闭已存在的数据通道');
|
||
dcRef.current.close();
|
||
dcRef.current = null;
|
||
}
|
||
|
||
// 数据通道处理
|
||
if (role === 'sender') {
|
||
const dataChannel = pc.createDataChannel('shared-channel', {
|
||
ordered: true,
|
||
maxRetransmits: 3
|
||
});
|
||
dcRef.current = dataChannel;
|
||
|
||
dataChannel.onopen = (event) => {
|
||
console.log('[DataChannelManager] 数据通道已打开 (发送方)');
|
||
// 确保所有连接状态都正确更新
|
||
stateManagerRef.current.updateState({
|
||
isDataChannelConnected: true,
|
||
isConnected: true,
|
||
isPeerConnected: true,
|
||
error: null,
|
||
isConnecting: false,
|
||
canRetry: false,
|
||
state: 'open'
|
||
});
|
||
|
||
// 如果是重新连接,触发数据同步
|
||
if (isReconnect) {
|
||
console.log('[DataChannelManager] 发送方重新连接,数据通道已打开,准备同步数据');
|
||
// 发送同步请求消息
|
||
setTimeout(() => {
|
||
if (dataChannel.readyState === 'open') {
|
||
dataChannel.send(JSON.stringify({
|
||
type: 'sync-request',
|
||
payload: { timestamp: Date.now() }
|
||
}));
|
||
console.log('[DataChannelManager] 发送方发送数据同步请求');
|
||
}
|
||
}, 300); // 等待数据通道完全稳定
|
||
}
|
||
};
|
||
|
||
|
||
dataChannel.onmessage = handleDataChannelMessage;
|
||
|
||
dataChannel.onerror = (error) => {
|
||
console.error('[DataChannelManager] 数据通道错误:', error);
|
||
|
||
// 获取更详细的错误信息
|
||
let errorMessage = '数据通道连接失败';
|
||
let shouldRetry = false;
|
||
|
||
// 根据数据通道状态提供更具体的错误信息
|
||
switch (dataChannel.readyState) {
|
||
case 'connecting':
|
||
errorMessage = '数据通道正在连接中,请稍候...';
|
||
shouldRetry = true;
|
||
break;
|
||
case 'closing':
|
||
errorMessage = '数据通道正在关闭,连接即将断开';
|
||
break;
|
||
case 'closed':
|
||
errorMessage = '数据通道已关闭,P2P连接失败';
|
||
shouldRetry = true;
|
||
break;
|
||
default:
|
||
// 检查PeerConnection状态
|
||
if (pc) {
|
||
switch (pc.connectionState) {
|
||
case 'failed':
|
||
errorMessage = 'P2P连接失败,可能是网络防火墙阻止了连接,请尝试切换网络或使用VPN';
|
||
shouldRetry = true;
|
||
break;
|
||
case 'disconnected':
|
||
errorMessage = 'P2P连接已断开,网络可能不稳定';
|
||
shouldRetry = true;
|
||
break;
|
||
default:
|
||
errorMessage = '数据通道连接失败,可能是网络环境受限';
|
||
shouldRetry = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
console.error(`[DataChannelManager] 数据通道详细错误 - 状态: ${dataChannel.readyState}, 消息: ${errorMessage}, 建议重试: ${shouldRetry}`);
|
||
|
||
stateManagerRef.current.updateState({
|
||
error: errorMessage,
|
||
isConnecting: false,
|
||
isPeerConnected: false, // 数据通道出错时,P2P连接肯定不可用
|
||
isDataChannelConnected: false,
|
||
canRetry: shouldRetry // 设置是否可以重试
|
||
});
|
||
};
|
||
} else {
|
||
pc.ondatachannel = (event) => {
|
||
const dataChannel = event.channel;
|
||
dcRef.current = dataChannel;
|
||
|
||
dataChannel.onopen = (event) => {
|
||
console.log('[DataChannelManager] 数据通道已打开 (接收方)');
|
||
// 确保所有连接状态都正确更新
|
||
stateManagerRef.current.updateState({
|
||
isWebSocketConnected: true,
|
||
isDataChannelConnected: true,
|
||
isConnected: true,
|
||
isPeerConnected: true,
|
||
error: null,
|
||
isConnecting: false,
|
||
canRetry: false,
|
||
state: 'open'
|
||
});
|
||
|
||
// 如果是重新连接,触发数据同步
|
||
if (isReconnect) {
|
||
console.log('[DataChannelManager] 接收方重新连接,数据通道已打开,准备同步数据');
|
||
// 发送同步请求消息
|
||
setTimeout(() => {
|
||
if (dataChannel.readyState === 'open') {
|
||
dataChannel.send(JSON.stringify({
|
||
type: 'sync-request',
|
||
payload: { timestamp: Date.now() }
|
||
}));
|
||
console.log('[DataChannelManager] 接收方发送数据同步请求');
|
||
}
|
||
}, 300); // 等待数据通道完全稳定
|
||
}
|
||
};
|
||
|
||
dataChannel.onmessage = handleDataChannelMessage;
|
||
|
||
dataChannel.onerror = (error) => {
|
||
console.error('[DataChannelManager] 数据通道错误 (接收方):', error);
|
||
|
||
// 获取更详细的错误信息
|
||
let errorMessage = '数据通道连接失败';
|
||
let shouldRetry = false;
|
||
|
||
// 根据数据通道状态提供更具体的错误信息
|
||
switch (dataChannel.readyState) {
|
||
case 'connecting':
|
||
errorMessage = '数据通道正在连接中,请稍候...';
|
||
shouldRetry = true;
|
||
break;
|
||
case 'closing':
|
||
errorMessage = '数据通道正在关闭,连接即将断开';
|
||
break;
|
||
case 'closed':
|
||
errorMessage = '数据通道已关闭,P2P连接失败';
|
||
shouldRetry = true;
|
||
break;
|
||
default:
|
||
// 检查PeerConnection状态
|
||
if (pc) {
|
||
switch (pc.connectionState) {
|
||
case 'failed':
|
||
errorMessage = 'P2P连接失败,可能是网络防火墙阻止了连接,请尝试切换网络或使用VPN';
|
||
shouldRetry = true;
|
||
break;
|
||
case 'disconnected':
|
||
errorMessage = 'P2P连接已断开,网络可能不稳定';
|
||
shouldRetry = true;
|
||
break;
|
||
default:
|
||
errorMessage = '数据通道连接失败,可能是网络环境受限';
|
||
shouldRetry = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
console.error(`[DataChannelManager] 数据通道详细错误 (接收方) - 状态: ${dataChannel.readyState}, 消息: ${errorMessage}, 建议重试: ${shouldRetry}`);
|
||
|
||
stateManagerRef.current.updateState({
|
||
error: errorMessage,
|
||
isConnecting: false,
|
||
isPeerConnected: false, // 数据通道出错时,P2P连接肯定不可用
|
||
isDataChannelConnected: false,
|
||
canRetry: shouldRetry // 设置是否可以重试
|
||
});
|
||
};
|
||
};
|
||
}
|
||
|
||
console.log('[DataChannelManager] 数据通道创建完成,角色:', role, '是否重新连接:', isReconnect);
|
||
}, [stateManager]);
|
||
|
||
// 处理数据通道消息
|
||
const handleDataChannelMessage = useCallback((event: MessageEvent) => {
|
||
console.log('[DataChannelManager] 收到数据通道消息,类型:', typeof event.data);
|
||
console.log('[DataChannelManager] 数据通道当前状态:', messageHandlers.current);
|
||
if (typeof event.data === 'string') {
|
||
try {
|
||
const message = JSON.parse(event.data) as IWebMessage;
|
||
console.log('[DataChannelManager] 收到消息:', message.type, message.channel || 'default');
|
||
|
||
// 根据通道分发消息
|
||
if (message.channel) {
|
||
const handler = messageHandlers.current.get(message.channel);
|
||
if (handler) {
|
||
handler(message);
|
||
}
|
||
} else {
|
||
// 兼容旧版本,广播给所有处理器
|
||
messageHandlers.current.forEach(handler => handler(message));
|
||
}
|
||
} catch (error) {
|
||
console.error('[DataChannelManager] 解析消息失败:', error);
|
||
}
|
||
} else if (event.data instanceof ArrayBuffer) {
|
||
console.log('[DataChannelManager] 收到数据:', event.data.byteLength, 'bytes');
|
||
|
||
// 数据优先发给文件传输处理器
|
||
const fileHandler = dataHandlers.current.get('file-transfer');
|
||
if (fileHandler) {
|
||
fileHandler(event.data);
|
||
} else {
|
||
// 如果没有文件处理器,发给第一个处理器
|
||
const firstHandler = dataHandlers.current.values().next().value;
|
||
if (firstHandler) {
|
||
firstHandler(event.data);
|
||
}
|
||
}
|
||
}
|
||
}, []);
|
||
|
||
// 注册消息处理器
|
||
const registerMessageHandler = useCallback((channel: string, handler: MessageHandler) => {
|
||
console.log('[DataChannelManager] 注册消息处理器:', channel);
|
||
messageHandlers.current.set(channel, handler);
|
||
return () => {
|
||
console.log('[DataChannelManager] 取消注册消息处理器:', channel);
|
||
messageHandlers.current.delete(channel);
|
||
};
|
||
}, []);
|
||
|
||
// 注册数据处理器
|
||
const registerDataHandler = useCallback((channel: string, handler: DataHandler) => {
|
||
console.log('[DataChannelManager] 注册数据处理器:', channel);
|
||
dataHandlers.current.set(channel, handler);
|
||
|
||
return () => {
|
||
console.log('[DataChannelManager] 取消注册数据处理器:', channel);
|
||
dataHandlers.current.delete(channel);
|
||
};
|
||
}, []);
|
||
|
||
// 发送消息
|
||
const sendMessage = useCallback((message: IWebMessage, channel?: string) => {
|
||
const dataChannel = dcRef.current;
|
||
if (!dataChannel || dataChannel.readyState !== 'open') {
|
||
console.error('[DataChannelManager] 数据通道未准备就绪');
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
const messageWithChannel = channel ? { ...message, channel } : message;
|
||
dataChannel.send(JSON.stringify(messageWithChannel));
|
||
console.log('[DataChannelManager] 发送消息:', message.type, channel || 'default');
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[DataChannelManager] 发送消息失败:', error);
|
||
return false;
|
||
}
|
||
}, []);
|
||
|
||
// 发送二进制数据
|
||
const sendData = useCallback((data: ArrayBuffer) => {
|
||
const dataChannel = dcRef.current;
|
||
if (!dataChannel || dataChannel.readyState !== 'open') {
|
||
console.error('[DataChannelManager] 数据通道未准备就绪');
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
dataChannel.send(data);
|
||
console.log('[DataChannelManager] 发送数据:', data.byteLength, 'bytes');
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[DataChannelManager] 发送数据失败:', error);
|
||
return false;
|
||
}
|
||
}, []);
|
||
|
||
|
||
|
||
// 获取数据通道状态
|
||
const getChannelState = useCallback(() => {
|
||
return stateManagerRef.current.getState();
|
||
}, []);
|
||
|
||
// 实时更新数据通道状态
|
||
useEffect(() => {
|
||
const updateChannelState = () => {
|
||
const readyState = dcRef.current?.readyState || 'closed';
|
||
console.log('[DataChannelManager] 数据通道状态更新:', readyState);
|
||
|
||
// 更新状态存储中的数据通道状态
|
||
stateManagerRef.current.updateState({
|
||
state: readyState,
|
||
isDataChannelConnected: readyState === 'open'
|
||
});
|
||
};
|
||
|
||
// 立即更新一次
|
||
updateChannelState();
|
||
|
||
// 如果数据通道存在,设置状态变化监听
|
||
if (dcRef.current) {
|
||
const dc = dcRef.current;
|
||
const originalOnOpen = dc.onopen;
|
||
const originalOnClose = dc.onclose;
|
||
const originalOnError = dc.onerror;
|
||
|
||
dc.onopen = (event) => {
|
||
console.log('[DataChannelManager] 数据通道打开事件触发');
|
||
updateChannelState();
|
||
if (originalOnOpen) originalOnOpen.call(dc, event);
|
||
};
|
||
|
||
dc.onclose = (event) => {
|
||
console.log('[DataChannelManager] 数据通道关闭事件触发');
|
||
updateChannelState();
|
||
if (originalOnClose) originalOnClose.call(dc, event);
|
||
};
|
||
|
||
dc.onerror = (error) => {
|
||
console.log('[DataChannelManager] 数据通道错误事件触发');
|
||
updateChannelState();
|
||
if (originalOnError) originalOnError.call(dc, error);
|
||
};
|
||
}
|
||
|
||
// 清理函数
|
||
return () => {
|
||
if (dcRef.current) {
|
||
// 恢复原始事件处理器
|
||
// 注意:在实际应用中,可能需要更复杂的事件处理器管理
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
return {
|
||
createDataChannel,
|
||
sendMessage,
|
||
sendData,
|
||
getConnectState: getChannelState,
|
||
registerDataHandler,
|
||
registerMessageHandler,
|
||
handleDataChannelMessage,
|
||
};
|
||
} |