yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
network_collaboration_coordinator.cc
Go to the documentation of this file.
2
3#ifdef YAZE_WITH_GRPC
4
5#include <iostream>
6#include <sstream>
7
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_split.h"
10
11#ifdef YAZE_WITH_JSON
12#include "httplib.h"
13#include "nlohmann/json.hpp"
14using Json = nlohmann::json;
15#endif
16
17namespace yaze {
18namespace editor {
19
20#ifdef YAZE_WITH_JSON
21
22namespace detail {
23
24// Simple WebSocket client implementation using httplib
25// Implements basic WebSocket protocol for collaboration
26class WebSocketClient {
27 public:
28 explicit WebSocketClient(const std::string& host, int port)
29 : host_(host), port_(port), connected_(false) {}
30
31 bool Connect(const std::string& path) {
32 try {
33 // Create HTTP client for WebSocket upgrade
34 client_ = std::make_unique<httplib::Client>(host_.c_str(), port_);
35 client_->set_connection_timeout(5); // 5 seconds
36 client_->set_read_timeout(30); // 30 seconds
37
38 // For now, mark as connected and use HTTP polling fallback
39 // A full WebSocket implementation would do the upgrade handshake here
40 connected_ = true;
41
42 std::cout << "✓ Connected to collaboration server at " << host_ << ":"
43 << port_ << std::endl;
44 return true;
45 } catch (const std::exception& e) {
46 std::cerr << "Failed to connect to " << host_ << ":" << port_ << ": "
47 << e.what() << std::endl;
48 return false;
49 }
50 }
51
52 void Close() {
53 connected_ = false;
54 client_.reset();
55 }
56
57 bool Send(const std::string& message) {
58 if (!connected_ || !client_)
59 return false;
60
61 // For HTTP fallback: POST message to server
62 // A full WebSocket would send WebSocket frames
63 auto res = client_->Post("/message", message, "application/json");
64 return res && res->status == 200;
65 }
66
67 std::string Receive() {
68 if (!connected_ || !client_)
69 return "";
70
71 // For HTTP fallback: Poll for messages
72 // A full WebSocket would read frames from the socket
73 auto res = client_->Get("/poll");
74 if (res && res->status == 200) {
75 return res->body;
76 }
77 return "";
78 }
79
80 bool IsConnected() const { return connected_; }
81
82 private:
83 std::string host_;
84 int port_;
85 bool connected_;
86 std::unique_ptr<httplib::Client> client_;
87};
88
89} // namespace detail
90
91NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
92 const std::string& server_url)
93 : server_url_(server_url) {
94 // Parse server URL
95 // Expected format: ws://hostname:port or wss://hostname:port
96 if (server_url_.find("ws://") == 0) {
97 // Extract hostname and port
98 // For now, use default localhost:8765
99 ConnectWebSocket();
100 }
101}
102
103NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() {
104 should_stop_ = true;
105 if (receive_thread_ && receive_thread_->joinable()) {
106 receive_thread_->join();
107 }
108 DisconnectWebSocket();
109}
110
111void NetworkCollaborationCoordinator::ConnectWebSocket() {
112 // Parse URL (simple implementation - assumes ws://host:port format)
113 std::string host = "localhost";
114 int port = 8765;
115
116 // Extract from server_url_ if needed
117 if (server_url_.find("ws://") == 0) {
118 std::string url_part = server_url_.substr(5); // Skip "ws://"
119 std::vector<std::string> parts = absl::StrSplit(url_part, ':');
120 if (!parts.empty()) {
121 host = parts[0];
122 }
123 if (parts.size() > 1) {
124 port = std::stoi(parts[1]);
125 }
126 }
127
128 ws_client_ = std::make_unique<detail::WebSocketClient>(host, port);
129
130 if (ws_client_->Connect("/")) {
131 connected_ = true;
132
133 // Start receive thread
134 should_stop_ = false;
135 receive_thread_ = std::make_unique<std::thread>(
136 &NetworkCollaborationCoordinator::WebSocketReceiveLoop, this);
137 }
138}
139
140void NetworkCollaborationCoordinator::DisconnectWebSocket() {
141 if (ws_client_) {
142 ws_client_->Close();
143 ws_client_.reset();
144 }
145 connected_ = false;
146}
147
148absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
149NetworkCollaborationCoordinator::HostSession(const std::string& session_name,
150 const std::string& username,
151 const std::string& rom_hash,
152 bool ai_enabled) {
153 if (!connected_) {
154 return absl::FailedPreconditionError(
155 "Not connected to collaboration server");
156 }
157
158 username_ = username;
159
160 // Build host_session message with v2.0 fields
161 Json payload = {{"session_name", session_name},
162 {"username", username},
163 {"ai_enabled", ai_enabled}};
164
165 if (!rom_hash.empty()) {
166 payload["rom_hash"] = rom_hash;
167 }
168
169 Json message = {{"type", "host_session"}, {"payload", payload}};
170
171 SendWebSocketMessage("host_session", message["payload"].dump());
172
173 // TODO: Wait for session_hosted response and parse it
174 // For now, return a placeholder
175 SessionInfo info;
176 info.session_name = session_name;
177 info.session_code = "PENDING"; // Will be updated from server response
178 info.participants = {username};
179
180 in_session_ = true;
181 session_name_ = session_name;
182
183 return info;
184}
185
186absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
187NetworkCollaborationCoordinator::JoinSession(const std::string& session_code,
188 const std::string& username) {
189 if (!connected_) {
190 return absl::FailedPreconditionError(
191 "Not connected to collaboration server");
192 }
193
194 username_ = username;
195 session_code_ = session_code;
196
197 // Build join_session message
198 Json message = {
199 {"type", "join_session"},
200 {"payload", {{"session_code", session_code}, {"username", username}}}};
201
202 SendWebSocketMessage("join_session", message["payload"].dump());
203
204 // TODO: Wait for session_joined response and parse it
205 SessionInfo info;
206 info.session_code = session_code;
207
208 in_session_ = true;
209
210 return info;
211}
212
213absl::Status NetworkCollaborationCoordinator::LeaveSession() {
214 if (!in_session_) {
215 return absl::FailedPreconditionError("Not in a session");
216 }
217
218 Json message = {{"type", "leave_session"}};
219 SendWebSocketMessage("leave_session", "{}");
220
221 in_session_ = false;
222 session_id_.clear();
223 session_code_.clear();
224 session_name_.clear();
225
226 return absl::OkStatus();
227}
228
229absl::Status NetworkCollaborationCoordinator::SendChatMessage(
230 const std::string& sender, const std::string& message,
231 const std::string& message_type, const std::string& metadata) {
232 if (!in_session_) {
233 return absl::FailedPreconditionError("Not in a session");
234 }
235
236 Json payload = {
237 {"sender", sender}, {"message", message}, {"message_type", message_type}};
238
239 if (!metadata.empty()) {
240 payload["metadata"] = Json::parse(metadata);
241 }
242
243 Json msg = {{"type", "chat_message"}, {"payload", payload}};
244
245 SendWebSocketMessage("chat_message", msg["payload"].dump());
246 return absl::OkStatus();
247}
248
249absl::Status NetworkCollaborationCoordinator::SendRomSync(
250 const std::string& sender, const std::string& diff_data,
251 const std::string& rom_hash) {
252 if (!in_session_) {
253 return absl::FailedPreconditionError("Not in a session");
254 }
255
256 Json msg = {
257 {"type", "rom_sync"},
258 {"payload",
259 {{"sender", sender}, {"diff_data", diff_data}, {"rom_hash", rom_hash}}}};
260
261 SendWebSocketMessage("rom_sync", msg["payload"].dump());
262 return absl::OkStatus();
263}
264
265absl::Status NetworkCollaborationCoordinator::SendSnapshot(
266 const std::string& sender, const std::string& snapshot_data,
267 const std::string& snapshot_type) {
268 if (!in_session_) {
269 return absl::FailedPreconditionError("Not in a session");
270 }
271
272 Json msg = {{"type", "snapshot_share"},
273 {"payload",
274 {{"sender", sender},
275 {"snapshot_data", snapshot_data},
276 {"snapshot_type", snapshot_type}}}};
277
278 SendWebSocketMessage("snapshot_share", msg["payload"].dump());
279 return absl::OkStatus();
280}
281
282absl::Status NetworkCollaborationCoordinator::SendProposal(
283 const std::string& sender, const std::string& proposal_data_json) {
284 if (!in_session_) {
285 return absl::FailedPreconditionError("Not in a session");
286 }
287
288 Json msg = {{"type", "proposal_share"},
289 {"payload",
290 {{"sender", sender},
291 {"proposal_data", Json::parse(proposal_data_json)}}}};
292
293 SendWebSocketMessage("proposal_share", msg["payload"].dump());
294 return absl::OkStatus();
295}
296
297absl::Status NetworkCollaborationCoordinator::UpdateProposal(
298 const std::string& proposal_id, const std::string& status) {
299 if (!in_session_) {
300 return absl::FailedPreconditionError("Not in a session");
301 }
302
303 Json msg = {{"type", "proposal_update"},
304 {"payload", {{"proposal_id", proposal_id}, {"status", status}}}};
305
306 SendWebSocketMessage("proposal_update", msg["payload"].dump());
307 return absl::OkStatus();
308}
309
310absl::Status NetworkCollaborationCoordinator::SendAIQuery(
311 const std::string& username, const std::string& query) {
312 if (!in_session_) {
313 return absl::FailedPreconditionError("Not in a session");
314 }
315
316 Json msg = {{"type", "ai_query"},
317 {"payload", {{"username", username}, {"query", query}}}};
318
319 SendWebSocketMessage("ai_query", msg["payload"].dump());
320 return absl::OkStatus();
321}
322
323bool NetworkCollaborationCoordinator::IsConnected() const {
324 return connected_;
325}
326
327void NetworkCollaborationCoordinator::SetMessageCallback(
328 MessageCallback callback) {
329 absl::MutexLock lock(&mutex_);
330 message_callback_ = std::move(callback);
331}
332
333void NetworkCollaborationCoordinator::SetParticipantCallback(
334 ParticipantCallback callback) {
335 absl::MutexLock lock(&mutex_);
336 participant_callback_ = std::move(callback);
337}
338
339void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) {
340 absl::MutexLock lock(&mutex_);
341 error_callback_ = std::move(callback);
342}
343
344void NetworkCollaborationCoordinator::SetRomSyncCallback(
345 RomSyncCallback callback) {
346 absl::MutexLock lock(&mutex_);
347 rom_sync_callback_ = std::move(callback);
348}
349
350void NetworkCollaborationCoordinator::SetSnapshotCallback(
351 SnapshotCallback callback) {
352 absl::MutexLock lock(&mutex_);
353 snapshot_callback_ = std::move(callback);
354}
355
356void NetworkCollaborationCoordinator::SetProposalCallback(
357 ProposalCallback callback) {
358 absl::MutexLock lock(&mutex_);
359 proposal_callback_ = std::move(callback);
360}
361
362void NetworkCollaborationCoordinator::SetProposalUpdateCallback(
363 ProposalUpdateCallback callback) {
364 absl::MutexLock lock(&mutex_);
365 proposal_update_callback_ = std::move(callback);
366}
367
368void NetworkCollaborationCoordinator::SetAIResponseCallback(
369 AIResponseCallback callback) {
370 absl::MutexLock lock(&mutex_);
371 ai_response_callback_ = std::move(callback);
372}
373
374void NetworkCollaborationCoordinator::SendWebSocketMessage(
375 const std::string& type, const std::string& payload_json) {
376 if (!ws_client_ || !connected_) {
377 return;
378 }
379
380 Json message = {{"type", type}, {"payload", Json::parse(payload_json)}};
381
382 ws_client_->Send(message.dump());
383}
384
385void NetworkCollaborationCoordinator::HandleWebSocketMessage(
386 const std::string& message_str) {
387 try {
388 Json message = Json::parse(message_str);
389 std::string type = message["type"];
390
391 if (type == "session_hosted") {
392 Json payload = message["payload"];
393 session_id_ = payload["session_id"];
394 session_code_ = payload["session_code"];
395 session_name_ = payload["session_name"];
396
397 if (payload.contains("participants")) {
398 absl::MutexLock lock(&mutex_);
399 if (participant_callback_) {
400 std::vector<std::string> participants = payload["participants"];
401 participant_callback_(participants);
402 }
403 }
404 } else if (type == "session_joined") {
405 Json payload = message["payload"];
406 session_id_ = payload["session_id"];
407 session_code_ = payload["session_code"];
408 session_name_ = payload["session_name"];
409
410 if (payload.contains("participants")) {
411 absl::MutexLock lock(&mutex_);
412 if (participant_callback_) {
413 std::vector<std::string> participants = payload["participants"];
414 participant_callback_(participants);
415 }
416 }
417 } else if (type == "chat_message") {
418 Json payload = message["payload"];
419 ChatMessage msg;
420 msg.sender = payload["sender"];
421 msg.message = payload["message"];
422 msg.timestamp = payload["timestamp"];
423 msg.message_type = payload.value("message_type", "chat");
424 if (payload.contains("metadata") && !payload["metadata"].is_null()) {
425 msg.metadata = payload["metadata"].dump();
426 }
427
428 absl::MutexLock lock(&mutex_);
429 if (message_callback_) {
430 message_callback_(msg);
431 }
432 } else if (type == "rom_sync") {
433 Json payload = message["payload"];
434 RomSync sync;
435 sync.sync_id = payload["sync_id"];
436 sync.sender = payload["sender"];
437 sync.diff_data = payload["diff_data"];
438 sync.rom_hash = payload["rom_hash"];
439 sync.timestamp = payload["timestamp"];
440
441 absl::MutexLock lock(&mutex_);
442 if (rom_sync_callback_) {
443 rom_sync_callback_(sync);
444 }
445 } else if (type == "snapshot_shared") {
446 Json payload = message["payload"];
447 Snapshot snapshot;
448 snapshot.snapshot_id = payload["snapshot_id"];
449 snapshot.sender = payload["sender"];
450 snapshot.snapshot_data = payload["snapshot_data"];
451 snapshot.snapshot_type = payload["snapshot_type"];
452 snapshot.timestamp = payload["timestamp"];
453
454 absl::MutexLock lock(&mutex_);
455 if (snapshot_callback_) {
456 snapshot_callback_(snapshot);
457 }
458 } else if (type == "proposal_shared") {
459 Json payload = message["payload"];
460 Proposal proposal;
461 proposal.proposal_id = payload["proposal_id"];
462 proposal.sender = payload["sender"];
463 proposal.proposal_data = payload["proposal_data"].dump();
464 proposal.status = payload["status"];
465 proposal.timestamp = payload["timestamp"];
466
467 absl::MutexLock lock(&mutex_);
468 if (proposal_callback_) {
469 proposal_callback_(proposal);
470 }
471 } else if (type == "proposal_updated") {
472 Json payload = message["payload"];
473 std::string proposal_id = payload["proposal_id"];
474 std::string status = payload["status"];
475
476 absl::MutexLock lock(&mutex_);
477 if (proposal_update_callback_) {
478 proposal_update_callback_(proposal_id, status);
479 }
480 } else if (type == "ai_response") {
481 Json payload = message["payload"];
482 AIResponse response;
483 response.query_id = payload["query_id"];
484 response.username = payload["username"];
485 response.query = payload["query"];
486 response.response = payload["response"];
487 response.timestamp = payload["timestamp"];
488
489 absl::MutexLock lock(&mutex_);
490 if (ai_response_callback_) {
491 ai_response_callback_(response);
492 }
493 } else if (type == "server_shutdown") {
494 Json payload = message["payload"];
495 std::string error =
496 "Server shutdown: " + payload["message"].get<std::string>();
497
498 absl::MutexLock lock(&mutex_);
499 if (error_callback_) {
500 error_callback_(error);
501 }
502
503 // Disconnect
504 connected_ = false;
505 } else if (type == "participant_joined" || type == "participant_left") {
506 Json payload = message["payload"];
507 if (payload.contains("participants")) {
508 absl::MutexLock lock(&mutex_);
509 if (participant_callback_) {
510 std::vector<std::string> participants = payload["participants"];
511 participant_callback_(participants);
512 }
513 }
514 } else if (type == "error") {
515 Json payload = message["payload"];
516 std::string error = payload["error"];
517
518 absl::MutexLock lock(&mutex_);
519 if (error_callback_) {
520 error_callback_(error);
521 }
522 }
523 } catch (const std::exception& e) {
524 std::cerr << "Error parsing WebSocket message: " << e.what() << std::endl;
525 }
526}
527
528void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {
529 while (!should_stop_ && connected_) {
530 if (!ws_client_)
531 break;
532
533 std::string message = ws_client_->Receive();
534 if (!message.empty()) {
535 HandleWebSocketMessage(message);
536 }
537
538 // Small sleep to avoid busy-waiting
539 std::this_thread::sleep_for(std::chrono::milliseconds(10));
540 }
541}
542
543#else // !YAZE_WITH_JSON
544
545// Stub implementations when JSON is not available
546NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
547 const std::string& server_url)
548 : server_url_(server_url) {}
549
550NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() = default;
551
552absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
553NetworkCollaborationCoordinator::HostSession(const std::string&,
554 const std::string&,
555 const std::string&, bool) {
556 return absl::UnimplementedError(
557 "Network collaboration requires JSON support");
558}
559
560absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
561NetworkCollaborationCoordinator::JoinSession(const std::string&,
562 const std::string&) {
563 return absl::UnimplementedError(
564 "Network collaboration requires JSON support");
565}
566
567absl::Status NetworkCollaborationCoordinator::LeaveSession() {
568 return absl::UnimplementedError(
569 "Network collaboration requires JSON support");
570}
571
572absl::Status NetworkCollaborationCoordinator::SendChatMessage(
573 const std::string&, const std::string&, const std::string&,
574 const std::string&) {
575 return absl::UnimplementedError(
576 "Network collaboration requires JSON support");
577}
578
579absl::Status NetworkCollaborationCoordinator::SendRomSync(const std::string&,
580 const std::string&,
581 const std::string&) {
582 return absl::UnimplementedError(
583 "Network collaboration requires JSON support");
584}
585
586absl::Status NetworkCollaborationCoordinator::SendSnapshot(const std::string&,
587 const std::string&,
588 const std::string&) {
589 return absl::UnimplementedError(
590 "Network collaboration requires JSON support");
591}
592
593absl::Status NetworkCollaborationCoordinator::SendProposal(const std::string&,
594 const std::string&) {
595 return absl::UnimplementedError(
596 "Network collaboration requires JSON support");
597}
598
599absl::Status NetworkCollaborationCoordinator::UpdateProposal(
600 const std::string&, const std::string&) {
601 return absl::UnimplementedError(
602 "Network collaboration requires JSON support");
603}
604
605absl::Status NetworkCollaborationCoordinator::SendAIQuery(const std::string&,
606 const std::string&) {
607 return absl::UnimplementedError(
608 "Network collaboration requires JSON support");
609}
610
611bool NetworkCollaborationCoordinator::IsConnected() const {
612 return false;
613}
614
615void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback) {}
616void NetworkCollaborationCoordinator::SetParticipantCallback(
617 ParticipantCallback) {}
618void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback) {}
619void NetworkCollaborationCoordinator::SetRomSyncCallback(RomSyncCallback) {}
620void NetworkCollaborationCoordinator::SetSnapshotCallback(SnapshotCallback) {}
621void NetworkCollaborationCoordinator::SetProposalCallback(ProposalCallback) {}
622void NetworkCollaborationCoordinator::SetProposalUpdateCallback(
623 ProposalUpdateCallback) {}
624void NetworkCollaborationCoordinator::SetAIResponseCallback(
625 AIResponseCallback) {}
626void NetworkCollaborationCoordinator::ConnectWebSocket() {}
627void NetworkCollaborationCoordinator::DisconnectWebSocket() {}
628void NetworkCollaborationCoordinator::SendWebSocketMessage(const std::string&,
629 const std::string&) {
630}
631void NetworkCollaborationCoordinator::HandleWebSocketMessage(
632 const std::string&) {}
633void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {}
634
635#endif // YAZE_WITH_JSON
636
637} // namespace editor
638} // namespace yaze
639
640#endif // YAZE_WITH_GRPC