"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProxyServer = void 0;
const http = __importStar(require("node:http"));
const http2 = __importStar(require("node:http2"));
const web_incoming_1 = require("./passes/web-incoming");
const ws_incoming_1 = require("./passes/ws-incoming");
const node_events_1 = require("node:events");
const debug_1 = __importDefault(require("debug"));
const common_1 = require("./common");
const log = (0, debug_1.default)("http-proxy-3");
class ProxyServer extends node_events_1.EventEmitter {
    /**
     * Creates the proxy server with specified options.
     * @param options - Config object passed to the proxy
     */
    constructor(options = {}) {
        super();
        // createRightProxy - Returns a function that when called creates the loader for
        // either `ws` or `web`'s passes.
        this.createRightProxy = (type) => {
            log("createRightProxy", { type });
            return (options) => {
                return (...args /* req, res, [head], [opts] */) => {
                    const req = args[0];
                    log("proxy: ", { type, path: req.url });
                    const res = args[1];
                    const passes = type === "ws" ? this.wsPasses : this.webPasses;
                    if (type == "ws") {
                        // socket -- proxy websocket errors to our error handler;
                        // see https://github.com/sagemathinc/http-proxy-3/issues/5
                        // NOTE: as mentioned below, res is the socket in this case.
                        // One of the passes does add an error handler, but there's no
                        // guarantee we even get to that pass before something bad happens,
                        // and there's no way for a user of http-proxy-3 to get ahold
                        // of this res object and attach their own error handler until
                        // after the passes. So we better attach one ASAP right here:
                        res.on("error", (err) => {
                            this.emit("error", err, req, res);
                        });
                    }
                    let counter = args.length - 1;
                    let head;
                    let cb;
                    // optional args parse begin
                    if (typeof args[counter] === "function") {
                        cb = args[counter];
                        counter--;
                    }
                    let requestOptions;
                    if (!(args[counter] instanceof Buffer) && args[counter] !== res) {
                        // Copy global options, and overwrite with request options
                        requestOptions = { ...options, ...args[counter] };
                        counter--;
                    }
                    else {
                        requestOptions = { ...options };
                    }
                    if (args[counter] instanceof Buffer) {
                        head = args[counter];
                    }
                    for (const e of ["target", "forward"]) {
                        if (typeof requestOptions[e] === "string") {
                            requestOptions[e] = (0, common_1.toURL)(requestOptions[e]);
                        }
                    }
                    if (!requestOptions.target && !requestOptions.forward) {
                        this.emit("error", new Error("Must set target or forward"), req, res);
                        return;
                    }
                    for (const pass of passes) {
                        /**
                         * Call of passes functions
                         *     pass(req, res, options, head)
                         *
                         * In WebSockets case, the `res` variable
                         * refer to the connection socket
                         *    pass(req, socket, options, head)
                         */
                        if (pass(req, res, requestOptions, head, this, cb)) {
                            // passes can return a truthy value to halt the loop
                            break;
                        }
                    }
                };
            };
        };
        this.onError = (err) => {
            // Force people to handle their own errors
            if (this.listeners("error").length === 1) {
                throw err;
            }
        };
        /**
         * A function that wraps the object in a webserver, for your convenience
         * @param port - Port to listen on
         * @param hostname - The hostname to listen on
         */
        this.listen = (port, hostname) => {
            log("listen", { port, hostname });
            const requestListener = (req, res) => {
                this.web(req, res);
            };
            this._server = this.options.ssl
                ? http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, requestListener)
                : http.createServer(requestListener);
            if (this.options.ws) {
                this._server.on("upgrade", (req, socket, head) => {
                    this.ws(req, socket, head);
                });
            }
            this._server.listen(port, hostname);
            return this;
        };
        // if the proxy started its own http server, this is the address of that server.
        this.address = () => {
            return this._server?.address();
        };
        /**
         * A function that closes the inner webserver and stops listening on given port
         */
        this.close = (cb) => {
            if (this._server == null) {
                cb?.();
                return;
            }
            // Wrap cb anb nullify server after all open connections are closed.
            this._server.close((err) => {
                this._server = null;
                cb?.(err);
            });
        };
        this.before = (type, passName, cb) => {
            if (type !== "ws" && type !== "web") {
                throw new Error("type must be `web` or `ws`");
            }
            const passes = (type === "ws" ? this.wsPasses : this.webPasses);
            let i = false;
            passes.forEach((v, idx) => {
                if (v.name === passName) {
                    i = idx;
                }
            });
            if (i === false) {
                throw new Error("No such pass");
            }
            passes.splice(i, 0, cb);
        };
        this.after = (type, passName, cb) => {
            if (type !== "ws" && type !== "web") {
                throw new Error("type must be `web` or `ws`");
            }
            const passes = (type === "ws" ? this.wsPasses : this.webPasses);
            let i = false;
            passes.forEach((v, idx) => {
                if (v.name === passName) {
                    i = idx;
                }
            });
            if (i === false) {
                throw new Error("No such pass");
            }
            passes.splice(i++, 0, cb);
        };
        log("creating a ProxyServer", options);
        options.prependPath = options.prependPath !== false;
        this.options = options;
        this.web = this.createRightProxy("web")(options);
        this.ws = this.createRightProxy("ws")(options);
        this.webPasses = Object.values(web_incoming_1.WEB_PASSES);
        this.wsPasses = Object.values(ws_incoming_1.WS_PASSES);
        this.on("error", this.onError);
    }
    /**
     * Creates the proxy server with specified options.
     * @param options Config object passed to the proxy
     * @returns Proxy object with handlers for `ws` and `web` requests
     */
    static createProxyServer(options) {
        return new ProxyServer(options);
    }
    /**
     * Creates the proxy server with specified options.
     * @param options Config object passed to the proxy
     * @returns Proxy object with handlers for `ws` and `web` requests
     */
    static createServer(options) {
        return new ProxyServer(options);
    }
    /**
     * Creates the proxy server with specified options.
     * @param options Config object passed to the proxy
     * @returns Proxy object with handlers for `ws` and `web` requests
     */
    static createProxy(options) {
        return new ProxyServer(options);
    }
}
exports.ProxyServer = ProxyServer;
