/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

import express, {Express} from 'express';
import http from 'http';
import path from 'path';
import fs from 'fs-extra';
import socketio from 'socket.io';
import {WEBSOCKET_MAX_MESSAGE_SIZE} from 'flipper-server-core';

type Config = {
  port: number;
  staticDir: string;
  entry: string;
};

export async function startBaseServer(config: Config): Promise<{
  app: Express;
  server: http.Server;
  socket: socketio.Server;
}> {
  const {app, server} = await startAssetServer(config);
  const socket = addWebsocket(server, config);
  return {
    app,
    server,
    socket,
  };
}

function startAssetServer(
  config: Config,
): Promise<{app: Express; server: http.Server}> {
  const app = express();

  app.use((_req, res, next) => {
    res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
    res.header('Expires', '-1');
    res.header('Pragma', 'no-cache');
    next();
  });

  app.get('/', (_req, res) => {
    fs.readFile(path.join(config.staticDir, config.entry), (_err, content) => {
      res.end(content);
    });
  });

  app.use(express.static(config.staticDir));

  const server = http.createServer(app);

  return new Promise((resolve) => {
    server.listen(config.port, 'localhost', () => resolve({app, server}));
  });
}

function addWebsocket(server: http.Server, config: Config) {
  const validHost = `localhost:${config.port}`;
  const io = new socketio.Server(server, {
    maxHttpBufferSize: WEBSOCKET_MAX_MESSAGE_SIZE,
    allowRequest(req, callback) {
      const noOriginHeader = req.headers.origin === undefined;
      if (noOriginHeader && req.headers.host === validHost) {
        // no origin header? Either the request is not cross-origin,
        // or the request is not originating from a browser, so should be OK to pass through
        callback(null, true);
      } else {
        // for now we don't allow cross origin request, so that an arbitrary website cannot try to
        // connect a socket to localhost:serverport, and try to use the all powerful Flipper APIs to read
        // for example files.
        // Potentially in the future we do want to allow this, e.g. if we want to connect to a local flipper-server
        // directly from intern. But before that, we should either authenticate the request somehow,
        // and discuss security impact and for example scope the files that can be read by Flipper.
        console.warn(
          `Refused sockect connection from cross domain request, origin: ${req.headers.origin}, host: ${req.headers.host}. Expected: ${validHost}`,
        );
        callback(null, false);
      }
    },
  });
  return io;
}
