{"version":3,"sources":["/home/runner/work/storage/storage/packages/blob/dist/client.cjs","../src/client.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACA;ACVA,+EAAwB;AAIxB,gCAAsB;AAiEtB,SAAS,oBAAA,CAEP,UAAA,EAAoB;AACpB,EAAA,OAAO,SAAS,WAAA,CAAY,OAAA,EAAmB;AAC7C,IAAA,GAAA,CAAI,CAAC,OAAA,CAAQ,KAAA,CAAM,UAAA,CAAW,qBAAqB,CAAA,EAAG;AACpD,MAAA,MAAM,IAAI,gCAAA,CAAU,CAAA,EAAA;AACtB,IAAA;AAEA,IAAA;AAAA;AAEU,MAAA;AAEA,MAAA;AAEA,MAAA;AACR,IAAA;AACU,MAAA;AACK,QAAA;AACf,MAAA;AACF,IAAA;AACF,EAAA;AACF;AAsB4D;AACzC,EAAA;AACJ,EAAA;AACd;AAqBY;AAEQ,EAAA;AACJ,EAAA;AACd;AAkBU;AAET,EAAA;AACmB,IAAA;AACJ,IAAA;AACf,EAAA;AACF;AA4BA;AACmB,EAAA;AACJ,EAAA;AACd;AAyBU;AAET,EAAA;AACmB,IAAA;AACJ,IAAA;AACf,EAAA;AACF;AA+CoB;AACH,EAAA;AACI,EAAA;AACP,IAAA;AACA,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AAEA,IAAA;AAAA;AAEU,MAAA;AAEA,MAAA;AAEA,MAAA;AAEY,MAAA;AACpB,IAAA;AACU,MAAA;AACR,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACyB,EAAA;AA1S3B,IAAA;AA2SW,IAAA;AACY,MAAA;AACjB,MAAA;AACe,MAAA;AACJ,MAAA;AACM,MAAA;AAClB,IAAA;AACH,EAAA;AACD;AAKwB;AACE,EAAA;AACvB,IAAA;AACkB,IAAA;AACI,IAAA;AACtB,IAAA;AACiB,IAAA;AACnB,EAAA;AACF;AAME;AAGwB,EAAA;AACR,IAAA;AAChB,EAAA;AAEwB,EAAA;AACtB,IAAA;AACqB,IAAA;AACH,IAAA;AACpB,EAAA;AACuB,EAAA;AACzB;AAKe;AACb,EAAA;AACA,EAAA;AACA,EAAA;AAKmB;AAEJ,EAAA;AAGS,EAAA;AAGnB,IAAA;AAGkB,IAAA;AACf,IAAA;AAGS,IAAA;AAGjB,EAAA;AAEuB,EAAA;AACrB,IAAA;AACqB,IAAA;AAAA;AAEN,IAAA;AACG,IAAA;AACpB,EAAA;AACO,EAAA;AACT;AAKwB;AACG,EAAA;AACF,IAAA;AACvB,EAAA;AACiB,EAAA;AAEG,EAAA;AACG,IAAA;AACvB,EAAA;AAEuB,EAAA;AACzB;AAqBgB;AAGC,EAAA;AACQ,EAAA;AAGA,EAAA;AACL,EAAA;AACpB;AAKmB;AACI,EAAA;AACJ,EAAA;AACnB;AA0IsB;AACpB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AAIA;AArkBF,EAAA;AAskBwB,EAAA;AAEJ,EAAA;AACJ,EAAA;AACP,IAAA;AACe,MAAA;AACF,MAAA;AACd,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACM,MAAA;AACe,MAAA;AACH,MAAA;AAGd,MAAA;AACY,QAAA;AAChB,MAAA;AAGK,MAAA;AACK,QAAA;AACN,UAAA;AACF,QAAA;AACF,MAAA;AAGM,MAAA;AACM,MAAA;AAEV,MAAA;AAGK,MAAA;AACL,QAAA;AACmB,QAAA;AACd,UAAA;AACI,UAAA;AACP,UAAA;AACA,UAAA;AAEM,YAAA;AACA,YAAA;AAEF,UAAA;AACJ,UAAA;AACD,QAAA;AACH,MAAA;AACF,IAAA;AACK,IAAA;AACG,MAAA;AAEJ,MAAA;AAKc,MAAA;AACJ,QAAA;AACZ,MAAA;AAEmB,MAAA;AACV,QAAA;AACP,QAAA;AACW,QAAA;AACZ,MAAA;AAEgB,MAAA;AACL,QAAA;AACZ,MAAA;AAEI,MAAA;AACI,QAAA;AACR,MAAA;AACe,MAAA;AACjB,IAAA;AACA,IAAA;AACsB,MAAA;AACxB,EAAA;AACF;AAKe;AAQY,EAAA;AACb,EAAA;AAI4B,EAAA;AACrB,IAAA;AACR,IAAA;AACP,MAAA;AACe,MAAA;AACI,MAAA;AACrB,IAAA;AACF,EAAA;AAEwB,EAAA;AACd,IAAA;AACa,IAAA;AACZ,IAAA;AACS,MAAA;AACL,MAAA;AACb,IAAA;AACgB,IAAA;AACjB,EAAA;AAEY,EAAA;AACS,IAAA;AACtB,EAAA;AAEI,EAAA;AACkB,IAAA;AACb,IAAA;AACD,EAAA;AACc,IAAA;AACtB,EAAA;AACF;AAKuB;AAED,EAAA;AACtB;AAKuB;AACjB,EAAA;AACqB,IAAA;AACjB,EAAA;AACC,IAAA;AACT,EAAA;AACF;AAmBsB;AACpB,EAAA;AACG,EAAA;AAC2C;AA9uBhD,EAAA;AA+uBwB,EAAA;AACV,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAIqB,EAAA;AACT,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAMmB,EAAA;AAGA,IAAA;AACnB,EAAA;AAEkB,EAAA;AACG,EAAA;AACE,EAAA;AAEA,EAAA;AAET,EAAA;AACF,IAAA;AACA,MAAA;AACV,IAAA;AACF,EAAA;AAEuB,EAAA;AACN,IAAA;AACV,MAAA;AACS,MAAA;AACb,IAAA;AACgB,EAAA;AAEM,EAAA;AAER,EAAA;AACK,IAAA;AACtB,EAAA;AACO,EAAA;AACY,IAAA;AACC,EAAA;AACtB;AAkEwB;AACN,EAAA;AAEF,EAAA;AACJ,IAAA;AACN,MAAA;AACF,IAAA;AACO,IAAA;AACT,EAAA;AAGgB,EAAA;AACQ,IAAA;AACxB,EAAA;AAGgB,EAAA;AACN,IAAA;AACN,MAAA;AACF,IAAA;AACO,IAAA;AACT,EAAA;AAIgB,EAAA;AACE,IAAA;AACI,MAAA;AACpB,IAAA;AACgB,IAAA;AACI,MAAA;AACpB,IAAA;AACF,EAAA;AAGc,EAAA;AAGM,IAAA;AACpB,EAAA;AAEO,EAAA;AACT;AAMS;AACH,EAAA;AAEoB,IAAA;AACL,IAAA;AACX,EAAA;AACC,IAAA;AACT,EAAA;AACF;AD7kB2B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/storage/storage/packages/blob/dist/client.cjs","sourcesContent":[null,"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      // @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  // ifMatch implies allowOverwrite — updating a blob by ETag inherently requires\n  // overwriting. Throw if the user explicitly contradicts this.\n  if (argsWithoutToken.ifMatch && argsWithoutToken.allowOverwrite === false) {\n    throw new BlobError(\n      'ifMatch and allowOverwrite: false are contradictory. ifMatch is used for conditional overwrites, which requires allowOverwrite to be true.',\n    );\n  }\n\n  // Implicitly enable allowOverwrite when ifMatch is set and allowOverwrite\n  // was not explicitly provided, to prevent the server from sending\n  // conflicting If-Match + If-None-Match headers to S3.\n  if (\n    argsWithoutToken.ifMatch &&\n    argsWithoutToken.allowOverwrite === undefined\n  ) {\n    argsWithoutToken.allowOverwrite = true;\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"]}