#pragma once // CDP WebSocket client minimo (RFC 6455). // // Conexion 1:1 con un target CDP (page/iframe/worker). Usa el url // `webSocketDebuggerUrl` que devuelve `/json`. Solo loopback (127.0.0.1), // asi que sin TLS y handshake simplificado. // // Modelo: // - connect(): handshake HTTP upgrade. Spawn reader thread. // - send_command(method, params_json): envia text frame {"id":N,"method":..., // "params":...}, retorna id. No bloquea. // - wait_response(id, out, ms): bloquea hasta que llega la respuesta con ese // id. Para llamadas one-shot. // - on_message_callback: se invoca por cada frame text recibido (responses // y events). El UI thread lo drena. #include #include #include #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include #endif namespace navegator { struct CdpWsConfig { std::string host = "127.0.0.1"; int port = 0; std::string path; // ej. "/devtools/page/ABC123" int timeout_ms = 5000; }; class CdpWs { public: CdpWs() = default; ~CdpWs(); CdpWs(const CdpWs&) = delete; CdpWs& operator=(const CdpWs&) = delete; // Parsea ws://host:port/path. Devuelve false si no es ws://. static bool parse_ws_url(const std::string& url, std::string& host, int& port, std::string& path); bool connect(const CdpWsConfig& cfg, std::string* err = nullptr); void close(); bool is_connected() const { return running_.load(); } // Envia un comando CDP. Devuelve el id asignado o -1 si error. int send_command(const std::string& method, const std::string& params_json = ""); // Drena la cola de mensajes recibidos. Devuelve hasta `max` y los retira. // Llamar desde UI thread cada frame. std::vector drain(size_t max = 256); // Bloquea esperando la respuesta del id dado. Devuelve false en timeout. // Solo util para llamadas sincronas (eval, getDocument, etc.) — para el // panel Network preferimos drain(). bool wait_response(int id, std::string& out_json, int timeout_ms); // Estadisticas (mostrar en UI). uint64_t bytes_in() const { return bytes_in_.load(); } uint64_t bytes_out() const { return bytes_out_.load(); } uint64_t frames_in() const { return frames_in_.load(); } std::string last_error() const { std::lock_guard lk(err_mu_); return last_err_; } private: void reader_loop(); bool send_frame_text(const std::string& payload); bool send_close_frame(); bool recv_frame(uint8_t& opcode, std::string& payload); bool send_all(const char* data, size_t len); bool recv_n(char* out, size_t n); void set_error(const std::string& e); #ifdef _WIN32 SOCKET sock_ = INVALID_SOCKET; #else int sock_ = -1; #endif std::thread reader_; std::atomic running_{false}; std::atomic stop_{false}; std::atomic next_id_{1}; std::atomic bytes_in_{0}; std::atomic bytes_out_{0}; std::atomic frames_in_{0}; std::mutex queue_mu_; std::queue queue_; std::mutex resp_mu_; std::condition_variable resp_cv_; std::unordered_map responses_; std::mutex send_mu_; // serializa envio para no entrelazar frames mutable std::mutex err_mu_; std::string last_err_; }; } // namespace navegator