"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

function _ws() {
  const data = require("ws");

  _ws = function () {
    return data;
  };

  return data;
}

function _cliTools() {
  const data = require("@react-native-community/cli-tools");

  _cliTools = function () {
    return data;
  };

  return data;
}

function _prettyFormat() {
  const data = _interopRequireDefault(require("pretty-format"));

  _prettyFormat = function () {
    return data;
  };

  return data;
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/**
 * This number is used to version the communication protocol between
 * Dev tooling like Flipper and Metro, so that in the future we can recognize
 * messages coming from old clients, so that it will be simpler to implement
 * backward compatibility.
 *
 * We start at 2 as the protocol is currently the same as used internally at FB,
 * which happens to be at version 2 as well.
 */
const PROTOCOL_VERSION = 2;

function parseMessage(data) {
  try {
    const message = JSON.parse(data);

    if (message.version === PROTOCOL_VERSION) {
      return message;
    }

    _cliTools().logger.error('Received message had wrong protocol version: ' + message.version);
  } catch {
    _cliTools().logger.error('Failed to parse the message as JSON:\n' + data);
  }

  return undefined;
}
/**
 * Two types of messages will arrive in this function,
 * 1) messages generated by Metro itself (through the reporter abstraction)
 *    those are yet to be serialized, and can contain any kind of data structure
 * 2) a specific event generated by Metro is `client_log`, which describes
 *    console.* calls in the app.
 *    The arguments send to the console are pretty printed so that they can be
 *    displayed in a nicer way in dev tools
 *
 * @param message
 */


function serializeMessage(message) {
  // We do want to send Metro report messages, but their contents is not guaranteed to be serializable.
  // For some known types we will pretty print otherwise not serializable parts first:
  let toSerialize = message;

  if (message && message.error && message.error instanceof Error) {
    toSerialize = { ...message,
      error: (0, _prettyFormat().default)(message.error, {
        escapeString: true,
        highlight: true,
        maxDepth: 3,
        min: true
      })
    };
  } else if (message && message.type === 'client_log') {
    toSerialize = { ...message,
      data: message.data.map(item => typeof item === 'string' ? item : (0, _prettyFormat().default)(item, {
        escapeString: true,
        highlight: true,
        maxDepth: 3,
        min: true,
        plugins: [_prettyFormat().default.plugins.ReactElement]
      }))
    };
  }

  try {
    return JSON.stringify(toSerialize);
  } catch (e) {
    _cliTools().logger.error('Failed to serialize: ' + e);

    return null;
  }
}

/**
 * Starts the eventsSocket at the given path
 *
 * @param server
 * @param path typically: 'events/'
 * @param messageSocket: webSocket to which all connected RN apps are listening
 */
function attachToServer(server, path, messageSocket) {
  const wss = new (_ws().Server)({
    server: server,
    path: path,

    verifyClient({
      origin
    }) {
      // This exposes the full JS logs and enables issuing commands like reload
      // so let's make sure only locally running stuff can connect to it
      return origin.startsWith('http://localhost:') || origin.startsWith('file:');
    }

  });
  const clients = new Map();
  let nextClientId = 0;
  /**
   * broadCastEvent is called by reportEvent (below), which is called by the
   * default reporter of this server, to make sure that all Metro events are
   * broadcasted to all connected clients
   * (that is, all devtools such as Flipper, _not_: connected apps)
   *
   * @param message
   */

  function broadCastEvent(message) {
    if (!clients.size) {
      return;
    }

    const serialized = serializeMessage(message);

    if (!serialized) {
      return;
    }

    for (const ws of clients.values()) {
      try {
        ws.send(serialized);
      } catch (e) {
        _cliTools().logger.error(`Failed to send broadcast to client due to:\n ${e.toString()}`);
      }
    }
  }

  wss.on('connection', function (clientWs) {
    const clientId = `client#${nextClientId++}`;
    clients.set(clientId, clientWs);

    clientWs.onclose = clientWs.onerror = () => {
      clients.delete(clientId);
    };

    clientWs.onmessage = event => {
      const message = parseMessage(event.data.toString());

      if (message == null) {
        return;
      }

      if (message.type === 'command') {
        try {
          /**
           * messageSocket.broadcast (not to be confused with our own broadcast above)
           * forwards a command to all connected React Native applications.
           */
          messageSocket.broadcast(message.command, message.params);
        } catch (e) {
          _cliTools().logger.error('Failed to forward message to clients: ', e);
        }
      } else {
        _cliTools().logger.error('Unknown message type: ', message.type);
      }
    };
  });
  return {
    reportEvent: event => {
      broadCastEvent(event);
    }
  };
}

var _default = {
  attachToServer
};
exports.default = _default;

//# sourceMappingURL=eventsSocketServer.js.map