/*!
 * @nuxt/server v2.16.0 (c) 2016-2023
 * Released under the MIT License
 * Repository: https://github.com/nuxt/nuxt.js
 * Website: https://nuxtjs.org
*/
'use strict';

const path = require('path');
const consola = require('consola');
const launchMiddleware = require('launch-editor-middleware');
const serveStatic = require('serve-static');
const servePlaceholder = require('serve-placeholder');
const connect = require('connect');
const compression = require('compression');
const utils = require('@nuxt/utils');
const vueRenderer = require('@nuxt/vue-renderer');
const generateETag = require('etag');
const fresh = require('fresh');
const ufo = require('ufo');
const fs = require('fs-extra');
const Youch = require('@nuxtjs/youch');
const http = require('http');
const https = require('https');
const enableDestroy = require('server-destroy');
const ip = require('ip');
const pify = require('pify');
const onHeaders = require('on-headers');

class ServerContext {
  constructor(server) {
    this.nuxt = server.nuxt;
    this.globals = server.globals;
    this.options = server.options;
    this.resources = server.resources;
  }
}

async function renderAndGetWindow(url = "http://localhost:3000", jsdomOpts = {}, {
  loadedCallback,
  loadingTimeout = 2e3,
  globals
} = {}) {
  const jsdom = await import('jsdom').then((m) => m.default || m).catch((e) => {
    consola.error(`
         jsdom is not installed. Please install jsdom with:
          $ yarn add --dev jsdom
          OR
          $ npm install --dev jsdom
        `);
    throw e;
  });
  const options = Object.assign({
    // Load subresources (https://github.com/tmpvar/jsdom#loading-subresources)
    resources: "usable",
    runScripts: "dangerously",
    virtualConsole: true,
    beforeParse(window2) {
      window2.scrollTo = () => {
      };
    }
  }, jsdomOpts);
  const jsdomErrHandler = (err) => {
    throw err;
  };
  if (options.virtualConsole) {
    if (options.virtualConsole === true) {
      options.virtualConsole = new jsdom.VirtualConsole().sendTo(consola);
    }
    options.virtualConsole.on("jsdomError", jsdomErrHandler);
  } else {
    delete options.virtualConsole;
  }
  const { window } = await jsdom.JSDOM.fromURL(url, options);
  const nuxtExists = window.document.body.innerHTML.includes(`id="${globals.id}"`);
  if (!nuxtExists) {
    const error = new Error("Could not load the nuxt app");
    error.body = window.document.body.innerHTML;
    window.close();
    throw error;
  }
  await utils.timeout(new Promise((resolve) => {
    window[loadedCallback] = () => resolve(window);
  }), loadingTimeout, `Components loading in renderAndGetWindow was not completed in ${loadingTimeout / 1e3}s`);
  if (options.virtualConsole) {
    options.virtualConsole.removeListener("jsdomError", jsdomErrHandler);
  }
  return window;
}

