@ahhaohho/s3-upload-sdk
Version:
S3 + CloudFront presigned URL SDK for AhhaOhho platform
366 lines (283 loc) • 8.28 kB
Markdown
S3 + CloudFront presigned URL SDK for AhhaOhho platform. This SDK allows clients to upload files directly to S3 using presigned URLs and get CloudFront URLs for the uploaded files.
- 🚀 Direct upload to S3 using presigned URLs
- ☁️ CloudFront URL support for fast content delivery
- 📦 TypeScript support with full type definitions
- 📊 Upload progress tracking
- 🔄 Multiple file upload support
- ✅ File validation (size, type)
- 🔐 Authorization token support
## Installation
```bash
npm install @ahhaohho/s3-upload-sdk
```
## Usage
### Basic Usage
```typescript
import { S3UploadClient } from '@ahhaohho/s3-upload-sdk';
// Initialize the client
const uploadClient = new S3UploadClient({
apiBaseUrl: 'https://api.ahhaohho.com',
authToken: 'your-auth-token', // Optional
});
// Upload a file
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
const file = fileInput.files[0];
try {
const result = await uploadClient.upload({
file: file,
folder: 'announcements', // Folder in S3
onProgress: (progress) => {
console.log(`Upload progress: ${progress}%`);
},
});
console.log('File uploaded successfully!');
console.log('CloudFront URL:', result.url);
console.log('S3 Key:', result.key);
console.log('File size:', result.size, 'bytes');
} catch (error) {
console.error('Upload failed:', error);
}
```
```typescript
const result = await uploadClient.upload({
file: file,
folder: 'profiles',
filename: 'user-avatar', // Will be: user-avatar-1234567890-abc123.jpg
contentType: 'image/jpeg',
});
```
```typescript
const files = Array.from(fileInput.files);
const results = await uploadClient.uploadMultiple(
files.map((file) => ({
file,
folder: 'announcements',
}))
);
const urls = results.map((r) => r.url);
console.log('All files uploaded:', urls);
```
```typescript
const validation = uploadClient.validateFile(
file,
5, // Max 5MB
['image/jpeg', 'image/png', 'image/gif'] // Allowed types
);
if (!validation.valid) {
alert(validation.error);
return;
}
// Proceed with upload
const result = await uploadClient.upload({
file,
folder: 'announcements',
});
```
```typescript
// Update token after login
uploadClient.setAuthToken(newToken);
```
```tsx
import React, { useState } from 'react';
import { S3UploadClient } from '@ahhaohho/s3-upload-sdk';
const uploadClient = new S3UploadClient({
apiBaseUrl: process.env.REACT_APP_API_URL,
});
function ImageUploader() {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [imageUrl, setImageUrl] = useState('');
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
// Validate file
const validation = uploadClient.validateFile(file, 10, [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
]);
if (!validation.valid) {
alert(validation.error);
return;
}
try {
setUploading(true);
setProgress(0);
const result = await uploadClient.upload({
file,
folder: 'announcements',
onProgress: (p) => setProgress(p),
});
setImageUrl(result.url);
alert('Upload successful!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed');
} finally {
setUploading(false);
}
};
return (
<div>
<input
type="file"
accept="image/*"
onChange={handleUpload}
disabled={uploading}
/>
{uploading && <div>Upload progress: {progress}%</div>}
{imageUrl && <img src={imageUrl} alt="Uploaded" />}
</div>
);
}
export default ImageUploader;
```
```vue
<template>
<div>
<input
type="file"
accept="image/*"
@change="handleUpload"
:disabled="uploading"
/>
<div v-if="uploading">Upload progress: {{ progress }}%</div>
<img v-if="imageUrl" :src="imageUrl" alt="Uploaded" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { S3UploadClient } from '@ahhaohho/s3-upload-sdk';
const uploadClient = new S3UploadClient({
apiBaseUrl: import.meta.env.VITE_API_URL,
});
const uploading = ref(false);
const progress = ref(0);
const imageUrl = ref('');
const handleUpload = async (e: Event) => {
const target = e.target as HTMLInputElement;
const file = target.files?.[0];
if (!file) return;
// Validate file
const validation = uploadClient.validateFile(file, 10, [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
]);
if (!validation.valid) {
alert(validation.error);
return;
}
try {
uploading.value = true;
progress.value = 0;
const result = await uploadClient.upload({
file,
folder: 'announcements',
onProgress: (p) => (progress.value = p),
});
imageUrl.value = result.url;
alert('Upload successful!');
} catch (error) {
console.error('Upload failed:', error);
alert('Upload failed');
} finally {
uploading.value = false;
}
};
</script>
```
```typescript
new S3UploadClient(config: S3UploadConfig)
```
**Parameters:**
- `config.apiBaseUrl` (string): Base URL of your API server
- `config.authToken` (string, optional): Authorization token
- `config.headers` (object, optional): Custom headers
#### Methods
##### `upload(options: UploadOptions): Promise<UploadResult>`
Upload a single file.
**Parameters:**
- `options.file` (File | Blob): File to upload
- `options.folder` (string): Folder path in S3
- `options.filename` (string, optional): Custom filename
- `options.contentType` (string, optional): Content type (MIME type)
- `options.onProgress` (function, optional): Progress callback
**Returns:**
- `url` (string): CloudFront URL
- `key` (string): S3 key
- `size` (number): File size in bytes
##### `uploadMultiple(files: UploadOptions[]): Promise<UploadResult[]>`
Upload multiple files in parallel.
##### `validateFile(file, maxSizeMB, allowedTypes): { valid: boolean, error?: string }`
Validate file before upload.
Update authorization token.
**Important**: AWS credentials are ONLY needed on your API server, NOT in the client SDK.
The SDK communicates with your API server, which then generates presigned URLs using AWS credentials.
You need to implement a presigned URL endpoint on your backend server:
```typescript
// Example Express endpoint
app.post('/communities/s3/presigned-url', async (req, res) => {
const { folder, filename, contentType } = req.body;
// Generate presigned URL using AWS SDK
const s3 = new S3Client({ region: 'ap-northeast-2' });
const key = `raw/${folder}/${filename}`;
const command = new PutObjectCommand({
Bucket: 'your-bucket-name',
Key: key,
ContentType: contentType,
});
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
const publicUrl = `https://your-cloudfront-domain.cloudfront.net/${key}`;
res.json({
type: 'success',
status: 200,
data: {
uploadUrl,
publicUrl,
key,
expiresIn: 300,
},
message: null,
});
});
```
See the [server implementation guide](./SERVER_SETUP.md) for detailed instructions.
```typescript
try {
const result = await uploadClient.upload({ file, folder: 'announcements' });
} catch (error) {
if (error.code === 'PRESIGNED_URL_ERROR') {
console.error('Failed to get presigned URL:', error.message);
} else if (error.code === 'S3_UPLOAD_ERROR') {
console.error('Failed to upload to S3:', error.message);
} else {
console.error('Unknown error:', error);
}
}
```
This package is written in TypeScript and includes type definitions.
```typescript
import type { UploadResult, UploadOptions } from '@ahhaohho/s3-upload-sdk';
```
ISC
Issues and pull requests are welcome!