"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "startServer", {
    enumerable: true,
    get: function() {
        return startServer;
    }
});
const _http = /*#__PURE__*/ _interop_require_default(require("http"));
const _net = require("net");
const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../build/output/log"));
const _utils = require("../../shared/lib/utils");
const _env = require("@next/env");
const _utils1 = require("./utils");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {};
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
async function startServer({ dir , prevDir , port , isDev , hostname , useWorkers , allowRetry , keepAliveTimeout , onStdout , onStderr  }) {
    var _process_env_NODE_OPTIONS, _process_env_NODE_OPTIONS1;
    const sockets = new Set();
    let worker;
    let handlersReady = ()=>{};
    let handlersError = ()=>{};
    let isNodeDebugging = !!(process.execArgv.some((localArg)=>localArg.startsWith("--inspect")) || ((_process_env_NODE_OPTIONS = process.env.NODE_OPTIONS) == null ? void 0 : _process_env_NODE_OPTIONS.match == null ? void 0 : _process_env_NODE_OPTIONS.match(/--inspect(=\S+)?( |$)/)));
    if (process.execArgv.some((localArg)=>localArg.startsWith("--inspect-brk")) || ((_process_env_NODE_OPTIONS1 = process.env.NODE_OPTIONS) == null ? void 0 : _process_env_NODE_OPTIONS1.match == null ? void 0 : _process_env_NODE_OPTIONS1.match(/--inspect-brk(=\S+)?( |$)/))) {
        isNodeDebugging = "brk";
    }
    let handlersPromise = new Promise((resolve, reject)=>{
        handlersReady = resolve;
        handlersError = reject;
    });
    let requestHandler = async (_req, _res)=>{
        if (handlersPromise) {
            await handlersPromise;
            return requestHandler(_req, _res);
        }
        throw new Error("Invariant request handler was not setup");
    };
    let upgradeHandler = async (_req, _socket, _head)=>{
        if (handlersPromise) {
            await handlersPromise;
            return upgradeHandler(_req, _socket, _head);
        }
        throw new Error("Invariant upgrade handler was not setup");
    };
    // setup server listener as fast as possible
    const server = _http.default.createServer(async (req, res)=>{
        try {
            if (handlersPromise) {
                await handlersPromise;
                handlersPromise = undefined;
            }
            sockets.add(res);
            res.on("close", ()=>sockets.delete(res));
            await requestHandler(req, res);
        } catch (err) {
            res.statusCode = 500;
            res.end("Internal Server Error");
            _log.error(`Failed to handle request for ${req.url}`);
            console.error(err);
        }
    });
    if (keepAliveTimeout) {
        server.keepAliveTimeout = keepAliveTimeout;
    }
    server.on("upgrade", async (req, socket, head)=>{
        try {
            sockets.add(socket);
            socket.on("close", ()=>sockets.delete(socket));
            await upgradeHandler(req, socket, head);
        } catch (err) {
            socket.destroy();
            _log.error(`Failed to handle request for ${req.url}`);
            console.error(err);
        }
    });
    let portRetryCount = 0;
    server.on("error", (err)=>{
        if (allowRetry && port && isDev && err.code === "EADDRINUSE" && portRetryCount < 10) {
            _log.warn(`Port ${port} is in use, trying ${port + 1} instead.`);
            port += 1;
            portRetryCount += 1;
            server.listen(port, hostname);
        } else {
            _log.error(`Failed to start server`);
            console.error(err);
            process.exit(1);
        }
    });
    let targetHost = hostname;
    await new Promise((resolve)=>{
        server.on("listening", ()=>{
            const addr = server.address();
            port = typeof addr === "object" ? (addr == null ? void 0 : addr.port) || port : port;
            let host = !hostname || hostname === "0.0.0.0" ? "localhost" : hostname;
            let normalizedHostname = hostname || "0.0.0.0";
            if ((0, _net.isIPv6)(hostname)) {
                host = host === "::" ? "[::1]" : `[${host}]`;
                normalizedHostname = `[${hostname}]`;
            }
            targetHost = host;
            const appUrl = `http://${host}:${port}`;
            if (isNodeDebugging) {
                const debugPort = (0, _utils1.getDebugPort)();
                _log.info(`the --inspect${isNodeDebugging === "brk" ? "-brk" : ""} option was detected, the Next.js proxy server should be inspected at port ${debugPort}.`);
            }
            _log.ready(`started server on ${normalizedHostname}${(port + "").startsWith(":") ? "" : ":"}${port}, url: ${appUrl}`);
            resolve();
        });
        server.listen(port, hostname);
    });
    try {
        if (useWorkers) {
            var _routerWorker__workerPool;
            const httpProxy = require("next/dist/compiled/http-proxy");
            let renderServerPath = require.resolve("./render-server");
            let jestWorkerPath = require.resolve("next/dist/compiled/jest-worker");
            if (prevDir) {
                jestWorkerPath = jestWorkerPath.replace(prevDir, dir);
                renderServerPath = renderServerPath.replace(prevDir, dir);
            }
            const { Worker  } = require(jestWorkerPath);
            const routerWorker = new Worker(renderServerPath, {
                numWorkers: 1,
                // TODO: do we want to allow more than 10 OOM restarts?
                maxRetries: 10,
                forkOptions: {
                    execArgv: await (0, _utils1.genRouterWorkerExecArgv)(isNodeDebugging === undefined ? false : isNodeDebugging),
                    env: {
                        FORCE_COLOR: "1",
                        ..._env.initialEnv || process.env,
                        PORT: port + "",
                        NODE_OPTIONS: (0, _utils1.getNodeOptionsWithoutInspect)(),
                        ...process.env.NEXT_CPU_PROF ? {
                            __NEXT_PRIVATE_CPU_PROFILE: `CPU.router`
                        } : {},
                        WATCHPACK_WATCHER_LIMIT: "20"
                    }
                },
                exposedMethods: [
                    "initialize"
                ]
            });
            let didInitialize = false;
            for (const _worker of ((_routerWorker__workerPool = routerWorker._workerPool) == null ? void 0 : _routerWorker__workerPool._workers) || []){
                // eslint-disable-next-line no-loop-func
                _worker._child.on("exit", (code, signal)=>{
                    // catch failed initializing without retry
                    if ((code || signal) && !didInitialize) {
                        routerWorker == null ? void 0 : routerWorker.end();
                        process.exit(1);
                    }
                });
            }
            const workerStdout = routerWorker.getStdout();
            const workerStderr = routerWorker.getStderr();
            workerStdout.on("data", (data)=>{
                if (typeof onStdout === "function") {
                    onStdout(data);
                } else {
                    process.stdout.write(data);
                }
            });
            workerStderr.on("data", (data)=>{
                if (typeof onStderr === "function") {
                    onStderr(data);
                } else {
                    process.stderr.write(data);
                }
            });
            const { port: routerPort  } = await routerWorker.initialize({
                dir,
                port,
                hostname,
                dev: !!isDev,
                workerType: "router",
                isNodeDebugging: !!isNodeDebugging,
                keepAliveTimeout
            });
            didInitialize = true;
            const getProxyServer = (pathname)=>{
                const targetUrl = `http://${targetHost === "localhost" ? "127.0.0.1" : targetHost}:${routerPort}${pathname}`;
                const proxyServer = httpProxy.createProxy({
                    target: targetUrl,
                    changeOrigin: false,
                    ignorePath: true,
                    xfwd: true,
                    ws: true,
                    followRedirects: false
                });
                proxyServer.on("error", (_err)=>{
                // TODO?: enable verbose error logs with --debug flag?
                });
                return proxyServer;
            };
            // proxy to router worker
            requestHandler = async (req, res)=>{
                const urlParts = (req.url || "").split("?");
                const urlNoQuery = urlParts[0];
                // this normalizes repeated slashes in the path e.g. hello//world ->
                // hello/world or backslashes to forward slashes, this does not
                // handle trailing slash as that is handled the same as a next.config.js
                // redirect
                if (urlNoQuery == null ? void 0 : urlNoQuery.match(/(\\|\/\/)/)) {
                    const cleanUrl = (0, _utils.normalizeRepeatedSlashes)(req.url);
                    res.statusCode = 308;
                    res.setHeader("Location", cleanUrl);
                    res.end(cleanUrl);
                    return;
                }
                const proxyServer = getProxyServer(req.url || "/");
                // http-proxy does not properly detect a client disconnect in newer
                // versions of Node.js. This is caused because it only listens for the
                // `aborted` event on the our request object, but it also fully reads
                // and closes the request object. Node **will not** fire `aborted` when
                // the request is already closed. Listening for `close` on our response
                // object will detect the disconnect, and we can abort the proxy's
                // connection.
                proxyServer.on("proxyReq", (proxyReq)=>{
                    res.on("close", ()=>proxyReq.destroy());
                });
                proxyServer.on("proxyRes", (proxyRes)=>{
                    res.on("close", ()=>proxyRes.destroy());
                });
                proxyServer.web(req, res);
            };
            upgradeHandler = async (req, socket, head)=>{
                const proxyServer = getProxyServer(req.url || "/");
                proxyServer.ws(req, socket, head);
            };
            handlersReady();
        } else {
            // when not using a worker start next in main process
            const next = require("../next");
            const addr = server.address();
            const app = next({
                dir,
                hostname,
                dev: isDev,
                isNodeDebugging,
                httpServer: server,
                customServer: false,
                port: addr && typeof addr === "object" ? addr.port : port
            });
            // handle in process
            requestHandler = app.getRequestHandler();
            upgradeHandler = app.getUpgradeHandler();
            await app.prepare();
            handlersReady();
        }
    } catch (err) {
        // fatal error if we can't setup
        handlersError();
        console.error(err);
        process.exit(1);
    }
    // return teardown function for destroying the server
    async function teardown() {
        server.close();
        sockets.forEach((socket)=>{
            sockets.delete(socket);
            socket.destroy();
        });
        if (worker) {
            await worker.end();
        }
    }
    return teardown;
}

//# sourceMappingURL=start-server.js.map