img-upload-kit 0.0.0-stage → 2.0.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/CHANGELOG.md +89 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +192 -0
- package/README.md +242 -2
- package/dist/bin.js +1186 -0
- package/dist/bin.js.map +1 -0
- package/dist/blur.cjs +41 -0
- package/dist/blur.cjs.map +1 -0
- package/dist/blur.d.cts +17 -0
- package/dist/blur.d.ts +17 -0
- package/dist/blur.js +41 -0
- package/dist/blur.js.map +1 -0
- 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-2OVNXHTN.cjs +514 -0
- package/dist/chunk-2OVNXHTN.cjs.map +1 -0
- package/dist/chunk-3ICG2SLL.cjs +135 -0
- package/dist/chunk-3ICG2SLL.cjs.map +1 -0
- package/dist/chunk-4ASFG3KB.js +514 -0
- package/dist/chunk-4ASFG3KB.js.map +1 -0
- package/dist/chunk-AI3JJLSA.js +2370 -0
- package/dist/chunk-AI3JJLSA.js.map +1 -0
- package/dist/chunk-CV23O4YU.js +145 -0
- package/dist/chunk-CV23O4YU.js.map +1 -0
- package/dist/chunk-G7LLSKBV.js +158 -0
- package/dist/chunk-G7LLSKBV.js.map +1 -0
- package/dist/chunk-IB3TEDLR.js +294 -0
- package/dist/chunk-IB3TEDLR.js.map +1 -0
- package/dist/chunk-KJVYWKIG.cjs +158 -0
- package/dist/chunk-KJVYWKIG.cjs.map +1 -0
- package/dist/chunk-NEDBIIE3.cjs +226 -0
- package/dist/chunk-NEDBIIE3.cjs.map +1 -0
- package/dist/chunk-QHU7FUO4.cjs +37 -0
- package/dist/chunk-QHU7FUO4.cjs.map +1 -0
- package/dist/chunk-QQHZJKNA.js +135 -0
- package/dist/chunk-QQHZJKNA.js.map +1 -0
- package/dist/chunk-S2IFAWJK.js +37 -0
- package/dist/chunk-S2IFAWJK.js.map +1 -0
- package/dist/chunk-TT5C7K3Q.js +226 -0
- package/dist/chunk-TT5C7K3Q.js.map +1 -0
- package/dist/chunk-U2TWTEBY.cjs +2370 -0
- package/dist/chunk-U2TWTEBY.cjs.map +1 -0
- package/dist/chunk-WISG74VL.cjs +294 -0
- package/dist/chunk-WISG74VL.cjs.map +1 -0
- package/dist/chunk-YRXXQUDE.cjs +145 -0
- package/dist/chunk-YRXXQUDE.cjs.map +1 -0
- package/dist/cli.cjs +1241 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +99 -0
- package/dist/cli.d.ts +99 -0
- package/dist/cli.js +1194 -0
- package/dist/cli.js.map +1 -0
- package/dist/headless.cjs +27 -0
- package/dist/headless.cjs.map +1 -0
- package/dist/headless.d.cts +4 -0
- package/dist/headless.d.ts +4 -0
- package/dist/headless.js +27 -0
- package/dist/headless.js.map +1 -0
- package/dist/index-BIdnqtW8.d.ts +234 -0
- package/dist/index-Brk_uHs6.d.cts +234 -0
- package/dist/index-CPMlaQsC.d.ts +635 -0
- package/dist/index-DHINe-rA.d.cts +635 -0
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +43 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/memoryAdapter-Ck0ccZbF.d.cts +25 -0
- package/dist/memoryAdapter-DxEKaG0b.d.ts +25 -0
- package/dist/placeholders-C4ftwaII.d.ts +5 -0
- package/dist/placeholders-x_3rt65M.d.cts +5 -0
- package/dist/react.cjs +3779 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +476 -0
- package/dist/react.d.ts +476 -0
- package/dist/react.js +3779 -0
- package/dist/react.js.map +1 -0
- 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 +1801 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +142 -0
- package/dist/server.d.ts +142 -0
- package/dist/server.js +1801 -0
- package/dist/server.js.map +1 -0
- package/dist/storage.cjs +447 -0
- package/dist/storage.cjs.map +1 -0
- package/dist/storage.d.cts +163 -0
- package/dist/storage.d.ts +163 -0
- package/dist/storage.js +447 -0
- package/dist/storage.js.map +1 -0
- 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 +1122 -0
- 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/upload-C5Trd6Dh.d.cts +154 -0
- package/dist/upload-C5Trd6Dh.d.ts +154 -0
- package/package.json +198 -4
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/blur/blurDataUtils.ts
|
|
2
|
+
var _blurhash = require('blurhash');
|
|
3
|
+
function formatCssUrl(rawUrl) {
|
|
4
|
+
const escaped = rawUrl.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/[\r\n]/g, "");
|
|
5
|
+
return `url("${escaped}")`;
|
|
6
|
+
}
|
|
7
|
+
function getBlurData(image) {
|
|
8
|
+
if (!image) {
|
|
9
|
+
return {
|
|
10
|
+
blurDataUrl: "",
|
|
11
|
+
blurColor: "#E5E7EB",
|
|
12
|
+
hasBlur: false
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
blurDataUrl: image.blurDataUrl || "",
|
|
17
|
+
blurColor: image.blurColor || "#E5E7EB",
|
|
18
|
+
...image.blurHash ? { blurHash: image.blurHash } : {},
|
|
19
|
+
hasBlur: Boolean(image.blurDataUrl || image.blurHash)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function getImagePropsWithBlur(image) {
|
|
23
|
+
if (!_optionalChain([image, 'optionalAccess', _ => _.url])) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const blurData = getBlurData(image);
|
|
27
|
+
return {
|
|
28
|
+
src: image.url,
|
|
29
|
+
alt: image.altText || "",
|
|
30
|
+
width: image.width || 1920,
|
|
31
|
+
height: image.height || 1080,
|
|
32
|
+
...blurData.blurDataUrl && {
|
|
33
|
+
placeholder: "blur",
|
|
34
|
+
blurDataURL: blurData.blurDataUrl
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function getBackgroundPropsWithBlur(image) {
|
|
39
|
+
if (!_optionalChain([image, 'optionalAccess', _2 => _2.url])) {
|
|
40
|
+
return {
|
|
41
|
+
backgroundColor: "#E5E7EB"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const blurData = getBlurData(image);
|
|
45
|
+
return {
|
|
46
|
+
backgroundColor: blurData.blurColor,
|
|
47
|
+
backgroundImage: formatCssUrl(image.url),
|
|
48
|
+
backgroundSize: "cover",
|
|
49
|
+
backgroundPosition: "center",
|
|
50
|
+
backgroundRepeat: "no-repeat"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function getBlurPlaceholderStyle(image) {
|
|
54
|
+
const blurData = getBlurData(image);
|
|
55
|
+
if (!blurData.blurDataUrl) {
|
|
56
|
+
return {
|
|
57
|
+
backgroundColor: blurData.blurColor
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
backgroundColor: blurData.blurColor,
|
|
62
|
+
backgroundImage: formatCssUrl(blurData.blurDataUrl),
|
|
63
|
+
backgroundSize: "cover",
|
|
64
|
+
backgroundPosition: "center",
|
|
65
|
+
filter: "blur(20px)",
|
|
66
|
+
transform: "scale(1.1)"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function hasBlurData(image) {
|
|
70
|
+
return Boolean(_optionalChain([image, 'optionalAccess', _3 => _3.blurDataUrl]) || _optionalChain([image, 'optionalAccess', _4 => _4.blurColor]) || _optionalChain([image, 'optionalAccess', _5 => _5.blurHash]));
|
|
71
|
+
}
|
|
72
|
+
function getBulkBlurData(images) {
|
|
73
|
+
return images.map(getBlurData);
|
|
74
|
+
}
|
|
75
|
+
function createSrcSetWithBlur(image) {
|
|
76
|
+
if (!_optionalChain([image, 'optionalAccess', _6 => _6.url])) return "";
|
|
77
|
+
if (image.width && image.width > 0) {
|
|
78
|
+
return `${image.url} ${image.width}w`;
|
|
79
|
+
}
|
|
80
|
+
return image.url;
|
|
81
|
+
}
|
|
82
|
+
function preloadImageWithBlur(image) {
|
|
83
|
+
if (!_optionalChain([image, 'optionalAccess', _7 => _7.url]) || typeof document === "undefined" || !document.head) return;
|
|
84
|
+
const existingLinks = document.head.querySelectorAll(
|
|
85
|
+
'link[rel="preload"][as="image"]'
|
|
86
|
+
);
|
|
87
|
+
for (let i = 0; i < existingLinks.length; i++) {
|
|
88
|
+
const el = existingLinks[i];
|
|
89
|
+
if (el.getAttribute("href") === image.url || el.href === image.url) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const link = document.createElement("link");
|
|
94
|
+
link.rel = "preload";
|
|
95
|
+
link.as = "image";
|
|
96
|
+
link.href = image.url;
|
|
97
|
+
document.head.appendChild(link);
|
|
98
|
+
}
|
|
99
|
+
function extractImageUrls(image) {
|
|
100
|
+
if (!_optionalChain([image, 'optionalAccess', _8 => _8.url])) return [];
|
|
101
|
+
return [image.url];
|
|
102
|
+
}
|
|
103
|
+
function decodeBlurHashToDataURL(hash, width = 32, height = 32, punch = 1) {
|
|
104
|
+
if (!hash || typeof document === "undefined") return null;
|
|
105
|
+
const validation = _blurhash.isBlurhashValid.call(void 0, hash);
|
|
106
|
+
if (!validation.result) return null;
|
|
107
|
+
try {
|
|
108
|
+
const pixels = _blurhash.decode.call(void 0, hash, width, height, punch);
|
|
109
|
+
const canvas = document.createElement("canvas");
|
|
110
|
+
canvas.width = width;
|
|
111
|
+
canvas.height = height;
|
|
112
|
+
const ctx = canvas.getContext("2d");
|
|
113
|
+
if (!ctx) return null;
|
|
114
|
+
const imageData = ctx.createImageData(width, height);
|
|
115
|
+
imageData.data.set(pixels);
|
|
116
|
+
ctx.putImageData(imageData, 0, 0);
|
|
117
|
+
return canvas.toDataURL("image/png");
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
exports.getBlurData = getBlurData; exports.getImagePropsWithBlur = getImagePropsWithBlur; exports.getBackgroundPropsWithBlur = getBackgroundPropsWithBlur; exports.getBlurPlaceholderStyle = getBlurPlaceholderStyle; exports.hasBlurData = hasBlurData; exports.getBulkBlurData = getBulkBlurData; exports.createSrcSetWithBlur = createSrcSetWithBlur; exports.preloadImageWithBlur = preloadImageWithBlur; exports.extractImageUrls = extractImageUrls; exports.decodeBlurHashToDataURL = decodeBlurHashToDataURL;
|
|
135
|
+
//# sourceMappingURL=chunk-3ICG2SLL.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-3ICG2SLL.cjs","../src/blur/blurDataUtils.ts"],"names":[],"mappings":"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","file":"/home/runner/work/image-upload-kit/image-upload-kit/dist/chunk-3ICG2SLL.cjs","sourcesContent":[null,"import { decode as decodeBlurHash, isBlurhashValid } from 'blurhash';\nimport type { BlurData, BlurImageSource } from './types';\n\nexport interface BlurPlaceholderStyle {\n backgroundColor: string;\n backgroundImage?: string;\n backgroundSize?: string;\n backgroundPosition?: string;\n backgroundRepeat?: string;\n filter?: string;\n transform?: string;\n}\n\nfunction formatCssUrl(rawUrl: string): string {\n const escaped = rawUrl\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/[\\r\\n]/g, '');\n return `url(\"${escaped}\")`;\n}\n\nexport function getBlurData(\n image: BlurImageSource | null | undefined,\n): BlurData {\n if (!image) {\n return {\n blurDataUrl: '',\n blurColor: '#E5E7EB',\n hasBlur: false,\n };\n }\n\n return {\n blurDataUrl: image.blurDataUrl || '',\n blurColor: image.blurColor || '#E5E7EB',\n ...(image.blurHash ? { blurHash: image.blurHash } : {}),\n hasBlur: Boolean(image.blurDataUrl || image.blurHash),\n };\n}\n\nexport function getImagePropsWithBlur(\n image: BlurImageSource | null | undefined,\n) {\n if (!image?.url) {\n return null;\n }\n\n const blurData = getBlurData(image);\n\n return {\n src: image.url,\n alt: image.altText || '',\n width: image.width || 1920,\n height: image.height || 1080,\n ...(blurData.blurDataUrl && {\n placeholder: 'blur' as const,\n blurDataURL: blurData.blurDataUrl,\n }),\n };\n}\n\nexport function getBackgroundPropsWithBlur(\n image: BlurImageSource | null | undefined,\n): BlurPlaceholderStyle {\n if (!image?.url) {\n return {\n backgroundColor: '#E5E7EB',\n };\n }\n\n const blurData = getBlurData(image);\n\n return {\n backgroundColor: blurData.blurColor,\n backgroundImage: formatCssUrl(image.url),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat',\n };\n}\n\nexport function getBlurPlaceholderStyle(\n image: BlurImageSource | null | undefined,\n): BlurPlaceholderStyle {\n const blurData = getBlurData(image);\n\n if (!blurData.blurDataUrl) {\n return {\n backgroundColor: blurData.blurColor,\n };\n }\n\n return {\n backgroundColor: blurData.blurColor,\n backgroundImage: formatCssUrl(blurData.blurDataUrl),\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n filter: 'blur(20px)',\n transform: 'scale(1.1)',\n };\n}\n\nexport function hasBlurData(\n image: BlurImageSource | null | undefined,\n): boolean {\n return Boolean(image?.blurDataUrl || image?.blurColor || image?.blurHash);\n}\n\nexport function getBulkBlurData(\n images: Array<BlurImageSource | null | undefined>,\n): BlurData[] {\n return images.map(getBlurData);\n}\n\n/**\n * Builds a responsive `srcset` descriptor for the full-resolution image URL.\n * Never includes the low-resolution LQIP placeholder in `srcset` so browsers\n * cannot accidentally select the blurred placeholder as the final image.\n */\nexport function createSrcSetWithBlur(\n image: BlurImageSource | null | undefined,\n): string {\n if (!image?.url) return '';\n if (image.width && image.width > 0) {\n return `${image.url} ${image.width}w`;\n }\n return image.url;\n}\n\n/**\n * Preloads the target image URL in `document.head` via `<link rel=\"preload\" as=\"image\">`.\n * Does not set `imagesrcset` to the LQIP data URL and deduplicates existing preload links.\n */\nexport function preloadImageWithBlur(\n image: BlurImageSource | null | undefined,\n): void {\n if (!image?.url || typeof document === 'undefined' || !document.head) return;\n\n const existingLinks = document.head.querySelectorAll(\n 'link[rel=\"preload\"][as=\"image\"]',\n );\n for (let i = 0; i < existingLinks.length; i++) {\n const el = existingLinks[i] as HTMLLinkElement;\n if (el.getAttribute('href') === image.url || el.href === image.url) {\n return;\n }\n }\n\n const link = document.createElement('link');\n link.rel = 'preload';\n link.as = 'image';\n link.href = image.url;\n document.head.appendChild(link);\n}\n\nexport function extractImageUrls(\n image: BlurImageSource | null | undefined,\n): string[] {\n if (!image?.url) return [];\n return [image.url];\n}\n\n/**\n * Browser helper: Decodes a compact BlurHash string (e.g. \"LEHV6nWB2yk8pyo0adR*.7kCMdnj\")\n * into a `data:image/png;base64,...` URL using an offscreen `<canvas>`.\n */\nexport function decodeBlurHashToDataURL(\n hash: string,\n width: number = 32,\n height: number = 32,\n punch: number = 1,\n): string | null {\n if (!hash || typeof document === 'undefined') return null;\n const validation = isBlurhashValid(hash);\n if (!validation.result) return null;\n\n try {\n const pixels = decodeBlurHash(hash, width, height, punch);\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const imageData = ctx.createImageData(width, height);\n imageData.data.set(pixels);\n ctx.putImageData(imageData, 0, 0);\n return canvas.toDataURL('image/png');\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -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-4ASFG3KB.js.map
|