yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
websocket_client.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <mutex>
5#include <thread>
6
7#include "absl/strings/str_cat.h"
8#include "absl/strings/str_format.h"
9
10// Cross-platform WebSocket support using httplib
11#ifdef YAZE_WITH_JSON
12#define CPPHTTPLIB_OPENSSL_SUPPORT
13#include "httplib.h"
14#endif
15
16namespace yaze {
17
18namespace net {
19
20#ifdef YAZE_WITH_JSON
21
22// Platform-independent WebSocket implementation using httplib
23class WebSocketClient::Impl {
24 public:
25 Impl() : connected_(false), should_stop_(false) {}
26
27 ~Impl() {
28 Disconnect();
29 }
30
31 absl::Status Connect(const std::string& host, int port) {
32 std::lock_guard<std::mutex> lock(mutex_);
33
34 if (connected_) {
35 return absl::AlreadyExistsError("Already connected");
36 }
37
38 host_ = host;
39 port_ = port;
40
41 try {
42 // httplib WebSocket connection (cross-platform)
43 std::string url = absl::StrFormat("ws://%s:%d", host, port);
44
45 // Create WebSocket connection
46 client_ = std::make_unique<httplib::Client>(host, port);
47 client_->set_connection_timeout(5, 0); // 5 seconds
48 client_->set_read_timeout(30, 0); // 30 seconds
49
50 connected_ = true;
51 should_stop_ = false;
52
53 // Start receive thread
54 receive_thread_ = std::thread([this]() { ReceiveLoop(); });
55
56 return absl::OkStatus();
57
58 } catch (const std::exception& e) {
59 return absl::UnavailableError(
60 absl::StrCat("Failed to connect: ", e.what()));
61 }
62 }
63
64 void Disconnect() {
65 std::lock_guard<std::mutex> lock(mutex_);
66
67 if (!connected_) return;
68
69 should_stop_ = true;
70 connected_ = false;
71
72 if (receive_thread_.joinable()) {
73 receive_thread_.join();
74 }
75
76 client_.reset();
77 }
78
79 absl::Status Send(const std::string& message) {
80 std::lock_guard<std::mutex> lock(mutex_);
81
82 if (!connected_) {
83 return absl::FailedPreconditionError("Not connected");
84 }
85
86 try {
87 // In a real implementation, this would use WebSocket send
88 // For now, we'll use HTTP POST as fallback
89 auto res = client_->Post("/message", message, "application/json");
90
91 if (!res) {
92 return absl::UnavailableError("Failed to send message");
93 }
94
95 if (res->status != 200) {
96 return absl::InternalError(
97 absl::StrFormat("Server error: %d", res->status));
98 }
99
100 return absl::OkStatus();
101
102 } catch (const std::exception& e) {
103 return absl::InternalError(absl::StrCat("Send failed: ", e.what()));
104 }
105 }
106
107 void SetMessageCallback(std::function<void(const std::string&)> callback) {
108 std::lock_guard<std::mutex> lock(mutex_);
109 message_callback_ = callback;
110 }
111
112 void SetErrorCallback(std::function<void(const std::string&)> callback) {
113 std::lock_guard<std::mutex> lock(mutex_);
114 error_callback_ = callback;
115 }
116
117 bool IsConnected() const {
118 std::lock_guard<std::mutex> lock(mutex_);
119 return connected_;
120 }
121
122 private:
123 void ReceiveLoop() {
124 while (!should_stop_) {
125 try {
126 // Poll for messages (platform-independent)
127 std::this_thread::sleep_for(std::chrono::milliseconds(100));
128
129 // In a real WebSocket implementation, this would receive messages
130 // For now, this is a placeholder for the receive loop
131
132 } catch (const std::exception& e) {
133 if (error_callback_) {
134 error_callback_(e.what());
135 }
136 }
137 }
138 }
139
140 mutable std::mutex mutex_;
141 std::unique_ptr<httplib::Client> client_;
142 std::thread receive_thread_;
143
144 std::string host_;
145 int port_;
146 bool connected_;
147 bool should_stop_;
148
149 std::function<void(const std::string&)> message_callback_;
150 std::function<void(const std::string&)> error_callback_;
151};
152
153#else
154
155// Stub implementation when JSON is not available
157 public:
158 absl::Status Connect(const std::string&, int) {
159 return absl::UnimplementedError("WebSocket support requires JSON library");
160 }
161 void Disconnect() {}
162 absl::Status Send(const std::string&) {
163 return absl::UnimplementedError("WebSocket support requires JSON library");
164 }
165 void SetMessageCallback(std::function<void(const std::string&)>) {}
166 void SetErrorCallback(std::function<void(const std::string&)>) {}
167 bool IsConnected() const { return false; }
168};
169
170#endif // YAZE_WITH_JSON
171
172// ============================================================================
173// WebSocketClient Implementation
174// ============================================================================
175
177 : impl_(std::make_unique<Impl>()),
179}
180
184
185absl::Status WebSocketClient::Connect(const std::string& host, int port) {
186 auto status = impl_->Connect(host, port);
187
188 if (status.ok()) {
190 } else {
192 }
193
194 return status;
195}
196
202
203absl::StatusOr<SessionInfo> WebSocketClient::HostSession(
204 const std::string& session_name,
205 const std::string& username,
206 const std::string& rom_hash,
207 bool ai_enabled) {
208
209#ifdef YAZE_WITH_JSON
210 if (!IsConnected()) {
211 return absl::FailedPreconditionError("Not connected to server");
212 }
213
214 nlohmann::json message = {
215 {"type", "host_session"},
216 {"payload", {
217 {"session_name", session_name},
218 {"username", username},
219 {"rom_hash", rom_hash},
220 {"ai_enabled", ai_enabled}
221 }}
222 };
223
224 auto status = SendRaw(message);
225 if (!status.ok()) {
226 return status;
227 }
228
229 // In a real implementation, we'd wait for the server response
230 // For now, return a placeholder
231 SessionInfo session;
232 session.session_name = session_name;
233 session.host = username;
234 session.rom_hash = rom_hash;
235 session.ai_enabled = ai_enabled;
236
237 current_session_ = session;
238 return session;
239#else
240 return absl::UnimplementedError("JSON support required");
241#endif
242}
243
244absl::StatusOr<SessionInfo> WebSocketClient::JoinSession(
245 const std::string& session_code,
246 const std::string& username) {
247
248#ifdef YAZE_WITH_JSON
249 if (!IsConnected()) {
250 return absl::FailedPreconditionError("Not connected to server");
251 }
252
253 nlohmann::json message = {
254 {"type", "join_session"},
255 {"payload", {
256 {"session_code", session_code},
257 {"username", username}
258 }}
259 };
260
261 auto status = SendRaw(message);
262 if (!status.ok()) {
263 return status;
264 }
265
266 // Placeholder - would wait for server response
267 SessionInfo session;
268 session.session_code = session_code;
269
270 current_session_ = session;
271 return session;
272#else
273 return absl::UnimplementedError("JSON support required");
274#endif
275}
276
278#ifdef YAZE_WITH_JSON
279 if (!InSession()) {
280 return absl::FailedPreconditionError("Not in a session");
281 }
282
283 nlohmann::json message = {
284 {"type", "leave_session"},
285 {"payload", {}}
286 };
287
288 auto status = SendRaw(message);
290 return status;
291#else
292 return absl::UnimplementedError("JSON support required");
293#endif
294}
295
297 const std::string& message,
298 const std::string& sender) {
299
300#ifdef YAZE_WITH_JSON
301 nlohmann::json msg = {
302 {"type", "chat_message"},
303 {"payload", {
304 {"message", message},
305 {"sender", sender}
306 }}
307 };
308
309 return SendRaw(msg);
310#else
311 return absl::UnimplementedError("JSON support required");
312#endif
313}
314
316 const std::string& diff_data,
317 const std::string& rom_hash,
318 const std::string& sender) {
319
320#ifdef YAZE_WITH_JSON
321 nlohmann::json message = {
322 {"type", "rom_sync"},
323 {"payload", {
324 {"diff_data", diff_data},
325 {"rom_hash", rom_hash},
326 {"sender", sender}
327 }}
328 };
329
330 return SendRaw(message);
331#else
332 return absl::UnimplementedError("JSON support required");
333#endif
334}
335
337 const nlohmann::json& proposal_data,
338 const std::string& sender) {
339
340#ifdef YAZE_WITH_JSON
341 nlohmann::json message = {
342 {"type", "proposal_share"},
343 {"payload", {
344 {"sender", sender},
345 {"proposal_data", proposal_data}
346 }}
347 };
348
349 return SendRaw(message);
350#else
351 return absl::UnimplementedError("JSON support required");
352#endif
353}
354
356 const std::string& proposal_id,
357 bool approved,
358 const std::string& username) {
359
360#ifdef YAZE_WITH_JSON
361 nlohmann::json message = {
362 {"type", "proposal_vote"},
363 {"payload", {
364 {"proposal_id", proposal_id},
365 {"approved", approved},
366 {"username", username}
367 }}
368 };
369
370 return SendRaw(message);
371#else
372 return absl::UnimplementedError("JSON support required");
373#endif
374}
375
377 const std::string& proposal_id,
378 const std::string& status) {
379
380#ifdef YAZE_WITH_JSON
381 nlohmann::json message = {
382 {"type", "proposal_update"},
383 {"payload", {
384 {"proposal_id", proposal_id},
385 {"status", status}
386 }}
387 };
388
389 return SendRaw(message);
390#else
391 return absl::UnimplementedError("JSON support required");
392#endif
393}
394
395void WebSocketClient::OnMessage(const std::string& type, MessageCallback callback) {
396 message_callbacks_[type].push_back(callback);
397}
398
400 error_callbacks_.push_back(callback);
401}
402
404 state_callbacks_.push_back(callback);
405}
406
407absl::StatusOr<SessionInfo> WebSocketClient::GetSessionInfo() const {
408 if (!InSession()) {
409 return absl::FailedPreconditionError("Not in a session");
410 }
411 return current_session_;
412}
413
414// Private methods
415
416void WebSocketClient::HandleMessage(const std::string& message) {
417#ifdef YAZE_WITH_JSON
418 try {
419 auto json = nlohmann::json::parse(message);
420 std::string type = json["type"];
421
422 auto it = message_callbacks_.find(type);
423 if (it != message_callbacks_.end()) {
424 for (auto& callback : it->second) {
425 callback(json["payload"]);
426 }
427 }
428 } catch (const std::exception& e) {
429 HandleError(absl::StrCat("Failed to parse message: ", e.what()));
430 }
431#endif
432}
433
434void WebSocketClient::HandleError(const std::string& error) {
435 for (auto& callback : error_callbacks_) {
436 callback(error);
437 }
438}
439
441 if (state_ != state) {
442 state_ = state;
443 for (auto& callback : state_callbacks_) {
444 callback(state);
445 }
446 }
447}
448
449absl::Status WebSocketClient::SendRaw(const nlohmann::json& message) {
450#ifdef YAZE_WITH_JSON
451 try {
452 std::string msg_str = message.dump();
453 return impl_->Send(msg_str);
454 } catch (const std::exception& e) {
455 return absl::InternalError(absl::StrCat("Failed to serialize: ", e.what()));
456 }
457#else
458 return absl::UnimplementedError("JSON support required");
459#endif
460}
461
462} // namespace net
463
464} // namespace yaze
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)
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)
void HandleMessage(const std::string &message)
absl::Status SendRaw(const nlohmann::json &message)
void HandleError(const std::string &error)
ConnectionState
WebSocket connection states.
Main namespace for the application.
Definition controller.cc:20
Information about the current collaboration session.