img-upload-kit 1.1.1 → 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 +56 -41
- package/MIGRATION.md +192 -0
- package/README.md +138 -154
- 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-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 +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 +39 -24
- 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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,47 @@ All notable changes to `img-upload-kit` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.0.0] - 2026-11-06
|
|
9
|
+
|
|
10
|
+
See [`MIGRATION.md`](./MIGRATION.md) for step-by-step v1.x to v2.0.0 upgrade instructions and before/after examples.
|
|
11
|
+
|
|
12
|
+
### Breaking Changes
|
|
13
|
+
|
|
14
|
+
- **Dependencies**: Moved `sharp` and `lqip-modern` to optional `peerDependencies` (loaded lazily in `img-upload-kit/server` and `img-upload-kit/cli` with actionable install errors). Removed `lucide-react` in favor of zero-dependency inlined SVG icons (`src/react/icons.tsx`).
|
|
15
|
+
- **SVG Disabled by Default**: Removed `image/svg+xml` from `DEFAULT_ALLOWED_FORMATS` and all `STANDARD_PRESETS`. Enable via `allowSvg: true` on `createImageUploader`, a preset, or per-upload options.
|
|
16
|
+
- **Secure-by-Default HTTP Handler**: `createUploadHttpHandler` now ignores client `FormData` overrides (`customFilename`, `folder`, `maxWidth`, `maxHeight`, `outputFormat`, `quality`) unless explicitly opted in via `allowClientOverrides`.
|
|
17
|
+
- **Context-Aware `authorize(ctx)`**: `UploadHttpHandlerOptions.authorize` now receives `AuthorizeContext` (`{ action, request, path?, bucket?, preset? }`).
|
|
18
|
+
- **`<AnimatedImage>` Reveal Parity & Error State**: Default `animationDuration` changed from `700` ms to `600` ms with `scale(1.02 -> 1)` settle reveal (`revealScale`, default `1.02`), `inset: 0; scale(1.1); blur(20px)` placeholder geometry, immediate `5/6` placeholder fade, and `colorPlaceholder="flat"` default for color-only placeholders. On load error, the placeholder remains visible while the broken image is hidden (`data-state="error"`).
|
|
19
|
+
- **Storage, Blur & CLI Contracts**: Removed phantom `-blur.avif` / `-thumb.avif` deletes in Supabase `deleteByUrl`; made `?t=` preview cache-busting opt-in (`appendCacheBuster`); excluded 40px LQIP data URLs from `createSrcSetWithBlur`; preserved `/<bucket>/<path>` in `normalizeImagePath` for Supabase URLs; made `mirrorBufferLocally` async (`Promise<string>`); moved CLI auto-execution to `dist/bin.js`.
|
|
20
|
+
- **Package `exports`**: Updated `package.json` `exports` to nested `import` / `require` conditions with `.d.ts` and `.d.cts` types.
|
|
21
|
+
|
|
22
|
+
### Security
|
|
23
|
+
|
|
24
|
+
- **Binary Magic-Byte Sniffing (`sniffImageFormat`)**: Verified binary signatures (JPEG, PNG, GIF, WebP, AVIF/HEIF `ftyp`, TIFF, SVG) against `allowedFormats` before Sharp decoding, blocking spoofed MIME types.
|
|
25
|
+
- **Built-in SVG Sanitizer (`sanitizeSvg`)**: Dependency-free allowlist sanitizer when `allowSvg: true` that strips `<script>`, `<foreignObject>`, `<iframe|embed|object>`, `on*` handlers, unsafe `href`/`xlink:href` targets, and `<!ENTITY`/`DOCTYPE` declarations.
|
|
26
|
+
- **HTTP Handler Guards**: Added streaming `maxBodyBytes` enforcement (HTTP `413`), `allowedOrigins` CSRF validation (HTTP `403`), `DELETE` bucket allowlist enforcement (HTTP `403`), client filename extension enforcement + non-upsert collision suffixing, generic HTTP `500` error bodies with `onError(error, ctx)`, and `limitInputPixels` (default 50 MP) decompression-bomb protection.
|
|
27
|
+
- **Local Filesystem Containment**: Hardened `createLocalFsStorageAdapter` with `path.relative` traversal checks, Windows drive/colon rejection, and atomic `flag: 'wx'` writes when `upsert: false`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Responsive Image Variants (`variantWidths`)**: Generate multi-width responsive variants (`<base>-<w>w.<ext>`) in parallel, returning `variants: ImageVariant[]` and `srcSet` on `UploadedImageData` and cleaning up all variants in `deleteImage`. `<AnimatedImage image={data} />` automatically wires `srcSet` and default `sizes="100vw"` when `fill` is set.
|
|
32
|
+
- **Clipboard Paste & Mobile Camera Capture**: Added `enablePaste` (default `true`), `pasteTarget: 'dropzone' | 'document'`, and `capture?: 'user' | 'environment'` across `<SingleImageUpload>`, `<MultiImageUpload>`, and headless controllers (`useClipboardPaste`).
|
|
33
|
+
- **Per-Tile Cancel & Retry**: Added per-upload `AbortController` cancellation (`cancel(id)` / tile Cancel button) and per-tile or batch retry (`retry(id)` / `retryAll()`), plus `onUploadStart`, `onUploadComplete`, and `onUploadCancel` callbacks.
|
|
34
|
+
- **Form Integration (`name`, `formValue`, `ref`, `required`, `minImages`)**: Synchronized `<input type="hidden" name={name} />` (`formValue: 'url' | 'json'`), `forwardRef<ImageUploadHandle>` (`{ focus, open, clear }`), `onBlur`, and native constraint validation for `required` and `minImages`.
|
|
35
|
+
- **`<AnimatedImage>` Props**: Added first-class `priority` and `quality` props (preventing Next.js `loading="lazy"` conflicts), `revealScale`, `colorPlaceholder: 'flat' | 'gradient'`, `neutralColors`, `fallbackSrc` (single automatic retry), `renderError`, and `var(--token)` support in `resolveBackgroundColor`.
|
|
36
|
+
- **Edge-Safe Handler & Modular CSS Exports**: Added `img-upload-kit/server/handler` (zero Sharp/`node:fs` imports), `img-upload-kit/styles/*.css` layer exports, and `scripts/check-dist.mjs` build verification.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **RSC `'use client'` & Shared Singletons**: Preserved `'use client'` directives at the top of all client chunks and deduplicated `ImageUploadContext` and `blurService` across `img-upload-kit/react`, `img-upload-kit/headless`, and `img-upload-kit/blur`.
|
|
41
|
+
- **Server & Storage Correctness**: Fixed passthrough EXIF `.rotate()` and dimension swapping (EXIF 5–8), preserved GIF/TIFF/HEIF format labels, held `AsyncSemaphore` slots until timed-out Sharp jobs settle, rolled back uploaded storage objects when `onUploadRecord` throws, added S3 `IfNoneMatch: '*'` for `upsert: false`, and paginated S3/Supabase `list()`.
|
|
42
|
+
- **React State, Memory & Accessibility**: Fixed parallel upload progress races (`uploadTracker`), stale alt text on replacement, `URL.createObjectURL` preview cleanup, modal focus trapping/restoration (`useDialog`), WAI-ARIA radio keyboard navigation in `<QualitySelector>`, `role="progressbar"` attributes, and `aria-live="polite"` upload announcements.
|
|
43
|
+
|
|
44
|
+
### Performance
|
|
45
|
+
|
|
46
|
+
- **Single-Decode Blur Pipeline**: `generateBlurPlaceholder` now decodes the source image once into a 128 px buffer and derives the WebP LQIP data URL, 4-edge dominant color histogram, and BlurHash from the shared buffer (reducing Sharp decodes from 3–4 to 1).
|
|
47
|
+
- **Web Crypto & Object URLs**: Replaced `FileReader.readAsDataURL` client previews with zero-copy `URL.createObjectURL` and migrated server hashing to `globalThis.crypto`.
|
|
48
|
+
|
|
8
49
|
## [1.1.0] - 2026-10-31
|
|
9
50
|
|
|
10
51
|
### Added
|
|
@@ -26,49 +67,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
26
67
|
|
|
27
68
|
### Changed & Hardened
|
|
28
69
|
|
|
29
|
-
- **Server Validation Error Mapping**:
|
|
30
|
-
|
|
31
|
-
- **
|
|
32
|
-
|
|
33
|
-
- **
|
|
34
|
-
|
|
35
|
-
- **
|
|
36
|
-
|
|
37
|
-
- **BlurHash & SVG Detection**:
|
|
38
|
-
- Enhanced SVG validation in `blurGenerator.ts` to perform case-insensitive checks across the initial 1024 bytes and clamped BlurHash components to valid ranges (1–9).
|
|
39
|
-
- **Storage Parity & Upsert Semantics**:
|
|
40
|
-
- Implemented `deleteByUrl` in `createLocalFsStorageAdapter` for parity with cloud storage providers.
|
|
41
|
-
- Aligned default `upsert` semantics to `true` across all adapters for consistent overwrite behavior.
|
|
42
|
-
- **React Pure State Updaters & Controlled Value Sync**:
|
|
43
|
-
- Refactored `useMultiImageState.ts` and `useSingleImageState.ts` to maintain synchronous `previewsRef` snapshots.
|
|
44
|
-
- Removed side-effect dispatches (`emitImmediateChange`) from inside `setPreviews` functional updaters for strict React 18/19 concurrent rendering purity.
|
|
45
|
-
- Properly reset uploaded data and blur previews when single uploader `value` transitions to `null`.
|
|
46
|
-
- **Batch Preemption Clearing in Multi-Upload**:
|
|
47
|
-
- Fixed retention window in `MultiImageUpload.tsx`: active batch references are now explicitly cleared when an in-flight retention timer is preempted by a new upload batch.
|
|
70
|
+
- **Server Validation Error Mapping**: Updated `createUploadHttpHandler.POST` to map client validation errors directly to HTTP 400 Bad Request.
|
|
71
|
+
- **Collision Retry Discrimination**: Fixed collision detection in `uploadRetry.ts` to strictly match `already exists` and `EEXIST` error codes.
|
|
72
|
+
- **Sharp Mobile EXIF Auto-Rotation**: Added `.rotate()` during Sharp pipeline initialization in `imageOptimizer.ts`.
|
|
73
|
+
- **Transparent PNG Color Extraction**: Hardened `colorHistogram.ts` to ignore pixels with `alpha < 8`.
|
|
74
|
+
- **BlurHash & SVG Detection**: Enhanced SVG validation in `blurGenerator.ts` and clamped BlurHash components to valid ranges (1–9).
|
|
75
|
+
- **Storage Parity & Upsert Semantics**: Implemented `deleteByUrl` in `createLocalFsStorageAdapter` and aligned default `upsert` semantics to `true`.
|
|
76
|
+
- **React Pure State Updaters & Controlled Value Sync**: Refactored `useMultiImageState.ts` and `useSingleImageState.ts` for strict React 18/19 concurrent rendering purity.
|
|
77
|
+
- **Batch Preemption Clearing in Multi-Upload**: Fixed retention window in `MultiImageUpload.tsx` when preempted by a new upload batch.
|
|
48
78
|
|
|
49
79
|
## [1.0.0] - 2026-10-31
|
|
50
80
|
|
|
51
81
|
### Added
|
|
52
82
|
|
|
53
|
-
- **Core Types & Utilities (`img-upload-kit`)**:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- `optimizeImage` powered by Sharp with smart AVIF size-guarding (preventing file bloat on pre-compressed JPEGs), WebP fallback, and SVG passthrough.
|
|
61
|
-
- `generateBlurPlaceholder` generating compact WebP LQIP data URLs, optional BlurHash strings, and 4-edge weighted dominant color histograms.
|
|
62
|
-
- **Pluggable Storage Adapters (`img-upload-kit/storage`)**:
|
|
63
|
-
- `StorageAdapter` contract and `createStorageAdapter` authoring helper.
|
|
64
|
-
- Built-in `createSupabaseStorageAdapter`, `createS3StorageAdapter` (AWS S3, Cloudflare R2, MinIO, DigitalOcean Spaces), `createLocalFsStorageAdapter` (with path-traversal protection), and `createMemoryStorageAdapter`.
|
|
65
|
-
- `onUploadRecord` hook for persisting upload metadata to Prisma, Drizzle, or analytics pipelines.
|
|
66
|
-
- **Blur Manifest Service (`img-upload-kit/blur`)**:
|
|
67
|
-
- `BlurService` and `createBlurService` with bounded `LRUCache`, normalized URL/path lookup, and SVG fallback placeholders.
|
|
68
|
-
- **Pure React UI & Styles (`img-upload-kit/react`, `img-upload-kit/styles.css`)**:
|
|
69
|
-
- Framework-agnostic `<ImageUploadProvider>`, `<ImageUpload>`, `<SingleImageUpload>`, `<MultiImageUpload>`, `<BlurImage>`, and `<QualitySelector>`.
|
|
70
|
-
- First-class English & Arabic (`dir="rtl"`) localization, custom `renderImage` adapter slot, and themeable `--iuk-*` CSS custom properties.
|
|
71
|
-
- **Headless Hooks & Primitives (`img-upload-kit/headless`)**:
|
|
72
|
-
- Unstyled controllers: `HeadlessSingleUpload`, `HeadlessMultiUpload`, `useSingleUpload`, `useMultiUpload`, `useImageUpload`, `useDropzone`, `useFilePreview`.
|
|
73
|
-
- **Blur Manifest CLI (`img-upload-kit/cli`)**:
|
|
74
|
-
- `img-upload-kit` / `blur-cli` binaries (`generate`, `validate`, `sync`, `cleanup`, `health`, `inspect`) for build-time `blur-hashes.json` generation.
|
|
83
|
+
- **Core Types & Utilities (`img-upload-kit`)**: Universal visual role presets (`thumbnail`, `avatar`, `card`, `cover`, `hero`, `logo`, `original`), `defineUploadPresets`, `UploadKitRegister`, bilingual (`en` / `ar`) validation, byte formatters, and RGB/HEX color utilities.
|
|
84
|
+
- **Server Optimization & Upload Pipeline (`img-upload-kit/server`)**: `createImageUploader`, `createUploadHttpHandler`, `optimizeImage` with Sharp AVIF size-guarding, and `generateBlurPlaceholder` (WebP LQIP, BlurHash, 4-edge color histogram).
|
|
85
|
+
- **Pluggable Storage Adapters (`img-upload-kit/storage`)**: `createSupabaseStorageAdapter`, `createS3StorageAdapter`, `createLocalFsStorageAdapter`, `createMemoryStorageAdapter`, and `onUploadRecord`.
|
|
86
|
+
- **Blur Manifest Service (`img-upload-kit/blur`)**: `BlurService` and `createBlurService` with bounded `LRUCache`.
|
|
87
|
+
- **Pure React UI & Styles (`img-upload-kit/react`, `img-upload-kit/styles.css`)**: `<ImageUploadProvider>`, `<ImageUpload>`, `<SingleImageUpload>`, `<MultiImageUpload>`, `<BlurImage>`, and `<QualitySelector>` with RTL & `--iuk-*` tokens.
|
|
88
|
+
- **Headless Hooks & Primitives (`img-upload-kit/headless`)**: `HeadlessSingleUpload`, `HeadlessMultiUpload`, `useSingleUpload`, `useMultiUpload`, `useImageUpload`, `useDropzone`, `useFilePreview`.
|
|
89
|
+
- **Blur Manifest CLI (`img-upload-kit/cli`)**: `img-upload-kit` / `blur-cli` binaries (`generate`, `validate`, `sync`, `cleanup`, `health`, `inspect`).
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Migrating from v1.x to v2.0.0
|
|
2
|
+
|
|
3
|
+
`img-upload-kit` v2.0.0 hardens security by default, aligns `<AnimatedImage>` with production motion standards, fixes RSC `'use client'` bundling and shared React contexts, and introduces responsive variants, clipboard paste, camera capture, per-tile cancel/retry, and native form integration.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Dependencies & Peer Dependencies (`sharp`, `lqip-modern`, `lucide-react`)
|
|
8
|
+
|
|
9
|
+
- **`sharp` & `lqip-modern` are now optional peer dependencies.** Client-only or Edge consumers no longer download native Sharp binaries when installing `img-upload-kit`. Server (`img-upload-kit/server`) and CLI (`img-upload-kit/cli`) consumers must install `sharp` (and optionally `lqip-modern`).
|
|
10
|
+
- **`lucide-react` has been removed.** All UI icons are now inlined inside `img-upload-kit/react` (`src/react/icons.tsx`) with zero external icon dependencies.
|
|
11
|
+
- **Node.js `>= 20.0.0`** is recommended for development and server runtimes.
|
|
12
|
+
|
|
13
|
+
### Before (v1.x)
|
|
14
|
+
```bash
|
|
15
|
+
npm install img-upload-kit
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### After (v2.0.0)
|
|
19
|
+
```bash
|
|
20
|
+
# Client-only / React UI:
|
|
21
|
+
npm install img-upload-kit react react-dom
|
|
22
|
+
|
|
23
|
+
# Server / Full-Stack (Next.js, Remix, Hono, Node.js) or CLI:
|
|
24
|
+
npm install img-upload-kit sharp lqip-modern
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. SVG Uploads Disabled by Default (`allowSvg`) & Magic-Byte Sniffing
|
|
30
|
+
|
|
31
|
+
- **SVG (`image/svg+xml`) is disabled by default** across `DEFAULT_ALLOWED_FORMATS` and all `STANDARD_PRESETS` (including `logo`) to prevent stored XSS and XXE out of the box.
|
|
32
|
+
- **Opt-in with `allowSvg: true`** on `createImageUploader`, a preset, or a per-upload call. When enabled, SVGs pass through a built-in dependency-free allowlist sanitizer (`sanitizeSvg`) that strips `<script>`, `<foreignObject>`, `on*` handlers, unsafe `href`/`xlink:href` URIs, and `<!ENTITY` declarations.
|
|
33
|
+
- **Mandatory magic-byte sniffing (`sniffImageFormat`)**: Every upload inspects binary magic bytes rather than trusting `file.type`. Spoofed files (e.g. SVG or HTML disguised as `image/png`) are rejected with `UploadValidationError` (HTTP `400`).
|
|
34
|
+
|
|
35
|
+
### Before (v1.x)
|
|
36
|
+
```ts
|
|
37
|
+
const uploader = createImageUploader({ storage });
|
|
38
|
+
// SVG was accepted on card, cover, hero, logo, original without sanitization
|
|
39
|
+
await uploader.uploadImage(svgFile, { preset: 'logo' });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### After (v2.0.0)
|
|
43
|
+
```ts
|
|
44
|
+
const uploader = createImageUploader({
|
|
45
|
+
storage,
|
|
46
|
+
allowSvg: false, // Default — opt in globally or per preset:
|
|
47
|
+
presets: defineUploadPresets({
|
|
48
|
+
logo: {
|
|
49
|
+
folder: 'logos',
|
|
50
|
+
maxDimensions: { width: 800, height: 800 },
|
|
51
|
+
allowSvg: true, // Sanitizes SVG before storage
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 3. Secure-by-Default HTTP Handler (`allowClientOverrides`, `maxBodyBytes`, `allowedOrigins`, `onError`)
|
|
60
|
+
|
|
61
|
+
`createUploadHttpHandler` now ignores client-supplied `FormData` overrides (`customFilename`, `folder`, `maxWidth`, `maxHeight`, `outputFormat`, `quality`) unless explicitly enabled via `allowClientOverrides`:
|
|
62
|
+
|
|
63
|
+
- **Client overrides**: Clamped to preset `maxDimensions`. Client `customFilename` never forces `upsert: true` (preventing arbitrary object overwrites) and always uses the optimized output extension. Invalid `quality` values return HTTP `400`.
|
|
64
|
+
- **Body size & CSRF protection**: Enforces `maxBodyBytes` (streaming byte limit, HTTP `413`) and `allowedOrigins` (HTTP `403`).
|
|
65
|
+
- **`DELETE` bucket allowlist**: Rejects attempts to delete URLs/buckets outside the uploader's configured buckets (HTTP `403`).
|
|
66
|
+
- **Generic `500` responses**: Internal error messages and paths are never leaked to clients (`{ "error": "Internal server error" }`); inspect raw errors with `onError(error, ctx)`.
|
|
67
|
+
- **Edge-safe handler subpath**: Import `createUploadHttpHandler` from `img-upload-kit/server/handler` when running in Edge runtimes without Sharp or `node:fs`.
|
|
68
|
+
|
|
69
|
+
### Before (v1.x)
|
|
70
|
+
```ts
|
|
71
|
+
// Client FormData could freely override folder, customFilename, maxWidth, quality
|
|
72
|
+
export const { POST, DELETE } = createUploadHttpHandler({ uploader });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### After (v2.0.0)
|
|
76
|
+
```ts
|
|
77
|
+
export const { POST, DELETE } = createUploadHttpHandler({
|
|
78
|
+
uploader,
|
|
79
|
+
maxBodyBytes: 15 * 1024 * 1024,
|
|
80
|
+
allowedOrigins: ['https://app.example.com'],
|
|
81
|
+
allowClientOverrides: {
|
|
82
|
+
filename: true,
|
|
83
|
+
folder: (folder) => (folder.startsWith('users/') ? folder : null),
|
|
84
|
+
dimensions: true, // Clamped to preset maxDimensions
|
|
85
|
+
format: true,
|
|
86
|
+
quality: true,
|
|
87
|
+
},
|
|
88
|
+
onError: (error, ctx) => {
|
|
89
|
+
console.error(`[upload:${ctx.action}]`, error);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Context-Aware `authorize(ctx)` Signature
|
|
97
|
+
|
|
98
|
+
`UploadHttpHandlerOptions.authorize` now receives an `AuthorizeContext` object (`{ action, request, path?, bucket?, preset? }`) instead of a bare `Request`. Throwing inside `authorize` now returns HTTP `401 Unauthorized` instead of `500`.
|
|
99
|
+
|
|
100
|
+
### Before (v1.x)
|
|
101
|
+
```ts
|
|
102
|
+
const handler = createUploadHttpHandler({
|
|
103
|
+
uploader,
|
|
104
|
+
authorize: async (request: Request) => {
|
|
105
|
+
return request.headers.get('authorization') === 'Bearer secret';
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### After (v2.0.0)
|
|
111
|
+
```ts
|
|
112
|
+
const handler = createUploadHttpHandler({
|
|
113
|
+
uploader,
|
|
114
|
+
authorize: async ({ action, request, preset, bucket, path }) => {
|
|
115
|
+
if (action === 'delete' && bucket !== 'media') return false;
|
|
116
|
+
return request.headers.get('authorization') === 'Bearer secret';
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 5. `<AnimatedImage>` Visual Defaults, `priority`, & Error Handling
|
|
124
|
+
|
|
125
|
+
1. **Reveal animation & timing**: Default `animationDuration` changed from `700` ms to `600` ms. The image now transitions from `opacity: 0; transform: scale(1.02)` to `opacity: 1; transform: scale(1)` (`revealScale` prop, default `1.02`). The placeholder uses `inset: 0; scale(1.1); blur(20px)`, fades immediately (`0ms` delay) over `duration * 5 / 6` (`500ms`), and unmounts at `duration + 50ms`.
|
|
126
|
+
2. **Color-only placeholder (`colorPlaceholder`)**: When only `blurColor` is provided (no `blurDataUrl` or `blurHash`), `<AnimatedImage>` defaults to `colorPlaceholder="flat"` (paints wrapper `backgroundColor` without mounting an extra SVG `<img>`). Pass `colorPlaceholder="gradient"` for the v1 SVG layer. Wrapper backgrounds also accept `var(--css-token)`.
|
|
127
|
+
3. **First-class `priority` & `quality`**: Passing `priority` sets `loading="eager"` + `fetchPriority="high"` on native `<img>` and forwards `priority` to custom `as={Image}` components without conflicting with `loading="lazy"`.
|
|
128
|
+
4. **Placeholder-preserving error state**: On load error, the placeholder remains visible, the broken `<img>` is hidden (`visibility: hidden`, `data-state="error"`), `fallbackSrc` is retried once if provided, and `renderError={({ src, retry }) => ...}` renders custom recovery UI.
|
|
129
|
+
|
|
130
|
+
### Before (v1.x)
|
|
131
|
+
```tsx
|
|
132
|
+
// Next.js threw when passing priority via imageComponentProps due to forced loading="lazy"
|
|
133
|
+
<AnimatedImage
|
|
134
|
+
image={img}
|
|
135
|
+
as={Image}
|
|
136
|
+
imageComponentProps={{ priority: true, quality: 85 }}
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### After (v2.0.0)
|
|
141
|
+
```tsx
|
|
142
|
+
// First-class priority & quality props, fallbackSrc, and optional v1 fade restore:
|
|
143
|
+
<AnimatedImage
|
|
144
|
+
image={img}
|
|
145
|
+
as={Image}
|
|
146
|
+
priority
|
|
147
|
+
quality={85}
|
|
148
|
+
fallbackSrc="/images/fallback.webp"
|
|
149
|
+
renderError={({ retry }) => <button onClick={retry}>Retry</button>}
|
|
150
|
+
// Optional: restore v1 pure opacity fade & SVG color gradient
|
|
151
|
+
animationDuration={700}
|
|
152
|
+
revealScale={1}
|
|
153
|
+
colorPlaceholder="gradient"
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 6. Storage, Preview Cache-Busting, Blur & CLI Updates
|
|
160
|
+
|
|
161
|
+
- **Supabase `deleteByUrl`**: No longer issues speculative deletes for non-existent `-blur.avif` / `-thumb.avif` sibling paths.
|
|
162
|
+
- **Opt-in `?t=` preview cache-buster**: `<SingleImageUpload>` and `<PreviewTile>` no longer append `?t=...` by default (which previously invalidated pre-signed S3/R2/GCS URLs). Opt in with `<PreviewTile appendCacheBuster />` or `appendCacheBuster(url, { enabled: true })` (signed URLs are always skipped).
|
|
163
|
+
- **`createSrcSetWithBlur` & `preloadImageWithBlur`**: `createSrcSetWithBlur` no longer injects the 40px LQIP data URL into `srcset`, and `preloadImageWithBlur` deduplicates `<link rel="preload">` tags without overriding `imagesrcset`.
|
|
164
|
+
- **`normalizeImagePath`**: Preserves `/<bucket>/<path>` for Supabase public URLs consistently with S3 path-style URLs.
|
|
165
|
+
- **`mirrorBufferLocally` & CLI entry**: `mirrorBufferLocally` is now async (`Promise<string>`). Importing `img-upload-kit/cli` has zero side effects (the CLI binary entry moved to `dist/bin.js`).
|
|
166
|
+
|
|
167
|
+
### Before (v1.x)
|
|
168
|
+
```tsx
|
|
169
|
+
// Mutated preview URLs with ?t=..., breaking X-Amz-Signature on presigned URLs
|
|
170
|
+
<PreviewTile preview={item} onRemove={onRemove} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### After (v2.0.0)
|
|
174
|
+
```tsx
|
|
175
|
+
// Preserves URLs verbatim by default; opt in when overwriting static paths:
|
|
176
|
+
<PreviewTile preview={item} onRemove={onRemove} appendCacheBuster />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 7. Package `exports` & Per-Layer CSS Subpaths
|
|
182
|
+
|
|
183
|
+
`package.json` `exports` now use nested `import` / `require` conditions with dedicated `.d.ts` and `.d.cts` declarations, and expose individual CSS layers under `img-upload-kit/styles/*.css`:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// Full stylesheet (unchanged):
|
|
187
|
+
import 'img-upload-kit/styles.css';
|
|
188
|
+
|
|
189
|
+
// Or import individual CSS layers in v2.0:
|
|
190
|
+
import 'img-upload-kit/styles/tokens.css';
|
|
191
|
+
import 'img-upload-kit/styles/animated-image.css';
|
|
192
|
+
```
|