diff --git a/chuan-next/src/app/HomePage.tsx b/chuan-next/src/app/HomePage.tsx
index d221efb..5dc0e74 100644
--- a/chuan-next/src/app/HomePage.tsx
+++ b/chuan-next/src/app/HomePage.tsx
@@ -3,17 +3,30 @@
import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { Upload, MessageSquare, Monitor, TestTube } from 'lucide-react';
+import { Upload, MessageSquare, Monitor, Users } from 'lucide-react';
import Hero from '@/components/Hero';
import { WebRTCFileTransfer } from '@/components/WebRTCFileTransfer';
import { WebRTCTextImageTransfer } from '@/components/WebRTCTextImageTransfer';
import DesktopShare from '@/components/DesktopShare';
+import WeChatGroup from '@/components/WeChatGroup';
+import { WebRTCUnsupportedModal } from '@/components/WebRTCUnsupportedModal';
+import { useWebRTCSupport } from '@/hooks/useWebRTCSupport';
export default function HomePage() {
const searchParams = useSearchParams();
const [activeTab, setActiveTab] = useState('webrtc');
const [hasInitialized, setHasInitialized] = useState(false);
+ // WebRTC 支持检测
+ const {
+ webrtcSupport,
+ isSupported,
+ isChecked,
+ showUnsupportedModal,
+ closeUnsupportedModal,
+ showUnsupportedModalManually,
+ } = useWebRTCSupport();
+
// 根据URL参数设置初始标签(仅首次加载时)
useEffect(() => {
if (!hasInitialized) {
@@ -50,56 +63,123 @@ export default function HomePage() {
- {/* Main Content */}
-
-
- {/* Tabs Navigation - 横向布局 */}
-
-
-
-
- 文件传输
- 文件
-
-
-
- 文本消息
- 消息
-
-
-
- 共享桌面
- 桌面
-
-
+ {/* WebRTC 支持检测加载状态 */}
+ {!isChecked && (
+
+ )}
- {/* Tab Content */}
-
-
-
-
+ {/* 主要内容 - 只有在检测完成后才显示 */}
+ {isChecked && (
+
+ {/* WebRTC 不支持时的警告横幅 */}
+ {!isSupported && (
+
+
+
+
+
+ 当前浏览器不支持 WebRTC,功能可能无法正常使用
+
+
+
+
+
+ )}
-
-
-
+
+ {/* Tabs Navigation - 横向布局 */}
+
+
+
+
+ 文件传输
+ 文件
+ {!isSupported && *}
+
+
+
+ 文本消息
+ 消息
+ {!isSupported && *}
+
+
+
+ 共享桌面
+ 桌面
+ {!isSupported && *}
+
+
+
+ 微信群
+ 微信
+
+
+
+ {/* WebRTC 不支持时的提示 */}
+ {!isSupported && (
+
+ * 需要 WebRTC 支持才能使用
+
+ )}
+
-
-
-
-
-
-
+ {/* Tab Content */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ {/* WebRTC 不支持提示模态框 */}
+ {webrtcSupport && (
+
+ )}
);
}
diff --git a/chuan-next/src/app/api/create-room/route.ts b/chuan-next/src/app/api/create-room/route.ts
index edbc000..db26742 100644
--- a/chuan-next/src/app/api/create-room/route.ts
+++ b/chuan-next/src/app/api/create-room/route.ts
@@ -6,14 +6,14 @@ export async function POST(request: NextRequest) {
try {
console.log('API Route: Creating room, proxying to:', `${GO_BACKEND_URL}/api/create-room`);
- const body = await request.json();
-
+ // 不再需要解析和转发请求体,因为后端会忽略它们
const response = await fetch(`${GO_BACKEND_URL}/api/create-room`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify(body),
+ // 发送空body即可
+ body: JSON.stringify({}),
});
const data = await response.json();
diff --git a/chuan-next/src/components/WeChatGroup.tsx b/chuan-next/src/components/WeChatGroup.tsx
new file mode 100644
index 0000000..17d873f
--- /dev/null
+++ b/chuan-next/src/components/WeChatGroup.tsx
@@ -0,0 +1,68 @@
+"use client";
+
+import React from 'react';
+import { Users } from 'lucide-react';
+
+export default function WeChatGroup() {
+ return (
+
+
+ {/* 标题 */}
+
+
+
+
+
加入微信交流群
+
+ 佬们有意见/建议/bug反馈或者奇思妙想想来交流,可以扫码加入
+
+
+
+ {/* 二维码区域 */}
+
+
+ {/* 微信群二维码 - 请将此区域替换为实际的二维码图片 */}
+
+

+
+
+
+
+ {/* 说明文字 */}
+
+
+
🎉 欢迎加入我们的交流群!
+
+
+
+ 💬
+ 分享使用心得和建议
+
+
+ 🐛
+ 反馈问题和bug
+
+
+ 💡
+ 提出新功能想法
+
+
+ 🤝
+ 与其他用户交流技术
+
+
+
+
+
+ {/* 额外信息 */}
+
+
+
+ );
+}
diff --git a/chuan-next/src/components/WebRTCConnectionStatus.tsx b/chuan-next/src/components/WebRTCConnectionStatus.tsx
new file mode 100644
index 0000000..aed1ffc
--- /dev/null
+++ b/chuan-next/src/components/WebRTCConnectionStatus.tsx
@@ -0,0 +1,185 @@
+import React from 'react';
+import { AlertCircle, Wifi, WifiOff, Loader2, RotateCcw } from 'lucide-react';
+import { WebRTCConnection } from '@/hooks/webrtc/useSharedWebRTCManager';
+
+interface Props {
+ webrtc: WebRTCConnection;
+ className?: string;
+}
+
+/**
+ * WebRTC连接状态显示组件
+ * 显示详细的连接状态、错误信息和重试按钮
+ */
+export function WebRTCConnectionStatus({ webrtc, className = '' }: Props) {
+ const {
+ isConnected,
+ isConnecting,
+ isWebSocketConnected,
+ isPeerConnected,
+ error,
+ canRetry,
+ retry
+ } = webrtc;
+
+ // 状态图标
+ const getStatusIcon = () => {
+ if (isConnecting) {
+ return ;
+ }
+
+ if (error) {
+ // 区分信息提示和错误
+ if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
+ return ;
+ }
+ return ;
+ }
+
+ if (isPeerConnected) {
+ return ;
+ }
+
+ if (isWebSocketConnected) {
+ return ;
+ }
+
+ return ;
+ };
+
+ // 状态文本
+ const getStatusText = () => {
+ if (error) {
+ return error;
+ }
+
+ if (isConnecting) {
+ return '正在连接...';
+ }
+
+ if (isPeerConnected) {
+ return 'P2P连接已建立';
+ }
+
+ if (isWebSocketConnected) {
+ return '信令服务器已连接';
+ }
+
+ return '未连接';
+ };
+
+ // 状态颜色
+ const getStatusColor = () => {
+ if (error) {
+ // 区分信息提示和错误
+ if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
+ return 'text-yellow-600';
+ }
+ return 'text-red-600';
+ }
+ if (isConnecting) return 'text-blue-600';
+ if (isPeerConnected) return 'text-green-600';
+ if (isWebSocketConnected) return 'text-yellow-600';
+ return 'text-gray-600';
+ };
+
+ const handleRetry = async () => {
+ try {
+ await retry();
+ } catch (error) {
+ console.error('重试连接失败:', error);
+ }
+ };
+
+ return (
+
+
+ {getStatusIcon()}
+
+ {getStatusText()}
+
+
+
+ {/* 连接详细状态指示器 */}
+
+ {/* WebSocket状态 */}
+
+
+ {/* P2P状态 */}
+
+
+ {/* 重试按钮 */}
+ {canRetry && (
+
+ )}
+
+
+ );
+}
+
+/**
+ * 简单的连接状态指示器(用于空间受限的地方)
+ */
+export function WebRTCStatusIndicator({ webrtc, className = '' }: Props) {
+ const { isPeerConnected, isConnecting, error } = webrtc;
+
+ if (error) {
+ // 区分信息提示和错误
+ if (error.includes('对方已离开房间') || error.includes('已离开房间')) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+ }
+
+ if (isConnecting) {
+ return (
+
+ );
+ }
+
+ if (isPeerConnected) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/chuan-next/src/components/WebRTCFileTransfer.tsx b/chuan-next/src/components/WebRTCFileTransfer.tsx
index 0e7067d..1046e4f 100644
--- a/chuan-next/src/components/WebRTCFileTransfer.tsx
+++ b/chuan-next/src/components/WebRTCFileTransfer.tsx
@@ -309,21 +309,14 @@ export const WebRTCFileTransfer: React.FC = () => {
console.log('=== 创建房间 ===');
console.log('选中文件数:', selectedFiles.length);
- // 创建后端房间
+ // 创建后端房间 - 简化版本,不发送无用的文件信息
const response = await fetch('/api/create-room', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify({
- type: 'file',
- files: selectedFiles.map(file => ({
- name: file.name,
- size: file.size,
- type: file.type,
- lastModified: file.lastModified
- }))
- }),
+ // 不再发送文件列表,因为后端不使用这些信息
+ body: JSON.stringify({}),
});
const data = await response.json();
diff --git a/chuan-next/src/components/WebRTCUnsupportedModal.tsx b/chuan-next/src/components/WebRTCUnsupportedModal.tsx
new file mode 100644
index 0000000..0ebb342
--- /dev/null
+++ b/chuan-next/src/components/WebRTCUnsupportedModal.tsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import { AlertTriangle, Download, X, Chrome, Monitor } from 'lucide-react';
+import { WebRTCSupport, getBrowserInfo, getRecommendedBrowsers } from '@/lib/webrtc-support';
+
+interface Props {
+ isOpen: boolean;
+ onClose: () => void;
+ webrtcSupport: WebRTCSupport;
+}
+
+/**
+ * WebRTC 不支持提示模态框
+ */
+export function WebRTCUnsupportedModal({ isOpen, onClose, webrtcSupport }: Props) {
+ const browserInfo = getBrowserInfo();
+ const recommendedBrowsers = getRecommendedBrowsers();
+
+ if (!isOpen) return null;
+
+ const handleBrowserDownload = (url: string) => {
+ window.open(url, '_blank', 'noopener,noreferrer');
+ };
+
+ return (
+
+
+ {/* 头部 */}
+
+
+ {/* 内容 */}
+
+ {/* 当前浏览器信息 */}
+
+
当前浏览器状态
+
+
+ 浏览器: {browserInfo.name} {browserInfo.version}
+
+
+ WebRTC 支持:
+
+ 不支持
+
+
+
+
+
+ {/* 缺失的功能 */}
+
+
缺失的功能:
+
+ {webrtcSupport.missing.map((feature, index) => (
+
+ ))}
+
+
+
+ {/* 功能说明 */}
+
+
为什么需要 WebRTC?
+
+
+
+
+ 屏幕共享: 实时共享您的桌面屏幕
+
+
+
+
+
+ 文件传输: 点对点直接传输文件,快速且安全
+
+
+
+
+
+ 文本传输: 实时文本和图像传输
+
+
+
+
+
+ {/* 浏览器推荐 */}
+
+
推荐使用以下浏览器:
+
+ {recommendedBrowsers.map((browser, index) => (
+
handleBrowserDownload(browser.downloadUrl)}
+ >
+
+
+
{browser.name}
+
版本 {browser.minVersion}
+
+
+
+
+ ))}
+
+
+
+ {/* 浏览器特定建议 */}
+ {browserInfo.recommendations && (
+
+
建议
+
+ {browserInfo.recommendations.map((recommendation, index) => (
+ -
+
+ {recommendation}
+
+ ))}
+
+
+ )}
+
+ {/* 技术详情(可折叠) */}
+
+
+ 技术详情
+
+
+
+
+ RTCPeerConnection:
+
+ {webrtcSupport.details.rtcPeerConnection ? '支持' : '不支持'}
+
+
+
+ DataChannel:
+
+ {webrtcSupport.details.dataChannel ? '支持' : '不支持'}
+
+
+
+
+
+
+
+ {/* 底部按钮 */}
+
+
+
+
+
+
+ );
+}
diff --git a/chuan-next/src/components/webrtc/WebRTCDesktopReceiver.tsx b/chuan-next/src/components/webrtc/WebRTCDesktopReceiver.tsx
index 1f812bf..152772e 100644
--- a/chuan-next/src/components/webrtc/WebRTCDesktopReceiver.tsx
+++ b/chuan-next/src/components/webrtc/WebRTCDesktopReceiver.tsx
@@ -18,6 +18,7 @@ interface WebRTCDesktopReceiverProps {
export default function WebRTCDesktopReceiver({ className, initialCode, onConnectionChange }: WebRTCDesktopReceiverProps) {
const [inputCode, setInputCode] = useState(initialCode || '');
const [isLoading, setIsLoading] = useState(false);
+ const [isJoiningRoom, setIsJoiningRoom] = useState(false); // 添加加入房间状态
const [showDebug, setShowDebug] = useState(false);
const hasTriedAutoJoin = React.useRef(false); // 添加 ref 来跟踪是否已尝试自动加入
const { showToast } = useToast();
@@ -34,27 +35,82 @@ export default function WebRTCDesktopReceiver({ className, initialCode, onConnec
// 加入观看
const handleJoinViewing = useCallback(async () => {
- if (!inputCode.trim()) {
- showToast('请输入房间代码', 'error');
+ const trimmedCode = inputCode.trim();
+
+ // 检查房间代码格式
+ if (!trimmedCode || trimmedCode.length !== 6) {
+ showToast('请输入正确的6位房间代码', "error");
return;
}
+ // 防止重复调用 - 检查是否已经在连接或已连接
+ if (desktopShare.isConnecting || desktopShare.isViewing || isJoiningRoom) {
+ console.log('已在连接中或已连接,跳过重复的房间状态检查');
+ return;
+ }
+
+ setIsJoiningRoom(true);
+
try {
- setIsLoading(true);
- console.log('[DesktopShareReceiver] 用户加入观看房间:', inputCode);
+ console.log('[DesktopShareReceiver] 开始验证房间状态...');
- await desktopShare.joinSharing(inputCode.trim().toUpperCase());
+ // 先检查房间状态
+ const response = await fetch(`/api/room-info?code=${trimmedCode}`);
+
+ 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 = '房间不存在,请检查房间代码是否正确';
+ }
+ showToast(errorMessage, "error");
+ return;
+ }
+
+ // 检查发送方是否在线
+ if (!result.sender_online) {
+ showToast('发送方不在线,请确认房间代码是否正确或联系发送方', "error");
+ return;
+ }
+
+ console.log('[DesktopShareReceiver] 房间状态检查通过,开始连接...');
+ setIsLoading(true);
+
+ await desktopShare.joinSharing(trimmedCode.toUpperCase());
console.log('[DesktopShareReceiver] 加入观看成功');
showToast('已加入桌面共享', 'success');
} catch (error) {
console.error('[DesktopShareReceiver] 加入观看失败:', error);
- const errorMessage = error instanceof Error ? error.message : '加入观看失败';
+
+ let errorMessage = '加入观看失败';
+ if (error instanceof Error) {
+ if (error.message.includes('network') || error.message.includes('fetch')) {
+ errorMessage = '网络连接失败,请检查网络状况';
+ } else if (error.message.includes('timeout')) {
+ errorMessage = '请求超时,请重试';
+ } else if (error.message.includes('HTTP 404')) {
+ errorMessage = '房间不存在,请检查房间代码';
+ } else if (error.message.includes('HTTP 500')) {
+ errorMessage = '服务器错误,请稍后重试';
+ } else {
+ errorMessage = error.message;
+ }
+ }
+
showToast(errorMessage, 'error');
} finally {
setIsLoading(false);
+ setIsJoiningRoom(false); // 重置加入房间状态
}
- }, [desktopShare, inputCode, showToast]);
+ }, [desktopShare, inputCode, isJoiningRoom, showToast]);
// 停止观看
const handleStopViewing = useCallback(async () => {
@@ -77,38 +133,94 @@ export default function WebRTCDesktopReceiver({ className, initialCode, onConnec
initialCode,
isViewing: desktopShare.isViewing,
isConnecting: desktopShare.isConnecting,
+ isJoiningRoom,
hasTriedAutoJoin: hasTriedAutoJoin.current
});
const autoJoin = async () => {
- if (initialCode && !desktopShare.isViewing && !desktopShare.isConnecting && !hasTriedAutoJoin.current) {
+ if (initialCode && !desktopShare.isViewing && !desktopShare.isConnecting && !isJoiningRoom && !hasTriedAutoJoin.current) {
hasTriedAutoJoin.current = true;
- console.log('[WebRTCDesktopReceiver] 检测到初始代码,自动加入观看:', initialCode);
+ const trimmedCode = initialCode.trim();
+
+ // 检查房间代码格式
+ if (!trimmedCode || trimmedCode.length !== 6) {
+ showToast('房间代码格式不正确', "error");
+ return;
+ }
+
+ setIsJoiningRoom(true);
+ console.log('[WebRTCDesktopReceiver] 检测到初始代码,开始验证并自动加入:', trimmedCode);
try {
+ // 先检查房间状态
+ console.log('[WebRTCDesktopReceiver] 验证房间状态...');
+ const response = await fetch(`/api/room-info?code=${trimmedCode}`);
+
+ 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 = '房间不存在,请检查房间代码是否正确';
+ }
+ showToast(errorMessage, "error");
+ return;
+ }
+
+ // 检查发送方是否在线
+ if (!result.sender_online) {
+ showToast('发送方不在线,请确认房间代码是否正确或联系发送方', "error");
+ return;
+ }
+
+ console.log('[WebRTCDesktopReceiver] 房间验证通过,开始自动连接...');
setIsLoading(true);
- await desktopShare.joinSharing(initialCode.trim().toUpperCase());
+
+ await desktopShare.joinSharing(trimmedCode.toUpperCase());
console.log('[WebRTCDesktopReceiver] 自动加入观看成功');
showToast('已加入桌面共享', 'success');
} catch (error) {
console.error('[WebRTCDesktopReceiver] 自动加入观看失败:', error);
- const errorMessage = error instanceof Error ? error.message : '加入观看失败';
+
+ let errorMessage = '自动加入观看失败';
+ if (error instanceof Error) {
+ if (error.message.includes('network') || error.message.includes('fetch')) {
+ errorMessage = '网络连接失败,请检查网络状况';
+ } else if (error.message.includes('timeout')) {
+ errorMessage = '请求超时,请重试';
+ } else if (error.message.includes('HTTP 404')) {
+ errorMessage = '房间不存在,请检查房间代码';
+ } else if (error.message.includes('HTTP 500')) {
+ errorMessage = '服务器错误,请稍后重试';
+ } else {
+ errorMessage = error.message;
+ }
+ }
+
showToast(errorMessage, 'error');
} finally {
setIsLoading(false);
+ setIsJoiningRoom(false);
}
} else {
console.log('[WebRTCDesktopReceiver] 不满足自动加入条件:', {
hasInitialCode: !!initialCode,
notViewing: !desktopShare.isViewing,
notConnecting: !desktopShare.isConnecting,
+ notJoiningRoom: !isJoiningRoom,
notTriedBefore: !hasTriedAutoJoin.current
});
}
};
autoJoin();
- }, [initialCode, desktopShare.isViewing, desktopShare.isConnecting]); // 移除了 desktopShare.joinSharing 和 showToast
+ }, [initialCode, desktopShare.isViewing, desktopShare.isConnecting, isJoiningRoom]); // 添加isJoiningRoom依赖
return (
@@ -138,11 +250,11 @@ export default function WebRTCDesktopReceiver({ className, initialCode, onConnec
setInputCode(e.target.value.replace(/[^A-Z0-9]/g, '').toUpperCase())}
+ onChange={(e) => setInputCode(e.target.value.replace(/[^123456789ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijklmnpqrstuvwxyz]/g, ''))}
placeholder="请输入房间代码"
className="text-center text-2xl sm:text-3xl tracking-[0.3em] sm:tracking-[0.5em] font-mono h-12 sm:h-16 border-2 border-slate-200 rounded-xl focus:border-purple-500 focus:ring-purple-500 bg-white/80 backdrop-blur-sm pb-2 sm:pb-4"
maxLength={6}
- disabled={isLoading}
+ disabled={isLoading || isJoiningRoom}
/>
@@ -153,10 +265,15 @@ export default function WebRTCDesktopReceiver({ className, initialCode, onConnec