新增房间验证工具函数,包含房间代码格式验证和房间状态检查逻辑

This commit is contained in:
MatrixSeven
2026-03-01 00:08:29 +08:00
parent 84d7caea8c
commit 1a6a7369b9
25 changed files with 2697 additions and 1241 deletions

View File

@@ -1,5 +1,4 @@
// 连接相关的hooks
export { useConnectionState } from './useConnectionState';
export { useRoomConnection } from './useRoomConnection';
export { useSharedWebRTCManager } from './useSharedWebRTCManager';
export { useWebRTCSupport } from './useWebRTCSupport';

View File

@@ -1,137 +0,0 @@
import { useState, useEffect, useCallback } from 'react';
import { useToast } from '@/components/ui/toast-simple';
interface UseConnectionStateProps {
isWebSocketConnected: boolean;
isConnected: boolean;
isConnecting: boolean;
error: string;
pickupCode: string;
fileListLength: number;
currentTransferFile: any;
setCurrentTransferFile: (file: any) => void;
updateFileListStatus: (callback: (prev: any[]) => any[]) => void;
}
export const useConnectionState = ({
isWebSocketConnected,
isConnected,
isConnecting,
error,
pickupCode,
fileListLength,
currentTransferFile,
setCurrentTransferFile,
updateFileListStatus
}: UseConnectionStateProps) => {
const { showToast } = useToast();
const [lastError, setLastError] = useState<string>('');
// 处理连接错误
useEffect(() => {
if (error && error !== lastError) {
console.log('=== 连接错误处理 ===');
console.log('错误信息:', error);
// 根据错误类型显示不同的提示
let errorMessage = error;
if (error.includes('WebSocket')) {
errorMessage = '服务器连接失败,请检查网络连接或稍后重试';
} else if (error.includes('数据通道')) {
errorMessage = '数据通道连接失败,请重新尝试连接';
} else if (error.includes('连接超时')) {
errorMessage = '连接超时,请检查网络状况或重新尝试';
} else if (error.includes('连接失败')) {
errorMessage = 'WebRTC连接失败可能是网络环境限制请尝试刷新页面';
} else if (error.includes('信令错误')) {
errorMessage = '信令服务器错误,请稍后重试';
} else if (error.includes('创建连接失败')) {
errorMessage = '无法建立P2P连接请检查网络设置';
}
// 显示错误提示
showToast(errorMessage, "error");
setLastError(error);
// 如果是严重连接错误,清理传输状态
if (error.includes('连接失败') || error.includes('数据通道连接失败') || error.includes('WebSocket')) {
console.log('严重连接错误,清理传输状态');
setCurrentTransferFile(null);
// 重置所有正在传输的文件状态
updateFileListStatus((prev: any[]) => prev.map(item =>
item.status === 'downloading'
? { ...item, status: 'ready' as const, progress: 0 }
: item
));
}
}
}, [error, lastError, showToast, setCurrentTransferFile, updateFileListStatus]);
// 监听连接状态变化和清理传输状态
useEffect(() => {
console.log('=== 连接状态变化 ===');
console.log('WebSocket连接状态:', isWebSocketConnected);
console.log('WebRTC连接状态:', isConnected);
console.log('连接中状态:', isConnecting);
// 当连接断开或有错误时,清理所有传输状态
const shouldCleanup = (!isWebSocketConnected && !isConnected && !isConnecting && pickupCode) ||
((!isConnected && !isConnecting) || error);
if (shouldCleanup) {
const hasCurrentTransfer = !!currentTransferFile;
const hasFileList = fileListLength > 0;
// 只有在之前有连接活动时才显示断开提示和清理状态
if (hasFileList || hasCurrentTransfer) {
if (!isWebSocketConnected && pickupCode) {
showToast('与服务器的连接已断开,请重新连接', "error");
}
console.log('连接断开,清理传输状态');
if (currentTransferFile) {
setCurrentTransferFile(null);
}
// 重置所有正在下载的文件状态
updateFileListStatus((prev: any[]) => {
const hasDownloadingFiles = prev.some(item => item.status === 'downloading');
if (hasDownloadingFiles) {
console.log('重置正在传输的文件状态');
return prev.map(item =>
item.status === 'downloading'
? { ...item, status: 'ready' as const, progress: 0 }
: item
);
}
return prev;
});
}
}
// WebSocket连接成功时的提示
if (isWebSocketConnected && isConnecting && !isConnected) {
console.log('WebSocket已连接正在建立P2P连接...');
}
}, [isWebSocketConnected, isConnected, isConnecting, pickupCode, error, showToast, currentTransferFile, fileListLength, setCurrentTransferFile, updateFileListStatus]);
// 监听连接状态变化并提供日志
useEffect(() => {
console.log('=== WebRTC连接状态变化 ===');
console.log('连接状态:', {
isConnected,
isConnecting,
isWebSocketConnected,
pickupCode,
fileListLength
});
}, [isConnected, isConnecting, isWebSocketConnected, pickupCode, fileListLength]);
return {
lastError
};
};

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from 'react';
import { useToast } from '@/components/ui/toast-simple';
import { validateRoomCode, checkRoomStatus } from '@/lib/room-utils';
interface UseRoomConnectionProps {
connect: (code: string, role: 'sender' | 'receiver') => void;
@@ -11,61 +12,6 @@ export const useRoomConnection = ({ connect, isConnecting, isConnected }: UseRoo
const { showToast } = useToast();
const [isJoiningRoom, setIsJoiningRoom] = useState(false);
const validateRoomCode = (code: string): string | null => {
const trimmedCode = code.trim();
if (!trimmedCode || trimmedCode.length !== 6) {
return '请输入正确的6位取件码';
}
return null;
};
const checkRoomStatus = async (code: string) => {
const response = await fetch(`/api/room-info?code=${code}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: 无法检查房间状态`);
}
const result = await response.json();
if (!result.success) {
let errorMessage = result.message || '房间不存在或已过期';
if (result.message?.includes('expired')) {
errorMessage = '房间已过期,请联系发送方重新创建';
} else if (result.message?.includes('not found')) {
errorMessage = '房间不存在,请检查取件码是否正确';
}
throw new Error(errorMessage);
}
// 检查房间是否已满
if (result.is_room_full) {
throw new Error('当前房间人数已满,正在传输中无法加入');
}
if (!result.sender_online) {
throw new Error('发送方不在线,请确认取件码是否正确或联系发送方');
}
return result;
};
const handleNetworkError = (error: Error): string => {
if (error.message.includes('network') || error.message.includes('fetch')) {
return '网络连接失败,请检查网络状况';
} else if (error.message.includes('timeout')) {
return '请求超时,请重试';
} else if (error.message.includes('HTTP 404')) {
return '房间不存在,请检查取件码';
} else if (error.message.includes('HTTP 500')) {
return '服务器错误,请稍后重试';
} else if (error.message.includes('房间人数已满') || error.message.includes('正在传输中无法加入')) {
return '当前房间人数已满,正在传输中无法加入,请稍后再试';
} else {
return error.message;
}
};
// 加入房间 (接收模式)
const joinRoom = useCallback(async (code: string) => {
console.log('=== 加入房间 ===');
@@ -88,7 +34,12 @@ export const useRoomConnection = ({ connect, isConnecting, isConnected }: UseRoo
try {
console.log('检查房间状态...');
await checkRoomStatus(code.trim());
const result = await checkRoomStatus(code.trim());
if (!result.success) {
showToast(result.error || '检查房间状态失败', "error");
return;
}
console.log('房间状态检查通过,开始连接...');
connect(code.trim(), 'receiver');
@@ -96,7 +47,7 @@ export const useRoomConnection = ({ connect, isConnecting, isConnected }: UseRoo
} catch (error) {
console.error('检查房间状态失败:', error);
const errorMessage = error instanceof Error ? handleNetworkError(error) : '检查房间状态失败';
const errorMessage = error instanceof Error ? error.message : '检查房间状态失败';
showToast(errorMessage, "error");
} finally {
setIsJoiningRoom(false);

View File

@@ -1,5 +1,5 @@
import { useCallback } from 'react';
import { useWebRTCStateManager } from './useWebRTCStateManager';
import { useCallback, useMemo } from 'react';
import { useWebRTCStore, type WebRTCStateManager } from '../ui/webRTCStore';
import { useWebRTCDataChannelManager, WebRTCMessage } from './useWebRTCDataChannelManager';
import { useWebRTCTrackManager } from './useWebRTCTrackManager';
import { useWebRTCConnectionCore } from './useWebRTCConnectionCore';
@@ -50,8 +50,27 @@ export interface WebRTCConnection {
* 整合所有模块,提供统一的接口
*/
export function useSharedWebRTCManager(): WebRTCConnection {
// 创建各个管理器实例
const stateManager = useWebRTCStateManager();
// 直接从 zustand store 创建状态管理器
const store = useWebRTCStore();
const stateManager: WebRTCStateManager = useMemo(() => ({
getState: () => ({
isConnected: store.isConnected,
isConnecting: store.isConnecting,
isWebSocketConnected: store.isWebSocketConnected,
isPeerConnected: store.isPeerConnected,
error: store.error,
canRetry: store.canRetry,
currentRoom: store.currentRoom,
}),
updateState: store.updateState,
setCurrentRoom: store.setCurrentRoom,
resetToInitial: store.resetToInitial,
isConnectedToRoom: (roomCode: string, role: 'sender' | 'receiver') =>
store.currentRoom?.code === roomCode &&
store.currentRoom?.role === role &&
store.isConnected,
}), [store]);
const dataChannelManager = useWebRTCDataChannelManager(stateManager);
const trackManager = useWebRTCTrackManager(stateManager);
const connectionCore = useWebRTCConnectionCore(
@@ -60,8 +79,15 @@ export function useSharedWebRTCManager(): WebRTCConnection {
trackManager
);
// 获取当前状态
const state = stateManager.getState();
// 从 store 获取当前状态
const state = {
isConnected: store.isConnected,
isConnecting: store.isConnecting,
isWebSocketConnected: store.isWebSocketConnected,
isPeerConnected: store.isPeerConnected,
error: store.error,
canRetry: store.canRetry,
};
// 创建 createOfferNow 方法
const createOfferNow = useCallback(async () => {

View File

@@ -2,7 +2,7 @@
import { useRef, useCallback } from 'react';
import { getWsUrl } from '@/lib/config';
import { getIceServersConfig } from '../settings/useIceServersConfig';
import { WebRTCStateManager } from './useWebRTCStateManager';
import { WebRTCStateManager } from '../ui/webRTCStore';
import { WebRTCDataChannelManager, WebRTCMessage } from './useWebRTCDataChannelManager';
import { WebRTCTrackManager } from './useWebRTCTrackManager';

View File

@@ -1,5 +1,5 @@
import { useRef, useCallback } from 'react';
import { WebRTCStateManager } from './useWebRTCStateManager';
import { WebRTCStateManager } from '../ui/webRTCStore';
// 消息类型
export interface WebRTCMessage {

View File

@@ -1,78 +0,0 @@
import { useCallback } from 'react';
import { useWebRTCStore } from '../ui/webRTCStore';
// 基础连接状态
export interface WebRTCState {
isConnected: boolean;
isConnecting: boolean;
isWebSocketConnected: boolean;
isPeerConnected: boolean;
error: string | null;
canRetry: boolean;
}
/**
* WebRTC 状态管理器
* 负责连接状态的统一管理
*/
export interface WebRTCStateManager {
// 获取当前状态
getState: () => WebRTCState;
// 更新状态
updateState: (updates: Partial<WebRTCState>) => void;
// 设置当前房间
setCurrentRoom: (room: { code: string; role: 'sender' | 'receiver' } | null) => void;
// 重置到初始状态
resetToInitial: () => void;
// 检查是否已连接到指定房间
isConnectedToRoom: (roomCode: string, role: 'sender' | 'receiver') => boolean;
}
/**
* WebRTC 状态管理 Hook
* 封装对 webRTCStore 的操作,提供状态更新和查询的统一接口
*/
export function useWebRTCStateManager(): WebRTCStateManager {
const webrtcStore = useWebRTCStore();
const getState = useCallback((): WebRTCState => {
return {
isConnected: webrtcStore.isConnected,
isConnecting: webrtcStore.isConnecting,
isWebSocketConnected: webrtcStore.isWebSocketConnected,
isPeerConnected: webrtcStore.isPeerConnected,
error: webrtcStore.error,
canRetry: webrtcStore.canRetry,
};
}, [webrtcStore]);
const updateState = useCallback((updates: Partial<WebRTCState>) => {
webrtcStore.updateState(updates);
}, [webrtcStore]);
const setCurrentRoom = useCallback((room: { code: string; role: 'sender' | 'receiver' } | null) => {
webrtcStore.setCurrentRoom(room);
}, [webrtcStore]);
const resetToInitial = useCallback(() => {
webrtcStore.resetToInitial();
}, [webrtcStore]);
const isConnectedToRoom = useCallback((roomCode: string, role: 'sender' | 'receiver') => {
return webrtcStore.currentRoom?.code === roomCode &&
webrtcStore.currentRoom?.role === role &&
webrtcStore.isConnected;
}, [webrtcStore]);
return {
getState,
updateState,
setCurrentRoom,
resetToInitial,
isConnectedToRoom,
};
}

View File

@@ -1,5 +1,5 @@
import { useCallback, useRef } from 'react';
import { WebRTCStateManager } from './useWebRTCStateManager';
import { WebRTCStateManager } from '../ui/webRTCStore';
/**
* WebRTC 媒体轨道管理器

View File

@@ -1,13 +1,5 @@
import { useRef, useCallback, useEffect } from 'react';
interface FileInfo {
id: string;
name: string;
size: number;
type: string;
status: 'ready' | 'downloading' | 'completed';
progress: number;
}
import type { FileInfo } from '@/types';
interface UseFileListSyncProps {
sendFileList: (fileInfos: FileInfo[]) => void;

View File

@@ -1,13 +1,5 @@
import { useState, useCallback, useEffect } from 'react';
interface FileInfo {
id: string;
name: string;
size: number;
type: string;
status: 'ready' | 'downloading' | 'completed';
progress: number;
}
import type { FileInfo } from '@/types';
interface UseFileStateManagerProps {
mode: 'send' | 'receive';

View File

@@ -1,5 +1,6 @@
import { useState, useCallback, useRef, useEffect } from 'react';
import type { WebRTCConnection } from '../connection/useSharedWebRTCManager';
import type { FileInfo } from '@/types';
// 文件传输状态
interface FileTransferState {
@@ -21,16 +22,6 @@ interface FileReceiveProgress {
progress: number;
}
// 文件信息
interface FileInfo {
id: string;
name: string;
size: number;
type: string;
status: 'ready' | 'downloading' | 'completed';
progress: number;
}
// 文件元数据
interface FileMetadata {
id: string;

View File

@@ -1,20 +1,32 @@
import { create } from 'zustand';
interface WebRTCState {
export interface WebRTCState {
isConnected: boolean;
isConnecting: boolean;
isWebSocketConnected: boolean;
isPeerConnected: boolean;
error: string | null;
canRetry: boolean; // 新增:是否可以重试
canRetry: boolean;
currentRoom: { code: string; role: 'sender' | 'receiver' } | null;
}
/**
* WebRTC 状态管理器接口
* 供 ConnectionCore / DataChannelManager / TrackManager 作为参数使用
*/
export interface WebRTCStateManager {
getState: () => WebRTCState;
updateState: (updates: Partial<WebRTCState>) => void;
setCurrentRoom: (room: { code: string; role: 'sender' | 'receiver' } | null) => void;
resetToInitial: () => void;
isConnectedToRoom: (roomCode: string, role: 'sender' | 'receiver') => boolean;
}
interface WebRTCStore extends WebRTCState {
updateState: (updates: Partial<WebRTCState>) => void;
setCurrentRoom: (room: { code: string; role: 'sender' | 'receiver' } | null) => void;
reset: () => void;
resetToInitial: () => void; // 新增:完全重置到初始状态
resetToInitial: () => void;
}
const initialState: WebRTCState = {
@@ -23,7 +35,7 @@ const initialState: WebRTCState = {
isWebSocketConnected: false,
isPeerConnected: false,
error: null,
canRetry: false, // 初始状态下不需要重试
canRetry: false,
currentRoom: null,
};
@@ -42,5 +54,5 @@ export const useWebRTCStore = create<WebRTCStore>((set) => ({
reset: () => set(initialState),
resetToInitial: () => set(initialState), // 完全重置到初始状态
resetToInitial: () => set(initialState),
}));