From 550be8bcc63a7a8002f82f9c67bb7c351430c2fb Mon Sep 17 00:00:00 2001 From: MatrixSeven Date: Mon, 15 Sep 2025 18:28:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E9=99=8D=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ConnectionStatus.tsx | 12 +- .../src/components/WebRTCFileTransfer.tsx | 34 +- chuan-next/src/components/WebRTCSettings.tsx | 38 +-- .../components/WebRTCTextImageTransfer.tsx | 12 +- .../components/examples/WebSocketExample.tsx | 153 +++++++++ .../components/webrtc/WebRTCFileReceive.tsx | 40 +-- .../components/webrtc/WebRTCTextReceiver.tsx | 18 +- .../components/webrtc/WebRTCTextSender.tsx | 44 ++- chuan-next/src/hooks/connection/index.ts | 5 +- .../state/useWebConnectStateManager.ts | 80 +++++ .../state/webConnectStore.ts} | 31 +- chuan-next/src/hooks/connection/types.ts | 123 ++++++++ .../src/hooks/connection/useConnectManager.ts | 249 +++++++++++++++ .../hooks/connection/useConnectionState.ts | 2 +- .../src/hooks/connection/useRoomConnection.ts | 5 +- .../hooks/connection/useWebRTCStateManager.ts | 78 ----- .../{ => webrtc}/useSharedWebRTCManager.ts | 64 +--- .../{ => webrtc}/useWebRTCConnectionCore.ts | 19 +- .../useWebRTCDataChannelManager.ts | 138 ++++---- .../{ => webrtc}/useWebRTCTrackManager.ts | 51 +-- chuan-next/src/hooks/connection/ws/index.ts | 2 + .../connection/ws/useWebSocketConnection.ts | 298 ++++++++++++++++++ .../desktop-share/useDesktopShareBusiness.ts | 24 +- .../hooks/file-transfer/useFileListSync.ts | 2 +- .../file-transfer/useFileTransferBusiness.ts | 44 +-- .../text-transfer/useTextTransferBusiness.ts | 35 +- chuan-next/src/hooks/ui/index.ts | 5 +- chuan-next/src/hooks/ui/useTabNavigation.ts | 8 +- chuan-next/src/hooks/ui/useURLHandler.ts | 6 +- 29 files changed, 1189 insertions(+), 431 deletions(-) create mode 100644 chuan-next/src/components/examples/WebSocketExample.tsx create mode 100644 chuan-next/src/hooks/connection/state/useWebConnectStateManager.ts rename chuan-next/src/hooks/{ui/webRTCStore.ts => connection/state/webConnectStore.ts} (55%) create mode 100644 chuan-next/src/hooks/connection/types.ts create mode 100644 chuan-next/src/hooks/connection/useConnectManager.ts delete mode 100644 chuan-next/src/hooks/connection/useWebRTCStateManager.ts rename chuan-next/src/hooks/connection/{ => webrtc}/useSharedWebRTCManager.ts (51%) rename chuan-next/src/hooks/connection/{ => webrtc}/useWebRTCConnectionCore.ts (97%) rename chuan-next/src/hooks/connection/{ => webrtc}/useWebRTCDataChannelManager.ts (86%) rename chuan-next/src/hooks/connection/{ => webrtc}/useWebRTCTrackManager.ts (88%) create mode 100644 chuan-next/src/hooks/connection/ws/index.ts create mode 100644 chuan-next/src/hooks/connection/ws/useWebSocketConnection.ts diff --git a/chuan-next/src/components/ConnectionStatus.tsx b/chuan-next/src/components/ConnectionStatus.tsx index bf3e72d..87d8710 100644 --- a/chuan-next/src/components/ConnectionStatus.tsx +++ b/chuan-next/src/components/ConnectionStatus.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { cn } from '@/lib/utils'; +import { useReadConnectState } from '@/hooks/connection/state/useWebConnectStateManager'; +import { Role } from '@/hooks/connection/types'; import { useWebRTCStore } from '@/hooks/index'; +import { cn } from '@/lib/utils'; interface ConnectionStatusProps { // 房间信息 - 只需要这个基本信息 @@ -14,7 +15,10 @@ interface ConnectionStatusProps { } // 连接状态枚举 -const getConnectionStatus = (connection: { isWebSocketConnected?: boolean; isPeerConnected?: boolean; isConnecting?: boolean; error?: string | null }, currentRoom: { code: string; role: 'sender' | 'receiver' } | null) => { +const getConnectionStatus = (currentRoom: { code: string; role: Role } | null) => { + + const { getConnectState } = useReadConnectState(); // 确保状态管理器被初始化 + const connection = getConnectState(); const isWebSocketConnected = connection?.isWebSocketConnected || false; const isPeerConnected = connection?.isPeerConnected || false; const isConnecting = connection?.isConnecting || false; @@ -162,7 +166,7 @@ export function ConnectionStatus(props: ConnectionStatusProps) { return {getConnectionStatusText(connection)}; } - const status = getConnectionStatus(connection, currentRoom ?? null); + const status = getConnectionStatus(currentRoom ?? null); if (compact) { return ( diff --git a/chuan-next/src/components/WebRTCFileTransfer.tsx b/chuan-next/src/components/WebRTCFileTransfer.tsx index db9426c..eb1a9fd 100644 --- a/chuan-next/src/components/WebRTCFileTransfer.tsx +++ b/chuan-next/src/components/WebRTCFileTransfer.tsx @@ -1,14 +1,14 @@ "use client"; -import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; -import { useSharedWebRTCManager, useConnectionState, useRoomConnection } from '@/hooks/connection'; -import { useFileTransferBusiness, useFileListSync, useFileStateManager } from '@/hooks/file-transfer'; -import { useURLHandler } from '@/hooks/ui'; import { Button } from '@/components/ui/button'; import { useToast } from '@/components/ui/toast-simple'; -import { Upload, Download } from 'lucide-react'; -import { WebRTCFileUpload } from '@/components/webrtc/WebRTCFileUpload'; import { WebRTCFileReceive } from '@/components/webrtc/WebRTCFileReceive'; +import { WebRTCFileUpload } from '@/components/webrtc/WebRTCFileUpload'; +import { useConnectionState, useRoomConnection, useSharedWebRTCManager } from '@/hooks/connection'; +import { useFileListSync, useFileStateManager, useFileTransferBusiness } from '@/hooks/file-transfer'; +import { useURLHandler } from '@/hooks/ui'; +import { Download, Upload } from 'lucide-react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; interface FileInfo { id: string; @@ -35,7 +35,7 @@ export const WebRTCFileTransfer: React.FC = () => { // 创建共享连接 const connection = useSharedWebRTCManager(); - const stableConnection = useMemo(() => connection, [connection.isConnected, connection.isConnecting, connection.isWebSocketConnected, connection.error]); + const stableConnection = useMemo(() => connection, [connection.getConnectState().isConnected, connection.getConnectState().isConnecting, connection.getConnectState().isWebSocketConnected, connection.getConnectState().error]); // 使用共享连接创建业务层 const { @@ -60,8 +60,8 @@ export const WebRTCFileTransfer: React.FC = () => { mode, pickupCode, isConnected, - isPeerConnected: connection.isPeerConnected, - getChannelState: connection.getChannelState + isPeerConnected: connection.getConnectState().isPeerConnected, + getChannelState: () => connection.getConnectState().state }); const { @@ -80,7 +80,7 @@ export const WebRTCFileTransfer: React.FC = () => { mode, pickupCode, syncFileListToReceiver, - isPeerConnected: connection.isPeerConnected + isPeerConnected: connection.getConnectState().isPeerConnected }); const { joinRoom: originalJoinRoom } = useRoomConnection({ @@ -409,24 +409,24 @@ export const WebRTCFileTransfer: React.FC = () => { selectedFilesCount: selectedFiles.length, fileListCount: fileList.length }); - }, [isConnected, connection.isPeerConnected, isConnecting, isWebSocketConnected, pickupCode, mode, selectedFiles.length, fileList.length]); + }, [isConnected, connection.getConnectState().isPeerConnected, isConnecting, isWebSocketConnected, pickupCode, mode, selectedFiles.length, fileList.length]); // 监听P2P连接建立时的状态变化 useEffect(() => { - if (connection.isPeerConnected && mode === 'send' && fileList.length > 0) { + if (connection.getConnectState().isPeerConnected && mode === 'send' && fileList.length > 0) { console.log('P2P连接已建立,数据通道首次打开,初始化文件列表'); // 数据通道第一次打开时进行初始化 syncFileListToReceiver(fileList, '数据通道初始化'); } - }, [connection.isPeerConnected, mode, syncFileListToReceiver]); + }, [connection.getConnectState().isPeerConnected, mode, syncFileListToReceiver]); // 监听fileList大小变化并同步 useEffect(() => { - if (connection.isPeerConnected && mode === 'send' && pickupCode) { + if (connection.getConnectState().isPeerConnected && mode === 'send' && pickupCode) { console.log('fileList大小变化,同步到接收方:', fileList.length); syncFileListToReceiver(fileList, 'fileList大小变化'); } - }, [fileList.length, connection.isPeerConnected, mode, pickupCode, syncFileListToReceiver]); + }, [fileList.length, connection.getConnectState().isPeerConnected, mode, pickupCode, syncFileListToReceiver]); // 监听selectedFiles变化,同步更新fileList并发送给接收方 useEffect(() => { @@ -635,9 +635,7 @@ export const WebRTCFileTransfer: React.FC = () => { onJoinRoom={joinRoom} files={fileList} onDownloadFile={handleDownloadRequest} - isConnected={isConnected} - isConnecting={isConnecting} - isWebSocketConnected={isWebSocketConnected} + downloadedFiles={downloadedFiles} error={error} onReset={resetConnection} diff --git a/chuan-next/src/components/WebRTCSettings.tsx b/chuan-next/src/components/WebRTCSettings.tsx index cd6c543..9b24107 100644 --- a/chuan-next/src/components/WebRTCSettings.tsx +++ b/chuan-next/src/components/WebRTCSettings.tsx @@ -1,28 +1,28 @@ "use client"; -import React, { useState } from 'react'; -import { - Settings, - Plus, - Trash2, - RotateCcw, - Save, - Info, - Server, +import { Button } from '@/components/ui/button'; +import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import { Input } from '@/components/ui/input'; +import { useToast } from '@/components/ui/toast-simple'; +import { useWebRTCStore } from '@/hooks/connection/state/webConnectStore'; +import { IceServerConfig, useIceServersConfig } from '@/hooks/settings/useIceServersConfig'; +import { + AlertTriangle, + Database, Eye, EyeOff, - AlertTriangle, + Info, + Plus, + RotateCcw, + Save, + Server, + Settings, Shield, - Database, - X, - Wifi + Trash2, + Wifi, + X } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { useIceServersConfig, IceServerConfig } from '@/hooks/settings/useIceServersConfig'; -import { useToast } from '@/components/ui/toast-simple'; -import { useWebRTCStore } from '@/hooks/ui/webRTCStore'; -import { ConfirmDialog } from '@/components/ui/confirm-dialog'; +import React, { useState } from 'react'; interface AddServerModalProps { isOpen: boolean; diff --git a/chuan-next/src/components/WebRTCTextImageTransfer.tsx b/chuan-next/src/components/WebRTCTextImageTransfer.tsx index ec6a9db..9de42c7 100644 --- a/chuan-next/src/components/WebRTCTextImageTransfer.tsx +++ b/chuan-next/src/components/WebRTCTextImageTransfer.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useState, useCallback } from 'react'; -import { useURLHandler } from '@/hooks/ui'; -import { useWebRTCStore } from '@/hooks/ui/webRTCStore'; -import { WebRTCTextSender } from '@/components/webrtc/WebRTCTextSender'; -import { WebRTCTextReceiver } from '@/components/webrtc/WebRTCTextReceiver'; import { Button } from '@/components/ui/button'; -import { MessageSquare, Send, Download, X } from 'lucide-react'; +import { WebRTCTextReceiver } from '@/components/webrtc/WebRTCTextReceiver'; +import { WebRTCTextSender } from '@/components/webrtc/WebRTCTextSender'; +import { useWebRTCStore } from '@/hooks/connection/state/webConnectStore'; +import { useURLHandler } from '@/hooks/ui'; +import { Download, Send, X } from 'lucide-react'; +import React, { useCallback, useState } from 'react'; export const WebRTCTextImageTransfer: React.FC = () => { // 状态管理 diff --git a/chuan-next/src/components/examples/WebSocketExample.tsx b/chuan-next/src/components/examples/WebSocketExample.tsx new file mode 100644 index 0000000..bf4ec74 --- /dev/null +++ b/chuan-next/src/components/examples/WebSocketExample.tsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react'; +import { IWebMessage } from '../../hooks/connection/types'; +import { useWebSocketConnection } from '../../hooks/connection/ws/useWebSocketConnection'; + +/** + * WebSocket 连接示例组件 + * 展示如何使用 WebSocket 实现的 IWebConnection 接口 + */ +export function WebSocketExample() { + const [roomCode, setRoomCode] = useState(''); + const [role, setRole] = useState<'sender' | 'receiver'>('sender'); + const [message, setMessage] = useState(''); + + // 使用 WebSocket 连接 + const connection = useWebSocketConnection(); + const state = connection.getConnectState(); + + // 连接到房间 + const handleConnect = async () => { + if (roomCode.trim()) { + await connection.connect(roomCode.trim(), role); + } + }; + + // 发送消息 + const handleSendMessage = () => { + if (message.trim()) { + connection.sendMessage({ + type: 'text', + payload: { text: message.trim() } + }); + setMessage(''); + } + }; + + // 注册消息处理器 + React.useEffect(() => { + const unsubscribe = connection.registerMessageHandler('text', (msg: IWebMessage) => { + console.log('收到文本消息:', msg.payload.text); + }); + + return unsubscribe; + }, [connection]); + + return ( +
+

WebSocket 连接示例

+ + {/* 连接状态 */} +
+

连接状态: {state.isConnected ? '已连接' : '未连接'}

+

传输类型: {connection.connectType}

+ {state.error && ( +

错误: {state.error}

+ )} + {state.currentRoom && ( +

房间: {state.currentRoom.code} ({state.currentRoom.role})

+ )} +
+ + {/* 连接控制 */} + {!state.isConnected ? ( +
+
+ + setRoomCode(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded" + placeholder="输入房间代码" + /> +
+ +
+ + +
+ + + + {state.canRetry && ( + + )} +
+ ) : ( +
+ {/* 消息发送 */} +
+ +
+ setMessage(e.target.value)} + className="flex-1 px-3 py-2 border border-gray-300 rounded" + placeholder="输入消息内容" + onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} + /> + +
+
+ + {/* 断开连接 */} + +
+ )} + + {/* 功能说明 */} +
+

