websocket_server.hpp 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #pragma once
  2. #include <string>
  3. #include <memory>
  4. #include <thread>
  5. #include <atomic>
  6. #include <unordered_map>
  7. #include <unordered_set>
  8. #include <shared_mutex>
  9. #include <functional>
  10. #include <libwebsockets.h>
  11. #include <nlohmann/json.hpp>
  12. #include "auth/jwt_utils.hpp"
  13. namespace smartbotic::webserver {
  14. // WebSocket client connection
  15. struct WebSocketClient {
  16. struct lws* wsi = nullptr;
  17. std::string id;
  18. std::string user_id;
  19. std::string username;
  20. bool authenticated = false;
  21. std::unordered_set<std::string> subscriptions;
  22. std::vector<std::string> send_queue;
  23. };
  24. // WebSocket message handler
  25. using WebSocketMessageHandler = std::function<void(
  26. const std::string& client_id,
  27. const nlohmann::json& message
  28. )>;
  29. struct WebSocketServerConfig {
  30. int port = 8080; // Same port as HTTP (uses vhost)
  31. std::string path = "/ws";
  32. int max_payload_size = 1024 * 1024; // 1MB
  33. };
  34. class WebSocketServer {
  35. public:
  36. WebSocketServer(const WebSocketServerConfig& config, auth::JwtUtils& jwt);
  37. ~WebSocketServer();
  38. // Lifecycle
  39. void start();
  40. void stop();
  41. // Broadcasting
  42. void broadcast(const std::string& channel, const nlohmann::json& data);
  43. void sendToClient(const std::string& client_id, const nlohmann::json& message);
  44. void sendToUser(const std::string& user_id, const nlohmann::json& message);
  45. // Subscription management
  46. void subscribe(const std::string& client_id, const std::string& channel);
  47. void unsubscribe(const std::string& client_id, const std::string& channel);
  48. // Who currently has a workflow open. Derived from the subscriptions rather
  49. // than stored separately, so a client that drops off never leaves a ghost
  50. // behind - there is no second list that can disagree with the connections.
  51. void publishPresence(const std::string& workflow_id);
  52. static std::string presenceWorkflowId(const std::string& channel);
  53. static std::string presenceChannel(const std::string& workflow_id);
  54. // Message handler
  55. void setMessageHandler(WebSocketMessageHandler handler);
  56. // Stats
  57. size_t getConnectionCount() const;
  58. // LWS callbacks (public for protocol access)
  59. int onConnect(struct lws* wsi);
  60. void onDisconnect(struct lws* wsi);
  61. int onReceive(struct lws* wsi, const char* data, size_t len);
  62. int onWritable(struct lws* wsi);
  63. private:
  64. void serviceLoop();
  65. void processMessage(WebSocketClient& client, const nlohmann::json& message);
  66. void handleAuth(WebSocketClient& client, const nlohmann::json& message);
  67. void handleSubscribe(WebSocketClient& client, const nlohmann::json& message);
  68. void handleUnsubscribe(WebSocketClient& client, const nlohmann::json& message);
  69. bool matchesChannel(const std::string& subscription, const std::string& channel);
  70. WebSocketServerConfig config_;
  71. auth::JwtUtils& jwt_;
  72. struct lws_context* context_ = nullptr;
  73. std::thread service_thread_;
  74. std::atomic<bool> running_{false};
  75. std::unordered_map<struct lws*, std::unique_ptr<WebSocketClient>> clients_;
  76. std::unordered_map<std::string, struct lws*> client_id_map_;
  77. mutable std::shared_mutex clients_mutex_;
  78. // Track clients needing writable callback (for cross-thread signaling)
  79. std::unordered_set<struct lws*> pending_writable_;
  80. std::mutex pending_mutex_;
  81. WebSocketMessageHandler message_handler_;
  82. };
  83. } // namespace smartbotic::webserver