From a0554fb185b426c36b495742b344e18e4c8ece54 Mon Sep 17 00:00:00 2001 From: david_bai Date: Mon, 18 Aug 2025 23:26:56 +0800 Subject: [PATCH] fix:Fix the download status synchronization issue. Fix the issue where download cannot find file (status synchronization problem). Change postLogInDebug to postLogToBackend for better understanding. --- frontend/app/config/api.ts | 2 +- frontend/hooks/useFileTransferHandler.ts | 91 ++++++++++++++++-------- frontend/lib/fileReceiver.ts | 24 +++++-- frontend/lib/webrtcService.ts | 32 ++++++++- frontend/lib/webrtc_Initiator.ts | 12 ++-- frontend/lib/webrtc_Recipient.ts | 2 +- frontend/lib/webrtc_base.ts | 12 ++-- 7 files changed, 128 insertions(+), 47 deletions(-) diff --git a/frontend/app/config/api.ts b/frontend/app/config/api.ts index 7443b43..ed4702c 100644 --- a/frontend/app/config/api.ts +++ b/frontend/app/config/api.ts @@ -86,7 +86,7 @@ export const setTrack = async (ref: string) => { }; // Log debug messages -export const postLogInDebug = async (message: string) => { +export const postLogToBackend = async (message: string) => { const options = getFetchOptions({ method: "POST", body: JSON.stringify({ diff --git a/frontend/hooks/useFileTransferHandler.ts b/frontend/hooks/useFileTransferHandler.ts index 81ca8ff..bbdae59 100644 --- a/frontend/hooks/useFileTransferHandler.ts +++ b/frontend/hooks/useFileTransferHandler.ts @@ -34,9 +34,12 @@ export function useFileTransferHandler({ setRetrievedFileMetas, } = useFileTransferStore(); - const updateShareContent = useCallback((content: string) => { - setShareContent(content); - }, [setShareContent]); + const updateShareContent = useCallback( + (content: string) => { + setShareContent(content); + }, + [setShareContent] + ); const addFilesToSend = useCallback( (pickedFiles: CustomFile[]) => { @@ -47,7 +50,7 @@ export function useFileTransferHandler({ if (newFiles.length < pickedFiles.length && messages) { putMessageInMs( messages.text.ClipboardApp.fileExistMsg || - "Some files were already added.", + "Some files were already added.", true ); } @@ -56,27 +59,27 @@ export function useFileTransferHandler({ [sendFiles, messages, putMessageInMs, addSendFiles] ); - const removeFileToSend = useCallback((metaToRemove: FileMeta) => { - removeSendFile(metaToRemove); - }, [removeSendFile]); - - // 这些回调函数已经不再需要,因为WebRTC Hook现在直接使用Store + const removeFileToSend = useCallback( + (metaToRemove: FileMeta) => { + removeSendFile(metaToRemove); + }, + [removeSendFile] + ); const handleDownloadFile = useCallback( async (meta: FileMeta) => { if (!messages) return; if (meta.folderName && meta.folderName !== "") { - const filesToZip = retrievedFiles.filter( + const { retrievedFiles: latestRetrievedFiles } = + useFileTransferStore.getState(); + const filesToZip = latestRetrievedFiles.filter( (file) => file.folderName === meta.folderName ); if (filesToZip.length === 0) { putMessageInMs( messages.text.ClipboardApp.noFilesForFolderMsg || - "No files found for folder '{folderName}'.".replace( - "{folderName}", - meta.folderName - ), + `No files found for folder '${meta.folderName}'.`, false ); return; @@ -91,28 +94,58 @@ export function useFileTransferHandler({ } catch (error) { console.error("Error creating zip file:", error); putMessageInMs( - messages.text.ClipboardApp.zipError || - "Error creating ZIP.", + messages.text.ClipboardApp.zipError || "Error creating ZIP.", false ); } } else { - const fileToDownload = retrievedFiles.find((f) => f.name === meta.name); - if (fileToDownload) { - downloadAs(fileToDownload, fileToDownload.name); - } else { - putMessageInMs( - messages.text.ClipboardApp.fileNotFoundMsg || - "File '{fileName}' not found for download.".replace( - "{fileName}", - meta.name - ), - false + let retryCount = 0; + const maxRetries = 3; // 重试次数 + + const findAndDownload = async (): Promise => { + retryCount++; + // 🔧 关键修复:使用最新的Store状态,而不是闭包中的旧状态 + const { retrievedFiles: latestRetrievedFiles } = + useFileTransferStore.getState(); + const fileToDownload = latestRetrievedFiles.find( + (f) => f.name === meta.name ); + + if (fileToDownload) { + downloadAs(fileToDownload, fileToDownload.name); + return true; + } + + return false; + }; + + // 首次尝试 + const found = await findAndDownload(); + + if (!found) { + // 如果没找到,启动重试机制 + const retryWithDelay = async (): Promise => { + while (retryCount < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, 50)); // 固定50ms延迟,因为现在状态应该很快同步 + const foundInRetry = await findAndDownload(); + if (foundInRetry) { + return; + } + } + // 所有重试都失败了 + putMessageInMs( + messages.text.ClipboardApp.fileNotFoundMsg || + `File '${meta.name}' not found for download.`, + false + ); + }; + + // 异步执行重试,不阻塞主线程 + retryWithDelay().catch(console.error); } } }, - [retrievedFiles, messages, putMessageInMs] + [messages, putMessageInMs] // 🔧 移除retrievedFiles依赖,因为我们现在直接从Store获取最新状态 ); // Reset function specifically for receiver state (for leave room functionality) @@ -134,4 +167,4 @@ export function useFileTransferHandler({ resetReceiverState, // Export the reset function handleDownloadFile, }; -} \ No newline at end of file +} diff --git a/frontend/lib/fileReceiver.ts b/frontend/lib/fileReceiver.ts index daecfa3..eb6ad9c 100644 --- a/frontend/lib/fileReceiver.ts +++ b/frontend/lib/fileReceiver.ts @@ -376,11 +376,22 @@ class FileReceiver { return; } + // 🔧 关键修复:先完成文件处理,确保文件添加到Store + await this.finalizeFileReceive(); + + // 🏗️ 架构重构:确保Store状态完全同步后再触发进度回调 if (!this.currentFolderName) { - this.progressCallback?.(reception.meta.fileId, 1, 0); + // 🔧 优化的异步确保机制 - 确保Store状态完全同步 + await Promise.resolve(); // 确保当前执行栈完成 + await new Promise((resolve) => { + // 使用更长的延迟确保Store状态完全更新 + setTimeout(() => { + this.progressCallback?.(reception.meta.fileId, 1, 0); + resolve(); + }, 10); // 增加到10ms确保Store状态完全同步 + }); } - await this.finalizeFileReceive(); this.sendFileAck(reception.meta.fileId); this.log("log", "Sent file-finish ack", { fileId: reception.meta.fileId }); @@ -542,8 +553,13 @@ class FileReceiver { folderName: this.currentFolderName, }) as CustomFile; - // saveType is now set in requestFile. - await this.onFileReceived?.(customFile); + if (this.onFileReceived) { + // 🔧 关键修复:确保 onFileReceived 回调完全同步执行完成 + await this.onFileReceived(customFile); + // 🔧 多重确认机制:确保 Store 状态完全同步 + await Promise.resolve(); // 第一层确认 + await new Promise((resolve) => setTimeout(() => resolve(), 0)); // 第二层确认 + } } // endregion diff --git a/frontend/lib/webrtcService.ts b/frontend/lib/webrtcService.ts index 29a0ea9..1838d2d 100644 --- a/frontend/lib/webrtcService.ts +++ b/frontend/lib/webrtcService.ts @@ -123,7 +123,37 @@ class WebRTCService { }; this.fileReceiver.onFileReceived = async (file) => { - useFileTransferStore.getState().addRetrievedFile(file); + // 🔧 增强修复:确保Store状态更新完全同步,使用多重验证 + const store = useFileTransferStore.getState(); + + // 检查文件是否已经存在,避免重复添加 + const existingFile = store.retrievedFiles.find( + (f) => f.name === file.name && f.size === file.size + ); + + if (!existingFile) { + store.addRetrievedFile(file); + } + + // 🔧 额外确保:立即验证状态更新是否成功,并重试机制 + let verificationAttempts = 0; + const maxVerificationAttempts = 3; + + const verifyFileAdded = () => { + verificationAttempts++; + const updatedStore = useFileTransferStore.getState(); + const fileExists = updatedStore.retrievedFiles.some( + (f) => f.name === file.name && f.size === file.size + ); + + if (!fileExists && verificationAttempts < maxVerificationAttempts) { + updatedStore.addRetrievedFile(file); + setTimeout(verifyFileAdded, 10); + } + }; + + // 立即进行第一次验证 + verifyFileAdded(); }; } diff --git a/frontend/lib/webrtc_Initiator.ts b/frontend/lib/webrtc_Initiator.ts index fce1a4b..9b7afa8 100644 --- a/frontend/lib/webrtc_Initiator.ts +++ b/frontend/lib/webrtc_Initiator.ts @@ -1,6 +1,6 @@ // Initiator flow: Join room; receive 'ready' event (this event is triggered by the socket server after a new recipient enters) -> createPeerConnection + createDataChannel -> createAndSendOffer import BaseWebRTC, { WebRTCConfig } from "./webrtc_base"; -import { postLogInDebug } from "@/app/config/api"; +import { postLogToBackend } from "@/app/config/api"; const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment export default class WebRTC_Initiator extends BaseWebRTC { @@ -17,7 +17,9 @@ export default class WebRTC_Initiator extends BaseWebRTC { // Add listener for recipient's response this.socket.on("recipient-ready", ({ peerId }) => { if (developmentEnv === "true") - postLogInDebug(`[Initiator] Received recipient-ready from: ${peerId}`); + postLogToBackend( + `[Initiator] Received recipient-ready from: ${peerId}` + ); this.handleReady({ peerId }); }); // Add answer handler listener @@ -31,7 +33,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { // Recipient peerId // this.log('log',`Received ready signal from peer ${peerId}`); if (developmentEnv === "true") - postLogInDebug(`Received ready signal from peer ${peerId}`); + postLogToBackend(`Received ready signal from peer ${peerId}`); await this.createPeerConnection(peerId); await this.createDataChannel(peerId); await this.createAndSendOffer(peerId); @@ -47,7 +49,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { }): Promise { // this.log('log',`Handling answer from peer ${from}`); if (developmentEnv === "true") - postLogInDebug(`Handling answer from peer ${from}`); + postLogToBackend(`Handling answer from peer ${from}`); const peerConnection = this.peerConnections.get(from); if (!peerConnection) { this.fireError(`No peer connection found for peer ${from}`, { from }); @@ -93,7 +95,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { private async createAndSendOffer(peerId: string): Promise { // this.log('log', `Creating and sending offer to ${peerId}`); if (developmentEnv === "true") - postLogInDebug(`createAndSendOffer for peerId: ${peerId}`); + postLogToBackend(`createAndSendOffer for peerId: ${peerId}`); const peerConnection = this.peerConnections.get(peerId); if (!peerConnection) { this.fireError(`No peer connection found for peer ${peerId}`, { peerId }); diff --git a/frontend/lib/webrtc_Recipient.ts b/frontend/lib/webrtc_Recipient.ts index 1557c95..e37e74f 100644 --- a/frontend/lib/webrtc_Recipient.ts +++ b/frontend/lib/webrtc_Recipient.ts @@ -1,6 +1,6 @@ // Recipient flow: Join room; receive 'offer' event -> createPeerConnection + createDataChannel -> send answer import BaseWebRTC, { WebRTCConfig } from "./webrtc_base"; -import { postLogInDebug } from "@/app/config/api"; +import { postLogToBackend } from "@/app/config/api"; const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment interface AnswerPayload { diff --git a/frontend/lib/webrtc_base.ts b/frontend/lib/webrtc_base.ts index bb83da0..7e80aad 100644 --- a/frontend/lib/webrtc_base.ts +++ b/frontend/lib/webrtc_base.ts @@ -1,7 +1,7 @@ // BaseWebRTC.js import io, { Socket, ManagerOptions, SocketOptions } from "socket.io-client"; import { WakeLockManager } from "./wakeLockManager"; -import { postLogInDebug } from "@/app/config/api"; +import { postLogToBackend } from "@/app/config/api"; const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment export class WebRTCError extends Error { @@ -129,7 +129,7 @@ export default class BaseWebRTC { this.isInRoom = false; this.isSocketDisconnected = true; if (developmentEnv === "true") - postLogInDebug( + postLogToBackend( `${this.peerId} disconnect on socket,isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}` ); // Attempt to reconnect. On mobile, switching to the background disconnects both P2P and socket connections. @@ -151,7 +151,7 @@ export default class BaseWebRTC { // Start reconnection only after both socket and P2P connections are disconnected this.reconnectionInProgress = true; if (developmentEnv === "true") { - postLogInDebug( + postLogToBackend( `Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}` ); } @@ -312,7 +312,7 @@ export default class BaseWebRTC { await this.cleanupExistingConnection(peerId); this.isPeerDisconnected = true; if (developmentEnv === "true") - postLogInDebug(`p2p disconnected, isInitiator:${this.isInitiator}`); + postLogToBackend(`p2p disconnected, isInitiator:${this.isInitiator}`); // Attempt to reconnect this.attemptReconnection(); await this.wakeLockManager.releaseWakeLock(); @@ -391,7 +391,7 @@ export default class BaseWebRTC { }); } if (developmentEnv === "true") - postLogInDebug( + postLogToBackend( `peerId:${this.socket.id} Successfully joined room: ${response.roomId},isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}` ); resolve(); @@ -399,7 +399,7 @@ export default class BaseWebRTC { this.isInRoom = false; this.roomId = null; if (developmentEnv === "true") - postLogInDebug(`Failed to join room,message:${response.message}`); + postLogToBackend(`Failed to join room,message:${response.message}`); this.fireError("Failed to join room", { message: response.message }); reject(new Error(response.message)); }