@signskart/uploader 2.0.12 → 2.0.13
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/README.md +271 -14
- package/dist/index.d.mts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +117 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +116 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Production-grade Upload Manager SDK by **Signskart**.
|
|
4
4
|
|
|
5
|
-
A powerful, fully-typed file upload SDK with queue management, retry logic, cancellation support, concurrency control, and multi-provider architecture (S3 + Cloudinary).
|
|
5
|
+
A powerful, fully-typed file upload SDK with queue management, retry logic, cancellation support, concurrency control, and multi-provider architecture (S3 + Cloudinary + Firebase Storage).
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ A powerful, fully-typed file upload SDK with queue management, retry logic, canc
|
|
|
13
13
|
* 📊 Real-time progress tracking
|
|
14
14
|
* ❌ Cancel uploads (AbortController support)
|
|
15
15
|
* ⚡ Concurrency control
|
|
16
|
-
* ☁ Multi-provider support (Amazon S3, Cloudinary)
|
|
16
|
+
* ☁ Multi-provider support (Amazon S3, Cloudinary, Firebase Storage)
|
|
17
17
|
* 🧠 Fully typed (TypeScript)
|
|
18
18
|
* 🌍 Works with React, Vue, Next.js, Vite, vanilla JS
|
|
19
19
|
* 🔐 Secure S3 presigned upload support
|
|
@@ -51,25 +51,22 @@ Cloudinary does NOT require backend if using unsigned preset.
|
|
|
51
51
|
|
|
52
52
|
# 🚀 Quick Start
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# ☁ Using Amazon S3
|
|
57
|
-
|
|
58
|
-
## 1️⃣ Frontend Setup
|
|
54
|
+
## 1️⃣ Client (S3)
|
|
59
55
|
|
|
60
56
|
```ts
|
|
61
57
|
import { UploadManager, S3Uploader } from '@signskart/uploader';
|
|
62
58
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const manager = new UploadManager(
|
|
60
|
+
new S3Uploader({
|
|
61
|
+
apiBaseUrl: import.meta.env.VITE_APP_API_BASE_URL,
|
|
62
|
+
publicUrl: import.meta.env.VITE_AWS_S3_BASE_URL,
|
|
63
|
+
}),
|
|
64
|
+
3
|
|
65
|
+
);
|
|
69
66
|
|
|
70
67
|
const task = manager.add({
|
|
71
68
|
file,
|
|
72
|
-
folder: '
|
|
69
|
+
folder: 'uploads',
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
task.events.subscribe((state) => {
|
|
@@ -77,6 +74,76 @@ task.events.subscribe((state) => {
|
|
|
77
74
|
});
|
|
78
75
|
```
|
|
79
76
|
|
|
77
|
+
## 2️⃣ Server Presign Endpoint
|
|
78
|
+
|
|
79
|
+
Use `createS3PresignHandler` in your backend and expose:
|
|
80
|
+
|
|
81
|
+
`POST /api/s3/presign-upload`
|
|
82
|
+
|
|
83
|
+
Expected request body:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"fileName": "logo.png",
|
|
88
|
+
"folder": "uploads",
|
|
89
|
+
"contentType": "image/png"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expected response body:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"signedUrl": "https://...",
|
|
98
|
+
"key": "uploads/1710000000000-logo.png",
|
|
99
|
+
"url": "https://cdn.example.com/uploads/1710000000000-logo.png"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# ✅ Signskart Admin Pattern (Recommended)
|
|
106
|
+
|
|
107
|
+
Goal: keep upload implementation inside `@signskart/uploader`; in admin only pass env and selected file.
|
|
108
|
+
|
|
109
|
+
## 1️⃣ Admin wrapper (env only)
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// apps/signskart-admin/src/lib/uploads/createUploadManager.ts
|
|
113
|
+
import { S3Uploader, UploadManager } from '@signskart/uploader';
|
|
114
|
+
|
|
115
|
+
export const createUploadManager = (concurrency = 3) => {
|
|
116
|
+
return new UploadManager(
|
|
117
|
+
new S3Uploader({
|
|
118
|
+
apiBaseUrl: import.meta.env.VITE_APP_API_BASE_URL,
|
|
119
|
+
publicUrl: import.meta.env.VITE_AWS_S3_BASE_URL,
|
|
120
|
+
}),
|
|
121
|
+
concurrency
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 2️⃣ Admin component (select file + call manager)
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
const uploadManager = useMemo(() => createUploadManager(3), []);
|
|
130
|
+
|
|
131
|
+
const handleUpload = (file: File) => {
|
|
132
|
+
const task = uploadManager.add({
|
|
133
|
+
file,
|
|
134
|
+
folder: 'assets/store',
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
task.events.subscribe((state) => {
|
|
138
|
+
if (state.status === 'success') {
|
|
139
|
+
console.log('Uploaded URL:', state.response?.url);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This keeps admin thin: env + selected file only.
|
|
146
|
+
|
|
80
147
|
---
|
|
81
148
|
|
|
82
149
|
## 2️⃣ Backend Setup (Next.js Example)
|
|
@@ -162,6 +229,196 @@ const task = manager.add({
|
|
|
162
229
|
|
|
163
230
|
---
|
|
164
231
|
|
|
232
|
+
# 🔥 Firebase Storage Full Examples (Next.js + Node.js)
|
|
233
|
+
|
|
234
|
+
Firebase mode is different from S3 mode:
|
|
235
|
+
|
|
236
|
+
* S3: frontend uploads via backend presign endpoint.
|
|
237
|
+
* Firebase: frontend can upload directly with Firebase client SDK.
|
|
238
|
+
|
|
239
|
+
## 1️⃣ Next.js Client Example (using uploader package)
|
|
240
|
+
|
|
241
|
+
Install:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
npm install @signskart/uploader firebase
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Create env file:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
# .env.local
|
|
251
|
+
NEXT_PUBLIC_FIREBASE_API_KEY=...
|
|
252
|
+
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
|
|
253
|
+
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...
|
|
254
|
+
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=...
|
|
255
|
+
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...
|
|
256
|
+
NEXT_PUBLIC_FIREBASE_APP_ID=...
|
|
257
|
+
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=...
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Create uploader helper:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// lib/firebaseUploader.ts
|
|
264
|
+
import { FirebaseStorageUploader, UploadManager } from '@signskart/uploader';
|
|
265
|
+
|
|
266
|
+
export const createFirebaseUploadManager = (concurrency = 3) => {
|
|
267
|
+
return new UploadManager(
|
|
268
|
+
new FirebaseStorageUploader({
|
|
269
|
+
firebaseConfig: {
|
|
270
|
+
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
|
271
|
+
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
|
272
|
+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
|
|
273
|
+
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
|
|
274
|
+
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
|
|
275
|
+
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
|
|
276
|
+
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
|
|
277
|
+
},
|
|
278
|
+
appName: 'signskart-nextjs-uploader',
|
|
279
|
+
}),
|
|
280
|
+
concurrency
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Use in a client component:
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
// app/upload/page.tsx
|
|
289
|
+
'use client';
|
|
290
|
+
|
|
291
|
+
import { useMemo, useState } from 'react';
|
|
292
|
+
import { createFirebaseUploadManager } from '@/lib/firebaseUploader';
|
|
293
|
+
|
|
294
|
+
export default function UploadPage() {
|
|
295
|
+
const [progress, setProgress] = useState(0);
|
|
296
|
+
const [uploadedUrl, setUploadedUrl] = useState('');
|
|
297
|
+
const [status, setStatus] = useState('idle');
|
|
298
|
+
|
|
299
|
+
const manager = useMemo(() => createFirebaseUploadManager(3), []);
|
|
300
|
+
|
|
301
|
+
const onSelectFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
302
|
+
const file = event.target.files?.[0];
|
|
303
|
+
if (!file) return;
|
|
304
|
+
|
|
305
|
+
const task = manager.add({
|
|
306
|
+
file,
|
|
307
|
+
folder: 'uploads/nextjs',
|
|
308
|
+
metadata: { source: 'nextjs-client' },
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
task.events.subscribe((state) => {
|
|
312
|
+
setProgress(state.progress);
|
|
313
|
+
setStatus(state.status);
|
|
314
|
+
|
|
315
|
+
if (state.status === 'success') {
|
|
316
|
+
setUploadedUrl(state.response?.url || '');
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
await task.start();
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error('Upload failed:', error);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<main style={{ padding: 24 }}>
|
|
329
|
+
<h1>Firebase Upload</h1>
|
|
330
|
+
<input type="file" onChange={onSelectFile} />
|
|
331
|
+
<p>Status: {status}</p>
|
|
332
|
+
<p>Progress: {progress}%</p>
|
|
333
|
+
{uploadedUrl ? (
|
|
334
|
+
<p>
|
|
335
|
+
File URL: <a href={uploadedUrl} target="_blank" rel="noreferrer">{uploadedUrl}</a>
|
|
336
|
+
</p>
|
|
337
|
+
) : null}
|
|
338
|
+
</main>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## 2️⃣ Node.js Example (server upload flow)
|
|
344
|
+
|
|
345
|
+
`FirebaseStorageUploader` in this package is browser-oriented (uses browser upload APIs).
|
|
346
|
+
For pure Node.js backend upload, use Firebase Admin SDK.
|
|
347
|
+
|
|
348
|
+
Install:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
npm install express multer firebase-admin
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Server example:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// server.ts
|
|
358
|
+
import express from 'express';
|
|
359
|
+
import multer from 'multer';
|
|
360
|
+
import admin from 'firebase-admin';
|
|
361
|
+
|
|
362
|
+
const app = express();
|
|
363
|
+
const upload = multer({ storage: multer.memoryStorage() });
|
|
364
|
+
|
|
365
|
+
admin.initializeApp({
|
|
366
|
+
credential: admin.credential.cert({
|
|
367
|
+
projectId: process.env.FIREBASE_PROJECT_ID,
|
|
368
|
+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
|
|
369
|
+
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
|
|
370
|
+
}),
|
|
371
|
+
storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const bucket = admin.storage().bucket();
|
|
375
|
+
|
|
376
|
+
app.post('/api/firebase/upload', upload.single('file'), async (req, res) => {
|
|
377
|
+
try {
|
|
378
|
+
if (!req.file) {
|
|
379
|
+
return res.status(400).json({ error: 'File is required' });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const folder = String(req.body.folder || 'uploads/node');
|
|
383
|
+
const objectName = `${folder}/${Date.now()}-${req.file.originalname}`;
|
|
384
|
+
const object = bucket.file(objectName);
|
|
385
|
+
|
|
386
|
+
await object.save(req.file.buffer, {
|
|
387
|
+
contentType: req.file.mimetype,
|
|
388
|
+
resumable: false,
|
|
389
|
+
metadata: {
|
|
390
|
+
metadata: {
|
|
391
|
+
source: 'node-api',
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await object.makePublic();
|
|
397
|
+
const url = `https://storage.googleapis.com/${bucket.name}/${objectName}`;
|
|
398
|
+
|
|
399
|
+
return res.json({ url, key: objectName, provider: 'firebase' });
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error(error);
|
|
402
|
+
return res.status(500).json({ error: 'Upload failed' });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
app.listen(3000, () => {
|
|
407
|
+
console.log('Server running on http://localhost:3000');
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Node.js env example:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
FIREBASE_PROJECT_ID=...
|
|
415
|
+
FIREBASE_CLIENT_EMAIL=...
|
|
416
|
+
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
|
|
417
|
+
FIREBASE_STORAGE_BUCKET=your-project-id.appspot.com
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
165
422
|
# 🧠 API Reference
|
|
166
423
|
|
|
167
424
|
---
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { FirebaseOptions } from 'firebase/app';
|
|
2
|
+
|
|
3
|
+
type UploadProvider = 's3' | 'cloudinary' | 'firebase';
|
|
2
4
|
interface UploadOptions {
|
|
3
5
|
file: File;
|
|
4
6
|
folder: string;
|
|
@@ -76,6 +78,36 @@ declare class CloudinaryUploader extends BaseUploader {
|
|
|
76
78
|
upload(options: UploadOptions, onProgress: (percent: number) => void): Promise<UploadResponse>;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
interface FirebaseStorageConfig {
|
|
82
|
+
firebaseConfig: FirebaseOptions;
|
|
83
|
+
bucketUrl?: string;
|
|
84
|
+
appName?: string;
|
|
85
|
+
}
|
|
86
|
+
declare class FirebaseStorageUploader extends BaseUploader {
|
|
87
|
+
private config;
|
|
88
|
+
private app;
|
|
89
|
+
constructor(config: FirebaseStorageConfig);
|
|
90
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
91
|
+
private buildObjectPath;
|
|
92
|
+
private getOrCreateApp;
|
|
93
|
+
private toStringRecord;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface CreateClientUploadManagerConfig {
|
|
97
|
+
provider?: UploadProvider | string;
|
|
98
|
+
concurrency?: number;
|
|
99
|
+
s3?: {
|
|
100
|
+
apiBaseUrl?: string;
|
|
101
|
+
publicUrl?: string;
|
|
102
|
+
};
|
|
103
|
+
firebase?: {
|
|
104
|
+
firebaseConfig?: FirebaseOptions;
|
|
105
|
+
bucketUrl?: string;
|
|
106
|
+
appName?: string;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
declare const createClientUploadManager: (config?: CreateClientUploadManagerConfig) => UploadManager;
|
|
110
|
+
|
|
79
111
|
interface PresignConfig {
|
|
80
112
|
region: string;
|
|
81
113
|
accessKeyId: string;
|
|
@@ -95,4 +127,4 @@ declare function createS3PresignHandler(config: PresignConfig): ({ fileName, con
|
|
|
95
127
|
publicUrl: string;
|
|
96
128
|
}>;
|
|
97
129
|
|
|
98
|
-
export { CloudinaryUploader, type PresignConfig, type PresignRequest, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState, createS3PresignHandler };
|
|
130
|
+
export { CloudinaryUploader, type CreateClientUploadManagerConfig, type FirebaseStorageConfig, FirebaseStorageUploader, type PresignConfig, type PresignRequest, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState, createClientUploadManager, createS3PresignHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { FirebaseOptions } from 'firebase/app';
|
|
2
|
+
|
|
3
|
+
type UploadProvider = 's3' | 'cloudinary' | 'firebase';
|
|
2
4
|
interface UploadOptions {
|
|
3
5
|
file: File;
|
|
4
6
|
folder: string;
|
|
@@ -76,6 +78,36 @@ declare class CloudinaryUploader extends BaseUploader {
|
|
|
76
78
|
upload(options: UploadOptions, onProgress: (percent: number) => void): Promise<UploadResponse>;
|
|
77
79
|
}
|
|
78
80
|
|
|
81
|
+
interface FirebaseStorageConfig {
|
|
82
|
+
firebaseConfig: FirebaseOptions;
|
|
83
|
+
bucketUrl?: string;
|
|
84
|
+
appName?: string;
|
|
85
|
+
}
|
|
86
|
+
declare class FirebaseStorageUploader extends BaseUploader {
|
|
87
|
+
private config;
|
|
88
|
+
private app;
|
|
89
|
+
constructor(config: FirebaseStorageConfig);
|
|
90
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
91
|
+
private buildObjectPath;
|
|
92
|
+
private getOrCreateApp;
|
|
93
|
+
private toStringRecord;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface CreateClientUploadManagerConfig {
|
|
97
|
+
provider?: UploadProvider | string;
|
|
98
|
+
concurrency?: number;
|
|
99
|
+
s3?: {
|
|
100
|
+
apiBaseUrl?: string;
|
|
101
|
+
publicUrl?: string;
|
|
102
|
+
};
|
|
103
|
+
firebase?: {
|
|
104
|
+
firebaseConfig?: FirebaseOptions;
|
|
105
|
+
bucketUrl?: string;
|
|
106
|
+
appName?: string;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
declare const createClientUploadManager: (config?: CreateClientUploadManagerConfig) => UploadManager;
|
|
110
|
+
|
|
79
111
|
interface PresignConfig {
|
|
80
112
|
region: string;
|
|
81
113
|
accessKeyId: string;
|
|
@@ -95,4 +127,4 @@ declare function createS3PresignHandler(config: PresignConfig): ({ fileName, con
|
|
|
95
127
|
publicUrl: string;
|
|
96
128
|
}>;
|
|
97
129
|
|
|
98
|
-
export { CloudinaryUploader, type PresignConfig, type PresignRequest, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState, createS3PresignHandler };
|
|
130
|
+
export { CloudinaryUploader, type CreateClientUploadManagerConfig, type FirebaseStorageConfig, FirebaseStorageUploader, type PresignConfig, type PresignRequest, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState, createClientUploadManager, createS3PresignHandler };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var app = require('firebase/app');
|
|
4
|
+
var storage = require('firebase/storage');
|
|
3
5
|
var clientS3 = require('@aws-sdk/client-s3');
|
|
4
6
|
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
5
7
|
|
|
@@ -184,6 +186,119 @@ var CloudinaryUploader = class extends BaseUploader {
|
|
|
184
186
|
});
|
|
185
187
|
}
|
|
186
188
|
};
|
|
189
|
+
var FirebaseStorageUploader = class extends BaseUploader {
|
|
190
|
+
constructor(config) {
|
|
191
|
+
super();
|
|
192
|
+
this.config = config;
|
|
193
|
+
this.app = this.getOrCreateApp(config.firebaseConfig, config.appName);
|
|
194
|
+
}
|
|
195
|
+
async upload(options, onProgress, signal) {
|
|
196
|
+
const storage$1 = storage.getStorage(this.app, this.config.bucketUrl);
|
|
197
|
+
const objectPath = this.buildObjectPath(options.folder, options.fileName || options.file.name);
|
|
198
|
+
const storageRef = storage.ref(storage$1, objectPath);
|
|
199
|
+
const metadata = {
|
|
200
|
+
contentType: options.file.type,
|
|
201
|
+
customMetadata: this.toStringRecord(options.metadata)
|
|
202
|
+
};
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
const uploadTask = storage.uploadBytesResumable(storageRef, options.file, metadata);
|
|
205
|
+
let isSettled = false;
|
|
206
|
+
const settleReject = (error) => {
|
|
207
|
+
if (isSettled) return;
|
|
208
|
+
isSettled = true;
|
|
209
|
+
reject(error);
|
|
210
|
+
};
|
|
211
|
+
const settleResolve = (response) => {
|
|
212
|
+
if (isSettled) return;
|
|
213
|
+
isSettled = true;
|
|
214
|
+
resolve(response);
|
|
215
|
+
};
|
|
216
|
+
const abortUpload = () => {
|
|
217
|
+
uploadTask.cancel();
|
|
218
|
+
settleReject(new Error("Upload cancelled"));
|
|
219
|
+
};
|
|
220
|
+
if (signal?.aborted) {
|
|
221
|
+
abortUpload();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
signal?.addEventListener("abort", abortUpload, { once: true });
|
|
225
|
+
uploadTask.on(
|
|
226
|
+
"state_changed",
|
|
227
|
+
(snapshot) => {
|
|
228
|
+
const percent = snapshot.totalBytes ? Math.round(snapshot.bytesTransferred / snapshot.totalBytes * 100) : 0;
|
|
229
|
+
onProgress(percent);
|
|
230
|
+
},
|
|
231
|
+
(error) => {
|
|
232
|
+
signal?.removeEventListener("abort", abortUpload);
|
|
233
|
+
settleReject(error);
|
|
234
|
+
},
|
|
235
|
+
async () => {
|
|
236
|
+
signal?.removeEventListener("abort", abortUpload);
|
|
237
|
+
try {
|
|
238
|
+
const url = await storage.getDownloadURL(uploadTask.snapshot.ref);
|
|
239
|
+
settleResolve({
|
|
240
|
+
url,
|
|
241
|
+
provider: "firebase",
|
|
242
|
+
key: uploadTask.snapshot.ref.fullPath
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
settleReject(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
buildObjectPath(folder, fileName) {
|
|
252
|
+
return [folder, fileName].filter(Boolean).join("/").replace(/\/{2,}/g, "/").replace(/^\//, "");
|
|
253
|
+
}
|
|
254
|
+
getOrCreateApp(firebaseConfig, appName) {
|
|
255
|
+
if (!appName) {
|
|
256
|
+
return app.getApps().length > 0 ? app.getApp() : app.initializeApp(firebaseConfig);
|
|
257
|
+
}
|
|
258
|
+
const existingApp = app.getApps().find((app) => app.name === appName);
|
|
259
|
+
return existingApp || app.initializeApp(firebaseConfig, appName);
|
|
260
|
+
}
|
|
261
|
+
toStringRecord(metadata) {
|
|
262
|
+
if (!metadata) return void 0;
|
|
263
|
+
return Object.entries(metadata).reduce((result, [key, value]) => {
|
|
264
|
+
if (value !== void 0 && value !== null) {
|
|
265
|
+
result[key] = String(value);
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}, {});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// src/factories/createClientUploadManager.ts
|
|
273
|
+
var normalizeProvider = (provider) => (provider || "s3").toLowerCase();
|
|
274
|
+
var hasFirebaseConfig = (config) => Boolean(config?.apiKey && config?.authDomain && config?.projectId && config?.storageBucket && config?.appId);
|
|
275
|
+
var createClientUploadManager = (config = {}) => {
|
|
276
|
+
const provider = normalizeProvider(config.provider);
|
|
277
|
+
const concurrency = config.concurrency ?? 3;
|
|
278
|
+
if (provider === "firebase") {
|
|
279
|
+
if (!hasFirebaseConfig(config.firebase?.firebaseConfig)) {
|
|
280
|
+
throw new Error('Missing required Firebase configuration for upload provider "firebase".');
|
|
281
|
+
}
|
|
282
|
+
return new UploadManager(
|
|
283
|
+
new FirebaseStorageUploader({
|
|
284
|
+
firebaseConfig: config.firebase.firebaseConfig,
|
|
285
|
+
bucketUrl: config.firebase?.bucketUrl,
|
|
286
|
+
appName: config.firebase?.appName
|
|
287
|
+
}),
|
|
288
|
+
concurrency
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (!config.s3?.apiBaseUrl || !config.s3?.publicUrl) {
|
|
292
|
+
throw new Error('Missing required S3 configuration: "apiBaseUrl" and "publicUrl" are required.');
|
|
293
|
+
}
|
|
294
|
+
return new UploadManager(
|
|
295
|
+
new S3Uploader({
|
|
296
|
+
apiBaseUrl: config.s3.apiBaseUrl,
|
|
297
|
+
publicUrl: config.s3.publicUrl
|
|
298
|
+
}),
|
|
299
|
+
concurrency
|
|
300
|
+
);
|
|
301
|
+
};
|
|
187
302
|
function createS3PresignHandler(config) {
|
|
188
303
|
const s3 = new clientS3.S3Client({
|
|
189
304
|
region: config.region,
|
|
@@ -219,9 +334,11 @@ function createS3PresignHandler(config) {
|
|
|
219
334
|
}
|
|
220
335
|
|
|
221
336
|
exports.CloudinaryUploader = CloudinaryUploader;
|
|
337
|
+
exports.FirebaseStorageUploader = FirebaseStorageUploader;
|
|
222
338
|
exports.S3Uploader = S3Uploader;
|
|
223
339
|
exports.UploadManager = UploadManager;
|
|
224
340
|
exports.UploadTask = UploadTask;
|
|
341
|
+
exports.createClientUploadManager = createClientUploadManager;
|
|
225
342
|
exports.createS3PresignHandler = createS3PresignHandler;
|
|
226
343
|
//# sourceMappingURL=index.js.map
|
|
227
344
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts","../src/server/createS3PresignHandler.ts"],"names":["S3Client","PutObjectCommand","getSignedUrl"],"mappings":"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","file":"index.js","sourcesContent":["type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued'\n };\n }\n\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n progress => this.update({ progress }),\n this.abortController.signal\n );\n\n this.update({ status: 'success', progress: 100, response });\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message = error instanceof Error ? error.message : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({ status: 'error', error: message });\n return;\n }\n\n this.retries++;\n await this.sleep(this.retryDelay * Math.pow(2, this.retries - 1));\n }\n }\n }\n\n cancel() {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n private update(update: Partial<UploadTaskState>) {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n private sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(private uploader: BaseUploader, private concurrency = 3) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(`${this.config.apiBaseUrl}/api/s3/presign-upload`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type\n })\n });\n\n if (!presignRes.ok) throw new Error('Failed to get presigned URL');\n\n const { signedUrl, key } = await presignRes.json();\n\n if (!signedUrl || !key) throw new Error('Invalid presign response');\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n key,\n });\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload network error'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', signedUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({ url: data.secure_url, provider: 'cloudinary' });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open('POST', `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`);\n xhr.send(formData);\n });\n }\n}","import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nexport interface PresignConfig {\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n publicUrl: string;\n expiresIn?: number;\n}\n\nexport interface PresignRequest {\n fileName: string;\n contentType: string;\n folder?: string;\n}\n\nexport function createS3PresignHandler(config: PresignConfig) {\n const s3 = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n });\n\n return async function generatePresignedUpload({\n fileName,\n contentType,\n folder,\n }: PresignRequest) {\n if (!fileName || !contentType) {\n throw new Error('fileName and contentType are required');\n }\n\n const safeFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const key = `${folder || 'uploads'}/${Date.now()}-${safeFileName}`;\n\n const command = new PutObjectCommand({\n Bucket: config.bucket,\n Key: key,\n ContentType: contentType,\n });\n\n const signedUrl = await getSignedUrl(s3, command, {\n expiresIn: config.expiresIn ?? 300,\n });\n\n return {\n signedUrl,\n key,\n publicUrl: `${config.publicUrl}/${key}`,\n };\n };\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts","../src/providers/FirebaseStorageUploader.ts","../src/factories/createClientUploadManager.ts","../src/server/createS3PresignHandler.ts"],"names":["storage","getStorage","ref","uploadBytesResumable","getDownloadURL","getApps","getApp","initializeApp","S3Client","PutObjectCommand","getSignedUrl"],"mappings":"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","file":"index.js","sourcesContent":["type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued'\n };\n }\n\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n progress => this.update({ progress }),\n this.abortController.signal\n );\n\n this.update({ status: 'success', progress: 100, response });\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message = error instanceof Error ? error.message : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({ status: 'error', error: message });\n return;\n }\n\n this.retries++;\n await this.sleep(this.retryDelay * Math.pow(2, this.retries - 1));\n }\n }\n }\n\n cancel() {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n private update(update: Partial<UploadTaskState>) {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n private sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(private uploader: BaseUploader, private concurrency = 3) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(`${this.config.apiBaseUrl}/api/s3/presign-upload`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type\n })\n });\n\n if (!presignRes.ok) throw new Error('Failed to get presigned URL');\n\n const { signedUrl, key } = await presignRes.json();\n\n if (!signedUrl || !key) throw new Error('Invalid presign response');\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n key,\n });\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload network error'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', signedUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({ url: data.secure_url, provider: 'cloudinary' });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open('POST', `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`);\n xhr.send(formData);\n });\n }\n}","import { getApp, getApps, initializeApp, type FirebaseApp, type FirebaseOptions } from 'firebase/app';\nimport { getDownloadURL, getStorage, ref, uploadBytesResumable, type UploadMetadata } from 'firebase/storage';\nimport { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\nexport interface FirebaseStorageConfig {\n firebaseConfig: FirebaseOptions;\n bucketUrl?: string;\n appName?: string;\n}\n\nexport class FirebaseStorageUploader extends BaseUploader {\n private app: FirebaseApp;\n\n constructor(private config: FirebaseStorageConfig) {\n super();\n this.app = this.getOrCreateApp(config.firebaseConfig, config.appName);\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const storage = getStorage(this.app, this.config.bucketUrl);\n const objectPath = this.buildObjectPath(options.folder, options.fileName || options.file.name);\n const storageRef = ref(storage, objectPath);\n const metadata: UploadMetadata = {\n contentType: options.file.type,\n customMetadata: this.toStringRecord(options.metadata),\n };\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const uploadTask = uploadBytesResumable(storageRef, options.file, metadata);\n let isSettled = false;\n\n const settleReject = (error: Error) => {\n if (isSettled) return;\n isSettled = true;\n reject(error);\n };\n\n const settleResolve = (response: UploadResponse) => {\n if (isSettled) return;\n isSettled = true;\n resolve(response);\n };\n\n const abortUpload = () => {\n uploadTask.cancel();\n settleReject(new Error('Upload cancelled'));\n };\n\n if (signal?.aborted) {\n abortUpload();\n return;\n }\n\n signal?.addEventListener('abort', abortUpload, { once: true });\n\n uploadTask.on(\n 'state_changed',\n snapshot => {\n const percent = snapshot.totalBytes\n ? Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100)\n : 0;\n\n onProgress(percent);\n },\n error => {\n signal?.removeEventListener('abort', abortUpload);\n settleReject(error);\n },\n async () => {\n signal?.removeEventListener('abort', abortUpload);\n\n try {\n const url = await getDownloadURL(uploadTask.snapshot.ref);\n\n settleResolve({\n url,\n provider: 'firebase',\n key: uploadTask.snapshot.ref.fullPath,\n });\n } catch (error) {\n settleReject(error as Error);\n }\n }\n );\n });\n }\n\n private buildObjectPath(folder: string, fileName: string) {\n return [folder, fileName]\n .filter(Boolean)\n .join('/')\n .replace(/\\/{2,}/g, '/')\n .replace(/^\\//, '');\n }\n\n private getOrCreateApp(firebaseConfig: FirebaseOptions, appName?: string) {\n if (!appName) {\n return getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);\n }\n\n const existingApp = getApps().find(app => app.name === appName);\n return existingApp || initializeApp(firebaseConfig, appName);\n }\n\n private toStringRecord(metadata?: Record<string, any>) {\n if (!metadata) return undefined;\n\n return Object.entries(metadata).reduce<Record<string, string>>((result, [key, value]) => {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n\n return result;\n }, {});\n }\n}\n","import type { FirebaseOptions } from 'firebase/app';\nimport { UploadManager } from '../core/UploadManager';\nimport type { UploadProvider } from '../core/types';\nimport { FirebaseStorageUploader } from '../providers/FirebaseStorageUploader';\nimport { S3Uploader } from '../providers/S3Uploader';\n\nexport interface CreateClientUploadManagerConfig {\n provider?: UploadProvider | string;\n concurrency?: number;\n s3?: {\n apiBaseUrl?: string;\n publicUrl?: string;\n };\n firebase?: {\n firebaseConfig?: FirebaseOptions;\n bucketUrl?: string;\n appName?: string;\n };\n}\n\nconst normalizeProvider = (provider: CreateClientUploadManagerConfig['provider']) =>\n (provider || 's3').toLowerCase();\n\nconst hasFirebaseConfig = (config?: FirebaseOptions) =>\n Boolean(config?.apiKey && config?.authDomain && config?.projectId && config?.storageBucket && config?.appId);\n\nexport const createClientUploadManager = (config: CreateClientUploadManagerConfig = {}) => {\n const provider = normalizeProvider(config.provider);\n const concurrency = config.concurrency ?? 3;\n\n if (provider === 'firebase') {\n if (!hasFirebaseConfig(config.firebase?.firebaseConfig)) {\n throw new Error('Missing required Firebase configuration for upload provider \"firebase\".');\n }\n\n return new UploadManager(\n new FirebaseStorageUploader({\n firebaseConfig: config.firebase!.firebaseConfig!,\n bucketUrl: config.firebase?.bucketUrl,\n appName: config.firebase?.appName,\n }),\n concurrency\n );\n }\n\n if (!config.s3?.apiBaseUrl || !config.s3?.publicUrl) {\n throw new Error('Missing required S3 configuration: \"apiBaseUrl\" and \"publicUrl\" are required.');\n }\n\n return new UploadManager(\n new S3Uploader({\n apiBaseUrl: config.s3.apiBaseUrl,\n publicUrl: config.s3.publicUrl,\n }),\n concurrency\n );\n};\n","import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nexport interface PresignConfig {\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n publicUrl: string;\n expiresIn?: number;\n}\n\nexport interface PresignRequest {\n fileName: string;\n contentType: string;\n folder?: string;\n}\n\nexport function createS3PresignHandler(config: PresignConfig) {\n const s3 = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n });\n\n return async function generatePresignedUpload({\n fileName,\n contentType,\n folder,\n }: PresignRequest) {\n if (!fileName || !contentType) {\n throw new Error('fileName and contentType are required');\n }\n\n const safeFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const key = `${folder || 'uploads'}/${Date.now()}-${safeFileName}`;\n\n const command = new PutObjectCommand({\n Bucket: config.bucket,\n Key: key,\n ContentType: contentType,\n });\n\n const signedUrl = await getSignedUrl(s3, command, {\n expiresIn: config.expiresIn ?? 300,\n });\n\n return {\n signedUrl,\n key,\n publicUrl: `${config.publicUrl}/${key}`,\n };\n };\n}"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getApps, getApp, initializeApp } from 'firebase/app';
|
|
2
|
+
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
|
|
1
3
|
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
2
4
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
3
5
|
|
|
@@ -182,6 +184,119 @@ var CloudinaryUploader = class extends BaseUploader {
|
|
|
182
184
|
});
|
|
183
185
|
}
|
|
184
186
|
};
|
|
187
|
+
var FirebaseStorageUploader = class extends BaseUploader {
|
|
188
|
+
constructor(config) {
|
|
189
|
+
super();
|
|
190
|
+
this.config = config;
|
|
191
|
+
this.app = this.getOrCreateApp(config.firebaseConfig, config.appName);
|
|
192
|
+
}
|
|
193
|
+
async upload(options, onProgress, signal) {
|
|
194
|
+
const storage = getStorage(this.app, this.config.bucketUrl);
|
|
195
|
+
const objectPath = this.buildObjectPath(options.folder, options.fileName || options.file.name);
|
|
196
|
+
const storageRef = ref(storage, objectPath);
|
|
197
|
+
const metadata = {
|
|
198
|
+
contentType: options.file.type,
|
|
199
|
+
customMetadata: this.toStringRecord(options.metadata)
|
|
200
|
+
};
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const uploadTask = uploadBytesResumable(storageRef, options.file, metadata);
|
|
203
|
+
let isSettled = false;
|
|
204
|
+
const settleReject = (error) => {
|
|
205
|
+
if (isSettled) return;
|
|
206
|
+
isSettled = true;
|
|
207
|
+
reject(error);
|
|
208
|
+
};
|
|
209
|
+
const settleResolve = (response) => {
|
|
210
|
+
if (isSettled) return;
|
|
211
|
+
isSettled = true;
|
|
212
|
+
resolve(response);
|
|
213
|
+
};
|
|
214
|
+
const abortUpload = () => {
|
|
215
|
+
uploadTask.cancel();
|
|
216
|
+
settleReject(new Error("Upload cancelled"));
|
|
217
|
+
};
|
|
218
|
+
if (signal?.aborted) {
|
|
219
|
+
abortUpload();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
signal?.addEventListener("abort", abortUpload, { once: true });
|
|
223
|
+
uploadTask.on(
|
|
224
|
+
"state_changed",
|
|
225
|
+
(snapshot) => {
|
|
226
|
+
const percent = snapshot.totalBytes ? Math.round(snapshot.bytesTransferred / snapshot.totalBytes * 100) : 0;
|
|
227
|
+
onProgress(percent);
|
|
228
|
+
},
|
|
229
|
+
(error) => {
|
|
230
|
+
signal?.removeEventListener("abort", abortUpload);
|
|
231
|
+
settleReject(error);
|
|
232
|
+
},
|
|
233
|
+
async () => {
|
|
234
|
+
signal?.removeEventListener("abort", abortUpload);
|
|
235
|
+
try {
|
|
236
|
+
const url = await getDownloadURL(uploadTask.snapshot.ref);
|
|
237
|
+
settleResolve({
|
|
238
|
+
url,
|
|
239
|
+
provider: "firebase",
|
|
240
|
+
key: uploadTask.snapshot.ref.fullPath
|
|
241
|
+
});
|
|
242
|
+
} catch (error) {
|
|
243
|
+
settleReject(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
buildObjectPath(folder, fileName) {
|
|
250
|
+
return [folder, fileName].filter(Boolean).join("/").replace(/\/{2,}/g, "/").replace(/^\//, "");
|
|
251
|
+
}
|
|
252
|
+
getOrCreateApp(firebaseConfig, appName) {
|
|
253
|
+
if (!appName) {
|
|
254
|
+
return getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);
|
|
255
|
+
}
|
|
256
|
+
const existingApp = getApps().find((app) => app.name === appName);
|
|
257
|
+
return existingApp || initializeApp(firebaseConfig, appName);
|
|
258
|
+
}
|
|
259
|
+
toStringRecord(metadata) {
|
|
260
|
+
if (!metadata) return void 0;
|
|
261
|
+
return Object.entries(metadata).reduce((result, [key, value]) => {
|
|
262
|
+
if (value !== void 0 && value !== null) {
|
|
263
|
+
result[key] = String(value);
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}, {});
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/factories/createClientUploadManager.ts
|
|
271
|
+
var normalizeProvider = (provider) => (provider || "s3").toLowerCase();
|
|
272
|
+
var hasFirebaseConfig = (config) => Boolean(config?.apiKey && config?.authDomain && config?.projectId && config?.storageBucket && config?.appId);
|
|
273
|
+
var createClientUploadManager = (config = {}) => {
|
|
274
|
+
const provider = normalizeProvider(config.provider);
|
|
275
|
+
const concurrency = config.concurrency ?? 3;
|
|
276
|
+
if (provider === "firebase") {
|
|
277
|
+
if (!hasFirebaseConfig(config.firebase?.firebaseConfig)) {
|
|
278
|
+
throw new Error('Missing required Firebase configuration for upload provider "firebase".');
|
|
279
|
+
}
|
|
280
|
+
return new UploadManager(
|
|
281
|
+
new FirebaseStorageUploader({
|
|
282
|
+
firebaseConfig: config.firebase.firebaseConfig,
|
|
283
|
+
bucketUrl: config.firebase?.bucketUrl,
|
|
284
|
+
appName: config.firebase?.appName
|
|
285
|
+
}),
|
|
286
|
+
concurrency
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (!config.s3?.apiBaseUrl || !config.s3?.publicUrl) {
|
|
290
|
+
throw new Error('Missing required S3 configuration: "apiBaseUrl" and "publicUrl" are required.');
|
|
291
|
+
}
|
|
292
|
+
return new UploadManager(
|
|
293
|
+
new S3Uploader({
|
|
294
|
+
apiBaseUrl: config.s3.apiBaseUrl,
|
|
295
|
+
publicUrl: config.s3.publicUrl
|
|
296
|
+
}),
|
|
297
|
+
concurrency
|
|
298
|
+
);
|
|
299
|
+
};
|
|
185
300
|
function createS3PresignHandler(config) {
|
|
186
301
|
const s3 = new S3Client({
|
|
187
302
|
region: config.region,
|
|
@@ -216,6 +331,6 @@ function createS3PresignHandler(config) {
|
|
|
216
331
|
};
|
|
217
332
|
}
|
|
218
333
|
|
|
219
|
-
export { CloudinaryUploader, S3Uploader, UploadManager, UploadTask, createS3PresignHandler };
|
|
334
|
+
export { CloudinaryUploader, FirebaseStorageUploader, S3Uploader, UploadManager, UploadTask, createClientUploadManager, createS3PresignHandler };
|
|
220
335
|
//# sourceMappingURL=index.mjs.map
|
|
221
336
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts","../src/server/createS3PresignHandler.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued'\n };\n }\n\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n progress => this.update({ progress }),\n this.abortController.signal\n );\n\n this.update({ status: 'success', progress: 100, response });\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message = error instanceof Error ? error.message : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({ status: 'error', error: message });\n return;\n }\n\n this.retries++;\n await this.sleep(this.retryDelay * Math.pow(2, this.retries - 1));\n }\n }\n }\n\n cancel() {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n private update(update: Partial<UploadTaskState>) {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n private sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(private uploader: BaseUploader, private concurrency = 3) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(`${this.config.apiBaseUrl}/api/s3/presign-upload`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type\n })\n });\n\n if (!presignRes.ok) throw new Error('Failed to get presigned URL');\n\n const { signedUrl, key } = await presignRes.json();\n\n if (!signedUrl || !key) throw new Error('Invalid presign response');\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n key,\n });\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload network error'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', signedUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({ url: data.secure_url, provider: 'cloudinary' });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open('POST', `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`);\n xhr.send(formData);\n });\n }\n}","import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nexport interface PresignConfig {\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n publicUrl: string;\n expiresIn?: number;\n}\n\nexport interface PresignRequest {\n fileName: string;\n contentType: string;\n folder?: string;\n}\n\nexport function createS3PresignHandler(config: PresignConfig) {\n const s3 = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n });\n\n return async function generatePresignedUpload({\n fileName,\n contentType,\n folder,\n }: PresignRequest) {\n if (!fileName || !contentType) {\n throw new Error('fileName and contentType are required');\n }\n\n const safeFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const key = `${folder || 'uploads'}/${Date.now()}-${safeFileName}`;\n\n const command = new PutObjectCommand({\n Bucket: config.bucket,\n Key: key,\n ContentType: contentType,\n });\n\n const signedUrl = await getSignedUrl(s3, command, {\n expiresIn: config.expiresIn ?? 300,\n });\n\n return {\n signedUrl,\n key,\n publicUrl: `${config.publicUrl}/${key}`,\n };\n };\n}"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts","../src/providers/FirebaseStorageUploader.ts","../src/factories/createClientUploadManager.ts","../src/server/createS3PresignHandler.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued'\n };\n }\n\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n progress => this.update({ progress }),\n this.abortController.signal\n );\n\n this.update({ status: 'success', progress: 100, response });\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message = error instanceof Error ? error.message : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({ status: 'error', error: message });\n return;\n }\n\n this.retries++;\n await this.sleep(this.retryDelay * Math.pow(2, this.retries - 1));\n }\n }\n }\n\n cancel() {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n private update(update: Partial<UploadTaskState>) {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n private sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(private uploader: BaseUploader, private concurrency = 3) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(`${this.config.apiBaseUrl}/api/s3/presign-upload`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type\n })\n });\n\n if (!presignRes.ok) throw new Error('Failed to get presigned URL');\n\n const { signedUrl, key } = await presignRes.json();\n\n if (!signedUrl || !key) throw new Error('Invalid presign response');\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n key,\n });\n } else {\n reject(new Error(`S3 upload failed with status ${xhr.status}`));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload network error'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', signedUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = event => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({ url: data.secure_url, provider: 'cloudinary' });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open('POST', `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`);\n xhr.send(formData);\n });\n }\n}","import { getApp, getApps, initializeApp, type FirebaseApp, type FirebaseOptions } from 'firebase/app';\nimport { getDownloadURL, getStorage, ref, uploadBytesResumable, type UploadMetadata } from 'firebase/storage';\nimport { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\nexport interface FirebaseStorageConfig {\n firebaseConfig: FirebaseOptions;\n bucketUrl?: string;\n appName?: string;\n}\n\nexport class FirebaseStorageUploader extends BaseUploader {\n private app: FirebaseApp;\n\n constructor(private config: FirebaseStorageConfig) {\n super();\n this.app = this.getOrCreateApp(config.firebaseConfig, config.appName);\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const storage = getStorage(this.app, this.config.bucketUrl);\n const objectPath = this.buildObjectPath(options.folder, options.fileName || options.file.name);\n const storageRef = ref(storage, objectPath);\n const metadata: UploadMetadata = {\n contentType: options.file.type,\n customMetadata: this.toStringRecord(options.metadata),\n };\n\n return new Promise<UploadResponse>((resolve, reject) => {\n const uploadTask = uploadBytesResumable(storageRef, options.file, metadata);\n let isSettled = false;\n\n const settleReject = (error: Error) => {\n if (isSettled) return;\n isSettled = true;\n reject(error);\n };\n\n const settleResolve = (response: UploadResponse) => {\n if (isSettled) return;\n isSettled = true;\n resolve(response);\n };\n\n const abortUpload = () => {\n uploadTask.cancel();\n settleReject(new Error('Upload cancelled'));\n };\n\n if (signal?.aborted) {\n abortUpload();\n return;\n }\n\n signal?.addEventListener('abort', abortUpload, { once: true });\n\n uploadTask.on(\n 'state_changed',\n snapshot => {\n const percent = snapshot.totalBytes\n ? Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100)\n : 0;\n\n onProgress(percent);\n },\n error => {\n signal?.removeEventListener('abort', abortUpload);\n settleReject(error);\n },\n async () => {\n signal?.removeEventListener('abort', abortUpload);\n\n try {\n const url = await getDownloadURL(uploadTask.snapshot.ref);\n\n settleResolve({\n url,\n provider: 'firebase',\n key: uploadTask.snapshot.ref.fullPath,\n });\n } catch (error) {\n settleReject(error as Error);\n }\n }\n );\n });\n }\n\n private buildObjectPath(folder: string, fileName: string) {\n return [folder, fileName]\n .filter(Boolean)\n .join('/')\n .replace(/\\/{2,}/g, '/')\n .replace(/^\\//, '');\n }\n\n private getOrCreateApp(firebaseConfig: FirebaseOptions, appName?: string) {\n if (!appName) {\n return getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);\n }\n\n const existingApp = getApps().find(app => app.name === appName);\n return existingApp || initializeApp(firebaseConfig, appName);\n }\n\n private toStringRecord(metadata?: Record<string, any>) {\n if (!metadata) return undefined;\n\n return Object.entries(metadata).reduce<Record<string, string>>((result, [key, value]) => {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n\n return result;\n }, {});\n }\n}\n","import type { FirebaseOptions } from 'firebase/app';\nimport { UploadManager } from '../core/UploadManager';\nimport type { UploadProvider } from '../core/types';\nimport { FirebaseStorageUploader } from '../providers/FirebaseStorageUploader';\nimport { S3Uploader } from '../providers/S3Uploader';\n\nexport interface CreateClientUploadManagerConfig {\n provider?: UploadProvider | string;\n concurrency?: number;\n s3?: {\n apiBaseUrl?: string;\n publicUrl?: string;\n };\n firebase?: {\n firebaseConfig?: FirebaseOptions;\n bucketUrl?: string;\n appName?: string;\n };\n}\n\nconst normalizeProvider = (provider: CreateClientUploadManagerConfig['provider']) =>\n (provider || 's3').toLowerCase();\n\nconst hasFirebaseConfig = (config?: FirebaseOptions) =>\n Boolean(config?.apiKey && config?.authDomain && config?.projectId && config?.storageBucket && config?.appId);\n\nexport const createClientUploadManager = (config: CreateClientUploadManagerConfig = {}) => {\n const provider = normalizeProvider(config.provider);\n const concurrency = config.concurrency ?? 3;\n\n if (provider === 'firebase') {\n if (!hasFirebaseConfig(config.firebase?.firebaseConfig)) {\n throw new Error('Missing required Firebase configuration for upload provider \"firebase\".');\n }\n\n return new UploadManager(\n new FirebaseStorageUploader({\n firebaseConfig: config.firebase!.firebaseConfig!,\n bucketUrl: config.firebase?.bucketUrl,\n appName: config.firebase?.appName,\n }),\n concurrency\n );\n }\n\n if (!config.s3?.apiBaseUrl || !config.s3?.publicUrl) {\n throw new Error('Missing required S3 configuration: \"apiBaseUrl\" and \"publicUrl\" are required.');\n }\n\n return new UploadManager(\n new S3Uploader({\n apiBaseUrl: config.s3.apiBaseUrl,\n publicUrl: config.s3.publicUrl,\n }),\n concurrency\n );\n};\n","import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nexport interface PresignConfig {\n region: string;\n accessKeyId: string;\n secretAccessKey: string;\n bucket: string;\n publicUrl: string;\n expiresIn?: number;\n}\n\nexport interface PresignRequest {\n fileName: string;\n contentType: string;\n folder?: string;\n}\n\nexport function createS3PresignHandler(config: PresignConfig) {\n const s3 = new S3Client({\n region: config.region,\n credentials: {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n },\n });\n\n return async function generatePresignedUpload({\n fileName,\n contentType,\n folder,\n }: PresignRequest) {\n if (!fileName || !contentType) {\n throw new Error('fileName and contentType are required');\n }\n\n const safeFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const key = `${folder || 'uploads'}/${Date.now()}-${safeFileName}`;\n\n const command = new PutObjectCommand({\n Bucket: config.bucket,\n Key: key,\n ContentType: contentType,\n });\n\n const signedUrl = await getSignedUrl(s3, command, {\n expiresIn: config.expiresIn ?? 300,\n });\n\n return {\n signedUrl,\n key,\n publicUrl: `${config.publicUrl}/${key}`,\n };\n };\n}"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signskart/uploader",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
4
4
|
"description": "Upload manager SDK for Node.js, React, Vite, and Next.js",
|
|
5
5
|
"author": "Signskart",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,12 +24,14 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
+
"dev": "tsup --watch",
|
|
27
28
|
"build": "tsup",
|
|
28
29
|
"prepublishOnly": "npm run build"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@aws-sdk/client-s3": "^3.540.0",
|
|
32
|
-
"@aws-sdk/s3-request-presigner": "^3.540.0"
|
|
33
|
+
"@aws-sdk/s3-request-presigner": "^3.540.0",
|
|
34
|
+
"firebase": "^12.12.1"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"tsup": "^8.0.0",
|