#include "api.h" #include #include #include #include #include #include "codes.h" #include "finalaction.h" constexpr const char* json = "application/json"; constexpr const char* urlEncoded = "application/x-www-form-urlencoded"; const std::map testStructure({ {"type", QMetaType::QString}, {"version", QMetaType::QString}, }); const std::map resultStructure({ {"result", QMetaType::LongLong}, }); const std::map tokensStructure({ {"accessToken", QMetaType::QString}, {"renewToken", QMetaType::QString} }); struct NetworkReplyDeleter { void operator () (QNetworkReply* reply) { reply->deleteLater(); } }; API::API(const QUrl& address, QObject* parent): QObject(parent), address(address), network(), state(NoServer) {} QUrl API::getAddress() const { return address; } API::State API::getState() const { return state; } void API::setTokens(const QString access, const QString &renew) { accessToken = access; renewToken = renew; setState(Authenticating); //dont forget to remove QTimer::singleShot(1000, this, [this] () { setState(Authenticated); }); } void API::setAddress(const QUrl& path) { if (address == path) return; if (state == Authenticated) { //do something } address = path; emit addressChanged(address); setState(address.isEmpty() ? NoServer : NotAuthenticated); } std::optional API::readResult(QNetworkReply* reply) { QVariant contentType = reply->header(QNetworkRequest::ContentTypeHeader); if (! contentType.isValid() || contentType.userType() != QMetaType::QString || contentType.toString() != json ) return std::nullopt; QByteArray data = reply->readAll(); QJsonDocument document = QJsonDocument::fromJson(data); if (!document.isObject()) return std::nullopt; QJsonObject object = document.object(); return object.toVariantMap(); } bool API::validateResponse(const std::optional& data, const std::map& structure) { if (!data.has_value()) return false; for (const std::pair& pair : structure) { QVariantMap::ConstIterator itr = data->find(pair.first); if (itr == data->end()) return false; if (itr->userType() != pair.second) return false; } return true; } void API::setState(State newState) { if (newState == state) return; state = newState; emit stateChanged(state); } void API::test(const QString& path, const QJSValue& finished) { qDebug() << "Testing" << path; if (state == Offline) return callCallback(finished, "Need to be online to test"); QUrl address(path); QNetworkRequest request(path + "/info"); request.setHeader(QNetworkRequest::ContentTypeHeader, json); QNetworkReply* reply = network.get(request); connect(reply, &QNetworkReply::finished, std::bind(&API::onTestFinished, this, reply, address, finished) ); } void API::onTestFinished(QNetworkReply* reply, const QUrl& addr, const QJSValue& finished) { std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); if (error != QNetworkReply::NoError) return callCallback(finished, reply->errorString()); std::optional data = readResult(reply); if (!validateResponse(data, testStructure)) return callCallback(finished, "Malformed response"); QString type = data->value("type").toString(); if (type != "pica") return callCallback(finished, "server of this type (" + type + ") is not supported"); QString version = data->value("version").toString(); if (version != "0.0.1") return callCallback(finished, "server of this version (" + version + ") is not supported"); callCallback(finished, QString(), {QJSValue(true)}); address = ""; //to provoke singal change even if it's the same server setAddress(addr); } void API::sendRegister(const QString& login, const QString& password, const QJSValue& finished) { qDebug() << "Registering..."; if (state != NotAuthenticated) return callCallback(finished, "Can not register in current state"); QUrlQuery params({ {"login", login}, {"password", password} }); QString path = address.path(); QUrl regUrl = address; regUrl.setPath(path + "/register"); QNetworkRequest request(regUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded); QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8()); connect(reply, &QNetworkReply::finished, std::bind(&API::onRegisterFinished, this, reply, finished) ); } void API::sendLogin(const QString& login, const QString& password, const QJSValue& finished) { qDebug() << "Logging in..."; if (state != NotAuthenticated) return callCallback(finished, "Can not register in current state"); QUrlQuery params({ {"login", login}, {"password", password} }); QString path = address.path(); QUrl url = address; url.setPath(path + "/login"); QNetworkRequest request(url); request.setHeader(QNetworkRequest::ContentTypeHeader, urlEncoded); setState(Authenticating); QNetworkReply* reply = network.post(request, params.toString(QUrl::FullyEncoded).toUtf8()); connect(reply, &QNetworkReply::finished, std::bind(&API::onLoginFinished, this, reply, finished) ); } void API::onRegisterFinished(QNetworkReply* reply, const QJSValue& finished) const { std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); std::optional data = readResult(reply); std::optional code; QString detail; bool success = false; if (validateResponse(data, resultStructure)) { code = Codes::convertRegister(data->value("result").toInt()); success = code.value() == Codes::Register::success; if (!success) detail = Codes::description(code.value()); } if (error != QNetworkReply::NoError) return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail)); if (!code) return callCallback(finished, "Malformed result"); callCallback(finished, detail, {QJSValue(success)}); } void API::onLoginFinished(QNetworkReply* reply, const QJSValue& finished) { State state = NotAuthenticated; FinalAction action([this, &state]() { //this should be executed on leaving the function in any way setState(state); //setting the state as a result of this object destruction }); //this way any error will result in NotAuthenticated, and on success it should be Authenticated std::unique_ptr rpl(reply); QNetworkReply::NetworkError error = reply->error(); std::optional data = readResult(reply); std::optional code; QString detail; bool success = false; if (validateResponse(data, resultStructure)) { code = Codes::convertLogin(data->value("result").toInt()); success = code.value() == Codes::Login::success; if (!success) detail = Codes::description(code.value()); } if (error != QNetworkReply::NoError) return callCallback(finished, reply->errorString() + (success ? "" : ": " + detail)); if (!code) return callCallback(finished, "Malformed result"); if (!validateResponse(data, tokensStructure)) return callCallback(finished, "Malformed result: missing tokens"); callCallback(finished, detail, {QJSValue(success)}); state = Authenticated; accessToken = data->value("accessToken").toString(); renewToken = data->value("renewToken").toString(); emit storeTokens(accessToken, renewToken); } void API::callCallback(const QJSValue& callback, const QString& error, const QJSValueList& arguments) const { if (callback.isCallable()) { if (error.isEmpty()) callback.call(QJSValueList({QJSValue(QJSValue::NullValue)}) + arguments); else callback.call({QJSValue(error)}); } }