feat: enhance ConfigManager with fetch capabilities for Xray configuration

This commit is contained in:
aiamnezia
2026-01-15 23:05:36 +04:00
parent 84d908d4d8
commit e98c11079a
4 changed files with 203 additions and 1 deletions

View File

@@ -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 <QDir>
#include <QFile>
@@ -11,7 +15,9 @@
#include <QJsonDocument>
#include <QJsonParseError>
#include <QSaveFile>
#include <QSysInfo>
#include <QStandardPaths>
#include <QUuid>
ConfigManager::ConfigManager(const std::shared_ptr<Settings> &settings)
: m_settings(settings)
@@ -19,6 +25,21 @@ ConfigManager::ConfigManager(const std::shared_ptr<Settings> &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::ConfigData> ConfigManager::buildConfig(QString &errorDescription) const
{
errorDescription.clear();
@@ -81,6 +102,68 @@ std::optional<ConfigManager::ConfigData> ConfigManager::buildConfig(QString &err
return data;
}
std::optional<ConfigManager::ConfigData> 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<QString> ConfigManager::extractSerializedXrayConfig(const QJsonObj
return std::nullopt;
}
std::optional<QString> 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<int>(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);

View File

@@ -20,6 +20,7 @@ public:
explicit ConfigManager(const std::shared_ptr<Settings> &settings);
std::optional<ConfigData> buildConfig(QString &errorDescription) const;
std::optional<ConfigData> 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<QJsonObject> findServerByUuid(const QString &uuid) const;
std::optional<QString> extractSerializedXrayConfig(const QJsonObject &server) const;
std::optional<QString> fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const;
QString tempDirectory() const;
std::shared_ptr<Settings> m_settings;

View File

@@ -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 {};

View File

@@ -195,6 +195,11 @@ PageType {
portField.syncPortValue()
}
}
function onLocalProxyStartFailed(message) {
PageController.showNotificationMessage(message)
localProxySwitch.syncState()
}
}
Connections {