@igorvaryvoda/sirv-upload-widget 0.1.7 → 0.1.8

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 CHANGED
@@ -9,7 +9,6 @@ A React file upload widget for [Sirv CDN](https://sirv.com) with batch uploads,
9
9
  - **CSV/Excel import** for bulk URL imports
10
10
  - **Sirv file picker** to browse existing files
11
11
  - **HEIC/HEIF conversion** for iPhone photos
12
- - **Presigned URL support** for secure direct uploads
13
12
  - **Dark mode** with automatic system preference detection
14
13
  - **Customizable styling** via CSS variables
15
14
  - **TypeScript** support with full type definitions
@@ -18,52 +17,19 @@ A React file upload widget for [Sirv CDN](https://sirv.com) with batch uploads,
18
17
 
19
18
  ```bash
20
19
  npm install @sirv/upload-widget
21
- # or
22
- yarn add @sirv/upload-widget
23
- # or
24
- pnpm add @sirv/upload-widget
25
20
  ```
26
21
 
27
22
  ## Quick Start
28
23
 
29
- ### 1. Create a presign endpoint on your backend
24
+ ### 1. Deploy the upload proxy
30
25
 
31
- The widget uploads directly to Sirv using presigned URLs. Your backend just needs one endpoint:
26
+ The widget uploads through a proxy that handles Sirv authentication. Deploy to Cloudflare Workers:
32
27
 
33
- ```typescript
34
- // app/api/sirv/presign/route.ts (Next.js)
35
- import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
36
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
37
-
38
- const s3 = new S3Client({
39
- endpoint: 'https://s3.sirv.com',
40
- region: 'us-east-1',
41
- credentials: {
42
- accessKeyId: process.env.SIRV_S3_KEY!,
43
- secretAccessKey: process.env.SIRV_S3_SECRET!,
44
- },
45
- forcePathStyle: true,
46
- })
47
-
48
- export async function POST(req: Request) {
49
- const { filename, contentType, folder } = await req.json()
50
- const key = `${folder}/${filename}`.replace(/^\/+/, '')
51
-
52
- const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({
53
- Bucket: process.env.SIRV_BUCKET!,
54
- Key: key,
55
- ContentType: contentType,
56
- }), { expiresIn: 300 })
57
-
58
- return Response.json({
59
- uploadUrl,
60
- publicUrl: `https://${process.env.SIRV_BUCKET}.sirv.com/${key}`,
61
- path: '/' + key,
62
- })
63
- }
64
- ```
28
+ [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/IgorVaryvoda/sirv-uploader/tree/main/examples/cloudflare-worker)
29
+
30
+ You'll need your Sirv API credentials from [Sirv Dashboard → Settings → API](https://my.sirv.com/#/account/settings/api).
65
31
 
66
- ### 2. Use the widget in your React app
32
+ ### 2. Use the widget
67
33
 
68
34
  ```tsx
69
35
  import { SirvUploader } from '@sirv/upload-widget'
