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.
This commit is contained in:
david_bai
2025-08-18 23:26:56 +08:00
parent 9c290754f2
commit a0554fb185
7 changed files with 128 additions and 47 deletions
+1 -1
View File
@@ -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({
+62 -29
View File
@@ -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<boolean> => {
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<void> => {
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,
};
}
}
+20 -4
View File
@@ -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<void>((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<void>((resolve) => setTimeout(() => resolve(), 0)); // 第二层确认
}
}
// endregion
+31 -1
View File
@@ -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();
};
}
+7 -5
View File
@@ -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<void> {
// 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<void> {
// 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 });
+1 -1
View File
@@ -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 {
+6 -6
View File
@@ -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));
}