@vercel/blob 2.1.0-f0898fb-20260127125323 → 2.1.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport * as crypto from 'crypto';\n// When bundled via a bundler supporting the `browser` field, then\n// the `undici` module will be replaced with https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\n// for browser contexts. See ./undici-browser.js and ./package.json\nimport { fetch } from 'undici';\nimport type {\n BlobAccessType,\n BlobCommandOptions,\n WithUploadProgress,\n} from './helpers';\nimport { BlobError, getTokenFromOptionsOrEnv } from './helpers';\nimport type { CommonCompleteMultipartUploadOptions } from './multipart/complete';\nimport { createCompleteMultipartUploadMethod } from './multipart/complete';\nimport { createCreateMultipartUploadMethod } from './multipart/create';\nimport { createCreateMultipartUploaderMethod } from './multipart/create-uploader';\nimport type { CommonMultipartUploadOptions } from './multipart/upload';\nimport { createUploadPartMethod } from './multipart/upload';\nimport { createPutMethod } from './put';\nimport type { PutBlobResult } from './put-helpers';\n\n/**\n * Interface for put, upload and multipart upload operations.\n * This type omits all options that are encoded in the client token.\n */\nexport interface ClientCommonCreateBlobOptions {\n /**\n * Whether the blob should be publicly accessible.\n * - 'public': The blob will be publicly accessible via its URL.\n * - 'private': The blob will require authentication to access.\n */\n access: BlobAccessType;\n /**\n * Defines the content type of the blob. By default, this value is inferred from the pathname.\n * Sent as the 'content-type' header when downloading a blob.\n */\n contentType?: string;\n /**\n * `AbortSignal` to cancel the running request. See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n */\n abortSignal?: AbortSignal;\n}\n\n/**\n * Shared interface for put and multipart operations that use client tokens.\n */\nexport interface ClientTokenOptions {\n /**\n * A client token that was generated by your server using the `generateClientToken` method.\n */\n token: string;\n}\n\n/**\n * Shared interface for put and upload operations.\n * @internal This is an internal interface not intended for direct use by consumers.\n */\ninterface ClientCommonPutOptions\n extends ClientCommonCreateBlobOptions,\n WithUploadProgress {\n /**\n * Whether to use multipart upload. Use this when uploading large files.\n * It will split the file into multiple parts, upload them in parallel and retry failed parts.\n */\n multipart?: boolean;\n}\n\n/**\n * @internal Internal function to validate client token options.\n */\nfunction createPutExtraChecks<\n TOptions extends ClientTokenOptions & ClientCommonCreateBlobOptions,\n>(methodName: string) {\n return function extraChecks(options: TOptions) {\n if (!options.token.startsWith('vercel_blob_client_')) {\n throw new BlobError(`${methodName} must be called with a client token`);\n }\n\n if (\n // @ts-expect-error -- Runtime check for DX.\n options.addRandomSuffix !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.allowOverwrite !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.cacheControlMaxAge !== undefined\n ) {\n throw new BlobError(\n `${methodName} doesn't allow \\`addRandomSuffix\\`, \\`cacheControlMaxAge\\` or \\`allowOverwrite\\`. Configure these options at the server side when generating client tokens.`,\n );\n }\n };\n}\n\n/**\n * Options for the client-side put operation.\n */\nexport type ClientPutCommandOptions = ClientCommonPutOptions &\n ClientTokenOptions;\n\n/**\n * Uploads a file to the blob store using a client token.\n *\n * @param pathname - The pathname to upload the blob to, including the extension. This will influence the URL of your blob.\n * @param body - The content of your blob. Can be a string, File, Blob, Buffer or ReadableStream.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the blob. By default, it's derived from the pathname.\n * - multipart - (Optional) Whether to use multipart upload for large files. It will split the file into multiple parts, upload them in parallel and retry failed parts.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const put = createPutMethod<ClientPutCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`put`'),\n});\n\n/**\n * Options for creating a multipart upload from the client side.\n */\nexport type ClientCreateMultipartUploadCommandOptions =\n ClientCommonCreateBlobOptions & ClientTokenOptions;\n\n/**\n * Creates a multipart upload. This is the first step in the manual multipart upload process.\n *\n * @param pathname - A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to an object containing:\n * - key: A string that identifies the blob object.\n * - uploadId: A string that identifies the multipart upload. Both are needed for subsequent uploadPart calls.\n */\nexport const createMultipartUpload =\n createCreateMultipartUploadMethod<ClientCreateMultipartUploadCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`createMultipartUpload`'),\n });\n\n/**\n * Creates a multipart uploader that simplifies the multipart upload process.\n * This is a wrapper around the manual multipart upload process that provides a more convenient API.\n *\n * @param pathname - A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to an uploader object with the following properties and methods:\n * - key: A string that identifies the blob object.\n * - uploadId: A string that identifies the multipart upload.\n * - uploadPart: A method to upload a part of the file.\n * - complete: A method to complete the multipart upload process.\n */\nexport const createMultipartUploader =\n createCreateMultipartUploaderMethod<ClientCreateMultipartUploadCommandOptions>(\n {\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`createMultipartUpload`'),\n },\n );\n\n/**\n * @internal Internal type for multipart upload options.\n */\ntype ClientMultipartUploadCommandOptions = ClientCommonCreateBlobOptions &\n ClientTokenOptions &\n CommonMultipartUploadOptions &\n WithUploadProgress;\n\n/**\n * Uploads a part of a multipart upload.\n * Used as part of the manual multipart upload process.\n *\n * @param pathname - Same value as the pathname parameter passed to createMultipartUpload. This will influence the final URL of your blob.\n * @param body - A blob object as ReadableStream, String, ArrayBuffer or Blob based on these supported body types. Each part must be a minimum of 5MB, except the last one which can be smaller.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - uploadId - (Required) A string returned from createMultipartUpload which identifies the multipart upload.\n * - key - (Required) A string returned from createMultipartUpload which identifies the blob object.\n * - partNumber - (Required) A number identifying which part is uploaded (1-based index).\n * - contentType - (Optional) The media type for the blob. By default, it's derived from the pathname.\n * - abortSignal - (Optional) AbortSignal to cancel the running request.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the uploaded part information containing etag and partNumber, which will be needed for the completeMultipartUpload call.\n */\nexport const uploadPart =\n createUploadPartMethod<ClientMultipartUploadCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`multipartUpload`'),\n });\n\n/**\n * @internal Internal type for completing multipart uploads.\n */\ntype ClientCompleteMultipartUploadCommandOptions =\n ClientCommonCreateBlobOptions &\n ClientTokenOptions &\n CommonCompleteMultipartUploadOptions;\n\n/**\n * Completes a multipart upload by combining all uploaded parts.\n * This is the final step in the manual multipart upload process.\n *\n * @param pathname - Same value as the pathname parameter passed to createMultipartUpload.\n * @param parts - An array containing all the uploaded parts information from previous uploadPart calls. Each part must have properties etag and partNumber.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - uploadId - (Required) A string returned from createMultipartUpload which identifies the multipart upload.\n * - key - (Required) A string returned from createMultipartUpload which identifies the blob object.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to the finalized blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const completeMultipartUpload =\n createCompleteMultipartUploadMethod<ClientCompleteMultipartUploadCommandOptions>(\n {\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`completeMultipartUpload`'),\n },\n );\n\n/**\n * Options for client-side upload operations.\n */\nexport interface CommonUploadOptions {\n /**\n * A route that implements the `handleUpload` function for generating a client token.\n */\n handleUploadUrl: string;\n /**\n * Additional data which will be sent to your `handleUpload` route.\n */\n clientPayload?: string;\n /**\n * Additional headers to be sent when making the request to your `handleUpload` route.\n * This is useful for sending authorization headers or any other custom headers.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Options for the upload method, which handles client-side uploads.\n */\nexport type UploadOptions = ClientCommonPutOptions & CommonUploadOptions;\n\n/**\n * Uploads a blob into your store from the client.\n * Detailed documentation can be found here: https://vercel.com/docs/vercel-blob/using-blob-sdk#client-uploads\n *\n * If you want to upload from your server instead, check out the documentation for the put operation: https://vercel.com/docs/vercel-blob/using-blob-sdk#upload-a-blob\n *\n * Unlike the put method, this method does not require a client token as it will fetch one from your server.\n *\n * @param pathname - The pathname to upload the blob to. This includes the filename and extension.\n * @param body - The contents of your blob. This has to be a supported fetch body type (string, Blob, File, ArrayBuffer, etc).\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' or 'private'. Public blobs are accessible via URL, private blobs require authentication.\n * - handleUploadUrl - (Required) A string specifying the route to call for generating client tokens for client uploads.\n * - clientPayload - (Optional) A string to be sent to your handleUpload server code. Example use-case: attaching the post id an image relates to.\n * - headers - (Optional) An object containing custom headers to be sent with the request to your handleUpload route. Example use-case: sending Authorization headers.\n * - contentType - (Optional) A string indicating the media type. By default, it's extracted from the pathname's extension.\n * - multipart - (Optional) Whether to use multipart upload for large files. It will split the file into multiple parts, upload them in parallel and retry failed parts.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const upload = createPutMethod<UploadOptions>({\n allowedOptions: ['contentType'],\n extraChecks(options) {\n if (options.handleUploadUrl === undefined) {\n throw new BlobError(\n \"client/`upload` requires the 'handleUploadUrl' parameter\",\n );\n }\n\n if (\n // @ts-expect-error -- Runtime check for DX.\n options.addRandomSuffix !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.createPutExtraChecks !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.cacheControlMaxAge !== undefined\n ) {\n throw new BlobError(\n \"client/`upload` doesn't allow `addRandomSuffix`, `cacheControlMaxAge` or `allowOverwrite`. Configure these options at the server side when generating client tokens.\",\n );\n }\n },\n async getToken(pathname, options) {\n return retrieveClientToken({\n handleUploadUrl: options.handleUploadUrl,\n pathname,\n clientPayload: options.clientPayload ?? null,\n multipart: options.multipart ?? false,\n headers: options.headers,\n });\n },\n});\n\n/**\n * @internal Internal function to import a crypto key.\n */\nasync function importKey(token: string): Promise<CryptoKey> {\n return globalThis.crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify'],\n );\n}\n\n/**\n * @internal Internal function to sign a payload.\n */\nasync function signPayload(\n payload: string,\n token: string,\n): Promise<string | undefined> {\n if (!globalThis.crypto) {\n return crypto.createHmac('sha256', token).update(payload).digest('hex');\n }\n\n const signature = await globalThis.crypto.subtle.sign(\n 'HMAC',\n await importKey(token),\n new TextEncoder().encode(payload),\n );\n return Buffer.from(new Uint8Array(signature)).toString('hex');\n}\n\n/**\n * @internal Internal function to verify a callback signature.\n */\nasync function verifyCallbackSignature({\n token,\n signature,\n body,\n}: {\n token: string;\n signature: string;\n body: string;\n}): Promise<boolean> {\n // callback signature is signed using the server token\n const secret = token;\n // Browsers, Edge runtime and Node >=20 implement the Web Crypto API\n\n if (!globalThis.crypto) {\n // Node <20 falls back to the Node.js crypto module\n const digest = crypto\n .createHmac('sha256', secret)\n .update(body)\n .digest('hex');\n const digestBuffer = Buffer.from(digest);\n const signatureBuffer = Buffer.from(signature);\n\n return (\n digestBuffer.length === signatureBuffer.length &&\n crypto.timingSafeEqual(digestBuffer, signatureBuffer)\n );\n }\n\n const verified = await globalThis.crypto.subtle.verify(\n 'HMAC',\n await importKey(token),\n // @ts-expect-error Buffer is compatible with BufferSource at runtime\n hexToArrayByte(signature),\n new TextEncoder().encode(body),\n );\n return verified;\n}\n\n/**\n * @internal Internal utility function to convert hex to array byte.\n */\nfunction hexToArrayByte(input: string): Buffer {\n if (input.length % 2 !== 0) {\n throw new RangeError('Expected string to be an even number of characters');\n }\n const view = new Uint8Array(input.length / 2);\n\n for (let i = 0; i < input.length; i += 2) {\n view[i / 2] = Number.parseInt(input.substring(i, i + 2), 16);\n }\n\n return Buffer.from(view);\n}\n\n/**\n * Decoded payload from a client token.\n */\nexport type DecodedClientTokenPayload = Omit<\n GenerateClientTokenOptions,\n 'token'\n> & {\n /**\n * Timestamp in milliseconds when the token will expire.\n */\n validUntil: number;\n};\n\n/**\n * Extracts and decodes the payload from a client token.\n *\n * @param clientToken - The client token string to decode\n * @returns The decoded payload containing token options\n */\nexport function getPayloadFromClientToken(\n clientToken: string,\n): DecodedClientTokenPayload {\n const [, , , , encodedToken] = clientToken.split('_');\n const encodedPayload = Buffer.from(encodedToken ?? '', 'base64')\n .toString()\n .split('.')[1];\n const decodedPayload = Buffer.from(encodedPayload ?? '', 'base64').toString();\n return JSON.parse(decodedPayload) as DecodedClientTokenPayload;\n}\n\n/**\n * @internal Event type constants for internal use.\n */\nconst EventTypes = {\n generateClientToken: 'blob.generate-client-token',\n uploadCompleted: 'blob.upload-completed',\n} as const;\n\n/**\n * Event for generating a client token for blob uploads.\n * @internal This is an internal interface used by the SDK.\n */\ninterface GenerateClientTokenEvent {\n /**\n * Type identifier for the generate client token event.\n */\n type: (typeof EventTypes)['generateClientToken'];\n\n /**\n * Payload containing information needed to generate a client token.\n */\n payload: {\n /**\n * The destination path for the blob.\n */\n pathname: string;\n\n /**\n * Whether the upload will use multipart uploading.\n */\n multipart: boolean;\n\n /**\n * Additional data from the client which will be available in onBeforeGenerateToken.\n */\n clientPayload: string | null;\n };\n}\n\n/**\n * Event that occurs when a client upload has completed.\n * @internal This is an internal interface used by the SDK.\n */\ninterface UploadCompletedEvent {\n /**\n * Type identifier for the upload completed event.\n */\n type: (typeof EventTypes)['uploadCompleted'];\n\n /**\n * Payload containing information about the uploaded blob.\n */\n payload: {\n /**\n * Details about the blob that was uploaded.\n */\n blob: PutBlobResult;\n\n /**\n * Optional payload that was defined during token generation.\n */\n tokenPayload?: string | null;\n };\n}\n\n/**\n * Union type representing either a request to generate a client token or a notification that an upload completed.\n */\nexport type HandleUploadBody = GenerateClientTokenEvent | UploadCompletedEvent;\n\n/**\n * Type representing either a Node.js IncomingMessage or a web standard Request object.\n * @internal This is an internal type used by the SDK.\n */\ntype RequestType = IncomingMessage | Request;\n\n/**\n * Options for the handleUpload function.\n */\nexport interface HandleUploadOptions {\n /**\n * The request body containing upload information.\n */\n body: HandleUploadBody;\n\n /**\n * Function called before generating the client token for uploads.\n *\n * @param pathname - The destination path for the blob\n * @param clientPayload - A string payload specified on the client when calling upload()\n * @param multipart - A boolean specifying whether the file is a multipart upload\n *\n * @returns An object with configuration options for the client token including the optional callbackUrl\n */\n onBeforeGenerateToken: (\n pathname: string,\n clientPayload: string | null,\n multipart: boolean,\n ) => Promise<\n Pick<\n GenerateClientTokenOptions,\n | 'allowedContentTypes'\n | 'maximumSizeInBytes'\n | 'validUntil'\n | 'addRandomSuffix'\n | 'allowOverwrite'\n | 'cacheControlMaxAge'\n > & { tokenPayload?: string | null; callbackUrl?: string }\n >;\n\n /**\n * Function called by Vercel Blob when the client upload finishes.\n * This is useful to update your database with the blob URL that was uploaded.\n *\n * @param body - Contains information about the completed upload including the blob details\n */\n onUploadCompleted?: (body: UploadCompletedEvent['payload']) => Promise<void>;\n\n /**\n * A string specifying the read-write token to use when making requests.\n * It defaults to process.env.BLOB_READ_WRITE_TOKEN when deployed on Vercel.\n */\n token?: string;\n\n /**\n * An IncomingMessage or Request object to be used to determine the action to take.\n */\n request: RequestType;\n}\n\n/**\n * A server-side route helper to manage client uploads. It has two responsibilities:\n * 1. Generate tokens for client uploads\n * 2. Listen for completed client uploads, so you can update your database with the URL of the uploaded file\n *\n * @param options - Configuration options for handling uploads\n * - request - (Required) An IncomingMessage or Request object to be used to determine the action to take.\n * - body - (Required) The request body containing upload information.\n * - onBeforeGenerateToken - (Required) Function called before generating the client token for uploads.\n * - onUploadCompleted - (Optional) Function called by Vercel Blob when the client upload finishes.\n * - token - (Optional) A string specifying the read-write token to use when making requests. Defaults to process.env.BLOB_READ_WRITE_TOKEN.\n * @returns A promise that resolves to either a client token generation result or an upload completion result\n */\nexport async function handleUpload({\n token,\n request,\n body,\n onBeforeGenerateToken,\n onUploadCompleted,\n}: HandleUploadOptions): Promise<\n | { type: 'blob.generate-client-token'; clientToken: string }\n | { type: 'blob.upload-completed'; response: 'ok' }\n> {\n const resolvedToken = getTokenFromOptionsOrEnv({ token });\n\n const type = body.type;\n switch (type) {\n case 'blob.generate-client-token': {\n const { pathname, clientPayload, multipart } = body.payload;\n const payload = await onBeforeGenerateToken(\n pathname,\n clientPayload,\n multipart,\n );\n const tokenPayload = payload.tokenPayload ?? clientPayload;\n const { callbackUrl: providedCallbackUrl, ...tokenOptions } = payload;\n let callbackUrl = providedCallbackUrl;\n\n // If onUploadCompleted is provided but no callbackUrl was provided, try to infer it from environment\n if (onUploadCompleted && !callbackUrl) {\n callbackUrl = getCallbackUrl(request);\n }\n\n // If no onUploadCompleted but callbackUrl was provided, warn about it\n if (!onUploadCompleted && callbackUrl) {\n console.warn(\n 'callbackUrl was provided but onUploadCompleted is not defined. The callback will not be handled.',\n );\n }\n\n // one hour\n const oneHourInSeconds = 60 * 60;\n const now = new Date();\n const validUntil =\n payload.validUntil ??\n now.setSeconds(now.getSeconds() + oneHourInSeconds);\n\n return {\n type,\n clientToken: await generateClientTokenFromReadWriteToken({\n ...tokenOptions,\n token: resolvedToken,\n pathname,\n onUploadCompleted: callbackUrl\n ? {\n callbackUrl,\n tokenPayload,\n }\n : undefined,\n validUntil,\n }),\n };\n }\n case 'blob.upload-completed': {\n const signatureHeader = 'x-vercel-signature';\n const signature = (\n 'credentials' in request\n ? (request.headers.get(signatureHeader) ?? '')\n : (request.headers[signatureHeader] ?? '')\n ) as string;\n\n if (!signature) {\n throw new BlobError('Missing callback signature');\n }\n\n const isVerified = await verifyCallbackSignature({\n token: resolvedToken,\n signature,\n body: JSON.stringify(body),\n });\n\n if (!isVerified) {\n throw new BlobError('Invalid callback signature');\n }\n\n if (onUploadCompleted) {\n await onUploadCompleted(body.payload);\n }\n return { type, response: 'ok' };\n }\n default:\n throw new BlobError('Invalid event type');\n }\n}\n\n/**\n * @internal Internal function to retrieve a client token from server.\n */\nasync function retrieveClientToken(options: {\n pathname: string;\n handleUploadUrl: string;\n clientPayload: string | null;\n multipart: boolean;\n abortSignal?: AbortSignal;\n headers?: Record<string, string>;\n}): Promise<string> {\n const { handleUploadUrl, pathname } = options;\n const url = isAbsoluteUrl(handleUploadUrl)\n ? handleUploadUrl\n : toAbsoluteUrl(handleUploadUrl);\n\n const event: GenerateClientTokenEvent = {\n type: EventTypes.generateClientToken,\n payload: {\n pathname,\n clientPayload: options.clientPayload,\n multipart: options.multipart,\n },\n };\n\n const res = await fetch(url, {\n method: 'POST',\n body: JSON.stringify(event),\n headers: {\n 'content-type': 'application/json',\n ...options.headers,\n },\n signal: options.abortSignal,\n });\n\n if (!res.ok) {\n throw new BlobError('Failed to retrieve the client token');\n }\n\n try {\n const { clientToken } = (await res.json()) as { clientToken: string };\n return clientToken;\n } catch {\n throw new BlobError('Failed to retrieve the client token');\n }\n}\n\n/**\n * @internal Internal utility to convert a relative URL to absolute URL.\n */\nfunction toAbsoluteUrl(url: string): string {\n // location is available in web workers too: https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n return new URL(url, location.href).href;\n}\n\n/**\n * @internal Internal utility to check if a URL is absolute.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n try {\n return Boolean(new URL(url));\n } catch {\n return false;\n }\n}\n\n/**\n * Generates a client token from a read-write token. This function must be called from a server environment.\n * The client token contains permissions and constraints that limit what the client can do.\n *\n * @param options - Options for generating the client token\n * - pathname - (Required) The destination path for the blob.\n * - token - (Optional) A string specifying the read-write token to use. Defaults to process.env.BLOB_READ_WRITE_TOKEN.\n * - onUploadCompleted - (Optional) Configuration for upload completion callback.\n * - maximumSizeInBytes - (Optional) A number specifying the maximum size in bytes that can be uploaded (max 5TB).\n * - allowedContentTypes - (Optional) An array of media types that are allowed to be uploaded. Wildcards are supported (text/*).\n * - validUntil - (Optional) A timestamp in ms when the token will expire. Defaults to one hour from generation.\n * - addRandomSuffix - (Optional) Whether to add a random suffix to the filename. Defaults to false.\n * - allowOverwrite - (Optional) Whether to allow overwriting existing blobs. Defaults to false.\n * - cacheControlMaxAge - (Optional) Number of seconds to configure cache duration. Defaults to one month.\n * @returns A promise that resolves to the generated client token string which can be used in client-side upload operations.\n */\nexport async function generateClientTokenFromReadWriteToken({\n token,\n ...argsWithoutToken\n}: GenerateClientTokenOptions): Promise<string> {\n if (typeof window !== 'undefined') {\n throw new BlobError(\n '\"generateClientTokenFromReadWriteToken\" must be called from a server environment',\n );\n }\n\n const timestamp = new Date();\n timestamp.setSeconds(timestamp.getSeconds() + 30);\n const readWriteToken = getTokenFromOptionsOrEnv({ token });\n\n const [, , , storeId = null] = readWriteToken.split('_');\n\n if (!storeId) {\n throw new BlobError(\n token ? 'Invalid `token` parameter' : 'Invalid `BLOB_READ_WRITE_TOKEN`',\n );\n }\n\n const payload = Buffer.from(\n JSON.stringify({\n ...argsWithoutToken,\n validUntil: argsWithoutToken.validUntil ?? timestamp.getTime(),\n }),\n ).toString('base64');\n\n const securedKey = await signPayload(payload, readWriteToken);\n\n if (!securedKey) {\n throw new BlobError('Unable to sign client token');\n }\n return `vercel_blob_client_${storeId}_${Buffer.from(\n `${securedKey}.${payload}`,\n ).toString('base64')}`;\n}\n\n/**\n * Options for generating a client token.\n */\nexport interface GenerateClientTokenOptions extends BlobCommandOptions {\n /**\n * The destination path for the blob\n */\n pathname: string;\n\n /**\n * Configuration for upload completion callback\n */\n onUploadCompleted?: {\n callbackUrl: string;\n tokenPayload?: string | null;\n };\n\n /**\n * A number specifying the maximum size in bytes that can be uploaded. The maximum is 5TB.\n */\n maximumSizeInBytes?: number;\n\n /**\n * An array of strings specifying the media type that are allowed to be uploaded.\n * By default, it's all content types. Wildcards are supported (text/*)\n */\n allowedContentTypes?: string[];\n\n /**\n * A number specifying the timestamp in ms when the token will expire.\n * By default, it's now + 1 hour.\n */\n validUntil?: number;\n\n /**\n * Adds a random suffix to the filename.\n * @defaultvalue false\n */\n addRandomSuffix?: boolean;\n\n /**\n * Allow overwriting an existing blob. By default this is set to false and will throw an error if the blob already exists.\n * @defaultvalue false\n */\n allowOverwrite?: boolean;\n\n /**\n * Number in seconds to configure how long Blobs are cached. Defaults to one month. Cannot be set to a value lower than 1 minute.\n * @defaultvalue 30 * 24 * 60 * 60 (1 Month)\n */\n cacheControlMaxAge?: number;\n}\n\n/**\n * @internal Helper function to determine the callback URL for client uploads\n * when onUploadCompleted is provided but no callbackUrl was specified\n */\nfunction getCallbackUrl(request: RequestType): string | undefined {\n const reqPath = getPathFromRequestUrl(request.url!);\n\n if (!reqPath) {\n console.warn(\n 'onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable.',\n );\n return undefined;\n }\n\n // Check if we have VERCEL_BLOB_CALLBACK_URL env var (works on or off Vercel)\n if (process.env.VERCEL_BLOB_CALLBACK_URL) {\n return `${process.env.VERCEL_BLOB_CALLBACK_URL}${reqPath}`;\n }\n\n // Not hosted on Vercel and no VERCEL_BLOB_CALLBACK_URL\n if (process.env.VERCEL !== '1') {\n console.warn(\n 'onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable.',\n );\n return undefined;\n }\n\n // If hosted on Vercel, generate default callbackUrl\n\n if (process.env.VERCEL_ENV === 'preview') {\n if (process.env.VERCEL_BRANCH_URL) {\n return `https://${process.env.VERCEL_BRANCH_URL}${reqPath}`;\n }\n if (process.env.VERCEL_URL) {\n return `https://${process.env.VERCEL_URL}${reqPath}`;\n }\n }\n\n if (\n process.env.VERCEL_ENV === 'production' &&\n process.env.VERCEL_PROJECT_PRODUCTION_URL\n ) {\n return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}${reqPath}`;\n }\n\n return undefined;\n}\n\n/**\n * @internal Helper function to safely extract pathname and query string from request URL\n * Handles both full URLs (http://localhost:3000/api/upload?test=1) and relative paths (/api/upload?test=1)\n */\nfunction getPathFromRequestUrl(url: string): string | null {\n try {\n // Using dummy.com as base URL to handle relative paths\n const parsedUrl = new URL(url, 'https://dummy.com');\n return parsedUrl.pathname + parsedUrl.search;\n } catch {\n return null;\n }\n}\n\nexport { createFolder } from './create-folder';\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http';\nimport * as crypto from 'crypto';\n// When bundled via a bundler supporting the `browser` field, then\n// the `undici` module will be replaced with https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\n// for browser contexts. See ./undici-browser.js and ./package.json\nimport { fetch } from 'undici';\nimport type { BlobCommandOptions, WithUploadProgress } from './helpers';\nimport { BlobError, getTokenFromOptionsOrEnv } from './helpers';\nimport type { CommonCompleteMultipartUploadOptions } from './multipart/complete';\nimport { createCompleteMultipartUploadMethod } from './multipart/complete';\nimport { createCreateMultipartUploadMethod } from './multipart/create';\nimport { createCreateMultipartUploaderMethod } from './multipart/create-uploader';\nimport type { CommonMultipartUploadOptions } from './multipart/upload';\nimport { createUploadPartMethod } from './multipart/upload';\nimport { createPutMethod } from './put';\nimport type { PutBlobResult } from './put-helpers';\n\n/**\n * Interface for put, upload and multipart upload operations.\n * This type omits all options that are encoded in the client token.\n */\nexport interface ClientCommonCreateBlobOptions {\n /**\n * Whether the blob should be publicly accessible.\n */\n access: 'public';\n /**\n * Defines the content type of the blob. By default, this value is inferred from the pathname.\n * Sent as the 'content-type' header when downloading a blob.\n */\n contentType?: string;\n /**\n * `AbortSignal` to cancel the running request. See https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal\n */\n abortSignal?: AbortSignal;\n}\n\n/**\n * Shared interface for put and multipart operations that use client tokens.\n */\nexport interface ClientTokenOptions {\n /**\n * A client token that was generated by your server using the `generateClientToken` method.\n */\n token: string;\n}\n\n/**\n * Shared interface for put and upload operations.\n * @internal This is an internal interface not intended for direct use by consumers.\n */\ninterface ClientCommonPutOptions\n extends ClientCommonCreateBlobOptions,\n WithUploadProgress {\n /**\n * Whether to use multipart upload. Use this when uploading large files.\n * It will split the file into multiple parts, upload them in parallel and retry failed parts.\n */\n multipart?: boolean;\n}\n\n/**\n * @internal Internal function to validate client token options.\n */\nfunction createPutExtraChecks<\n TOptions extends ClientTokenOptions & ClientCommonCreateBlobOptions,\n>(methodName: string) {\n return function extraChecks(options: TOptions) {\n if (!options.token.startsWith('vercel_blob_client_')) {\n throw new BlobError(`${methodName} must be called with a client token`);\n }\n\n if (\n // @ts-expect-error -- Runtime check for DX.\n options.addRandomSuffix !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.allowOverwrite !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.cacheControlMaxAge !== undefined\n ) {\n throw new BlobError(\n `${methodName} doesn't allow \\`addRandomSuffix\\`, \\`cacheControlMaxAge\\` or \\`allowOverwrite\\`. Configure these options at the server side when generating client tokens.`,\n );\n }\n };\n}\n\n/**\n * Options for the client-side put operation.\n */\nexport type ClientPutCommandOptions = ClientCommonPutOptions &\n ClientTokenOptions;\n\n/**\n * Uploads a file to the blob store using a client token.\n *\n * @param pathname - The pathname to upload the blob to, including the extension. This will influence the URL of your blob.\n * @param body - The content of your blob. Can be a string, File, Blob, Buffer or ReadableStream.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the blob. By default, it's derived from the pathname.\n * - multipart - (Optional) Whether to use multipart upload for large files. It will split the file into multiple parts, upload them in parallel and retry failed parts.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const put = createPutMethod<ClientPutCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`put`'),\n});\n\n/**\n * Options for creating a multipart upload from the client side.\n */\nexport type ClientCreateMultipartUploadCommandOptions =\n ClientCommonCreateBlobOptions & ClientTokenOptions;\n\n/**\n * Creates a multipart upload. This is the first step in the manual multipart upload process.\n *\n * @param pathname - A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to an object containing:\n * - key: A string that identifies the blob object.\n * - uploadId: A string that identifies the multipart upload. Both are needed for subsequent uploadPart calls.\n */\nexport const createMultipartUpload =\n createCreateMultipartUploadMethod<ClientCreateMultipartUploadCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`createMultipartUpload`'),\n });\n\n/**\n * Creates a multipart uploader that simplifies the multipart upload process.\n * This is a wrapper around the manual multipart upload process that provides a more convenient API.\n *\n * @param pathname - A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to an uploader object with the following properties and methods:\n * - key: A string that identifies the blob object.\n * - uploadId: A string that identifies the multipart upload.\n * - uploadPart: A method to upload a part of the file.\n * - complete: A method to complete the multipart upload process.\n */\nexport const createMultipartUploader =\n createCreateMultipartUploaderMethod<ClientCreateMultipartUploadCommandOptions>(\n {\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`createMultipartUpload`'),\n },\n );\n\n/**\n * @internal Internal type for multipart upload options.\n */\ntype ClientMultipartUploadCommandOptions = ClientCommonCreateBlobOptions &\n ClientTokenOptions &\n CommonMultipartUploadOptions &\n WithUploadProgress;\n\n/**\n * Uploads a part of a multipart upload.\n * Used as part of the manual multipart upload process.\n *\n * @param pathname - Same value as the pathname parameter passed to createMultipartUpload. This will influence the final URL of your blob.\n * @param body - A blob object as ReadableStream, String, ArrayBuffer or Blob based on these supported body types. Each part must be a minimum of 5MB, except the last one which can be smaller.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - uploadId - (Required) A string returned from createMultipartUpload which identifies the multipart upload.\n * - key - (Required) A string returned from createMultipartUpload which identifies the blob object.\n * - partNumber - (Required) A number identifying which part is uploaded (1-based index).\n * - contentType - (Optional) The media type for the blob. By default, it's derived from the pathname.\n * - abortSignal - (Optional) AbortSignal to cancel the running request.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the uploaded part information containing etag and partNumber, which will be needed for the completeMultipartUpload call.\n */\nexport const uploadPart =\n createUploadPartMethod<ClientMultipartUploadCommandOptions>({\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`multipartUpload`'),\n });\n\n/**\n * @internal Internal type for completing multipart uploads.\n */\ntype ClientCompleteMultipartUploadCommandOptions =\n ClientCommonCreateBlobOptions &\n ClientTokenOptions &\n CommonCompleteMultipartUploadOptions;\n\n/**\n * Completes a multipart upload by combining all uploaded parts.\n * This is the final step in the manual multipart upload process.\n *\n * @param pathname - Same value as the pathname parameter passed to createMultipartUpload.\n * @param parts - An array containing all the uploaded parts information from previous uploadPart calls. Each part must have properties etag and partNumber.\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - token - (Required) A client token generated by your server using the generateClientTokenFromReadWriteToken method.\n * - uploadId - (Required) A string returned from createMultipartUpload which identifies the multipart upload.\n * - key - (Required) A string returned from createMultipartUpload which identifies the blob object.\n * - contentType - (Optional) The media type for the file. If not specified, it's derived from the file extension.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * @returns A promise that resolves to the finalized blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const completeMultipartUpload =\n createCompleteMultipartUploadMethod<ClientCompleteMultipartUploadCommandOptions>(\n {\n allowedOptions: ['contentType'],\n extraChecks: createPutExtraChecks('client/`completeMultipartUpload`'),\n },\n );\n\n/**\n * Options for client-side upload operations.\n */\nexport interface CommonUploadOptions {\n /**\n * A route that implements the `handleUpload` function for generating a client token.\n */\n handleUploadUrl: string;\n /**\n * Additional data which will be sent to your `handleUpload` route.\n */\n clientPayload?: string;\n /**\n * Additional headers to be sent when making the request to your `handleUpload` route.\n * This is useful for sending authorization headers or any other custom headers.\n */\n headers?: Record<string, string>;\n}\n\n/**\n * Options for the upload method, which handles client-side uploads.\n */\nexport type UploadOptions = ClientCommonPutOptions & CommonUploadOptions;\n\n/**\n * Uploads a blob into your store from the client.\n * Detailed documentation can be found here: https://vercel.com/docs/vercel-blob/using-blob-sdk#client-uploads\n *\n * If you want to upload from your server instead, check out the documentation for the put operation: https://vercel.com/docs/vercel-blob/using-blob-sdk#upload-a-blob\n *\n * Unlike the put method, this method does not require a client token as it will fetch one from your server.\n *\n * @param pathname - The pathname to upload the blob to. This includes the filename and extension.\n * @param body - The contents of your blob. This has to be a supported fetch body type (string, Blob, File, ArrayBuffer, etc).\n * @param options - Configuration options including:\n * - access - (Required) Must be 'public' as blobs are publicly accessible.\n * - handleUploadUrl - (Required) A string specifying the route to call for generating client tokens for client uploads.\n * - clientPayload - (Optional) A string to be sent to your handleUpload server code. Example use-case: attaching the post id an image relates to.\n * - headers - (Optional) An object containing custom headers to be sent with the request to your handleUpload route. Example use-case: sending Authorization headers.\n * - contentType - (Optional) A string indicating the media type. By default, it's extracted from the pathname's extension.\n * - multipart - (Optional) Whether to use multipart upload for large files. It will split the file into multiple parts, upload them in parallel and retry failed parts.\n * - abortSignal - (Optional) AbortSignal to cancel the operation.\n * - onUploadProgress - (Optional) Callback to track upload progress: onUploadProgress(\\{loaded: number, total: number, percentage: number\\})\n * @returns A promise that resolves to the blob information, including pathname, contentType, contentDisposition, url, and downloadUrl.\n */\nexport const upload = createPutMethod<UploadOptions>({\n allowedOptions: ['contentType'],\n extraChecks(options) {\n if (options.handleUploadUrl === undefined) {\n throw new BlobError(\n \"client/`upload` requires the 'handleUploadUrl' parameter\",\n );\n }\n\n if (\n // @ts-expect-error -- Runtime check for DX.\n options.addRandomSuffix !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.createPutExtraChecks !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.cacheControlMaxAge !== undefined ||\n // @ts-expect-error -- Runtime check for DX.\n options.ifMatch !== undefined\n ) {\n throw new BlobError(\n \"client/`upload` doesn't allow `addRandomSuffix`, `cacheControlMaxAge`, `allowOverwrite` or `ifMatch`. Configure these options at the server side when generating client tokens.\",\n );\n }\n },\n async getToken(pathname, options) {\n return retrieveClientToken({\n handleUploadUrl: options.handleUploadUrl,\n pathname,\n clientPayload: options.clientPayload ?? null,\n multipart: options.multipart ?? false,\n headers: options.headers,\n });\n },\n});\n\n/**\n * @internal Internal function to import a crypto key.\n */\nasync function importKey(token: string): Promise<CryptoKey> {\n return globalThis.crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(token),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign', 'verify'],\n );\n}\n\n/**\n * @internal Internal function to sign a payload.\n */\nasync function signPayload(\n payload: string,\n token: string,\n): Promise<string | undefined> {\n if (!globalThis.crypto) {\n return crypto.createHmac('sha256', token).update(payload).digest('hex');\n }\n\n const signature = await globalThis.crypto.subtle.sign(\n 'HMAC',\n await importKey(token),\n new TextEncoder().encode(payload),\n );\n return Buffer.from(new Uint8Array(signature)).toString('hex');\n}\n\n/**\n * @internal Internal function to verify a callback signature.\n */\nasync function verifyCallbackSignature({\n token,\n signature,\n body,\n}: {\n token: string;\n signature: string;\n body: string;\n}): Promise<boolean> {\n // callback signature is signed using the server token\n const secret = token;\n // Browsers, Edge runtime and Node >=20 implement the Web Crypto API\n\n if (!globalThis.crypto) {\n // Node <20 falls back to the Node.js crypto module\n const digest = crypto\n .createHmac('sha256', secret)\n .update(body)\n .digest('hex');\n const digestBuffer = Buffer.from(digest);\n const signatureBuffer = Buffer.from(signature);\n\n return (\n digestBuffer.length === signatureBuffer.length &&\n crypto.timingSafeEqual(digestBuffer, signatureBuffer)\n );\n }\n\n const verified = await globalThis.crypto.subtle.verify(\n 'HMAC',\n await importKey(token),\n // @ts-expect-error Buffer is compatible with BufferSource at runtime\n hexToArrayByte(signature),\n new TextEncoder().encode(body),\n );\n return verified;\n}\n\n/**\n * @internal Internal utility function to convert hex to array byte.\n */\nfunction hexToArrayByte(input: string): Buffer {\n if (input.length % 2 !== 0) {\n throw new RangeError('Expected string to be an even number of characters');\n }\n const view = new Uint8Array(input.length / 2);\n\n for (let i = 0; i < input.length; i += 2) {\n view[i / 2] = Number.parseInt(input.substring(i, i + 2), 16);\n }\n\n return Buffer.from(view);\n}\n\n/**\n * Decoded payload from a client token.\n */\nexport type DecodedClientTokenPayload = Omit<\n GenerateClientTokenOptions,\n 'token'\n> & {\n /**\n * Timestamp in milliseconds when the token will expire.\n */\n validUntil: number;\n};\n\n/**\n * Extracts and decodes the payload from a client token.\n *\n * @param clientToken - The client token string to decode\n * @returns The decoded payload containing token options\n */\nexport function getPayloadFromClientToken(\n clientToken: string,\n): DecodedClientTokenPayload {\n const [, , , , encodedToken] = clientToken.split('_');\n const encodedPayload = Buffer.from(encodedToken ?? '', 'base64')\n .toString()\n .split('.')[1];\n const decodedPayload = Buffer.from(encodedPayload ?? '', 'base64').toString();\n return JSON.parse(decodedPayload) as DecodedClientTokenPayload;\n}\n\n/**\n * @internal Event type constants for internal use.\n */\nconst EventTypes = {\n generateClientToken: 'blob.generate-client-token',\n uploadCompleted: 'blob.upload-completed',\n} as const;\n\n/**\n * Event for generating a client token for blob uploads.\n * @internal This is an internal interface used by the SDK.\n */\ninterface GenerateClientTokenEvent {\n /**\n * Type identifier for the generate client token event.\n */\n type: (typeof EventTypes)['generateClientToken'];\n\n /**\n * Payload containing information needed to generate a client token.\n */\n payload: {\n /**\n * The destination path for the blob.\n */\n pathname: string;\n\n /**\n * Whether the upload will use multipart uploading.\n */\n multipart: boolean;\n\n /**\n * Additional data from the client which will be available in onBeforeGenerateToken.\n */\n clientPayload: string | null;\n };\n}\n\n/**\n * Event that occurs when a client upload has completed.\n * @internal This is an internal interface used by the SDK.\n */\ninterface UploadCompletedEvent {\n /**\n * Type identifier for the upload completed event.\n */\n type: (typeof EventTypes)['uploadCompleted'];\n\n /**\n * Payload containing information about the uploaded blob.\n */\n payload: {\n /**\n * Details about the blob that was uploaded.\n */\n blob: PutBlobResult;\n\n /**\n * Optional payload that was defined during token generation.\n */\n tokenPayload?: string | null;\n };\n}\n\n/**\n * Union type representing either a request to generate a client token or a notification that an upload completed.\n */\nexport type HandleUploadBody = GenerateClientTokenEvent | UploadCompletedEvent;\n\n/**\n * Type representing either a Node.js IncomingMessage or a web standard Request object.\n * @internal This is an internal type used by the SDK.\n */\ntype RequestType = IncomingMessage | Request;\n\n/**\n * Options for the handleUpload function.\n */\nexport interface HandleUploadOptions {\n /**\n * The request body containing upload information.\n */\n body: HandleUploadBody;\n\n /**\n * Function called before generating the client token for uploads.\n *\n * @param pathname - The destination path for the blob\n * @param clientPayload - A string payload specified on the client when calling upload()\n * @param multipart - A boolean specifying whether the file is a multipart upload\n *\n * @returns An object with configuration options for the client token including the optional callbackUrl\n */\n onBeforeGenerateToken: (\n pathname: string,\n clientPayload: string | null,\n multipart: boolean,\n ) => Promise<\n Pick<\n GenerateClientTokenOptions,\n | 'allowedContentTypes'\n | 'maximumSizeInBytes'\n | 'validUntil'\n | 'addRandomSuffix'\n | 'allowOverwrite'\n | 'cacheControlMaxAge'\n | 'ifMatch'\n > & { tokenPayload?: string | null; callbackUrl?: string }\n >;\n\n /**\n * Function called by Vercel Blob when the client upload finishes.\n * This is useful to update your database with the blob URL that was uploaded.\n *\n * @param body - Contains information about the completed upload including the blob details\n */\n onUploadCompleted?: (body: UploadCompletedEvent['payload']) => Promise<void>;\n\n /**\n * A string specifying the read-write token to use when making requests.\n * It defaults to process.env.BLOB_READ_WRITE_TOKEN when deployed on Vercel.\n */\n token?: string;\n\n /**\n * An IncomingMessage or Request object to be used to determine the action to take.\n */\n request: RequestType;\n}\n\n/**\n * A server-side route helper to manage client uploads. It has two responsibilities:\n * 1. Generate tokens for client uploads\n * 2. Listen for completed client uploads, so you can update your database with the URL of the uploaded file\n *\n * @param options - Configuration options for handling uploads\n * - request - (Required) An IncomingMessage or Request object to be used to determine the action to take.\n * - body - (Required) The request body containing upload information.\n * - onBeforeGenerateToken - (Required) Function called before generating the client token for uploads.\n * - onUploadCompleted - (Optional) Function called by Vercel Blob when the client upload finishes.\n * - token - (Optional) A string specifying the read-write token to use when making requests. Defaults to process.env.BLOB_READ_WRITE_TOKEN.\n * @returns A promise that resolves to either a client token generation result or an upload completion result\n */\nexport async function handleUpload({\n token,\n request,\n body,\n onBeforeGenerateToken,\n onUploadCompleted,\n}: HandleUploadOptions): Promise<\n | { type: 'blob.generate-client-token'; clientToken: string }\n | { type: 'blob.upload-completed'; response: 'ok' }\n> {\n const resolvedToken = getTokenFromOptionsOrEnv({ token });\n\n const type = body.type;\n switch (type) {\n case 'blob.generate-client-token': {\n const { pathname, clientPayload, multipart } = body.payload;\n const payload = await onBeforeGenerateToken(\n pathname,\n clientPayload,\n multipart,\n );\n const tokenPayload = payload.tokenPayload ?? clientPayload;\n const { callbackUrl: providedCallbackUrl, ...tokenOptions } = payload;\n let callbackUrl = providedCallbackUrl;\n\n // If onUploadCompleted is provided but no callbackUrl was provided, try to infer it from environment\n if (onUploadCompleted && !callbackUrl) {\n callbackUrl = getCallbackUrl(request);\n }\n\n // If no onUploadCompleted but callbackUrl was provided, warn about it\n if (!onUploadCompleted && callbackUrl) {\n console.warn(\n 'callbackUrl was provided but onUploadCompleted is not defined. The callback will not be handled.',\n );\n }\n\n // one hour\n const oneHourInSeconds = 60 * 60;\n const now = new Date();\n const validUntil =\n payload.validUntil ??\n now.setSeconds(now.getSeconds() + oneHourInSeconds);\n\n return {\n type,\n clientToken: await generateClientTokenFromReadWriteToken({\n ...tokenOptions,\n token: resolvedToken,\n pathname,\n onUploadCompleted: callbackUrl\n ? {\n callbackUrl,\n tokenPayload,\n }\n : undefined,\n validUntil,\n }),\n };\n }\n case 'blob.upload-completed': {\n const signatureHeader = 'x-vercel-signature';\n const signature = (\n 'credentials' in request\n ? (request.headers.get(signatureHeader) ?? '')\n : (request.headers[signatureHeader] ?? '')\n ) as string;\n\n if (!signature) {\n throw new BlobError('Missing callback signature');\n }\n\n const isVerified = await verifyCallbackSignature({\n token: resolvedToken,\n signature,\n body: JSON.stringify(body),\n });\n\n if (!isVerified) {\n throw new BlobError('Invalid callback signature');\n }\n\n if (onUploadCompleted) {\n await onUploadCompleted(body.payload);\n }\n return { type, response: 'ok' };\n }\n default:\n throw new BlobError('Invalid event type');\n }\n}\n\n/**\n * @internal Internal function to retrieve a client token from server.\n */\nasync function retrieveClientToken(options: {\n pathname: string;\n handleUploadUrl: string;\n clientPayload: string | null;\n multipart: boolean;\n abortSignal?: AbortSignal;\n headers?: Record<string, string>;\n}): Promise<string> {\n const { handleUploadUrl, pathname } = options;\n const url = isAbsoluteUrl(handleUploadUrl)\n ? handleUploadUrl\n : toAbsoluteUrl(handleUploadUrl);\n\n const event: GenerateClientTokenEvent = {\n type: EventTypes.generateClientToken,\n payload: {\n pathname,\n clientPayload: options.clientPayload,\n multipart: options.multipart,\n },\n };\n\n const res = await fetch(url, {\n method: 'POST',\n body: JSON.stringify(event),\n headers: {\n 'content-type': 'application/json',\n ...options.headers,\n },\n signal: options.abortSignal,\n });\n\n if (!res.ok) {\n throw new BlobError('Failed to retrieve the client token');\n }\n\n try {\n const { clientToken } = (await res.json()) as { clientToken: string };\n return clientToken;\n } catch {\n throw new BlobError('Failed to retrieve the client token');\n }\n}\n\n/**\n * @internal Internal utility to convert a relative URL to absolute URL.\n */\nfunction toAbsoluteUrl(url: string): string {\n // location is available in web workers too: https://developer.mozilla.org/en-US/docs/Web/API/Window/location\n return new URL(url, location.href).href;\n}\n\n/**\n * @internal Internal utility to check if a URL is absolute.\n */\nfunction isAbsoluteUrl(url: string): boolean {\n try {\n return Boolean(new URL(url));\n } catch {\n return false;\n }\n}\n\n/**\n * Generates a client token from a read-write token. This function must be called from a server environment.\n * The client token contains permissions and constraints that limit what the client can do.\n *\n * @param options - Options for generating the client token\n * - pathname - (Required) The destination path for the blob.\n * - token - (Optional) A string specifying the read-write token to use. Defaults to process.env.BLOB_READ_WRITE_TOKEN.\n * - onUploadCompleted - (Optional) Configuration for upload completion callback.\n * - maximumSizeInBytes - (Optional) A number specifying the maximum size in bytes that can be uploaded (max 5TB).\n * - allowedContentTypes - (Optional) An array of media types that are allowed to be uploaded. Wildcards are supported (text/*).\n * - validUntil - (Optional) A timestamp in ms when the token will expire. Defaults to one hour from generation.\n * - addRandomSuffix - (Optional) Whether to add a random suffix to the filename. Defaults to false.\n * - allowOverwrite - (Optional) Whether to allow overwriting existing blobs. Defaults to false.\n * - cacheControlMaxAge - (Optional) Number of seconds to configure cache duration. Defaults to one month.\n * - ifMatch - (Optional) Only write if the ETag matches (optimistic concurrency control).\n * @returns A promise that resolves to the generated client token string which can be used in client-side upload operations.\n */\nexport async function generateClientTokenFromReadWriteToken({\n token,\n ...argsWithoutToken\n}: GenerateClientTokenOptions): Promise<string> {\n if (typeof window !== 'undefined') {\n throw new BlobError(\n '\"generateClientTokenFromReadWriteToken\" must be called from a server environment',\n );\n }\n\n const timestamp = new Date();\n timestamp.setSeconds(timestamp.getSeconds() + 30);\n const readWriteToken = getTokenFromOptionsOrEnv({ token });\n\n const [, , , storeId = null] = readWriteToken.split('_');\n\n if (!storeId) {\n throw new BlobError(\n token ? 'Invalid `token` parameter' : 'Invalid `BLOB_READ_WRITE_TOKEN`',\n );\n }\n\n const payload = Buffer.from(\n JSON.stringify({\n ...argsWithoutToken,\n validUntil: argsWithoutToken.validUntil ?? timestamp.getTime(),\n }),\n ).toString('base64');\n\n const securedKey = await signPayload(payload, readWriteToken);\n\n if (!securedKey) {\n throw new BlobError('Unable to sign client token');\n }\n return `vercel_blob_client_${storeId}_${Buffer.from(\n `${securedKey}.${payload}`,\n ).toString('base64')}`;\n}\n\n/**\n * Options for generating a client token.\n */\nexport interface GenerateClientTokenOptions extends BlobCommandOptions {\n /**\n * The destination path for the blob\n */\n pathname: string;\n\n /**\n * Configuration for upload completion callback\n */\n onUploadCompleted?: {\n callbackUrl: string;\n tokenPayload?: string | null;\n };\n\n /**\n * A number specifying the maximum size in bytes that can be uploaded. The maximum is 5TB.\n */\n maximumSizeInBytes?: number;\n\n /**\n * An array of strings specifying the media type that are allowed to be uploaded.\n * By default, it's all content types. Wildcards are supported (text/*)\n */\n allowedContentTypes?: string[];\n\n /**\n * A number specifying the timestamp in ms when the token will expire.\n * By default, it's now + 1 hour.\n */\n validUntil?: number;\n\n /**\n * Adds a random suffix to the filename.\n * @defaultvalue false\n */\n addRandomSuffix?: boolean;\n\n /**\n * Allow overwriting an existing blob. By default this is set to false and will throw an error if the blob already exists.\n * @defaultvalue false\n */\n allowOverwrite?: boolean;\n\n /**\n * Number in seconds to configure how long Blobs are cached. Defaults to one month. Cannot be set to a value lower than 1 minute.\n * @defaultvalue 30 * 24 * 60 * 60 (1 Month)\n */\n cacheControlMaxAge?: number;\n\n /**\n * Only write if the ETag matches (optimistic concurrency control).\n * Use this for conditional writes to prevent overwriting changes made by others.\n * If the ETag doesn't match, a `BlobPreconditionFailedError` will be thrown.\n */\n ifMatch?: string;\n}\n\n/**\n * @internal Helper function to determine the callback URL for client uploads\n * when onUploadCompleted is provided but no callbackUrl was specified\n */\nfunction getCallbackUrl(request: RequestType): string | undefined {\n const reqPath = getPathFromRequestUrl(request.url!);\n\n if (!reqPath) {\n console.warn(\n 'onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable.',\n );\n return undefined;\n }\n\n // Check if we have VERCEL_BLOB_CALLBACK_URL env var (works on or off Vercel)\n if (process.env.VERCEL_BLOB_CALLBACK_URL) {\n return `${process.env.VERCEL_BLOB_CALLBACK_URL}${reqPath}`;\n }\n\n // Not hosted on Vercel and no VERCEL_BLOB_CALLBACK_URL\n if (process.env.VERCEL !== '1') {\n console.warn(\n 'onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable.',\n );\n return undefined;\n }\n\n // If hosted on Vercel, generate default callbackUrl\n\n if (process.env.VERCEL_ENV === 'preview') {\n if (process.env.VERCEL_BRANCH_URL) {\n return `https://${process.env.VERCEL_BRANCH_URL}${reqPath}`;\n }\n if (process.env.VERCEL_URL) {\n return `https://${process.env.VERCEL_URL}${reqPath}`;\n }\n }\n\n if (\n process.env.VERCEL_ENV === 'production' &&\n process.env.VERCEL_PROJECT_PRODUCTION_URL\n ) {\n return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}${reqPath}`;\n }\n\n return undefined;\n}\n\n/**\n * @internal Helper function to safely extract pathname and query string from request URL\n * Handles both full URLs (http://localhost:3000/api/upload?test=1) and relative paths (/api/upload?test=1)\n */\nfunction getPathFromRequestUrl(url: string): string | null {\n try {\n // Using dummy.com as base URL to handle relative paths\n const parsedUrl = new URL(url, 'https://dummy.com');\n return parsedUrl.pathname + parsedUrl.search;\n } catch {\n return null;\n }\n}\n\nexport { createFolder } from './create-folder';\n"],"mappings":"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","names":[]}
@@ -12,19 +12,11 @@ interface BlobCommandOptions {
12
12
  */
13
13
  abortSignal?: AbortSignal;
14
14
  }
