"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.UIServer = void 0;
function _express() {
  const data = _interopRequireDefault(require("express"));
  _express = function () {
    return data;
  };
  return data;
}
function _expressHistoryApiFallback() {
  const data = _interopRequireDefault(require("express-history-api-fallback"));
  _expressHistoryApiFallback = function () {
    return data;
  };
  return data;
}
function _toolboxNetwork() {
  const data = require("@teambit/toolbox.network.get-port");
  _toolboxNetwork = function () {
    return data;
  };
  return data;
}
function _toolboxString() {
  const data = require("@teambit/toolbox.string.strip-trailing-char");
  _toolboxString = function () {
    return data;
  };
  return data;
}
function _httpProxy() {
  const data = _interopRequireDefault(require("http-proxy"));
  _httpProxy = function () {
    return data;
  };
  return data;
}
function _path() {
  const data = require("path");
  _path = function () {
    return data;
  };
  return data;
}
function _core() {
  const data = require("@rspack/core");
  _core = function () {
    return data;
  };
  return data;
}
function _devServer() {
  const data = require("@rspack/dev-server");
  _devServer = function () {
    return data;
  };
  return data;
}
function _ssrMiddleware() {
  const data = require("./ssr-middleware");
  _ssrMiddleware = function () {
    return data;
  };
  return data;
}
function _ui() {
  const data = require("./ui.aspect");
  _ui = function () {
    return data;
  };
  return data;
}
function _rspackDev() {
  const data = require("./rspack/rspack.dev.config");
  _rspackDev = function () {
    return data;
  };
  return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class UIServer {
  constructor(graphql, expressExtension, ui, uiRoot, uiRootExtension, logger, publicDir, plugins) {
    this.graphql = graphql;
    this.expressExtension = expressExtension;
    this.ui = ui;
    this.uiRoot = uiRoot;
    this.uiRootExtension = uiRootExtension;
    this.logger = logger;
    this.publicDir = publicDir;
    this.plugins = plugins;
    _defineProperty(this, "_app", void 0);
    _defineProperty(this, "_server", void 0);
    _defineProperty(this, "_proxyRoutes", new Set());
    _defineProperty(this, "_port", 0);
    _defineProperty(this, "setReady", void 0);
    _defineProperty(this, "startPromise", new Promise(resolve => this.setReady = resolve));
  }
  getName() {
    return this.uiRoot.name;
  }
  get port() {
    return this._port;
  }

  /** the hostname for the server to listen at. Currently statically 'localhost' */
  get host() {
    return 'localhost';
  }

  /** the server listens at this url */
  get fullUrl() {
    const port = this.port !== 80 ? `:${this.port}` : '';
    return `http://${this.host}${port}`;
  }
  get buildOptions() {
    return this.uiRoot.buildOptions;
  }

  /**
   * get the webpack configuration of the UI server.
   */
  async getDevConfig() {
    const aspects = await this.uiRoot.resolveAspects(_ui().UIRuntime.name);
    return (0, _rspackDev().devConfig)(this.uiRoot.path, [await this.ui.generateRoot(aspects, this.uiRootExtension)], this.uiRoot.name);
  }
  get whenReady() {
    return Promise.all([this.startPromise, ...this.plugins.map(x => x?.whenReady)]);
  }
  addComponentServerProxy(server) {
    const envId = server.context.envRuntime.id;
    const previewRoute = `/preview/${envId}`;
    const hmrRoute = `/_hmr/${envId}`;
    const entries = [{
      context: [previewRoute],
      target: `http://${this.host}:${server.port}`
    }, {
      context: [hmrRoute],
      target: `ws://${this.host}:${server.port}`,
      ws: true
    }];
    if (this._proxyRoutes.has(previewRoute) || this._proxyRoutes.has(hmrRoute)) {
      this.logger.debug(`Routes for environment ${envId} already exist, skipping`);
      return;
    }
    try {
      const dynamicProxy = _httpProxy().default.createProxyServer();
      dynamicProxy.on('error', e => {
        this.logger.error(e.message);
      });

      // Cache JS/CSS assets in the browser for 120s so subsequent preview iframes
      // reuse the same bundle without re-downloading from the component dev server.
      dynamicProxy.on('proxyRes', (proxyRes, req) => {
        const url = req.url || '';
        if (/\.(js|css)(\?.*)?$/.test(url)) {
          proxyRes.headers['cache-control'] = 'public, max-age=120';
        }
      });
      const wsHandler = (req, socket, head) => {
        try {
          const reqUrl = req.url?.replace(/\?.+$/, '') || '';
          const path = (0, _toolboxString().stripTrailingChar)(reqUrl, '/');
          const entry = entries.find(proxy => proxy.ws && proxy.context.some(item => {
            const itemPath = (0, _toolboxString().stripTrailingChar)(item, '/');
            return path === itemPath || path.startsWith(itemPath);
          }));
          if (!entry) {
            return;
          }
          dynamicProxy.ws(req, socket, head, {
            target: entry.target
          });
        } catch (err) {
          this.logger.error(`WebSocket handling error for ${envId}: ${err.message}`);
        }
      };
      try {
        this._server.on('upgrade', wsHandler);
      } catch (err) {
        this.logger.error(`Failed to register WebSocket handler for ${envId}: ${err.message}`);
      }
      const router = _express().default.Router();
      router.use((req, res) => {
        try {
          const originalUrl = req.originalUrl;
          this.logger.debug(`Proxying request to ${envId}: ${originalUrl}`);
          req.url = originalUrl;
          dynamicProxy.web(req, res, {
            target: entries[0].target
          });
        } catch (err) {
          this.logger.error(`Error in component router for ${envId}: ${err.message}`);
          if (!res.headersSent) {
            res.status(502).send(`Component server proxy error: ${err.message}`);
          }
        }
      });
      entries.forEach(entry => {
        entry.context.forEach(route => {
          try {
            this.logger.debug(`Setting up dynamic HTTP route: ${route}/* -> ${entry.target}`);
            this._proxyRoutes.add(route);
            this._app.use(route, router);

            // Move it to the beginning of the stack for priority
            try {
              const stack = this._app._router.stack;
              const lastMiddleware = stack.pop();
              if (lastMiddleware) {
                stack.unshift(lastMiddleware);
              }
            } catch (stackErr) {
              this.logger.error(`Error manipulating middleware stack: ${stackErr.message}`);
            }
          } catch (routeErr) {
            this.logger.error(`Error setting up route ${route}: ${routeErr.message}`);
          }
        });
      });
    } catch (err) {
      this.logger.error(`Failed to set up component proxy for ${envId}: ${err.message}`);
    }
  }
  async configureProxy(app, server) {
    const proxyServer = _httpProxy().default.createProxyServer();
    proxyServer.on('error', e => {
      this.logger.error(e.message);
    });
    const proxyEntries = this.getProxyFromPlugins();
    server.on('upgrade', (req, socket, head) => {
      const reqUrl = req.url?.replace(/\?.+$/, '') || '';
      const path = (0, _toolboxString().stripTrailingChar)(reqUrl, '/');
      const entry = proxyEntries.find(proxy => proxy.context.some(item => item === (0, _toolboxString().stripTrailingChar)(path, '/')));
      if (!entry) {
        return;
      }
      proxyServer.ws(req, socket, head, {
        target: entry.target
      });
    });
    proxyEntries.forEach(entry => {
      entry.context.forEach(route => {
        this._proxyRoutes.add(route);
        app.use(`${route}/*`, (req, res) => {
          req.url = req.originalUrl;
          proxyServer.web(req, res, entry);
        });
      });
    });
    this._app = app;
    this._server = server;
  }
  /**
   * start a UI server.
   */
  async start({
    bundleUiRoot,
    portRange
  } = {}) {
    const app = this.expressExtension.createApp();
    const publicDir = `/${this.publicDir}`;
    const defaultRoot = (0, _path().join)(this.uiRoot.path, publicDir);
    const root = bundleUiRoot || defaultRoot;
    this.logger.debug(`UiServer, start from ${root}`);
    const server = await this.graphql.createServer({
      app
    });
    await this.configureProxy(app, server);
    app.use(_express().default.static(root, {
      index: false
    }));
    const port = await _toolboxNetwork().Port.getPortFromRange(portRange || [3100, 3200]);
    await this.setupServerSideRendering({
      root,
      port,
      app
    });
    app.use((0, _expressHistoryApiFallback().default)('index.html', {
      root
    }));
    server.listen(port);
    this._port = port;

    // important: we use the string of the following message for the http.e2e.ts. if you change the message,
    // please make sure you change the `HTTP_SERVER_READY_MSG` const.
    const readyMessage = `UI server of ${this.uiRootExtension} is listening to port ${port}`;
    this.logger.info(readyMessage);
    this.setReady();
  }
  async setupServerSideRendering({
    root,
    port,
    app
  }) {
    if (!this.buildOptions?.ssr) return;
    const ssrMiddleware = await (0, _ssrMiddleware().createSsrMiddleware)({
      root,
      port,
      title: this.uiRoot.name,
      logger: this.logger
    });
    if (!ssrMiddleware) {
      this.logger.warn('[ssr] middleware failed setup');
      return;
    }

    // eslint-disable-next-line @typescript-eslint/no-misused-promises
    app.get('*', ssrMiddleware);
    this.logger.debug('[ssr] serving for "*"');
  }

  /**
   * start a UI dev server.
   */
  async dev({
    portRange
  } = {}) {
    const devServerPort = await this.selectPort(portRange);
    await this.start({
      portRange: [4100, 4200]
    });
    const expressAppPort = this._port;
    const config = await this.getDevConfig();
    const compiler = (0, _core().rspack)(config);
    const devServerConfig = await this.getDevServerConfig(devServerPort, expressAppPort, config.devServer);
    const devServer = new (_devServer().RspackDevServer)(devServerConfig, compiler);
    await devServer.start();
    this._port = devServerPort;
    return devServer;
  }
  async selectPort(portRange) {
    return _toolboxNetwork().Port.getPortFromRange(portRange || [3100, 3200]);
  }
  getProxyFromPlugins() {
    const proxiesByPlugin = this.plugins.flatMap(plugin => {
      return plugin.getProxy ? plugin.getProxy() : [];
    });
    return proxiesByPlugin;
  }
  async getProxy(port = 4000) {
    const proxyEntries = this.getProxyFromPlugins();
    const catchAllProxies = [{
      context: ['/preview'],
      target: `http://${this.host}:${port}`,
      changeOrigin: true
    }];
    const gqlProxies = [{
      context: ['/graphql', '/api'],
      target: `http://${this.host}:${port}`,
      changeOrigin: true
    }, {
      context: ['/subscriptions'],
      target: `ws://${this.host}:${port}`,
      ws: true
    }];
    return gqlProxies.concat(proxyEntries).concat(catchAllProxies);
  }
  async getDevServerConfig(appPort, gqlPort, config) {
    const proxy = await this.getProxy(gqlPort);
    const devServerConf = _objectSpread(_objectSpread({}, config), {}, {
      proxy: proxy,
      port: appPort
    });
    return devServerConf;
  }
  static create(props) {
    return new UIServer(props.graphql, props.express, props.ui, props.uiRoot, props.uiRootExtension, props.logger, props.publicDir, props.startPlugins);
  }
}
exports.UIServer = UIServer;

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