Skip to content

Commit

Permalink
feature: added subscription expiration date for premium v2 (#1261)
Browse files Browse the repository at this point in the history
* feature: added subscription expiration date for premium v2

* feature: added a check for the presence of the “services” field in the response body of the getServicesList() function

* feature: added prohibition to change location when connection is active

* bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend
  • Loading branch information
Nethius authored Dec 9, 2024
1 parent 9688a8e commit 2db9971
Show file tree
Hide file tree
Showing 11 changed files with 280 additions and 155 deletions.
7 changes: 7 additions & 0 deletions client/core/controllers/apiController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,13 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)

auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater();

if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains("services")) {
return ErrorCode::ApiServicesMissingError;
}
}

return errorCode;
}

Expand Down
1 change: 1 addition & 0 deletions client/core/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ namespace amnezia
ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106,
ApiServicesMissingError = 1107,

// QFile errors
OpenError = 1200,
Expand Down
3 changes: 2 additions & 1 deletion client/core/errorstrings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;

case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;

// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
Expand Down
2 changes: 1 addition & 1 deletion client/ui/controllers/connectionController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ void ConnectionController::openConnection()
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit updateApiConfigFromGateway();
} else if (configVersion && m_serversModel->isApiKeyExpired(serverIndex)) {
qDebug() << "attempt to update api config by end_date event";
qDebug() << "attempt to update api config by expires_at event";
if (configVersion == ApiConfigSources::Telegram) {
emit updateApiConfigFromTelegram();
} else {
Expand Down
112 changes: 78 additions & 34 deletions client/ui/models/apiServicesModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ namespace
constexpr char storeEndpoint[] = "store_endpoint";

constexpr char isAvailable[] = "is_available";

constexpr char subscription[] = "subscription";
constexpr char endDate[] = "end_date";
}

namespace serviceType
Expand All @@ -51,23 +54,23 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(rowCount()))
return QVariant();

QJsonObject service = m_services.at(index.row()).toObject();
QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject();
auto serviceType = service.value(configKey::serviceType).toString();
auto apiServiceData = m_services.at(index.row());
auto serviceType = apiServiceData.type;
auto isServiceAvailable = apiServiceData.isServiceAvailable;

switch (role) {
case NameRole: {
return serviceInfo.value(configKey::name).toString();
return apiServiceData.serviceInfo.name;
}
case CardDescriptionRole: {
auto speed = serviceInfo.value(configKey::speed).toString();
auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) {
return tr("Classic VPN for comfortable work, downloading large files and watching videos. "
"Works for any sites. Speed up to %1 MBit/s")
.arg(speed);
} else if (serviceType == serviceType::amneziaFree){
QString description = tr("VPN to access blocked sites in regions with high levels of Internet censorship. ");
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
if (isServiceAvailable) {
description += tr("<p><a style=\"color: #EB5757;\">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>");
}
return description;
Expand All @@ -83,25 +86,24 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
}
case IsServiceAvailableRole: {
if (serviceType == serviceType::amneziaFree) {
if (service.value(configKey::isAvailable).isBool() && !service.value(configKey::isAvailable).toBool()) {
if (isServiceAvailable) {
return false;
}
}
return true;
}
case SpeedRole: {
auto speed = serviceInfo.value(configKey::speed).toString();
return tr("%1 MBit/s").arg(speed);
return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed);
}
case WorkPeriodRole: {
auto timelimit = serviceInfo.value(configKey::timelimit).toString();
if (timelimit == "0") {
case TimeLimitRole: {
auto timeLimit = apiServiceData.serviceInfo.timeLimit;
if (timeLimit == "0") {
return "";
}
return tr("%1 days").arg(timelimit);
return tr("%1 days").arg(timeLimit);
}
case RegionRole: {
return serviceInfo.value(configKey::region).toString();
return apiServiceData.serviceInfo.region;
}
case FeaturesRole: {
if (serviceType == serviceType::amneziaPremium) {
Expand All @@ -113,12 +115,15 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
}
}
case PriceRole: {
auto price = serviceInfo.value(configKey::price).toString();
auto price = apiServiceData.serviceInfo.price;
if (price == "free") {
return tr("Free");
}
return tr("%1 $/month").arg(price);
}
case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
}
}

return QVariant();
Expand All @@ -128,15 +133,18 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
{
beginResetModel();

m_services.clear();

m_countryCode = data.value(configKey::userCountryCode).toString();
m_services = data.value(configKey::services).toArray();
if (m_services.isEmpty()) {
QJsonObject service;
service.insert(configKey::serviceInfo, data.value(configKey::serviceInfo));
service.insert(configKey::serviceType, data.value(configKey::serviceType));
auto services = data.value(configKey::services).toArray();

m_services.push_back(service);
if (services.isEmpty()) {
m_services.push_back(getApiServicesData(data));
m_selectedServiceIndex = 0;
} else {
for (const auto &service : services) {
m_services.push_back(getApiServicesData(service.toObject()));
}
}

endResetModel();
Expand All @@ -149,32 +157,32 @@ void ApiServicesModel::setServiceIndex(const int index)

QJsonObject ApiServicesModel::getSelectedServiceInfo()
{
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.value(configKey::serviceInfo).toObject();
auto service = m_services.at(m_selectedServiceIndex);
return service.serviceInfo.object;
}

QString ApiServicesModel::getSelectedServiceType()
{
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.value(configKey::serviceType).toString();
auto service = m_services.at(m_selectedServiceIndex);
return service.type;
}

QString ApiServicesModel::getSelectedServiceProtocol()
{
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.value(configKey::serviceProtocol).toString();
auto service = m_services.at(m_selectedServiceIndex);
return service.protocol;
}

QString ApiServicesModel::getSelectedServiceName()
{
auto modelIndex = index(m_selectedServiceIndex, 0);
return data(modelIndex, ApiServicesModel::Roles::NameRole).toString();
auto service = m_services.at(m_selectedServiceIndex);
return service.serviceInfo.name;
}

QJsonArray ApiServicesModel::getSelectedServiceCountries()
{
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.value(configKey::availableCountries).toArray();
auto service = m_services.at(m_selectedServiceIndex);
return service.availableCountries;
}

QString ApiServicesModel::getCountryCode()
Expand All @@ -184,8 +192,8 @@ QString ApiServicesModel::getCountryCode()

QString ApiServicesModel::getStoreEndpoint()
{
QJsonObject service = m_services.at(m_selectedServiceIndex).toObject();
return service.value(configKey::storeEndpoint).toString();
auto service = m_services.at(m_selectedServiceIndex);
return service.storeEndpoint;
}

QVariant ApiServicesModel::getSelectedServiceData(const QString roleString)
Expand All @@ -209,10 +217,46 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
roles[ServiceDescriptionRole] = "serviceDescription";
roles[IsServiceAvailableRole] = "isServiceAvailable";
roles[SpeedRole] = "speed";
roles[WorkPeriodRole] = "workPeriod";
roles[TimeLimitRole] = "timeLimit";
roles[RegionRole] = "region";
roles[FeaturesRole] = "features";
roles[PriceRole] = "price";
roles[EndDateRole] = "endDate";

return roles;
}

ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJsonObject &data)
{
auto serviceInfo = data.value(configKey::serviceInfo).toObject();
auto serviceType = data.value(configKey::serviceType).toString();
auto serviceProtocol = data.value(configKey::serviceProtocol).toString();
auto availableCountries = data.value(configKey::availableCountries).toArray();

auto subscriptionObject = data.value(configKey::subscription).toObject();

ApiServicesData serviceData;
serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString();
serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString();
serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString();
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();

serviceData.type = serviceType;
serviceData.protocol = serviceProtocol;

serviceData.storeEndpoint = serviceInfo.value(configKey::storeEndpoint).toString();

if (serviceInfo.value(configKey::isAvailable).isBool()) {
serviceData.isServiceAvailable = data.value(configKey::isAvailable).toBool();
} else {
serviceData.isServiceAvailable = true;
}

serviceData.serviceInfo.object = serviceInfo;
serviceData.availableCountries = availableCountries;

serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString();

return serviceData;
}
40 changes: 37 additions & 3 deletions client/ui/models/apiServicesModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <QAbstractListModel>
#include <QJsonArray>
#include <QJsonObject>

class ApiServicesModel : public QAbstractListModel
{
Expand All @@ -15,10 +16,11 @@ class ApiServicesModel : public QAbstractListModel
ServiceDescriptionRole,
IsServiceAvailableRole,
SpeedRole,
WorkPeriodRole,
TimeLimitRole,
RegionRole,
FeaturesRole,
PriceRole
PriceRole,
EndDateRole
};

explicit ApiServicesModel(QObject *parent = nullptr);
Expand Down Expand Up @@ -48,8 +50,40 @@ public slots:
QHash<int, QByteArray> roleNames() const override;

private:
struct ServiceInfo
{
QString name;
QString speed;
QString timeLimit;
QString region;
QString price;

QJsonObject object;
};

struct Subscription
{
QString endDate;
};

struct ApiServicesData
{
bool isServiceAvailable;

QString type;
QString protocol;
QString storeEndpoint;

ServiceInfo serviceInfo;
Subscription subscription;

QJsonArray availableCountries;
};

ApiServicesData getApiServicesData(const QJsonObject &data);

QString m_countryCode;
QJsonArray m_services;
QVector<ApiServicesData> m_services;

int m_selectedServiceIndex;
};
Expand Down
33 changes: 27 additions & 6 deletions client/ui/models/servers_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace
constexpr char serviceProtocol[] = "service_protocol";