const nuxtMiddleware = ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware(req, res, next) {
  const context = utils.getContext(req, res);
  try {
    const url = ufo.normalizeURL(req.url);
    res.statusCode = 200;
    const result = await renderRoute(url, context);
    if (!result) {
      await nuxt.callHook("server:nuxt:renderLoading", req, res);
      return;
    }
    await nuxt.callHook("render:route", url, result, context);
    const {
      html,
      cspScriptSrcHashes,
      error,
      redirected,
      preloadFiles
    } = result;
    if (redirected && context.target !== utils.TARGETS.static) {
      await nuxt.callHook("render:routeDone", url, result, context);
      return html;
    }
    if (error) {
      res.statusCode = context.nuxt.error.statusCode || 500;
    }
    if (options.render.csp && cspScriptSrcHashes) {
      const { allowedSources, policies } = options.render.csp;
      const isReportOnly = !!options.render.csp.reportOnly;
      const cspHeader = isReportOnly ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
      res.setHeader(cspHeader, getCspString({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }));
    }
    if (!error && options.render.etag) {
      const { hash } = options.render.etag;
      const etag = hash ? hash(html, options.render.etag) : generateETag(html, options.render.etag);
      if (fresh(req.headers, { etag })) {
        res.statusCode = 304;
        await nuxt.callHook("render:beforeResponse", url, result, context);
        res.end();
        await nuxt.callHook("render:routeDone", url, result, context);
        return;
      }
      res.setHeader("ETag", etag);
    }
    if (!error && options.render.http2.push) {
      const { shouldPush, pushAssets } = options.render.http2;
      const { publicPath } = resources.clientManifest;
      const links = pushAssets ? pushAssets(req, res, publicPath, preloadFiles) : defaultPushAssets(preloadFiles, shouldPush, publicPath, options);
      if (links.length > 0) {
        res.setHeader("Link", links.join(", "));
      }
    }
    res.setHeader("Content-Type", "text/html; charset=utf-8");
    res.setHeader("Accept-Ranges", "none");
    res.setHeader("Content-Length", Buffer.byteLength(html));
    await nuxt.callHook("render:beforeResponse", url, result, context);
    res.end(html, "utf8");
    await nuxt.callHook("render:routeDone", url, result, context);
    return html;
  } catch (err) {
    if (context && context.redirected) {
      consola.error(err);
      return err;
    }
    if (err.name === "URIError") {
      err.statusCode = 400;
    }
    next(err);
  }
};
const defaultPushAssets = (preloadFiles, shouldPush, publicPath, options) => {
  if (shouldPush && options.dev) {
    consola.warn("http2.shouldPush is deprecated. Use http2.pushAssets function");
  }
  const links = [];
  preloadFiles.forEach(({ file, asType, fileWithoutQuery, modern }) => {
    if (!shouldPush && asType !== "script" && asType !== "style") {
      return;
    }
    if (shouldPush && !shouldPush(fileWithoutQuery, asType)) {
      return;
    }
    const { crossorigin } = options.render;
    const cors = `${crossorigin ? ` crossorigin=${crossorigin};` : ""}`;
    const rel = modern && asType === "script" ? "modulepreload" : "preload";
    links.push(`<${publicPath}${file}>; rel=${rel};${cors} as=${asType}`);
  });
  return links;
};
const getCspString = ({ cspScriptSrcHashes, allowedSources, policies, isReportOnly }) => {
  const joinedHashes = cspScriptSrcHashes.join(" ");
  const baseCspStr = `script-src 'self' ${joinedHashes}`;
  const policyObjectAvailable = typeof policies === "object" && policies !== null && !Array.isArray(policies);
  if (Array.isArray(allowedSources) && allowedSources.length) {
    return isReportOnly && policyObjectAvailable && !!policies["report-uri"] ? `${baseCspStr} ${allowedSources.join(" ")}; report-uri ${policies["report-uri"]};` : `${baseCspStr} ${allowedSources.join(" ")}`;
  }
  if (policyObjectAvailable) {
    const transformedPolicyObject = transformPolicyObject(policies, cspScriptSrcHashes);
    return Object.entries(transformedPolicyObject).map(([k, v]) => `${k} ${Array.isArray(v) ? v.join(" ") : v}`).join("; ");
  }
  return baseCspStr;
};
const transformPolicyObject = (policies, cspScriptSrcHashes) => {
  const userHasDefinedScriptSrc = policies["script-src"] && Array.isArray(policies["script-src"]);
  const additionalPolicies = userHasDefinedScriptSrc ? policies["script-src"] : [];
  const hashAndPolicyList = cspScriptSrcHashes.concat("'self'", additionalPolicies);
  return { ...policies, "script-src": hashAndPolicyList };
};

const errorMiddleware = ({ resources, options }) => async function errorMiddleware(_error, req, res, next) {
  const error = normalizeError(_error, options);
  const sendResponse = (content, type = "text/html") => {
    res.statusCode = error.statusCode;
    res.statusMessage = "RuntimeError";
    res.setHeader("Content-Type", type + "; charset=utf-8");
    res.setHeader("Content-Length", Buffer.byteLength(content));
    res.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate");
    if (error.headers) {
      for (const name in error.headers) {
        res.setHeader(name, error.headers[name]);
      }
    }
    res.end(content, "utf-8");
  };
  const hasReqHeader = (header, includes) => req.headers[header] && req.headers[header].toLowerCase().includes(includes);
  const isJson = hasReqHeader("accept", "application/json") || hasReqHeader("user-agent", "curl/");
  if (!options.debug) {
    if (error.statusCode !== 404) {
      consola.error(error);
    }
    const json = {
      status: error.statusCode,
      message: error.message,
      name: error.name
    };
    if (isJson) {
      sendResponse(JSON.stringify(json, void 0, 2), "text/json");
      return;
    }
    const html2 = resources.errorTemplate(json);
    sendResponse(html2);
    return;
  }
  const youch = new Youch(
    error,
    req,
    readSource,
    options.router.base,
    true
  );
  if (isJson) {
    const json = await youch.toJSON();
    sendResponse(JSON.stringify(json, void 0, 2), "text/json");
    return;
  }
  const html = await youch.toHTML();
  sendResponse(html);
};
const sanitizeName = (name) => name ? name.replace("webpack:///", "").split("?")[0] : null;
const normalizeError = (_error, { srcDir, rootDir, buildDir }) => {
  if (typeof _error === "string") {
    _error = { message: _error };
  } else if (!_error) {
    _error = { message: "<empty>" };
  }
  const error = new Error(_error.message);
  error.name = _error.name;
  error.statusCode = _error.statusCode || 500;
  error.headers = _error.headers;
  const searchPath = [
    srcDir,
    rootDir,
    path.join(buildDir, "dist", "server"),
    buildDir,
    process.cwd()
  ];
  const findInPaths = (fileName) => {
    for (const dir of searchPath) {
      const fullPath = path.resolve(dir, fileName);
      if (fs.existsSync(fullPath)) {
        return fullPath;
      }
    }
    return fileName;
  };
  error.stack = (_error.stack || "").split("\n").map((line) => {
    const match = line.match(/\(([^)]+)\)|([^\s]+\.[^\s]+):/);
    if (!match) {
      return line;
    }
    const src = match[1] || match[2] || "";
    return line.replace(src, findInPaths(sanitizeName(src)));
  }).join("\n");
  return error;
};
async function readSource(frame) {
  if (fs.existsSync(frame.fileName)) {
    frame.fullPath = frame.fileName;
    frame.contents = await fs.readFile(frame.fileName, "utf-8");
  }
}

