{"version":3,"file":"index.cjs","names":["CurlCode","CurlError","Easy","Easy","Curl"],"sources":["../src/utils.ts","../src/request.ts","../src/index.ts"],"sourcesContent":["import type { IncomingHttpHeaders } from \"http\";\nimport { CurlCode, Easy } from \"node-libcurl\";\nimport { CurlError } from \"./errors\";\nimport type { HttpVerb, Options } from \"./types\";\n\ninterface RequestInputs {\n  method: HttpVerb;\n  url: string;\n  options: Options;\n}\n\n/**\n * Handles query string parameters in a URL by modifying or appending them\n * based on the provided object.\n *\n * Arrays of primitives, e.g. { quizIds: [1,2,3] }, will be of the form:\n *   https://google.com.au/?quizIds%5B0%5D=0&quizIds%5B1%5D=1&quizIds%5B2%5D=2\n *   i.e. https://www.google.com.au/?quizIds[0]=0&quizIds[1]=1&quizIds[2]=2\n *\n * @param {string} url - The URL to handle query string parameters for.\n * @param {Object.<string, any>} qs - query string parameters to modify or append.\n * @returns {string} The modified URL with the updated query string parameters.\n */\nexport const handleQs = (\n  url: string,\n  qs: { [key: string]: unknown },\n): string => {\n  const urlObj = new URL(url);\n  for (const [key, value] of Object.entries(qs)) {\n    if (Array.isArray(value)) {\n      urlObj.searchParams.delete(key);\n      value.forEach((item, i) => {\n        /* v8 ignore next */\n        urlObj.searchParams.append(`${key}[${i}]`, String(item));\n      });\n    } else if (value === null) {\n      urlObj.searchParams.set(key, \"\");\n    } else if (value !== undefined) {\n      urlObj.searchParams.set(key, String(value));\n    }\n  }\n  urlObj.search = urlObj.searchParams.toString();\n  return urlObj.href;\n};\n\n/**\n * Parses incoming HTTP headers to an array of formatted strings.\n *\n * @param {IncomingHttpHeaders} headers - The header object to parse.\n * @returns {string[]} An array of formatted header strings.\n */\nexport const parseIncomingHeaders = (\n  headers?: IncomingHttpHeaders,\n): string[] => {\n  return headers\n    ? Object.entries(headers)\n        .filter(([_, value]) => value !== undefined)\n        .map(([key, value]) => (value === \"\" ? `${key};` : `${key}: ${value}`))\n    : [];\n};\n\n/**\n * Parses an array of header lines as IncomingHttpHeaders.\n *\n * @param {string[]} headerLines - An array of header lines to parse.\n * @returns {IncomingHttpHeaders} An object containing parsed headers.\n */\nexport const parseReturnedHeaders = (\n  headerLines: string[],\n): IncomingHttpHeaders => {\n  return headerLines.reduce((acc, header) => {\n    const [name, ...values] = header.split(\":\");\n    if (name && values.length > 0) {\n      acc[name.trim().toLowerCase()] = values.join(\":\").trim();\n    }\n    return acc;\n  }, {} as IncomingHttpHeaders);\n};\n\n/**\n * Checks CURL code and throws a `CurlError` if it indicates failure.\n *\n * @param {CurlCode} code - The CURL error code to check.\n * @param {RequestInputs} requestInputs - input parameters for the CURL request.\n * @throws {CurlError} Throws a `CurlError` if the CURL code indicates failure.\n */\nexport const checkValidCurlCode = (\n  code: CurlCode,\n  requestInputs: RequestInputs,\n) => {\n  if (code !== CurlCode.CURLE_OK) {\n    throw new CurlError(\n      code,\n      `\n      Curl request failed with code ${code}:\n        - ${Easy.strError(code)}\n\n      You can also look up the Libcurl Error (code ${code}) here:\n        - https://curl.se/libcurl/c/libcurl-errors.html\n\n      DEBUG: {\n        method: \"${requestInputs.method}\",\n        url: \"${requestInputs.url}\",\n        options: ${JSON.stringify(requestInputs.options)}\n      }\n    `,\n    );\n  }\n};\n\n/**\n * Checks the status code and body of an HTTP response\n *\n * @param {number} statusCode - The status code of the HTTP response.\n * @param {Buffer} body - The body of the HTTP response.\n * @throws {Error} if the status code is >= 300.\n */\nexport const checkValidStatusCode = (statusCode: number, body: Buffer) => {\n  if (statusCode >= 300) {\n    throw new Error(`\nServer responded with status code\n  ${statusCode}\n\nBody:\n  ${body.toString()}\n\nUse 'res.body' instead of 'res.getBody()' to not have any errors thrown.\n\nThe status code (in this case, ${statusCode}) can be checked manually\nwith res.statusCode.\n    `);\n  }\n};\n","import { Curl, Easy, type HttpPostField } from \"node-libcurl\";\nimport type {\n  BufferEncoding,\n  CustomJsonType,\n  GetJSON,\n  HttpVerb,\n  Options,\n  Response,\n} from \"./types\";\nimport {\n  checkValidCurlCode,\n  checkValidStatusCode,\n  handleQs,\n  parseIncomingHeaders,\n  parseReturnedHeaders,\n} from \"./utils\";\n\n/**\n * Create a libcurl Easy object with default configurations\n *\n * @param {HttpVerb} method - The HTTP method (e.g., 'GET', 'POST', 'PUT')\n * @param {Options} options - configuration options for the request.\n * @returns {Easy} an initialized libcurl Easy object with default options\n */\nconst createCurlObjectWithDefaults = (\n  method: HttpVerb,\n  options: Options,\n): Easy => {\n  const curl = new Easy();\n  curl.setOpt(Curl.option.CUSTOMREQUEST, method);\n  curl.setOpt(Curl.option.TIMEOUT_MS, options.timeout ?? 0);\n  curl.setOpt(\n    Curl.option.FOLLOWLOCATION,\n    options.followRedirects === undefined || options.followRedirects,\n  );\n  curl.setOpt(Curl.option.MAXREDIRS, options.maxRedirects ?? -1);\n  curl.setOpt(Curl.option.SSL_VERIFYPEER, !options.insecure);\n  curl.setOpt(Curl.option.NOBODY, method === \"HEAD\");\n  return curl;\n};\n\n/**\n * Handles query string parameters in a URL, modifies the URL if necessary,\n * and sets it as the CURLOPT_URL option in the given cURL Easy object.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {string} url - The URL to handle query string parameters for\n * @param {Object.<string, any>} qs - query string parameters for the request\n */\nconst handleQueryString = (\n  curl: Easy,\n  url: string,\n  qs?: { [key: string]: unknown },\n): void => {\n  url = qs && Object.keys(qs).length ? handleQs(url, qs) : url;\n  curl.setOpt(Curl.option.URL, url);\n};\n\n/**\n * Sets up a callback function for the cURL Easy object to handle returned\n * headers and populate the input array with header lines.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {string[]} returnedHeaderArray - array for returned header lines\n */\nconst handleOutgoingHeaders = (curl: Easy, returnedHeaderArray: string[]) => {\n  curl.setOpt(Curl.option.HEADERFUNCTION, (headerLine) => {\n    returnedHeaderArray.push(headerLine.toString(\"utf-8\").trim());\n    return headerLine.length;\n  });\n};\n\n/**\n * Sets the JSON payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {any} json - The JSON body to be sent\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst setJSONPayload = (\n  curl: Easy,\n  json: CustomJsonType,\n  httpHeaders: string[],\n): void => {\n  httpHeaders.push(\"Content-Type: application/json\");\n  const payload = JSON.stringify(json);\n  httpHeaders.push(`Content-Length: ${Buffer.byteLength(payload, \"utf-8\")}`);\n  curl.setOpt(Curl.option.POSTFIELDS, payload);\n};\n\n/**\n * Sets the buffer payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {string | Buffer} body - The body to be sent in the request.\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst setBodyPayload = (\n  curl: Easy,\n  body: string | Buffer,\n  httpHeaders: string[],\n): void => {\n  if (Buffer.isBuffer(body)) {\n    let position = 0;\n    curl.setOpt(Curl.option.POST, true);\n    curl.setOpt(Curl.option.POSTFIELDSIZE, -1);\n    curl.setOpt(\n      Curl.option.READFUNCTION,\n      (buffer: Buffer, size: number, nmemb: number): number => {\n        const amountToRead = size * nmemb;\n        if (position === body.length) {\n          return 0;\n        }\n        const totalWritten = body.copy(\n          buffer,\n          0,\n          position,\n          Math.min(amountToRead, body.length),\n        );\n        position += totalWritten;\n        return totalWritten;\n      },\n    );\n  } else {\n    curl.setOpt(Curl.option.POSTFIELDS, body);\n    httpHeaders.push(`Content-Length: ${Buffer.byteLength(body, \"utf-8\")}`);\n  }\n};\n\n/**\n * Sets the buffer payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {Array<HttpPostField>} formData - list of files/contents to be sent\n */\nconst setFormPayload = (curl: Easy, formData: HttpPostField[]) => {\n  curl.setOpt(Curl.option.HTTPPOST, formData);\n};\n\n/**\n * Prepares the request body and headers for a cURL request based on provided\n * options. Also sets up a callback function for the cURL Easy object to handle\n * returned body and populates the input buffet.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {Options} options - Options for configuring the request\n * @param {{ body: Buffer }} buffer - wrapped buffer for the returned body\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst handleBodyAndRequestHeaders = (\n  curl: Easy,\n  options: Options,\n  buffer: { body: Buffer },\n  httpHeaders: string[],\n): void => {\n  if (options.json) {\n    setJSONPayload(curl, options.json, httpHeaders);\n  } else if (options.body) {\n    setBodyPayload(curl, options.body, httpHeaders);\n  } else if (options.formData) {\n    setFormPayload(curl, options.formData);\n  } else {\n    httpHeaders.push(\"Content-Length: 0\");\n  }\n  curl.setOpt(Curl.option.WRITEFUNCTION, (buff, nmemb, size) => {\n    buffer.body = Buffer.concat([buffer.body, buff.subarray(0, nmemb * size)]);\n    return nmemb * size;\n  });\n\n  curl.setOpt(Curl.option.HTTPHEADER, httpHeaders);\n};\n\n/**\n * Performs an HTTP request using cURL with the specified parameters.\n *\n * @param {HttpVerb} method - The HTTP method for the request (e.g., 'GET', 'POST')\n * @param {string} url - The URL to make the request to\n * @param {Options} [options={}] - An object to configure the request\n * @returns {Response} - HTTP response consisting of status code, headers, and body\n */\nconst request = (\n  method: HttpVerb,\n  url: string,\n  options: Options = {},\n): Response => {\n  const curl = createCurlObjectWithDefaults(method, options);\n  handleQueryString(curl, url, options.qs);\n\n  // Body/JSON and Headers (incoming)\n  const bufferWrap = { body: Buffer.alloc(0) };\n  handleBodyAndRequestHeaders(\n    curl,\n    options,\n    bufferWrap,\n    parseIncomingHeaders(options.headers),\n  );\n\n  // Headers (outgoing)\n  const returnedHeaderArray: string[] = [];\n  handleOutgoingHeaders(curl, returnedHeaderArray);\n\n  if (options.setEasyOptions) {\n    options.setEasyOptions(curl, Curl.option);\n  }\n\n  // Execute request\n  const code = curl.perform();\n  checkValidCurlCode(code, { method, url, options });\n\n  // Creating return object\n  const statusCode = curl.getInfo(\"RESPONSE_CODE\").data as number;\n  const headers = parseReturnedHeaders(returnedHeaderArray);\n  const { body } = bufferWrap;\n\n  /**\n   * Get the body of a response with an optional encoding.\n   *\n   * @throws {Error} if the status code is >= 300\n   * @returns {Buffer | string} buffer body by default, string body with encoding\n   */\n\n  function getBody<Encoding extends BufferEncoding>(encoding: Encoding): string;\n  function getBody(encoding?: undefined): Buffer;\n  function getBody(encoding?: BufferEncoding): string | Buffer {\n    checkValidStatusCode(statusCode, body);\n    return encoding ? body.toString(encoding) : body;\n  }\n\n  /**\n   * Get the JSON-parsed body of a response.\n   *\n   * @throws {Error} if the body is into a valid JSON\n   * @returns {any} parsed JSON body\n   */\n  const getJSON: GetJSON = (encoding?) => {\n    try {\n      return JSON.parse(body.toString(encoding));\n    } catch (err) {\n      /* v8 ignore next */\n      const errorMessage = err instanceof Error ? err.message : String(err);\n      throw new Error(\n        `\nThe server body response for\n  - ${method}\n  - ${url}\ncannot be parsed as JSON.\n\nBody:\n  ${body.toString(encoding)}\n\nJSON-Parsing Error Message:\n  ${errorMessage}\n      `,\n        { cause: err },\n      );\n    }\n  };\n\n  url = curl.getInfo(\"EFFECTIVE_URL\").data as string;\n\n  curl.close();\n  return { statusCode, headers, url, body, getBody, getJSON };\n};\n\nexport default request;\n","import request from \"./request\";\n\nexport type { HttpPostField } from \"node-libcurl\";\nexport type {\n  BufferEncoding,\n  HttpVerb,\n  Options,\n  Response,\n  SetEasyOptionCallback,\n} from \"./types\";\n\nexport default request;\n"],"mappings":"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"}