15
- /**
16
- * The access level of a blob.
17
- * - 'public': The blob is publicly accessible via its URL.
18
- * - 'private': The blob requires authentication to access.
19
- */
20
- type BlobAccessType = 'public' | 'private';
21
15
  interface CommonCreateBlobOptions extends BlobCommandOptions {
22
16
  /**
23
- * Whether the blob should be publicly accessible.
24
- * - 'public': The blob will be publicly accessible via its URL.
25
- * - 'private': The blob will require authentication to access.
17
+ * Whether the blob should be publicly accessible. The only currently allowed value is `public`.
26
18
  */
27
- access: BlobAccessType;
19
+ access: 'public';
28
20
  /**
29
21
  * Adds a random suffix to the filename.
30
22
  * @defaultvalue false
@@ -45,6 +37,12 @@ interface CommonCreateBlobOptions extends BlobCommandOptions {
45
37
  * @defaultvalue 30 * 24 * 60 * 60 (1 Month)
46
38
  */
47
39
  cacheControlMaxAge?: number;
40
+ /**
41
+ * Only perform the operation if the blob's current ETag matches this value.
42
+ * Use this for optimistic concurrency control to prevent overwriting changes made by others.
43
+ * If the ETag doesn't match, a `BlobPreconditionFailedError` will be thrown.
44
+ */
45
+ ifMatch?: string;
48
46
  }
