@humaan/payload-storage-imagekit 2.6.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -6
- package/dist/clientImageIdentity.d.ts +5 -0
- package/dist/clientImageIdentity.js +86 -0
- package/dist/clientImageIdentity.js.map +1 -0
- package/dist/clientUploadVerification.d.ts +30 -0
- package/dist/clientUploadVerification.js +104 -0
- package/dist/clientUploadVerification.js.map +1 -0
- package/dist/clientUploads.d.ts +41 -4
- package/dist/clientUploads.js +4 -4
- package/dist/clientUploads.js.map +1 -1
- package/dist/components/ImageKitUploadHandler.d.ts +8 -2
- package/dist/components/ImageKitUploadHandler.js +80 -65
- package/dist/components/ImageKitUploadHandler.js.map +1 -1
- package/dist/endpoints/createImageKitUploadAuthHandler.d.ts +12 -4
- package/dist/endpoints/createImageKitUploadAuthHandler.js +162 -29
- package/dist/endpoints/createImageKitUploadAuthHandler.js.map +1 -1
- package/dist/imageKitUploadToken.d.ts +12 -0
- package/dist/imageKitUploadToken.js +25 -0
- package/dist/imageKitUploadToken.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +366 -206
- package/dist/index.js.map +1 -1
- package/package.json +32 -33
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@ This plugin integrates `@payloadcms/plugin-cloud-storage` with ImageKit and prov
|
|
|
21
21
|
pnpm add @humaan/payload-storage-imagekit
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
Payload `3.90.1` or newer is required. Use matching versions of `payload` and
|
|
25
|
+
`@payloadcms/plugin-cloud-storage`.
|
|
26
|
+
|
|
24
27
|
## Usage
|
|
25
28
|
|
|
26
29
|
```ts
|
|
@@ -59,13 +62,13 @@ export default buildConfig({
|
|
|
59
62
|
- `privateKey` (required): ImageKit private API key
|
|
60
63
|
- `publicKey` (required): ImageKit public key
|
|
61
64
|
- `urlEndpoint` (required): ImageKit URL endpoint, e.g. `https://ik.imagekit.io/your_id`
|
|
62
|
-
- `clientUploads` (optional): when `true
|
|
63
|
-
Duplicate
|
|
65
|
+
- `clientUploads` (optional): when `true` (or `{ access }`), Payload admin uploads go directly from the browser to ImageKit Upload API V2. Payload checks collection write access before authorization, signs every provider upload option, assigns an unpredictable per-upload object key, and verifies ImageKit metadata before persistence. This helps avoid server request body limits such as Vercel's. No additional ImageKit credentials or environment variables are required.
|
|
66
|
+
Duplicate and concurrent filenames receive distinct object paths and cannot overwrite each other.
|
|
64
67
|
- `signedUrlExpiresIn` (optional): expiry in seconds for the signed URLs generated for [private](#private-files) collections. Defaults to `900` (15 minutes). Signed URLs are generated fresh on every request, so a short window is safe.
|
|
65
68
|
- `enabled` (optional): enable / disable plugin
|
|
66
69
|
- `focalPoint` (optional): broad default for Payload's native focal-point picker (`upload.focalPoint`) on every configured collection. Focal points are disabled by default for configured collections, so prefer the per-collection `collections.<slug>.focalPoint` option for mixed upload collections, e.g. enable it for `media` and leave it off for `documents`. When `true`, editors can place a focal point and `focalX`/`focalY` (0–100) are stored on each doc for focal-aware cropping at render time. A collection's own `upload.focalPoint` still takes precedence.
|
|
67
70
|
- `alwaysInsertFields` (optional): pass-through to cloud storage plugin
|
|
68
|
-
- `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin
|
|
71
|
+
- `disablePayloadAccessControl` (optional): pass-through to cloud storage plugin. It cannot be enabled for private collections or collections that can store SVG/XML, because those files require Payload's file route for signing or Content Security Policy headers. To use this option, every configured collection must have a MIME allow list that excludes XML-family types.
|
|
69
72
|
|
|
70
73
|
## Exports
|
|
71
74
|
|
|
@@ -154,10 +157,87 @@ How it works:
|
|
|
154
157
|
- **Per-document status:** each document records its actual ImageKit privacy at upload time in `imagekit.isPrivate` (hidden from the admin form). A file's privacy on ImageKit is fixed when it is uploaded, so this stored flag — not the collection's current setting — is the **source of truth** for delivery. This means existing files keep working if you later flip a collection's `private` setting. (Documents uploaded before this flag existed fall back to the collection setting.)
|
|
155
158
|
- **Delivery:** files are served through Payload's own file route (`/api/<collection>/file/<filename>`), which enforces the collection's `access.read` control and then redirects to a freshly signed, expiring ImageKit URL. Nothing signed is ever persisted to the database.
|
|
156
159
|
- **Admin thumbnails** are routed through the same Payload file route so they sign on demand and respect access control (including ImageKit's `ik-thumbnail.jpg` previews for videos and PDFs, with `/ik-video.mp4` applied first for videos without a `.mp4` or `.mov` extension).
|
|
157
|
-
- **Admin indicators:** private collections get a note in their admin description (visible in the list and edit views). The ImageKit metadata
|
|
160
|
+
- **Admin indicators:** private collections get a note in their admin description (visible in the list and edit views). The ImageKit metadata sits in a "Storage: ImageKit" panel that is collapsed by default, hidden while uploading and shown after a file has been stored, with `imagekit.url` visible as a read-only field and `imagekit.isPrivate` shown as read-only when the stored file is private.
|
|
158
161
|
- **Signed URLs:** because the stored `imagekit.url` is the unsigned URL (which 401s for private files), private documents also expose a virtual `imagekit.signedUrl` field — a freshly signed, time-limited URL computed on each read (never persisted). Use it through Payload's API to reach the file directly on ImageKit. The access-control-gated alternative is the document's top-level `url` (the Payload file route).
|
|
159
162
|
- **Access control is required.** Because signing happens at request time behind Payload access control, `disablePayloadAccessControl` cannot be combined with private collections — doing so throws at startup. Set per-collection `access.read` rules to control who can read each file.
|
|
160
163
|
|
|
164
|
+
## Payload 3.90 upload security
|
|
165
|
+
|
|
166
|
+
Versions through `2.6.2` do not implement Payload 3.90's hardened client-upload
|
|
167
|
+
contract. Do not use those releases with `clientUploads: true` on Payload 3.90.
|
|
168
|
+
Upgrade to this release, or disable `clientUploads`. Server-side ImageKit uploads
|
|
169
|
+
were not affected by this adapter issue.
|
|
170
|
+
|
|
171
|
+
Client uploads now use ImageKit Upload API V2 and Payload's signed upload receipts.
|
|
172
|
+
Payload chooses the sanitized filename and adds numeric suffixes for duplicates.
|
|
173
|
+
For PNG, JPEG, GIF, and WebP images, the browser handler checks the file signature
|
|
174
|
+
before authorization and corrects a mismatched MIME label or extension. ImageKit
|
|
175
|
+
still verifies the actual content against the signed MIME and size checks.
|
|
176
|
+
Uploads go directly into the collection folder. Authorization binds the collection,
|
|
177
|
+
user, path, filename, MIME type, size, privacy mode, expiry, a per-upload ImageKit tag,
|
|
178
|
+
and create-only upload options. ImageKit metadata is checked on every completed upload,
|
|
179
|
+
including when Payload does not need to fetch file bytes.
|
|
180
|
+
|
|
181
|
+
After ImageKit returns a file ID, the browser sends it and the original receipt back
|
|
182
|
+
to the authorization endpoint with `action: 'complete'`. The server checks the file
|
|
183
|
+
directly by ID against the signed authorization and issues a receipt containing that
|
|
184
|
+
ID. Document saves use this receipt without waiting for ImageKit's asset listings.
|
|
185
|
+
The tag links the initial authorization to the new file; completed receipts use the
|
|
186
|
+
verified file ID. A unique `imagekit.fileId` index prevents receipt reuse.
|
|
187
|
+
ImageKit filename generation stays disabled. If an ImageKit-only file or a concurrent
|
|
188
|
+
upload occupies the selected path, the browser requests a fresh signed authorization
|
|
189
|
+
with the next numeric suffix and retries. Payload checks each new name against its
|
|
190
|
+
database. Up to 10 upload attempts are made; existing ImageKit files are never overwritten.
|
|
191
|
+
|
|
192
|
+
Keep `upload.allowRestrictedFileTypes` unset or `false` for normal Media and Images
|
|
193
|
+
collections, including collections that accept safe SVG files. Payload 3.90's admin
|
|
194
|
+
recognizes SVG and XML-family files and automatically sends them through the normal
|
|
195
|
+
server-side multipart flow instead of the ImageKit client-upload handler. Payload
|
|
196
|
+
then inspects the bytes, accepts safe SVG according to the collection's MIME allow
|
|
197
|
+
list, and rejects unsafe SVG/XML. Custom API clients must follow the same rule and
|
|
198
|
+
send restricted files through Payload rather than requesting direct-upload
|
|
199
|
+
authorization.
|
|
200
|
+
|
|
201
|
+
Setting `upload.allowRestrictedFileTypes: true` is an explicit validation opt-out,
|
|
202
|
+
not the standard way to enable SVG. It permits direct client upload after ImageKit
|
|
203
|
+
MIME, size, path, and privacy verification, but Payload does not sanitize or inspect
|
|
204
|
+
the complete SVG/XML contents. Use it only for files that are already sanitized or
|
|
205
|
+
otherwise fully trusted. Explicitly allowed XML-family files are streamed through
|
|
206
|
+
Payload's file route with Payload's Content Security Policy rather than redirected
|
|
207
|
+
directly to ImageKit. Do not render their stored `imagekit.url` directly from a
|
|
208
|
+
same-origin ImageKit delivery domain.
|
|
209
|
+
|
|
210
|
+
Because that protection requires Payload's file route, `disablePayloadAccessControl`
|
|
211
|
+
cannot be used for a collection that can store SVG/XML. Configure a MIME allow list
|
|
212
|
+
that excludes XML-family types if direct public ImageKit URLs are required.
|
|
213
|
+
|
|
214
|
+
Payload 3.90 adds `_objectKey` to cloud-storage upload collections, and this adapter
|
|
215
|
+
adds a unique index on `imagekit.fileId`. Regenerate Payload types after upgrading.
|
|
216
|
+
SQL users must also create and run a migration:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
pnpm payload generate:types
|
|
220
|
+
pnpm payload migrate:create imagekit-file-id-receipts
|
|
221
|
+
pnpm payload migrate
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Existing files and documents with or without `_objectKey` remain supported in place;
|
|
225
|
+
no ImageKit relocation is required. New client uploads do not set `_objectKey`.
|
|
226
|
+
|
|
227
|
+
Payload 3.90 also caps server-side multipart requests at 50 MiB by default. This
|
|
228
|
+
does not apply to browser-to-ImageKit client uploads, but it does apply when Payload
|
|
229
|
+
routes SVG/XML through server validation. If server-side uploads larger than 50 MiB
|
|
230
|
+
are required, raise Payload's complete-request limit, for example:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
upload: {
|
|
234
|
+
requestSizeLimit: 75 * 1024 * 1024,
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The adapter streams Payload temporary files to ImageKit for server-side uploads,
|
|
239
|
+
rather than loading those files into a second buffer.
|
|
240
|
+
|
|
161
241
|
## Folder Migration
|
|
162
242
|
|
|
163
243
|
Use `migrateFolder` to copy uploads from one ImageKit root folder to another (e.g. staging to production).
|
|
@@ -218,8 +298,9 @@ DRY_RUN=false pnpx tsx --env-file=../.env imagekit-migration.ts
|
|
|
218
298
|
|
|
219
299
|
## Notes
|
|
220
300
|
|
|
221
|
-
-
|
|
222
|
-
-
|
|
301
|
+
- New client and server-side files use: `<folder>/<collectionSlug>/<filename>`.
|
|
302
|
+
- Existing client uploads with `_objectKey` keep their original per-upload folders.
|
|
303
|
+
- With `clientUploads: true`, the browser uploads the original file directly to ImageKit, then Payload verifies the provider object and continues its normal document save flow.
|
|
223
304
|
- Deletion prefers stored `imagekit.fileId`, with path lookup fallback.
|
|
224
305
|
- Admin thumbnail behavior:
|
|
225
306
|
- Images: `<imagekit.url>?tr=w-300,h-300`
|
|
@@ -233,3 +314,12 @@ pnpm dev
|
|
|
233
314
|
pnpm test:int
|
|
234
315
|
pnpm build
|
|
235
316
|
```
|
|
317
|
+
|
|
318
|
+
The optional credential-backed ImageKit V2 tests use separate environment variables
|
|
319
|
+
so they cannot run accidentally with normal development credentials:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
IMAGEKIT_INTEGRATION_PRIVATE_KEY=... \
|
|
323
|
+
IMAGEKIT_INTEGRATION_PUBLIC_KEY=... \
|
|
324
|
+
pnpm test:imagekit
|
|
325
|
+
```
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** A browser-side hint only: ImageKit must still verify the actual uploaded content. */ export const getClientImageIdentity = async (file)=>{
|
|
2
|
+
const original = {
|
|
3
|
+
filename: file.name,
|
|
4
|
+
mimeType: file.type
|
|
5
|
+
};
|
|
6
|
+
if (![
|
|
7
|
+
'image/gif',
|
|
8
|
+
'image/jpeg',
|
|
9
|
+
'image/png',
|
|
10
|
+
'image/webp'
|
|
11
|
+
].includes(file.type)) {
|
|
12
|
+
return original;
|
|
13
|
+
}
|
|
14
|
+
const header = new Uint8Array(await file.slice(0, 12).arrayBuffer());
|
|
15
|
+
const matches = (bytes, offset = 0)=>bytes.every((byte, index)=>header[offset + index] === byte);
|
|
16
|
+
let detected;
|
|
17
|
+
if (matches([
|
|
18
|
+
0x89,
|
|
19
|
+
0x50,
|
|
20
|
+
0x4e,
|
|
21
|
+
0x47,
|
|
22
|
+
0x0d,
|
|
23
|
+
0x0a,
|
|
24
|
+
0x1a,
|
|
25
|
+
0x0a
|
|
26
|
+
])) {
|
|
27
|
+
detected = {
|
|
28
|
+
extension: 'png',
|
|
29
|
+
mimeType: 'image/png'
|
|
30
|
+
};
|
|
31
|
+
} else if (matches([
|
|
32
|
+
0xff,
|
|
33
|
+
0xd8,
|
|
34
|
+
0xff
|
|
35
|
+
])) {
|
|
36
|
+
detected = {
|
|
37
|
+
extension: 'jpg',
|
|
38
|
+
mimeType: 'image/jpeg'
|
|
39
|
+
};
|
|
40
|
+
} else if (matches([
|
|
41
|
+
0x47,
|
|
42
|
+
0x49,
|
|
43
|
+
0x46,
|
|
44
|
+
0x38,
|
|
45
|
+
0x37,
|
|
46
|
+
0x61
|
|
47
|
+
]) || matches([
|
|
48
|
+
0x47,
|
|
49
|
+
0x49,
|
|
50
|
+
0x46,
|
|
51
|
+
0x38,
|
|
52
|
+
0x39,
|
|
53
|
+
0x61
|
|
54
|
+
])) {
|
|
55
|
+
detected = {
|
|
56
|
+
extension: 'gif',
|
|
57
|
+
mimeType: 'image/gif'
|
|
58
|
+
};
|
|
59
|
+
} else if (matches([
|
|
60
|
+
0x52,
|
|
61
|
+
0x49,
|
|
62
|
+
0x46,
|
|
63
|
+
0x46
|
|
64
|
+
]) && matches([
|
|
65
|
+
0x57,
|
|
66
|
+
0x45,
|
|
67
|
+
0x42,
|
|
68
|
+
0x50
|
|
69
|
+
], 8)) {
|
|
70
|
+
detected = {
|
|
71
|
+
extension: 'webp',
|
|
72
|
+
mimeType: 'image/webp'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (!detected) {
|
|
76
|
+
return original;
|
|
77
|
+
}
|
|
78
|
+
const extension = file.name.split('.').pop()?.toLowerCase();
|
|
79
|
+
const matchingExtension = extension === detected.extension || detected.mimeType === 'image/jpeg' && extension === 'jpeg';
|
|
80
|
+
return {
|
|
81
|
+
filename: matchingExtension ? file.name : `${file.name.replace(/\.[^./\\]+$/, '')}.${detected.extension}`,
|
|
82
|
+
mimeType: detected.mimeType
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=clientImageIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/clientImageIdentity.ts"],"sourcesContent":["type ImageIdentity = { extension: string; mimeType: string }\n\n/** A browser-side hint only: ImageKit must still verify the actual uploaded content. */\nexport const getClientImageIdentity = async (file: File): Promise<{\n filename: string\n mimeType: string\n}> => {\n const original = { filename: file.name, mimeType: file.type }\n if (!['image/gif', 'image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {\n return original\n }\n\n const header = new Uint8Array(await file.slice(0, 12).arrayBuffer())\n const matches = (bytes: number[], offset = 0) =>\n bytes.every((byte, index) => header[offset + index] === byte)\n let detected: ImageIdentity | undefined\n if (matches([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {\n detected = { extension: 'png', mimeType: 'image/png' }\n } else if (matches([0xff, 0xd8, 0xff])) {\n detected = { extension: 'jpg', mimeType: 'image/jpeg' }\n } else if (\n matches([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]) ||\n matches([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])\n ) {\n detected = { extension: 'gif', mimeType: 'image/gif' }\n } else if (matches([0x52, 0x49, 0x46, 0x46]) && matches([0x57, 0x45, 0x42, 0x50], 8)) {\n detected = { extension: 'webp', mimeType: 'image/webp' }\n }\n\n if (!detected) {\n return original\n }\n\n const extension = file.name.split('.').pop()?.toLowerCase()\n const matchingExtension =\n extension === detected.extension ||\n (detected.mimeType === 'image/jpeg' && extension === 'jpeg')\n return {\n filename: matchingExtension\n ? file.name\n : `${file.name.replace(/\\.[^./\\\\]+$/, '')}.${detected.extension}`,\n mimeType: detected.mimeType,\n }\n}\n"],"names":["getClientImageIdentity","file","original","filename","name","mimeType","type","includes","header","Uint8Array","slice","arrayBuffer","matches","bytes","offset","every","byte","index","detected","extension","split","pop","toLowerCase","matchingExtension","replace"],"mappings":"AAEA,sFAAsF,GACtF,OAAO,MAAMA,yBAAyB,OAAOC;IAI3C,MAAMC,WAAW;QAAEC,UAAUF,KAAKG,IAAI;QAAEC,UAAUJ,KAAKK,IAAI;IAAC;IAC5D,IAAI,CAAC;QAAC;QAAa;QAAc;QAAa;KAAa,CAACC,QAAQ,CAACN,KAAKK,IAAI,GAAG;QAC/E,OAAOJ;IACT;IAEA,MAAMM,SAAS,IAAIC,WAAW,MAAMR,KAAKS,KAAK,CAAC,GAAG,IAAIC,WAAW;IACjE,MAAMC,UAAU,CAACC,OAAiBC,SAAS,CAAC,GAC1CD,MAAME,KAAK,CAAC,CAACC,MAAMC,QAAUT,MAAM,CAACM,SAASG,MAAM,KAAKD;IAC1D,IAAIE;IACJ,IAAIN,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,GAAG;QAC7DM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAY;IACvD,OAAO,IAAIO,QAAQ;QAAC;QAAM;QAAM;KAAK,GAAG;QACtCM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAa;IACxD,OAAO,IACLO,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,KAC5CA,QAAQ;QAAC;QAAM;QAAM;QAAM;QAAM;QAAM;KAAK,GAC5C;QACAM,WAAW;YAAEC,WAAW;YAAOd,UAAU;QAAY;IACvD,OAAO,IAAIO,QAAQ;QAAC;QAAM;QAAM;QAAM;KAAK,KAAKA,QAAQ;QAAC;QAAM;QAAM;QAAM;KAAK,EAAE,IAAI;QACpFM,WAAW;YAAEC,WAAW;YAAQd,UAAU;QAAa;IACzD;IAEA,IAAI,CAACa,UAAU;QACb,OAAOhB;IACT;IAEA,MAAMiB,YAAYlB,KAAKG,IAAI,CAACgB,KAAK,CAAC,KAAKC,GAAG,IAAIC;IAC9C,MAAMC,oBACJJ,cAAcD,SAASC,SAAS,IAC/BD,SAASb,QAAQ,KAAK,gBAAgBc,cAAc;IACvD,OAAO;QACLhB,UAAUoB,oBACNtB,KAAKG,IAAI,GACT,GAAGH,KAAKG,IAAI,CAACoB,OAAO,CAAC,eAAe,IAAI,CAAC,EAAEN,SAASC,SAAS,EAAE;QACnEd,UAAUa,SAASb,QAAQ;IAC7B;AACF,EAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type ImageKit from '@imagekit/nodejs';
|
|
2
|
+
import type { CollectionConfig } from 'payload';
|
|
3
|
+
import type { ImageKitReceiptContext } from './clientUploads.js';
|
|
4
|
+
export type VerifiedImageKitUpload = {
|
|
5
|
+
fileId: string;
|
|
6
|
+
filePath: string;
|
|
7
|
+
fileType?: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
name: string;
|
|
10
|
+
size: number;
|
|
11
|
+
thumbnailUrl?: string;
|
|
12
|
+
url: string;
|
|
13
|
+
};
|
|
14
|
+
type CollectionSecurityConfig = {
|
|
15
|
+
collection: CollectionConfig;
|
|
16
|
+
isPrivate: boolean;
|
|
17
|
+
prefix: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const getClientUploadStorageFilePath: ({ collectionPrefix, context, }: {
|
|
20
|
+
collectionPrefix: string;
|
|
21
|
+
context: ImageKitReceiptContext;
|
|
22
|
+
}) => string;
|
|
23
|
+
export declare const createImageKitClientUploadVerifier: ({ client, collections, }: {
|
|
24
|
+
client: ImageKit;
|
|
25
|
+
collections: Record<string, CollectionSecurityConfig>;
|
|
26
|
+
}) => ({ collectionSlug, context, }: {
|
|
27
|
+
collectionSlug: string;
|
|
28
|
+
context: unknown;
|
|
29
|
+
}) => Promise<VerifiedImageKitUpload>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities';
|
|
2
|
+
import { APIError } from 'payload';
|
|
3
|
+
import { assertClientUploadAllowed } from 'payload/internal';
|
|
4
|
+
import { validateMimeType } from 'payload/shared';
|
|
5
|
+
import { isImageKitReceiptContext } from './clientUploads.js';
|
|
6
|
+
const getMimeTypeEssence = (mimeType)=>(mimeType.split(';', 1)[0] || '').trim().toLowerCase();
|
|
7
|
+
const toImageKitPath = (storageFilePath)=>`/${storageFilePath.replace(/^\/+/, '')}`;
|
|
8
|
+
const assetLookupRetryDelays = [
|
|
9
|
+
250,
|
|
10
|
+
500,
|
|
11
|
+
1000,
|
|
12
|
+
2000
|
|
13
|
+
];
|
|
14
|
+
const findFileIDByPath = async (client, filePath)=>{
|
|
15
|
+
const parentPath = `${filePath.slice(0, filePath.lastIndexOf('/') + 1)}`;
|
|
16
|
+
const name = filePath.slice(filePath.lastIndexOf('/') + 1);
|
|
17
|
+
const limit = 100;
|
|
18
|
+
for(let skip = 0;; skip += limit){
|
|
19
|
+
const assets = await client.assets.list({
|
|
20
|
+
type: 'file',
|
|
21
|
+
limit,
|
|
22
|
+
path: parentPath,
|
|
23
|
+
skip
|
|
24
|
+
});
|
|
25
|
+
const found = assets.find((asset)=>'fileId' in asset && asset.filePath === filePath && asset.name === name);
|
|
26
|
+
if (found && 'fileId' in found) {
|
|
27
|
+
return found.fileId;
|
|
28
|
+
}
|
|
29
|
+
if (assets.length < limit) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export const getClientUploadStorageFilePath = ({ collectionPrefix, context })=>buildStoragePathData({
|
|
35
|
+
collectionPrefix,
|
|
36
|
+
docPrefix: [
|
|
37
|
+
context.prefix,
|
|
38
|
+
context._objectKey
|
|
39
|
+
].filter(Boolean).join('/'),
|
|
40
|
+
filename: context.filename
|
|
41
|
+
}).storageFilePath;
|
|
42
|
+
export const createImageKitClientUploadVerifier = ({ client, collections })=>{
|
|
43
|
+
const verifiedContexts = new WeakMap();
|
|
44
|
+
return async ({ collectionSlug, context })=>{
|
|
45
|
+
if (!isImageKitReceiptContext(context)) {
|
|
46
|
+
throw new APIError('Invalid ImageKit client upload context.', 400);
|
|
47
|
+
}
|
|
48
|
+
const collectionSecurity = collections[collectionSlug];
|
|
49
|
+
if (!collectionSecurity || context.collectionSlug !== collectionSlug || context.isPrivate !== collectionSecurity.isPrivate) {
|
|
50
|
+
throw new APIError('ImageKit client upload reference does not match this collection.', 400);
|
|
51
|
+
}
|
|
52
|
+
const cached = verifiedContexts.get(context);
|
|
53
|
+
if (cached) {
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
const storageFilePath = getClientUploadStorageFilePath({
|
|
57
|
+
collectionPrefix: collectionSecurity.prefix,
|
|
58
|
+
context
|
|
59
|
+
});
|
|
60
|
+
const expectedFilePath = toImageKitPath(storageFilePath);
|
|
61
|
+
let fileId = context.fileId;
|
|
62
|
+
// Receipts without a file ID can still be verified by their signed path and binding.
|
|
63
|
+
// The completion handshake avoids depending on asset listing visibility.
|
|
64
|
+
if (!fileId) {
|
|
65
|
+
fileId = await findFileIDByPath(client, expectedFilePath);
|
|
66
|
+
for (const delay of assetLookupRetryDelays){
|
|
67
|
+
if (fileId) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
await new Promise((resolve)=>setTimeout(resolve, delay));
|
|
71
|
+
fileId = await findFileIDByPath(client, expectedFilePath);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!fileId) {
|
|
75
|
+
throw new APIError('ImageKit client upload was not found at its authorized path.', 400);
|
|
76
|
+
}
|
|
77
|
+
const uploadedFile = await client.files.get(fileId);
|
|
78
|
+
const uploadedMime = typeof uploadedFile.mime === 'string' ? getMimeTypeEssence(uploadedFile.mime) : '';
|
|
79
|
+
const uploadedSize = uploadedFile.size;
|
|
80
|
+
assertClientUploadAllowed({
|
|
81
|
+
collection: collectionSecurity.collection,
|
|
82
|
+
filename: context.filename,
|
|
83
|
+
mimeType: uploadedMime
|
|
84
|
+
});
|
|
85
|
+
const allowedMimeTypes = typeof collectionSecurity.collection.upload === 'object' && Array.isArray(collectionSecurity.collection.upload.mimeTypes) ? collectionSecurity.collection.upload.mimeTypes : [];
|
|
86
|
+
if (uploadedFile.fileId !== fileId || uploadedFile.filePath !== expectedFilePath || uploadedFile.name !== context.filename || context.uploadTag !== undefined && !uploadedFile.tags?.includes(context.uploadTag) || !uploadedMime || uploadedMime !== context.expectedMimeType || typeof uploadedSize !== 'number' || !Number.isSafeInteger(uploadedSize) || uploadedSize !== context.expectedSize || allowedMimeTypes.length > 0 && !validateMimeType(uploadedMime, allowedMimeTypes) || Boolean(uploadedFile.isPrivateFile) !== collectionSecurity.isPrivate || typeof uploadedFile.url !== 'string' || uploadedFile.url.length === 0) {
|
|
87
|
+
throw new APIError('ImageKit client upload metadata did not match its authorization.', 400);
|
|
88
|
+
}
|
|
89
|
+
const verified = {
|
|
90
|
+
name: uploadedFile.name,
|
|
91
|
+
fileId,
|
|
92
|
+
filePath: expectedFilePath,
|
|
93
|
+
fileType: uploadedFile.fileType,
|
|
94
|
+
mimeType: uploadedMime,
|
|
95
|
+
size: uploadedSize,
|
|
96
|
+
thumbnailUrl: typeof uploadedFile.thumbnail === 'string' ? uploadedFile.thumbnail : undefined,
|
|
97
|
+
url: uploadedFile.url
|
|
98
|
+
};
|
|
99
|
+
verifiedContexts.set(context, verified);
|
|
100
|
+
return verified;
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=clientUploadVerification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/clientUploadVerification.ts"],"sourcesContent":["import type ImageKit from '@imagekit/nodejs'\nimport type { CollectionConfig } from 'payload'\n\nimport { buildStoragePathData } from '@payloadcms/plugin-cloud-storage/utilities'\nimport { APIError } from 'payload'\nimport { assertClientUploadAllowed } from 'payload/internal'\nimport { validateMimeType } from 'payload/shared'\n\nimport type { ImageKitReceiptContext } from './clientUploads.js'\n\nimport { isImageKitReceiptContext } from './clientUploads.js'\n\nexport type VerifiedImageKitUpload = {\n fileId: string\n filePath: string\n fileType?: string\n mimeType: string\n name: string\n size: number\n thumbnailUrl?: string\n url: string\n}\n\ntype CollectionSecurityConfig = {\n collection: CollectionConfig\n isPrivate: boolean\n prefix: string\n}\n\nconst getMimeTypeEssence = (mimeType: string): string =>\n (mimeType.split(';', 1)[0] || '').trim().toLowerCase()\n\nconst toImageKitPath = (storageFilePath: string): string =>\n `/${storageFilePath.replace(/^\\/+/, '')}`\n\nconst assetLookupRetryDelays = [250, 500, 1000, 2000]\n\nconst findFileIDByPath = async (\n client: ImageKit,\n filePath: string,\n): Promise<string | undefined> => {\n const parentPath = `${filePath.slice(0, filePath.lastIndexOf('/') + 1)}`\n const name = filePath.slice(filePath.lastIndexOf('/') + 1)\n const limit = 100\n\n for (let skip = 0; ; skip += limit) {\n const assets = await client.assets.list({\n type: 'file',\n limit,\n path: parentPath,\n skip,\n })\n const found = assets.find(\n (asset) => 'fileId' in asset && asset.filePath === filePath && asset.name === name,\n )\n\n if (found && 'fileId' in found) {\n return found.fileId\n }\n\n if (assets.length < limit) {\n return undefined\n }\n }\n}\n\nexport const getClientUploadStorageFilePath = ({\n collectionPrefix,\n context,\n}: {\n collectionPrefix: string\n context: ImageKitReceiptContext\n}): string =>\n buildStoragePathData({\n collectionPrefix,\n docPrefix: [context.prefix, context._objectKey].filter(Boolean).join('/'),\n filename: context.filename,\n }).storageFilePath\n\nexport const createImageKitClientUploadVerifier = ({\n client,\n collections,\n}: {\n client: ImageKit\n collections: Record<string, CollectionSecurityConfig>\n}) => {\n const verifiedContexts = new WeakMap<object, VerifiedImageKitUpload>()\n\n return async ({\n collectionSlug,\n context,\n }: {\n collectionSlug: string\n context: unknown\n }): Promise<VerifiedImageKitUpload> => {\n if (!isImageKitReceiptContext(context)) {\n throw new APIError('Invalid ImageKit client upload context.', 400)\n }\n\n const collectionSecurity = collections[collectionSlug]\n if (\n !collectionSecurity ||\n context.collectionSlug !== collectionSlug ||\n context.isPrivate !== collectionSecurity.isPrivate\n ) {\n throw new APIError('ImageKit client upload reference does not match this collection.', 400)\n }\n\n const cached = verifiedContexts.get(context)\n if (cached) {\n return cached\n }\n\n const storageFilePath = getClientUploadStorageFilePath({\n collectionPrefix: collectionSecurity.prefix,\n context,\n })\n const expectedFilePath = toImageKitPath(storageFilePath)\n let fileId = context.fileId\n\n // Receipts without a file ID can still be verified by their signed path and binding.\n // The completion handshake avoids depending on asset listing visibility.\n if (!fileId) {\n fileId = await findFileIDByPath(client, expectedFilePath)\n for (const delay of assetLookupRetryDelays) {\n if (fileId) {\n break\n }\n await new Promise((resolve) => setTimeout(resolve, delay))\n fileId = await findFileIDByPath(client, expectedFilePath)\n }\n }\n\n if (!fileId) {\n throw new APIError('ImageKit client upload was not found at its authorized path.', 400)\n }\n\n const uploadedFile = await client.files.get(fileId)\n const uploadedMime =\n typeof uploadedFile.mime === 'string' ? getMimeTypeEssence(uploadedFile.mime) : ''\n const uploadedSize = uploadedFile.size\n\n assertClientUploadAllowed({\n collection: collectionSecurity.collection,\n filename: context.filename,\n mimeType: uploadedMime,\n })\n const allowedMimeTypes =\n typeof collectionSecurity.collection.upload === 'object' &&\n Array.isArray(collectionSecurity.collection.upload.mimeTypes)\n ? collectionSecurity.collection.upload.mimeTypes\n : []\n\n if (\n uploadedFile.fileId !== fileId ||\n uploadedFile.filePath !== expectedFilePath ||\n uploadedFile.name !== context.filename ||\n (context.uploadTag !== undefined && !uploadedFile.tags?.includes(context.uploadTag)) ||\n !uploadedMime ||\n uploadedMime !== context.expectedMimeType ||\n typeof uploadedSize !== 'number' ||\n !Number.isSafeInteger(uploadedSize) ||\n uploadedSize !== context.expectedSize ||\n (allowedMimeTypes.length > 0 && !validateMimeType(uploadedMime, allowedMimeTypes)) ||\n Boolean(uploadedFile.isPrivateFile) !== collectionSecurity.isPrivate ||\n typeof uploadedFile.url !== 'string' ||\n uploadedFile.url.length === 0\n ) {\n throw new APIError('ImageKit client upload metadata did not match its authorization.', 400)\n }\n\n const verified: VerifiedImageKitUpload = {\n name: uploadedFile.name,\n fileId,\n filePath: expectedFilePath,\n fileType: uploadedFile.fileType,\n mimeType: uploadedMime,\n size: uploadedSize,\n thumbnailUrl: typeof uploadedFile.thumbnail === 'string' ? uploadedFile.thumbnail : undefined,\n url: uploadedFile.url,\n }\n\n verifiedContexts.set(context, verified)\n return verified\n }\n}\n"],"names":["buildStoragePathData","APIError","assertClientUploadAllowed","validateMimeType","isImageKitReceiptContext","getMimeTypeEssence","mimeType","split","trim","toLowerCase","toImageKitPath","storageFilePath","replace","assetLookupRetryDelays","findFileIDByPath","client","filePath","parentPath","slice","lastIndexOf","name","limit","skip","assets","list","type","path","found","find","asset","fileId","length","undefined","getClientUploadStorageFilePath","collectionPrefix","context","docPrefix","prefix","_objectKey","filter","Boolean","join","filename","createImageKitClientUploadVerifier","collections","verifiedContexts","WeakMap","collectionSlug","collectionSecurity","isPrivate","cached","get","expectedFilePath","delay","Promise","resolve","setTimeout","uploadedFile","files","uploadedMime","mime","uploadedSize","size","collection","allowedMimeTypes","upload","Array","isArray","mimeTypes","uploadTag","tags","includes","expectedMimeType","Number","isSafeInteger","expectedSize","isPrivateFile","url","verified","fileType","thumbnailUrl","thumbnail","set"],"mappings":"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"}
|
package/dist/clientUploads.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
1
|
+
import type { ClientUploadContext } from '@payloadcms/plugin-cloud-storage/types';
|
|
2
|
+
export declare const imageKitUploadAuthPath = "/imagekit-upload-auth";
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Provider metadata is verified server-side and is no longer accepted as upload
|
|
5
|
+
* proof. This backwards-compatible shape remains exported for existing type consumers.
|
|
6
|
+
*/
|
|
3
7
|
export type ImageKitClientUploadContext = {
|
|
4
8
|
fileId?: string;
|
|
5
9
|
filePath?: string;
|
|
@@ -9,7 +13,8 @@ export type ImageKitClientUploadContext = {
|
|
|
9
13
|
size?: number;
|
|
10
14
|
thumbnailUrl?: string;
|
|
11
15
|
url?: string;
|
|
12
|
-
}
|
|
16
|
+
} & Partial<ClientUploadContext>;
|
|
17
|
+
/** @deprecated The upload handler is configured internally by Payload 3.90.1. */
|
|
13
18
|
export type ImageKitUploadHandlerProps = {
|
|
14
19
|
authPath: `/${string}`;
|
|
15
20
|
collectionSlug: string;
|
|
@@ -17,4 +22,36 @@ export type ImageKitUploadHandlerProps = {
|
|
|
17
22
|
isPrivate?: boolean;
|
|
18
23
|
publicKey: string;
|
|
19
24
|
};
|
|
20
|
-
export
|
|
25
|
+
export type ImageKitReceiptContext = {
|
|
26
|
+
/** Legacy uploads stored beneath a per-upload folder. */
|
|
27
|
+
_objectKey?: string;
|
|
28
|
+
collectionSlug: string;
|
|
29
|
+
expectedMimeType: string;
|
|
30
|
+
expectedSize: number;
|
|
31
|
+
/** Present only after the server verifies the completed upload. */
|
|
32
|
+
fileId?: string;
|
|
33
|
+
filename: string;
|
|
34
|
+
isPrivate: boolean;
|
|
35
|
+
prefix: string;
|
|
36
|
+
/** Binds a pending authorization to the newly uploaded provider object. */
|
|
37
|
+
uploadTag?: string;
|
|
38
|
+
};
|
|
39
|
+
export type ImageKitUploadPayload = {
|
|
40
|
+
checks: string;
|
|
41
|
+
fileName: string;
|
|
42
|
+
folder: string;
|
|
43
|
+
isPrivateFile: 'false' | 'true';
|
|
44
|
+
overwriteFile: 'false';
|
|
45
|
+
tags: string;
|
|
46
|
+
useUniqueFileName: 'false';
|
|
47
|
+
};
|
|
48
|
+
export type ImageKitUploadAuthorization = {
|
|
49
|
+
clientUploadContext: ClientUploadContext;
|
|
50
|
+
filename: string;
|
|
51
|
+
token: string;
|
|
52
|
+
uploadPayload: ImageKitUploadPayload;
|
|
53
|
+
};
|
|
54
|
+
export type ImageKitUploadCompletion = {
|
|
55
|
+
clientUploadContext: ClientUploadContext;
|
|
56
|
+
};
|
|
57
|
+
export declare const isImageKitReceiptContext: (value: unknown) => value is ImageKitReceiptContext;
|
package/dist/clientUploads.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export const
|
|
2
|
-
export const
|
|
3
|
-
export const isImageKitClientUploadContext = (value)=>{
|
|
1
|
+
export const imageKitUploadAuthPath = '/imagekit-upload-auth';
|
|
2
|
+
export const isImageKitReceiptContext = (value)=>{
|
|
4
3
|
if (!value || typeof value !== 'object') {
|
|
5
4
|
return false;
|
|
6
5
|
}
|
|
7
|
-
|
|
6
|
+
const context = value;
|
|
7
|
+
return (context._objectKey === undefined || typeof context._objectKey === 'string' && context._objectKey.length > 0) && (context.uploadTag === undefined || typeof context.uploadTag === 'string' && context.uploadTag.length > 0) && Boolean(context._objectKey || context.uploadTag || context.fileId) && typeof context.collectionSlug === 'string' && context.collectionSlug.length > 0 && typeof context.expectedMimeType === 'string' && context.expectedMimeType.length > 0 && typeof context.expectedSize === 'number' && Number.isSafeInteger(context.expectedSize) && context.expectedSize >= 0 && (context.fileId === undefined || typeof context.fileId === 'string' && context.fileId.length > 0) && typeof context.filename === 'string' && context.filename.length > 0 && typeof context.isPrivate === 'boolean' && typeof context.prefix === 'string';
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
//# sourceMappingURL=clientUploads.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../src/clientUploads.ts"],"sourcesContent":["import type { ClientUploadContext } from '@payloadcms/plugin-cloud-storage/types'\n\nexport const imageKitUploadAuthPath = '/imagekit-upload-auth'\n\n/**\n * @deprecated Provider metadata is verified server-side and is no longer accepted as upload\n * proof. This backwards-compatible shape remains exported for existing type consumers.\n */\nexport type ImageKitClientUploadContext = {\n fileId?: string\n filePath?: string\n fileType?: string\n mimeType?: string\n name?: string\n size?: number\n thumbnailUrl?: string\n url?: string\n} & Partial<ClientUploadContext>\n\n/** @deprecated The upload handler is configured internally by Payload 3.90.1. */\nexport type ImageKitUploadHandlerProps = {\n authPath: `/${string}`\n collectionSlug: string\n folder: string\n isPrivate?: boolean\n publicKey: string\n}\n\nexport type ImageKitReceiptContext = {\n /** Legacy uploads stored beneath a per-upload folder. */\n _objectKey?: string\n collectionSlug: string\n expectedMimeType: string\n expectedSize: number\n /** Present only after the server verifies the completed upload. */\n fileId?: string\n filename: string\n isPrivate: boolean\n prefix: string\n /** Binds a pending authorization to the newly uploaded provider object. */\n uploadTag?: string\n}\n\nexport type ImageKitUploadPayload = {\n checks: string\n fileName: string\n folder: string\n isPrivateFile: 'false' | 'true'\n overwriteFile: 'false'\n tags: string\n useUniqueFileName: 'false'\n}\n\nexport type ImageKitUploadAuthorization = {\n clientUploadContext: ClientUploadContext\n filename: string\n token: string\n uploadPayload: ImageKitUploadPayload\n}\n\nexport type ImageKitUploadCompletion = {\n clientUploadContext: ClientUploadContext\n}\n\nexport const isImageKitReceiptContext = (value: unknown): value is ImageKitReceiptContext => {\n if (!value || typeof value !== 'object') {\n return false\n }\n\n const context = value as Partial<ImageKitReceiptContext>\n\n return (\n (context._objectKey === undefined ||\n (typeof context._objectKey === 'string' && context._objectKey.length > 0)) &&\n (context.uploadTag === undefined ||\n (typeof context.uploadTag === 'string' && context.uploadTag.length > 0)) &&\n Boolean(context._objectKey || context.uploadTag || context.fileId) &&\n typeof context.collectionSlug === 'string' &&\n context.collectionSlug.length > 0 &&\n typeof context.expectedMimeType === 'string' &&\n context.expectedMimeType.length > 0 &&\n typeof context.expectedSize === 'number' &&\n Number.isSafeInteger(context.expectedSize) &&\n context.expectedSize >= 0 &&\n (context.fileId === undefined ||\n (typeof context.fileId === 'string' && context.fileId.length > 0)) &&\n typeof context.filename === 'string' &&\n context.filename.length > 0 &&\n typeof context.isPrivate === 'boolean' &&\n typeof context.prefix === 'string'\n )\n}\n"],"names":["imageKitUploadAuthPath","isImageKitReceiptContext","value","context","_objectKey","undefined","length","uploadTag","Boolean","fileId","collectionSlug","expectedMimeType","expectedSize","Number","isSafeInteger","filename","isPrivate","prefix"],"mappings":"AAEA,OAAO,MAAMA,yBAAyB,wBAAuB;AA8D7D,OAAO,MAAMC,2BAA2B,CAACC;IACvC,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU;QACvC,OAAO;IACT;IAEA,MAAMC,UAAUD;IAEhB,OACE,AAACC,CAAAA,QAAQC,UAAU,KAAKC,aACrB,OAAOF,QAAQC,UAAU,KAAK,YAAYD,QAAQC,UAAU,CAACE,MAAM,GAAG,CAAC,KACzEH,CAAAA,QAAQI,SAAS,KAAKF,aACpB,OAAOF,QAAQI,SAAS,KAAK,YAAYJ,QAAQI,SAAS,CAACD,MAAM,GAAG,CAAC,KACxEE,QAAQL,QAAQC,UAAU,IAAID,QAAQI,SAAS,IAAIJ,QAAQM,MAAM,KACjE,OAAON,QAAQO,cAAc,KAAK,YAClCP,QAAQO,cAAc,CAACJ,MAAM,GAAG,KAChC,OAAOH,QAAQQ,gBAAgB,KAAK,YACpCR,QAAQQ,gBAAgB,CAACL,MAAM,GAAG,KAClC,OAAOH,QAAQS,YAAY,KAAK,YAChCC,OAAOC,aAAa,CAACX,QAAQS,YAAY,KACzCT,QAAQS,YAAY,IAAI,KACvBT,CAAAA,QAAQM,MAAM,KAAKJ,aACjB,OAAOF,QAAQM,MAAM,KAAK,YAAYN,QAAQM,MAAM,CAACH,MAAM,GAAG,CAAC,KAClE,OAAOH,QAAQY,QAAQ,KAAK,YAC5BZ,QAAQY,QAAQ,CAACT,MAAM,GAAG,KAC1B,OAAOH,QAAQa,SAAS,KAAK,aAC7B,OAAOb,QAAQc,MAAM,KAAK;AAE9B,EAAC"}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export declare const ImageKitUploadHandler: ({ children, collectionSlug, enabled, extra, prefix, serverHandlerPath, }: {
|
|
2
|
+
children: import("react").ReactNode;
|
|
3
|
+
collectionSlug: import("payload").UploadCollectionSlug;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
extra: Record<string, unknown>;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
serverHandlerPath: `/${string}`;
|
|
8
|
+
}) => import("react").JSX.Element;
|