"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var server_exports = {};
__export(server_exports, {
  Client: () => Client,
  ClientsController: () => ClientsController,
  ZwavejsServer: () => ZwavejsServer,
  ZwavejsServerRemoteController: () => ZwavejsServerRemoteController
});
module.exports = __toCommonJS(server_exports);
var import_ws = require("ws");
var import_ciao = require("@homebridge/ciao");
var import_zwave_js = require("zwave-js");
var import_zwave_js2 = require("zwave-js");
var import_forward = require("./forward.js");
var import_state = require("./state.js");
var import_http = require("http");
var import_events = require("events");
var import_const = require("./const.js");
var import_message_handler = require("./node/message_handler.js");
var import_message_handler2 = require("./controller/message_handler.js");
var import_error = require("./error.js");
var import_instance = require("./instance.js");
var import_command = require("./command.js");
var import_message_handler3 = require("./driver/message_handler.js");
var import_logging = require("./logging.js");
var import_message_handler4 = require("./broadcast_node/message_handler.js");
var import_message_handler5 = require("./multicast_group/message_handler.js");
var import_message_handler6 = require("./endpoint/message_handler.js");
var import_message_handler7 = require("./utils/message_handler.js");
var import_inclusion_user_callbacks = require("./inclusion_user_callbacks.js");
var import_message_handler8 = require("./config_manager/message_handler.js");
var import_message_handler9 = require("./zniffer/message_handler.js");
var import_stringify = require("../util/stringify.js");
function getVersionData(driver) {
  return {
    homeId: driver.controller.homeId,
    driverVersion: import_zwave_js2.libVersion,
    serverVersion: import_const.version,
    minSchemaVersion: import_const.minSchemaVersion,
    maxSchemaVersion: import_const.maxSchemaVersion
  };
}
__name(getVersionData, "getVersionData");
class Client {
  static {
    __name(this, "Client");
  }
  socket;
  clientsController;
  driver;
  logger;
  remoteController;
  receiveEvents = false;
  _outstandingPing = false;
  schemaVersion = import_const.minSchemaVersion;
  receiveLogs = false;
  additionalUserAgentComponents;
  instanceHandlers;
  constructor(socket, clientsController, driver, logger, remoteController) {
    this.socket = socket;
    this.clientsController = clientsController;
    this.driver = driver;
    this.logger = logger;
    this.remoteController = remoteController;
    socket.on("pong", () => {
      this._outstandingPing = false;
    });
    socket.on("message", (data) => this.receiveMessage(data));
    this.instanceHandlers = {
      [import_instance.Instance.config_manager]: new import_message_handler8.ConfigManagerMessageHandler(this.driver),
      [import_instance.Instance.controller]: new import_message_handler2.ControllerMessageHandler(this.clientsController, this.driver, this),
      [import_instance.Instance.driver]: new import_message_handler3.DriverMessageHandler(this.remoteController, this.clientsController, this.logger, this.driver, this),
      [import_instance.Instance.node]: new import_message_handler.NodeMessageHandler(this.driver, this),
      [import_instance.Instance.multicast_group]: new import_message_handler5.MulticastGroupMessageHandler(this.driver, this),
      [import_instance.Instance.broadcast_node]: new import_message_handler4.BroadcastNodeMessageHandler(this.driver, this),
      [import_instance.Instance.endpoint]: new import_message_handler6.EndpointMessageHandler(this.driver, this),
      [import_instance.Instance.utils]: new import_message_handler7.UtilsMessageHandler(),
      [import_instance.Instance.zniffer]: new import_message_handler9.ZnifferMessageHandler(driver, clientsController)
    };
  }
  get isConnected() {
    return this.socket.readyState === this.socket.OPEN;
  }
  setSchemaVersion(schemaVersion) {
    this.schemaVersion = schemaVersion;
    if (this.schemaVersion < import_const.minSchemaVersion || this.schemaVersion > import_const.maxSchemaVersion) {
      throw new import_error.SchemaIncompatibleError(this.schemaVersion);
    }
  }
  async receiveMessage(data) {
    let msg;
    try {
      msg = JSON.parse(data);
    } catch {
      this.logger.debug(`Unable to parse data: ${data}`);
      this.socket.close();
      return;
    }
    try {
      if (msg.command === import_command.ServerCommand.initialize) {
        this.setSchemaVersion(msg.schemaVersion);
        this.additionalUserAgentComponents = msg.additionalUserAgentComponents;
        this.sendResultSuccess(msg.messageId, {});
        return;
      }
      if (msg.command === import_command.ServerCommand.setApiSchema) {
        this.setSchemaVersion(msg.schemaVersion);
        this.sendResultSuccess(msg.messageId, {});
        return;
      }
      if (msg.command === import_command.ServerCommand.startListening) {
        this.sendResultSuccess(msg.messageId, { state: (0, import_state.dumpState)(this.driver, this.schemaVersion) }, true);
        this.receiveEvents = true;
        return;
      }
      if (msg.command === import_command.ServerCommand.updateLogConfig) {
        this.driver.updateLogConfig((0, import_logging.preserveLogTransports)(this.driver, msg.config));
        this.sendResultSuccess(msg.messageId, {});
        return;
      }
      if (msg.command === import_command.ServerCommand.getLogConfig) {
        this.sendResultSuccess(msg.messageId, {
          config: (0, import_state.dumpLogConfig)(this.driver, this.schemaVersion)
        });
        return;
      }
      if (msg.command === import_command.ServerCommand.startListeningLogs) {
        this.receiveLogs = true;
        this.clientsController.configureLoggingEventForwarder(msg.filter);
        this.sendResultSuccess(msg.messageId, {});
        return;
      }
      if (msg.command === import_command.ServerCommand.stopListeningLogs) {
        this.receiveLogs = false;
        this.clientsController.cleanupLoggingEventForwarder();
        this.sendResultSuccess(msg.messageId, {});
        return;
      }
      const instance = msg.command.split(".")[0];
      if (this.instanceHandlers[instance]) {
        return this.sendResultSuccess(msg.messageId, await this.instanceHandlers[instance].handle(msg));
      }
      throw new import_error.UnknownCommandError(msg.command);
    } catch (err) {
      if (err instanceof import_error.BaseError) {
        this.logger.error("Message error", err);
        const { errorCode, name, message, stack, ...args } = err;
        return this.sendResultError(msg.messageId, errorCode, message, args);
      }
      if (err instanceof import_zwave_js.ZWaveError) {
        this.logger.error("Z-Wave error", err);
        return this.sendResultZWaveError(msg.messageId, err.code, err.message);
      }
      let error;
      if (err instanceof Error) {
        error = err;
      } else {
        error = new Error(`${err}`);
      }
      this.logger.error("Unexpected error", error);
      this.sendResultError(msg.messageId, import_error.ErrorCode.unknownError, error.stack ?? error.message, {});
    }
  }
  sendVersion() {
    this.sendData({ type: "version", ...getVersionData(this.driver) });
  }
  sendResultSuccess(messageId, result, compress = false) {
    this.sendData({ type: "result", success: true, messageId, result }, compress);
  }
  sendResultError(messageId, errorCode, message, args) {
    if (this.schemaVersion <= 31) {
      this.sendResultZWaveError(messageId, -1, `${errorCode}: ${message}`);
    } else {
      this.sendData({
        type: "result",
        success: false,
        messageId,
        errorCode,
        message,
        args
      });
    }
  }
  sendResultZWaveError(messageId, zjsErrorCode, message) {
    if (this.schemaVersion <= 31) {
      this.sendData({
        type: "result",
        success: false,
        messageId,
        errorCode: import_error.ErrorCode.zwaveError,
        zwaveErrorCode: zjsErrorCode,
        zwaveErrorMessage: message
      });
    } else {
      this.sendData({
        type: "result",
        success: false,
        messageId,
        errorCode: import_error.ErrorCode.zwaveError,
        zwaveErrorCode: zjsErrorCode,
        zwaveErrorCodeName: (0, import_zwave_js.getEnumMemberName)(import_zwave_js.ZWaveErrorCodes, zjsErrorCode),
        zwaveErrorMessage: message
      });
    }
  }
  sendEvent(event, options) {
    if (this.schemaVersion < (options?.minSchemaVersion ?? 0))
      return;
    if (this.schemaVersion > (options?.maxSchemaVersion ?? Infinity))
      return;
    this.sendData({ type: "event", event });
  }
  trySendEvent(event, options) {
    if (!this.isConnected || !this.receiveEvents)
      return;
    this.sendEvent(event, options);
  }
  sendData(data, compress = false) {
    this.socket.send(JSON.stringify(data, import_stringify.stringifyReplacer), { compress });
  }
  checkAlive() {
    if (this._outstandingPing) {
      this.disconnect();
      return;
    }
    this._outstandingPing = true;
    this.socket.ping();
  }
  disconnect() {
    this.socket.close();
  }
}
class ClientsController extends import_events.EventEmitter {
  static {
    __name(this, "ClientsController");
  }
  driver;
  logger;
  remoteController;
  clients = [];
  pingInterval;
  eventForwarder;
  cleanupScheduled = false;
  loggingEventForwarder;
  grantSecurityClassesPromise;
  validateDSKAndEnterPinPromise;
  constructor(driver, logger, remoteController) {
    super();
    this.driver = driver;
    this.logger = logger;
    this.remoteController = remoteController;
  }
  addSocket(socket) {
    this.logger.debug("New client");
    const client = new Client(socket, this, this.driver, this.logger, this.remoteController);
    socket.on("error", (error) => {
      this.logger.error("Client socket error", error);
    });
    socket.on("close", (code, reason) => {
      this.logger.info("Client disconnected");
      this.logger.debug(`Code ${code}: ${reason}`);
      this.scheduleClientCleanup();
    });
    client.sendVersion();
    this.clients.push(client);
    if (this.pingInterval === void 0) {
      this.pingInterval = setInterval(() => {
        const newClients = [];
        for (const client2 of this.clients) {
          if (client2.isConnected) {
            newClients.push(client2);
          } else {
            client2.disconnect();
          }
        }
        this.clients = newClients;
      }, 3e4);
    }
    if (this.eventForwarder === void 0) {
      this.eventForwarder = new import_forward.EventForwarder(this);
      this.eventForwarder.start();
    }
  }
  get loggingEventForwarderStarted() {
    return this.loggingEventForwarder?.started === true;
  }
  restartLoggingEventForwarderIfNeeded() {
    this.loggingEventForwarder?.restartIfNeeded();
  }
  configureLoggingEventForwarder(filter) {
    if (this.loggingEventForwarder === void 0) {
      this.loggingEventForwarder = new import_logging.LoggingEventForwarder(this, this.driver, this.logger);
    }
    if (!this.loggingEventForwarderStarted) {
      this.loggingEventForwarder?.start(filter);
    }
  }
  cleanupLoggingEventForwarder() {
    if (this.clients.filter((cl) => cl.receiveLogs).length == 0 && this.loggingEventForwarderStarted) {
      this.loggingEventForwarder?.stop();
    }
  }
  scheduleClientCleanup() {
    if (this.cleanupScheduled) {
      return;
    }
    this.cleanupScheduled = true;
    setTimeout(() => this.cleanupClients(), 0);
  }
  cleanupClients() {
    this.cleanupScheduled = false;
    this.clients = this.clients.filter((cl) => cl.isConnected);
    this.cleanupLoggingEventForwarder();
  }
  sendEventToListeningClients(event, options) {
    for (const client of this.clients) {
      if (client.isConnected && client.receiveEvents && client.schemaVersion >= (options?.minSchemaVersion ?? 0) && client.schemaVersion <= (options?.maxSchemaVersion ?? Infinity)) {
        client.sendEvent(typeof event === "function" ? event(client) : event);
      }
    }
  }
  disconnect() {
    if (this.pingInterval !== void 0) {
      clearInterval(this.pingInterval);
    }
    this.pingInterval = void 0;
    this.clients.forEach((client) => client.disconnect());
    this.clients = [];
    this.cleanupLoggingEventForwarder();
  }
}
class ZwavejsServerRemoteController {
  static {
    __name(this, "ZwavejsServerRemoteController");
  }
  driver;
  zwaveJsServer;
  constructor(driver, zwaveJsServer) {
    this.driver = driver;
    this.zwaveJsServer = zwaveJsServer;
  }
  async hardResetController() {
    await this.driver.hardReset();
    this.zwaveJsServer.emit("hard reset");
  }
}
class ZwavejsServer extends import_events.EventEmitter {
  static {
    __name(this, "ZwavejsServer");
  }
  driver;
  options;
  server;
  wsServer;
  sockets;
  logger;
  defaultPort = 3e3;
  responder;
  service;
  remoteController;
  constructor(driver, options = {}) {
    super();
    this.driver = driver;
    this.options = options;
    this.remoteController = new ZwavejsServerRemoteController(driver, this);
    this.logger = options.logger ?? console;
  }
  async start(shouldSetInclusionUserCallbacks = false) {
    if (!this.driver.ready) {
      throw new Error("Cannot start server when driver not ready");
    }
    this.driver.updateUserAgent({ [import_const.applicationName]: import_const.version });
    this.server = (0, import_http.createServer)();
    this.wsServer = new import_ws.WebSocketServer({
      server: this.server,
      perMessageDeflate: true
    });
    this.sockets = new ClientsController(this.driver, this.logger, this.remoteController);
    if (shouldSetInclusionUserCallbacks) {
      this.setInclusionUserCallbacks();
    }
    this.wsServer.on("connection", (socket) => this.sockets.addSocket(socket));
    const port = this.options.port || this.defaultPort;
    const host = this.options.host;
    const localEndpointString = `${host ?? "<all interfaces>"}:${port}`;
    this.logger.debug(`Starting server on ${localEndpointString}`);
    this.wsServer.on("error", this.onError.bind(this, this.wsServer));
    this.server.on("error", this.onError.bind(this, this.server));
    this.server.listen(port, host);
    await (0, import_events.once)(this.server, "listening");
    this.emit("listening");
    this.logger.info(`ZwaveJS server listening on ${localEndpointString}`);
    if (this.options.enableDNSServiceDiscovery) {
      this.responder = (0, import_ciao.getResponder)();
      this.service = this.responder.createService({
        name: this.driver.controller.homeId.toString(),
        port,
        type: import_const.dnssdServiceType,
        protocol: "tcp",
        txt: getVersionData(this.driver)
      });
      this.service.advertise().then(() => {
        this.logger.info(`DNS Service Discovery enabled`);
      });
    }
  }
  setInclusionUserCallbacks() {
    if (this.sockets === void 0) {
      throw new Error("Server must be started before setting the inclusion user callbacks");
    }
    this.driver.updateOptions({
      inclusionUserCallbacks: (0, import_inclusion_user_callbacks.inclusionUserCallbacks)(this.sockets)
    });
  }
  onError(sourceClass, error) {
    this.emit("error", error, sourceClass);
    this.logger.error(error);
  }
  async destroy() {
    this.logger.debug(`Closing server...`);
    if (this.sockets) {
      this.sockets.disconnect();
      this.sockets.removeAllListeners();
      delete this.sockets;
    }
    if (this.wsServer) {
      this.wsServer.close();
      await (0, import_events.once)(this.wsServer, "close");
      this.wsServer.removeAllListeners();
      delete this.wsServer;
    }
    if (this.server) {
      this.server.close();
      await (0, import_events.once)(this.server, "close");
      this.server.removeAllListeners();
      delete this.server;
    }
    if (this.service) {
      await this.service.end();
      await this.service.destroy();
      this.service.removeAllListeners();
      delete this.service;
    }
    if (this.responder) {
      await this.responder.shutdown();
      delete this.responder;
    }
    this.logger.info(`Server closed`);
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Client,
  ClientsController,
  ZwavejsServer,
  ZwavejsServerRemoteController
});
//# sourceMappingURL=server.js.map
