img-upload-kit 1.1.1 → 2.0.1
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/CHANGELOG.md +89 -74
- package/MIGRATION.md +192 -0
- package/README.md +243 -259
- package/dist/bin.js +1186 -0
- package/dist/bin.js.map +1 -0
- package/dist/blur.cjs +40 -412
- package/dist/blur.cjs.map +1 -1
- package/dist/blur.d.cts +5 -5
- package/dist/blur.d.ts +5 -5
- package/dist/blur.js +39 -393
- package/dist/blur.js.map +1 -1
- package/dist/blurDataUtils-DveBRtqE.d.cts +43 -0
- package/dist/blurDataUtils-yx9Q6HlU.d.ts +43 -0
- package/dist/blurService-D5TpMNji.d.ts +83 -0
- package/dist/blurService-DacwylJx.d.cts +83 -0
- package/dist/chunk-25GYKQ6J.js +158 -0
- package/dist/chunk-25GYKQ6J.js.map +1 -0
- package/dist/chunk-34L4TEE3.cjs +226 -0
- package/dist/chunk-34L4TEE3.cjs.map +1 -0
- package/dist/chunk-DFCSN3X7.cjs +158 -0
- package/dist/chunk-DFCSN3X7.cjs.map +1 -0
- package/dist/chunk-EWVIL57J.js +294 -0
- package/dist/chunk-EWVIL57J.js.map +1 -0
- package/dist/chunk-FP5YIWG5.cjs +135 -0
- package/dist/chunk-FP5YIWG5.cjs.map +1 -0
- package/dist/chunk-HILELQGS.js +226 -0
- package/dist/chunk-HILELQGS.js.map +1 -0
- package/dist/chunk-IDLLCVGB.js +2370 -0
- package/dist/chunk-IDLLCVGB.js.map +1 -0
- package/dist/chunk-J6A44Y3W.js +514 -0
- package/dist/chunk-J6A44Y3W.js.map +1 -0
- package/dist/chunk-LOSVFO56.cjs +145 -0
- package/dist/chunk-LOSVFO56.cjs.map +1 -0
- package/dist/chunk-MBQJ6NVJ.cjs +294 -0
- package/dist/chunk-MBQJ6NVJ.cjs.map +1 -0
- package/dist/chunk-MHGQEBPW.js +145 -0
- package/dist/chunk-MHGQEBPW.js.map +1 -0
- package/dist/chunk-QHU7FUO4.cjs +37 -0
- package/dist/chunk-QHU7FUO4.cjs.map +1 -0
- package/dist/chunk-S2IFAWJK.js +37 -0
- package/dist/chunk-S2IFAWJK.js.map +1 -0
- package/dist/chunk-TA5Y64JM.cjs +2370 -0
- package/dist/chunk-TA5Y64JM.cjs.map +1 -0
- package/dist/chunk-VYVZVUNI.js +135 -0
- package/dist/chunk-VYVZVUNI.js.map +1 -0
- package/dist/chunk-XKQMCY5Z.cjs +514 -0
- package/dist/chunk-XKQMCY5Z.cjs.map +1 -0
- package/dist/cli.cjs +273 -76
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +269 -73
- package/dist/cli.js.map +1 -1
- package/dist/headless.cjs +8 -1880
- package/dist/headless.cjs.map +1 -1
- package/dist/headless.d.cts +3 -1
- package/dist/headless.d.ts +3 -1
- package/dist/headless.js +25 -1886
- package/dist/headless.js.map +1 -1
- package/dist/index-BIdnqtW8.d.ts +234 -0
- package/dist/index-Brk_uHs6.d.cts +234 -0
- package/dist/{index-FVXXKf2v.d.ts → index-CPMlaQsC.d.ts} +76 -153
- package/dist/{index-FVXXKf2v.d.cts → index-DHINe-rA.d.cts} +76 -153
- package/dist/index.cjs +64 -532
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +63 -504
- package/dist/index.js.map +1 -1
- package/dist/{memoryAdapter-CBu7_3Pp.d.ts → memoryAdapter-Ck0ccZbF.d.cts} +3 -2
- package/dist/{memoryAdapter-BJjyLR-1.d.cts → memoryAdapter-DxEKaG0b.d.ts} +3 -2
- package/dist/placeholders-C4ftwaII.d.ts +5 -0
- package/dist/placeholders-x_3rt65M.d.cts +5 -0
- package/dist/react.cjs +1952 -3257
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +188 -50
- package/dist/react.d.ts +188 -50
- package/dist/react.js +1763 -3013
- package/dist/react.js.map +1 -1
- package/dist/server-handler.cjs +14 -0
- package/dist/server-handler.cjs.map +1 -0
- package/dist/server-handler.d.cts +3 -0
- package/dist/server-handler.d.ts +3 -0
- package/dist/server-handler.js +14 -0
- package/dist/server-handler.js.map +1 -0
- package/dist/server.cjs +1096 -532
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +77 -154
- package/dist/server.d.ts +77 -154
- package/dist/server.js +1050 -448
- package/dist/server.js.map +1 -1
- package/dist/storage.cjs +239 -232
- package/dist/storage.cjs.map +1 -1
- package/dist/storage.d.cts +21 -5
- package/dist/storage.d.ts +21 -5
- package/dist/storage.js +227 -207
- package/dist/storage.js.map +1 -1
- package/dist/styles/animated-image.css +112 -0
- package/dist/styles/dropzone-and-tiles.css +247 -0
- package/dist/styles/modals-and-dock.css +248 -0
- package/dist/styles/primitives.css +197 -0
- package/dist/styles/quality-and-inputs.css +184 -0
- package/dist/styles/tokens.css +123 -0
- package/dist/styles.css +235 -220
- package/dist/types-BrP-o9nd.d.cts +85 -0
- package/dist/types-sZ3KoFQY.d.ts +85 -0
- package/dist/types-yShGNQnW.d.cts +61 -0
- package/dist/types-yShGNQnW.d.ts +61 -0
- package/dist/{types-CiLY0DwB.d.cts → upload-C5Trd6Dh.d.cts} +27 -53
- package/dist/{types-CiLY0DwB.d.ts → upload-C5Trd6Dh.d.ts} +27 -53
- package/package.json +92 -32
- package/dist/blurService-DqsnfPTd.d.cts +0 -172
- package/dist/blurService-DqsnfPTd.d.ts +0 -172
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parsePublicStorageUrl
|
|
3
|
+
} from "./chunk-S2IFAWJK.js";
|
|
4
|
+
|
|
5
|
+
// src/server/handler/responses.ts
|
|
6
|
+
var UploadValidationError = class extends Error {
|
|
7
|
+
status = 400;
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "UploadValidationError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function isClientValidationError(error) {
|
|
14
|
+
if (!error) return false;
|
|
15
|
+
if (error instanceof UploadValidationError) return true;
|
|
16
|
+
if (typeof error === "object" && error !== null && "status" in error && error.status === 400) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof Error && (error.name === "UploadValidationError" || error.name === "ValidationError")) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function jsonResponse(body, status = 200) {
|
|
25
|
+
return new Response(JSON.stringify(body), {
|
|
26
|
+
status,
|
|
27
|
+
headers: { "Content-Type": "application/json" }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function handleUnexpectedError(error, ctx, onError) {
|
|
31
|
+
if (isClientValidationError(error)) {
|
|
32
|
+
const message = error instanceof Error ? error.message : "Invalid upload request";
|
|
33
|
+
return jsonResponse({ error: message }, 400);
|
|
34
|
+
}
|
|
35
|
+
if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") {
|
|
36
|
+
const status = error.status;
|
|
37
|
+
if (status >= 400 && status < 500) {
|
|
38
|
+
const message = error instanceof Error ? error.message : "Request rejected";
|
|
39
|
+
return jsonResponse({ error: message }, status);
|
|
40
|
+
}
|
|
41
|
+
if (status === 503) {
|
|
42
|
+
return jsonResponse(
|
|
43
|
+
{ error: "Server busy, please try again later" },
|
|
44
|
+
503
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
onError?.(error, ctx);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
return jsonResponse({ error: "Internal server error" }, 500);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/server/handler/guards.ts
|
|
56
|
+
var DEFAULT_FALLBACK_MAX_MB = 25;
|
|
57
|
+
var MIN_DEFAULT_BODY_BYTES = 1024 * 1024;
|
|
58
|
+
function resolveMaxBodyBytes(options) {
|
|
59
|
+
if (typeof options.maxBodyBytes === "number" && Number.isFinite(options.maxBodyBytes) && options.maxBodyBytes > 0) {
|
|
60
|
+
return Math.floor(options.maxBodyBytes);
|
|
61
|
+
}
|
|
62
|
+
const uploaderMaxMB = typeof options.uploader.getMaxSizeMB === "function" ? options.uploader.getMaxSizeMB(options.allowedPresets) : DEFAULT_FALLBACK_MAX_MB;
|
|
63
|
+
const computed = Math.ceil(1.25 * uploaderMaxMB * 1024 * 1024);
|
|
64
|
+
return Math.max(MIN_DEFAULT_BODY_BYTES, computed);
|
|
65
|
+
}
|
|
66
|
+
function extractRequestOrigin(request) {
|
|
67
|
+
const originHeader = request.headers.get("origin");
|
|
68
|
+
if (originHeader && originHeader !== "null") {
|
|
69
|
+
return originHeader.trim();
|
|
70
|
+
}
|
|
71
|
+
const refererHeader = request.headers.get("referer");
|
|
72
|
+
if (refererHeader) {
|
|
73
|
+
try {
|
|
74
|
+
return new URL(refererHeader).origin;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function checkAllowedOrigin(request, allowedOrigins) {
|
|
82
|
+
if (!allowedOrigins) return null;
|
|
83
|
+
const origin = extractRequestOrigin(request);
|
|
84
|
+
if (!origin) {
|
|
85
|
+
return jsonResponse({ error: "Forbidden origin" }, 403);
|
|
86
|
+
}
|
|
87
|
+
let allowed = false;
|
|
88
|
+
try {
|
|
89
|
+
allowed = typeof allowedOrigins === "function" ? Boolean(allowedOrigins(origin)) : allowedOrigins.includes(origin);
|
|
90
|
+
} catch {
|
|
91
|
+
allowed = false;
|
|
92
|
+
}
|
|
93
|
+
if (!allowed) {
|
|
94
|
+
return jsonResponse({ error: "Forbidden origin" }, 403);
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
async function runAuthorize(authorize, ctx) {
|
|
99
|
+
if (!authorize) return null;
|
|
100
|
+
try {
|
|
101
|
+
const allowed = await authorize(ctx);
|
|
102
|
+
if (!allowed) {
|
|
103
|
+
return jsonResponse({ error: "Unauthorized" }, 401);
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
} catch {
|
|
107
|
+
return jsonResponse({ error: "Unauthorized" }, 401);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function readBoundedFormData(request, maxBodyBytes) {
|
|
111
|
+
const contentLengthRaw = request.headers.get("content-length");
|
|
112
|
+
if (contentLengthRaw !== null && contentLengthRaw.trim() !== "") {
|
|
113
|
+
const contentLength = Number(contentLengthRaw);
|
|
114
|
+
if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {
|
|
115
|
+
return {
|
|
116
|
+
earlyErrorResponse: jsonResponse(
|
|
117
|
+
{
|
|
118
|
+
error: `Payload too large: request body (${contentLength} bytes) exceeds limit of ${maxBodyBytes} bytes`
|
|
119
|
+
},
|
|
120
|
+
413
|
|
121
|
+
)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!request.body) {
|
|
126
|
+
try {
|
|
127
|
+
const formData = await request.formData();
|
|
128
|
+
return { formData };
|
|
129
|
+
} catch {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const reader = request.body.getReader();
|
|
134
|
+
const chunks = [];
|
|
135
|
+
let totalBytes = 0;
|
|
136
|
+
try {
|
|
137
|
+
while (true) {
|
|
138
|
+
const { done, value } = await reader.read();
|
|
139
|
+
if (done) break;
|
|
140
|
+
if (value) {
|
|
141
|
+
totalBytes += value.byteLength;
|
|
142
|
+
if (totalBytes > maxBodyBytes) {
|
|
143
|
+
await reader.cancel().catch(() => {
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
earlyErrorResponse: jsonResponse(
|
|
147
|
+
{
|
|
148
|
+
error: `Payload too large: streamed body exceeds limit of ${maxBodyBytes} bytes`
|
|
149
|
+
},
|
|
150
|
+
413
|
|
151
|
+
)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
chunks.push(value);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
try {
|
|
159
|
+
reader.releaseLock();
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (totalBytes === 0) {
|
|
164
|
+
return {};
|
|
165
|
+
}
|
|
166
|
+
const combined = new Uint8Array(totalBytes);
|
|
167
|
+
let offset = 0;
|
|
168
|
+
for (const chunk of chunks) {
|
|
169
|
+
combined.set(chunk, offset);
|
|
170
|
+
offset += chunk.byteLength;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const formData = await new Response(combined, {
|
|
174
|
+
headers: request.headers
|
|
175
|
+
}).formData();
|
|
176
|
+
return { formData };
|
|
177
|
+
} catch {
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/server/handler/deleteRoute.ts
|
|
183
|
+
function parseVirtualHostedS3Bucket(url) {
|
|
184
|
+
try {
|
|
185
|
+
const parsed = new URL(url);
|
|
186
|
+
const s3Match = parsed.hostname.match(
|
|
187
|
+
/^([a-z0-9.-]+)\.s3(?:[.-][a-z0-9-]+)?\.amazonaws\.com$/i
|
|
188
|
+
);
|
|
189
|
+
if (s3Match) {
|
|
190
|
+
const key = decodeURIComponent(parsed.pathname).replace(/^\/+/, "");
|
|
191
|
+
if (key) {
|
|
192
|
+
return { bucket: s3Match[1], path: key };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
function resolveAllowedBuckets(options) {
|
|
200
|
+
if (options.allowedBuckets && options.allowedBuckets.length > 0) {
|
|
201
|
+
return new Set(options.allowedBuckets);
|
|
202
|
+
}
|
|
203
|
+
if (typeof options.uploader.getAllowedBuckets === "function") {
|
|
204
|
+
return new Set(options.uploader.getAllowedBuckets());
|
|
205
|
+
}
|
|
206
|
+
if (options.uploader.defaultBucket) {
|
|
207
|
+
return /* @__PURE__ */ new Set([options.uploader.defaultBucket]);
|
|
208
|
+
}
|
|
209
|
+
return /* @__PURE__ */ new Set(["images"]);
|
|
210
|
+
}
|
|
211
|
+
function createDeleteHandler(options) {
|
|
212
|
+
const {
|
|
213
|
+
uploader,
|
|
214
|
+
authorize,
|
|
215
|
+
allowedOrigins,
|
|
216
|
+
allowClientBucketOverride = false,
|
|
217
|
+
allowedBuckets,
|
|
218
|
+
onError
|
|
219
|
+
} = options;
|
|
220
|
+
return async function DELETE(request) {
|
|
221
|
+
try {
|
|
222
|
+
const originError = checkAllowedOrigin(request, allowedOrigins);
|
|
223
|
+
if (originError) return originError;
|
|
224
|
+
const { searchParams } = new URL(request.url);
|
|
225
|
+
const url = searchParams.get("url");
|
|
226
|
+
const storagePath = searchParams.get("path");
|
|
227
|
+
const bucketParam = searchParams.get("bucket");
|
|
228
|
+
const allowedBucketSet = resolveAllowedBuckets(options);
|
|
229
|
+
const parsedFromUrl = url ? (typeof uploader.parseUrl === "function" ? uploader.parseUrl(url) : null) ?? parsePublicStorageUrl(url) ?? parseVirtualHostedS3Bucket(url) : null;
|
|
230
|
+
const resolvedPath = parsedFromUrl?.path ?? storagePath ?? void 0;
|
|
231
|
+
const resolvedBucket = parsedFromUrl?.bucket ?? (bucketParam || uploader.defaultBucket || "images");
|
|
232
|
+
const authError = await runAuthorize(authorize, {
|
|
233
|
+
action: "delete",
|
|
234
|
+
request,
|
|
235
|
+
...resolvedPath ? { path: resolvedPath } : {},
|
|
236
|
+
...resolvedBucket ? { bucket: resolvedBucket } : {}
|
|
237
|
+
});
|
|
238
|
+
if (authError) return authError;
|
|
239
|
+
if (url) {
|
|
240
|
+
if (parsedFromUrl?.bucket) {
|
|
241
|
+
if (allowedBuckets && allowedBuckets.length > 0 && !allowedBuckets.includes(parsedFromUrl.bucket)) {
|
|
242
|
+
return jsonResponse(
|
|
243
|
+
{ error: `Forbidden bucket "${parsedFromUrl.bucket}"` },
|
|
244
|
+
403
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (!allowClientBucketOverride && allowedBucketSet.size > 0 && !allowedBucketSet.has(parsedFromUrl.bucket)) {
|
|
248
|
+
return jsonResponse(
|
|
249
|
+
{ error: `Forbidden bucket "${parsedFromUrl.bucket}"` },
|
|
250
|
+
403
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const deleted = await uploader.deleteByUrl(url);
|
|
255
|
+
return jsonResponse({ success: deleted }, deleted ? 200 : 400);
|
|
256
|
+
}
|
|
257
|
+
if (bucketParam) {
|
|
258
|
+
if (allowedBuckets && allowedBuckets.length > 0 && !allowedBuckets.includes(bucketParam)) {
|
|
259
|
+
return jsonResponse(
|
|
260
|
+
{ error: `Forbidden bucket "${bucketParam}"` },
|
|
261
|
+
403
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
if (!allowClientBucketOverride && allowedBucketSet.size > 0 && !allowedBucketSet.has(bucketParam)) {
|
|
265
|
+
return jsonResponse(
|
|
266
|
+
{ error: `Forbidden bucket "${bucketParam}"` },
|
|
267
|
+
403
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!storagePath) {
|
|
272
|
+
return jsonResponse(
|
|
273
|
+
{ error: 'Missing required "path" or "url" query parameter' },
|
|
274
|
+
400
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const bucket = allowClientBucketOverride ? bucketParam || void 0 : void 0;
|
|
278
|
+
await uploader.deleteImage(storagePath, bucket);
|
|
279
|
+
return jsonResponse({ success: true }, 200);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return handleUnexpectedError(
|
|
282
|
+
error,
|
|
283
|
+
{ action: "delete", request },
|
|
284
|
+
onError
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/server/handler/parseUploadForm.ts
|
|
291
|
+
var ALLOWED_OUTPUT_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif"]);
|
|
292
|
+
function parseJsonRecord(raw) {
|
|
293
|
+
if (!raw) return void 0;
|
|
294
|
+
try {
|
|
295
|
+
const parsed = JSON.parse(raw);
|
|
296
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
297
|
+
const clean = {};
|
|
298
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
299
|
+
if (v !== void 0 && v !== null) {
|
|
300
|
+
clean[k] = String(v);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return Object.keys(clean).length > 0 ? clean : void 0;
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
return void 0;
|
|
308
|
+
}
|
|
309
|
+
function peekUploadAuthContext(formData, request, options) {
|
|
310
|
+
const preset = formData?.get("preset") || void 0;
|
|
311
|
+
const rawBucket = formData?.get("bucket") || void 0;
|
|
312
|
+
const rawFolder = formData?.get("folder") || void 0;
|
|
313
|
+
const presetConfig = preset && typeof options.uploader.resolvePreset === "function" ? options.uploader.resolvePreset(preset) : void 0;
|
|
314
|
+
const bucket = options.allowClientBucketOverride ? rawBucket || presetConfig?.bucket || options.uploader.defaultBucket : presetConfig?.bucket || options.uploader.defaultBucket;
|
|
315
|
+
const folder = options.allowClientOverrides?.folder ? rawFolder || presetConfig?.folder : presetConfig?.folder;
|
|
316
|
+
return {
|
|
317
|
+
action: "upload",
|
|
318
|
+
request,
|
|
319
|
+
...folder ? { path: folder } : {},
|
|
320
|
+
...bucket ? { bucket } : {},
|
|
321
|
+
...preset ? { preset } : {}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function parseUploadForm(formData, authCtx, options) {
|
|
325
|
+
if (!formData) {
|
|
326
|
+
return {
|
|
327
|
+
errorResponse: jsonResponse({ error: "No file provided" }, 400)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const file = formData.get("file");
|
|
331
|
+
if (!file || typeof file === "string" || typeof file.size === "number" && file.size === 0 && !file.name) {
|
|
332
|
+
return {
|
|
333
|
+
errorResponse: jsonResponse({ error: "No file provided" }, 400)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (typeof file.size === "number" && file.size === 0) {
|
|
337
|
+
return {
|
|
338
|
+
errorResponse: jsonResponse({ error: "File is empty" }, 400)
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const {
|
|
342
|
+
uploader,
|
|
343
|
+
allowedPresets,
|
|
344
|
+
allowClientOverrides,
|
|
345
|
+
allowClientBucketOverride = false,
|
|
346
|
+
allowedBuckets
|
|
347
|
+
} = options;
|
|
348
|
+
const preset = formData.get("preset") || void 0;
|
|
349
|
+
if (preset) {
|
|
350
|
+
if (allowedPresets && allowedPresets.length > 0 && !allowedPresets.includes(preset)) {
|
|
351
|
+
return {
|
|
352
|
+
errorResponse: jsonResponse(
|
|
353
|
+
{
|
|
354
|
+
error: `Invalid preset "${preset}". Allowed presets: ${allowedPresets.join(", ")}`
|
|
355
|
+
},
|
|
356
|
+
400
|
|
357
|
+
)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (typeof uploader.resolvePreset === "function" && !uploader.resolvePreset(preset)) {
|
|
361
|
+
return {
|
|
362
|
+
errorResponse: jsonResponse(
|
|
363
|
+
{ error: `Invalid preset "${preset}".` },
|
|
364
|
+
400
|
|
365
|
+
)
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const presetConfig = preset && typeof uploader.resolvePreset === "function" ? uploader.resolvePreset(preset) : void 0;
|
|
370
|
+
const rawBucket = formData.get("bucket") || void 0;
|
|
371
|
+
if (rawBucket && allowedBuckets && !allowedBuckets.includes(rawBucket)) {
|
|
372
|
+
return {
|
|
373
|
+
errorResponse: jsonResponse(
|
|
374
|
+
{ error: `Bucket "${rawBucket}" is not allowed` },
|
|
375
|
+
403
|
|
376
|
+
)
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const bucket = allowClientBucketOverride ? rawBucket : void 0;
|
|
380
|
+
let folder;
|
|
381
|
+
const rawFolder = formData.get("folder") || void 0;
|
|
382
|
+
if (rawFolder !== void 0) {
|
|
383
|
+
if (allowClientOverrides?.folder === true) {
|
|
384
|
+
folder = rawFolder;
|
|
385
|
+
} else if (typeof allowClientOverrides?.folder === "function") {
|
|
386
|
+
const scoped = allowClientOverrides.folder(rawFolder, authCtx);
|
|
387
|
+
if (scoped === null) {
|
|
388
|
+
return {
|
|
389
|
+
errorResponse: jsonResponse(
|
|
390
|
+
{ error: "Invalid or disallowed folder" },
|
|
391
|
+
400
|
|
392
|
+
)
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
folder = scoped || void 0;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const rawFilename = formData.get("customFilename") || void 0;
|
|
399
|
+
const customFilename = allowClientOverrides?.filename ? rawFilename : void 0;
|
|
400
|
+
const outputFormatRaw = formData.get("outputFormat");
|
|
401
|
+
if (outputFormatRaw && !ALLOWED_OUTPUT_FORMATS.has(outputFormatRaw)) {
|
|
402
|
+
return {
|
|
403
|
+
errorResponse: jsonResponse(
|
|
404
|
+
{
|
|
405
|
+
error: `Invalid output format "${outputFormatRaw}". Allowed formats: jpeg, png, webp, avif`
|
|
406
|
+
},
|
|
407
|
+
400
|
|
408
|
+
)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const outputFormat = allowClientOverrides?.format && outputFormatRaw ? outputFormatRaw : void 0;
|
|
412
|
+
const qualityRaw = formData.get("quality");
|
|
413
|
+
let quality;
|
|
414
|
+
if (qualityRaw !== null && qualityRaw.trim() !== "") {
|
|
415
|
+
const parsedQuality = Number(qualityRaw);
|
|
416
|
+
if (!Number.isFinite(parsedQuality) || !Number.isInteger(parsedQuality) || parsedQuality < 1 || parsedQuality > 100) {
|
|
417
|
+
return {
|
|
418
|
+
errorResponse: jsonResponse(
|
|
419
|
+
{
|
|
420
|
+
error: `Invalid quality "${qualityRaw}". Must be an integer between 1 and 100.`
|
|
421
|
+
},
|
|
422
|
+
400
|
|
423
|
+
)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (allowClientOverrides?.quality) {
|
|
427
|
+
quality = presetConfig?.quality ? Math.min(parsedQuality, presetConfig.quality) : parsedQuality;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const maxWidthRaw = formData.get("maxWidth");
|
|
431
|
+
const maxHeightRaw = formData.get("maxHeight");
|
|
432
|
+
let maxDimensions;
|
|
433
|
+
if (maxWidthRaw !== null && maxWidthRaw.trim() !== "" || maxHeightRaw !== null && maxHeightRaw.trim() !== "") {
|
|
434
|
+
const w = Number(maxWidthRaw);
|
|
435
|
+
const h = Number(maxHeightRaw);
|
|
436
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || w < 1 || h < 1) {
|
|
437
|
+
return {
|
|
438
|
+
errorResponse: jsonResponse(
|
|
439
|
+
{ error: "Invalid maxWidth or maxHeight dimensions" },
|
|
440
|
+
400
|
|
441
|
+
)
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (allowClientOverrides?.dimensions) {
|
|
445
|
+
const cap = presetConfig?.maxDimensions;
|
|
446
|
+
maxDimensions = cap ? {
|
|
447
|
+
width: Math.min(Math.floor(w), cap.width),
|
|
448
|
+
height: Math.min(Math.floor(h), cap.height)
|
|
449
|
+
} : { width: Math.floor(w), height: Math.floor(h) };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const altText = formData.get("altText") || void 0;
|
|
453
|
+
const metadata = parseJsonRecord(formData.get("metadata"));
|
|
454
|
+
return {
|
|
455
|
+
file,
|
|
456
|
+
uploadOptions: {
|
|
457
|
+
preset,
|
|
458
|
+
folder,
|
|
459
|
+
bucket,
|
|
460
|
+
altText,
|
|
461
|
+
customFilename,
|
|
462
|
+
...customFilename ? { isClientFilename: true } : {},
|
|
463
|
+
outputFormat,
|
|
464
|
+
quality,
|
|
465
|
+
maxDimensions,
|
|
466
|
+
metadata,
|
|
467
|
+
signal: authCtx.request.signal
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/server/handler/index.ts
|
|
473
|
+
function createUploadHttpHandler(options) {
|
|
474
|
+
const { uploader, authorize, allowedOrigins, onError } = options;
|
|
475
|
+
const maxBodyBytes = resolveMaxBodyBytes(options);
|
|
476
|
+
async function POST(request) {
|
|
477
|
+
try {
|
|
478
|
+
const originError = checkAllowedOrigin(request, allowedOrigins);
|
|
479
|
+
if (originError) return originError;
|
|
480
|
+
const { formData, earlyErrorResponse } = await readBoundedFormData(
|
|
481
|
+
request,
|
|
482
|
+
maxBodyBytes
|
|
483
|
+
);
|
|
484
|
+
if (earlyErrorResponse) return earlyErrorResponse;
|
|
485
|
+
const authCtx = peekUploadAuthContext(formData, request, options);
|
|
486
|
+
const authError = await runAuthorize(authorize, authCtx);
|
|
487
|
+
if (authError) return authError;
|
|
488
|
+
const parsed = parseUploadForm(formData, authCtx, options);
|
|
489
|
+
if (parsed.errorResponse) return parsed.errorResponse;
|
|
490
|
+
const result = await uploader.uploadImage(
|
|
491
|
+
parsed.file,
|
|
492
|
+
parsed.uploadOptions
|
|
493
|
+
);
|
|
494
|
+
return jsonResponse(result, 200);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
return handleUnexpectedError(
|
|
497
|
+
error,
|
|
498
|
+
{ action: "upload", request },
|
|
499
|
+
onError
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const DELETE = createDeleteHandler(options);
|
|
504
|
+
return { POST, DELETE };
|
|
505
|
+
}
|
|
506
|
+
var createUploadHandler = createUploadHttpHandler;
|
|
507
|
+
|
|
508
|
+
export {
|
|
509
|
+
UploadValidationError,
|
|
510
|
+
isClientValidationError,
|
|
511
|
+
createUploadHttpHandler,
|
|
512
|
+
createUploadHandler
|
|
513
|
+
};
|
|
514
|
+
//# sourceMappingURL=chunk-J6A44Y3W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/handler/responses.ts","../src/server/handler/guards.ts","../src/server/handler/deleteRoute.ts","../src/server/handler/parseUploadForm.ts","../src/server/handler/index.ts"],"sourcesContent":["import type { UploadErrorContext } from './options';\r\n\r\nexport class UploadValidationError extends Error {\r\n readonly status = 400;\r\n constructor(message: string) {\r\n super(message);\r\n this.name = 'UploadValidationError';\r\n }\r\n}\r\n\r\nexport function isClientValidationError(error: unknown): boolean {\r\n if (!error) return false;\r\n if (error instanceof UploadValidationError) return true;\r\n if (\r\n typeof error === 'object' &&\r\n error !== null &&\r\n 'status' in error &&\r\n (error as { status: unknown }).status === 400\r\n ) {\r\n return true;\r\n }\r\n if (\r\n error instanceof Error &&\r\n (error.name === 'UploadValidationError' || error.name === 'ValidationError')\r\n ) {\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nexport function jsonResponse(body: unknown, status: number = 200): Response {\r\n return new Response(JSON.stringify(body), {\r\n status,\r\n headers: { 'Content-Type': 'application/json' },\r\n });\r\n}\r\n\r\nexport function handleUnexpectedError(\r\n error: unknown,\r\n ctx: UploadErrorContext,\r\n onError?: (error: unknown, ctx: UploadErrorContext) => void,\r\n): Response {\r\n if (isClientValidationError(error)) {\r\n const message =\r\n error instanceof Error ? error.message : 'Invalid upload request';\r\n return jsonResponse({ error: message }, 400);\r\n }\r\n\r\n if (\r\n typeof error === 'object' &&\r\n error !== null &&\r\n 'status' in error &&\r\n typeof (error as { status: unknown }).status === 'number'\r\n ) {\r\n const status = (error as { status: number }).status;\r\n if (status >= 400 && status < 500) {\r\n const message =\r\n error instanceof Error ? error.message : 'Request rejected';\r\n return jsonResponse({ error: message }, status);\r\n }\r\n if (status === 503) {\r\n return jsonResponse(\r\n { error: 'Server busy, please try again later' },\r\n 503,\r\n );\r\n }\r\n }\r\n\r\n try {\r\n onError?.(error, ctx);\r\n } catch {\r\n // Ignore secondary logger failures\r\n }\r\n\r\n return jsonResponse({ error: 'Internal server error' }, 500);\r\n}\r\n","import type { AuthorizeContext, UploadHttpHandlerOptions } from './options';\r\nimport { jsonResponse } from './responses';\r\n\r\nconst DEFAULT_FALLBACK_MAX_MB = 25;\r\nconst MIN_DEFAULT_BODY_BYTES = 1024 * 1024; // 1 MB floor when maxBodyBytes is not explicitly set\r\n\r\nexport function resolveMaxBodyBytes<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): number {\r\n if (\r\n typeof options.maxBodyBytes === 'number' &&\r\n Number.isFinite(options.maxBodyBytes) &&\r\n options.maxBodyBytes > 0\r\n ) {\r\n return Math.floor(options.maxBodyBytes);\r\n }\r\n\r\n const uploaderMaxMB =\r\n typeof options.uploader.getMaxSizeMB === 'function'\r\n ? options.uploader.getMaxSizeMB(options.allowedPresets)\r\n : DEFAULT_FALLBACK_MAX_MB;\r\n\r\n const computed = Math.ceil(1.25 * uploaderMaxMB * 1024 * 1024);\r\n return Math.max(MIN_DEFAULT_BODY_BYTES, computed);\r\n}\r\n\r\nfunction extractRequestOrigin(request: Request): string | null {\r\n const originHeader = request.headers.get('origin');\r\n if (originHeader && originHeader !== 'null') {\r\n return originHeader.trim();\r\n }\r\n\r\n const refererHeader = request.headers.get('referer');\r\n if (refererHeader) {\r\n try {\r\n return new URL(refererHeader).origin;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport function checkAllowedOrigin(\r\n request: Request,\r\n allowedOrigins?: string[] | ((origin: string) => boolean),\r\n): Response | null {\r\n if (!allowedOrigins) return null;\r\n\r\n const origin = extractRequestOrigin(request);\r\n if (!origin) {\r\n return jsonResponse({ error: 'Forbidden origin' }, 403);\r\n }\r\n\r\n let allowed = false;\r\n try {\r\n allowed =\r\n typeof allowedOrigins === 'function'\r\n ? Boolean(allowedOrigins(origin))\r\n : allowedOrigins.includes(origin);\r\n } catch {\r\n allowed = false;\r\n }\r\n\r\n if (!allowed) {\r\n return jsonResponse({ error: 'Forbidden origin' }, 403);\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport async function runAuthorize(\r\n authorize: UploadHttpHandlerOptions['authorize'],\r\n ctx: AuthorizeContext,\r\n): Promise<Response | null> {\r\n if (!authorize) return null;\r\n\r\n try {\r\n const allowed = await authorize(ctx);\r\n if (!allowed) {\r\n return jsonResponse({ error: 'Unauthorized' }, 401);\r\n }\r\n return null;\r\n } catch {\r\n return jsonResponse({ error: 'Unauthorized' }, 401);\r\n }\r\n}\r\n\r\nexport interface BoundedFormDataResult {\r\n formData?: FormData;\r\n earlyErrorResponse?: Response;\r\n}\r\n\r\nexport async function readBoundedFormData(\r\n request: Request,\r\n maxBodyBytes: number,\r\n): Promise<BoundedFormDataResult> {\r\n const contentLengthRaw = request.headers.get('content-length');\r\n if (contentLengthRaw !== null && contentLengthRaw.trim() !== '') {\r\n const contentLength = Number(contentLengthRaw);\r\n if (Number.isFinite(contentLength) && contentLength > maxBodyBytes) {\r\n return {\r\n earlyErrorResponse: jsonResponse(\r\n {\r\n error: `Payload too large: request body (${contentLength} bytes) exceeds limit of ${maxBodyBytes} bytes`,\r\n },\r\n 413,\r\n ),\r\n };\r\n }\r\n }\r\n\r\n if (!request.body) {\r\n try {\r\n const formData = await request.formData();\r\n return { formData };\r\n } catch {\r\n return {};\r\n }\r\n }\r\n\r\n const reader = request.body.getReader();\r\n const chunks: Uint8Array[] = [];\r\n let totalBytes = 0;\r\n\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n if (value) {\r\n totalBytes += value.byteLength;\r\n if (totalBytes > maxBodyBytes) {\r\n await reader.cancel().catch(() => {});\r\n return {\r\n earlyErrorResponse: jsonResponse(\r\n {\r\n error: `Payload too large: streamed body exceeds limit of ${maxBodyBytes} bytes`,\r\n },\r\n 413,\r\n ),\r\n };\r\n }\r\n chunks.push(value);\r\n }\r\n }\r\n } finally {\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n // Ignore release errors\r\n }\r\n }\r\n\r\n if (totalBytes === 0) {\r\n return {};\r\n }\r\n\r\n const combined = new Uint8Array(totalBytes);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n combined.set(chunk, offset);\r\n offset += chunk.byteLength;\r\n }\r\n\r\n try {\r\n const formData = await new Response(combined, {\r\n headers: request.headers,\r\n }).formData();\r\n return { formData };\r\n } catch {\r\n return {};\r\n }\r\n}\r\n","import { parsePublicStorageUrl } from '../../storage/urlParser';\r\nimport { checkAllowedOrigin, runAuthorize } from './guards';\r\nimport type { UploadHttpHandlerOptions } from './options';\r\nimport { handleUnexpectedError, jsonResponse } from './responses';\r\n\r\nfunction parseVirtualHostedS3Bucket(url: string): {\r\n bucket: string;\r\n path: string;\r\n} | null {\r\n try {\r\n const parsed = new URL(url);\r\n const s3Match = parsed.hostname.match(\r\n /^([a-z0-9.-]+)\\.s3(?:[.-][a-z0-9-]+)?\\.amazonaws\\.com$/i,\r\n );\r\n if (s3Match) {\r\n const key = decodeURIComponent(parsed.pathname).replace(/^\\/+/, '');\r\n if (key) {\r\n return { bucket: s3Match[1], path: key };\r\n }\r\n }\r\n } catch {\r\n // Ignore invalid URLs\r\n }\r\n return null;\r\n}\r\n\r\nfunction resolveAllowedBuckets<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): Set<string> {\r\n if (options.allowedBuckets && options.allowedBuckets.length > 0) {\r\n return new Set(options.allowedBuckets);\r\n }\r\n if (typeof options.uploader.getAllowedBuckets === 'function') {\r\n return new Set(options.uploader.getAllowedBuckets());\r\n }\r\n if (options.uploader.defaultBucket) {\r\n return new Set([options.uploader.defaultBucket]);\r\n }\r\n return new Set(['images']);\r\n}\r\n\r\nexport function createDeleteHandler<TPreset extends string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n) {\r\n const {\r\n uploader,\r\n authorize,\r\n allowedOrigins,\r\n allowClientBucketOverride = false,\r\n allowedBuckets,\r\n onError,\r\n } = options;\r\n\r\n return async function DELETE(request: Request): Promise<Response> {\r\n try {\r\n const originError = checkAllowedOrigin(request, allowedOrigins);\r\n if (originError) return originError;\r\n\r\n const { searchParams } = new URL(request.url);\r\n const url = searchParams.get('url');\r\n const storagePath = searchParams.get('path');\r\n const bucketParam = searchParams.get('bucket');\r\n\r\n const allowedBucketSet = resolveAllowedBuckets(options);\r\n\r\n const parsedFromUrl = url\r\n ? (typeof uploader.parseUrl === 'function'\r\n ? uploader.parseUrl(url)\r\n : null) ??\r\n parsePublicStorageUrl(url) ??\r\n parseVirtualHostedS3Bucket(url)\r\n : null;\r\n\r\n const resolvedPath = parsedFromUrl?.path ?? storagePath ?? undefined;\r\n const resolvedBucket =\r\n parsedFromUrl?.bucket ??\r\n (bucketParam || uploader.defaultBucket || 'images');\r\n\r\n const authError = await runAuthorize(authorize, {\r\n action: 'delete',\r\n request,\r\n ...(resolvedPath ? { path: resolvedPath } : {}),\r\n ...(resolvedBucket ? { bucket: resolvedBucket } : {}),\r\n });\r\n if (authError) return authError;\r\n\r\n if (url) {\r\n if (parsedFromUrl?.bucket) {\r\n if (\r\n allowedBuckets &&\r\n allowedBuckets.length > 0 &&\r\n !allowedBuckets.includes(parsedFromUrl.bucket)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${parsedFromUrl.bucket}\"` },\r\n 403,\r\n );\r\n }\r\n if (\r\n !allowClientBucketOverride &&\r\n allowedBucketSet.size > 0 &&\r\n !allowedBucketSet.has(parsedFromUrl.bucket)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${parsedFromUrl.bucket}\"` },\r\n 403,\r\n );\r\n }\r\n }\r\n\r\n const deleted = await uploader.deleteByUrl(url);\r\n return jsonResponse({ success: deleted }, deleted ? 200 : 400);\r\n }\r\n\r\n if (bucketParam) {\r\n if (\r\n allowedBuckets &&\r\n allowedBuckets.length > 0 &&\r\n !allowedBuckets.includes(bucketParam)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${bucketParam}\"` },\r\n 403,\r\n );\r\n }\r\n if (\r\n !allowClientBucketOverride &&\r\n allowedBucketSet.size > 0 &&\r\n !allowedBucketSet.has(bucketParam)\r\n ) {\r\n return jsonResponse(\r\n { error: `Forbidden bucket \"${bucketParam}\"` },\r\n 403,\r\n );\r\n }\r\n }\r\n\r\n if (!storagePath) {\r\n return jsonResponse(\r\n { error: 'Missing required \"path\" or \"url\" query parameter' },\r\n 400,\r\n );\r\n }\r\n\r\n const bucket = allowClientBucketOverride\r\n ? bucketParam || undefined\r\n : undefined;\r\n\r\n await uploader.deleteImage(storagePath, bucket);\r\n return jsonResponse({ success: true }, 200);\r\n } catch (error) {\r\n return handleUnexpectedError(\r\n error,\r\n { action: 'delete', request },\r\n onError,\r\n );\r\n }\r\n };\r\n}\r\n","import type {\r\n ExplicitOutputFormat,\r\n ImageDimensions,\r\n ImageUploadOptions,\r\n} from '../../types';\r\nimport type { AuthorizeContext, UploadHttpHandlerOptions } from './options';\r\nimport { jsonResponse } from './responses';\r\n\r\nconst ALLOWED_OUTPUT_FORMATS = new Set(['jpeg', 'png', 'webp', 'avif']);\r\n\r\nfunction parseJsonRecord(\r\n raw: string | null,\r\n): Record<string, string> | undefined {\r\n if (!raw) return undefined;\r\n try {\r\n const parsed: unknown = JSON.parse(raw);\r\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\r\n const clean: Record<string, string> = {};\r\n for (const [k, v] of Object.entries(parsed)) {\r\n if (v !== undefined && v !== null) {\r\n clean[k] = String(v);\r\n }\r\n }\r\n return Object.keys(clean).length > 0 ? clean : undefined;\r\n }\r\n } catch {\r\n // Ignore malformed metadata JSON\r\n }\r\n return undefined;\r\n}\r\n\r\nexport function peekUploadAuthContext<TPreset extends string>(\r\n formData: FormData | undefined,\r\n request: Request,\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): AuthorizeContext {\r\n const preset =\r\n (formData?.get('preset') as string | null) || undefined;\r\n const rawBucket =\r\n (formData?.get('bucket') as string | null) || undefined;\r\n const rawFolder =\r\n (formData?.get('folder') as string | null) || undefined;\r\n const presetConfig =\r\n preset && typeof options.uploader.resolvePreset === 'function'\r\n ? options.uploader.resolvePreset(preset)\r\n : undefined;\r\n\r\n const bucket = options.allowClientBucketOverride\r\n ? rawBucket || presetConfig?.bucket || options.uploader.defaultBucket\r\n : presetConfig?.bucket || options.uploader.defaultBucket;\r\n\r\n const folder = options.allowClientOverrides?.folder\r\n ? rawFolder || presetConfig?.folder\r\n : presetConfig?.folder;\r\n\r\n return {\r\n action: 'upload',\r\n request,\r\n ...(folder ? { path: folder } : {}),\r\n ...(bucket ? { bucket } : {}),\r\n ...(preset ? { preset } : {}),\r\n };\r\n}\r\n\r\nexport interface ParsedUploadFormResult<TPreset extends string> {\r\n file?: File;\r\n uploadOptions?: ImageUploadOptions<TPreset>;\r\n errorResponse?: Response;\r\n}\r\n\r\nexport function parseUploadForm<TPreset extends string>(\r\n formData: FormData | undefined,\r\n authCtx: AuthorizeContext,\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n): ParsedUploadFormResult<TPreset> {\r\n if (!formData) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'No file provided' }, 400),\r\n };\r\n }\r\n\r\n const file = formData.get('file') as File | null;\r\n if (\r\n !file ||\r\n typeof file === 'string' ||\r\n (typeof file.size === 'number' && file.size === 0 && !file.name)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'No file provided' }, 400),\r\n };\r\n }\r\n if (typeof file.size === 'number' && file.size === 0) {\r\n return {\r\n errorResponse: jsonResponse({ error: 'File is empty' }, 400),\r\n };\r\n }\r\n\r\n const {\r\n uploader,\r\n allowedPresets,\r\n allowClientOverrides,\r\n allowClientBucketOverride = false,\r\n allowedBuckets,\r\n } = options;\r\n\r\n const preset = (formData.get('preset') as TPreset | null) || undefined;\r\n if (preset) {\r\n if (\r\n allowedPresets &&\r\n allowedPresets.length > 0 &&\r\n !allowedPresets.includes(preset)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid preset \"${preset}\". Allowed presets: ${allowedPresets.join(', ')}`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (\r\n typeof uploader.resolvePreset === 'function' &&\r\n !uploader.resolvePreset(preset)\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: `Invalid preset \"${preset}\".` },\r\n 400,\r\n ),\r\n };\r\n }\r\n }\r\n\r\n const presetConfig =\r\n preset && typeof uploader.resolvePreset === 'function'\r\n ? uploader.resolvePreset(preset)\r\n : undefined;\r\n\r\n const rawBucket = (formData.get('bucket') as string | null) || undefined;\r\n if (rawBucket && allowedBuckets && !allowedBuckets.includes(rawBucket)) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: `Bucket \"${rawBucket}\" is not allowed` },\r\n 403,\r\n ),\r\n };\r\n }\r\n const bucket = allowClientBucketOverride ? rawBucket : undefined;\r\n\r\n let folder: string | undefined;\r\n const rawFolder = (formData.get('folder') as string | null) || undefined;\r\n if (rawFolder !== undefined) {\r\n if (allowClientOverrides?.folder === true) {\r\n folder = rawFolder;\r\n } else if (typeof allowClientOverrides?.folder === 'function') {\r\n const scoped = allowClientOverrides.folder(rawFolder, authCtx);\r\n if (scoped === null) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: 'Invalid or disallowed folder' },\r\n 400,\r\n ),\r\n };\r\n }\r\n folder = scoped || undefined;\r\n }\r\n }\r\n\r\n const rawFilename =\r\n (formData.get('customFilename') as string | null) || undefined;\r\n const customFilename = allowClientOverrides?.filename\r\n ? rawFilename\r\n : undefined;\r\n\r\n const outputFormatRaw = formData.get('outputFormat') as string | null;\r\n if (outputFormatRaw && !ALLOWED_OUTPUT_FORMATS.has(outputFormatRaw)) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid output format \"${outputFormatRaw}\". Allowed formats: jpeg, png, webp, avif`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n const outputFormat =\r\n allowClientOverrides?.format && outputFormatRaw\r\n ? (outputFormatRaw as ExplicitOutputFormat)\r\n : undefined;\r\n\r\n const qualityRaw = formData.get('quality') as string | null;\r\n let quality: number | undefined;\r\n if (qualityRaw !== null && qualityRaw.trim() !== '') {\r\n const parsedQuality = Number(qualityRaw);\r\n if (\r\n !Number.isFinite(parsedQuality) ||\r\n !Number.isInteger(parsedQuality) ||\r\n parsedQuality < 1 ||\r\n parsedQuality > 100\r\n ) {\r\n return {\r\n errorResponse: jsonResponse(\r\n {\r\n error: `Invalid quality \"${qualityRaw}\". Must be an integer between 1 and 100.`,\r\n },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (allowClientOverrides?.quality) {\r\n quality = presetConfig?.quality\r\n ? Math.min(parsedQuality, presetConfig.quality)\r\n : parsedQuality;\r\n }\r\n }\r\n\r\n const maxWidthRaw = formData.get('maxWidth') as string | null;\r\n const maxHeightRaw = formData.get('maxHeight') as string | null;\r\n let maxDimensions: ImageDimensions | undefined;\r\n if (\r\n (maxWidthRaw !== null && maxWidthRaw.trim() !== '') ||\r\n (maxHeightRaw !== null && maxHeightRaw.trim() !== '')\r\n ) {\r\n const w = Number(maxWidthRaw);\r\n const h = Number(maxHeightRaw);\r\n if (!Number.isFinite(w) || !Number.isFinite(h) || w < 1 || h < 1) {\r\n return {\r\n errorResponse: jsonResponse(\r\n { error: 'Invalid maxWidth or maxHeight dimensions' },\r\n 400,\r\n ),\r\n };\r\n }\r\n if (allowClientOverrides?.dimensions) {\r\n const cap = presetConfig?.maxDimensions;\r\n maxDimensions = cap\r\n ? {\r\n width: Math.min(Math.floor(w), cap.width),\r\n height: Math.min(Math.floor(h), cap.height),\r\n }\r\n : { width: Math.floor(w), height: Math.floor(h) };\r\n }\r\n }\r\n\r\n const altText = (formData.get('altText') as string | null) || undefined;\r\n const metadata = parseJsonRecord(formData.get('metadata') as string | null);\r\n\r\n return {\r\n file,\r\n uploadOptions: {\r\n preset,\r\n folder,\r\n bucket,\r\n altText,\r\n customFilename,\r\n ...(customFilename ? { isClientFilename: true } : {}),\r\n outputFormat,\r\n quality,\r\n maxDimensions,\r\n metadata,\r\n signal: authCtx.request.signal,\r\n },\r\n };\r\n}\r\n","import { createDeleteHandler } from './deleteRoute';\r\nimport {\r\n checkAllowedOrigin,\r\n readBoundedFormData,\r\n resolveMaxBodyBytes,\r\n runAuthorize,\r\n} from './guards';\r\nimport type {\r\n AllowClientOverrides,\r\n AuthorizeContext,\r\n UploadErrorContext,\r\n UploadHttpHandlerOptions,\r\n} from './options';\r\nimport { parseUploadForm, peekUploadAuthContext } from './parseUploadForm';\r\nimport {\r\n handleUnexpectedError,\r\n isClientValidationError,\r\n jsonResponse,\r\n UploadValidationError,\r\n} from './responses';\r\n\r\nexport {\r\n type AllowClientOverrides,\r\n type AuthorizeContext,\r\n isClientValidationError,\r\n type UploadErrorContext,\r\n type UploadHttpHandlerOptions,\r\n UploadValidationError,\r\n};\r\n\r\n/**\r\n * Standard Web Fetch (`Request` -> `Response`) upload & delete route handlers.\r\n * Works across Next.js App Router, Remix, React Router, SvelteKit, Astro, Hono, and Node Fetch servers.\r\n */\r\nexport function createUploadHttpHandler<TPreset extends string = string>(\r\n options: UploadHttpHandlerOptions<TPreset>,\r\n) {\r\n const { uploader, authorize, allowedOrigins, onError } = options;\r\n const maxBodyBytes = resolveMaxBodyBytes(options);\r\n\r\n async function POST(request: Request): Promise<Response> {\r\n try {\r\n const originError = checkAllowedOrigin(request, allowedOrigins);\r\n if (originError) return originError;\r\n\r\n const { formData, earlyErrorResponse } = await readBoundedFormData(\r\n request,\r\n maxBodyBytes,\r\n );\r\n if (earlyErrorResponse) return earlyErrorResponse;\r\n\r\n const authCtx = peekUploadAuthContext(formData, request, options);\r\n const authError = await runAuthorize(authorize, authCtx);\r\n if (authError) return authError;\r\n\r\n const parsed = parseUploadForm(formData, authCtx, options);\r\n if (parsed.errorResponse) return parsed.errorResponse;\r\n\r\n const result = await uploader.uploadImage(\r\n parsed.file!,\r\n parsed.uploadOptions,\r\n );\r\n\r\n return jsonResponse(result, 200);\r\n } catch (error) {\r\n return handleUnexpectedError(\r\n error,\r\n { action: 'upload', request },\r\n onError,\r\n );\r\n }\r\n }\r\n\r\n const DELETE = createDeleteHandler(options);\r\n\r\n return { POST, DELETE };\r\n}\r\n\r\nexport const createUploadHandler = createUploadHttpHandler;\r\n"],"mappings":"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","names":[]}
|