@ibanzajoe/uploader 1.2.1 → 1.3.0
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 +146 -12
- package/dist/index.cjs +21 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.js +21 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,22 +37,77 @@ function App() {
|
|
|
37
37
|
}
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
## Headless client (no React required)
|
|
40
|
+
## Headless upload client (no React required)
|
|
41
|
+
|
|
42
|
+
The `@ibanzajoe/uploader/core` entry is a framework-free client — use it in a Node
|
|
43
|
+
script, a serverless function, a Vue/Svelte app, or behind your own UI. It has no
|
|
44
|
+
React dependency.
|
|
41
45
|
|
|
42
46
|
```ts
|
|
43
47
|
import { UploaderClient } from '@ibanzajoe/uploader/core'
|
|
44
48
|
|
|
45
49
|
const client = new UploaderClient({
|
|
46
|
-
apikey: 'pk_your_api_key',
|
|
50
|
+
apikey: 'pk_your_api_key', // required — your public key
|
|
47
51
|
apiUrl: 'https://your-api.example.com',
|
|
48
52
|
})
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
// Upload one file (a browser File/Blob, or a Node Blob/File):
|
|
55
|
+
const result = await client.upload(file, {
|
|
56
|
+
filename: 'photo.jpg',
|
|
57
|
+
onProgress: (pct) => console.log(`${pct}%`),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
console.log(result.handle) // stable id, e.g. "abc123def456"
|
|
61
|
+
console.log(result.url) // delivery URL (cdn for public, edge for hotlink/signed)
|
|
62
|
+
console.log(result.size, result.mimetype)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Upload **many** files with bounded concurrency:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const results = await client.uploadAll(files, { concurrency: 3 })
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**`new UploaderClient(options)`**
|
|
72
|
+
|
|
73
|
+
| Option | Type | Description |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `apikey` | `string` | **Required.** Public key (`pk_…`). |
|
|
76
|
+
| `apiUrl` | `string` | Base URL of the API. Omit for same-origin. |
|
|
77
|
+
| `security` | `{ policy, signature }` | Signed policy applied to every request (see Delivery protection). |
|
|
78
|
+
| `deliveryProtection` | `'public' \| 'hotlink' \| 'signed'` | Client-wide **default** protection for uploads (see below). |
|
|
79
|
+
| `allowedOrigins` | `string[]` | Client-wide default per-file origin lock. |
|
|
80
|
+
| `directUpload` | `boolean` | Leave unset to auto-detect direct-to-bucket; `false` forces the proxied flow. |
|
|
81
|
+
|
|
82
|
+
**`client.upload(file, options?) → Promise<FileResult>`**
|
|
83
|
+
|
|
84
|
+
| Upload option | Type | Description |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `onProgress` | `(percent: number) => void` | `0`–`100`. Real byte progress on the direct + multipart paths. |
|
|
87
|
+
| `filename` | `string` | Override the stored filename. |
|
|
88
|
+
| `path` | `string` | Storage path prefix. |
|
|
89
|
+
| `signal` | `AbortSignal` | Cancel the upload (throws `UploaderError` code `ABORTED`). |
|
|
90
|
+
| `chunkSize` | `number` | Multipart chunk size in bytes (default ~5 MB). |
|
|
91
|
+
| `deliveryProtection` | `'public' \| 'hotlink' \| 'signed'` | **Per-upload** protection for this file — overrides the client default. |
|
|
92
|
+
| `allowedOrigins` | `string[]` | Per-upload origin lock for this file. |
|
|
93
|
+
|
|
94
|
+
**`FileResult`**
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
type FileResult = {
|
|
98
|
+
handle: string // stable public id — build delivery/transform URLs from it
|
|
99
|
+
url: string // delivery URL for the original
|
|
100
|
+
filename: string
|
|
101
|
+
mimetype: string
|
|
102
|
+
size: number // bytes
|
|
103
|
+
status: 'Stored'
|
|
104
|
+
}
|
|
53
105
|
```
|
|
54
106
|
|
|
55
|
-
Large files (≥ 6 MiB)
|
|
107
|
+
Large files (≥ ~6 MiB) upload via multipart automatically; smaller files use a
|
|
108
|
+
single PUT. When the account/plan allows it, bytes go **direct to storage** via a
|
|
109
|
+
presigned URL (they never transit the API); otherwise the client transparently
|
|
110
|
+
falls back to a proxied upload.
|
|
56
111
|
|
|
57
112
|
## Components
|
|
58
113
|
|
|
@@ -67,16 +122,93 @@ Full-screen modal picker with drag-and-drop, progress, and error UI.
|
|
|
67
122
|
| `open` | `boolean` | Whether the modal is open. **Required.** |
|
|
68
123
|
| `onClose` | `() => void` | Called when the modal should close (ESC, backdrop, cancel) |
|
|
69
124
|
| `onUploadDone` | `(res: PickerResponse) => void` | Called when all uploads complete |
|
|
125
|
+
| `deliveryProtection` | `'public' \| 'hotlink' \| 'signed'` | Protection for every file this picker uploads (see Delivery protection). Omit → account default. |
|
|
126
|
+
| `allowedOrigins` | `string[]` | Per-file origin lock (see Delivery protection). Omit → account allowlist. |
|
|
70
127
|
| `pickerOptions` | `PickerOptions` | File constraints + sources — `{ accept?: string[], maxFiles?, maxSize?, fromSources?, cameraFacingMode? }` |
|
|
71
128
|
| `theme` | `UploaderTheme` | Per-instance design tokens (see Theming) |
|
|
72
129
|
|
|
130
|
+
`<DropPane>` accepts the same `apikey` / `apiUrl` / `deliveryProtection` /
|
|
131
|
+
`allowedOrigins` / callback props.
|
|
132
|
+
|
|
133
|
+
> **Requires `@ibanzajoe/uploader` ≥ 1.3.0** for `deliveryProtection` /
|
|
134
|
+
> `allowedOrigins` on the picker components. (Earlier versions only honored them
|
|
135
|
+
> on the headless client.)
|
|
136
|
+
|
|
73
137
|
### `<DropPane>`
|
|
74
138
|
|
|
75
139
|
Inline drop zone that can be embedded in a form.
|
|
76
140
|
|
|
77
141
|
### `usePicker(options)`
|
|
78
142
|
|
|
79
|
-
Headless hook
|
|
143
|
+
Headless hook for building a fully custom picker UI. Takes the same options as the
|
|
144
|
+
components (all `UploaderClientOptions` incl. `deliveryProtection`/`allowedOrigins`,
|
|
145
|
+
plus `pickerOptions` and the `onUpload*` callbacks) and returns:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
{ files, addFiles, removeFile, editFile, retryFile, upload, progress, isUploading, isDone }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Delivery protection (public / hotlink / signed)
|
|
152
|
+
|
|
153
|
+
Every file is served under one of three protection modes. **You choose the mode per
|
|
154
|
+
upload** (or set a client-wide default); it must be one your account's plan allows.
|
|
155
|
+
|
|
156
|
+
| Mode | Who can access | Delivery URL you get back |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `public` | anyone with the link | `https://cdn.…/<key>` (CDN-cached, cheapest) |
|
|
159
|
+
| `hotlink` | requests from your allowed domains (Origin/Referer allowlist) | `https://edge.…/file/<handle>` |
|
|
160
|
+
| `signed` | only holders of a valid **signed URL** you mint server-side | `https://edge.…/file/<handle>` |
|
|
161
|
+
|
|
162
|
+
Set it per upload (headless or picker), or as a client-wide default:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// headless — per upload wins over the client default
|
|
166
|
+
await client.upload(logo, { deliveryProtection: 'public' })
|
|
167
|
+
await client.upload(productImg, { deliveryProtection: 'hotlink' })
|
|
168
|
+
await client.upload(idScan, { deliveryProtection: 'signed' })
|
|
169
|
+
|
|
170
|
+
// client-wide default
|
|
171
|
+
const client = new UploaderClient({ apikey, apiUrl, deliveryProtection: 'signed' })
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// React picker (≥ 1.3.0)
|
|
176
|
+
<PickerOverlay apikey="pk_…" deliveryProtection="signed" open={open} onClose={close} />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Resolution order: **per-upload → client default → the account default** configured in
|
|
180
|
+
the dashboard. If you request a mode your plan doesn't allow, the API responds `403
|
|
181
|
+
DELIVERY_MODE_NOT_ALLOWED`.
|
|
182
|
+
|
|
183
|
+
**`allowedOrigins`** (the domain list):
|
|
184
|
+
- For `hotlink`: omit it and the file uses your **account allowlist** (set in the
|
|
185
|
+
dashboard settings). Pass a per-file `allowedOrigins` only to override it for that
|
|
186
|
+
file (it replaces, not merges).
|
|
187
|
+
- For `signed`: the signature is the gate; a per-file `allowedOrigins` is an optional
|
|
188
|
+
extra domain lock layered on top.
|
|
189
|
+
|
|
190
|
+
### Viewing a `signed` file
|
|
191
|
+
|
|
192
|
+
A `signed` file's `url` is not directly loadable — each view needs a fresh signed URL,
|
|
193
|
+
minted **server-side** with your API key secret. Use the one-call helper from the
|
|
194
|
+
**server** entry (Node only — never ship the secret to the browser):
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { getSignedDeliveryUrl } from '@ibanzajoe/uploader/server'
|
|
198
|
+
|
|
199
|
+
// in an authenticated backend route:
|
|
200
|
+
const url = getSignedDeliveryUrl({
|
|
201
|
+
handle: file.handle,
|
|
202
|
+
secret: process.env.UPLOADER_API_SECRET, // the API key's secret
|
|
203
|
+
baseUrl: 'https://edge.your-domain.com', // origin of FileResult.url
|
|
204
|
+
expiresIn: 300, // seconds (default 300)
|
|
205
|
+
// ops: [resize({ w: 400 }), output({ format: 'webp' })], // optional transform
|
|
206
|
+
})
|
|
207
|
+
// → https://edge.…/file/<handle>?policy=…&signature=… (hand to <img src>)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Prefer to build it yourself? `withSignedPolicy(url, { policy, signature })` from
|
|
211
|
+
`@ibanzajoe/uploader/core` appends a `policy`/`signature` pair you produced.
|
|
80
212
|
|
|
81
213
|
## Theming
|
|
82
214
|
|
|
@@ -178,11 +310,13 @@ const client = new UploaderClient({
|
|
|
178
310
|
})
|
|
179
311
|
```
|
|
180
312
|
|
|
181
|
-
For building **read** URLs of signed files,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
the
|
|
185
|
-
|
|
313
|
+
For building **read** URLs of signed files, prefer the one-call
|
|
314
|
+
`getSignedDeliveryUrl()` from `@ibanzajoe/uploader/server` (see
|
|
315
|
+
[Delivery protection → Viewing a signed file](#viewing-a-signed-file)). It signs and
|
|
316
|
+
assembles the URL for you. If you'd rather bring your own `policy`/`signature`
|
|
317
|
+
(produced **server-side** — HMAC over the API key's secret; the SDK never signs in
|
|
318
|
+
the browser), append them with `withSignedPolicy(url, { policy, signature })`. Full
|
|
319
|
+
recipe + example:
|
|
186
320
|
[docs/14 §7.3](https://github.com/ibanzajoe/file-uploader/blob/main/docs/14-sdk-developer-guide.md#73-signed-urls-the-private-tier).
|
|
187
321
|
|
|
188
322
|
## Building
|
package/dist/index.cjs
CHANGED
|
@@ -506,11 +506,22 @@ function nextId() {
|
|
|
506
506
|
function usePicker(opts) {
|
|
507
507
|
const [files, setFiles] = (0, import_react.useState)([]);
|
|
508
508
|
const clientRef = (0, import_react.useRef)(null);
|
|
509
|
-
|
|
509
|
+
const clientSigRef = (0, import_react.useRef)("");
|
|
510
|
+
const clientSig = JSON.stringify({
|
|
511
|
+
apikey: opts.apikey,
|
|
512
|
+
apiUrl: opts.apiUrl ?? null,
|
|
513
|
+
security: opts.security ?? null,
|
|
514
|
+
deliveryProtection: opts.deliveryProtection ?? null,
|
|
515
|
+
allowedOrigins: opts.allowedOrigins ?? null
|
|
516
|
+
});
|
|
517
|
+
if (!clientRef.current || clientSigRef.current !== clientSig) {
|
|
518
|
+
clientSigRef.current = clientSig;
|
|
510
519
|
clientRef.current = new UploaderClient({
|
|
511
520
|
apikey: opts.apikey,
|
|
512
521
|
apiUrl: opts.apiUrl,
|
|
513
|
-
security: opts.security
|
|
522
|
+
security: opts.security,
|
|
523
|
+
deliveryProtection: opts.deliveryProtection,
|
|
524
|
+
allowedOrigins: opts.allowedOrigins
|
|
514
525
|
});
|
|
515
526
|
}
|
|
516
527
|
const addFiles = (0, import_react.useCallback)(
|
|
@@ -1613,6 +1624,8 @@ function PickerOverlay({
|
|
|
1613
1624
|
apikey,
|
|
1614
1625
|
apiUrl,
|
|
1615
1626
|
security,
|
|
1627
|
+
deliveryProtection,
|
|
1628
|
+
allowedOrigins,
|
|
1616
1629
|
pickerOptions,
|
|
1617
1630
|
onUploadDone,
|
|
1618
1631
|
onFileUploadFinished,
|
|
@@ -1631,6 +1644,8 @@ function PickerOverlay({
|
|
|
1631
1644
|
apikey,
|
|
1632
1645
|
apiUrl,
|
|
1633
1646
|
security,
|
|
1647
|
+
deliveryProtection,
|
|
1648
|
+
allowedOrigins,
|
|
1634
1649
|
pickerOptions,
|
|
1635
1650
|
onUploadDone,
|
|
1636
1651
|
onFileUploadFinished,
|
|
@@ -1913,6 +1928,8 @@ function DropPane({
|
|
|
1913
1928
|
apikey,
|
|
1914
1929
|
apiUrl,
|
|
1915
1930
|
security,
|
|
1931
|
+
deliveryProtection,
|
|
1932
|
+
allowedOrigins,
|
|
1916
1933
|
pickerOptions,
|
|
1917
1934
|
onUploadDone,
|
|
1918
1935
|
onFileUploadFinished,
|
|
@@ -1930,6 +1947,8 @@ function DropPane({
|
|
|
1930
1947
|
apikey,
|
|
1931
1948
|
apiUrl,
|
|
1932
1949
|
security,
|
|
1950
|
+
deliveryProtection,
|
|
1951
|
+
allowedOrigins,
|
|
1933
1952
|
pickerOptions,
|
|
1934
1953
|
onUploadDone,
|
|
1935
1954
|
onFileUploadFinished,
|