refactor:Refactored the useRoomManager,useWebRTCConnection hook
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
"use client";
|
||||
import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import React, { useRef, useCallback, useEffect, useMemo } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useRichTextToPlainText from "../hooks/useRichTextToPlainText";
|
||||
import QRCodeComponent from "./ClipboardApp/ShareCard";
|
||||
@@ -19,31 +13,47 @@ import { SendTabPanel } from "./ClipboardApp/SendTabPanel";
|
||||
import { RetrieveTabPanel } from "./ClipboardApp/RetrieveTabPanel";
|
||||
import FullScreenDropZone from "./ClipboardApp/FullScreenDropZone";
|
||||
import { traverseFileTree } from "@/lib/fileUtils";
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
|
||||
const ClipboardApp = () => {
|
||||
const { shareMessage, retrieveMessage, putMessageInMs } =
|
||||
useClipboardAppMessages();
|
||||
|
||||
const [retrieveRoomIdInput, setRetrieveRoomIdInput] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<"send" | "retrieve">("send");
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const dragCounter = useRef(0);
|
||||
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null); // Ref for the receiver's "Join Room" button
|
||||
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const { messages, isLoadingMessages } = usePageSetup({
|
||||
setRetrieveRoomId: setRetrieveRoomIdInput,
|
||||
setActiveTab,
|
||||
setRetrieveRoomId: useFileTransferStore.getState().setRetrieveRoomIdInput,
|
||||
setActiveTab: useFileTransferStore.getState().setActiveTab,
|
||||
retrieveJoinRoomBtnRef,
|
||||
});
|
||||
|
||||
// 从 store 中获取状态
|
||||
const {
|
||||
activeTab,
|
||||
retrieveRoomIdInput,
|
||||
isDragging,
|
||||
shareContent,
|
||||
sendFiles,
|
||||
retrievedContent,
|
||||
retrievedFileMetas,
|
||||
sendProgress,
|
||||
receiveProgress,
|
||||
isAnyFileTransferring,
|
||||
shareRoomId,
|
||||
initShareRoomId,
|
||||
shareLink,
|
||||
shareRoomStatusText,
|
||||
retrieveRoomStatusText,
|
||||
setIsDragging,
|
||||
setRetrieveRoomIdInput,
|
||||
setActiveTab,
|
||||
} = useFileTransferStore();
|
||||
|
||||
const richTextToPlainText = useRichTextToPlainText();
|
||||
|
||||
// Initialize File Transfer Handler Hook
|
||||
const {
|
||||
shareContent,
|
||||
sendFiles,
|
||||
retrievedContent,
|
||||
retrievedFileMetas,
|
||||
updateShareContent,
|
||||
addFilesToSend,
|
||||
removeFileToSend,
|
||||
@@ -51,23 +61,21 @@ const ClipboardApp = () => {
|
||||
onFileMetadataReceived,
|
||||
onFileFullyReceived,
|
||||
handleDownloadFile,
|
||||
resetReceiverState,
|
||||
} = useFileTransferHandler({ messages, putMessageInMs });
|
||||
|
||||
// Calculate the derived states for unload protection
|
||||
const isContentPresent = useMemo(() => {
|
||||
return (
|
||||
shareContent !== "" || retrievedContent !== "" || sendFiles.length > 0
|
||||
);
|
||||
}, [shareContent, retrievedContent, sendFiles]);
|
||||
|
||||
// Initialize WebRTC Connection Hook
|
||||
const {
|
||||
sender,
|
||||
receiver,
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
sendProgress,
|
||||
receiveProgress,
|
||||
isAnyFileTransferring,
|
||||
broadcastDataToAllPeers,
|
||||
requestFile,
|
||||
requestFolder,
|
||||
@@ -88,20 +96,10 @@ const ClipboardApp = () => {
|
||||
onFileReceived: onFileFullyReceived,
|
||||
});
|
||||
|
||||
// Function to reset sender peer count
|
||||
const resetSenderPeerCount = useCallback(() => {
|
||||
// This will be used by useRoomManager to reset sharePeerCount
|
||||
// We can access the sharePeerCount setter through a custom approach
|
||||
// For now, we'll implement this by triggering a re-render
|
||||
// The actual reset happens in useWebRTCConnection via sender.leaveRoomAndCleanup()
|
||||
// But we need to ensure the UI updates, so we'll add this logic there
|
||||
}, []);
|
||||
|
||||
const resetAppState = useCallback(async () => {
|
||||
// Graceful state reset instead of page reload
|
||||
try {
|
||||
// Reset file transfer state
|
||||
resetReceiverState();
|
||||
useFileTransferStore.getState().resetReceiverState();
|
||||
|
||||
// Reset WebRTC connection state
|
||||
await resetReceiverConnection();
|
||||
@@ -112,18 +110,12 @@ const ClipboardApp = () => {
|
||||
console.log("Application state reset successfully");
|
||||
} catch (error) {
|
||||
console.error("Error during state reset:", error);
|
||||
// Fallback to page reload if graceful reset fails
|
||||
window.location.reload();
|
||||
}
|
||||
}, [resetReceiverState, resetReceiverConnection]);
|
||||
}, [resetReceiverConnection, setRetrieveRoomIdInput]);
|
||||
|
||||
// Initialize Room Manager Hook
|
||||
const {
|
||||
shareRoomId,
|
||||
initShareRoomId,
|
||||
shareLink,
|
||||
shareRoomStatusText,
|
||||
retrieveRoomStatusText,
|
||||
processRoomIdInput,
|
||||
joinRoom,
|
||||
generateShareLinkAndBroadcast,
|
||||
@@ -134,13 +126,8 @@ const ClipboardApp = () => {
|
||||
putMessageInMs,
|
||||
sender,
|
||||
receiver,
|
||||
activeTab,
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
senderDisconnected,
|
||||
broadcastDataToPeers: () =>
|
||||
broadcastDataToAllPeers(shareContent, sendFiles),
|
||||
resetApp: resetAppState,
|
||||
resetSenderConnection,
|
||||
});
|
||||
|
||||
@@ -214,12 +201,9 @@ const ClipboardApp = () => {
|
||||
window.removeEventListener("dragover", handleDragOver);
|
||||
window.removeEventListener("drop", handleDrop);
|
||||
};
|
||||
}, [activeTab, handleFileDrop]);
|
||||
}, [activeTab, handleFileDrop, setIsDragging]);
|
||||
|
||||
if (isLoadingMessages || !messages) {
|
||||
// Use a skeleton screen placeholder to replace the simple text loading prompt.
|
||||
// The height of this placeholder is similar to the height of the component that is finally loaded,
|
||||
// This prevents layout displacement and ensures that the lazy loading component below will not be triggered prematurely.
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 w-full md:max-w-4xl">
|
||||
<div className="min-h-[1000px] w-full bg-gray-200/50 dark:bg-gray-800/50 rounded-lg animate-pulse">
|
||||
@@ -287,7 +271,7 @@ const ClipboardApp = () => {
|
||||
) : (
|
||||
<RetrieveTabPanel
|
||||
messages={messages}
|
||||
putMessageInMs={putMessageInMs} // Needed for onLocationPick
|
||||
putMessageInMs={putMessageInMs}
|
||||
retrieveRoomStatusText={retrieveRoomStatusText}
|
||||
retrieveRoomIdInput={retrieveRoomIdInput}
|
||||
setRetrieveRoomIdInput={setRetrieveRoomIdInput}
|
||||
@@ -300,7 +284,6 @@ const ClipboardApp = () => {
|
||||
receiveProgress={receiveProgress}
|
||||
isAnyFileTransferring={isAnyFileTransferring}
|
||||
handleDownloadFile={handleDownloadFile}
|
||||
// Pass WebRTC interaction methods
|
||||
requestFile={requestFile}
|
||||
requestFolder={requestFolder}
|
||||
setReceiverDirectoryHandle={setReceiverDirectoryHandle}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
||||
import { useEffect, useCallback, useMemo } from "react";
|
||||
import { fetchRoom, createRoom, checkRoom, leaveRoom } from "@/app/config/api";
|
||||
import { debounce } from "lodash";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import type WebRTC_Initiator from "@/lib/webrtc_Initiator";
|
||||
import type WebRTC_Recipient from "@/lib/webrtc_Recipient";
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
|
||||
function format_peopleMsg(template: string, peerCount: number) {
|
||||
return template.replace("{peerCount}", peerCount.toString());
|
||||
@@ -18,13 +19,8 @@ interface UseRoomManagerProps {
|
||||
) => void;
|
||||
sender: WebRTC_Initiator | null;
|
||||
receiver: WebRTC_Recipient | null;
|
||||
activeTab: "send" | "retrieve";
|
||||
sharePeerCount: number;
|
||||
retrievePeerCount: number;
|
||||
senderDisconnected: boolean;
|
||||
broadcastDataToPeers: () => Promise<boolean>;
|
||||
resetApp: () => void; // Add a reset function prop
|
||||
resetSenderConnection: () => Promise<void>; // Add function to reset sender connection
|
||||
resetSenderConnection: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useRoomManager({
|
||||
@@ -32,21 +28,32 @@ export function useRoomManager({
|
||||
putMessageInMs,
|
||||
sender,
|
||||
receiver,
|
||||
activeTab,
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
senderDisconnected,
|
||||
broadcastDataToPeers,
|
||||
resetApp,
|
||||
resetSenderConnection,
|
||||
}: UseRoomManagerProps) {
|
||||
const [shareRoomId, setShareRoomId] = useState(""); // Represents the validated or initially fetched room ID
|
||||
const [initShareRoomId, setInitShareRoomId] = useState(""); // Stores the initially fetched room ID for comparison
|
||||
const [shareLink, setShareLink] = useState("");
|
||||
const [shareRoomStatusText, setShareRoomStatusText] = useState("");
|
||||
const [retrieveRoomStatusText, setRetrieveRoomStatusText] = useState("");
|
||||
// 从 store 中获取状态
|
||||
const {
|
||||
shareRoomId,
|
||||
initShareRoomId,
|
||||
shareLink,
|
||||
shareRoomStatusText,
|
||||
retrieveRoomStatusText,
|
||||
activeTab,
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
senderDisconnected,
|
||||
setShareRoomId,
|
||||
setInitShareRoomId,
|
||||
setShareLink,
|
||||
setShareRoomStatusText,
|
||||
setRetrieveRoomStatusText,
|
||||
setSharePeerCount,
|
||||
setRetrievePeerCount,
|
||||
resetReceiverState,
|
||||
resetSenderApp,
|
||||
} = useFileTransferStore();
|
||||
|
||||
// Receiver leave room function (renamed and simplified)
|
||||
// Receiver leave room function
|
||||
const handleLeaveReceiverRoom = useCallback(async () => {
|
||||
if (!receiver || !receiver.roomId || !receiver.peerId || !messages) return;
|
||||
try {
|
||||
@@ -57,18 +64,26 @@ export function useRoomManager({
|
||||
putMessageInMs("Failed to leave the room.", false);
|
||||
} finally {
|
||||
// Reset application state
|
||||
resetApp();
|
||||
resetReceiverState();
|
||||
// Reset peer count
|
||||
setRetrievePeerCount(0);
|
||||
}
|
||||
}, [receiver, putMessageInMs, resetApp, messages]);
|
||||
}, [
|
||||
receiver,
|
||||
putMessageInMs,
|
||||
messages,
|
||||
resetReceiverState,
|
||||
setRetrievePeerCount,
|
||||
]);
|
||||
|
||||
// Reset sender app state (preserve send content, get new room ID)
|
||||
const resetSenderApp = useCallback(async () => {
|
||||
// Reset sender app state
|
||||
const resetSenderAppState = useCallback(async () => {
|
||||
try {
|
||||
// 1. Clean up WebRTC connections and reset peer count
|
||||
await resetSenderConnection();
|
||||
|
||||
// 2. Clear share link
|
||||
setShareLink("");
|
||||
// 2. Clear share link and progress
|
||||
resetSenderApp();
|
||||
|
||||
// 3. Get new room ID from backend
|
||||
const newRoomId = await fetchRoom();
|
||||
@@ -83,9 +98,15 @@ export function useRoomManager({
|
||||
console.error("Error during sender state reset:", error);
|
||||
putMessageInMs("Error resetting sender state.", true);
|
||||
}
|
||||
}, [resetSenderConnection, putMessageInMs]);
|
||||
}, [
|
||||
resetSenderConnection,
|
||||
putMessageInMs,
|
||||
resetSenderApp,
|
||||
setShareRoomId,
|
||||
setInitShareRoomId,
|
||||
]);
|
||||
|
||||
// Sender leave room function (new)
|
||||
// Sender leave room function
|
||||
const handleLeaveSenderRoom = useCallback(async () => {
|
||||
if (!sender || !sender.roomId || !sender.peerId || !messages) return;
|
||||
try {
|
||||
@@ -96,9 +117,9 @@ export function useRoomManager({
|
||||
putMessageInMs("Failed to leave the room.", true);
|
||||
} finally {
|
||||
// Reset sender state and get new room ID
|
||||
await resetSenderApp();
|
||||
await resetSenderAppState();
|
||||
}
|
||||
}, [sender, putMessageInMs, resetSenderApp, messages]);
|
||||
}, [sender, putMessageInMs, resetSenderAppState, messages]);
|
||||
|
||||
// Initialize shareRoomId on mount
|
||||
useEffect(() => {
|
||||
@@ -124,7 +145,14 @@ export function useRoomManager({
|
||||
};
|
||||
initNewRoom();
|
||||
}
|
||||
}, [messages, initShareRoomId]);
|
||||
}, [
|
||||
messages,
|
||||
initShareRoomId,
|
||||
activeTab,
|
||||
setShareRoomId,
|
||||
setInitShareRoomId,
|
||||
putMessageInMs,
|
||||
]);
|
||||
|
||||
// Debounced function to actually check the room ID and update the state
|
||||
const performDebouncedRoomCheck = useMemo(
|
||||
@@ -133,24 +161,18 @@ export function useRoomManager({
|
||||
if (!messages || !putMessageInMs) return;
|
||||
|
||||
if (!roomIdToCheck.trim()) {
|
||||
// If the input is cleared, don't perform a check, but you can clear the message or handle it otherwise
|
||||
// putMessageInMs(messages.text.ClipboardApp.roomCheck.empty_msg, true);
|
||||
// Consider resetting shareRoomId to initShareRoomId if you want to restore the default when input is cleared
|
||||
// setShareRoomId(initShareRoomId);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const available = await checkRoom(roomIdToCheck);
|
||||
if (available) {
|
||||
setShareRoomId(roomIdToCheck); // Update the validated shareRoomId
|
||||
setShareRoomId(roomIdToCheck);
|
||||
putMessageInMs(
|
||||
messages.text.ClipboardApp.roomCheck.available_msg,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
// Room is not available, do not update shareRoomId; it will retain the last valid or initial value
|
||||
// The value in the user's input box is managed by SendTabPanel's local state and will not roll back because of this
|
||||
putMessageInMs(
|
||||
messages.text.ClipboardApp.roomCheck.notAvailable_msg,
|
||||
true
|
||||
@@ -158,13 +180,9 @@ export function useRoomManager({
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking room availability:", error);
|
||||
putMessageInMs(
|
||||
//messages.text.ClipboardApp.roomCheck.error_msg ||
|
||||
"Error checking room.",
|
||||
true
|
||||
);
|
||||
putMessageInMs("Error checking room.", true);
|
||||
}
|
||||
}, 750), // Increased debounce delay to 750ms
|
||||
}, 750),
|
||||
[messages, putMessageInMs, setShareRoomId]
|
||||
);
|
||||
|
||||
@@ -172,12 +190,9 @@ export function useRoomManager({
|
||||
const processRoomIdInput = useCallback(
|
||||
(inputRoomId: string) => {
|
||||
if (!inputRoomId.trim() && messages && putMessageInMs) {
|
||||
// If the user clears the input box
|
||||
putMessageInMs(messages.text.ClipboardApp.roomCheck.empty_msg, true);
|
||||
performDebouncedRoomCheck.cancel();
|
||||
// And if you want the validated shareRoomId to revert to the initial state upon clearing:
|
||||
// setShareRoomId(initShareRoomId); // This would update the QR code, etc., to the initial ID
|
||||
return; // Don't proceed with debounced check for an empty string
|
||||
return;
|
||||
}
|
||||
performDebouncedRoomCheck(inputRoomId);
|
||||
},
|
||||
@@ -197,7 +212,7 @@ export function useRoomManager({
|
||||
}
|
||||
|
||||
const peer = isSenderSide ? sender : receiver;
|
||||
if (!peer) return; // Should be caught by above, but for type safety
|
||||
if (!peer) return;
|
||||
|
||||
if (!currentRoomIdToJoin.trim()) {
|
||||
putMessageInMs(
|
||||
@@ -219,7 +234,6 @@ export function useRoomManager({
|
||||
);
|
||||
return;
|
||||
}
|
||||
// If creation is successful, WebRTC's joinRoom will use this ID, and we should update shareRoomId to this newly created ID
|
||||
setShareRoomId(currentRoomIdToJoin);
|
||||
} catch (error) {
|
||||
putMessageInMs(
|
||||
@@ -233,9 +247,6 @@ export function useRoomManager({
|
||||
}
|
||||
|
||||
try {
|
||||
// WebRTC joinRoom uses the ID provided by the user (for the receiver) or the validated/newly created ID (for the sender)
|
||||
// For the sender, if createRoom above was successful and set shareRoomId, peer.joinRoom should use it
|
||||
// But if currentRoomIdToJoin is initShareRoomId, use it directly
|
||||
const actualRoomIdForSenderJoin =
|
||||
isSenderSide && currentRoomIdToJoin !== initShareRoomId
|
||||
? currentRoomIdToJoin
|
||||
@@ -254,7 +265,6 @@ export function useRoomManager({
|
||||
const link = `${window.location.origin}${window.location.pathname}?roomId=${actualRoomIdForSenderJoin}`;
|
||||
setShareLink(link);
|
||||
if (actualRoomIdForSenderJoin !== shareRoomId) {
|
||||
// If joining was successful by entering a new ID, update shareRoomId
|
||||
setShareRoomId(actualRoomIdForSenderJoin);
|
||||
}
|
||||
}
|
||||
@@ -283,7 +293,7 @@ export function useRoomManager({
|
||||
);
|
||||
|
||||
const generateShareLinkAndBroadcast = useCallback(async () => {
|
||||
if (!sender || !messages || !putMessageInMs || !shareRoomId) return; // Ensure shareRoomId is valid
|
||||
if (!sender || !messages || !putMessageInMs || !shareRoomId) return;
|
||||
|
||||
if (sender.peerConnections.size === 0) {
|
||||
putMessageInMs(messages.text.ClipboardApp.waitting_tips, true);
|
||||
@@ -292,7 +302,14 @@ export function useRoomManager({
|
||||
}
|
||||
const link = `${window.location.origin}${window.location.pathname}?roomId=${shareRoomId}`;
|
||||
setShareLink(link);
|
||||
}, [sender, messages, putMessageInMs, shareRoomId]);
|
||||
}, [
|
||||
sender,
|
||||
messages,
|
||||
putMessageInMs,
|
||||
shareRoomId,
|
||||
setShareLink,
|
||||
broadcastDataToPeers,
|
||||
]);
|
||||
|
||||
// useEffect for room status text
|
||||
useEffect(() => {
|
||||
@@ -339,20 +356,22 @@ export function useRoomManager({
|
||||
receiver,
|
||||
messages,
|
||||
senderDisconnected,
|
||||
sender?.isInRoom, // Add isInRoom state to dependencies
|
||||
receiver?.isInRoom, // Add isInRoom state to dependencies
|
||||
sender?.isInRoom,
|
||||
receiver?.isInRoom,
|
||||
setShareRoomStatusText,
|
||||
setRetrieveRoomStatusText,
|
||||
]);
|
||||
|
||||
return {
|
||||
shareRoomId, // This is the validated or initial room ID
|
||||
initShareRoomId, // Exposed for UI comparison or reset logic
|
||||
shareRoomId,
|
||||
initShareRoomId,
|
||||
shareLink,
|
||||
shareRoomStatusText,
|
||||
retrieveRoomStatusText,
|
||||
processRoomIdInput, // New input processing function
|
||||
processRoomIdInput,
|
||||
joinRoom,
|
||||
generateShareLinkAndBroadcast,
|
||||
handleLeaveReceiverRoom, // Renamed function
|
||||
handleLeaveSenderRoom, // New function
|
||||
handleLeaveReceiverRoom,
|
||||
handleLeaveSenderRoom,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
getIceServers,
|
||||
getSocketOptions,
|
||||
} from "@/app/config/environment";
|
||||
import type { CustomFile, fileMetadata, FileMeta } from "@/types/webrtc"; // Assuming FileMeta might be used by caller
|
||||
import type { CustomFile, fileMetadata, FileMeta } from "@/types/webrtc";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import { useFileTransferStore } from "@/stores/fileTransferStore";
|
||||
|
||||
const developmentEnv = process.env.NEXT_PUBLIC_development === "true";
|
||||
|
||||
@@ -21,7 +22,7 @@ export type ProgressState = { [fileId: string]: FileProgressPeers };
|
||||
interface UseWebRTCConnectionProps {
|
||||
shareContent: string;
|
||||
sendFiles: CustomFile[];
|
||||
isContentPresent: boolean; // To know if there is any content (text or files)
|
||||
isContentPresent: boolean;
|
||||
// Callbacks for data received from peers
|
||||
onStringReceived: (data: string, peerId: string) => void;
|
||||
onFileMetaReceived: (meta: fileMetadata, peerId: string) => void;
|
||||
@@ -53,12 +54,20 @@ export function useWebRTCConnection({
|
||||
const [receiverFileTransfer, setReceiverFileTransfer] =
|
||||
useState<FileReceiver | null>(null);
|
||||
|
||||
const [sharePeerCount, setSharePeerCount] = useState(0);
|
||||
const [retrievePeerCount, setRetrievePeerCount] = useState(0);
|
||||
|
||||
const [sendProgress, setSendProgress] = useState<ProgressState>({});
|
||||
const [receiveProgress, setReceiveProgress] = useState<ProgressState>({});
|
||||
const [senderDisconnected, setSenderDisconnected] = useState(false);
|
||||
// 从 store 中获取状态
|
||||
const {
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
sendProgress,
|
||||
receiveProgress,
|
||||
senderDisconnected,
|
||||
setSharePeerCount,
|
||||
setRetrievePeerCount,
|
||||
setSendProgress,
|
||||
setReceiveProgress,
|
||||
setSenderDisconnected,
|
||||
setIsAnyFileTransferring,
|
||||
} = useFileTransferStore();
|
||||
|
||||
// Calculate isAnyFileTransferring internally based on progress states
|
||||
const isAnyFileTransferring = useMemo(() => {
|
||||
@@ -66,13 +75,22 @@ export function useWebRTCConnection({
|
||||
...Object.values(sendProgress),
|
||||
...Object.values(receiveProgress),
|
||||
];
|
||||
return allProgress.some((fileProgress) =>
|
||||
Object.values(fileProgress).some(
|
||||
(progress) => progress.progress > 0 && progress.progress < 1
|
||||
)
|
||||
);
|
||||
return allProgress.some((fileProgress: unknown) => {
|
||||
const typedFileProgress = fileProgress as FileProgressPeers;
|
||||
return Object.values(typedFileProgress).some(
|
||||
(progress: unknown) => {
|
||||
const typedProgress = progress as PeerProgressDetails;
|
||||
return typedProgress.progress > 0 && typedProgress.progress < 1;
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [sendProgress, receiveProgress]);
|
||||
|
||||
// 更新 store 中的 isAnyFileTransferring 状态
|
||||
useEffect(() => {
|
||||
setIsAnyFileTransferring(isAnyFileTransferring);
|
||||
}, [isAnyFileTransferring, setIsAnyFileTransferring]);
|
||||
|
||||
// Initialize WebRTC objects and their cleanup
|
||||
useEffect(() => {
|
||||
const webRTCConfig = {
|
||||
@@ -106,7 +124,6 @@ export function useWebRTCConnection({
|
||||
console.error(
|
||||
"SenderFileTransfer not initialized for sendStringAndMetasToPeer"
|
||||
);
|
||||
// TODO: Use putMessageInMs for critical errors visible to user?
|
||||
return;
|
||||
}
|
||||
if (textContent) {
|
||||
@@ -123,12 +140,11 @@ export function useWebRTCConnection({
|
||||
const broadcastDataToAllPeers = useCallback(
|
||||
async (textContent: string, filesToSend: CustomFile[]) => {
|
||||
if (!sender || sender.peerConnections.size === 0) {
|
||||
// The caller (useRoomManager) will handle user message like "waiting for peers"
|
||||
if (developmentEnv)
|
||||
console.warn(
|
||||
"No sender peers to broadcast to, or sender not initialized."
|
||||
);
|
||||
return false; // Indicate failure or no action
|
||||
return false;
|
||||
}
|
||||
if (!senderFileTransfer) {
|
||||
console.error("senderFileTransfer is not initialized for broadcast.");
|
||||
@@ -143,11 +159,10 @@ export function useWebRTCConnection({
|
||||
sendStringAndMetasToPeer(peerId, textContent, filesToSend)
|
||||
)
|
||||
);
|
||||
return true; // Indicate success
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error broadcasting data to peers:", error);
|
||||
// Optionally use putMessageInMs here for a generic broadcast error
|
||||
return false; // Indicate failure
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[sender, senderFileTransfer, sendStringAndMetasToPeer]
|
||||
@@ -161,22 +176,18 @@ export function useWebRTCConnection({
|
||||
console.log(`Sender connection state with ${peerId}: ${state}`);
|
||||
setSharePeerCount(sender.peerConnections.size);
|
||||
if (state === "connected") {
|
||||
senderFileTransfer.setProgressCallback((fileId, progress, speed) => {
|
||||
setSendProgress((prev) => ({
|
||||
senderFileTransfer.setProgressCallback((fileId, progress: number, speed: number) => {
|
||||
setSendProgress((prev: ProgressState) => ({
|
||||
...prev,
|
||||
[fileId]: { ...prev[fileId], [peerId]: { progress, speed } },
|
||||
}));
|
||||
}, peerId);
|
||||
// putMessageInMs(`Connected to a new peer (sender side). Total: ${sender.peerConnections.size}`, true);
|
||||
}
|
||||
// Add more detailed user messages based on state if needed via putMessageInMs
|
||||
};
|
||||
sender.onDataChannelOpen = () =>
|
||||
broadcastDataToAllPeers(shareContent, sendFiles);
|
||||
|
||||
sender.onPeerDisconnected = (peerId) => {
|
||||
// Use setTimeout to ensure cleanupExistingConnection has been called
|
||||
// before we update the peer count
|
||||
setTimeout(() => {
|
||||
setSharePeerCount(sender.peerConnections.size);
|
||||
}, 0);
|
||||
@@ -184,7 +195,6 @@ export function useWebRTCConnection({
|
||||
|
||||
sender.onError = (error) => {
|
||||
console.error("Sender Error:", error.message, error.context);
|
||||
// Optionally, use putMessageInMs to show a user-friendly error
|
||||
putMessageInMs(`Connection error: ${error.message}`, true);
|
||||
};
|
||||
}
|
||||
@@ -196,14 +206,13 @@ export function useWebRTCConnection({
|
||||
setRetrievePeerCount(receiver.peerConnections.size);
|
||||
if (state === "connected") {
|
||||
receiverFileTransfer.setProgressCallback(
|
||||
(fileId, progress, speed) => {
|
||||
setReceiveProgress((prev) => ({
|
||||
(fileId, progress: number, speed: number) => {
|
||||
setReceiveProgress((prev: ProgressState) => ({
|
||||
...prev,
|
||||
[fileId]: { ...prev[fileId], [peerId]: { progress, speed } },
|
||||
}));
|
||||
}
|
||||
);
|
||||
// Example: putMessageInMs(`Connected to a new peer (receiver side). Total: ${receiver.peerConnections.size}`, false);
|
||||
} else if (state === "failed" || state === "disconnected") {
|
||||
if (isAnyFileTransferring) {
|
||||
receiverFileTransfer.gracefulShutdown();
|
||||
@@ -234,18 +243,13 @@ export function useWebRTCConnection({
|
||||
receiver.onPeerDisconnected = (peerId) => {
|
||||
if (developmentEnv)
|
||||
console.log(`Receiver peer ${peerId} disconnected.`);
|
||||
// On the receiver side, any peer is a sender.
|
||||
setSenderDisconnected(true);
|
||||
// Set peer count to 0 since the peer has disconnected
|
||||
// Note: receiver.peerConnections.size might still be > 0 at this point
|
||||
// because cleanupExistingConnection hasn't been called yet
|
||||
setRetrievePeerCount(0);
|
||||
};
|
||||
|
||||
receiver.onConnectionEstablished = (peerId) => {
|
||||
if (developmentEnv)
|
||||
console.log(`Receiver connection established with ${peerId}.`);
|
||||
// If a connection is re-established, assume sender is back.
|
||||
setSenderDisconnected(false);
|
||||
};
|
||||
|
||||
@@ -267,6 +271,11 @@ export function useWebRTCConnection({
|
||||
shareContent,
|
||||
sendFiles,
|
||||
isAnyFileTransferring,
|
||||
setSharePeerCount,
|
||||
setRetrievePeerCount,
|
||||
setSendProgress,
|
||||
setReceiveProgress,
|
||||
setSenderDisconnected,
|
||||
]);
|
||||
|
||||
// Effect to handle graceful shutdown on page unload
|
||||
@@ -276,7 +285,6 @@ export function useWebRTCConnection({
|
||||
if (isAnyFileTransferring) {
|
||||
receiverFileTransfer?.gracefulShutdown();
|
||||
}
|
||||
// Show the browser's confirmation dialog
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
}
|
||||
@@ -291,7 +299,6 @@ export function useWebRTCConnection({
|
||||
|
||||
const requestFile = useCallback(
|
||||
(fileId: string, peerId?: string) => {
|
||||
// Assuming FileReceiver methods can take optional peerId
|
||||
if (!receiverFileTransfer) return;
|
||||
if (developmentEnv)
|
||||
console.log(
|
||||
@@ -333,29 +340,24 @@ export function useWebRTCConnection({
|
||||
// Reset function for receiver connection (for leave room functionality)
|
||||
const resetReceiverConnection = useCallback(async () => {
|
||||
if (receiver) {
|
||||
// First reset all UI states to ensure consistent state
|
||||
setSenderDisconnected(false);
|
||||
setRetrievePeerCount(0);
|
||||
// Then cleanup the WebRTC connection
|
||||
await receiver.leaveRoomAndCleanup();
|
||||
}
|
||||
}, [receiver]);
|
||||
}, [receiver, setSenderDisconnected, setRetrievePeerCount]);
|
||||
|
||||
// Reset function for sender connection (for leave room functionality)
|
||||
const resetSenderConnection = useCallback(async () => {
|
||||
if (sender) {
|
||||
// First cleanup the WebRTC connection (this sets isInRoom = false)
|
||||
await sender.leaveRoomAndCleanup();
|
||||
// Then reset UI state to ensure consistent state
|
||||
setSharePeerCount(0);
|
||||
}
|
||||
}, [sender]);
|
||||
}, [sender, setSharePeerCount]);
|
||||
|
||||
// Manual safe save function (replaces the beforeunload graceful shutdown)
|
||||
// Manual safe save function
|
||||
const manualSafeSave = useCallback(() => {
|
||||
if (receiverFileTransfer) {
|
||||
receiverFileTransfer.gracefulShutdown();
|
||||
// Provide user feedback
|
||||
if (putMessageInMs && messages) {
|
||||
putMessageInMs(
|
||||
messages.text.FileListDisplay.safeSaveSuccessMsg,
|
||||
@@ -367,22 +369,21 @@ export function useWebRTCConnection({
|
||||
}, [receiverFileTransfer, putMessageInMs, messages]);
|
||||
|
||||
return {
|
||||
sender, // Exposed for useRoomManager (e.g., sender.isInRoom, sender.joinRoom)
|
||||
receiver, // Exposed for useRoomManager
|
||||
// Not exposing senderFileTransfer/receiverFileTransfer directly to encourage using specific methods
|
||||
sender,
|
||||
receiver,
|
||||
sharePeerCount,
|
||||
retrievePeerCount,
|
||||
sendProgress,
|
||||
receiveProgress,
|
||||
isAnyFileTransferring, // Export the calculated state
|
||||
isAnyFileTransferring,
|
||||
broadcastDataToAllPeers,
|
||||
requestFile,
|
||||
requestFolder,
|
||||
setReceiverDirectoryHandle,
|
||||
getReceiverSaveType,
|
||||
senderDisconnected,
|
||||
resetReceiverConnection, // Export the new reset function
|
||||
resetSenderConnection, // Export the new sender reset function
|
||||
manualSafeSave, // Export the manual safe save function
|
||||
resetReceiverConnection,
|
||||
resetSenderConnection,
|
||||
manualSafeSave,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ interface FileTransferState {
|
||||
isAnyFileTransferring: boolean;
|
||||
|
||||
// UI 状态
|
||||
activeTab: 'send' | 'receive';
|
||||
activeTab: 'send' | 'retrieve';
|
||||
retrieveRoomIdInput: string;
|
||||
isDragging: boolean;
|
||||
|
||||
@@ -63,7 +63,7 @@ interface FileTransferState {
|
||||
setIsAnyFileTransferring: (transferring: boolean) => void;
|
||||
|
||||
// UI 状态相关 actions
|
||||
setActiveTab: (tab: 'send' | 'receive') => void;
|
||||
setActiveTab: (tab: 'send' | 'retrieve') => void;
|
||||
setRetrieveRoomIdInput: (input: string) => void;
|
||||
setIsDragging: (dragging: boolean) => void;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user