text: "use strict";
  var __importDefault = (this && this.__importDefault) || function (mod) {
      return (mod && mod.__esModule) ? mod : { "default": mod };
  };
  var __importStar = (this && this.__importStar) || function (mod) {
      if (mod && mod.__esModule) return mod;
      var result = {};
      if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
      result["default"] = mod;
      return result;
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  const path_1 = require("path");
  const url_1 = require("url");
  const querystring_1 = require("querystring");
  const fs_1 = __importDefault(require("fs"));
  const render_1 = require("./render");
  const send_html_1 = require("./send-html");
  const serve_static_1 = require("./serve-static");
  const router_1 = __importStar(require("./router"));
  const utils_1 = require("./utils");
  const next_config_1 = __importDefault(require("next-server/next-config"));
  const constants_1 = require("next-server/constants");
  const envConfig = __importStar(require("../lib/runtime-config"));
  const load_components_1 = require("./load-components");
  class Server {
      constructor({ dir = '.', staticMarkup = false, quiet = false, conf = null } = {}) {
          this.dir = path_1.resolve(dir);
          this.quiet = quiet;
          const phase = this.currentPhase();
          this.nextConfig = next_config_1.default(phase, this.dir, conf);
          this.distDir = path_1.join(this.dir, this.nextConfig.distDir);
          // Only serverRuntimeConfig needs the default
          // publicRuntimeConfig gets it's default in client/index.js
          const { serverRuntimeConfig = {}, publicRuntimeConfig, assetPrefix, generateEtags, target } = this.nextConfig;
          if (process.env.NODE_ENV === 'production' && target !== 'server')
              throw new Error('Cannot start server when target is not server. https://err.sh/zeit/next.js/next-start-serverless');
          this.buildId = this.readBuildId();
          this.renderOpts = {
              ampEnabled: this.nextConfig.experimental.amp,
              staticMarkup,
              buildId: this.buildId,
              generateEtags,
          };
          // Only the `publicRuntimeConfig` key is exposed to the client side
          // It'll be rendered as part of __NEXT_DATA__ on the client side
          if (publicRuntimeConfig) {
              this.renderOpts.runtimeConfig = publicRuntimeConfig;
          }
          // Initialize next/config with the environment configuration
          envConfig.setConfig({
              serverRuntimeConfig,
              publicRuntimeConfig,
          });
          const routes = this.generateRoutes();
          this.router = new router_1.default(routes);
          this.setAssetPrefix(assetPrefix);
      }
      currentPhase() {
          return constants_1.PHASE_PRODUCTION_SERVER;
      }
      logError(...args) {
          if (this.quiet)
              return;
          // tslint:disable-next-line
          console.error(...args);
      }
      handleRequest(req, res, parsedUrl) {
          // Parse url if parsedUrl not provided
          if (!parsedUrl || typeof parsedUrl !== 'object') {
              const url = req.url;
              parsedUrl = url_1.parse(url, true);
          }
          // Parse the querystring ourselves if the user doesn't handle querystring parsing
          if (typeof parsedUrl.query === 'string') {
              parsedUrl.query = querystring_1.parse(parsedUrl.query);
          }
          res.statusCode = 200;
          return this.run(req, res, parsedUrl)
              .catch((err) => {
              this.logError(err);
              res.statusCode = 500;
              res.end('Internal Server Error');
          });
      }
      getRequestHandler() {
          return this.handleRequest.bind(this);
      }
      setAssetPrefix(prefix) {
          this.renderOpts.assetPrefix = prefix ? prefix.replace(/\/$/, '') : '';
      }
      // Backwards compatibility
      async prepare() { }
      // Backwards compatibility
      async close() { }
      setImmutableAssetCacheControl(res) {
          res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
      }
      generateRoutes() {
          const routes = [
              {
                  match: router_1.route('/_next/static/:path*'),
                  fn: async (req, res, params, parsedUrl) => {
                      // The commons folder holds commonschunk files
                      // The chunks folder holds dynamic entries
                      // The buildId folder holds pages and potentially other assets. As buildId changes per build it can be long-term cached.
                      if (params.path[0] === constants_1.CLIENT_STATIC_FILES_RUNTIME || params.path[0] === 'chunks' || params.path[0] === this.buildId) {
                          this.setImmutableAssetCacheControl(res);
                      }
                      const p = path_1.join(this.distDir, constants_1.CLIENT_STATIC_FILES_PATH, ...(params.path || []));
                      await this.serveStatic(req, res, p, parsedUrl);
                  },
              },
              {
                  match: router_1.route('/_next/:path*'),
                  // This path is needed because `render()` does a check for `/_next` and the calls the routing again
                  fn: async (req, res, _params, parsedUrl) => {
                      await this.render404(req, res, parsedUrl);
                  },
              },
              {
                  // It's very important to keep this route's param optional.
                  // (but it should support as many params as needed, separated by '/')
                  // Otherwise this will lead to a pretty simple DOS attack.
                  // See more: https://github.com/zeit/next.js/issues/2617
                  match: router_1.route('/static/:path*'),
                  fn: async (req, res, params, parsedUrl) => {
                      const p = path_1.join(this.dir, 'static', ...(params.path || []));
                      await this.serveStatic(req, res, p, parsedUrl);
                  },
              },
          ];
          if (this.nextConfig.useFileSystemPublicRoutes) {
              // It's very important to keep this route's param optional.
              // (but it should support as many params as needed, separated by '/')
              // Otherwise this will lead to a pretty simple DOS attack.
              // See more: https://github.com/zeit/next.js/issues/2617
              routes.push({
                  match: router_1.route('/:path*'),
                  fn: async (req, res, _params, parsedUrl) => {
                      const { pathname, query } = parsedUrl;
                      if (!pathname) {
                          throw new Error('pathname is undefined');
                      }
                      // Currently, we will render amp even if the user passes `?amp=0`.
                      if (this.nextConfig.experimental.amp && query.amp) {
                          await this.renderToAMP(req, res, pathname, query, parsedUrl);
                      }
                      else {
                          await this.render(req, res, pathname, query, parsedUrl);
                      }
                  },
              });
          }
          return routes;
      }
      async run(req, res, parsedUrl) {
          try {
              const fn = this.router.match(req, res, parsedUrl);
              if (fn) {
                  await fn();
                  return;
              }
          }
          catch (err) {
              if (err.code === 'DECODE_FAILED') {
                  res.statusCode = 400;
                  return this.renderError(null, req, res, '/_error', {});
              }
              throw err;
          }
          if (req.method === 'GET' || req.method === 'HEAD') {
              await this.render404(req, res, parsedUrl);
          }
          else {
              res.statusCode = 501;
              res.end('Not Implemented');
          }
      }
      async sendHTML(req, res, html) {
          const { generateEtags } = this.renderOpts;
          return send_html_1.sendHTML(req, res, html, { generateEtags });
      }
      async render(req, res, pathname, query = {}, parsedUrl) {
          const url = req.url;
          if (utils_1.isInternalUrl(url)) {
              return this.handleRequest(req, res, parsedUrl);
          }
          if (utils_1.isBlockedPage(pathname)) {
              return this.render404(req, res, parsedUrl);
          }
          const html = await this.renderToHTML(req, res, pathname, query);
          // Request was ended by the user
          if (html === null) {
              return;
          }
          if (this.nextConfig.poweredByHeader) {
              res.setHeader('X-Powered-By', 'Next.js ' + "8.0.3");
          }
          return this.sendHTML(req, res, html);
      }
      async renderToAMP(req, res, pathname, query = {}, parsedUrl) {
          if (!this.nextConfig.experimental.amp) {
              throw new Error('"experimental.amp" is not enabled in "next.config.js"');
          }
          const url = req.url;
          if (utils_1.isInternalUrl(url)) {
              return this.handleRequest(req, res, parsedUrl);
          }
          if (utils_1.isBlockedPage(pathname)) {
              return this.render404(req, res, parsedUrl);
          }
          const html = await this.renderToAMPHTML(req, res, pathname, query);
          // Request was ended by the user
          if (html === null) {
              return;
          }
          if (this.nextConfig.poweredByHeader) {
              res.setHeader('X-Powered-By', 'Next.js ' + process.env.NEXT_VERSION);
          }
          return this.sendHTML(req, res, html);
      }
      async renderToHTMLWithComponents(req, res, pathname, query = {}, opts) {
          const result = await load_components_1.loadComponents(this.distDir, this.buildId, pathname);
          return render_1.renderToHTML(req, res, pathname, query, Object.assign({}, result, opts));
      }
      async renderToAMPHTML(req, res, pathname, query = {}) {
          if (!this.nextConfig.experimental.amp) {
              throw new Error('"experimental.amp" is not enabled in "next.config.js"');
          }
          return this.renderToHTML(req, res, pathname, query, { amphtml: true });
      }
      async renderToHTML(req, res, pathname, query = {}, { amphtml } = {}) {
          try {
              // To make sure the try/catch is executed
              const html = await this.renderToHTMLWithComponents(req, res, pathname, query, Object.assign({}, this.renderOpts, { amphtml }));
              return html;
          }
          catch (err) {
              if (err.code === 'ENOENT') {
                  res.statusCode = 404;
                  return this.renderErrorToHTML(null, req, res, pathname, query);
              }
              else {
                  this.logError(err);
                  res.statusCode = 500;
                  return this.renderErrorToHTML(err, req, res, pathname, query);
              }
          }
      }
      async renderError(err, req, res, pathname, query = {}) {
          res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
          const html = await this.renderErrorToHTML(err, req, res, pathname, query);
          if (html === null) {
              return;
          }
          return this.sendHTML(req, res, html);
      }
      async renderErrorToHTML(err, req, res, _pathname, query = {}) {
          return this.renderToHTMLWithComponents(req, res, '/_error', query, Object.assign({}, this.renderOpts, { err }));
      }
      async render404(req, res, parsedUrl) {
          const url = req.url;
          const { pathname, query } = parsedUrl ? parsedUrl : url_1.parse(url, true);
          if (!pathname) {
              throw new Error('pathname is undefined');
          }
          res.statusCode = 404;
          return this.renderError(null, req, res, pathname, query);
      }
      async serveStatic(req, res, path, parsedUrl) {
          if (!this.isServeableUrl(path)) {
              return this.render404(req, res, parsedUrl);
          }
          try {
              await serve_static_1.serveStatic(req, res, path);
          }
          catch (err) {
              if (err.code === 'ENOENT' || err.statusCode === 404) {
                  this.render404(req, res, parsedUrl);
              }
              else {
                  throw err;
              }
          }
      }
      isServeableUrl(path) {
          const resolved = path_1.resolve(path);
          if (resolved.indexOf(path_1.join(this.distDir) + path_1.sep) !== 0 &&
              resolved.indexOf(path_1.join(this.dir, 'static') + path_1.sep) !== 0) {
              // Seems like the user is trying to traverse the filesystem.
              return false;
          }
          return true;
      }
      readBuildId() {
          const buildIdFile = path_1.join(this.distDir, constants_1.BUILD_ID_FILE);
          try {
              return fs_1.default.readFileSync(buildIdFile, 'utf8').trim();
          }
          catch (err) {
              if (!fs_1.default.existsSync(buildIdFile)) {
                  throw new Error(`Could not find a valid build in the '${this.distDir}' directory! Try building your app with 'next build' before starting the server.`);
              }
              throw err;
          }
      }
  }
  exports.default = Server;
  