7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
12#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
14#define CPPHTTPLIB_OPENSSL_SUPPORT
24#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
27class WebSocketClient::Impl {
29 Impl() : connected_(false), should_stop_(false) {}
33 absl::Status
Connect(
const std::string& host,
int port) {
34 std::lock_guard<std::mutex> lock(mutex_);
37 return absl::AlreadyExistsError(
"Already connected");
45 std::string url = absl::StrFormat(
"ws://%s:%d", host, port);
48 client_ = std::make_unique<httplib::Client>(host, port);
49 client_->set_connection_timeout(5, 0);
50 client_->set_read_timeout(30, 0);
56 receive_thread_ = std::thread([
this]() { ReceiveLoop(); });
58 return absl::OkStatus();
60 }
catch (
const std::exception& e) {
61 return absl::UnavailableError(
62 absl::StrCat(
"Failed to connect: ", e.what()));
67 std::lock_guard<std::mutex> lock(mutex_);
75 if (receive_thread_.joinable()) {
76 receive_thread_.join();
82 absl::Status
Send(
const std::string& message) {
83 std::lock_guard<std::mutex> lock(mutex_);
86 return absl::FailedPreconditionError(
"Not connected");
92 auto res = client_->Post(
"/message", message,
"application/json");
95 return absl::UnavailableError(
"Failed to send message");
98 if (res->status != 200) {
99 return absl::InternalError(
100 absl::StrFormat(
"Server error: %d", res->status));
103 return absl::OkStatus();
105 }
catch (
const std::exception& e) {
106 return absl::InternalError(absl::StrCat(
"Send failed: ", e.what()));
111 std::lock_guard<std::mutex> lock(mutex_);
112 message_callback_ = callback;
116 std::lock_guard<std::mutex> lock(mutex_);
117 error_callback_ = callback;
121 std::lock_guard<std::mutex> lock(mutex_);
127 while (!should_stop_) {
130 std::this_thread::sleep_for(std::chrono::milliseconds(100));
135 }
catch (
const std::exception& e) {
136 if (error_callback_) {
137 error_callback_(e.what());
143 mutable std::mutex mutex_;
144 std::unique_ptr<httplib::Client> client_;
145 std::thread receive_thread_;
152 std::function<void(
const std::string&)> message_callback_;
153 std::function<void(
const std::string&)> error_callback_;
156#elif defined(__EMSCRIPTEN__)
159class WebSocketClient::Impl {
161 absl::Status
Connect(
const std::string&,
int) {
162 return absl::UnimplementedError(
163 "Use EmscriptenWebSocket for WASM WebSocket connections");
166 absl::Status
Send(
const std::string&) {
167 return absl::UnimplementedError(
168 "Use EmscriptenWebSocket for WASM WebSocket connections");
180 absl::Status
Connect(
const std::string&,
int) {
181 return absl::UnimplementedError(
"WebSocket support requires JSON library");
184 absl::Status
Send(
const std::string&) {
185 return absl::UnimplementedError(
"WebSocket support requires JSON library");
206 auto status =
impl_->Connect(host, port);
224 const std::string& session_name,
const std::string& username,
225 const std::string& rom_hash,
bool ai_enabled) {
228 return absl::FailedPreconditionError(
"Not connected to server");
231 nlohmann::json message = {{
"type",
"host_session"},
233 {{
"session_name", session_name},
234 {
"username", username},
235 {
"rom_hash", rom_hash},
236 {
"ai_enabled", ai_enabled}}}};
238 auto status =
SendRaw(message);
247 session.
host = username;
254 return absl::UnimplementedError(
"JSON support required");
259 const std::string& session_code,
const std::string& username) {
262 return absl::FailedPreconditionError(
"Not connected to server");
265 nlohmann::json message = {
266 {
"type",
"join_session"},
267 {
"payload", {{
"session_code", session_code}, {
"username", username}}}};
269 auto status =
SendRaw(message);
281 return absl::UnimplementedError(
"JSON support required");
288 return absl::FailedPreconditionError(
"Not in a session");
291 nlohmann::json message = {{
"type",
"leave_session"}, {
"payload", {}}};
293 auto status =
SendRaw(message);
297 return absl::UnimplementedError(
"JSON support required");
302 const std::string& sender) {
304 nlohmann::json msg = {
305 {
"type",
"chat_message"},
306 {
"payload", {{
"message", message}, {
"sender", sender}}}};
310 return absl::UnimplementedError(
"JSON support required");
315 const std::string& rom_hash,
316 const std::string& sender) {
318 nlohmann::json message = {
319 {
"type",
"rom_sync"},
321 {{
"diff_data", diff_data}, {
"rom_hash", rom_hash}, {
"sender", sender}}}};
325 return absl::UnimplementedError(
"JSON support required");
330 const std::string& sender) {
332 nlohmann::json message = {
333 {
"type",
"proposal_share"},
334 {
"payload", {{
"sender", sender}, {
"proposal_data", proposal_data}}}};
338 return absl::UnimplementedError(
"JSON support required");
344 const std::string& username) {
346 nlohmann::json message = {{
"type",
"proposal_vote"},
348 {{
"proposal_id", proposal_id},
349 {
"approved", approved},
350 {
"username", username}}}};
354 return absl::UnimplementedError(
"JSON support required");
359 const std::string& proposal_id,
const std::string& status) {
361 nlohmann::json message = {
362 {
"type",
"proposal_update"},
363 {
"payload", {{
"proposal_id", proposal_id}, {
"status", status}}}};
367 return absl::UnimplementedError(
"JSON support required");
386 return absl::FailedPreconditionError(
"Not in a session");
396 auto json = nlohmann::json::parse(message);
397 std::string type = json[
"type"];
401 for (
auto& callback : it->second) {
402 callback(json[
"payload"]);
405 }
catch (
const std::exception& e) {
406 HandleError(absl::StrCat(
"Failed to parse message: ", e.what()));
429 std::string msg_str = message.dump();
430 return impl_->Send(msg_str);
431 }
catch (
const std::exception& e) {
432 return absl::InternalError(absl::StrCat(
"Failed to serialize: ", e.what()));
435 return absl::UnimplementedError(
"JSON support required");
void SetErrorCallback(std::function< void(const std::string &)>)
void SetMessageCallback(std::function< void(const std::string &)>)
absl::Status Send(const std::string &)
absl::Status Connect(const std::string &, int)
void SetState(ConnectionState state)
absl::Status Connect(const std::string &host, int port=8765)
absl::StatusOr< SessionInfo > HostSession(const std::string &session_name, const std::string &username, const std::string &rom_hash, bool ai_enabled=true)
absl::Status VoteOnProposal(const std::string &proposal_id, bool approved, const std::string &username)
std::function< void(ConnectionState)> StateCallback
absl::Status UpdateProposalStatus(const std::string &proposal_id, const std::string &status)
absl::Status ShareProposal(const nlohmann::json &proposal_data, const std::string &sender)
absl::Status LeaveSession()
void OnStateChange(StateCallback callback)
absl::StatusOr< SessionInfo > GetSessionInfo() const
std::function< void(const std::string &)> ErrorCallback
std::unique_ptr< Impl > impl_
void OnMessage(const std::string &type, MessageCallback callback)
absl::Status SendChatMessage(const std::string &message, const std::string &sender)
void OnError(ErrorCallback callback)
std::function< void(const nlohmann::json &)> MessageCallback
std::vector< StateCallback > state_callbacks_
std::vector< ErrorCallback > error_callbacks_
std::map< std::string, std::vector< MessageCallback > > message_callbacks_
absl::StatusOr< SessionInfo > JoinSession(const std::string &session_code, const std::string &username)
absl::Status SendRomSync(const std::string &diff_data, const std::string &rom_hash, const std::string &sender)
SessionInfo current_session_
void HandleMessage(const std::string &message)
absl::Status SendRaw(const nlohmann::json &message)
void HandleError(const std::string &error)
ConnectionState
WebSocket connection states.
Information about the current collaboration session.