From e98c11079a0c2e16c63222fd5b745c5dcde5d094 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Thu, 15 Jan 2026 23:05:36 +0400 Subject: [PATCH] feat: enhance ConfigManager with fetch capabilities for Xray configuration --- client/core/local-proxy/configmanager.cpp | 195 ++++++++++++++++++ client/core/local-proxy/configmanager.h | 2 + client/core/local-proxy/proxyservice.cpp | 2 +- .../ui/qml/Pages2/PageSettingsLocalProxy.qml | 5 + 4 files changed, 203 insertions(+), 1 deletion(-) diff --git a/client/core/local-proxy/configmanager.cpp b/client/core/local-proxy/configmanager.cpp index b9e64a7e3..8ed94431a 100644 --- a/client/core/local-proxy/configmanager.cpp +++ b/client/core/local-proxy/configmanager.cpp @@ -1,9 +1,13 @@ #include "configmanager.h" #include "containers/containers_defs.h" +#include "core/api/apiDefs.h" #include "core/api/apiUtils.h" +#include "core/controllers/gatewayController.h" +#include "core/defs.h" #include "proxylogger.h" #include "settings.h" +#include "version.h" #include #include @@ -11,7 +15,9 @@ #include #include #include +#include #include +#include ConfigManager::ConfigManager(const std::shared_ptr &settings) : m_settings(settings) @@ -19,6 +25,21 @@ ConfigManager::ConfigManager(const std::shared_ptr &settings) ProxyLogger::getInstance().debug("ConfigManager initialized (Settings-backed)"); } +namespace { +namespace gateway_key { +constexpr char apiConfig[] = "api_config"; +constexpr char authData[] = "auth_data"; +constexpr char userCountryCode[] = "user_country_code"; +constexpr char serviceType[] = "service_type"; +constexpr char serviceProtocol[] = "service_protocol"; +constexpr char uuid[] = "installation_uuid"; +constexpr char osVersion[] = "os_version"; +constexpr char appVersion[] = "app_version"; +constexpr char publicKey[] = "public_key"; +constexpr char vless[] = "vless"; +} // namespace gateway_key +} // namespace + std::optional ConfigManager::buildConfig(QString &errorDescription) const { errorDescription.clear(); @@ -81,6 +102,68 @@ std::optional ConfigManager::buildConfig(QString &err return data; } +std::optional ConfigManager::buildConfigWithFetch(QString &errorDescription) const +{ + errorDescription.clear(); + + if (!m_settings) { + const QString message = QStringLiteral("Settings backend is not available"); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + const QString ownerUuid = m_settings->localProxyOwnerUuid(); + if (ownerUuid.isEmpty()) { + const QString message = QStringLiteral("Local proxy owner UUID is not configured"); + ProxyLogger::getInstance().warning(message); + errorDescription = message; + return std::nullopt; + } + + const auto ownerServer = findServerByUuid(ownerUuid); + if (!ownerServer) { + const QString message = QStringLiteral("Owner server with UUID %1 not found in Settings").arg(ownerUuid); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + if (!apiUtils::isPremiumServer(*ownerServer)) { + const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable") + .arg(ownerServer->value(amnezia::config_key::name).toString()); + ProxyLogger::getInstance().warning(message); + errorDescription = message; + return std::nullopt; + } + + auto serializedConfig = extractSerializedXrayConfig(*ownerServer); + if (!serializedConfig || serializedConfig->isEmpty()) { + auto fetchedConfig = fetchSerializedXrayConfigFromGateway(*ownerServer, errorDescription); + if (!fetchedConfig || fetchedConfig->isEmpty()) { + return std::nullopt; + } + serializedConfig = fetchedConfig; + } + + QJsonParseError parseError; + const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) { + const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString()); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + ConfigData data; + data.ownerUuid = ownerUuid; + data.serverName = ownerServer->value(amnezia::config_key::name).toString(); + data.serializedConfig = *serializedConfig; + data.parsedConfig = doc.object(); + + return data; +} + bool ConfigManager::writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const { errorDescription.clear(); @@ -183,6 +266,118 @@ std::optional ConfigManager::extractSerializedXrayConfig(const QJsonObj return std::nullopt; } +std::optional ConfigManager::fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const +{ + errorDescription.clear(); + + if (!m_settings) { + const QString message = QStringLiteral("Settings backend is not available"); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + const QJsonObject apiConfig = server.value(gateway_key::apiConfig).toObject(); + if (apiConfig.isEmpty()) { + const QString message = QStringLiteral("Server API config is missing"); + ProxyLogger::getInstance().warning(message); + errorDescription = message; + return std::nullopt; + } + + const QString userCountryCode = apiConfig.value(gateway_key::userCountryCode).toString(); + const QString serviceType = apiConfig.value(gateway_key::serviceType).toString(); + if (userCountryCode.isEmpty() || serviceType.isEmpty()) { + const QString message = QStringLiteral("Server API config lacks service identifiers"); + ProxyLogger::getInstance().warning(message); + errorDescription = message; + return std::nullopt; + } + + QJsonObject apiPayload; + apiPayload[gateway_key::osVersion] = QSysInfo::productType(); + apiPayload[gateway_key::appVersion] = QString(APP_VERSION); + + const QString appLanguage = m_settings->getAppLanguage().name().split("_").first(); + if (!appLanguage.isEmpty()) { + apiPayload[apiDefs::key::appLanguage] = appLanguage; + } + + apiPayload[gateway_key::uuid] = m_settings->getInstallationUuid(true); + apiPayload[gateway_key::userCountryCode] = userCountryCode; + apiPayload[gateway_key::serviceType] = serviceType; + apiPayload[gateway_key::serviceProtocol] = gateway_key::vless; + apiPayload[gateway_key::publicKey] = QUuid::createUuid().toString(QUuid::WithoutBraces); + + const QJsonObject authData = server.value(gateway_key::authData).toObject(); + if (!authData.isEmpty()) { + apiPayload[gateway_key::authData] = authData; + } + + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); + + QByteArray responseBody; + const amnezia::ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + if (errorCode != amnezia::ErrorCode::NoError) { + const QString message = QStringLiteral("Gateway request failed with error code %1").arg(static_cast(errorCode)); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + QJsonParseError responseError; + const QJsonDocument responseDoc = QJsonDocument::fromJson(responseBody, &responseError); + if (responseError.error != QJsonParseError::NoError || !responseDoc.isObject()) { + const QString message = QStringLiteral("Failed to parse gateway response: %1").arg(responseError.errorString()); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + QString data = responseDoc.object().value(amnezia::config_key::config).toString(); + if (data.isEmpty()) { + const QString message = QStringLiteral("Gateway response lacks config payload"); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + data.replace("vpn://", ""); + QByteArray decoded = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + if (decoded.isEmpty()) { + const QString message = QStringLiteral("Gateway config payload is empty"); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + const QByteArray uncompressed = qUncompress(decoded); + if (!uncompressed.isEmpty()) { + decoded = uncompressed; + } + + QJsonParseError configError; + const QJsonDocument configDoc = QJsonDocument::fromJson(decoded, &configError); + if (configError.error != QJsonParseError::NoError || !configDoc.isObject()) { + const QString message = QStringLiteral("Failed to parse gateway config JSON: %1").arg(configError.errorString()); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + const auto serializedConfig = extractSerializedXrayConfig(configDoc.object()); + if (!serializedConfig || serializedConfig->isEmpty()) { + const QString message = QStringLiteral("Gateway response lacks Xray last_config payload"); + ProxyLogger::getInstance().error(message); + errorDescription = message; + return std::nullopt; + } + + ProxyLogger::getInstance().info("Fetched Xray config from gateway"); + return serializedConfig; +} + QString ConfigManager::tempDirectory() const { const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); diff --git a/client/core/local-proxy/configmanager.h b/client/core/local-proxy/configmanager.h index 768f4a0f3..261a7345f 100644 --- a/client/core/local-proxy/configmanager.h +++ b/client/core/local-proxy/configmanager.h @@ -20,6 +20,7 @@ public: explicit ConfigManager(const std::shared_ptr &settings); std::optional buildConfig(QString &errorDescription) const; + std::optional buildConfigWithFetch(QString &errorDescription) const; bool writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const; bool removeTempConfig() const; QString tempConfigPath() const; @@ -27,6 +28,7 @@ public: private: std::optional findServerByUuid(const QString &uuid) const; std::optional extractSerializedXrayConfig(const QJsonObject &server) const; + std::optional fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const; QString tempDirectory() const; std::shared_ptr m_settings; diff --git a/client/core/local-proxy/proxyservice.cpp b/client/core/local-proxy/proxyservice.cpp index 6286ee089..6ab237026 100644 --- a/client/core/local-proxy/proxyservice.cpp +++ b/client/core/local-proxy/proxyservice.cpp @@ -28,7 +28,7 @@ QJsonObject ProxyService::getConfig() } QString error; - const auto configData = m_configManager->buildConfig(error); + const auto configData = m_configManager->buildConfigWithFetch(error); if (!configData) { logConfigError(error); return {}; diff --git a/client/ui/qml/Pages2/PageSettingsLocalProxy.qml b/client/ui/qml/Pages2/PageSettingsLocalProxy.qml index 2008053f0..0bfd17d62 100644 --- a/client/ui/qml/Pages2/PageSettingsLocalProxy.qml +++ b/client/ui/qml/Pages2/PageSettingsLocalProxy.qml @@ -195,6 +195,11 @@ PageType { portField.syncPortValue() } } + + function onLocalProxyStartFailed(message) { + PageController.showNotificationMessage(message) + localProxySwitch.syncState() + } } Connections {