import {
  BlobError,
  createCompleteMultipartUploadMethod,
  createCreateMultipartUploadMethod,
  createCreateMultipartUploaderMethod,
  createFolder,
  createPutMethod,
  createUploadPartMethod,
  getReadWriteBlobTokenFromOptionsOrEnv,
  parseStoreIdFromReadWriteToken,
  presign
} from "./chunk-OYCIHDFF.js";

// src/client.ts
import * as crypto from "crypto";
import { fetch } from "undici";
function createPutExtraChecks(methodName) {
  return function extraChecks(options) {
    if (!options.token.startsWith("vercel_blob_client_")) {
      throw new BlobError(`${methodName} must be called with a client token`);
    }
    if (
      // @ts-expect-error -- Runtime check for DX.
      options.addRandomSuffix !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.allowOverwrite !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.cacheControlMaxAge !== void 0
    ) {
      throw new BlobError(
        `${methodName} doesn't allow \`addRandomSuffix\`, \`cacheControlMaxAge\` or \`allowOverwrite\`. Configure these options at the server side when generating client tokens.`
      );
    }
  };
}
var put = createPutMethod({
  allowedOptions: ["contentType"],
  extraChecks: createPutExtraChecks("client/`put`")
});
var createMultipartUpload = createCreateMultipartUploadMethod({
  allowedOptions: ["contentType"],
  extraChecks: createPutExtraChecks("client/`createMultipartUpload`")
});
var createMultipartUploader = createCreateMultipartUploaderMethod(
  {
    allowedOptions: ["contentType"],
    extraChecks: createPutExtraChecks("client/`createMultipartUpload`")
  }
);
var uploadPart = createUploadPartMethod({
  allowedOptions: ["contentType"],
  extraChecks: createPutExtraChecks("client/`multipartUpload`")
});
var completeMultipartUpload = createCompleteMultipartUploadMethod(
  {
    allowedOptions: ["contentType"],
    extraChecks: createPutExtraChecks("client/`completeMultipartUpload`")
  }
);
var upload = createPutMethod({
  allowedOptions: ["contentType"],
  extraChecks(options) {
    if (options.handleUploadUrl === void 0) {
      throw new BlobError(
        "client/`upload` requires the 'handleUploadUrl' parameter"
      );
    }
    if (
      // @ts-expect-error -- Runtime check for DX.
      options.addRandomSuffix !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.allowOverwrite !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.cacheControlMaxAge !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.ifMatch !== void 0
    ) {
      throw new BlobError(
        "client/`upload` doesn't allow `addRandomSuffix`, `cacheControlMaxAge`, `allowOverwrite` or `ifMatch`. Configure these options at the server side when generating client tokens."
      );
    }
  },
  async getToken(pathname, options) {
    var _a, _b;
    return retrieveClientToken({
      handleUploadUrl: options.handleUploadUrl,
      pathname,
      clientPayload: (_a = options.clientPayload) != null ? _a : null,
      multipart: (_b = options.multipart) != null ? _b : false,
      headers: options.headers
    });
  }
});
var uploadPresigned = createPutMethod({
  allowedOptions: ["contentType"],
  extraChecks(options) {
    if (options.handleUploadUrl === void 0) {
      throw new BlobError(
        "client/`upload` requires the 'handleUploadUrl' parameter"
      );
    }
    if (
      // @ts-expect-error -- Runtime check for DX.
      options.addRandomSuffix !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.allowOverwrite !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.cacheControlMaxAge !== void 0 || // @ts-expect-error -- Runtime check for DX.
      options.ifMatch !== void 0
    ) {
      throw new BlobError(
        "client/`uploadPresigned` doesn't allow `addRandomSuffix`, `cacheControlMaxAge`, `allowOverwrite` or `ifMatch`. Configure these options at the server side when generating presigned URLs."
      );
    }
  },
  async getPresignedUrlPayload(pathname, options) {
    var _a, _b;
    return retrievePresignedUrlPayload({
      pathname,
      handleUploadUrl: options.handleUploadUrl,
      clientPayload: (_a = options.clientPayload) != null ? _a : null,
      multipart: (_b = options.multipart) != null ? _b : false,
      headers: options.headers
    });
  }
});
async function importKey(token) {
  return globalThis.crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(token),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign", "verify"]
  );
}
async function signPayload(payload, token) {
  if (!globalThis.crypto) {
    return crypto.createHmac("sha256", token).update(payload).digest("hex");
  }
  const signature = await globalThis.crypto.subtle.sign(
    "HMAC",
    await importKey(token),
    new TextEncoder().encode(payload)
  );
  return Buffer.from(new Uint8Array(signature)).toString("hex");
}
function publicKeyDerFromPem(pem) {
  var _a;
  const match = pem.trim().match(/-----BEGIN PUBLIC KEY-----([^-]*)-----END PUBLIC KEY-----/s);
  const b64 = (_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.replace(/\s+/g, "");
  if (!b64) {
    return void 0;
  }
  try {
    return Buffer.from(b64, "base64");
  } catch {
    return void 0;
  }
}
async function verifyCallbackSignaturePresigned({
  webhookPublicKey,
  signature,
  body
}) {
  var _a;
  if (typeof signature !== "string" || !/^[0-9a-fA-F]+$/.test(signature) || signature.length !== 128) {
    return false;
  }
  const signatureBuf = Buffer.from(signature, "hex");
  const bodyBuf = Buffer.from(body, "utf8");
  const der = publicKeyDerFromPem(webhookPublicKey);
  if (((_a = globalThis.crypto) == null ? void 0 : _a.subtle) && der) {
    try {
      const derCopy = Uint8Array.from(der);
      const verifyKey = await globalThis.crypto.subtle.importKey(
        "spki",
        derCopy,
        { name: "Ed25519" },
        false,
        ["verify"]
      );
      const sigBytes = new Uint8Array(64);
      sigBytes.set(signatureBuf.subarray(0, 64), 0);
      const ok = await globalThis.crypto.subtle.verify(
        "Ed25519",
        verifyKey,
        sigBytes,
        new TextEncoder().encode(body)
      );
      return ok;
    } catch {
      return false;
    }
  }
  if (typeof crypto.createPublicKey === "function") {
    try {
      const key = crypto.createPublicKey(webhookPublicKey.trim());
      return crypto.verify(null, bodyBuf, key, signatureBuf);
    } catch {
      return false;
    }
  }
  return false;
}
async function verifyCallbackSignature({
  token,
  signature,
  body
}) {
  const secret = token;
  if (!globalThis.crypto) {
    const digest = crypto.createHmac("sha256", secret).update(body).digest("hex");
    const digestBuffer = Buffer.from(digest);
    const signatureBuffer = Buffer.from(signature);
    return digestBuffer.length === signatureBuffer.length && crypto.timingSafeEqual(digestBuffer, signatureBuffer);
  }
  const verified = await globalThis.crypto.subtle.verify(
    "HMAC",
    await importKey(token),
    // @ts-expect-error Buffer is compatible with BufferSource at runtime
    hexToArrayByte(signature),
    new TextEncoder().encode(body)
  );
  return verified;
}
function hexToArrayByte(input) {
  if (input.length % 2 !== 0) {
    throw new RangeError("Expected string to be an even number of characters");
  }
  const view = new Uint8Array(input.length / 2);
  for (let i = 0; i < input.length; i += 2) {
    view[i / 2] = Number.parseInt(input.substring(i, i + 2), 16);
  }
  return Buffer.from(view);
}
function getPayloadFromClientToken(clientToken) {
  const [, , , , encodedToken] = clientToken.split("_");
  const encodedPayload = Buffer.from(encodedToken != null ? encodedToken : "", "base64").toString().split(".")[1];
  const decodedPayload = Buffer.from(encodedPayload != null ? encodedPayload : "", "base64").toString();
  return JSON.parse(decodedPayload);
}
var EventTypes = {
  generateClientToken: "blob.generate-client-token",
  generatePresignedUrl: "blob.generate-presigned-url",
  uploadCompleted: "blob.upload-completed"
};
async function handleUpload({
  token,
  request,
  body,
  onBeforeGenerateToken,
  onUploadCompleted
}) {
  var _a, _b, _c, _d;
  const resolvedToken = getReadWriteBlobTokenFromOptionsOrEnv({ token });
  const type = body.type;
  switch (type) {
    case "blob.generate-client-token": {
      const { pathname, clientPayload, multipart } = body.payload;
      const payload = await onBeforeGenerateToken(
        pathname,
        clientPayload,
        multipart
      );
      const tokenPayload = (_a = payload.tokenPayload) != null ? _a : clientPayload;
      const { callbackUrl: providedCallbackUrl, ...tokenOptions } = payload;
      let callbackUrl = providedCallbackUrl;
      if (onUploadCompleted && !callbackUrl) {
        callbackUrl = getCallbackUrl(request);
      }
      if (!onUploadCompleted && callbackUrl) {
        console.warn(
          "callbackUrl was provided but onUploadCompleted is not defined. The callback will not be handled."
        );
      }
      const oneHourInSeconds = 60 * 60;
      const now = /* @__PURE__ */ new Date();
      const validUntil = (_b = payload.validUntil) != null ? _b : now.setSeconds(now.getSeconds() + oneHourInSeconds);
      return {
        type,
        clientToken: await generateClientTokenFromReadWriteToken({
          ...tokenOptions,
          token: resolvedToken,
          pathname,
          onUploadCompleted: callbackUrl ? {
            callbackUrl,
            tokenPayload
          } : void 0,
          validUntil
        })
      };
    }
    case "blob.upload-completed": {
      const signatureHeader = "x-vercel-signature";
      const signature = "credentials" in request ? (_c = request.headers.get(signatureHeader)) != null ? _c : "" : (_d = request.headers[signatureHeader]) != null ? _d : "";
      if (!signature) {
        throw new BlobError("Missing callback signature");
      }
      const isVerified = await verifyCallbackSignature({
        token: resolvedToken,
        signature,
        body: JSON.stringify(body)
      });
      if (!isVerified) {
        throw new BlobError("Invalid callback signature");
      }
      if (onUploadCompleted) {
        await onUploadCompleted(body.payload);
      }
      return { type, response: "ok" };
    }
    default:
      throw new BlobError("Invalid event type");
  }
}
async function handleUploadPresigned({
  body,
  request,
  webhookPublicKey,
  getSignedToken,
  onUploadCompleted
}) {
  var _a, _b, _c, _d;
  const resolvedWebhookPublicKey = webhookPublicKey != null ? webhookPublicKey : process.env.BLOB_WEBHOOK_PUBLIC_KEY;
  if (!resolvedWebhookPublicKey) {
    throw new BlobError("Missing webhook public key");
  }
  const type = body.type;
  switch (type) {
    case "blob.generate-presigned-url": {
      const { pathname, clientPayload, multipart } = body.payload;
      const { token, urlOptions = {} } = await getSignedToken(
        pathname,
        clientPayload,
        multipart
      );
      const tokenPayload = (_b = (_a = urlOptions == null ? void 0 : urlOptions.tokenPayload) != null ? _a : clientPayload) != null ? _b : void 0;
      const { callbackUrl: providedCallbackUrl } = urlOptions;
      let callbackUrl = providedCallbackUrl;
      if (onUploadCompleted && !callbackUrl) {
        callbackUrl = getCallbackUrl(request);
      }
      if (!onUploadCompleted && callbackUrl) {
        console.warn(
          "callbackUrl was provided but onUploadCompleted is not defined. The callback will not be handled."
        );
      }
      const urlOptionsWithCallback = {
        ...urlOptions,
        onUploadCompleted: callbackUrl ? {
          callbackUrl,
          tokenPayload
        } : void 0
      };
      const presignedUrlPayload = await presign(token, {
        ...urlOptionsWithCallback,
        operation: "put",
        pathname
      });
      return { type, presignedUrlPayload };
    }
    case "blob.upload-completed": {
      const signatureHeader = "x-vercel-signature";
      const signature = "credentials" in request ? (_c = request.headers.get(signatureHeader)) != null ? _c : "" : (_d = request.headers[signatureHeader]) != null ? _d : "";
      if (!signature) {
        throw new BlobError("Missing callback signature");
      }
      const isVerified = await verifyCallbackSignaturePresigned({
        webhookPublicKey: resolvedWebhookPublicKey,
        signature,
        body: JSON.stringify(body)
      });
      if (!isVerified) {
        throw new BlobError("Invalid callback signature");
      }
      if (onUploadCompleted) {
        await onUploadCompleted(body.payload);
      }
      return { type, response: "ok" };
    }
    default:
      throw new BlobError("Invalid event type");
  }
}
async function retrieveClientToken(options) {
  const { handleUploadUrl, pathname } = options;
  const url = isAbsoluteUrl(handleUploadUrl) ? handleUploadUrl : toAbsoluteUrl(handleUploadUrl);
  const event = {
    type: EventTypes.generateClientToken,
    payload: {
      pathname,
      clientPayload: options.clientPayload,
      multipart: options.multipart
    }
  };
  const res = await fetch(url, {
    method: "POST",
    body: JSON.stringify(event),
    headers: {
      "content-type": "application/json",
      ...options.headers
    },
    signal: options.abortSignal
  });
  if (!res.ok) {
    throw new BlobError("Failed to  retrieve the client token");
  }
  try {
    const { clientToken } = await res.json();
    return clientToken;
  } catch {
    throw new BlobError("Failed to retrieve the client token");
  }
}
async function retrievePresignedUrlPayload(options) {
  const { handleUploadUrl, pathname } = options;
  const url = isAbsoluteUrl(handleUploadUrl) ? handleUploadUrl : toAbsoluteUrl(handleUploadUrl);
  const event = {
    type: EventTypes.generatePresignedUrl,
    payload: {
      pathname,
      clientPayload: options.clientPayload,
      multipart: options.multipart
    }
  };
  const res = await fetch(url, {
    method: "POST",
    body: JSON.stringify(event),
    headers: {
      "content-type": "application/json",
      ...options.headers
    },
    signal: options.abortSignal
  });
  if (!res.ok) {
    throw new BlobError("Failed to retrieve the presigned URL");
  }
  try {
    const { presignedUrlPayload } = await res.json();
    if (presignedUrlPayload) {
      return presignedUrlPayload;
    }
    throw new BlobError("Missing presignedUrlPayload");
  } catch (error) {
    if (error instanceof BlobError) {
      throw error;
    }
    throw new BlobError("Failed to retrieve the presigned URL");
  }
}
function toAbsoluteUrl(url) {
  return new URL(url, location.href).href;
}
function isAbsoluteUrl(url) {
  try {
    return Boolean(new URL(url));
  } catch {
    return false;
  }
}
async function generateClientTokenFromReadWriteToken({
  token,
  ...argsWithoutToken
}) {
  var _a;
  if (typeof window !== "undefined") {
    throw new BlobError(
      '"generateClientTokenFromReadWriteToken" must be called from a server environment'
    );
  }
  if (argsWithoutToken.ifMatch && argsWithoutToken.allowOverwrite === false) {
    throw new BlobError(
      "ifMatch and allowOverwrite: false are contradictory. ifMatch is used for conditional overwrites, which requires allowOverwrite to be true."
    );
  }
  if (argsWithoutToken.ifMatch && argsWithoutToken.allowOverwrite === void 0) {
    argsWithoutToken.allowOverwrite = true;
  }
  const timestamp = /* @__PURE__ */ new Date();
  timestamp.setSeconds(timestamp.getSeconds() + 30);
  const readWriteToken = getReadWriteBlobTokenFromOptionsOrEnv({ token });
  const storeId = parseStoreIdFromReadWriteToken(readWriteToken) || null;
  if (!storeId) {
    throw new BlobError(
      token ? "Invalid `token` parameter" : "Invalid `BLOB_READ_WRITE_TOKEN`"
    );
  }
  const payload = Buffer.from(
    JSON.stringify({
      ...argsWithoutToken,
      validUntil: (_a = argsWithoutToken.validUntil) != null ? _a : timestamp.getTime()
    })
  ).toString("base64");
  const securedKey = await signPayload(payload, readWriteToken);
  if (!securedKey) {
    throw new BlobError("Unable to sign client token");
  }
  return `vercel_blob_client_${storeId}_${Buffer.from(
    `${securedKey}.${payload}`
  ).toString("base64")}`;
}
function getCallbackUrl(request) {
  const reqPath = getPathFromRequestUrl(request.url);
  if (!reqPath) {
    console.warn(
      "onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable."
    );
    return void 0;
  }
  if (process.env.VERCEL_BLOB_CALLBACK_URL) {
    return `${process.env.VERCEL_BLOB_CALLBACK_URL}${reqPath}`;
  }
  if (process.env.VERCEL !== "1") {
    console.warn(
      "onUploadCompleted provided but no callbackUrl could be determined. Please provide a callbackUrl in onBeforeGenerateToken or set the VERCEL_BLOB_CALLBACK_URL environment variable."
    );
    return void 0;
  }
  if (process.env.VERCEL_ENV === "preview") {
    if (process.env.VERCEL_BRANCH_URL) {
      return `https://${process.env.VERCEL_BRANCH_URL}${reqPath}`;
    }
    if (process.env.VERCEL_URL) {
      return `https://${process.env.VERCEL_URL}${reqPath}`;
    }
  }
  if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
    return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}${reqPath}`;
  }
  return void 0;
}
function getPathFromRequestUrl(url) {
  try {
    const parsedUrl = new URL(url, "https://dummy.com");
    return parsedUrl.pathname + parsedUrl.search;
  } catch {
    return null;
  }
}
export {
  completeMultipartUpload,
  createFolder,
  createMultipartUpload,
  createMultipartUploader,
  generateClientTokenFromReadWriteToken,
  getPayloadFromClientToken,
  handleUpload,
  handleUploadPresigned,
  put,
  upload,
  uploadPart,
  uploadPresigned
};
//# sourceMappingURL=client.js.map