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_ <<
":"
43 << port_ << std::endl;
45 }
catch (
const std::exception& e) {
46 std::cerr <<
"Failed to connect to " << host_ <<
":" << port_ <<
": "
47 << e.what() << std::endl;
57 bool Send(
const std::string& message) {
58 if (!connected_ || !client_)
63 auto res = client_->Post(
"/message", message,
"application/json");
64 return res && res->status == 200;
67 std::string Receive() {
68 if (!connected_ || !client_)
73 auto res = client_->Get(
"/poll");
74 if (res && res->status == 200) {
80 bool IsConnected()
const {
return connected_; }
86 std::unique_ptr<httplib::Client> client_;
91NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
92 const std::string& server_url)
93 : server_url_(server_url) {
96 if (server_url_.find(
"ws://") == 0) {
103NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() {
105 if (receive_thread_ && receive_thread_->joinable()) {
106 receive_thread_->join();
108 DisconnectWebSocket();
111void NetworkCollaborationCoordinator::ConnectWebSocket() {
113 std::string host =
"localhost";
117 if (server_url_.find(
"ws://") == 0) {
118 std::string url_part = server_url_.substr(5);
119 std::vector<std::string> parts = absl::StrSplit(url_part,
':');
120 if (!parts.empty()) {
123 if (parts.size() > 1) {
124 port = std::stoi(parts[1]);
128 ws_client_ = std::make_unique<detail::WebSocketClient>(host, port);
130 if (ws_client_->Connect(
"/")) {
134 should_stop_ =
false;
135 receive_thread_ = std::make_unique<std::thread>(
136 &NetworkCollaborationCoordinator::WebSocketReceiveLoop,
this);
140void NetworkCollaborationCoordinator::DisconnectWebSocket() {
148absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
149NetworkCollaborationCoordinator::HostSession(
const std::string& session_name,
150 const std::string& username,
151 const std::string& rom_hash,
154 return absl::FailedPreconditionError(
155 "Not connected to collaboration server");
158 username_ = username;
161 Json payload = {{
"session_name", session_name},
162 {
"username", username},
163 {
"ai_enabled", ai_enabled}};
165 if (!rom_hash.empty()) {
166 payload[
"rom_hash"] = rom_hash;
169 Json message = {{
"type",
"host_session"}, {
"payload", payload}};
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(
191 "Not connected to collaboration server");
194 username_ = username;
195 session_code_ = session_code;
199 {
"type",
"join_session"},
200 {
"payload", {{
"session_code", session_code}, {
"username", username}}}};
202 SendWebSocketMessage(
"join_session", message[
"payload"].dump());
206 info.session_code = session_code;
213absl::Status NetworkCollaborationCoordinator::LeaveSession() {
215 return absl::FailedPreconditionError(
"Not in a session");
218 Json message = {{
"type",
"leave_session"}};
219 SendWebSocketMessage(
"leave_session",
"{}");
223 session_code_.clear();
224 session_name_.clear();
226 return absl::OkStatus();
229absl::Status NetworkCollaborationCoordinator::SendChatMessage(
230 const std::string& sender,
const std::string& message,
231 const std::string& message_type,
const std::string& metadata) {
233 return absl::FailedPreconditionError(
"Not in a session");
237 {
"sender", sender}, {
"message", message}, {
"message_type", message_type}};
239 if (!metadata.empty()) {
240 payload[
"metadata"] = Json::parse(metadata);
243 Json msg = {{
"type",
"chat_message"}, {
"payload", payload}};
245 SendWebSocketMessage(
"chat_message", msg[
"payload"].dump());
246 return absl::OkStatus();
249absl::Status NetworkCollaborationCoordinator::SendRomSync(
250 const std::string& sender,
const std::string& diff_data,
251 const std::string& rom_hash) {
253 return absl::FailedPreconditionError(
"Not in a session");
257 {
"type",
"rom_sync"},
259 {{
"sender", sender}, {
"diff_data", diff_data}, {
"rom_hash", rom_hash}}}};
261 SendWebSocketMessage(
"rom_sync", msg[
"payload"].dump());
262 return absl::OkStatus();
265absl::Status NetworkCollaborationCoordinator::SendSnapshot(
266 const std::string& sender,
const std::string& snapshot_data,
267 const std::string& snapshot_type) {
269 return absl::FailedPreconditionError(
"Not in a session");
272 Json msg = {{
"type",
"snapshot_share"},
275 {
"snapshot_data", snapshot_data},
276 {
"snapshot_type", snapshot_type}}}};
278 SendWebSocketMessage(
"snapshot_share", msg[
"payload"].dump());
279 return absl::OkStatus();
282absl::Status NetworkCollaborationCoordinator::SendProposal(
283 const std::string& sender,
const std::string& proposal_data_json) {
285 return absl::FailedPreconditionError(
"Not in a session");
288 Json msg = {{
"type",
"proposal_share"},
291 {
"proposal_data", Json::parse(proposal_data_json)}}}};
293 SendWebSocketMessage(
"proposal_share", msg[
"payload"].dump());
294 return absl::OkStatus();
297absl::Status NetworkCollaborationCoordinator::UpdateProposal(
298 const std::string& proposal_id,
const std::string& status) {
300 return absl::FailedPreconditionError(
"Not in a session");
303 Json msg = {{
"type",
"proposal_update"},
304 {
"payload", {{
"proposal_id", proposal_id}, {
"status", status}}}};
306 SendWebSocketMessage(
"proposal_update", msg[
"payload"].dump());
307 return absl::OkStatus();
310absl::Status NetworkCollaborationCoordinator::SendAIQuery(
311 const std::string& username,
const std::string& query) {
313 return absl::FailedPreconditionError(
"Not in a session");
316 Json msg = {{
"type",
"ai_query"},
317 {
"payload", {{
"username", username}, {
"query", query}}}};
319 SendWebSocketMessage(
"ai_query", msg[
"payload"].dump());
320 return absl::OkStatus();
323bool NetworkCollaborationCoordinator::IsConnected()
const {
327void NetworkCollaborationCoordinator::SetMessageCallback(
328 MessageCallback callback) {
329 absl::MutexLock lock(&mutex_);
330 message_callback_ = std::move(callback);
333void NetworkCollaborationCoordinator::SetParticipantCallback(
334 ParticipantCallback callback) {
335 absl::MutexLock lock(&mutex_);
336 participant_callback_ = std::move(callback);
339void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) {
340 absl::MutexLock lock(&mutex_);
341 error_callback_ = std::move(callback);
344void NetworkCollaborationCoordinator::SetRomSyncCallback(
345 RomSyncCallback callback) {
346 absl::MutexLock lock(&mutex_);
347 rom_sync_callback_ = std::move(callback);
350void NetworkCollaborationCoordinator::SetSnapshotCallback(
351 SnapshotCallback callback) {
352 absl::MutexLock lock(&mutex_);
353 snapshot_callback_ = std::move(callback);
356void NetworkCollaborationCoordinator::SetProposalCallback(
357 ProposalCallback callback) {
358 absl::MutexLock lock(&mutex_);
359 proposal_callback_ = std::move(callback);
362void NetworkCollaborationCoordinator::SetProposalUpdateCallback(
363 ProposalUpdateCallback callback) {
364 absl::MutexLock lock(&mutex_);
365 proposal_update_callback_ = std::move(callback);
368void NetworkCollaborationCoordinator::SetAIResponseCallback(
369 AIResponseCallback callback) {
370 absl::MutexLock lock(&mutex_);
371 ai_response_callback_ = std::move(callback);
374void NetworkCollaborationCoordinator::SendWebSocketMessage(
375 const std::string& type,
const std::string& payload_json) {
376 if (!ws_client_ || !connected_) {
380 Json message = {{
"type", type}, {
"payload", Json::parse(payload_json)}};
382 ws_client_->Send(message.dump());
385void NetworkCollaborationCoordinator::HandleWebSocketMessage(
386 const std::string& message_str) {
388 Json message = Json::parse(message_str);
389 std::string type = message[
"type"];
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"];
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);
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"];
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);
417 }
else if (type ==
"chat_message") {
418 Json payload = message[
"payload"];
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();
428 absl::MutexLock lock(&mutex_);
429 if (message_callback_) {
430 message_callback_(msg);
432 }
else if (type ==
"rom_sync") {
433 Json payload = message[
"payload"];
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"];
441 absl::MutexLock lock(&mutex_);
442 if (rom_sync_callback_) {
443 rom_sync_callback_(sync);
445 }
else if (type ==
"snapshot_shared") {
446 Json payload = message[
"payload"];
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"];
454 absl::MutexLock lock(&mutex_);
455 if (snapshot_callback_) {
456 snapshot_callback_(snapshot);
458 }
else if (type ==
"proposal_shared") {
459 Json payload = message[
"payload"];
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"];
467 absl::MutexLock lock(&mutex_);
468 if (proposal_callback_) {
469 proposal_callback_(proposal);
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"];
476 absl::MutexLock lock(&mutex_);
477 if (proposal_update_callback_) {
478 proposal_update_callback_(proposal_id, status);
480 }
else if (type ==
"ai_response") {
481 Json payload = message[
"payload"];
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"];
489 absl::MutexLock lock(&mutex_);
490 if (ai_response_callback_) {
491 ai_response_callback_(response);
493 }
else if (type ==
"server_shutdown") {
494 Json payload = message[
"payload"];
496 "Server shutdown: " + payload[
"message"].get<std::string>();
498 absl::MutexLock lock(&mutex_);
499 if (error_callback_) {
500 error_callback_(error);
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);
514 }
else if (type ==
"error") {
515 Json payload = message[
"payload"];
516 std::string error = payload[
"error"];
518 absl::MutexLock lock(&mutex_);
519 if (error_callback_) {
520 error_callback_(error);
523 }
catch (
const std::exception& e) {
524 std::cerr <<
"Error parsing WebSocket message: " << e.what() << std::endl;
528void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {
529 while (!should_stop_ && connected_) {
533 std::string message = ws_client_->Receive();
534 if (!message.empty()) {
535 HandleWebSocketMessage(message);
539 std::this_thread::sleep_for(std::chrono::milliseconds(10));
546NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
547 const std::string& server_url)
548 : server_url_(server_url) {}
550NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() =
default;
552absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
553NetworkCollaborationCoordinator::HostSession(
const std::string&,
555 const std::string&,
bool) {
556 return absl::UnimplementedError(
557 "Network collaboration requires JSON support");
560absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
561NetworkCollaborationCoordinator::JoinSession(
const std::string&,
562 const std::string&) {
563 return absl::UnimplementedError(
564 "Network collaboration requires JSON support");
567absl::Status NetworkCollaborationCoordinator::LeaveSession() {
568 return absl::UnimplementedError(
569 "Network collaboration requires JSON support");
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");
579absl::Status NetworkCollaborationCoordinator::SendRomSync(
const std::string&,
581 const std::string&) {
582 return absl::UnimplementedError(
583 "Network collaboration requires JSON support");
586absl::Status NetworkCollaborationCoordinator::SendSnapshot(
const std::string&,
588 const std::string&) {
589 return absl::UnimplementedError(
590 "Network collaboration requires JSON support");
593absl::Status NetworkCollaborationCoordinator::SendProposal(
const std::string&,
594 const std::string&) {
595 return absl::UnimplementedError(
596 "Network collaboration requires JSON support");
599absl::Status NetworkCollaborationCoordinator::UpdateProposal(
600 const std::string&,
const std::string&) {
601 return absl::UnimplementedError(
602 "Network collaboration requires JSON support");
605absl::Status NetworkCollaborationCoordinator::SendAIQuery(
const std::string&,
606 const std::string&) {
607 return absl::UnimplementedError(
608 "Network collaboration requires JSON support");
611bool NetworkCollaborationCoordinator::IsConnected()
const {
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&) {
631void NetworkCollaborationCoordinator::HandleWebSocketMessage(
632 const std::string&) {}
633void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {}