/** * @file WSClient.h * @brief RFC 6455 WebSocket client (POSIX sockets, background receive thread). * * The receive thread continuously reads frames and places them in a queue. * The main (ImGui) thread drains the queue each frame and also sends frames. * * Reuses WSFrame.h / SHA1.h / Base64.h from Source/Applications/StreamHub/. */ #pragma once #include #include #include #include #include #include #include #include namespace StreamHubClient { /** * @brief A received WebSocket frame. */ struct WSMessage { bool isBinary; std::vector data; ///< Payload bytes (text frames are also stored here) }; /** * @brief Minimal RFC 6455 WebSocket client. * * Usage: * WSClient ws; * ws.connect("127.0.0.1", 8090); * // main loop: * ws.poll(handler); // drain received messages * ws.sendText(json); // send a JSON command * ws.disconnect(); */ class WSClient { public: WSClient(); ~WSClient(); /** * @brief (Re)connect to host:port in the background. * Non-blocking — sets a reconnect target; the receive thread handles the actual * connect/reconnect loop. */ void connect(const std::string& host, uint16_t port); /** @brief Gracefully close the WebSocket connection and stop the receive thread. */ void disconnect(); /** * @brief Non-blocking reconnect to a (possibly new) host:port. * Closes the current socket so the background thread detects the drop and * reconnects immediately. Does NOT join the thread — safe to call from the UI. */ void reconnect(const std::string& host, uint16_t port); /** @return true if the WebSocket handshake completed and the connection is open. */ bool isConnected() const; /** * @brief Drain all pending received messages and pass each to `handler`. * Must be called from the main thread. */ void poll(const std::function& handler); /** @brief Send a JSON text frame. Thread-safe. */ void sendText(const std::string& json); /** @brief Send a binary frame. Thread-safe. */ void sendBinary(const uint8_t* data, size_t len); private: void recvLoop(); bool doConnect(); bool doHandshake(); bool recvExact(uint8_t* buf, size_t len); bool sendRaw(const uint8_t* buf, size_t len); std::string host_; uint16_t port_ = 0; int sock_ = -1; std::atomic connected_ {false}; std::atomic stopThread_ {false}; std::atomic reconnect_ {false}; std::thread recvThread_; mutable std::mutex sendMutex_; mutable std::mutex queueMutex_; std::queue recvQueue_; }; } /* namespace StreamHubClient */