Request Adapters
Request adapters are the bridge between ChunkFlow and your server. They handle all HTTP communication for file uploads.
Overview
ChunkFlow provides two built-in adapters:
- Fetch Adapter - Modern, Promise-based using Fetch API
- XHR Adapter - XMLHttpRequest-based with native progress tracking
You can also create custom adapters for specific needs.
Fetch Adapter
The Fetch Adapter uses the modern Fetch API for HTTP requests.
Installation
npm install @chunkflowjs/coreBasic Usage
import { createFetchAdapter } from "@chunkflowjs/core";
import { UploadManager } from "@chunkflowjs/core";
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
});
const manager = new UploadManager({
requestAdapter: adapter,
});Configuration Options
interface FetchAdapterOptions {
baseURL: string; // Required: API base URL
headers?: Record<string, string>; // Custom headers
timeout?: number; // Request timeout (default: 30000ms)
fetch?: typeof fetch; // Custom fetch implementation
onError?: (error: Error) => void; // Error callback
}Complete Example
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
headers: {
"Authorization": "Bearer your-token",
"X-Custom-Header": "value",
},
timeout: 60000, // 60 seconds
onError: (error) => {
console.error("Upload error:", error);
},
});When to Use Fetch Adapter
✅ Modern browsers only (no IE support needed)
✅ Prefer Promise-based API
✅ Need streaming response support
✅ Want simpler, more modern code
XHR Adapter
The XHR Adapter uses XMLHttpRequest for HTTP requests with native progress tracking.
Installation
npm install @chunkflowjs/coreBasic Usage
import { createXHRAdapter } from "@chunkflowjs/core";
import { UploadManager } from "@chunkflowjs/core";
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
});
const manager = new UploadManager({
requestAdapter: adapter,
});Configuration Options
interface XHRAdapterOptions {
baseURL: string; // Required: API base URL
headers?: Record<string, string>; // Custom headers
timeout?: number; // Request timeout (default: 30000ms)
withCredentials?: boolean; // Send cookies (default: false)
onUploadProgress?: (event: ProgressEvent) => void; // Progress callback
onError?: (error: Error) => void; // Error callback
}Complete Example
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
headers: {
"Authorization": "Bearer your-token",
},
timeout: 60000,
withCredentials: true, // Send cookies
onUploadProgress: (event) => {
if (event.lengthComputable) {
const percent = (event.loaded / event.total) * 100;
console.log(`Progress: ${percent.toFixed(2)}%`);
}
},
onError: (error) => {
console.error("Upload error:", error);
},
});When to Use XHR Adapter
✅ Need native upload progress tracking
✅ Need to support older browsers (IE10+)
✅ Need request abort functionality
✅ Working with legacy codebases using XHR
Comparison
| Feature | Fetch Adapter | XHR Adapter |
|---|---|---|
| Upload Progress | ⚠️ Requires polyfill | ✅ Native |
| Browser Support | Modern browsers | IE10+ |
| API Style | Promise-based | Callback-based |
| Request Abort | ✅ AbortController | ✅ Native |
| Streaming | ✅ Yes | ❌ No |
| Code Complexity | Simple | Medium |
React Integration
With Fetch Adapter
import { createFetchAdapter } from "@chunkflowjs/core";
import { UploadProvider } from "@chunkflowjs/upload-client-react";
import { UploadButton, UploadList } from "@chunkflowjs/upload-component-react";
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
});
function App() {
return (
<UploadProvider requestAdapter={adapter}>
<UploadButton accept="*" multiple>
Select Files
</UploadButton>
<UploadList />
</UploadProvider>
);
}With XHR Adapter
import { createXHRAdapter } from "@chunkflowjs/core";
import { UploadProvider } from "@chunkflowjs/upload-client-react";
import { UploadButton, UploadList } from "@chunkflowjs/upload-component-react";
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
onUploadProgress: (event) => {
if (event.lengthComputable) {
console.log(`Progress: ${(event.loaded / event.total) * 100}%`);
}
},
});
function App() {
return (
<UploadProvider requestAdapter={adapter}>
<UploadButton accept="*" multiple>
Select Files
</UploadButton>
<UploadList />
</UploadProvider>
);
}Vue Integration
With Fetch Adapter
import { createApp } from "vue";
import { UploadPlugin } from "@chunkflowjs/upload-client-vue";
import { createFetchAdapter } from "@chunkflowjs/core";
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
});
const app = createApp(App);
app.use(UploadPlugin, { requestAdapter: adapter });With XHR Adapter
import { createApp } from "vue";
import { UploadPlugin } from "@chunkflowjs/upload-client-vue";
import { createXHRAdapter } from "@chunkflowjs/core";
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
onUploadProgress: (event) => {
if (event.lengthComputable) {
console.log(`Progress: ${(event.loaded / event.total) * 100}%`);
}
},
});
const app = createApp(App);
app.use(UploadPlugin, { requestAdapter: adapter });Custom Adapter
You can create a custom adapter by implementing the RequestAdapter interface:
import { RequestAdapter } from "@chunkflowjs/protocol";
class CustomAdapter implements RequestAdapter {
constructor(private baseURL: string) {}
async createFile(request) {
// Implement file creation
const response = await fetch(`${this.baseURL}/upload/create`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
return response.json();
}
async verifyHash(request) {
// Implement hash verification
const response = await fetch(`${this.baseURL}/upload/verify`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
return response.json();
}
async uploadChunk(request) {
// Implement chunk upload
const formData = new FormData();
formData.append("uploadToken", request.uploadToken);
formData.append("chunkIndex", request.chunkIndex.toString());
formData.append("chunkHash", request.chunkHash);
formData.append("chunk", request.chunk);
const response = await fetch(`${this.baseURL}/upload/chunk`, {
method: "POST",
body: formData,
});
return response.json();
}
async mergeFile(request) {
// Implement file merge
const response = await fetch(`${this.baseURL}/upload/merge`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(request),
});
return response.json();
}
}
// Use custom adapter
const adapter = new CustomAdapter("http://localhost:3000/api");
const manager = new UploadManager({ requestAdapter: adapter });Server Requirements
All adapters expect your server to implement these endpoints:
POST /upload/create
Create a new upload session.
Request:
{
"fileName": "video.mp4",
"fileSize": 104857600,
"fileType": "video/mp4",
"preferredChunkSize": 2097152
}Response:
{
"uploadToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"negotiatedChunkSize": 2097152
}POST /upload/verify
Verify file and chunk hashes.
Request:
{
"uploadToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"fileHash": "abc123...",
"chunkHashes": ["hash1", "hash2", "hash3"]
}Response:
{
"fileExists": false,
"existingChunks": [0, 2],
"missingChunks": [1, 3, 4]
}POST /upload/chunk
Upload a single chunk (FormData).
Request:
uploadToken: JWT tokenchunkIndex: Chunk index (0-based)chunkHash: MD5 hash of chunkchunk: Chunk file data (Blob)
Response:
{
"success": true,
"chunkHash": "hash1"
}POST /upload/merge
Merge all chunks into final file.
Request:
{
"uploadToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"fileHash": "abc123...",
"chunkHashes": ["hash1", "hash2", "hash3"]
}Response:
{
"success": true,
"fileUrl": "/upload/files/abc123",
"fileId": "abc123"
}Error Handling
Both adapters provide error callbacks:
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
onError: (error) => {
// Network errors
if (error.message === "Network error") {
console.error("Network connection failed");
}
// Timeout errors
if (error.message.includes("timeout")) {
console.error("Request timed out");
}
// HTTP errors
if (error.message.startsWith("HTTP")) {
console.error("Server error:", error.message);
}
},
});Best Practices
1. Set Appropriate Timeout
Adjust timeout based on expected file sizes and network conditions:
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
timeout: 60000, // 60 seconds for large files
});2. Add Authentication
Include auth tokens in headers:
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
headers: {
"Authorization": `Bearer ${getAuthToken()}`,
},
});3. Handle Progress (XHR only)
Provide user feedback during uploads:
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
onUploadProgress: (event) => {
if (event.lengthComputable) {
updateProgressBar((event.loaded / event.total) * 100);
}
},
});4. Implement Error Recovery
Handle errors gracefully and provide retry options:
const adapter = createFetchAdapter({
baseURL: "http://localhost:3000/api",
onError: (error) => {
logError(error);
showRetryButton();
},
});Troubleshooting
CORS Issues
If you encounter CORS errors, ensure your server has proper CORS headers:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: trueFor XHR with credentials:
const adapter = createXHRAdapter({
baseURL: "http://localhost:3000/api",
withCredentials: true,
});Timeout Issues
If requests are timing out:
- Increase timeout for large files:
timeout: 120000 // 2 minutes- Consider reducing chunk size:
const manager = new UploadManager({
requestAdapter: adapter,
defaultChunkSize: 1 * 1024 * 1024, // 1MB chunks
});