49
47
  /**
50
48
  * Event object passed to the onUploadProgress callback.
@@ -116,6 +114,10 @@ interface PutBlobResult {
116
114
  * The content disposition header value.
117
115
  */
118
116
  contentDisposition: string;
117
+ /**
118
+ * The ETag of the blob. Can be used with `ifMatch` for conditional writes.
119
+ */
120
+ etag: string;
119
121
  }
120
122
  /**
121
123
  * Represents the body content for a put operation.
@@ -210,4 +212,4 @@ interface CreateFolderResult {
210
212
  */
211
213
  declare function createFolder(pathname: string, options?: BlobCommandOptions): Promise<CreateFolderResult>;
212
214
 
213
- export { type BlobAccessType as B, type CommonCompleteMultipartUploadOptions as C, type OnUploadProgressCallback as O, type PutBlobResult as P, type UploadPartCommandOptions as U, type WithUploadProgress as W, type BlobCommandOptions as a, type Part as b, type PutBody as c, type CommonMultipartUploadOptions as d, createFolder as e, type CommonCreateBlobOptions as f, BlobError as g, type CompleteMultipartUploadCommandOptions as h, type PartInput as i, type UploadProgressEvent as j, getDownloadUrl as k };
215
+ export { type BlobCommandOptions as B, type CommonCompleteMultipartUploadOptions as C, type OnUploadProgressCallback as O, type PutBlobResult as P, type UploadPartCommandOptions as U, type WithUploadProgress as W, type Part as a, type PutBody as b, type CommonMultipartUploadOptions as c, createFolder as d, type CommonCreateBlobOptions as e, BlobError as f, type CompleteMultipartUploadCommandOptions as g, type PartInput as h, type UploadProgressEvent as i, getDownloadUrl as j };
@@ -12,19 +12,11 @@ interface BlobCommandOptions {
12
12
  */
13
13
  abortSignal?: AbortSignal;
14
14
  }
