SockJSServer.js 3.17 KB
"use strict";

const sockjs = require("sockjs");
const BaseServer = require("./BaseServer");

/** @typedef {import("../Server").WebSocketServerConfiguration} WebSocketServerConfiguration */
/** @typedef {import("../Server").ClientConnection} ClientConnection */

// Workaround for sockjs@~0.3.19
// sockjs will remove Origin header, however Origin header is required for checking host.
// See https://github.com/webpack/webpack-dev-server/issues/1604 for more information
{
  // @ts-ignore
  const SockjsSession = require("sockjs/lib/transport").Session;
  const decorateConnection = SockjsSession.prototype.decorateConnection;

  /**
   * @param {import("http").IncomingMessage} req
   */
  // eslint-disable-next-line func-names
  SockjsSession.prototype.decorateConnection = function (req) {
    decorateConnection.call(this, req);

    const connection = this.connection;

    if (
      connection.headers &&
      !("origin" in connection.headers) &&
      "origin" in req.headers
    ) {
      connection.headers.origin = req.headers.origin;
    }
  };
}

module.exports = class SockJSServer extends BaseServer {
  // options has: error (function), debug (function), server (http/s server), path (string)
  /**
   * @param {import("../Server")} server
   */
  constructor(server) {
    super(server);

    this.implementation = sockjs.createServer({
      // Use provided up-to-date sockjs-client
      sockjs_url: "/__webpack_dev_server__/sockjs.bundle.js",
      // Default logger is very annoy. Limit useless logs.
      /**
       * @param {string} severity
       * @param {string} line
       */
      log: (severity, line) => {
        if (severity === "error") {
          this.server.logger.error(line);
        } else if (severity === "info") {
          this.server.logger.log(line);
        } else {
          this.server.logger.debug(line);
        }
      },
    });

    /**
     * @param {import("sockjs").ServerOptions & { path?: string }} options
     * @returns {string | undefined}
     */
    const getPrefix = (options) => {
      if (typeof options.prefix !== "undefined") {
        return options.prefix;
      }

      return options.path;
    };

    const options = {
      .../** @type {WebSocketServerConfiguration} */
      (this.server.options.webSocketServer).options,
      prefix: getPrefix(
        /** @type {NonNullable<WebSocketServerConfiguration["options"]>} */
        (
          /** @type {WebSocketServerConfiguration} */
          (this.server.options.webSocketServer).options
        )
      ),
    };

    this.implementation.installHandlers(
      /** @type {import("http").Server} */ (this.server.server),
      options
    );

    this.implementation.on("connection", (client) => {
      // @ts-ignore
      // Implement the the same API as for `ws`
      client.send = client.write;
      // @ts-ignore
      client.terminate = client.close;

      this.clients.push(/** @type {ClientConnection} */ (client));

      client.on("close", () => {
        this.clients.splice(
          this.clients.indexOf(/** @type {ClientConnection} */ (client)),
          1
        );
      });
    });

    // @ts-ignore
    this.implementation.close = (callback) => {
      callback();
    };
  }
};