@d3oxy/s3-pilot
Version:
A TypeScript wrapper for AWS S3 with support for multiple clients, buckets, and secure file downloads.
270 lines (232 loc) • 9.31 kB
text/typescript
import { S3Pilot, S3ClientSettings, S3ClientsSetup } from "../index";
// Example configuration
const s3Pilot = new S3Pilot<
S3ClientsSetup<{
main: S3ClientSettings<"my-private-bucket" | "public-assets">;
}>
>({
main: {
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
buckets: ["my-private-bucket", "public-assets"],
},
});
/**
* Example 1: Server-Side File Download (Recommended for CORS issues)
*
* This approach downloads the file to your server and streams it to the client.
* This avoids CORS issues and gives you full control over access permissions.
*/
async function serverSideDownloadExample() {
try {
// Download file content as Buffer
const fileResponse = await s3Pilot.getFile("main", "my-private-bucket", {
key: "documents/contract.pdf",
});
console.log("File downloaded successfully!");
console.log("Content type:", fileResponse.contentType);
console.log("File size:", fileResponse.contentLength, "bytes");
console.log("Last modified:", fileResponse.lastModified);
console.log("ETag:", fileResponse.etag);
// In a real API endpoint, you would do:
// res.setHeader('Content-Type', fileResponse.contentType || 'application/octet-stream');
// res.setHeader('Content-Disposition', 'attachment; filename="contract.pdf"');
// res.setHeader('Content-Length', fileResponse.contentLength?.toString() || '0');
// res.send(fileResponse.buffer);
} catch (error) {
console.error("Failed to download file:", error);
}
}
/**
* Example 2: Streaming Large Files
*
* For files larger than 100MB, use streaming to avoid memory issues.
*/
async function streamingDownloadExample() {
try {
// Get file as stream
const streamResponse = await s3Pilot.getFileStream("main", "my-private-bucket", {
key: "large-files/video.mp4",
});
console.log("Stream created successfully!");
console.log("Content type:", streamResponse.contentType);
console.log("File size:", streamResponse.contentLength, "bytes");
// In a real API endpoint, you would do:
// res.setHeader('Content-Type', streamResponse.contentType || 'application/octet-stream');
// res.setHeader('Content-Disposition', 'attachment; filename="video.mp4"');
// res.setHeader('Content-Length', streamResponse.contentLength?.toString() || '0');
// streamResponse.stream.pipe(res);
} catch (error) {
console.error("Failed to create stream:", error);
}
}
/**
* Example 3: Enhanced Signed URL with Download Headers
*
* Generate signed URLs that force browser download behavior.
* This can help avoid CORS issues when properly configured.
*/
async function enhancedSignedUrlExample() {
try {
// Generate signed URL with download headers
const signedUrl = await s3Pilot.generateSignedUrl("main", "my-private-bucket", {
key: "documents/report.pdf",
expiresIn: 3600, // 1 hour
responseContentDisposition: 'attachment; filename="report.pdf"',
responseContentType: "application/pdf",
responseCacheControl: "no-cache",
});
console.log("Enhanced signed URL generated:");
console.log(signedUrl);
// The browser will automatically download the file when accessing this URL
// because of the 'attachment' content disposition header
} catch (error) {
console.error("Failed to generate signed URL:", error);
}
}
/**
* Example 4: Basic Signed URL (for viewing files)
*
* Generate signed URLs for viewing files in the browser.
*/
async function basicSignedUrlExample() {
try {
// Generate basic signed URL
const signedUrl = await s3Pilot.generateSignedUrl("main", "my-private-bucket", {
key: "images/photo.jpg",
expiresIn: 1800, // 30 minutes
});
console.log("Basic signed URL generated:");
console.log(signedUrl);
// This URL can be used to view the image in the browser
} catch (error) {
console.error("Failed to generate signed URL:", error);
}
}
/**
* Example 5: Complete API Endpoint Example
*
* This shows how to implement a secure file download endpoint
* that handles different file types and sizes.
*/
async function apiEndpointExample() {
// Simulating Express.js/Hono.js endpoint
const fileKey = "documents/important-document.pdf";
const fileName = "important-document.pdf";
const fileType = "application/pdf";
try {
// Check if file exists and get metadata
const fileResponse = await s3Pilot.getFile("main", "my-private-bucket", {
key: fileKey,
});
// Set appropriate headers
const headers = {
"Content-Type": fileResponse.contentType || "application/octet-stream",
"Content-Disposition": `attachment; filename="${fileName}"`,
"Content-Length": fileResponse.contentLength?.toString() || "0",
"Cache-Control": "no-cache",
};
console.log("File download headers:", headers);
console.log("File content length:", fileResponse.contentLength);
// In a real endpoint:
// res.writeHead(200, headers);
// res.end(fileResponse.buffer);
} catch (error) {
console.error("File not found or access denied");
// res.status(404).json({ error: 'File not found' });
}
}
/**
* Example 6: Large File Streaming Endpoint
*
* For files larger than 100MB, use streaming to avoid memory issues.
*/
async function largeFileStreamingExample() {
const fileKey = "large-files/database-backup.zip";
try {
const streamResponse = await s3Pilot.getFileStream("main", "my-private-bucket", {
key: fileKey,
});
const headers = {
"Content-Type": streamResponse.contentType || "application/octet-stream",
"Content-Disposition": `attachment; filename="${fileKey.split("/").pop()}"`,
"Content-Length": streamResponse.contentLength?.toString() || "0",
"Cache-Control": "no-cache",
};
console.log("Streaming file with headers:", headers);
// In a real endpoint:
// res.writeHead(200, headers);
// streamResponse.stream.pipe(res);
} catch (error) {
console.error("Failed to stream file:", error);
// res.status(404).json({ error: 'File not found' });
}
}
/**
* Example 7: Conditional Download Based on File Size
*
* Choose between buffer download and streaming based on file size.
*/
async function conditionalDownloadExample() {
const fileKey = "documents/document.pdf";
const sizeThreshold = 50 * 1024 * 1024; // 50MB
try {
// First, get file metadata to check size
const fileResponse = await s3Pilot.getFile("main", "my-private-bucket", {
key: fileKey,
});
if (fileResponse.contentLength && fileResponse.contentLength > sizeThreshold) {
// Use streaming for large files
console.log("File is large, using streaming...");
const streamResponse = await s3Pilot.getFileStream("main", "my-private-bucket", {
key: fileKey,
});
// streamResponse.stream.pipe(res);
console.log("Streaming large file");
} else {
// Use buffer for smaller files
console.log("File is small, using buffer...");
// res.send(fileResponse.buffer);
console.log("Sending file buffer");
}
} catch (error) {
console.error("Failed to download file:", error);
}
}
// Run examples
async function runExamples() {
console.log("=== S3 Pilot File Download Examples ===\n");
console.log("1. Server-Side Download Example:");
await serverSideDownloadExample();
console.log();
console.log("2. Streaming Download Example:");
await streamingDownloadExample();
console.log();
console.log("3. Enhanced Signed URL Example:");
await enhancedSignedUrlExample();
console.log();
console.log("4. Basic Signed URL Example:");
await basicSignedUrlExample();
console.log();
console.log("5. API Endpoint Example:");
await apiEndpointExample();
console.log();
console.log("6. Large File Streaming Example:");
await largeFileStreamingExample();
console.log();
console.log("7. Conditional Download Example:");
await conditionalDownloadExample();
console.log();
}
// Uncomment to run examples (requires AWS credentials)
// runExamples().catch(console.error);
export {
serverSideDownloadExample,
streamingDownloadExample,
enhancedSignedUrlExample,
basicSignedUrlExample,
apiEndpointExample,
largeFileStreamingExample,
conditionalDownloadExample,
};