15
- /**
16
- * The access level of a blob.
17
- * - 'public': The blob is publicly accessible via its URL.
18
- * - 'private': The blob requires authentication to access.
19
- */
20
- type BlobAccessType = 'public' | 'private';
21
15
  interface CommonCreateBlobOptions extends BlobCommandOptions {
22
16
  /**
23
- * Whether the blob should be publicly accessible.
24
- * - 'public': The blob will be publicly accessible via its URL.
25
- * - 'private': The blob will require authentication to access.
17
+ * Whether the blob should be publicly accessible. The only currently allowed value is `public`.
26
18
  */
27
- access: BlobAccessType;
19
+ access: 'public';
28
20
  /**
29
21
  * Adds a random suffix to the filename.
30
22
  * @defaultvalue false
@@ -45,6 +37,12 @@ interface CommonCreateBlobOptions extends BlobCommandOptions {
45
37
  * @defaultvalue 30 * 24 * 60 * 60 (1 Month)
46
38
  */
47
39
  cacheControlMaxAge?: number;
40
+ /**
41
+ * Only perform the operation if the blob's current ETag matches this value.
42
+ * Use this for optimistic concurrency control to prevent overwriting changes made by others.
43
+ * If the ETag doesn't match, a `BlobPreconditionFailedError` will be thrown.
44
+ */
45
+ ifMatch?: string;
48
46
  }
