'use strict';
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Request = void 0;
const net_1 = __importDefault(require("net"));
const interacts_with_headers_1 = require("./interacts-with-headers");
class Request extends interacts_with_headers_1.InteractsWithHeaders {
    /**
     * Create a new instance for the given `request`.
     *
     * @param {Object} request
     */
    constructor(request) {
        request = request || {};
        super(request.headers);
        this.request = request;
    }
    /**
     * Returns the client IP address.
     *
     * @returns {String|undefined}
     */
    getClientIp() {
        var _a, _b, _c, _d, _e;
        return (_e = (_d = (_c = (_b = (_a = this.fromHeaders()) !== null && _a !== void 0 ? _a : this.fromSocket()) !== null && _b !== void 0 ? _b : this.fromConnection()) !== null && _c !== void 0 ? _c : this.fromInfo()) !== null && _d !== void 0 ? _d : this.fromRaw()) !== null && _e !== void 0 ? _e : this.fromRequestContext();
    }
    /**
     * Returns the IP address if available in the HTTP request headers.
     *
     * @returns {String|undefined}
     */
    fromHeaders() {
        if (this.hasHeaders()) {
            // nginx (if configured), load balancers (AWS ELB), and other proxies
            if (this.hasIpInForwardedFor()) {
                return this.getFromForwardedFor();
            }
            // Heroku, AWS EC2, nginx (if configured), and others
            if (this.hasIpInHeader('x-client-ip')) {
                return this.ipInHeader('x-client-ip');
            }
            // used by some proxies, like nginx
            if (this.hasIpInHeader('x-real-ip')) {
                return this.header('x-real-ip');
            }
            // Cloudflare
            if (this.hasIpInHeader('cf-connecting-ip')) {
                return this.header('cf-connecting-ip');
            }
            // Fastly and Firebase
            if (this.hasIpInHeader('fastly-client-ip')) {
                return this.header('fastly-client-ip');
            }
            // Akamai, Cloudflare
            if (this.hasIpInHeader('true-client-ip')) {
                return this.header('true-client-ip');
            }
            // Rackspace
            if (this.hasIpInHeader('x-cluster-client-ip')) {
                return this.header('x-cluster-client-ip');
            }
        }
    }
    /**
     * Determine whether a valid IP address is available in the “x-forwarded-for” HTTP header.
     *
     * @returns {Boolean}
     */
    hasIpInForwardedFor() {
        return this.isIp(this.getFromForwardedFor());
    }
    /**
     * Returns the IP address if available from the “x-forwarded-for” HTTP header.
     *
     * @returns {String|undefined}
     */
    getFromForwardedFor() {
        if (this.hasIpInHeader('x-forwarded-for')) {
            return this.ipInHeader('x-forwarded-for');
        }
        if (this.hasIpInHeader('x-forwarded')) {
            return this.ipInHeader('x-forwarded');
        }
        if (this.hasIpInHeader('forwarded-for')) {
            return this.ipInHeader('forwarded-for');
        }
        if (this.hasIpInHeader('forwarded')) {
            return this.ipInHeader('forwarded');
        }
    }
    /**
     * Determine whether the request IP comes from the given header `name`.
     *
     * @param {String} name - the header name
     *
     * @returns {Boolean}
     */
    hasIpInHeader(name) {
        return !!this.ipInHeader(name);
    }
    /**
     * Returns the first IP address from the `name`d header.
     *
     * @param {String} name
     *
     * @returns {String|undefined}
     */
    ipInHeader(name) {
        var _a;
        return this.findIp((_a = this.header(name)) === null || _a === void 0 ? void 0 : _a.split(','));
    }
    /**
     * Returns the first valid IP address from the list of IP address candidates.
     *
     * @param {Array} ips
     *
     * @returns {String|undefined}
     */
    findIp(ips = []) {
        return ips
            .map(ip => ip.trim())
            .map(ip => this.removePortFrom(ip))
            .find(ip => this.isIp(ip));
    }
    /**
     * Returns the plain IP v4 address without the port number.
     *
     * @param {String} ip
     *
     * @returns {String}
     */
    removePortFrom(ip) {
        if (this.isIpv6(ip)) {
            return ip;
        }
        return ip.includes(':')
            ? ip.split(':')[0]
            : ip;
    }
    /**
     * Returns the IP address if available in the request connection.
     *
     * @returns {String|undefined}
     */
    fromConnection() {
        if (!this.hasConnection()) {
            return;
        }
        if (this.isIp(this.request.connection.remoteAddress)) {
            return this.request.connection.remoteAddress;
        }
        if (!this.request.connection.socket) {
            return;
        }
        if (this.isIp(this.request.connection.socket.remoteAddress)) {
            return this.request.connection.socket.remoteAddress;
        }
    }
    /**
     * Determine whether the request has a `connection` object assigned.
     *
     * @returns {Boolean}
     */
    hasConnection() {
        return !!this.request.connection;
    }
    /**
     * Returns the IP address if available in the request socket.
     *
     * @returns {String|undefined}
     */
    fromSocket() {
        if (!this.hasSocket()) {
            return;
        }
        if (this.isIp(this.request.socket.remoteAddress)) {
            return this.request.socket.remoteAddress;
        }
    }
    /**
     * Determine whether the request has a `socket` object assigned.
     *
     * @returns {Boolean}
     */
    hasSocket() {
        return !!this.request.socket;
    }
    /**
     * Returns the IP address if available in the request info object.
     *
     * @returns {String|undefined}
     */
    fromInfo() {
        if (!this.hasInfo()) {
            return;
        }
        if (this.isIp(this.request.info.remoteAddress)) {
            return this.request.info.remoteAddress;
        }
    }
    /**
     * Determine whether the request has an `info` object assigned.
     *
     * @returns {Boolean}
     */
    hasInfo() {
        return !!this.request.info;
    }
    /**
     * Returns the IP address if available from the raw request object. The
     * `raw` request object is typically available in web frameworks like
     * Fastify or hapi providing the original Node.js request instance.
     *
     * @returns {String|undefined}
     */
    fromRaw() {
        if (this.hasRaw()) {
            return new Request(this.request.raw).getClientIp();
        }
    }
    /**
     * Determine whether the request has a `requestContext` object assigned.
     *
     * @returns {Boolean}
     */
    hasRaw() {
        return !!this.raw();
    }
    /**
     * Returns the raw request object.
     *
     * @returns {Object}
     */
    raw() {
        return this.request.raw;
    }
    /**
     * Returns the IP address if available in the request context. The request
     * context is typically available in serverless functions, like AWS Lambda.
     *
     * @returns {String|undefined}
     */
    fromRequestContext() {
        // AWS API Gateway/Lambda
        if (!this.hasRequestContext()) {
            return;
        }
        if (!this.requestContext().identity) {
            return;
        }
        if (this.isIp(this.requestContext().identity.sourceIp)) {
            return this.requestContext().identity.sourceIp;
        }
    }
    /**
     * Determine whether the request has a `requestContext` object assigned.
     *
     * @returns {Boolean}
     */
    hasRequestContext() {
        return !!this.requestContext();
    }
    /**
     * Returns the request context.
     *
     * @returns {*}
     */
    requestContext() {
        return this.request.requestContext;
    }
    /**
     * Determine whether it’s a valid `ip` address.
     *
     * @param {String} ip
     *
     * @returns {Boolean}
     */
    isIp(ip) {
        return this.isIpv4(ip) || this.isIpv6(ip);
    }
    /**
     * Determine whether the given `ip` address is a valid IP v4 address.
     *
     * @param {String} ip
     *
     * @returns {Boolean}
     */
    isIpv4(ip) {
        return net_1.default.isIP(ip !== null && ip !== void 0 ? ip : '') === 4;
    }
    /**
     * Determine whether the given `ip` address is a valid IP v4 address.
     *
     * @param {String} ip
     *
     * @returns {Boolean}
     */
    isIpv6(ip) {
        return net_1.default.isIP(ip !== null && ip !== void 0 ? ip : '') === 6;
    }
}
exports.Request = Request;
