/**************************************************************************/ /* aethex_cloud.cpp */ /**************************************************************************/ /* AeThex Engine */ /* https://aethex.dev */ /**************************************************************************/ #include "aethex_cloud.h" #include "aethex_auth.h" #include "aethex_cloud_save.h" #include "aethex_asset_library.h" #include "aethex_telemetry.h" #include "core/io/json.h" #include "core/config/project_settings.h" #include "core/version.h" AethexCloud *AethexCloud::singleton = nullptr; AethexCloud *AethexCloud::get_singleton() { return singleton; } void AethexCloud::_bind_methods() { // Configuration ClassDB::bind_method(D_METHOD("set_gateway_url", "url"), &AethexCloud::set_gateway_url); ClassDB::bind_method(D_METHOD("get_gateway_url"), &AethexCloud::get_gateway_url); ClassDB::bind_method(D_METHOD("set_api_key", "key"), &AethexCloud::set_api_key); ClassDB::bind_method(D_METHOD("get_api_key"), &AethexCloud::get_api_key); // Connection ClassDB::bind_method(D_METHOD("connect_to_gateway"), &AethexCloud::connect_to_gateway); ClassDB::bind_method(D_METHOD("disconnect_from_gateway"), &AethexCloud::disconnect_from_gateway); ClassDB::bind_method(D_METHOD("get_status"), &AethexCloud::get_status); ClassDB::bind_method(D_METHOD("is_connected"), &AethexCloud::is_connected); // Auth token ClassDB::bind_method(D_METHOD("set_auth_token", "token"), &AethexCloud::set_auth_token); ClassDB::bind_method(D_METHOD("get_auth_token"), &AethexCloud::get_auth_token); // Sub-services ClassDB::bind_method(D_METHOD("get_auth"), &AethexCloud::get_auth); ClassDB::bind_method(D_METHOD("get_cloud_save"), &AethexCloud::get_cloud_save); ClassDB::bind_method(D_METHOD("get_asset_library"), &AethexCloud::get_asset_library); ClassDB::bind_method(D_METHOD("get_telemetry"), &AethexCloud::get_telemetry); // HTTP ClassDB::bind_method(D_METHOD("make_request", "endpoint", "method", "data"), &AethexCloud::make_request, DEFVAL(HTTPClient::METHOD_GET), DEFVAL(Dictionary())); ClassDB::bind_method(D_METHOD("make_request_async", "endpoint", "method", "data", "callback"), &AethexCloud::make_request_async, DEFVAL(HTTPClient::METHOD_GET), DEFVAL(Dictionary()), DEFVAL(Callable())); // Properties ADD_PROPERTY(PropertyInfo(Variant::STRING, "gateway_url"), "set_gateway_url", "get_gateway_url"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "api_key"), "set_api_key", "get_api_key"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "auth_token"), "set_auth_token", "get_auth_token"); // Signals ADD_SIGNAL(MethodInfo("connected")); ADD_SIGNAL(MethodInfo("disconnected")); ADD_SIGNAL(MethodInfo("connection_error", PropertyInfo(Variant::STRING, "error"))); ADD_SIGNAL(MethodInfo("message_received", PropertyInfo(Variant::DICTIONARY, "message"))); // Enums BIND_ENUM_CONSTANT(STATUS_DISCONNECTED); BIND_ENUM_CONSTANT(STATUS_CONNECTING); BIND_ENUM_CONSTANT(STATUS_CONNECTED); BIND_ENUM_CONSTANT(STATUS_ERROR); } AethexCloud::AethexCloud() { singleton = this; // Default gateway URL - can be overridden in project settings gateway_url = "https://engine.aethex.dev"; // Load from project settings if available if (ProjectSettings::get_singleton()->has_setting("aethex/cloud/gateway_url")) { gateway_url = GLOBAL_GET("aethex/cloud/gateway_url"); } if (ProjectSettings::get_singleton()->has_setting("aethex/cloud/api_key")) { api_key = GLOBAL_GET("aethex/cloud/api_key"); } // Initialize sub-services auth.instantiate(); cloud_save.instantiate(); asset_library.instantiate(); telemetry.instantiate(); // Set parent reference auth->set_cloud(this); cloud_save->set_cloud(this); asset_library->set_cloud(this); telemetry->set_cloud(this); // Initialize WebSocket websocket.instantiate(); } AethexCloud::~AethexCloud() { disconnect_from_gateway(); singleton = nullptr; } void AethexCloud::set_gateway_url(const String &p_url) { gateway_url = p_url; } String AethexCloud::get_gateway_url() const { return gateway_url; } void AethexCloud::set_api_key(const String &p_key) { api_key = p_key; } String AethexCloud::get_api_key() const { return api_key; } void AethexCloud::set_auth_token(const String &p_token) { auth_token = p_token; } String AethexCloud::get_auth_token() const { return auth_token; } Error AethexCloud::connect_to_gateway() { if (status == STATUS_CONNECTED || status == STATUS_CONNECTING) { return OK; } status = STATUS_CONNECTING; // Connect WebSocket for real-time String ws_url = gateway_url.replace("https://", "wss://").replace("http://", "ws://") + "/ws"; Error err = websocket->connect_to_url(ws_url); if (err != OK) { status = STATUS_ERROR; emit_signal("connection_error", "Failed to connect to WebSocket"); return err; } return OK; } void AethexCloud::disconnect_from_gateway() { if (websocket.is_valid()) { websocket->close(); } websocket_connected = false; status = STATUS_DISCONNECTED; emit_signal("disconnected"); } AethexCloud::ConnectionStatus AethexCloud::get_status() const { return status; } bool AethexCloud::is_connected() const { return status == STATUS_CONNECTED; } Ref AethexCloud::get_auth() const { return auth; } Ref AethexCloud::get_cloud_save() const { return cloud_save; } Ref AethexCloud::get_asset_library() const { return asset_library; } Ref AethexCloud::get_telemetry() const { return telemetry; } Dictionary AethexCloud::make_request(const String &p_endpoint, HTTPClient::Method p_method, const Dictionary &p_data) { Dictionary result; result["success"] = false; Ref http; http.instantiate(); // Parse gateway URL String host = gateway_url.replace("https://", "").replace("http://", ""); bool use_ssl = gateway_url.begins_with("https://"); int port = use_ssl ? 443 : 80; // Connect Error err = http->connect_to_host(host, port); if (err != OK) { result["error"] = "Failed to connect to gateway"; return result; } // Wait for connection while (http->get_status() == HTTPClient::STATUS_CONNECTING || http->get_status() == HTTPClient::STATUS_RESOLVING) { http->poll(); OS::get_singleton()->delay_usec(100000); // 100ms } if (http->get_status() != HTTPClient::STATUS_CONNECTED) { result["error"] = "Connection failed"; return result; } // Prepare headers Vector headers; headers.push_back("Content-Type: application/json"); headers.push_back("X-Engine-Version: " + String(VERSION_FULL_CONFIG)); if (!auth_token.is_empty()) { headers.push_back("Authorization: Bearer " + auth_token); } if (!api_key.is_empty()) { headers.push_back("X-API-Key: " + api_key); } // Prepare body String body_str; if (!p_data.is_empty()) { body_str = JSON::stringify(p_data); } // Make request err = http->request(p_method, p_endpoint, headers, body_str); if (err != OK) { result["error"] = "Request failed"; return result; } // Wait for response while (http->get_status() == HTTPClient::STATUS_REQUESTING) { http->poll(); OS::get_singleton()->delay_usec(100000); } if (!http->has_response()) { result["error"] = "No response"; return result; } // Read response PackedByteArray response_body; while (http->get_status() == HTTPClient::STATUS_BODY) { http->poll(); PackedByteArray chunk = http->read_response_body_chunk(); if (chunk.size() > 0) { response_body.append_array(chunk); } OS::get_singleton()->delay_usec(10000); } // Parse response int response_code = http->get_response_code(); String response_str = String::utf8((const char *)response_body.ptr(), response_body.size()); result["status_code"] = response_code; result["success"] = response_code >= 200 && response_code < 300; // Try to parse as JSON JSON json; if (json.parse(response_str) == OK) { result["data"] = json.get_data(); } else { result["data"] = response_str; } return result; } void AethexCloud::make_request_async(const String &p_endpoint, HTTPClient::Method p_method, const Dictionary &p_data, const Callable &p_callback) { // For async requests, we'll use HTTPRequest node in actual implementation // This is a simplified version that calls synchronously // TODO: Implement proper async with threading or HTTPRequest Dictionary result = make_request(p_endpoint, p_method, p_data); if (p_callback.is_valid()) { p_callback.call(result); } } void AethexCloud::_on_websocket_message(const PackedByteArray &p_data) { String message_str = String::utf8((const char *)p_data.ptr(), p_data.size()); JSON json; if (json.parse(message_str) == OK) { Dictionary message = json.get_data(); String type = message.get("type", ""); if (type == "connected") { status = STATUS_CONNECTED; websocket_connected = true; emit_signal("connected"); // Authenticate WebSocket if we have a token if (!auth_token.is_empty()) { Dictionary auth_msg; auth_msg["type"] = "auth"; auth_msg["token"] = auth_token; auth_msg["engineVersion"] = VERSION_FULL_CONFIG; websocket->send_text(JSON::stringify(auth_msg)); } } else if (type == "auth_success") { // WebSocket authenticated } else if (type == "pong") { // Heartbeat response } else { emit_signal("message_received", message); } } } void AethexCloud::process(double p_delta) { if (!websocket.is_valid()) { return; } websocket->poll(); WebSocketPeer::State ws_state = websocket->get_ready_state(); switch (ws_state) { case WebSocketPeer::STATE_OPEN: { while (websocket->get_available_packet_count() > 0) { PackedByteArray packet = websocket->get_packet(); _on_websocket_message(packet); } } break; case WebSocketPeer::STATE_CLOSING: // Still processing break; case WebSocketPeer::STATE_CLOSED: { if (websocket_connected) { websocket_connected = false; status = STATUS_DISCONNECTED; emit_signal("disconnected"); } } break; default: break; } }