8#include "absl/strings/str_format.h"
9#include "absl/strings/str_split.h"
13#include "nlohmann/json.hpp"
14using Json = nlohmann::json;
26class WebSocketClient {
28 explicit WebSocketClient(
const std::string& host,
int port)
29 : host_(host), port_(port), connected_(false) {}
31 bool Connect(
const std::string& path) {
34 client_ = std::make_unique<httplib::Client>(host_.c_str(), port_);
35 client_->set_connection_timeout(5);
36 client_->set_read_timeout(30);
42 std::cout <<
"✓ Connected to collaboration server at " << host_ <<
":" << port_ << std::endl;
44 }
catch (
const std::exception& e) {
45 std::cerr <<
"Failed to connect to " << host_ <<
":" << port_ <<
": " << e.what() << std::endl;
55 bool Send(
const std::string& message) {
56 if (!connected_ || !client_)
return false;
60 auto res = client_->Post(
"/message", message,
"application/json");
61 return res && res->status == 200;
64 std::string Receive() {
65 if (!connected_ || !client_)
return "";
69 auto res = client_->Get(
"/poll");
70 if (res && res->status == 200) {
76 bool IsConnected()
const {
return connected_; }
82 std::unique_ptr<httplib::Client> client_;
87NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
88 const std::string& server_url)
89 : server_url_(server_url) {
92 if (server_url_.find(
"ws://") == 0) {
99NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() {
101 if (receive_thread_ && receive_thread_->joinable()) {
102 receive_thread_->join();
104 DisconnectWebSocket();
107void NetworkCollaborationCoordinator::ConnectWebSocket() {
109 std::string host =
"localhost";
113 if (server_url_.find(
"ws://") == 0) {
114 std::string url_part = server_url_.substr(5);
115 std::vector<std::string> parts = absl::StrSplit(url_part,
':');
116 if (!parts.empty()) {
119 if (parts.size() > 1) {
120 port = std::stoi(parts[1]);
124 ws_client_ = std::make_unique<detail::WebSocketClient>(host, port);
126 if (ws_client_->Connect(
"/")) {
130 should_stop_ =
false;
131 receive_thread_ = std::make_unique<std::thread>(
132 &NetworkCollaborationCoordinator::WebSocketReceiveLoop,
this);
136void NetworkCollaborationCoordinator::DisconnectWebSocket() {
144absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
145NetworkCollaborationCoordinator::HostSession(
const std::string& session_name,
146 const std::string& username,
147 const std::string& rom_hash,
150 return absl::FailedPreconditionError(
"Not connected to collaboration server");
153 username_ = username;
157 {
"session_name", session_name},
158 {
"username", username},
159 {
"ai_enabled", ai_enabled}
162 if (!rom_hash.empty()) {
163 payload[
"rom_hash"] = rom_hash;
167 {
"type",
"host_session"},
171 SendWebSocketMessage(
"host_session", message[
"payload"].dump());
176 info.session_name = session_name;
177 info.session_code =
"PENDING";
178 info.participants = {username};
181 session_name_ = session_name;
186absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
187NetworkCollaborationCoordinator::JoinSession(
const std::string& session_code,
188 const std::string& username) {
190 return absl::FailedPreconditionError(
"Not connected to collaboration server");
193 username_ = username;
194 session_code_ = session_code;
198 {
"type",
"join_session"},
200 {
"session_code", session_code},
201 {
"username", username}
205 SendWebSocketMessage(
"join_session", message[
"payload"].dump());
209 info.session_code = session_code;
216absl::Status NetworkCollaborationCoordinator::LeaveSession() {
218 return absl::FailedPreconditionError(
"Not in a session");
221 Json message = {{
"type",
"leave_session"}};
222 SendWebSocketMessage(
"leave_session",
"{}");
226 session_code_.clear();
227 session_name_.clear();
229 return absl::OkStatus();
232absl::Status NetworkCollaborationCoordinator::SendChatMessage(
233 const std::string& sender,
const std::string& message,
234 const std::string& message_type,
const std::string& metadata) {
236 return absl::FailedPreconditionError(
"Not in a session");
241 {
"message", message},
242 {
"message_type", message_type}
245 if (!metadata.empty()) {
246 payload[
"metadata"] = Json::parse(metadata);
250 {
"type",
"chat_message"},
254 SendWebSocketMessage(
"chat_message", msg[
"payload"].dump());
255 return absl::OkStatus();
258absl::Status NetworkCollaborationCoordinator::SendRomSync(
259 const std::string& sender,
const std::string& diff_data,
260 const std::string& rom_hash) {
262 return absl::FailedPreconditionError(
"Not in a session");
266 {
"type",
"rom_sync"},
269 {
"diff_data", diff_data},
270 {
"rom_hash", rom_hash}
274 SendWebSocketMessage(
"rom_sync", msg[
"payload"].dump());
275 return absl::OkStatus();
278absl::Status NetworkCollaborationCoordinator::SendSnapshot(
279 const std::string& sender,
const std::string& snapshot_data,
280 const std::string& snapshot_type) {
282 return absl::FailedPreconditionError(
"Not in a session");
286 {
"type",
"snapshot_share"},
289 {
"snapshot_data", snapshot_data},
290 {
"snapshot_type", snapshot_type}
294 SendWebSocketMessage(
"snapshot_share", msg[
"payload"].dump());
295 return absl::OkStatus();
298absl::Status NetworkCollaborationCoordinator::SendProposal(
299 const std::string& sender,
const std::string& proposal_data_json) {
301 return absl::FailedPreconditionError(
"Not in a session");
305 {
"type",
"proposal_share"},
308 {
"proposal_data", Json::parse(proposal_data_json)}
312 SendWebSocketMessage(
"proposal_share", msg[
"payload"].dump());
313 return absl::OkStatus();
316absl::Status NetworkCollaborationCoordinator::UpdateProposal(
317 const std::string& proposal_id,
const std::string& status) {
319 return absl::FailedPreconditionError(
"Not in a session");
323 {
"type",
"proposal_update"},
325 {
"proposal_id", proposal_id},
330 SendWebSocketMessage(
"proposal_update", msg[
"payload"].dump());
331 return absl::OkStatus();
334absl::Status NetworkCollaborationCoordinator::SendAIQuery(
335 const std::string& username,
const std::string& query) {
337 return absl::FailedPreconditionError(
"Not in a session");
341 {
"type",
"ai_query"},
343 {
"username", username},
348 SendWebSocketMessage(
"ai_query", msg[
"payload"].dump());
349 return absl::OkStatus();
352bool NetworkCollaborationCoordinator::IsConnected()
const {
356void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback callback) {
357 absl::MutexLock lock(&mutex_);
358 message_callback_ = std::move(callback);
361void NetworkCollaborationCoordinator::SetParticipantCallback(
362 ParticipantCallback callback) {
363 absl::MutexLock lock(&mutex_);
364 participant_callback_ = std::move(callback);
367void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) {
368 absl::MutexLock lock(&mutex_);
369 error_callback_ = std::move(callback);
372void NetworkCollaborationCoordinator::SetRomSyncCallback(RomSyncCallback callback) {
373 absl::MutexLock lock(&mutex_);
374 rom_sync_callback_ = std::move(callback);
377void NetworkCollaborationCoordinator::SetSnapshotCallback(SnapshotCallback callback) {
378 absl::MutexLock lock(&mutex_);
379 snapshot_callback_ = std::move(callback);
382void NetworkCollaborationCoordinator::SetProposalCallback(ProposalCallback callback) {
383 absl::MutexLock lock(&mutex_);
384 proposal_callback_ = std::move(callback);
387void NetworkCollaborationCoordinator::SetProposalUpdateCallback(ProposalUpdateCallback callback) {
388 absl::MutexLock lock(&mutex_);
389 proposal_update_callback_ = std::move(callback);
392void NetworkCollaborationCoordinator::SetAIResponseCallback(AIResponseCallback callback) {
393 absl::MutexLock lock(&mutex_);
394 ai_response_callback_ = std::move(callback);
397void NetworkCollaborationCoordinator::SendWebSocketMessage(
398 const std::string& type,
const std::string& payload_json) {
399 if (!ws_client_ || !connected_) {
405 {
"payload", Json::parse(payload_json)}
408 ws_client_->Send(message.dump());
411void NetworkCollaborationCoordinator::HandleWebSocketMessage(
412 const std::string& message_str) {
414 Json message = Json::parse(message_str);
415 std::string type = message[
"type"];
417 if (type ==
"session_hosted") {
418 Json payload = message[
"payload"];
419 session_id_ = payload[
"session_id"];
420 session_code_ = payload[
"session_code"];
421 session_name_ = payload[
"session_name"];
423 if (payload.contains(
"participants")) {
424 absl::MutexLock lock(&mutex_);
425 if (participant_callback_) {
426 std::vector<std::string> participants = payload[
"participants"];
427 participant_callback_(participants);
430 }
else if (type ==
"session_joined") {
431 Json payload = message[
"payload"];
432 session_id_ = payload[
"session_id"];
433 session_code_ = payload[
"session_code"];
434 session_name_ = payload[
"session_name"];
436 if (payload.contains(
"participants")) {
437 absl::MutexLock lock(&mutex_);
438 if (participant_callback_) {
439 std::vector<std::string> participants = payload[
"participants"];
440 participant_callback_(participants);
443 }
else if (type ==
"chat_message") {
444 Json payload = message[
"payload"];
446 msg.sender = payload[
"sender"];
447 msg.message = payload[
"message"];
448 msg.timestamp = payload[
"timestamp"];
449 msg.message_type = payload.value(
"message_type",
"chat");
450 if (payload.contains(
"metadata") && !payload[
"metadata"].is_null()) {
451 msg.metadata = payload[
"metadata"].dump();
454 absl::MutexLock lock(&mutex_);
455 if (message_callback_) {
456 message_callback_(msg);
458 }
else if (type ==
"rom_sync") {
459 Json payload = message[
"payload"];
461 sync.sync_id = payload[
"sync_id"];
462 sync.sender = payload[
"sender"];
463 sync.diff_data = payload[
"diff_data"];
464 sync.rom_hash = payload[
"rom_hash"];
465 sync.timestamp = payload[
"timestamp"];
467 absl::MutexLock lock(&mutex_);
468 if (rom_sync_callback_) {
469 rom_sync_callback_(sync);
471 }
else if (type ==
"snapshot_shared") {
472 Json payload = message[
"payload"];
474 snapshot.snapshot_id = payload[
"snapshot_id"];
475 snapshot.sender = payload[
"sender"];
476 snapshot.snapshot_data = payload[
"snapshot_data"];
477 snapshot.snapshot_type = payload[
"snapshot_type"];
478 snapshot.timestamp = payload[
"timestamp"];
480 absl::MutexLock lock(&mutex_);
481 if (snapshot_callback_) {
482 snapshot_callback_(snapshot);
484 }
else if (type ==
"proposal_shared") {
485 Json payload = message[
"payload"];
487 proposal.proposal_id = payload[
"proposal_id"];
488 proposal.sender = payload[
"sender"];
489 proposal.proposal_data = payload[
"proposal_data"].dump();
490 proposal.status = payload[
"status"];
491 proposal.timestamp = payload[
"timestamp"];
493 absl::MutexLock lock(&mutex_);
494 if (proposal_callback_) {
495 proposal_callback_(proposal);
497 }
else if (type ==
"proposal_updated") {
498 Json payload = message[
"payload"];
499 std::string proposal_id = payload[
"proposal_id"];
500 std::string status = payload[
"status"];
502 absl::MutexLock lock(&mutex_);
503 if (proposal_update_callback_) {
504 proposal_update_callback_(proposal_id, status);
506 }
else if (type ==
"ai_response") {
507 Json payload = message[
"payload"];
509 response.query_id = payload[
"query_id"];
510 response.username = payload[
"username"];
511 response.query = payload[
"query"];
512 response.response = payload[
"response"];
513 response.timestamp = payload[
"timestamp"];
515 absl::MutexLock lock(&mutex_);
516 if (ai_response_callback_) {
517 ai_response_callback_(response);
519 }
else if (type ==
"server_shutdown") {
520 Json payload = message[
"payload"];
521 std::string error =
"Server shutdown: " + payload[
"message"].get<std::string>();
523 absl::MutexLock lock(&mutex_);
524 if (error_callback_) {
525 error_callback_(error);
530 }
else if (type ==
"participant_joined" || type ==
"participant_left") {
531 Json payload = message[
"payload"];
532 if (payload.contains(
"participants")) {
533 absl::MutexLock lock(&mutex_);
534 if (participant_callback_) {
535 std::vector<std::string> participants = payload[
"participants"];
536 participant_callback_(participants);
539 }
else if (type ==
"error") {
540 Json payload = message[
"payload"];
541 std::string error = payload[
"error"];
543 absl::MutexLock lock(&mutex_);
544 if (error_callback_) {
545 error_callback_(error);
548 }
catch (
const std::exception& e) {
549 std::cerr <<
"Error parsing WebSocket message: " << e.what() << std::endl;
553void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {
554 while (!should_stop_ && connected_) {
555 if (!ws_client_)
break;
557 std::string message = ws_client_->Receive();
558 if (!message.empty()) {
559 HandleWebSocketMessage(message);
563 std::this_thread::sleep_for(std::chrono::milliseconds(10));
570NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
571 const std::string& server_url) : server_url_(server_url) {}
573NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() =
default;
575absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
576NetworkCollaborationCoordinator::HostSession(
const std::string&,
const std::string&,
577 const std::string&,
bool) {
578 return absl::UnimplementedError(
"Network collaboration requires JSON support");
581absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
582NetworkCollaborationCoordinator::JoinSession(
const std::string&,
const std::string&) {
583 return absl::UnimplementedError(
"Network collaboration requires JSON support");
586absl::Status NetworkCollaborationCoordinator::LeaveSession() {
587 return absl::UnimplementedError(
"Network collaboration requires JSON support");
590absl::Status NetworkCollaborationCoordinator::SendChatMessage(
591 const std::string&,
const std::string&,
const std::string&,
const std::string&) {
592 return absl::UnimplementedError(
"Network collaboration requires JSON support");
595absl::Status NetworkCollaborationCoordinator::SendRomSync(
596 const std::string&,
const std::string&,
const std::string&) {
597 return absl::UnimplementedError(
"Network collaboration requires JSON support");
600absl::Status NetworkCollaborationCoordinator::SendSnapshot(
601 const std::string&,
const std::string&,
const std::string&) {
602 return absl::UnimplementedError(
"Network collaboration requires JSON support");
605absl::Status NetworkCollaborationCoordinator::SendProposal(
606 const std::string&,
const std::string&) {
607 return absl::UnimplementedError(
"Network collaboration requires JSON support");
610absl::Status NetworkCollaborationCoordinator::UpdateProposal(
611 const std::string&,
const std::string&) {
612 return absl::UnimplementedError(
"Network collaboration requires JSON support");
615absl::Status NetworkCollaborationCoordinator::SendAIQuery(
616 const std::string&,
const std::string&) {
617 return absl::UnimplementedError(
"Network collaboration requires JSON support");
620bool NetworkCollaborationCoordinator::IsConnected()
const {
return false; }
622void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback) {}
623void NetworkCollaborationCoordinator::SetParticipantCallback(ParticipantCallback) {}
624void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback) {}
625void NetworkCollaborationCoordinator::SetRomSyncCallback(RomSyncCallback) {}
626void NetworkCollaborationCoordinator::SetSnapshotCallback(SnapshotCallback) {}
627void NetworkCollaborationCoordinator::SetProposalCallback(ProposalCallback) {}
628void NetworkCollaborationCoordinator::SetProposalUpdateCallback(ProposalUpdateCallback) {}
629void NetworkCollaborationCoordinator::SetAIResponseCallback(AIResponseCallback) {}
630void NetworkCollaborationCoordinator::ConnectWebSocket() {}
631void NetworkCollaborationCoordinator::DisconnectWebSocket() {}
632void NetworkCollaborationCoordinator::SendWebSocketMessage(
const std::string&,
const std::string&) {}
633void NetworkCollaborationCoordinator::HandleWebSocketMessage(
const std::string&) {}
634void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {}
Main namespace for the application.