constexpr char publicKeyInfo[] = "public_key";
constexpr char endDate[] = "end_date";
constexpr char expiresAt[] = "expires_at";
}
}

Expand All @@ -39,6 +39,9 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
emit ServersModel::defaultServerNameChanged();
updateDefaultServerContainersModel();
});

connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
connect(this, &ServersModel::dataChanged, this, &ServersModel::processedServerChanged);
}

int ServersModel::rowCount(const QModelIndex &parent) const
Expand Down Expand Up @@ -79,6 +82,12 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int
return true;
}

bool ServersModel::setData(const int index, const QVariant &value, int role)
{
QModelIndex modelIndex = this->index(index);
return setData(modelIndex, value, role);
}

QVariant ServersModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) {
Expand Down Expand Up @@ -679,6 +688,18 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
return {};
}

bool ServersModel::setProcessedServerData(const QString &roleString, const QVariant &value)
{
const auto roles = roleNames();
for (auto it = roles.begin(); it != roles.end(); it++) {
if (QString(it.value()) == roleString) {
return setData(m_processedServerIndex, value, it.key());
}
}

return false;
}

bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
{
auto server = m_servers.at(m_defaultServerIndex).toObject();
Expand Down Expand Up @@ -718,18 +739,18 @@ bool ServersModel::isApiKeyExpired(const int serverIndex)
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();

auto publicKeyInfo = apiConfig.value(configKey::publicKeyInfo).toObject();
const QString endDate = publicKeyInfo.value(configKey::endDate).toString();
if (endDate.isEmpty()) {
publicKeyInfo.insert(configKey::endDate, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
const QString expiresAt = publicKeyInfo.value(configKey::expiresAt).toString();
if (expiresAt.isEmpty()) {
publicKeyInfo.insert(configKey::expiresAt, QDateTime::currentDateTimeUtc().addDays(1).toString(Qt::ISODate));
apiConfig.insert(configKey::publicKeyInfo, publicKeyInfo);
serverConfig.insert(configKey::apiConfig, apiConfig);
editServer(serverConfig, serverIndex);

return false;
}

auto endDateDateTime = QDateTime::fromString(endDate, Qt::ISODate).toUTC();
if (endDateDateTime < QDateTime::currentDateTimeUtc()) {
auto expiresAtDateTime = QDateTime::fromString(expiresAt, Qt::ISODate).toUTC();
if (expiresAtDateTime < QDateTime::currentDateTimeUtc()) {
return true;
}
return false;
Expand Down
Loading

0 comments on commit 2db9971

Please sign in to comment.