@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,322 @@
|
|
|
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 interface RNFile {
|
|
26
|
+
uri: string;
|
|
27
|
+
name: string;
|
|
28
|
+
type?: string;
|
|
29
|
+
size?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class S3FileManagerRN {
|
|
33
|
+
private s3Client: S3Client;
|
|
34
|
+
private bucketName: string;
|
|
35
|
+
|
|
36
|
+
constructor(config: S3Config) {
|
|
37
|
+
this.s3Client = new S3Client({
|
|
38
|
+
region: config.region,
|
|
39
|
+
credentials: {
|
|
40
|
+
accessKeyId: config.accessKeyId,
|
|
41
|
+
secretAccessKey: config.secretAccessKey,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
this.bucketName = config.bucketName;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Upload a file to S3 from React Native
|
|
49
|
+
*/
|
|
50
|
+
async uploadFile(
|
|
51
|
+
key: string,
|
|
52
|
+
file: RNFile | Buffer | string,
|
|
53
|
+
options: UploadOptions = {}
|
|
54
|
+
): Promise<string> {
|
|
55
|
+
let body: Buffer | string;
|
|
56
|
+
let contentType = options.contentType;
|
|
57
|
+
|
|
58
|
+
if (this.isRNFile(file)) {
|
|
59
|
+
body = await this.rnFileToBuffer(file);
|
|
60
|
+
contentType = contentType || file.type || 'application/octet-stream';
|
|
61
|
+
} else if (typeof file === 'string') {
|
|
62
|
+
body = file;
|
|
63
|
+
contentType = contentType || 'text/plain';
|
|
64
|
+
} else {
|
|
65
|
+
body = file;
|
|
66
|
+
contentType = contentType || 'application/octet-stream';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const command = new PutObjectCommand({
|
|
70
|
+
Bucket: this.bucketName,
|
|
71
|
+
Key: key,
|
|
72
|
+
Body: body,
|
|
73
|
+
ContentType: contentType,
|
|
74
|
+
Metadata: options.metadata,
|
|
75
|
+
ACL: options.publicRead ? 'public-read' : 'private',
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await this.s3Client.send(command);
|
|
79
|
+
return `https://${this.bucketName}.s3.amazonaws.com/${key}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Download a file from S3 to React Native
|
|
84
|
+
*/
|
|
85
|
+
async downloadFile(key: string): Promise<Buffer> {
|
|
86
|
+
const command = new GetObjectCommand({
|
|
87
|
+
Bucket: this.bucketName,
|
|
88
|
+
Key: key,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const response = await this.s3Client.send(command);
|
|
92
|
+
|
|
93
|
+
if (!response.Body) {
|
|
94
|
+
throw new Error('File not found or empty');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Convert stream to buffer
|
|
98
|
+
const chunks: Uint8Array[] = [];
|
|
99
|
+
const reader = response.Body.transformToWebStream().getReader();
|
|
100
|
+
|
|
101
|
+
while (true) {
|
|
102
|
+
const { done, value } = await reader.read();
|
|
103
|
+
if (done) break;
|
|
104
|
+
chunks.push(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Buffer.concat(chunks);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Delete a file from S3
|
|
112
|
+
*/
|
|
113
|
+
async deleteFile(key: string): Promise<void> {
|
|
114
|
+
const command = new DeleteObjectCommand({
|
|
115
|
+
Bucket: this.bucketName,
|
|
116
|
+
Key: key,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await this.s3Client.send(command);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Generate a presigned URL for file upload
|
|
124
|
+
*/
|
|
125
|
+
async getPresignedUploadUrl(
|
|
126
|
+
key: string,
|
|
127
|
+
contentType: string,
|
|
128
|
+
expiresIn: number = 3600
|
|
129
|
+
): Promise<string> {
|
|
130
|
+
const command = new PutObjectCommand({
|
|
131
|
+
Bucket: this.bucketName,
|
|
132
|
+
Key: key,
|
|
133
|
+
ContentType: contentType,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return getSignedUrl(this.s3Client, command, { expiresIn });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate a presigned URL for file download
|
|
141
|
+
*/
|
|
142
|
+
async getPresignedDownloadUrl(
|
|
143
|
+
key: string,
|
|
144
|
+
expiresIn: number = 3600
|
|
145
|
+
): Promise<string> {
|
|
146
|
+
const command = new GetObjectCommand({
|
|
147
|
+
Bucket: this.bucketName,
|
|
148
|
+
Key: key,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return getSignedUrl(this.s3Client, command, { expiresIn });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* List files in a directory
|
|
156
|
+
*/
|
|
157
|
+
async listFiles(prefix: string = '', maxKeys: number = 1000): Promise<FileInfo[]> {
|
|
158
|
+
const command = new ListObjectsV2Command({
|
|
159
|
+
Bucket: this.bucketName,
|
|
160
|
+
Prefix: prefix,
|
|
161
|
+
MaxKeys: maxKeys,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const response = await this.s3Client.send(command);
|
|
165
|
+
|
|
166
|
+
if (!response.Contents) {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return response.Contents.map((item) => ({
|
|
171
|
+
key: item.Key!,
|
|
172
|
+
size: item.Size || 0,
|
|
173
|
+
lastModified: item.LastModified!,
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a file exists
|
|
179
|
+
*/
|
|
180
|
+
async fileExists(key: string): Promise<boolean> {
|
|
181
|
+
try {
|
|
182
|
+
const command = new GetObjectCommand({
|
|
183
|
+
Bucket: this.bucketName,
|
|
184
|
+
Key: key,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await this.s3Client.send(command);
|
|
188
|
+
return true;
|
|
189
|
+
} catch (error: any) {
|
|
190
|
+
if (error.name === 'NoSuchKey') {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get file metadata
|
|
199
|
+
*/
|
|
200
|
+
async getFileMetadata(key: string): Promise<FileInfo | null> {
|
|
201
|
+
try {
|
|
202
|
+
const command = new GetObjectCommand({
|
|
203
|
+
Bucket: this.bucketName,
|
|
204
|
+
Key: key,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const response = await this.s3Client.send(command);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
key,
|
|
211
|
+
size: parseInt(response.ContentLength?.toString() || '0'),
|
|
212
|
+
lastModified: response.LastModified!,
|
|
213
|
+
contentType: response.ContentType,
|
|
214
|
+
metadata: response.Metadata,
|
|
215
|
+
};
|
|
216
|
+
} catch (error: any) {
|
|
217
|
+
if (error.name === 'NoSuchKey') {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Convert React Native file to Buffer
|
|
226
|
+
*/
|
|
227
|
+
private async rnFileToBuffer(file: RNFile): Promise<Buffer> {
|
|
228
|
+
try {
|
|
229
|
+
// For React Native, we need to fetch the file from the URI
|
|
230
|
+
const response = await fetch(file.uri);
|
|
231
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
232
|
+
return Buffer.from(arrayBuffer);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw new Error(`Failed to read React Native file: ${error}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Type guard to check if object is RNFile
|
|
240
|
+
*/
|
|
241
|
+
private isRNFile(file: any): file is RNFile {
|
|
242
|
+
return file && typeof file === 'object' && 'uri' in file && 'name' in file;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Upload multiple files
|
|
247
|
+
*/
|
|
248
|
+
async uploadMultipleFiles(
|
|
249
|
+
files: Array<{ key: string; file: RNFile | Buffer | string; options?: UploadOptions }>
|
|
250
|
+
): Promise<string[]> {
|
|
251
|
+
const uploadPromises = files.map(({ key, file, options }) =>
|
|
252
|
+
this.uploadFile(key, file, options)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
return Promise.all(uploadPromises);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Delete multiple files
|
|
260
|
+
*/
|
|
261
|
+
async deleteMultipleFiles(keys: string[]): Promise<void> {
|
|
262
|
+
const deletePromises = keys.map(key => this.deleteFile(key));
|
|
263
|
+
await Promise.all(deletePromises);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Copy file from one key to another
|
|
268
|
+
*/
|
|
269
|
+
async copyFile(sourceKey: string, destinationKey: string): Promise<void> {
|
|
270
|
+
const command = new PutObjectCommand({
|
|
271
|
+
Bucket: this.bucketName,
|
|
272
|
+
Key: destinationKey,
|
|
273
|
+
Body: await this.downloadFile(sourceKey),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await this.s3Client.send(command);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Move file (copy + delete)
|
|
281
|
+
*/
|
|
282
|
+
async moveFile(sourceKey: string, destinationKey: string): Promise<void> {
|
|
283
|
+
await this.copyFile(sourceKey, destinationKey);
|
|
284
|
+
await this.deleteFile(sourceKey);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Upload image with automatic resizing (placeholder for future implementation)
|
|
289
|
+
*/
|
|
290
|
+
async uploadImage(
|
|
291
|
+
key: string,
|
|
292
|
+
file: RNFile,
|
|
293
|
+
options: UploadOptions & { resize?: { width?: number; height?: number } } = {}
|
|
294
|
+
): Promise<string> {
|
|
295
|
+
// For now, just upload as regular file
|
|
296
|
+
// In the future, this could integrate with image processing libraries
|
|
297
|
+
return this.uploadFile(key, file, options);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get file size from React Native file
|
|
302
|
+
*/
|
|
303
|
+
async getFileSize(file: RNFile): Promise<number> {
|
|
304
|
+
if (file.size) {
|
|
305
|
+
return file.size;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const response = await fetch(file.uri, { method: 'HEAD' });
|
|
310
|
+
const contentLength = response.headers.get('content-length');
|
|
311
|
+
return contentLength ? parseInt(contentLength, 10) : 0;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.warn('Could not determine file size:', error);
|
|
314
|
+
return 0;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Export a factory function for easier configuration
|
|
320
|
+
export function createS3FileManagerRN(config: S3Config): S3FileManagerRN {
|
|
321
|
+
return new S3FileManagerRN(config);
|
|
322
|
+
}
|