49
47
  /**
50
48
  * Event object passed to the onUploadProgress callback.
@@ -116,6 +114,10 @@ interface PutBlobResult {
116
114
  * The content disposition header value.
117
115
  */
118
116
  contentDisposition: string;
117
+ /**
118
+ * The ETag of the blob. Can be used with `ifMatch` for conditional writes.
119
+ */
120
+ etag: string;
119
121
  }
120
122
  /**
121
123
  * Represents the body content for a put operation.
@@ -210,4 +212,4 @@ interface CreateFolderResult {
210
212
  */
211
213
  declare function createFolder(pathname: string, options?: BlobCommandOptions): Promise<CreateFolderResult>;
212
214
 
213
- export { type BlobAccessType as B, type CommonCompleteMultipartUploadOptions as C, type OnUploadProgressCallback as O, type PutBlobResult as P, type UploadPartCommandOptions as U, type WithUploadProgress as W, type BlobCommandOptions as a, type Part as b, type PutBody as c, type CommonMultipartUploadOptions as d, createFolder as e, type CommonCreateBlobOptions as f, BlobError as g, type CompleteMultipartUploadCommandOptions as h, type PartInput as i, type UploadProgressEvent as j, getDownloadUrl as k };
215
+ export { type BlobCommandOptions as B, type CommonCompleteMultipartUploadOptions as C, type OnUploadProgressCallback as O, type PutBlobResult as P, type UploadPartCommandOptions as U, type WithUploadProgress as W, type Part as a, type PutBody as b, type CommonMultipartUploadOptions as c, createFolder as d, type CommonCreateBlobOptions as e, BlobError as f, type CompleteMultipartUploadCommandOptions as g, type PartInput as h, type UploadProgressEvent as i, getDownloadUrl as j };
package/dist/index.cjs CHANGED
@@ -23,11 +23,11 @@
23
23
 
