yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
z3ed_network_client.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <condition_variable>
5#include <mutex>
6#include <thread>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
12
13#ifdef YAZE_WITH_JSON
14#include "nlohmann/json.hpp"
15#if !defined(_WIN32) && !defined(YAZE_IOS) && !defined(__EMSCRIPTEN__)
16#define CPPHTTPLIB_OPENSSL_SUPPORT
17#endif
18#endif
19
20#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
21#include "httplib.h"
22#endif
23
24// Undefine Windows macros that conflict with our method names
25// Must be outside the #ifdef so it applies to all code below
26#ifdef _WIN32
27#ifdef SendMessage
28#undef SendMessage
29#endif
30#endif
31
32namespace yaze {
33namespace cli {
34namespace net {
35
36#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
37
38// Implementation using httplib for cross-platform WebSocket support
39class Z3edNetworkClient::Impl {
40 public:
41 Impl() : connected_(false), in_session_(false) {}
42
43 ~Impl() { Disconnect(); }
44
45 absl::Status Connect(const std::string& host, int port) {
46 std::lock_guard<std::mutex> lock(mutex_);
47
48 if (connected_) {
49 return absl::AlreadyExistsError("Already connected");
50 }
51
52 host_ = host;
53 port_ = port;
54
55 try {
56 // Create HTTP client for WebSocket fallback
57 client_ = std::make_unique<httplib::Client>(host, port);
58 client_->set_connection_timeout(5, 0);
59 client_->set_read_timeout(30, 0);
60
61 // Test connection
62 auto res = client_->Get("/health");
63 if (!res || res->status != 200) {
64 return absl::UnavailableError("Server not responding");
65 }
66
67 connected_ = true;
68 return absl::OkStatus();
69
70 } catch (const std::exception& e) {
71 return absl::UnavailableError(
72 absl::StrCat("Connection failed: ", e.what()));
73 }
74 }
75
76 void Disconnect() {
77 std::lock_guard<std::mutex> lock(mutex_);
78 connected_ = false;
79 in_session_ = false;
80 client_.reset();
81 }
82
83 absl::Status JoinSession(const std::string& session_code,
84 const std::string& username) {
85 std::lock_guard<std::mutex> lock(mutex_);
86
87 if (!connected_) {
88 return absl::FailedPreconditionError("Not connected");
89 }
90
91 try {
92 nlohmann::json message = {
93 {"type", "join_session"},
94 {"payload",
95 {{"session_code", session_code}, {"username", username}}}};
96
97 auto res = client_->Post("/message", message.dump(), "application/json");
98
99 if (!res || res->status != 200) {
100 return absl::InternalError("Failed to join session");
101 }
102
103 in_session_ = true;
104 session_code_ = session_code;
105 username_ = username;
106
107 return absl::OkStatus();
108
109 } catch (const std::exception& e) {
110 return absl::InternalError(absl::StrCat("Join failed: ", e.what()));
111 }
112 }
113
114 absl::Status SubmitProposal(const std::string& description,
115 const std::string& proposal_json,
116 const std::string& username) {
117 std::lock_guard<std::mutex> lock(mutex_);
118
119 if (!connected_ || !in_session_) {
120 return absl::FailedPreconditionError("Not in a session");
121 }
122
123 try {
124 nlohmann::json proposal_data = nlohmann::json::parse(proposal_json);
125 proposal_data["description"] = description;
126
127 nlohmann::json message = {
128 {"type", "proposal_share"},
129 {"payload",
130 {{"sender", username}, {"proposal_data", proposal_data}}}};
131
132 auto res = client_->Post("/message", message.dump(), "application/json");
133
134 if (!res || res->status != 200) {
135 return absl::InternalError("Failed to submit proposal");
136 }
137
138 // Extract proposal ID from response if available
139 if (!res->body.empty()) {
140 try {
141 auto response_json = nlohmann::json::parse(res->body);
142 if (response_json.contains("proposal_id")) {
143 last_proposal_id_ = response_json["proposal_id"];
144 }
145 } catch (...) {
146 // Response parsing failed, continue
147 }
148 }
149
150 return absl::OkStatus();
151
152 } catch (const std::exception& e) {
153 return absl::InternalError(
154 absl::StrCat("Proposal submission failed: ", e.what()));
155 }
156 }
157
158 absl::StatusOr<std::string> GetProposalStatus(
159 const std::string& proposal_id) {
160 std::lock_guard<std::mutex> lock(mutex_);
161
162 if (!connected_) {
163 return absl::FailedPreconditionError("Not connected");
164 }
165
166 try {
167 // Query server for proposal status
168 auto res = client_->Get(
169 absl::StrFormat("/proposal/%s/status", proposal_id).c_str());
170
171 if (!res || res->status != 200) {
172 return absl::NotFoundError("Proposal not found");
173 }
174
175 auto response = nlohmann::json::parse(res->body);
176 return response["status"].get<std::string>();
177
178 } catch (const std::exception& e) {
179 return absl::InternalError(
180 absl::StrCat("Status check failed: ", e.what()));
181 }
182 }
183
184 absl::StatusOr<bool> WaitForApproval(const std::string& proposal_id,
185 int timeout_seconds) {
186 auto deadline = absl::Now() + absl::Seconds(timeout_seconds);
187
188 while (absl::Now() < deadline) {
189 auto status_result = GetProposalStatus(proposal_id);
190
191 if (!status_result.ok()) {
192 return status_result.status();
193 }
194
195 std::string status = *status_result;
196
197 if (status == "approved" || status == "applied") {
198 return true;
199 } else if (status == "rejected") {
200 return false;
201 }
202
203 // Poll every second
204 absl::SleepFor(absl::Seconds(1));
205 }
206
207 return absl::DeadlineExceededError("Approval timeout");
208 }
209
210 absl::Status SendMessage(const std::string& message,
211 const std::string& sender) {
212 std::lock_guard<std::mutex> lock(mutex_);
213
214 if (!connected_ || !in_session_) {
215 return absl::FailedPreconditionError("Not in a session");
216 }
217
218 try {
219 nlohmann::json msg = {
220 {"type", "chat_message"},
221 {"payload", {{"message", message}, {"sender", sender}}}};
222
223 auto res = client_->Post("/message", msg.dump(), "application/json");
224
225 if (!res || res->status != 200) {
226 return absl::InternalError("Failed to send message");
227 }
228
229 return absl::OkStatus();
230
231 } catch (const std::exception& e) {
232 return absl::InternalError(absl::StrCat("Send failed: ", e.what()));
233 }
234 }
235
236 absl::StatusOr<std::string> QueryAI(const std::string& query,
237 const std::string& username) {
238 std::lock_guard<std::mutex> lock(mutex_);
239
240 if (!connected_ || !in_session_) {
241 return absl::FailedPreconditionError("Not in a session");
242 }
243
244 try {
245 nlohmann::json message = {
246 {"type", "ai_query"},
247 {"payload", {{"query", query}, {"username", username}}}};
248
249 auto res = client_->Post("/message", message.dump(), "application/json");
250
251 if (!res || res->status != 200) {
252 return absl::InternalError("AI query failed");
253 }
254
255 // Wait for response (in a real implementation, this would use callbacks)
256 // For now, return placeholder
257 return std::string("AI agent endpoint not configured");
258
259 } catch (const std::exception& e) {
260 return absl::InternalError(absl::StrCat("AI query failed: ", e.what()));
261 }
262 }
263
264 bool IsConnected() const {
265 std::lock_guard<std::mutex> lock(mutex_);
266 return connected_;
267 }
268
269 std::string GetLastProposalId() const {
270 std::lock_guard<std::mutex> lock(mutex_);
271 return last_proposal_id_;
272 }
273
274 private:
275 mutable std::mutex mutex_;
276 std::unique_ptr<httplib::Client> client_;
277
278 std::string host_;
279 int port_;
280 bool connected_;
281 bool in_session_;
282
283 std::string session_code_;
284 std::string username_;
285 std::string last_proposal_id_;
286};
287
288#else
289
290// Stub implementation when JSON is not available or in WASM builds
292 public:
293 static constexpr const char* kUnavailableMessage =
294#if defined(__EMSCRIPTEN__)
295 "Network support is not available in WASM builds";
296#else
297 "Network support requires JSON library";
298#endif
299
300 absl::Status Connect(const std::string&, int) {
301 return absl::UnimplementedError(kUnavailableMessage);
302 }
303 void Disconnect() {}
304 absl::Status JoinSession(const std::string&, const std::string&) {
305 return absl::UnimplementedError(kUnavailableMessage);
306 }
307 absl::Status SubmitProposal(const std::string&, const std::string&,
308 const std::string&) {
309 return absl::UnimplementedError(kUnavailableMessage);
310 }
311 absl::StatusOr<std::string> GetProposalStatus(const std::string&) {
312 return absl::UnimplementedError(kUnavailableMessage);
313 }
314 absl::StatusOr<bool> WaitForApproval(const std::string&, int) {
315 return absl::UnimplementedError(kUnavailableMessage);
316 }
317 absl::Status SendMessage(const std::string&, const std::string&) {
318 return absl::UnimplementedError(kUnavailableMessage);
319 }
320 absl::StatusOr<std::string> QueryAI(const std::string&, const std::string&) {
321 return absl::UnimplementedError(kUnavailableMessage);
322 }
323 bool IsConnected() const { return false; }
324 std::string GetLastProposalId() const { return ""; }
325};
326
327#endif // YAZE_WITH_JSON && !__EMSCRIPTEN__
328
329// ============================================================================
330// Z3edNetworkClient Implementation
331// ============================================================================
332
333Z3edNetworkClient::Z3edNetworkClient() : impl_(std::make_unique<Impl>()) {}
334
336
337absl::Status Z3edNetworkClient::Connect(const std::string& host, int port) {
338 return impl_->Connect(host, port);
339}
340
341absl::Status Z3edNetworkClient::JoinSession(const std::string& session_code,
342 const std::string& username) {
343 return impl_->JoinSession(session_code, username);
344}
345
346absl::Status Z3edNetworkClient::SubmitProposal(const std::string& description,
347 const std::string& proposal_json,
348 const std::string& username) {
349 return impl_->SubmitProposal(description, proposal_json, username);
350}
351
352absl::StatusOr<std::string> Z3edNetworkClient::GetProposalStatus(
353 const std::string& proposal_id) {
354 return impl_->GetProposalStatus(proposal_id);
355}
356
358 const std::string& proposal_id, int timeout_seconds) {
359 return impl_->WaitForApproval(proposal_id, timeout_seconds);
360}
361
362absl::Status Z3edNetworkClient::SendMessage(const std::string& message,
363 const std::string& sender) {
364 return impl_->SendMessage(message, sender);
365}
366
367absl::StatusOr<std::string> Z3edNetworkClient::QueryAI(
368 const std::string& query, const std::string& username) {
369 return impl_->QueryAI(query, username);
370}
371
373 impl_->Disconnect();
374}
375
377 return impl_->IsConnected();
378}
379
380} // namespace net
381} // namespace cli
382} // namespace yaze
absl::Status SendMessage(const std::string &, const std::string &)
absl::Status Connect(const std::string &, int)
absl::StatusOr< bool > WaitForApproval(const std::string &, int)
absl::Status JoinSession(const std::string &, const std::string &)
absl::StatusOr< std::string > GetProposalStatus(const std::string &)
static constexpr const char * kUnavailableMessage
absl::Status SubmitProposal(const std::string &, const std::string &, const std::string &)
absl::StatusOr< std::string > QueryAI(const std::string &, const std::string &)
absl::StatusOr< bool > WaitForApproval(const std::string &proposal_id, int timeout_seconds=60)
absl::Status SendMessage(const std::string &message, const std::string &sender)
absl::Status JoinSession(const std::string &session_code, const std::string &username)
absl::StatusOr< std::string > QueryAI(const std::string &query, const std::string &username)
absl::Status Connect(const std::string &host, int port=8765)
absl::StatusOr< std::string > GetProposalStatus(const std::string &proposal_id)
absl::Status SubmitProposal(const std::string &description, const std::string &proposal_json, const std::string &username)