@oxyhq/services 5.10.5 → 5.10.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/OxyServices.js +1 -1
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/index.js +31 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/s3FileManager.js +243 -0
- package/lib/commonjs/utils/s3FileManager.js.map +1 -0
- package/lib/commonjs/utils/s3FileManagerExample.js +407 -0
- package/lib/commonjs/utils/s3FileManagerExample.js.map +1 -0
- package/lib/commonjs/utils/s3FileManagerRN.js +274 -0
- package/lib/commonjs/utils/s3FileManagerRN.js.map +1 -0
- package/lib/module/core/OxyServices.js +1 -1
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/s3FileManager.js +237 -0
- package/lib/module/utils/s3FileManager.js.map +1 -0
- package/lib/module/utils/s3FileManagerExample.js +400 -0
- package/lib/module/utils/s3FileManagerExample.js.map +1 -0
- package/lib/module/utils/s3FileManagerRN.js +268 -0
- package/lib/module/utils/s3FileManagerRN.js.map +1 -0
- package/lib/typescript/core/OxyServices.d.ts +2 -2
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/utils/s3FileManager.d.ts +81 -0
- package/lib/typescript/utils/s3FileManager.d.ts.map +1 -0
- package/lib/typescript/utils/s3FileManagerExample.d.ts +87 -0
- package/lib/typescript/utils/s3FileManagerExample.d.ts.map +1 -0
- package/lib/typescript/utils/s3FileManagerRN.d.ts +104 -0
- package/lib/typescript/utils/s3FileManagerRN.d.ts.map +1 -0
- package/package.json +3 -1
- package/src/core/OxyServices.ts +2 -2
- package/src/index.ts +17 -1
- package/src/utils/s3FileManager.ts +281 -0
- package/src/utils/s3FileManagerExample.ts +432 -0
- package/src/utils/s3FileManagerRN.ts +322 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
|
|
2
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
3
|
+
|
|
4
|
+
export interface S3Config {
|
|
5
|
+
region: string;
|
|
6
|
+
accessKeyId: string;
|
|
7
|
+
secretAccessKey: string;
|
|
8
|
+
bucketName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface UploadOptions {
|
|
12
|
+
contentType?: string;
|
|
13
|
+
metadata?: Record<string, string>;
|
|
14
|
+
publicRead?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FileInfo {
|
|
18
|
+
key: string;
|
|
19
|
+
size: number;
|
|
20
|
+
lastModified: Date;
|
|
21
|
+
contentType?: string;
|
|
22
|
+
metadata?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class S3FileManager {
|
|
26
|
+
private s3Client: S3Client;
|
|
27
|
+
private bucketName: string;
|
|
28
|
+
|
|
29
|
+
constructor(config: S3Config) {
|
|
30
|
+
this.s3Client = new S3Client({
|
|
31
|
+
region: config.region,
|
|
32
|
+
credentials: {
|
|
33
|
+
accessKeyId: config.accessKeyId,
|
|
34
|
+
secretAccessKey: config.secretAccessKey,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
this.bucketName = config.bucketName;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Upload a file to S3
|
|
42
|
+
*/
|
|
43
|
+
async uploadFile(
|
|
44
|
+
key: string,
|
|
45
|
+
file: File | Buffer | string,
|
|
46
|
+
options: UploadOptions = {}
|
|
47
|
+
): Promise<string> {
|
|
48
|
+
let body: Buffer | string;
|
|
49
|
+
let contentType = options.contentType;
|
|
50
|
+
|
|
51
|
+
if (file instanceof File) {
|
|
52
|
+
body = await this.fileToBuffer(file);
|
|
53
|
+
contentType = contentType || file.type;
|
|
54
|
+
} else if (typeof file === 'string') {
|
|
55
|
+
body = file;
|
|
56
|
+
contentType = contentType || 'text/plain';
|
|
57
|
+
} else {
|
|
58
|
+
body = file;
|
|
59
|
+
contentType = contentType || 'application/octet-stream';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const command = new PutObjectCommand({
|
|
63
|
+
Bucket: this.bucketName,
|
|
64
|
+
Key: key,
|
|
65
|
+
Body: body,
|
|
66
|
+
ContentType: contentType,
|
|
67
|
+
Metadata: options.metadata,
|
|
68
|
+
ACL: options.publicRead ? 'public-read' : 'private',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await this.s3Client.send(command);
|
|
72
|
+
return `https://${this.bucketName}.s3.amazonaws.com/${key}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Download a file from S3
|
|
77
|
+
*/
|
|
78
|
+
async downloadFile(key: string): Promise<Buffer> {
|
|
79
|
+
const command = new GetObjectCommand({
|
|
80
|
+
Bucket: this.bucketName,
|
|
81
|
+
Key: key,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const response = await this.s3Client.send(command);
|
|
85
|
+
|
|
86
|
+
if (!response.Body) {
|
|
87
|
+
throw new Error('File not found or empty');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert stream to buffer
|
|
91
|
+
const chunks: Uint8Array[] = [];
|
|
92
|
+
const reader = response.Body.transformToWebStream().getReader();
|
|
93
|
+
|
|
94
|
+
while (true) {
|
|
95
|
+
const { done, value } = await reader.read();
|
|
96
|
+
if (done) break;
|
|
97
|
+
chunks.push(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return Buffer.concat(chunks);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Delete a file from S3
|
|
105
|
+
*/
|
|
106
|
+
async deleteFile(key: string): Promise<void> {
|
|
107
|
+
const command = new DeleteObjectCommand({
|
|
108
|
+
Bucket: this.bucketName,
|
|
109
|
+
Key: key,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await this.s3Client.send(command);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate a presigned URL for file upload
|
|
117
|
+
*/
|
|
118
|
+
async getPresignedUploadUrl(
|
|
119
|
+
key: string,
|
|
120
|
+
contentType: string,
|
|
121
|
+
expiresIn: number = 3600
|
|
122
|
+
): Promise<string> {
|
|
123
|
+
const command = new PutObjectCommand({
|
|
124
|
+
Bucket: this.bucketName,
|
|
125
|
+
Key: key,
|
|
126
|
+
ContentType: contentType,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return getSignedUrl(this.s3Client, command, { expiresIn });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate a presigned URL for file download
|
|
134
|
+
*/
|
|
135
|
+
async getPresignedDownloadUrl(
|
|
136
|
+
key: string,
|
|
137
|
+
expiresIn: number = 3600
|
|
138
|
+
): Promise<string> {
|
|
139
|
+
const command = new GetObjectCommand({
|
|
140
|
+
Bucket: this.bucketName,
|
|
141
|
+
Key: key,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return getSignedUrl(this.s3Client, command, { expiresIn });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* List files in a directory
|
|
149
|
+
*/
|
|
150
|
+
async listFiles(prefix: string = '', maxKeys: number = 1000): Promise<FileInfo[]> {
|
|
151
|
+
const command = new ListObjectsV2Command({
|
|
152
|
+
Bucket: this.bucketName,
|
|
153
|
+
Prefix: prefix,
|
|
154
|
+
MaxKeys: maxKeys,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const response = await this.s3Client.send(command);
|
|
158
|
+
|
|
159
|
+
if (!response.Contents) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return response.Contents.map((item) => ({
|
|
164
|
+
key: item.Key!,
|
|
165
|
+
size: item.Size || 0,
|
|
166
|
+
lastModified: item.LastModified!,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if a file exists
|
|
172
|
+
*/
|
|
173
|
+
async fileExists(key: string): Promise<boolean> {
|
|
174
|
+
try {
|
|
175
|
+
const command = new GetObjectCommand({
|
|
176
|
+
Bucket: this.bucketName,
|
|
177
|
+
Key: key,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await this.s3Client.send(command);
|
|
181
|
+
return true;
|
|
182
|
+
} catch (error: any) {
|
|
183
|
+
if (error.name === 'NoSuchKey') {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get file metadata
|
|
192
|
+
*/
|
|
193
|
+
async getFileMetadata(key: string): Promise<FileInfo | null> {
|
|
194
|
+
try {
|
|
195
|
+
const command = new GetObjectCommand({
|
|
196
|
+
Bucket: this.bucketName,
|
|
197
|
+
Key: key,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const response = await this.s3Client.send(command);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
key,
|
|
204
|
+
size: parseInt(response.ContentLength?.toString() || '0'),
|
|
205
|
+
lastModified: response.LastModified!,
|
|
206
|
+
contentType: response.ContentType,
|
|
207
|
+
metadata: response.Metadata,
|
|
208
|
+
};
|
|
209
|
+
} catch (error: any) {
|
|
210
|
+
if (error.name === 'NoSuchKey') {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Convert File object to Buffer (for React Native compatibility)
|
|
219
|
+
*/
|
|
220
|
+
private async fileToBuffer(file: File): Promise<Buffer> {
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const reader = new FileReader();
|
|
223
|
+
reader.onload = () => {
|
|
224
|
+
if (reader.result instanceof ArrayBuffer) {
|
|
225
|
+
resolve(Buffer.from(reader.result));
|
|
226
|
+
} else {
|
|
227
|
+
reject(new Error('Failed to read file'));
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
reader.onerror = () => reject(reader.error);
|
|
231
|
+
reader.readAsArrayBuffer(file);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Upload multiple files
|
|
237
|
+
*/
|
|
238
|
+
async uploadMultipleFiles(
|
|
239
|
+
files: Array<{ key: string; file: File | Buffer | string; options?: UploadOptions }>
|
|
240
|
+
): Promise<string[]> {
|
|
241
|
+
const uploadPromises = files.map(({ key, file, options }) =>
|
|
242
|
+
this.uploadFile(key, file, options)
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return Promise.all(uploadPromises);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Delete multiple files
|
|
250
|
+
*/
|
|
251
|
+
async deleteMultipleFiles(keys: string[]): Promise<void> {
|
|
252
|
+
const deletePromises = keys.map(key => this.deleteFile(key));
|
|
253
|
+
await Promise.all(deletePromises);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Copy file from one key to another
|
|
258
|
+
*/
|
|
259
|
+
async copyFile(sourceKey: string, destinationKey: string): Promise<void> {
|
|
260
|
+
const command = new PutObjectCommand({
|
|
261
|
+
Bucket: this.bucketName,
|
|
262
|
+
Key: destinationKey,
|
|
263
|
+
Body: await this.downloadFile(sourceKey),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
await this.s3Client.send(command);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Move file (copy + delete)
|
|
271
|
+
*/
|
|
272
|
+
async moveFile(sourceKey: string, destinationKey: string): Promise<void> {
|
|
273
|
+
await this.copyFile(sourceKey, destinationKey);
|
|
274
|
+
await this.deleteFile(sourceKey);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Export a factory function for easier configuration
|
|
279
|
+
export function createS3FileManager(config: S3Config): S3FileManager {
|
|
280
|
+
return new S3FileManager(config);
|
|
281
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3 File Management Examples
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates how to use the S3 file management functionality
|
|
5
|
+
* in both web and React Native environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
S3FileManager,
|
|
10
|
+
createS3FileManager,
|
|
11
|
+
S3Config,
|
|
12
|
+
UploadOptions
|
|
13
|
+
} from './s3FileManager';
|
|
14
|
+
import {
|
|
15
|
+
S3FileManagerRN,
|
|
16
|
+
createS3FileManagerRN,
|
|
17
|
+
RNFile
|
|
18
|
+
} from './s3FileManagerRN';
|
|
19
|
+
|
|
20
|
+
// Example configuration
|
|
21
|
+
const s3Config: S3Config = {
|
|
22
|
+
region: 'us-east-1',
|
|
23
|
+
accessKeyId: 'your-access-key-id',
|
|
24
|
+
secretAccessKey: 'your-secret-access-key',
|
|
25
|
+
bucketName: 'your-bucket-name',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// WEB ENVIRONMENT EXAMPLES
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
export class WebFileManagerExample {
|
|
33
|
+
private s3Manager: S3FileManager;
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
this.s3Manager = createS3FileManager(s3Config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Example: Upload a file from web form
|
|
41
|
+
*/
|
|
42
|
+
async uploadFileFromForm(fileInput: HTMLInputElement): Promise<string> {
|
|
43
|
+
const file = fileInput.files?.[0];
|
|
44
|
+
if (!file) {
|
|
45
|
+
throw new Error('No file selected');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const key = `uploads/${Date.now()}-${file.name}`;
|
|
49
|
+
const uploadOptions: UploadOptions = {
|
|
50
|
+
contentType: file.type,
|
|
51
|
+
metadata: {
|
|
52
|
+
originalName: file.name,
|
|
53
|
+
uploadedBy: 'web-user',
|
|
54
|
+
uploadedAt: new Date().toISOString(),
|
|
55
|
+
},
|
|
56
|
+
publicRead: false,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const url = await this.s3Manager.uploadFile(key, file, uploadOptions);
|
|
61
|
+
console.log('File uploaded successfully:', url);
|
|
62
|
+
return url;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Upload failed:', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Example: Upload multiple files
|
|
71
|
+
*/
|
|
72
|
+
async uploadMultipleFiles(fileList: FileList): Promise<string[]> {
|
|
73
|
+
const files = Array.from(fileList).map(file => ({
|
|
74
|
+
key: `uploads/${Date.now()}-${file.name}`,
|
|
75
|
+
file,
|
|
76
|
+
options: {
|
|
77
|
+
contentType: file.type,
|
|
78
|
+
metadata: {
|
|
79
|
+
originalName: file.name,
|
|
80
|
+
uploadedBy: 'web-user',
|
|
81
|
+
uploadedAt: new Date().toISOString(),
|
|
82
|
+
},
|
|
83
|
+
publicRead: false,
|
|
84
|
+
} as UploadOptions,
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const urls = await this.s3Manager.uploadMultipleFiles(files);
|
|
89
|
+
console.log('Files uploaded successfully:', urls);
|
|
90
|
+
return urls;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Upload failed:', error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Example: Download a file
|
|
99
|
+
*/
|
|
100
|
+
async downloadFile(key: string): Promise<void> {
|
|
101
|
+
try {
|
|
102
|
+
const buffer = await this.s3Manager.downloadFile(key);
|
|
103
|
+
|
|
104
|
+
// Create a blob and download link
|
|
105
|
+
const blob = new Blob([buffer]);
|
|
106
|
+
const url = URL.createObjectURL(blob);
|
|
107
|
+
const a = document.createElement('a');
|
|
108
|
+
a.href = url;
|
|
109
|
+
a.download = key.split('/').pop() || 'download';
|
|
110
|
+
document.body.appendChild(a);
|
|
111
|
+
a.click();
|
|
112
|
+
document.body.removeChild(a);
|
|
113
|
+
URL.revokeObjectURL(url);
|
|
114
|
+
|
|
115
|
+
console.log('File downloaded successfully');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('Download failed:', error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Example: Get presigned URLs
|
|
124
|
+
*/
|
|
125
|
+
async getPresignedUrls(key: string): Promise<{ upload: string; download: string }> {
|
|
126
|
+
try {
|
|
127
|
+
const uploadUrl = await this.s3Manager.getPresignedUploadUrl(key, 'application/octet-stream');
|
|
128
|
+
const downloadUrl = await this.s3Manager.getPresignedDownloadUrl(key);
|
|
129
|
+
|
|
130
|
+
return { upload: uploadUrl, download: downloadUrl };
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Failed to get presigned URLs:', error);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Example: List files with pagination
|
|
139
|
+
*/
|
|
140
|
+
async listFiles(prefix: string = '', maxKeys: number = 50): Promise<void> {
|
|
141
|
+
try {
|
|
142
|
+
const files = await this.s3Manager.listFiles(prefix, maxKeys);
|
|
143
|
+
console.log('Files found:', files);
|
|
144
|
+
|
|
145
|
+
files.forEach(file => {
|
|
146
|
+
console.log(`- ${file.key} (${file.size} bytes, modified: ${file.lastModified})`);
|
|
147
|
+
});
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Failed to list files:', error);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Example: File operations (copy, move, delete)
|
|
156
|
+
*/
|
|
157
|
+
async performFileOperations(): Promise<void> {
|
|
158
|
+
const sourceKey = 'uploads/source-file.txt';
|
|
159
|
+
const destKey = 'uploads/destination-file.txt';
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Copy file
|
|
163
|
+
await this.s3Manager.copyFile(sourceKey, destKey);
|
|
164
|
+
console.log('File copied successfully');
|
|
165
|
+
|
|
166
|
+
// Move file
|
|
167
|
+
await this.s3Manager.moveFile(destKey, 'uploads/moved-file.txt');
|
|
168
|
+
console.log('File moved successfully');
|
|
169
|
+
|
|
170
|
+
// Delete file
|
|
171
|
+
await this.s3Manager.deleteFile('uploads/moved-file.txt');
|
|
172
|
+
console.log('File deleted successfully');
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('File operation failed:', error);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// REACT NATIVE ENVIRONMENT EXAMPLES
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
export class ReactNativeFileManagerExample {
|
|
185
|
+
private s3Manager: S3FileManagerRN;
|
|
186
|
+
|
|
187
|
+
constructor() {
|
|
188
|
+
this.s3Manager = createS3FileManagerRN(s3Config);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Example: Upload a file from React Native
|
|
193
|
+
*/
|
|
194
|
+
async uploadFileFromRN(fileInfo: RNFile): Promise<string> {
|
|
195
|
+
const key = `uploads/${Date.now()}-${fileInfo.name}`;
|
|
196
|
+
const uploadOptions: UploadOptions = {
|
|
197
|
+
contentType: fileInfo.type || 'application/octet-stream',
|
|
198
|
+
metadata: {
|
|
199
|
+
originalName: fileInfo.name,
|
|
200
|
+
uploadedBy: 'rn-user',
|
|
201
|
+
uploadedAt: new Date().toISOString(),
|
|
202
|
+
fileSize: fileInfo.size?.toString() || '0',
|
|
203
|
+
},
|
|
204
|
+
publicRead: false,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const url = await this.s3Manager.uploadFile(key, fileInfo, uploadOptions);
|
|
209
|
+
console.log('File uploaded successfully:', url);
|
|
210
|
+
return url;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Upload failed:', error);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Example: Upload image with size validation
|
|
219
|
+
*/
|
|
220
|
+
async uploadImageWithValidation(fileInfo: RNFile, maxSize: number = 5 * 1024 * 1024): Promise<string> {
|
|
221
|
+
// Get file size
|
|
222
|
+
const fileSize = await this.s3Manager.getFileSize(fileInfo);
|
|
223
|
+
|
|
224
|
+
if (fileSize > maxSize) {
|
|
225
|
+
throw new Error(`File size (${fileSize} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const key = `images/${Date.now()}-${fileInfo.name}`;
|
|
229
|
+
const uploadOptions: UploadOptions = {
|
|
230
|
+
contentType: fileInfo.type || 'image/jpeg',
|
|
231
|
+
metadata: {
|
|
232
|
+
originalName: fileInfo.name,
|
|
233
|
+
uploadedBy: 'rn-user',
|
|
234
|
+
uploadedAt: new Date().toISOString(),
|
|
235
|
+
fileSize: fileSize.toString(),
|
|
236
|
+
category: 'image',
|
|
237
|
+
},
|
|
238
|
+
publicRead: true, // Images are often public
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const url = await this.s3Manager.uploadImage(key, fileInfo, uploadOptions);
|
|
243
|
+
console.log('Image uploaded successfully:', url);
|
|
244
|
+
return url;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Image upload failed:', error);
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Example: Download file to React Native
|
|
253
|
+
*/
|
|
254
|
+
async downloadFileToRN(key: string): Promise<Buffer> {
|
|
255
|
+
try {
|
|
256
|
+
const buffer = await this.s3Manager.downloadFile(key);
|
|
257
|
+
console.log('File downloaded successfully, size:', buffer.length);
|
|
258
|
+
return buffer;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('Download failed:', error);
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Example: Batch operations
|
|
267
|
+
*/
|
|
268
|
+
async performBatchOperations(files: RNFile[]): Promise<void> {
|
|
269
|
+
try {
|
|
270
|
+
// Upload multiple files
|
|
271
|
+
const uploadPromises = files.map((file, index) => {
|
|
272
|
+
const key = `batch-uploads/${Date.now()}-${index}-${file.name}`;
|
|
273
|
+
return this.s3Manager.uploadFile(key, file, {
|
|
274
|
+
metadata: { batchId: Date.now().toString() },
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const uploadedUrls = await Promise.all(uploadPromises);
|
|
279
|
+
console.log('Batch upload completed:', uploadedUrls);
|
|
280
|
+
|
|
281
|
+
// Delete multiple files (example with keys)
|
|
282
|
+
const keysToDelete = uploadedUrls.map((url: string) => {
|
|
283
|
+
const key = url.split('/').pop();
|
|
284
|
+
return `batch-uploads/${key}`;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await this.s3Manager.deleteMultipleFiles(keysToDelete);
|
|
288
|
+
console.log('Batch delete completed');
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('Batch operations failed:', error);
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ============================================================================
|
|
297
|
+
// USAGE EXAMPLES
|
|
298
|
+
// ============================================================================
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Example usage in a React component
|
|
302
|
+
*/
|
|
303
|
+
export const useS3FileManager = () => {
|
|
304
|
+
const webManager = new WebFileManagerExample();
|
|
305
|
+
const rnManager = new ReactNativeFileManagerExample();
|
|
306
|
+
|
|
307
|
+
const handleFileUpload = async (file: File | RNFile) => {
|
|
308
|
+
try {
|
|
309
|
+
if ('uri' in file) {
|
|
310
|
+
// React Native file
|
|
311
|
+
return await rnManager.uploadFileFromRN(file);
|
|
312
|
+
} else {
|
|
313
|
+
// Web file
|
|
314
|
+
const mockInput = { files: [file] } as unknown as HTMLInputElement;
|
|
315
|
+
return await webManager.uploadFileFromForm(mockInput);
|
|
316
|
+
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.error('Upload failed:', error);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const handleFileDownload = async (key: string) => {
|
|
324
|
+
try {
|
|
325
|
+
if (typeof window !== 'undefined') {
|
|
326
|
+
// Web environment
|
|
327
|
+
await webManager.downloadFile(key);
|
|
328
|
+
} else {
|
|
329
|
+
// React Native environment
|
|
330
|
+
const buffer = await rnManager.downloadFileToRN(key);
|
|
331
|
+
// Handle buffer in React Native (save to file system, etc.)
|
|
332
|
+
return buffer;
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('Download failed:', error);
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
uploadFile: handleFileUpload,
|
|
342
|
+
downloadFile: handleFileDownload,
|
|
343
|
+
listFiles: webManager.listFiles.bind(webManager),
|
|
344
|
+
getPresignedUrls: webManager.getPresignedUrls.bind(webManager),
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Example: Error handling and retry logic
|
|
350
|
+
*/
|
|
351
|
+
export const uploadWithRetry = async (
|
|
352
|
+
s3Manager: S3FileManager | S3FileManagerRN,
|
|
353
|
+
key: string,
|
|
354
|
+
file: File | RNFile | Buffer,
|
|
355
|
+
options: UploadOptions = {},
|
|
356
|
+
maxRetries: number = 3
|
|
357
|
+
): Promise<string> => {
|
|
358
|
+
// Type guard to determine which manager we're using
|
|
359
|
+
const isRNManager = (manager: S3FileManager | S3FileManagerRN): manager is S3FileManagerRN => {
|
|
360
|
+
return 'getFileSize' in manager;
|
|
361
|
+
};
|
|
362
|
+
let lastError: Error | undefined;
|
|
363
|
+
|
|
364
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
365
|
+
try {
|
|
366
|
+
if (isRNManager(s3Manager)) {
|
|
367
|
+
// For React Native manager, ensure file is RNFile
|
|
368
|
+
if ('uri' in file) {
|
|
369
|
+
return await s3Manager.uploadFile(key, file, options);
|
|
370
|
+
} else {
|
|
371
|
+
throw new Error('React Native manager requires RNFile with uri property');
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
// For web manager, ensure file is not RNFile
|
|
375
|
+
if ('uri' in file) {
|
|
376
|
+
throw new Error('Web manager does not support RNFile');
|
|
377
|
+
} else {
|
|
378
|
+
return await s3Manager.uploadFile(key, file, options);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch (error) {
|
|
382
|
+
lastError = error as Error;
|
|
383
|
+
console.warn(`Upload attempt ${attempt} failed:`, error);
|
|
384
|
+
|
|
385
|
+
if (attempt < maxRetries) {
|
|
386
|
+
// Wait before retrying (exponential backoff)
|
|
387
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
388
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
throw new Error(`Upload failed after ${maxRetries} attempts. Last error: ${lastError?.message || 'Unknown error'}`);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Example: File validation utilities
|
|
398
|
+
*/
|
|
399
|
+
export const validateFile = (
|
|
400
|
+
file: File | RNFile,
|
|
401
|
+
options: {
|
|
402
|
+
maxSize?: number;
|
|
403
|
+
allowedTypes?: string[];
|
|
404
|
+
allowedExtensions?: string[];
|
|
405
|
+
} = {}
|
|
406
|
+
): { isValid: boolean; errors: string[] } => {
|
|
407
|
+
const errors: string[] = [];
|
|
408
|
+
const { maxSize = 10 * 1024 * 1024, allowedTypes = [], allowedExtensions = [] } = options;
|
|
409
|
+
|
|
410
|
+
// Check file size
|
|
411
|
+
if ('size' in file && file.size && file.size > maxSize) {
|
|
412
|
+
errors.push(`File size (${file.size} bytes) exceeds maximum allowed size (${maxSize} bytes)`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Check file type
|
|
416
|
+
if ('type' in file && file.type && allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
|
|
417
|
+
errors.push(`File type (${file.type}) is not allowed`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Check file extension
|
|
421
|
+
if (allowedExtensions.length > 0) {
|
|
422
|
+
const extension = file.name.split('.').pop()?.toLowerCase();
|
|
423
|
+
if (!extension || !allowedExtensions.includes(`.${extension}`)) {
|
|
424
|
+
errors.push(`File extension (.${extension}) is not allowed`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
isValid: errors.length === 0,
|
|
430
|
+
errors,
|
|
431
|
+
};
|
|
432
|
+
};
|