24
24
 
25
25
 
26
- var _chunk7TB5OCQAcjs = require('./chunk-7TB5OCQA.cjs');
26
+ var _chunk23VLASYPcjs = require('./chunk-23VLASYP.cjs');
27
27
 
28
28
  // src/del.ts
29
29
  async function del(urlOrPathname, options) {
30
- await _chunk7TB5OCQAcjs.requestApi.call(void 0,
30
+ await _chunk23VLASYPcjs.requestApi.call(void 0,
31
31
  "/delete",
32
32
  {
33
33
  method: "POST",
@@ -44,7 +44,7 @@ async function del(urlOrPathname, options) {
44
44
  // src/head.ts
45
45
  async function head(urlOrPathname, options) {
46
46
  const searchParams = new URLSearchParams({ url: urlOrPathname });
47
- const response = await _chunk7TB5OCQAcjs.requestApi.call(void 0,
47
+ const response = await _chunk23VLASYPcjs.requestApi.call(void 0,
48
48
  `?${searchParams.toString()}`,
49
49
  // HEAD can't have body as a response, so we use GET
50
50
  {
@@ -61,89 +61,8 @@ async function head(urlOrPathname, options) {
61
61
  contentType: response.contentType,
62
62
  contentDisposition: response.contentDisposition,
63
63
  cacheControl: response.cacheControl,
64
- uploadedAt: new Date(response.uploadedAt)
65
- };
66
- }
67
-
68
- // src/get.ts
69
- function isUrl(urlOrPathname) {
70
- return urlOrPathname.startsWith("http://") || urlOrPathname.startsWith("https://");
71
- }
72
- function extractPathnameFromUrl(url) {
73
- try {
74
- const parsedUrl = new URL(url);
75
- return parsedUrl.pathname.slice(1);
76
- } catch (e) {
77
- return url;
78
- }
79
- }
80
- function getStoreIdFromToken(token) {
81
- const [, , , storeId = ""] = token.split("_");
82
- return storeId;
83
- }
84
- function constructBlobUrl(storeId, pathname) {
85
- return `https://${storeId}.public.blob.vercel-storage.com/${pathname}`;
86
- }
87
- async function get(urlOrPathname, options) {
88
- if (!urlOrPathname) {
89
- throw new (0, _chunk7TB5OCQAcjs.BlobError)("url or pathname is required");
90
- }
91
- if (!options) {
92
- throw new (0, _chunk7TB5OCQAcjs.BlobError)("missing options, see usage");
93
- }
94
- if (options.access !== "public" && options.access !== "private") {
95
- throw new (0, _chunk7TB5OCQAcjs.BlobError)('access must be "public" or "private"');
96
- }
97
- const token = _chunk7TB5OCQAcjs.getTokenFromOptionsOrEnv.call(void 0, options);
98
- let blobUrl;
99
- let pathname;
100
- if (isUrl(urlOrPathname)) {
101
- blobUrl = urlOrPathname;
102
- pathname = extractPathnameFromUrl(urlOrPathname);
103
- } else {
104
- const storeId = getStoreIdFromToken(token);
105
- if (!storeId) {
106
- throw new (0, _chunk7TB5OCQAcjs.BlobError)("Invalid token: unable to extract store ID");
107
- }
108
- pathname = urlOrPathname;
109
- blobUrl = constructBlobUrl(storeId, pathname);
110
- }
111
- const headers = {
112
- authorization: `Bearer ${token}`
113
- };
114
- const response = await fetch(blobUrl, {
115
- method: "GET",
116
- headers,
117
- signal: options.abortSignal
118
- });
119
- if (!response.ok) {
120
- if (response.status === 404) {
121
- return null;
122
- }
123
- throw new (0, _chunk7TB5OCQAcjs.BlobError)(
124
- `Failed to fetch blob: ${response.status} ${response.statusText}`
125
- );
126
- }
127
- const stream = response.body;
128
- if (!stream) {
129
- throw new (0, _chunk7TB5OCQAcjs.BlobError)("Response body is null");
130
- }
131
- const contentLength = response.headers.get("content-length");
132
- const lastModified = response.headers.get("last-modified");
133
- const downloadUrl = new URL(blobUrl);
134
- downloadUrl.searchParams.set("download", "1");
135
- return {
136
- stream,
137
- blob: {
138
- url: blobUrl,
139
- downloadUrl: downloadUrl.toString(),
140
- pathname,
141
- contentType: response.headers.get("content-type") || "application/octet-stream",
142
- contentDisposition: response.headers.get("content-disposition") || "",
143
- cacheControl: response.headers.get("cache-control") || "",
144
- size: contentLength ? parseInt(contentLength, 10) : 0,
145
- uploadedAt: lastModified ? new Date(lastModified) : /* @__PURE__ */ new Date()
146
- }
64
+ uploadedAt: new Date(response.uploadedAt),
65
+ etag: response.etag
147
66
  };
148
67
  }
149
68
 
@@ -163,7 +82,7 @@ async function list(options) {
163
82
  if (options == null ? void 0 : options.mode) {
164
83
  searchParams.set("mode", options.mode);
165
84
  }
166
- const response = await _chunk7TB5OCQAcjs.requestApi.call(void 0,
85
+ const response = await _chunk23VLASYPcjs.requestApi.call(void 0,
167
86
  `?${searchParams.toString()}`,
168
87
  {
169
88
  method: "GET",
@@ -191,32 +110,32 @@ function mapBlobResult(blobResult) {
191
110
  downloadUrl: blobResult.downloadUrl,
192
111
  pathname: blobResult.pathname,
193
112
  size: blobResult.size,
194
- uploadedAt: new Date(blobResult.uploadedAt)
113
+ uploadedAt: new Date(blobResult.uploadedAt),
114
+ etag: blobResult.etag
195
115
  };
196
116
  }
197
117
 
198
118
  // src/copy.ts
199
119
  async function copy(fromUrlOrPathname, toPathname, options) {
200
120
  if (!options) {
201
- throw new (0, _chunk7TB5OCQAcjs.BlobError)("missing options, see usage");
121
+ throw new (0, _chunk23VLASYPcjs.BlobError)("missing options, see usage");
202
122
  }
203
- if (options.access !== "public" && options.access !== "private") {
204
- throw new (0, _chunk7TB5OCQAcjs.BlobError)('access must be "public" or "private"');
123
+ if (options.access !== "public") {
124
+ throw new (0, _chunk23VLASYPcjs.BlobError)('access must be "public"');
205
125
  }
206
- if (toPathname.length > _chunk7TB5OCQAcjs.MAXIMUM_PATHNAME_LENGTH) {
207
- throw new (0, _chunk7TB5OCQAcjs.BlobError)(
208
- `pathname is too long, maximum length is ${_chunk7TB5OCQAcjs.MAXIMUM_PATHNAME_LENGTH}`
126
+ if (toPathname.length > _chunk23VLASYPcjs.MAXIMUM_PATHNAME_LENGTH) {
127
+ throw new (0, _chunk23VLASYPcjs.BlobError)(
128
+ `pathname is too long, maximum length is ${_chunk23VLASYPcjs.MAXIMUM_PATHNAME_LENGTH}`
209
129
  );
210
130
  }
211
- for (const invalidCharacter of _chunk7TB5OCQAcjs.disallowedPathnameCharacters) {
131
+ for (const invalidCharacter of _chunk23VLASYPcjs.disallowedPathnameCharacters) {
212
132
  if (toPathname.includes(invalidCharacter)) {
213
- throw new (0, _chunk7TB5OCQAcjs.BlobError)(
133
+ throw new (0, _chunk23VLASYPcjs.BlobError)(
214
134
  `pathname cannot contain "${invalidCharacter}", please encode it if needed`
215
135
  );
216
136
  }
217
137
  }
218
138
  const headers = {};
219
- headers["x-vercel-blob-access"] = options.access;
220
139
  if (options.addRandomSuffix !== void 0) {
221
140
  headers["x-add-random-suffix"] = options.addRandomSuffix ? "1" : "0";
222
141
  }
@@ -229,11 +148,14 @@ async function copy(fromUrlOrPathname, toPathname, options) {
229
148
  if (options.cacheControlMaxAge !== void 0) {
230
149
  headers["x-cache-control-max-age"] = options.cacheControlMaxAge.toString();
231
150
  }
151
+ if (options.ifMatch) {
152
+ headers["x-if-match"] = options.ifMatch;
153
+ }
232
154
  const params = new URLSearchParams({
233
155
  pathname: toPathname,
234
156
  fromUrl: fromUrlOrPathname
235
157
  });
236
- const response = await _chunk7TB5OCQAcjs.requestApi.call(void 0,
158
+ const response = await _chunk23VLASYPcjs.requestApi.call(void 0,
237
159
  `?${params.toString()}`,
238
160
  {
239
161
  method: "PUT",
@@ -247,36 +169,40 @@ async function copy(fromUrlOrPathname, toPathname, options) {
247
169
  downloadUrl: response.downloadUrl,
248
170
  pathname: response.pathname,
249
171
  contentType: response.contentType,
250
- contentDisposition: response.contentDisposition
172
+ contentDisposition: response.contentDisposition,
173
+ etag: response.etag
251
174
  };
252
175
  }
253
176
 
254
177
  // src/index.ts
255
- var put = _chunk7TB5OCQAcjs.createPutMethod.call(void 0, {
178
+ var put = _chunk23VLASYPcjs.createPutMethod.call(void 0, {
256
179
  allowedOptions: [
257
180
  "cacheControlMaxAge",
258
181
  "addRandomSuffix",
259
182
  "allowOverwrite",
260
- "contentType"
183
+ "contentType",
184
+ "ifMatch"
261
185
  ]
262
186
  });
263
- var createMultipartUpload = _chunk7TB5OCQAcjs.createCreateMultipartUploadMethod.call(void 0, {
187
+ var createMultipartUpload = _chunk23VLASYPcjs.createCreateMultipartUploadMethod.call(void 0, {
264
188
  allowedOptions: [
265
189
  "cacheControlMaxAge",
266
190
  "addRandomSuffix",
267
191
  "allowOverwrite",
268
- "contentType"
192
+ "contentType",
193
+ "ifMatch"
269
194
  ]
270
195
  });
271
- var createMultipartUploader = _chunk7TB5OCQAcjs.createCreateMultipartUploaderMethod.call(void 0, {
196
+ var createMultipartUploader = _chunk23VLASYPcjs.createCreateMultipartUploaderMethod.call(void 0, {
272
197
  allowedOptions: [
273
198
  "cacheControlMaxAge",
274
199
  "addRandomSuffix",
275
200
  "allowOverwrite",
276
- "contentType"
201
+ "contentType",
202
+ "ifMatch"
277
203
  ]
278
204
  });
279
- var uploadPart = _chunk7TB5OCQAcjs.createUploadPartMethod.call(void 0, {
205
+ var uploadPart = _chunk23VLASYPcjs.createUploadPartMethod.call(void 0, {
280
206
  allowedOptions: [
281
207
  "cacheControlMaxAge",
282
208
  "addRandomSuffix",
@@ -284,7 +210,7 @@ var uploadPart = _chunk7TB5OCQAcjs.createUploadPartMethod.call(void 0, {
284
210
  "contentType"
285
211
  ]
286
212
  });
287
- var completeMultipartUpload = _chunk7TB5OCQAcjs.createCompleteMultipartUploadMethod.call(void 0, {
213
+ var completeMultipartUpload = _chunk23VLASYPcjs.createCompleteMultipartUploadMethod.call(void 0, {
288
214
  allowedOptions: [
289
215
  "cacheControlMaxAge",
290
216
  "addRandomSuffix",
@@ -318,5 +244,5 @@ var completeMultipartUpload = _chunk7TB5OCQAcjs.createCompleteMultipartUploadMet
318
244
 
319
245
 
320
246
 
321
- exports.BlobAccessError = _chunk7TB5OCQAcjs.BlobAccessError; exports.BlobClientTokenExpiredError = _chunk7TB5OCQAcjs.BlobClientTokenExpiredError; exports.BlobContentTypeNotAllowedError = _chunk7TB5OCQAcjs.BlobContentTypeNotAllowedError; exports.BlobError = _chunk7TB5OCQAcjs.BlobError; exports.BlobFileTooLargeError = _chunk7TB5OCQAcjs.BlobFileTooLargeError; exports.BlobNotFoundError = _chunk7TB5OCQAcjs.BlobNotFoundError; exports.BlobPathnameMismatchError = _chunk7TB5OCQAcjs.BlobPathnameMismatchError; exports.BlobRequestAbortedError = _chunk7TB5OCQAcjs.BlobRequestAbortedError; exports.BlobServiceNotAvailable = _chunk7TB5OCQAcjs.BlobServiceNotAvailable; exports.BlobServiceRateLimited = _chunk7TB5OCQAcjs.BlobServiceRateLimited; exports.BlobStoreNotFoundError = _chunk7TB5OCQAcjs.BlobStoreNotFoundError; exports.BlobStoreSuspendedError = _chunk7TB5OCQAcjs.BlobStoreSuspendedError; exports.BlobUnknownError = _chunk7TB5OCQAcjs.BlobUnknownError; exports.completeMultipartUpload = completeMultipartUpload; exports.copy = copy; exports.createFolder = _chunk7TB5OCQAcjs.createFolder; exports.createMultipartUpload = createMultipartUpload; exports.createMultipartUploader = createMultipartUploader; exports.del = del; exports.get = get; exports.getDownloadUrl = _chunk7TB5OCQAcjs.getDownloadUrl; exports.head = head; exports.list = list; exports.put = put; exports.uploadPart = uploadPart;
247
+ exports.BlobAccessError = _chunk23VLASYPcjs.BlobAccessError; exports.BlobClientTokenExpiredError = _chunk23VLASYPcjs.BlobClientTokenExpiredError; exports.BlobContentTypeNotAllowedError = _chunk23VLASYPcjs.BlobContentTypeNotAllowedError; exports.BlobError = _chunk23VLASYPcjs.BlobError; exports.BlobFileTooLargeError = _chunk23VLASYPcjs.BlobFileTooLargeError; exports.BlobNotFoundError = _chunk23VLASYPcjs.BlobNotFoundError; exports.BlobPathnameMismatchError = _chunk23VLASYPcjs.BlobPathnameMismatchError; exports.BlobPreconditionFailedError = _chunk23VLASYPcjs.BlobPreconditionFailedError; exports.BlobRequestAbortedError = _chunk23VLASYPcjs.BlobRequestAbortedError; exports.BlobServiceNotAvailable = _chunk23VLASYPcjs.BlobServiceNotAvailable; exports.BlobServiceRateLimited = _chunk23VLASYPcjs.BlobServiceRateLimited; exports.BlobStoreNotFoundError = _chunk23VLASYPcjs.BlobStoreNotFoundError; exports.BlobStoreSuspendedError = _chunk23VLASYPcjs.BlobStoreSuspendedError; exports.BlobUnknownError = _chunk23VLASYPcjs.BlobUnknownError; exports.completeMultipartUpload = completeMultipartUpload; exports.copy = copy; exports.createFolder = _chunk23VLASYPcjs.createFolder; exports.createMultipartUpload = createMultipartUpload; exports.createMultipartUploader = createMultipartUploader; exports.del = del; exports.getDownloadUrl = _chunk23VLASYPcjs.getDownloadUrl; exports.head = head; exports.list = list; exports.put = put; exports.uploadPart = uploadPart;
322
248
  //# sourceMappingURL=index.cjs.map