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:
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user