kodekeep/reqwest

View on GitHub
src/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
D
67%
import got from "got";
import http from "http";
import https from "https";
import { CookieJar } from "tough-cookie";
import { URLSearchParams } from "url";

import { ensureTrailingSlash } from "./helpers";
import { Response } from "./response";

type RequestOptions = Record<string, any>;

export class Reqwest {
    /**
     * The request body format.
     */
    #bodyFormat!: string;

    /**
     * The request options.
     */
    #options: RequestOptions = {};

    /**
     * Create a new HTTP Client instance.
     */
    public constructor() {
        this.asJson();
    }

    /**
     * Create a new HTTP Client instance and set the base URL for the request.
     */
    public static new(url: string): Reqwest {
        return new Reqwest().baseUrl(url);
    }

    /**
     * Set the base URL for the request.
     */
    public baseUrl(url: string): Reqwest {
        this.#options.prefixUrl = ensureTrailingSlash(url);

        return this;
    }

    /**
     * Indicate the request contains JSON.
     */
    public asJson(): Reqwest {
        return this.bodyFormat("json").contentType("application/json");
    }

    /**
     * Indicate the request contains form parameters.
     */
    public asForm(): Reqwest {
        return this.bodyFormat("form_params").contentType(
            "application/x-www-form-urlencoded"
        );
    }

    /**
     * Indicate the request is a multi-part form request.
     */
    public asMultipart(): Reqwest {
        return this.bodyFormat("multipart");
    }

    /**
     * Specify the body format of the request.
     */
    public bodyFormat(format: string): Reqwest {
        this.#bodyFormat = format;

        return this;
    }

    /**
     * Specify the request's content type.
     */
    public contentType(contentType: string): Reqwest {
        return this.withHeaders({ "Content-Type": contentType });
    }

    /**
     * Indicate that JSON should be returned by the server.
     */
    public acceptJson(): Reqwest {
        return this.accept("application/json");
    }

    /**
     * Indicate the type of content that should be returned by the server.
     */
    public accept(contentType: string): Reqwest {
        return this.withHeaders({ Accept: contentType });
    }

    /**
     * Add the given headers to the request.
     */
    public withHeaders(headers: object): Reqwest {
        this.#options.headers = { ...this.#options.headers, ...headers };

        return this;
    }

    /**
     * Specify the basic authentication username and password for the request.
     */
    public withBasicAuth(username: string, password: string): Reqwest {
        return this.withHeaders({
            Authorization: `Basic ${Buffer.from(username + ":" + password).toString(
                "base64"
            )}`,
        });
    }

    /**
     * Specify the digest authentication username and password for the request.
     */
    public withDigestAuth(username: string, password: string): Reqwest {
        throw new Error(
            `The [withDigestAuth("${username}", "${password}")] method is not yet supported.`
        );
    }

    /**
     * Specify an authorization token for the request.
     */
    public withToken(token: string): Reqwest {
        return this.withHeaders({ Authorization: `Bearer ${token}` });
    }

    /**
     * Specify the cookies that should be included with the request.
     */
    public withCookies(cookies: object, domain: string): Reqwest {
        const cookieJar: CookieJar = new CookieJar();

        for (const [key, value] of Object.entries(cookies)) {
            cookieJar.setCookie(`${key}=${value}`, domain);
        }

        this.#options.cookieJar = cookieJar;

        return this;
    }

    /**
     * Indicate that redirects should not be followed.
     */
    public withoutRedirecting(): Reqwest {
        this.#options.followRedirects = false;

        return this;
    }

    /**
     * Indicate that TLS certificates should not be verified.
     */
    public withoutVerifying(): Reqwest {
        this.#options.verify = false;

        return this;
    }

    /**
     * Specify the host that should be used as SOCKS proxy.
     */
    public withAgent(agent: { http: http.Agent; https: https.Agent }): Reqwest {
        this.#options.agent = agent;

        return this;
    }

    /**
     * Specify the timeout (in seconds) for the request.
     */
    public timeout(seconds: number): Reqwest {
        this.#options.timeout = seconds;

        return this;
    }

    /**
     * Specify the number of times the request should be attempted.
     */
    public retry(times: number, sleep?: number): Reqwest {
        this.#options.retry = {
            limit: times,
            maxRetryAfter: sleep,
        };

        return this;
    }

    /**
     * Merge new options into the client.
     */
    public withOptions(options: object): Reqwest {
        this.#options = { ...this.#options, ...options };

        return this;
    }

    /**
     * Issue a GET request to the given URL.
     */
    public async get(url: string, query?: object): Promise<Response> {
        return this.send("GET", url, { query });
    }

    /**
     * Issue a HEAD request to the given URL.
     */
    public async head(url: string, query?: object): Promise<Response> {
        return this.send("HEAD", url, { query });
    }

    /**
     * Issue a POST request to the given URL.
     */
    public async post(url: string, data?: object): Promise<Response> {
        return this.send("POST", url, { data });
    }

    /**
     * Issue a PATCH request to the given URL.
     */
    public async patch(url, data?: object): Promise<Response> {
        return this.send("PATCH", url, { data });
    }

    /**
     * Issue a PUT request to the given URL.
     */
    public async put(url, data?: object): Promise<Response> {
        return this.send("PUT", url, { data });
    }

    /**
     * Issue a DELETE request to the given URL.
     */
    public async delete(url: string, data?: object): Promise<Response> {
        return this.send("DELETE", url, { data });
    }

    /**
     * Send the request to the given URL.
     */
    private async send(
        method: string,
        url: string,
        data?: { query?: object; data?: any }
    ): Promise<Response> {
        const options: RequestOptions = {
            ...this.#options,
        };

        if (data && data.query) {
            options.searchParams = data.query;
        }

        if (data && data.data) {
            if (this.#bodyFormat === "json") {
                options.json = data.data;
            }

            if (this.#bodyFormat === "form_params") {
                options.body = new URLSearchParams();

                for (const [key, value] of Object.entries(data.data)) {
                    options.body.set(key, value);
                }
            }

            if (this.#bodyFormat === "multipart") {
                options.body = new FormData();

                for (const [key, value] of Object.entries(data.data)) {
                    options.body.append(key, value);
                }
            }
        }

        try {
            return new Response(
                await got[method.toLowerCase()](url.replace(/^\/+/g, ""), options)
            );
        } catch (error) {
            return new Response(error.response, error);
        }
    }
}