let RANDOM_PORT = "0";
class Listener {
  constructor({ port, host, socket, https: https2, app, dev, baseURL }) {
    this.port = port;
    this.host = host;
    this.socket = socket;
    this.https = https2;
    this.app = app;
    this.dev = dev;
    this.baseURL = baseURL;
    this.listening = false;
    this._server = null;
    this.server = null;
    this.address = null;
    this.url = null;
  }
  async close() {
    if (this.server && this.server.listening) {
      await this.server.destroy();
      consola.debug("server closed");
    }
    this.listening = false;
    this._server = null;
    this.server = null;
    this.address = null;
    this.url = null;
  }
  computeURL() {
    const address = this.server.address();
    if (!this.socket) {
      switch (address.address) {
        case "127.0.0.1":
          this.host = "localhost";
          break;
        case "0.0.0.0":
          this.host = ip.address();
          break;
      }
      this.port = address.port;
      this.url = `http${this.https ? "s" : ""}://${this.host}:${this.port}${this.baseURL}`;
      this.url = decodeURI(this.url);
      return;
    }
    this.url = `unix+http://${address}`;
  }
  async listen() {
    if (this.listening) {
      return;
    }
    const protocol = this.https ? https : http;
    const protocolOpts = this.https ? [this.https] : [];
    this._server = protocol.createServer.apply(protocol, protocolOpts.concat(this.app));
    const listenArgs = this.socket ? { path: this.socket } : { host: this.host, port: this.port };
    listenArgs.exclusive = false;
    try {
      this.server = await new Promise((resolve, reject) => {
        this._server.once("error", reject);
        this._server.listen(listenArgs, (error) => {
          this._server.off("error", reject);
          if (error) {
            reject(error);
          } else {
            resolve(this._server);
          }
        });
      });
    } catch (error) {
      return this.serverErrorHandler(error);
    }
    enableDestroy(this.server);
    pify(this.server.destroy);
    this.computeURL();
    this.listening = true;
  }
  async serverErrorHandler(error) {
    const addressInUse = error.code === "EADDRINUSE";
    if (addressInUse) {
      const address = this.socket || `${this.host}:${this.port}`;
      error.message = `Address \`${address}\` is already in use.`;
      if (this.dev && !this.socket && this.port !== RANDOM_PORT) {
        consola.warn(error.message);
        consola.info("Trying a random port...");
        this.port = RANDOM_PORT;
        await this.close();
        await this.listen();
        RANDOM_PORT = this.port;
        return;
      }
    }
    throw error;
  }
}

const createTimingMiddleware = (options) => (req, res, next) => {
  if (res.timing) {
    consola.warn("server-timing is already registered.");
  }
  res.timing = new ServerTiming();
  if (options && options.total) {
    res.timing.start("total", "Nuxt Server Time");
  }
  onHeaders(res, () => {
    res.timing.end("total");
    if (res.timing.headers.length > 0) {
      res.setHeader(
        "Server-Timing",
        [].concat(res.getHeader("Server-Timing") || []).concat(res.timing.headers).join(", ")
      );
    }
    res.timing.clear();
  });
  next();
};
class ServerTiming extends utils.Timer {
  constructor(...args) {
    super(...args);
    this.headers = [];
  }
  end(...args) {
    const time = super.end(...args);
    if (time) {
      this.headers.push(this.formatHeader(time));
    }
    return time;
  }
  clear() {
    super.clear();
    this.headers.length = 0;
  }
  formatHeader(time) {
    const desc = time.description ? `;desc="${time.description}"` : "";
    return `${time.name};dur=${time.duration}${desc}`;
  }
}

