UNPKG

@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
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, };