Implementing Resumable File Uploads in Vue.js with TypeScript
This challenge focuses on building a robust file upload component in Vue.js that supports resuming interrupted uploads. This is a crucial feature for handling large files and unstable network conditions, significantly improving user experience by preventing data loss and unnecessary re-uploads.
Problem Description
You are tasked with creating a Vue.js component that allows users to upload files. The core requirement is to implement "resumable uploads." This means that if an upload is interrupted (e.g., due to network disconnection, browser closure, or manual cancellation), the user should be able to resume the upload from where it left off the next time they attempt to upload the same file.
Key Requirements:
- File Selection: Allow users to select one or more files for upload.
- Upload Process: Implement the logic to upload files in chunks.
- Resumption Mechanism:
- When an upload starts, generate a unique identifier for the file and the upload session.
- If the upload is interrupted, store the state (e.g., which chunks have been uploaded) associated with this unique identifier. This state can be stored in
localStorageor a similar client-side mechanism. - When the user attempts to upload the same file again, check for existing upload state. If found, resume the upload from the next un-uploaded chunk.
- Progress Tracking: Display a clear progress indicator for each file being uploaded, showing the percentage complete.
- User Feedback: Provide feedback on upload status (e.g., "Uploading...", "Paused," "Resumed," "Completed," "Error").
- Chunking Strategy: Implement a strategy for dividing files into manageable chunks.
- Backend Interaction (Simulated): For this challenge, you will simulate the backend interaction. You do not need to set up a real server. Instead, simulate API calls for:
- Initiating an upload (which returns a unique upload ID).
- Uploading a specific chunk of a file.
- Checking the status of an upload (to see which chunks are already uploaded).
Expected Behavior:
- A user selects a large file.
- The file starts uploading in chunks.
- The user manually pauses the upload or simulates a network interruption.
- The component should reflect the paused state and store the progress.
- The user reopens the application or attempts to upload the same file again.
- The component detects the previous progress and automatically resumes the upload from the last successfully uploaded chunk.
- The upload completes successfully.
Edge Cases:
- Uploading very large files.
- Network interruptions during chunk uploads.
- User closing the browser tab/window during an upload.
- User cancelling an upload and then re-initiating it.
- Simultaneous uploads of multiple files.
- File integrity: Ensuring that the file hasn't been modified since the last upload attempt (though for this simulation, we'll assume the file is the same if its name and size match).
Examples
Example 1: Successful Upload and Resumption
Scenario: A user uploads large_document.pdf (100MB). The upload starts, and 30% is uploaded before the user's internet connection drops. The user reconnects and tries to upload the same file again.
Input:
- File:
large_document.pdf(size: 100MB) - Chunk size: 1MB
- Simulated API:
POST /upload/init->{ uploadId: "abc-123", totalChunks: 100 }POST /upload/chunk(withuploadId,chunkIndex,chunkData)GET /upload/status?uploadId=abc-123->{ uploadedChunks: [0, 1, ..., 29] }
Output: The component UI will show:
- File selected:
large_document.pdf - Upload status: "Uploading... 30%"
- Network disconnect simulated.
- Upload status changes to "Paused" or shows an error. Progress is saved.
- User re-selects or attempts to upload the same file.
- Component detects previous progress.
- Upload status: "Resuming... 30%"
- Upload continues from chunk 30.
- Upload status: "Uploading... 100%"
- Upload status: "Completed"
Example 2: File Modification
Scenario: A user starts uploading report.docx. The upload is interrupted. Later, the user modifies report.docx (e.g., changes content, which might affect file size or hash, though for simplicity we'll rely on size). When they try to upload again, the component should recognize it as a potentially different file.
Input:
- File 1:
report.docx(original size: 5MB) - Chunk size: 1MB
- Simulated API:
POST /upload/init->{ uploadId: "def-456", totalChunks: 5 }- Uploads chunks 0, 1.
- File
report.docxis modified, new size: 5.5MB. - User attempts to upload the modified
report.docx.
Output: The component should:
- Detect that the file size (or potentially a hash, if implemented) has changed compared to the stored upload state for
report.docx. - Start a new upload from scratch, ignoring the previous partial upload.
- Upload status: "Uploading... 100%"
- Upload status: "Completed"
Constraints
- File Size: Assume files can be up to 1GB.
- Chunk Size: Implement a configurable chunk size, with a default of 5MB.
- API Simulation: Simulate API responses with a delay of 100-500ms to mimic network latency. Use
setTimeoutandPromisefor this. - State Storage: Use
localStorageto store the upload progress information for each file. The key inlocalStorageshould be derived from the file's name and size (or a combination that uniquely identifies it). - Performance: The UI should remain responsive during chunk uploads. Avoid blocking the main thread.
- TypeScript: The entire solution must be written in TypeScript.
Notes
- You will need to implement the logic for chunking files using JavaScript's
File.slice()method. - Consider how to uniquely identify a file for resumption. A combination of filename and file size is a good starting point. For more robust solutions, you might consider calculating a file hash, but this adds complexity.
- The simulated API calls should return Promises. You'll need to handle these Promises effectively.
- Think about how to handle concurrent uploads. Each upload should be independent.
- Error handling is critical. What happens if a chunk upload fails repeatedly? Implement a retry mechanism with a limit.
- A clean-up mechanism for stale
localStorageentries might be beneficial in a real-world scenario, but is not strictly required for this challenge. - Focus on the core logic of chunking, state management, and resumption. A polished UI is secondary to functionality for this problem.