支持的功能:

+
    +
  • ✅ WebSocket 连接管理
  • +
  • ✅ 消息发送和接收
  • +
  • ✅ 二进制数据传输
  • +
  • ✅ 多通道消息处理
  • +
  • ✅ 自动重连机制
  • +
  • ❌ 媒体轨道(WebSocket 不支持)
  • +
  • ❌ P2P 直连(WebSocket 不支持)
  • +
+
+
+ ); +} \ No newline at end of file diff --git a/chuan-next/src/components/webrtc/WebRTCFileReceive.tsx b/chuan-next/src/components/webrtc/WebRTCFileReceive.tsx index 1024312..cb66cd6 100644 --- a/chuan-next/src/components/webrtc/WebRTCFileReceive.tsx +++ b/chuan-next/src/components/webrtc/WebRTCFileReceive.tsx @@ -1,12 +1,13 @@ "use client"; -import React, { useState, useCallback, useRef } from 'react'; +import { ConnectionStatus } from '@/components/ConnectionStatus'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { Download, FileText, Image, Video, Music, Archive, Clock, Zap } from 'lucide-react'; import { useToast } from '@/components/ui/toast-simple'; -import { ConnectionStatus } from '@/components/ConnectionStatus'; -import { TransferProgressTracker, formatTransferSpeed, formatTime } from '@/lib/transfer-utils'; +import { useReadConnectState } from '@/hooks/connection/state/useWebConnectStateManager'; +import { TransferProgressTracker } from '@/lib/transfer-utils'; +import { Archive, Clock, Download, FileText, Image, Music, Video, Zap } from 'lucide-react'; +import React, { useCallback, useRef, useState } from 'react'; interface FileInfo { id: string; @@ -39,9 +40,6 @@ interface WebRTCFileReceiveProps { onJoinRoom: (code: string) => void; files: FileInfo[]; onDownloadFile: (fileId: string) => void; - isConnected: boolean; - isConnecting: boolean; - isWebSocketConnected?: boolean; downloadedFiles?: Map; error?: string | null; onReset?: () => void; @@ -52,9 +50,6 @@ export function WebRTCFileReceive({ onJoinRoom, files, onDownloadFile, - isConnected, - isConnecting, - isWebSocketConnected = false, downloadedFiles, error = null, onReset, @@ -64,12 +59,16 @@ export function WebRTCFileReceive({ const [isValidating, setIsValidating] = useState(false); const { showToast } = useToast(); + // 用于跟踪传输进度的trackers const transferTrackers = useRef>(new Map()); // 使用传入的取件码或本地状态的取件码 const displayPickupCode = propPickupCode || pickupCode; + const { getConnectState } = useReadConnectState(); + + // 验证取件码是否存在 const validatePickupCode = async (code: string): Promise => { try { @@ -146,7 +145,7 @@ export function WebRTCFileReceive({ // 当验证失败时重置输入状态 React.useEffect(() => { - if (error && !isConnecting && !isConnected && !isValidating) { + if (error && !getConnectState().isConnecting && !getConnectState().isConnected && !isValidating) { // 延迟重置,确保用户能看到错误信息 const timer = setTimeout(() => { console.log('重置取件码输入'); @@ -155,10 +154,10 @@ export function WebRTCFileReceive({ return () => clearTimeout(timer); } - }, [error, isConnecting, isConnected, isValidating]); + }, [error, getConnectState, isValidating]); // 如果已经连接但没有文件,显示等待界面 - if ((isConnected || isConnecting) && files.length === 0) { + if ((getConnectState().isConnected || getConnectState().isConnecting) && files.length === 0) { return (
{/* 功能标题和状态 */} @@ -168,6 +167,7 @@ export function WebRTCFileReceive({
+ {getConnectState().isWebSocketConnected}

文件接收中

取件码: {displayPickupCode}

@@ -190,9 +190,9 @@ export function WebRTCFileReceive({ {/* 连接状态指示器 */}
-
- - {isConnected ? '连接已建立' : '连接中...'} +
+ + {getConnectState().isConnected ? '连接已建立' : '连接中...'}
@@ -326,7 +326,7 @@ export function WebRTCFileReceive({