@@ -72,7 +38,7 @@ import '@sirv/upload-widget/styles.css'
72
38
  export default function UploadPage() {
73
39
  return (
74
40
  <SirvUploader
75
- presignEndpoint="/api/sirv/presign"
41
+ proxyEndpoint="https://your-worker.workers.dev"
76
42
  folder="/uploads"
77
43
  onUpload={(files) => {
78
44
  console.log('Uploaded files:', files)
@@ -86,8 +52,7 @@ export default function UploadPage() {
86
52
 
87
53
  | Prop | Type | Default | Description |
88
54
  |------|------|---------|-------------|
89
- | `presignEndpoint` | `string` | - | **Recommended.** URL to get presigned upload URLs |
90
- | `proxyEndpoint` | `string` | - | Alternative: URL for full proxy mode |
55
+ | `proxyEndpoint` | `string` | - | URL of your upload proxy (Cloudflare Worker) |
91
56
  | `folder` | `string` | `"/"` | Default upload folder |
92
57
  | `onUpload` | `(files: SirvFile[]) => void` | - | Callback when files are uploaded |
93
58
  | `onError` | `(error: string, file?: SirvFile) => void` | - | Callback on upload errors |
@@ -96,7 +61,6 @@ export default function UploadPage() {
96
61
  | `maxFileSize` | `number` | `10485760` | Maximum file size in bytes |
97
62
  | `autoUpload` | `boolean` | `true` | Start upload immediately on file selection |
98
63
  | `concurrency` | `number` | `3` | Number of concurrent uploads |
99
- | `onConflict` | `'overwrite' \| 'rename' \| 'skip' \| 'ask'` | `'rename'` | Filename conflict handling |
100
64
  | `disabled` | `boolean` | `false` | Disable the widget |
101
65
  | `compact` | `boolean` | `false` | Compact mode for smaller spaces |
102
66
  | `theme` | `'auto' \| 'light' \| 'dark'` | `'auto'` | Color theme (auto follows system preference) |
@@ -116,7 +80,7 @@ features?: {
116
80
 
117
81
  ## Dark Mode
118
82
 
119
- The widget supports dark mode out of the box with three options:
83
+ The widget supports dark mode out of the box:
120
84
 
121
85
  ```tsx
122
86
  // Auto (default) - follows system preference
@@ -129,13 +93,9 @@ The widget supports dark mode out of the box with three options:
129
93
  <SirvUploader theme="dark" ... />
130
94
  ```
131
95
 
132
- Dark mode automatically activates when:
133
- - `theme="auto"` (default) and the user's system prefers dark mode
134
- - `theme="dark"` is explicitly set
135
-
136
96
  ## Styling
137
97
 
138
- Customize the widget using CSS variables:
98
+ Customize using CSS variables:
139
99
 
140
100
  ```css
141
101
  .sirv-uploader {
@@ -145,75 +105,29 @@ Customize the widget using CSS variables:
145
105
  --sirv-text: #1e293b;
146
106
  --sirv-border: #e2e8f0;
147
107
  --sirv-radius: 8px;
148
- /* ... see styles.css for all variables */
149
108
  }
150
109
  ```
151
110
 
152
- For dark mode customization, override the dark theme variables:
111
+ ## Self-hosting the Proxy
153
112
 
154
- ```css
155
- @media (prefers-color-scheme: dark) {
156
- .sirv-uploader {
157
- --sirv-primary: #3b82f6;
158
- --sirv-bg: #1e1e1e;
159
- --sirv-text: #e5e5e5;
160
- /* ... */
161
- }
162
- }
163
- ```
113
+ If you prefer to self-host, the proxy is a simple Cloudflare Worker:
164
114
 
165
- ## Individual Components
166
-
167
- For custom layouts, you can use the components individually:
168
-
169
- ```tsx
170
- import {
171
- DropZone,
172
- FileList,
173
- FilePicker,
174
- SpreadsheetImport,
175
- useSirvUpload,
176
- } from '@sirv/upload-widget'
115
+ ```typescript
116
+ // See examples/cloudflare-worker/src/index.ts
177
117
  ```
178
118
 
119
+ Required environment variables:
120
+ - `SIRV_CLIENT_ID` - API Client ID from Sirv
121
+ - `SIRV_CLIENT_SECRET` - API Client Secret from Sirv
122
+
179
123
  ## TypeScript
180
124
 
181
- Full TypeScript support with exported types:
125
+ Full TypeScript support:
182
126
 
183
127
  ```typescript
184
- import type {
185
- SirvFile,
186
- SirvUploaderProps,
187
- PresignRequest,
188
- PresignResponse,
189
- } from '@sirv/upload-widget'
128
+ import type { SirvFile, SirvUploaderProps } from '@sirv/upload-widget'
190
129
  ```
191
130
 
192
- ## Backend Examples
193
-
194
- ### Cloudflare Workers (One-Click Deploy)
195
-
196
- [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/IgorVaryvoda/sirv-uploader/tree/main/examples/cloudflare-worker)
197
-
198
- You'll be prompted for your Sirv account name and S3 credentials during deployment.
199
-
200
- ### Other Examples
201
-
202
- See the `/examples` folder for:
203
- - `cloudflare-worker/` - Cloudflare Workers (with deploy button)
204
- - `nextjs-presign.ts` - Next.js API route
205
- - `express-presign.ts` - Express.js server
206
-
207
- ## How It Works
208
-
209
- 1. User selects files in the widget
210
- 2. Widget requests a presigned URL from your backend
211
- 3. Your backend generates the URL using AWS SDK with Sirv's S3 endpoint
212
- 4. Widget uploads directly to Sirv using the presigned URL
213
- 5. File is available at `https://youraccount.sirv.com/path/to/file.jpg`
214
-
215
- This approach keeps your Sirv credentials secure on the server while allowing fast, direct uploads from the browser.
216
-
217
131
  ## License
218
132
 
219
133
  MIT
package/dist/index.js CHANGED
@@ -1374,21 +1374,14 @@ function useSirvUpload(options) {
1374
1374
  if (!proxyEndpoint) throw new Error("No proxy endpoint configured");
1375
1375
  if (!file.file) throw new Error("No file data");
1376
1376
  updateFile(file.id, { status: "uploading", progress: 10 });
1377
- const arrayBuffer = await file.file.arrayBuffer();
1378
- const base64 = btoa(
1379
- new Uint8Array(arrayBuffer).reduce((data, byte) => data + String.fromCharCode(byte), "")
1380
- );
1377
+ const uploadUrl = new URL(`${proxyEndpoint}/upload`);
1378
+ uploadUrl.searchParams.set("filename", file.filename);
1379
+ uploadUrl.searchParams.set("folder", folder);
1381
1380
  updateFile(file.id, { progress: 30 });
1382
- const res = await fetch(`${proxyEndpoint}/upload`, {
1381
+ const res = await fetch(uploadUrl.toString(), {
1383
1382
  method: "POST",
1384
- headers: { "Content-Type": "application/json" },
1385
- body: JSON.stringify({
1386
- data: base64,
1387
- filename: file.filename,
1388
- folder,
1389
- contentType: getMimeType(file.file),
1390
- onConflict: onConflict === "ask" ? "rename" : onConflict
1391
- }),
1383
+ headers: { "Content-Type": getMimeType(file.file) },
1384
+ body: file.file,
1392
1385
  signal
1393
1386
  });
1394
1387
  updateFile(file.id, { progress: 80 });
@@ -1407,7 +1400,7 @@ function useSirvUpload(options) {
1407
1400
  sirvPath: result.path
1408
1401
  });
1409
1402
  },
1410
- [proxyEndpoint, folder, onConflict, updateFile]
1403
+ [proxyEndpoint, folder, updateFile]
1411
1404
  );
1412
1405
  const uploadFile = react.useCallback(
1413
1406
  async (id) => {