class Server {
  constructor(nuxt) {
    this.nuxt = nuxt;
    this.options = nuxt.options;
    this.globals = utils.determineGlobals(nuxt.options.globalName, nuxt.options.globals);
    this.publicPath = utils.isUrl(this.options.build.publicPath) ? this.options.build._publicPath : this.options.build.publicPath.replace(/^\.+\//, "/");
    this.resources = {};
    this.listeners = [];
    this.app = connect();
    this.nuxt.hook("close", () => this.close());
    if (this.options.dev) {
      this.nuxt.hook("server:devMiddleware", (devMiddleware) => {
        this.devMiddleware = devMiddleware;
      });
    }
  }
  async ready() {
    if (this._readyCalled) {
      return this;
    }
    this._readyCalled = true;
    await this.nuxt.callHook("render:before", this, this.options.render);
    this.serverContext = new ServerContext(this);
    this.renderer = new vueRenderer.VueRenderer(this.serverContext);
    await this.renderer.ready();
    await this.setupMiddleware();
    await this.nuxt.callHook("render:done", this);
    return this;
  }
  async setupMiddleware() {
    await this.nuxt.callHook("render:setupMiddleware", this.app);
    if (!this.options.dev) {
      const { compressor } = this.options.render;
      if (typeof compressor === "object") {
        this.useMiddleware(compression(compressor));
      } else if (compressor) {
        this.useMiddleware(compressor);
      }
    }
    if (this.options.server.timing) {
      this.useMiddleware(createTimingMiddleware(this.options.server.timing));
    }
    if (this.options.render.static !== false) {
      const staticMiddleware = serveStatic(
        path.resolve(this.options.srcDir, this.options.dir.static),
        this.options.render.static
      );
      staticMiddleware.prefix = this.options.render.static.prefix;
      this.useMiddleware(staticMiddleware);
    }
    if (!this.options.dev) {
      const distDir = path.resolve(this.options.buildDir, "dist", "client");
      this.useMiddleware({
        path: this.publicPath,
        handler: serveStatic(
          distDir,
          this.options.render.dist
        )
      });
    }
    if (this.options.dev) {
      this.useMiddleware((req, res, next) => {
        if (!this.devMiddleware) {
          return next();
        }
        if (req.url.startsWith(this.publicPath) && req.url.endsWith(".js")) {
          res.setHeader("Vary", "*");
        }
        this.devMiddleware(req, res, next);
      });
      if (this.options.debug) {
        this.useMiddleware({
          path: "__open-in-editor",
          handler: launchMiddleware(this.options.editor)
        });
      }
    }
    for (const m of this.options.serverMiddleware) {
      this.useMiddleware(m);
    }
    const { fallback } = this.options.render;
    if (fallback) {
      if (fallback.dist) {
        this.useMiddleware({
          path: this.publicPath,
          handler: servePlaceholder.servePlaceholder(fallback.dist)
        });
      }
      if (fallback.static) {
        this.useMiddleware({
          path: "/",
          handler: servePlaceholder.servePlaceholder(fallback.static)
        });
      }
    }
    this.useMiddleware(nuxtMiddleware({
      options: this.options,
      nuxt: this.nuxt,
      renderRoute: this.renderRoute.bind(this),
      resources: this.resources
    }));
    const routerBase = this.nuxt.options.router.base;
    if (this.options.dev && routerBase !== "/") {
      this.useMiddleware({
        prefix: false,
        handler: (req, res, next) => {
          if (decodeURI(req.url).startsWith(decodeURI(routerBase))) {
            return next();
          }
          const to = utils.urlJoin(routerBase, req.url);
          consola.info(`[Development] Redirecting from \`${decodeURI(req.url)}\` to \`${decodeURI(to)}\` (router.base specified)`);
          res.writeHead(302, {
            Location: to
          });
          res.end();
        }
      });
    }
    await this.nuxt.callHook("render:errorMiddleware", this.app);
    this.useMiddleware(errorMiddleware({
      resources: this.resources,
      options: this.options
    }));
  }
  _normalizeMiddleware(middleware) {
    if (typeof middleware === "function") {
      middleware = { handle: middleware };
    }
    if (typeof middleware === "string") {
      middleware = this._requireMiddleware(middleware);
    }
    middleware = Object.assign({}, middleware);
    if (middleware.handler && !middleware.handle) {
      middleware.handle = middleware.handler;
      delete middleware.handler;
    }
    if (middleware.path && !middleware.route) {
      middleware.route = middleware.path;
      delete middleware.path;
    }
    if (typeof middleware.handle === "string") {
      Object.assign(middleware, this._requireMiddleware(middleware.handle));
    }
    if (!middleware.handle) {
      middleware.handle = (req, res, next) => {
        next(new Error("ServerMiddleware should expose a handle: " + middleware.entry));
      };
    }
    if (middleware.handle.prefix !== void 0 && middleware.prefix === void 0) {
      middleware.prefix = middleware.handle.prefix;
    }
    if (typeof middleware.handle.handle === "function") {
      const server = middleware.handle;
      middleware.handle = server.handle.bind(server);
    }
    return middleware;
  }
  _requireMiddleware(entry) {
    entry = this.nuxt.resolver.resolvePath(entry);
    let middleware;
    try {
      middleware = this.nuxt.resolver.requireModule(entry);
    } catch (error) {
      consola.error("ServerMiddleware Error:", error);
      middleware = (req, res, next) => {
        next(error);
      };
    }
    middleware = this._normalizeMiddleware(middleware);
    middleware.entry = entry;
    return middleware;
  }
  resolveMiddleware(middleware, fallbackRoute = "/") {
    middleware = this._normalizeMiddleware(middleware);
    if (!middleware.route) {
      middleware.route = fallbackRoute;
    }
    middleware._originalRoute = middleware.route;
    middleware.route = ((middleware.prefix !== false ? this.options.router.base : "") + (typeof middleware.route === "string" ? middleware.route : "")).replace(/\/\//g, "/");
    if (middleware.route.endsWith("/")) {
      middleware.route = middleware.route.slice(0, -1);
    }
    middleware.handle._middleware = middleware;
    return middleware;
  }
  useMiddleware(middleware) {
    const { route, handle } = this.resolveMiddleware(middleware);
    this.app.use(route, handle);
  }
  replaceMiddleware(query, middleware) {
    let serverStackItem;
    if (typeof query === "string") {
      serverStackItem = this.app.stack.find(({ handle: handle2 }) => handle2._middleware && handle2._middleware.entry === query);
    } else {
      serverStackItem = this.app.stack.find(({ handle: handle2 }) => handle2 === query);
    }
    if (!serverStackItem) {
      return;
    }
    this.unloadMiddleware(serverStackItem);
    const { route, handle } = this.resolveMiddleware(
      middleware,
      // #8584 pass the original route as fallback
      serverStackItem.handle._middleware ? serverStackItem.handle._middleware._originalRoute : serverStackItem.route
    );
    serverStackItem.handle = handle;
    serverStackItem.route = route;
    return serverStackItem;
  }
  unloadMiddleware({ handle }) {
    if (handle._middleware && typeof handle._middleware.unload === "function") {
      handle._middleware.unload();
    }
  }
  serverMiddlewarePaths() {
    return this.app.stack.map(({ handle }) => handle._middleware && handle._middleware.entry).filter(Boolean);
  }
  renderRoute() {
    return this.renderer.renderRoute.apply(this.renderer, arguments);
  }
  loadResources() {
    return this.renderer.loadResources.apply(this.renderer, arguments);
  }
  renderAndGetWindow(url, opts = {}, {
    loadingTimeout = 2e3,
    loadedCallback = this.globals.loadedCallback,
    globals = this.globals
  } = {}) {
    return renderAndGetWindow(url, opts, {
      loadingTimeout,
      loadedCallback,
      globals
    });
  }
  async listen(port, host, socket) {
    await this.nuxt.ready();
    const listener = new Listener({
      port: isNaN(parseInt(port)) ? this.options.server.port : port,
      host: host || this.options.server.host,
      socket: socket || this.options.server.socket,
      https: this.options.server.https,
      app: this.app,
      dev: this.options.dev,
      baseURL: this.options.router.base
    });
    await listener.listen();
    this.listeners.push(listener);
    await this.nuxt.callHook("listen", listener.server, listener);
    return listener;
  }
  async close() {
    if (this.__closed) {
      return;
    }
    this.__closed = true;
    await Promise.all(this.listeners.map((l) => l.close()));
    this.listeners = [];
    if (typeof this.renderer.close === "function") {
      await this.renderer.close();
    }
    this.app.stack.forEach(this.unloadMiddleware);
    this.app.removeAllListeners();
    this.app = null;
    for (const key in this.resources) {
      delete this.resources[key];
    }
  }
}

exports.Listener = Listener;
exports.Server = Server;
