diff --git a/frontend/components/ClipboardApp/FileListDisplay.tsx b/frontend/components/ClipboardApp/FileListDisplay.tsx index 435dc79..7d2943d 100644 --- a/frontend/components/ClipboardApp/FileListDisplay.tsx +++ b/frontend/components/ClipboardApp/FileListDisplay.tsx @@ -13,7 +13,7 @@ import type { Messages } from "@/types/messages"; import { useFileTransferStore } from "@/stores/fileTransferStore"; import { supportsAutoDownload } from "@/lib/browserUtils"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; function formatFolderDis(template: string, num: number, size: string) { return template.replace("{num}", num.toString()).replace("{size}", size); @@ -261,7 +261,7 @@ const FileListDisplay: React.FC = ({ if (isAutoDownloadSupported) { // Browsers that support automatic downloads like Chrome: Download directly - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[Download Debug] Auto-downloading file: ${item.name}` ); @@ -269,7 +269,7 @@ const FileListDisplay: React.FC = ({ onDownload(item); } else { // Non-Chrome browsers: Set to save status, wait for user manual click - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[Download Debug] Setting pendingSave for non-Chrome browser: ${item.name}` ); @@ -280,7 +280,7 @@ const FileListDisplay: React.FC = ({ })); } } else { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `Skipping download logic - isSaveToDisk: ${isSaveToDisk}, onDownload: ${!!onDownload}` ); diff --git a/frontend/lib/fileReceiver.ts b/frontend/lib/fileReceiver.ts index 951a766..51992ce 100644 --- a/frontend/lib/fileReceiver.ts +++ b/frontend/lib/fileReceiver.ts @@ -22,7 +22,7 @@ import { EmbeddedChunkMeta, } from "@/types/webrtc"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 Strict Sequential Buffering Writer - Optimizes large file disk I/O performance */ @@ -40,6 +40,18 @@ class SequencedDiskWriter { /**\n * Write a chunk, automatically managing order and buffering\n */ async writeChunk(chunkIndex: number, chunk: ArrayBuffer): Promise { + // 🔍 调试writeChunk调用 + if ( + developmentEnv === "development" && + (chunkIndex <= 5 || chunkIndex === this.nextWriteIndex) + ) { + postLogToBackend( + `[DEBUG-RESUME] 🎯 WriteChunk called - received:${chunkIndex}, expected:${ + this.nextWriteIndex + }, match:${chunkIndex === this.nextWriteIndex}` + ); + } + // 1. If it is the expected next chunk, write immediately if (chunkIndex === this.nextWriteIndex) { await this.flushSequentialChunks(chunk); @@ -50,26 +62,26 @@ class SequencedDiskWriter { if (chunkIndex > this.nextWriteIndex) { if (this.writeQueue.size < this.maxBufferSize) { this.writeQueue.set(chunkIndex, chunk); - if (developmentEnv === "true") { - postLogToBackend( - `[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}` - ); - } + // if (developmentEnv === "development") { + // postLogToBackend( + // `[DEBUG] 📦 BUFFERED chunk #${chunkIndex} (waiting for #${this.nextWriteIndex}), queue: ${this.writeQueue.size}/${this.maxBufferSize}` + // ); + // } } else { // Buffer full, forcing processing of the earliest chunk to free up space await this.forceFlushOldest(); this.writeQueue.set(chunkIndex, chunk); - if (developmentEnv === "true") { - postLogToBackend( - `[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}` - ); - } + // if (developmentEnv === "development") { + // postLogToBackend( + // `[DEBUG] ⚠️ BUFFER_FULL, forced flush and buffered chunk #${chunkIndex}` + // ); + // } } return; } // 3. If the chunk is expired, log a warning but ignore (already written) - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ⚠️ DUPLICATE chunk #${chunkIndex} ignored (already written #${this.nextWriteIndex})` ); @@ -87,7 +99,7 @@ class SequencedDiskWriter { await this.stream.write(firstChunk); this.totalWritten += firstChunk.byteLength; - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ✓ DISK_WRITE chunk #${this.nextWriteIndex} - ${firstChunk.byteLength} bytes, total: ${this.totalWritten}` ); @@ -123,7 +135,7 @@ class SequencedDiskWriter { } if (flushCount > 0) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 🔥 SEQUENTIAL_FLUSH ${flushCount} chunks, now at #${this.nextWriteIndex}, queue: ${this.writeQueue.size}` ); @@ -131,6 +143,13 @@ class SequencedDiskWriter { } } + /** + * Get the next expected write index + */ + get expectedIndex(): number { + return this.nextWriteIndex; + } + /** * Force refresh the earliest chunk to release buffer space */ @@ -142,11 +161,11 @@ class SequencedDiskWriter { const chunk = this.writeQueue.get(oldestIndex)!; // Warning: Unordered write - if (developmentEnv === "true") { - postLogToBackend( - `[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})` - ); - } + // if (developmentEnv === "development") { + // postLogToBackend( + // `[DEBUG] ⚠️ FORCE_FLUSH out-of-order chunk #${oldestIndex} (expected #${this.nextWriteIndex})` + // ); + // } // Use seek to write at the correct position (fallback handling) const fileOffset = oldestIndex * 65536; // Assume each chunk is 64KB @@ -204,7 +223,7 @@ class SequencedDiskWriter { const fileOffset = chunkIndex * 65536; await this.stream.seek(fileOffset); await this.stream.write(chunk); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 💾 FINAL_FLUSH chunk #${chunkIndex} at cleanup` ); @@ -410,6 +429,38 @@ class FileReceiver { const receptionPromise = new Promise((resolve, reject) => { const expectedChunksCount = Math.ceil((fileInfo.size - offset) / 65536); // Calculate expected chunk count + // 🔍 调试expectedChunksCount计算 + if (developmentEnv === "development") { + const totalChunks = Math.ceil(fileInfo.size / 65536); + const startChunkIndex = Math.floor(offset / 65536); + const calculatedExpected = totalChunks - startChunkIndex; + + postLogToBackend(`[DEBUG-CHUNKS] File: ${fileInfo.name}`); + postLogToBackend( + `[DEBUG-CHUNKS] File size: ${fileInfo.size}, offset: ${offset}` + ); + postLogToBackend( + `[DEBUG-CHUNKS] Total chunks in file: ${totalChunks} (0-${ + totalChunks - 1 + })` + ); + postLogToBackend( + `[DEBUG-CHUNKS] Start chunk index: ${startChunkIndex}` + ); + postLogToBackend( + `[DEBUG-CHUNKS] Expected chunks calculation: (${fileInfo.size} - ${offset}) / 65536 = ${expectedChunksCount}` + ); + postLogToBackend( + `[DEBUG-CHUNKS] Alternative calculation: ${totalChunks} - ${startChunkIndex} = ${calculatedExpected}` + ); + + if (expectedChunksCount !== calculatedExpected) { + postLogToBackend( + `[DEBUG-CHUNKS] ⚠️ MISMATCH: ${expectedChunksCount} vs ${calculatedExpected}` + ); + } + } + this.activeFileReception = { meta: fileInfo, chunks: new Array(expectedChunksCount).fill(null), // 🚀 Initialize as an empty array arranged by index @@ -435,7 +486,7 @@ class FileReceiver { this.webrtcConnection.sendData(JSON.stringify(request), this.peerId); this.log("log", "Sent fileRequest", { request }); } else { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ERROR: Cannot send fileRequest - no peerId available!` ); @@ -507,7 +558,7 @@ class FileReceiver { return true; }); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 📁 All files in folder completed - ${folderName}, files: ${completedFileIds.length}/${folderProgress.fileIds.length}` ); @@ -533,7 +584,7 @@ class FileReceiver { try { const arrayBuffer = await data.arrayBuffer(); if (data.size !== arrayBuffer.byteLength) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ⚠️ Blob size mismatch: ${data.size}→${arrayBuffer.byteLength}` ); @@ -541,7 +592,7 @@ class FileReceiver { } return arrayBuffer; } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ Blob conversion failed: ${error}`); } return null; @@ -556,13 +607,13 @@ class FileReceiver { new Uint8Array(newArrayBuffer).set(uint8Array); return newArrayBuffer; } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ TypedArray conversion failed: ${error}`); } return null; } } else { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ Unknown data type: ${Object.prototype.toString.call( data @@ -584,7 +635,7 @@ class FileReceiver { try { // 1. Check minimum packet length if (arrayBuffer.byteLength < 4) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ Invalid embedded packet - too small: ${arrayBuffer.byteLength}` ); @@ -599,7 +650,7 @@ class FileReceiver { // 3. Verify packet integrity const expectedTotalLength = 4 + metaLength; if (arrayBuffer.byteLength < expectedTotalLength) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ Incomplete embedded packet - expected: ${expectedTotalLength}, got: ${arrayBuffer.byteLength}` ); @@ -618,7 +669,7 @@ class FileReceiver { // 6. Verify chunk data size if (chunkData.byteLength !== chunkMeta.chunkSize) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ⚠️ Chunk size mismatch - meta: ${chunkMeta.chunkSize}, actual: ${chunkData.byteLength}` ); @@ -627,7 +678,7 @@ class FileReceiver { return { chunkMeta, chunkData }; } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ Failed to parse embedded packet: ${error}` ); @@ -664,7 +715,7 @@ class FileReceiver { if (arrayBuffer) { if (!this.activeFileReception) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ERROR: Received file chunk but no active file reception!` ); @@ -679,7 +730,7 @@ class FileReceiver { // 🚀 Unified processing: All data is processed as embedded packets await this.handleEmbeddedChunkPacket(arrayBuffer); } else { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ERROR: Failed to convert binary data to ArrayBuffer` ); @@ -773,7 +824,7 @@ class FileReceiver { // Verify fileId match if (chunkMeta.fileId !== reception.meta.fileId) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ⚠️ FileId mismatch - expected: ${reception.meta.fileId}, got: ${chunkMeta.fileId}` ); @@ -781,44 +832,74 @@ class FileReceiver { return; } - // Update expected chunks count (may differ from initial estimate) + // 🔧 修复:续传时不要调整expectedChunksCount + // chunkMeta.totalChunks是文件总chunk数,但续传时我们只接收部分chunks if (chunkMeta.totalChunks !== reception.expectedChunksCount) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { + const startChunkIndex = Math.floor(reception.initialOffset / 65536); + const calculatedExpected = chunkMeta.totalChunks - startChunkIndex; postLogToBackend( - `[DEBUG] ⚠️ Chunk count adjustment - expected: ${reception.expectedChunksCount}, actual: ${chunkMeta.totalChunks}` + `[DEBUG-CHUNKS] Chunk count info - fileTotal: ${chunkMeta.totalChunks}, currentExpected: ${reception.expectedChunksCount}, calculatedExpected: ${calculatedExpected}, startChunk: ${startChunkIndex}` ); - } - reception.expectedChunksCount = chunkMeta.totalChunks; - // Adjust chunks array size - if (reception.chunks.length < chunkMeta.totalChunks) { - const newChunks = new Array(chunkMeta.totalChunks).fill(null); - reception.chunks.forEach((chunk, index) => { - if (index < newChunks.length) newChunks[index] = chunk; - }); - reception.chunks = newChunks; + + // 🚫 不再调整expectedChunksCount,保持续传时的正确数量 + // reception.expectedChunksCount = chunkMeta.totalChunks; // 这行导致了问题! + + if (reception.expectedChunksCount !== calculatedExpected) { + postLogToBackend( + `[DEBUG-CHUNKS] ⚠️ Expected chunks mismatch, should be ${calculatedExpected}` + ); + } } } - // Store chunk by index - const chunkIndex = chunkMeta.chunkIndex; - if (chunkIndex >= 0 && chunkIndex < reception.chunks.length) { - reception.chunks[chunkIndex] = chunkData; - reception.chunkSequenceMap.set(chunkIndex, true); + // Store chunk by index - 🔧 修复:将绝对索引映射到相对索引 + const absoluteChunkIndex = chunkMeta.chunkIndex; // 发送端的绝对索引(如967-3650) + const startChunkIndex = Math.floor(reception.initialOffset / 65536); // 续传起始索引(如967) + const relativeChunkIndex = absoluteChunkIndex - startChunkIndex; // 在chunks数组中的相对索引(如0-2683) + + if (developmentEnv === "development" && absoluteChunkIndex <= 970) { + postLogToBackend( + `[DEBUG-CHUNKS] Index mapping - absolute:${absoluteChunkIndex}, start:${startChunkIndex}, relative:${relativeChunkIndex}, arraySize:${reception.chunks.length}` + ); + } + + if ( + relativeChunkIndex >= 0 && + relativeChunkIndex < reception.chunks.length + ) { + reception.chunks[relativeChunkIndex] = chunkData; + reception.chunkSequenceMap.set(absoluteChunkIndex, true); // 序列映射仍使用绝对索引 reception.receivedChunksCount++; // Update progress this.updateProgress(chunkData.byteLength); if (reception.sequencedWriter) { - // 🚀 Use strict sequential write management - await reception.sequencedWriter.writeChunk(chunkIndex, chunkData); + // 🔍 调试chunk接收匹配 (前5个和后5个chunks) + const lastFewChunks = + relativeChunkIndex >= reception.expectedChunksCount - 5; + if ( + developmentEnv === "development" && + (absoluteChunkIndex <= 970 || lastFewChunks) + ) { + postLogToBackend( + `[DEBUG-CHUNKS] 📦 Chunk #${absoluteChunkIndex} received - relative:${relativeChunkIndex}, size:${chunkData.byteLength}, writerExpects:${reception.sequencedWriter.expectedIndex}, isLastFew:${lastFewChunks}` + ); + } + + // 🚀 Use strict sequential write management - 使用绝对索引 + await reception.sequencedWriter.writeChunk( + absoluteChunkIndex, + chunkData + ); } } else { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( - `[DEBUG] ❌ Invalid chunk index - ${chunkIndex}, expected 0-${ - reception.chunks.length - 1 - }` + `[DEBUG-CHUNKS] ❌ Invalid relative chunk index - absolute:${absoluteChunkIndex}, relative:${relativeChunkIndex}, arraySize:${ + reception.chunks.length + }, expected:0-${reception.chunks.length - 1}` ); } } @@ -840,7 +921,8 @@ class FileReceiver { const currentTotalSize = reception.chunks.reduce((sum, chunk) => { return sum + (chunk instanceof ArrayBuffer ? chunk.byteLength : 0); }, 0); - const expectedSize = reception.meta.size; + // 🔧 修复:续传时应该比较的是剩余文件大小,不是整个文件大小 + const expectedSize = reception.meta.size - reception.initialOffset; // 🚀 Unified integrity check: sequential reception mode let sequencedCount = 0; @@ -850,10 +932,55 @@ class FileReceiver { } } const isSequencedComplete = sequencedCount === expectedChunks; - const sizeComplete = currentTotalSize >= expectedSize; const isDataComplete = isSequencedComplete && sizeComplete; + // 🔍 详细调试完成检查 (减少频率,只在关键时刻输出) + if ( + developmentEnv === "development" && + (isDataComplete || + sequencedCount % 500 === 0 || + sequencedCount > expectedChunks - 10) + ) { + // 检查最后几个chunks的状态 (显示相对索引) + const lastChunkIndex = expectedChunks - 1; + const lastFewChunks = []; + const startChunkIndex = Math.floor(reception.initialOffset / 65536); + + for (let i = Math.max(0, lastChunkIndex - 3); i <= lastChunkIndex; i++) { + const chunk = reception.chunks[i]; + const exists = chunk instanceof ArrayBuffer; + const size = exists ? (chunk as ArrayBuffer).byteLength : 0; + const absoluteIndex = startChunkIndex + i; // 对应的绝对索引 + lastFewChunks.push(`rel#${i}(abs#${absoluteIndex}):${exists}(${size})`); + } + + postLogToBackend( + `[DEBUG-COMPLETE] Check completion - file:${reception.meta.name}` + ); + postLogToBackend( + `[DEBUG-COMPLETE] Chunks: received:${sequencedCount}/${expectedChunks}, isSequenceComplete:${isSequencedComplete}` + ); + postLogToBackend( + `[DEBUG-COMPLETE] Size: current:${currentTotalSize}, expected:${expectedSize}, sizeComplete:${sizeComplete}, diff:${ + expectedSize - currentTotalSize + }` + ); + postLogToBackend( + `[DEBUG-COMPLETE] LastChunks: ${lastFewChunks.join(", ")}` + ); + postLogToBackend( + `[DEBUG-COMPLETE] IsDataComplete: ${isDataComplete}, isFinalized: ${reception.isFinalized}` + ); + + if (reception.sequencedWriter) { + const writerStatus = reception.sequencedWriter.getBufferStatus(); + postLogToBackend( + `[DEBUG-COMPLETE] SequencedWriter: nextIndex:${writerStatus.nextIndex}, totalWritten:${writerStatus.totalWritten}, queueSize:${writerStatus.queueSize}` + ); + } + } + // Prevent duplicate finalize if (reception.isFinalized) { return; @@ -862,6 +989,12 @@ class FileReceiver { if (isDataComplete) { reception.isFinalized = true; + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-COMPLETE] ✅ Starting finalization - isDataComplete:${isDataComplete}` + ); + } + try { await this.finalizeFileReceive(); @@ -870,7 +1003,7 @@ class FileReceiver { } this.activeFileReception = null; } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ Auto-finalize ERROR: ${error}`); } if (reception.completionNotifier) { @@ -958,10 +1091,14 @@ class FileReceiver { startChunkIndex ); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 📢 SEQUENCED_WRITER created - startIndex: ${startChunkIndex}, offset: ${offset}` ); + // 🔍 调试续传接收期望 + postLogToBackend( + `[DEBUG-RESUME] 🎯 SequencedWriter expects - startIndex:${startChunkIndex}, offset:${offset}, calculatedFrom:${offset}/65536` + ); } } catch (err) { this.fireError("Failed to create file on disk", { @@ -994,28 +1131,59 @@ class FileReceiver { private async finalizeLargeFileReceive(): Promise { const reception = this.activeFileReception; - if (!reception?.writeStream || !reception.fileHandle) return; + if (!reception?.writeStream || !reception.fileHandle) { + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-FINALIZE] ❌ Cannot finalize - missing writeStream:${!!reception?.writeStream} or fileHandle:${!!reception?.fileHandle}` + ); + } + return; + } try { + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-FINALIZE] 🚀 Starting finalization for ${reception.meta.name}` + ); + } + // 🚀 First close the strict sequential writing manager (flush all buffers) if (reception.sequencedWriter) { + if (developmentEnv === "development") { + postLogToBackend(`[DEBUG-FINALIZE] Closing SequencedWriter...`); + } await reception.sequencedWriter.close(); const status = reception.sequencedWriter.getBufferStatus(); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( - `[DEBUG] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}` + `[DEBUG-FINALIZE] 💾 SEQUENCED_WRITER closed - totalWritten: ${status.totalWritten}, finalQueue: ${status.queueSize}` ); } reception.sequencedWriter = null; } // Then close the file stream + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-FINALIZE] About to close writeStream for ${reception.meta.name}` + ); + } await reception.writeStream.close(); + if (developmentEnv === "development") { + postLogToBackend(`[DEBUG-FINALIZE] ✅ WriteStream closed successfully`); + } - if (developmentEnv === "true") { - postLogToBackend(`[DEBUG] ✅ LARGE_FILE finalized successfully`); + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-FINALIZE] ✅ LARGE_FILE finalized successfully - ${reception.meta.name}` + ); } } catch (error) { + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG-FINALIZE] ❌ Error during finalization: ${error}` + ); + } this.fireError("Error finalizing large file", { error }); } } @@ -1040,7 +1208,7 @@ class FileReceiver { // Final verification const sizeDifference = reception.meta.size - totalChunkSize; if (sizeDifference !== 0) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ SIZE_MISMATCH - missing: ${sizeDifference} bytes` ); @@ -1131,7 +1299,7 @@ class FileReceiver { this.peerId ); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 📤 Sent folderReceiveComplete - folderName: ${folderName}, completedFiles: ${completedFileIds.length}, allStoreUpdated: ${allStoreUpdated}, success: ${success}` ); diff --git a/frontend/lib/tracking.ts b/frontend/lib/tracking.ts index 4cc95b8..b87d5a8 100644 --- a/frontend/lib/tracking.ts +++ b/frontend/lib/tracking.ts @@ -4,7 +4,7 @@ export const trackReferrer = async () => { // Get URL parameters const urlParams = new URLSearchParams(window.location.search); let ref = urlParams.get("ref"); - if (process.env.NEXT_PUBLIC_development === "false") { + if (process.env.NODE_ENV === "production") { ref = urlParams.get("ref") || "noRef"; // Production environment, count daily active users, record as noRef if there is no ref } diff --git a/frontend/lib/transfer/FileTransferOrchestrator.ts b/frontend/lib/transfer/FileTransferOrchestrator.ts index 5d58dc2..842e4e5 100644 --- a/frontend/lib/transfer/FileTransferOrchestrator.ts +++ b/frontend/lib/transfer/FileTransferOrchestrator.ts @@ -14,7 +14,7 @@ import { StreamingFileReader } from "./StreamingFileReader"; import { TransferConfig } from "./TransferConfig"; import WebRTC_Initiator from "../webrtc_Initiator"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 File transfer orchestrator * Integrates all components to provide unified file transfer services @@ -211,7 +211,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate { peerState.readOffset || 0 ); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 🚀 Starting transfer - file: ${file.name}, size: ${( file.size / @@ -301,20 +301,32 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate { } } - if (developmentEnv === "true") { + if (developmentEnv === "development") { const totalTime = performance.now() - transferStartTime; const avgSpeedMBps = totalBytesSent / 1024 / 1024 / (totalTime / 1000); + const expectedTotalChunks = Math.ceil(file.size / 65536); + const startOffset = peerState.readOffset || 0; + const startChunkIndex = Math.floor(startOffset / 65536); + const expectedChunksSent = expectedTotalChunks - startChunkIndex; + postLogToBackend( - `[DEBUG] ✅ Transfer complete - file: ${file.name}, time: ${( + `[DEBUG-CHUNKS] ✅ Transfer complete - file: ${file.name}, time: ${( totalTime / 1000 - ).toFixed(1)}s, speed: ${avgSpeedMBps.toFixed( - 1 - )}MB/s, chunks: ${networkChunkIndex}` + ).toFixed(1)}s, speed: ${avgSpeedMBps.toFixed(1)}MB/s` ); + postLogToBackend( + `[DEBUG-CHUNKS] Chunks sent: ${networkChunkIndex}, expected: ${expectedChunksSent}, startChunk: ${startChunkIndex}, totalFileChunks: ${expectedTotalChunks}` + ); + + if (networkChunkIndex !== expectedChunksSent) { + postLogToBackend( + `[DEBUG-CHUNKS] ⚠️ CHUNK MISMATCH: sent ${networkChunkIndex} but expected ${expectedChunksSent}` + ); + } } } catch (error: any) { const errorMessage = `Streaming send error: ${error.message}`; - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ Transfer error: ${errorMessage}`); } this.fireError(errorMessage, { @@ -428,7 +440,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate { public handlePeerReconnection(peerId: string): void { // Clear all transfer states for this peer this.stateManager.clearPeerState(peerId); - if (developmentEnv === "true") + if (developmentEnv === "development") this.log( "log", `Successfully reset transfer state for reconnected peer ${peerId}` @@ -443,7 +455,7 @@ export class FileTransferOrchestrator implements MessageHandlerDelegate { this.networkTransmitter.cleanup(); this.progressTracker.cleanup(); this.messageHandler.cleanup(); - if (developmentEnv === "true") + if (developmentEnv === "development") this.log("log", "FileTransferOrchestrator cleaned up"); } } diff --git a/frontend/lib/transfer/MessageHandler.ts b/frontend/lib/transfer/MessageHandler.ts index 61e8d57..4481c7a 100644 --- a/frontend/lib/transfer/MessageHandler.ts +++ b/frontend/lib/transfer/MessageHandler.ts @@ -6,7 +6,7 @@ import { } from "@/types/webrtc"; import { StateManager } from "./StateManager"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 Message handling interface - Communicate with main orchestrator */ @@ -120,7 +120,7 @@ export class MessageHandler { message: FolderReceiveComplete, peerId: string ): void { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 📥 Folder complete - folderName: ${message.folderName}, files: ${message.completedFileIds.length}` ); @@ -172,7 +172,7 @@ export class MessageHandler { * 🧹 Clean up resources */ public cleanup(): void { - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend("[DEBUG] 🧹 MessageHandler cleaned up"); } } diff --git a/frontend/lib/transfer/NetworkTransmitter.ts b/frontend/lib/transfer/NetworkTransmitter.ts index 7b0da2c..fa897f2 100644 --- a/frontend/lib/transfer/NetworkTransmitter.ts +++ b/frontend/lib/transfer/NetworkTransmitter.ts @@ -2,7 +2,7 @@ import { EmbeddedChunkMeta } from "@/types/webrtc"; import { StateManager } from "./StateManager"; import WebRTC_Initiator from "../webrtc_Initiator"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 Network transmitter - Simplified version * Uses WebRTC native bufferedAmountLowThreshold for backpressure control @@ -34,7 +34,7 @@ export class NetworkTransmitter { // Key node logs (development environment only) if ( - developmentEnv === "true" && + developmentEnv === "development" && (metadata.chunkIndex % 100 === 0 || metadata.isLastChunk) ) { postLogToBackend( @@ -48,7 +48,7 @@ export class NetworkTransmitter { return true; } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] ❌ CHUNK #${metadata.chunkIndex} send failed: ${error}` ); @@ -106,7 +106,7 @@ export class NetworkTransmitter { if (!sendResult) { const errorMessage = `sendData failed`; - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ ${errorMessage}`); } throw new Error(errorMessage); @@ -148,7 +148,7 @@ export class NetworkTransmitter { }); // Only output backpressure logs in development environment - if (developmentEnv === "true") { + if (developmentEnv === "development") { const waitTime = performance.now() - startTime; postLogToBackend( `[DEBUG] 🚀 BACKPRESSURE - wait: ${waitTime.toFixed( @@ -182,7 +182,7 @@ export class NetworkTransmitter { } } catch (error) { const errorMessage = `sendWithBackpressure failed: ${error}`; - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ ${errorMessage}`); } throw new Error(errorMessage); @@ -239,7 +239,7 @@ export class NetworkTransmitter { * 🧹 Clean up resources */ public cleanup(): void { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend("[DEBUG] 🧹 NetworkTransmitter cleaned up"); } } diff --git a/frontend/lib/transfer/ProgressTracker.ts b/frontend/lib/transfer/ProgressTracker.ts index 18d0d06..f28b6c2 100644 --- a/frontend/lib/transfer/ProgressTracker.ts +++ b/frontend/lib/transfer/ProgressTracker.ts @@ -1,7 +1,7 @@ import { SpeedCalculator } from "@/lib/speedCalculator"; import { StateManager } from "./StateManager"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 Progress callback type definition */ @@ -225,7 +225,7 @@ export class ProgressTracker { */ cleanup(): void { // SpeedCalculator internally automatically cleans up expired data - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend("[DEBUG] 🧹 ProgressTracker cleaned up"); } } diff --git a/frontend/lib/transfer/StreamingFileReader.ts b/frontend/lib/transfer/StreamingFileReader.ts index 087c536..3a22e71 100644 --- a/frontend/lib/transfer/StreamingFileReader.ts +++ b/frontend/lib/transfer/StreamingFileReader.ts @@ -1,7 +1,7 @@ import { CustomFile } from "@/types/webrtc"; import { TransferConfig } from "./TransferConfig"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; +const developmentEnv = process.env.NODE_ENV; /** * 🚀 Network chunk interface */ @@ -46,9 +46,11 @@ export class StreamingFileReader { this.file = file; this.totalFileSize = file.size; this.totalFileOffset = startOffset; + // 🔧 修复:续传时currentBatchStartOffset应该从startOffset开始 + this.currentBatchStartOffset = startOffset; this.fileReader = new FileReader(); - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `[DEBUG] 📖 StreamingFileReader created - file: ${file.name}, size: ${( this.totalFileSize / @@ -56,6 +58,13 @@ export class StreamingFileReader { 1024 ).toFixed(1)}MB` ); + // 🔍 调试续传初始化 + const expectedGlobalChunk = Math.floor( + startOffset / this.NETWORK_CHUNK_SIZE + ); + postLogToBackend( + `[DEBUG-RESUME] 🏗️ StreamingFileReader created - totalFileOffset:${this.totalFileOffset}, currentBatchStartOffset:${this.currentBatchStartOffset}, expectedGlobalChunk:${expectedGlobalChunk}` + ); } } @@ -89,6 +98,15 @@ export class StreamingFileReader { // Delete frequent chunk progress logs + // 🔍 调试chunk发送 (前5个和最后5个chunks) + const totalChunks = this.calculateTotalNetworkChunks(); + const isLastFew = globalChunkIndex >= (totalChunks - 5); + if (developmentEnv === "development" && (globalChunkIndex <= 5 || isLastFew || isLast)) { + postLogToBackend( + `[DEBUG-CHUNKS] 📤 Send chunk #${globalChunkIndex}/${totalChunks} - size:${networkChunk.byteLength}, isLast:${isLast}, fileOffset:${this.totalFileOffset - networkChunk.byteLength}` + ); + } + return { chunk: networkChunk, chunkIndex: globalChunkIndex, @@ -160,11 +178,24 @@ export class StreamingFileReader { this.currentBatch = await this.readFileSlice(fileSlice); const readTime = performance.now() - readStartTime; - this.currentBatchStartOffset = this.totalFileOffset; - this.currentChunkIndexInBatch = 0; + const batchStartOffset = this.totalFileOffset; + this.currentBatchStartOffset = batchStartOffset; + + // 🔧 修复:如果不是从batch边界开始,说明是续传情况,需要计算正确的batch内索引 + if (batchStartOffset % this.BATCH_SIZE !== 0) { + // 续传情况:不是从batch边界开始 + const globalChunkIndex = Math.floor( + batchStartOffset / this.NETWORK_CHUNK_SIZE + ); + this.currentChunkIndexInBatch = + globalChunkIndex % this.CHUNKS_PER_BATCH; + } else { + // 正常情况:从batch边界开始 + this.currentChunkIndexInBatch = 0; + } // Only output batch reading logs in development environment - if (developmentEnv === "true") { + if (developmentEnv === "development") { const totalTime = performance.now() - startTime; const speedMBps = batchSize / 1024 / 1024 / (totalTime / 1000); postLogToBackend( @@ -174,9 +205,15 @@ export class StreamingFileReader { 1 )}MB/s` ); + // 🔍 调试batch内索引设置 + postLogToBackend( + `[DEBUG-RESUME] 📖 BATCH loaded - batchStartOffset:${batchStartOffset}, currentChunkIndexInBatch:${ + this.currentChunkIndexInBatch + }, isResume:${batchStartOffset % this.BATCH_SIZE !== 0}` + ); } } catch (error) { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`[DEBUG] ❌ BATCH_READ failed: ${error}`); } throw new Error(`Failed to load file batch: ${error}`); @@ -247,7 +284,19 @@ export class StreamingFileReader { this.currentBatchStartOffset / this.BATCH_SIZE ); const chunksInPreviousBatches = batchesBefore * this.CHUNKS_PER_BATCH; - return chunksInPreviousBatches + this.currentChunkIndexInBatch; + const result = chunksInPreviousBatches + this.currentChunkIndexInBatch; + + // 🔍 调试chunk索引计算 + if ( + developmentEnv === "development" && + this.currentChunkIndexInBatch <= 5 + ) { + postLogToBackend( + `[DEBUG-RESUME] 🧮 calculateGlobalChunkIndex - batchStartOffset:${this.currentBatchStartOffset}, batchesBefore:${batchesBefore}, chunksInPrev:${chunksInPreviousBatches}, chunkInBatch:${this.currentChunkIndexInBatch}, result:${result}` + ); + } + + return result; } /** @@ -324,10 +373,14 @@ export class StreamingFileReader { this.isFinished = false; this.isReading = false; this.currentBatch = null; - this.currentBatchStartOffset = 0; - this.currentChunkIndexInBatch = 0; - if (developmentEnv === "true") { - postLogToBackend(`[DEBUG] 🔄 StreamingFileReader reset`); + // 🔧 修复:reset时也要正确设置currentBatchStartOffset + this.currentBatchStartOffset = startOffset; + this.currentChunkIndexInBatch = 0; // 重置为0,loadNextBatch会重新计算 + + if (developmentEnv === "development") { + postLogToBackend( + `[DEBUG] 🔄 StreamingFileReader reset - startOffset:${startOffset}` + ); } } diff --git a/frontend/lib/webrtc_Initiator.ts b/frontend/lib/webrtc_Initiator.ts index 05ab205..9cdd619 100644 --- a/frontend/lib/webrtc_Initiator.ts +++ b/frontend/lib/webrtc_Initiator.ts @@ -1,7 +1,7 @@ // 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 { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment +const developmentEnv = process.env.NODE_ENV; // Development environment export default class WebRTC_Initiator extends BaseWebRTC { constructor(config: WebRTCConfig) { @@ -16,7 +16,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { }); // Add listener for recipient's response this.socket.on("recipient-ready", ({ peerId }) => { - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend( `[Initiator] Received recipient-ready from: ${peerId}` ); @@ -32,7 +32,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { private async handleReady({ peerId }: { peerId: string }): Promise { // Recipient peerId // this.log('log',`Received ready signal from peer ${peerId}`); - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend(`Received ready signal from peer ${peerId}`); await this.createPeerConnection(peerId); await this.createDataChannel(peerId); @@ -48,7 +48,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { from: string; }): Promise { // this.log('log',`Handling answer from peer ${from}`); - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend(`Handling answer from peer ${from}`); const peerConnection = this.peerConnections.get(from); if (!peerConnection) { @@ -96,7 +96,7 @@ export default class WebRTC_Initiator extends BaseWebRTC { // If it is the initiator, create and send an offer to the signaling server to negotiate a connection with the recipient. private async createAndSendOffer(peerId: string): Promise { // this.log('log', `Creating and sending offer to ${peerId}`); - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend(`createAndSendOffer for peerId: ${peerId}`); const peerConnection = this.peerConnections.get(peerId); if (!peerConnection) { diff --git a/frontend/lib/webrtc_base.ts b/frontend/lib/webrtc_base.ts index 967b218..64be02e 100644 --- a/frontend/lib/webrtc_base.ts +++ b/frontend/lib/webrtc_base.ts @@ -2,7 +2,7 @@ import io, { Socket, ManagerOptions, SocketOptions } from "socket.io-client"; import { WakeLockManager } from "./wakeLockManager"; import { postLogToBackend } from "@/app/config/api"; -const developmentEnv = process.env.NEXT_PUBLIC_development!; // Development environment +const developmentEnv = process.env.NODE_ENV; // Development environment export class WebRTCError extends Error { constructor(message: string, public context?: Record) { @@ -131,7 +131,7 @@ export default class BaseWebRTC { this.socket.on("disconnect", () => { this.isInRoom = false; this.isSocketDisconnected = true; - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend( `${this.peerId} disconnect on socket,isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}` ); @@ -153,7 +153,7 @@ export default class BaseWebRTC { if (this.isSocketDisconnected && this.isPeerDisconnected && this.roomId) { // Start reconnection only after both socket and P2P connections are disconnected this.reconnectionInProgress = true; - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend( `Starting reconnection, socket and peer both disconnected. isInitiator:${this.isInitiator}` ); @@ -314,7 +314,7 @@ export default class BaseWebRTC { disconnected: async () => { await this.cleanupExistingConnection(peerId); this.isPeerDisconnected = true; - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend(`p2p disconnected, isInitiator:${this.isInitiator}`); // Attempt to reconnect this.attemptReconnection(); @@ -402,7 +402,7 @@ export default class BaseWebRTC { }; dataChannel.onclose = () => { - if (developmentEnv === "true") { + if (developmentEnv === "development") { postLogToBackend(`DataChannel closed for peer: ${peerId}`); } this.log("log", `Data channel with ${peerId} closed.`); @@ -440,7 +440,7 @@ export default class BaseWebRTC { roomId: this.roomId, }); } - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend( `peerId:${this.socket.id} Successfully joined room: ${response.roomId},isInitiator:${this.isInitiator},isInRoom:${this.isInRoom}` ); @@ -448,7 +448,7 @@ export default class BaseWebRTC { } else { this.isInRoom = false; this.roomId = null; - if (developmentEnv === "true") + if (developmentEnv === "development") postLogToBackend(`Failed to join room,message:${response.message}`); this.fireError("Failed to join room", { message: response.message }); reject(new Error(response.message)); @@ -499,10 +499,10 @@ export default class BaseWebRTC { ? data.byteLength : 0; - if (developmentEnv === "true") - postLogToBackend( - `sendToPeer - type: ${dataType}, size: ${dataSize}, bufferedAmount: ${dataChannel.bufferedAmount}` - ); + // if (developmentEnv === "development") + // postLogToBackend( + // `sendToPeer - type: ${dataType}, size: ${dataSize}, bufferedAmount: ${dataChannel.bufferedAmount}` + // ); dataChannel.send(data); return true;