/* SPDX-FileCopyrightText: 2026 Project Tick
* SPDX-FileContributor: Project Tick
* SPDX-License-Identifier: GPL-3.0-or-later
*
* MeshMC - A Custom Launcher for Minecraft
* Copyright (C) 2026 Project Tick
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include "katabasis/PollServer.h"
#include "JsonResponse.h"
namespace
{
QMap toVerificationParams(const QVariantMap& map)
{
QMap params;
for (QVariantMap::const_iterator i = map.constBegin();
i != map.constEnd(); ++i) {
params[i.key()] = i.value().toString();
}
return params;
}
} // namespace
namespace Katabasis
{
PollServer::PollServer(QNetworkAccessManager* manager,
const QNetworkRequest& request,
const QByteArray& payload, int expiresIn,
QObject* parent)
: QObject(parent), manager_(manager), request_(request),
payload_(payload), expiresIn_(expiresIn)
{
expirationTimer.setTimerType(Qt::VeryCoarseTimer);
expirationTimer.setInterval(expiresIn * 1000);
expirationTimer.setSingleShot(true);
connect(&expirationTimer, SIGNAL(timeout()), this,
SLOT(onExpiration()));
expirationTimer.start();
pollTimer.setTimerType(Qt::VeryCoarseTimer);
pollTimer.setInterval(5 * 1000);
pollTimer.setSingleShot(true);
connect(&pollTimer, SIGNAL(timeout()), this, SLOT(onPollTimeout()));
}
int PollServer::interval() const
{
return pollTimer.interval() / 1000;
}
void PollServer::setInterval(int interval)
{
pollTimer.setInterval(interval * 1000);
}
void PollServer::startPolling()
{
if (expirationTimer.isActive()) {
pollTimer.start();
}
}
void PollServer::onPollTimeout()
{
qDebug() << "PollServer::onPollTimeout: retrying";
QNetworkReply* reply = manager_->post(request_, payload_);
connect(reply, SIGNAL(finished()), this, SLOT(onReplyFinished()));
}
void PollServer::onExpiration()
{
pollTimer.stop();
emit serverClosed(false);
}
void PollServer::onReplyFinished()
{
QNetworkReply* reply = qobject_cast(sender());
if (!reply) {
qDebug() << "PollServer::onReplyFinished: reply is null";
return;
}
QByteArray replyData = reply->readAll();
QMap params =
toVerificationParams(parseJsonResponse(replyData));
// Dump replyData
// SENSITIVE DATA in RelWithDebInfo or Debug builds
// qDebug() << "PollServer::onReplyFinished: replyData\n";
// qDebug() << QString( replyData );
if (reply->error() == QNetworkReply::TimeoutError) {
// rfc8628#section-3.2
// "On encountering a connection timeout, clients MUST unilaterally
// reduce their polling frequency before retrying. The use of an
// exponential backoff algorithm to achieve this, such as doubling
// the polling interval on each such connection timeout, is
// RECOMMENDED."
setInterval(interval() * 2);
pollTimer.start();
} else {
QString error = params.value("error");
if (error == "slow_down") {
// rfc8628#section-3.2
// "A variant of 'authorization_pending', the authorization
// request is still pending and polling should continue, but the
// interval MUST be increased by 5 seconds for this and all
// subsequent requests."
setInterval(interval() + 5);
pollTimer.start();
} else if (error == "authorization_pending") {
// keep trying - rfc8628#section-3.2
// "The authorization request is still pending as the end user
// hasn't yet completed the user-interaction steps
// (Section 3.3)."
pollTimer.start();
} else {
expirationTimer.stop();
emit serverClosed(true);
// let O2 handle the other cases
emit verificationReceived(params);
}
}
reply->deleteLater();
}
} // namespace Katabasis