File indexing completed on 2025-02-16 04:23:13

0001 /*
0002     SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "nextpushprovider.h"
0007 #include "client.h"
0008 #include "logging.h"
0009 #include "message.h"
0010 
0011 #include <QHostInfo>
0012 #include <QJsonDocument>
0013 #include <QJsonObject>
0014 #include <QNetworkReply>
0015 #include <QSettings>
0016 
0017 using namespace KUnifiedPush;
0018 
0019 NextPushProvider::NextPushProvider(QObject *parent)
0020     : AbstractPushProvider(Id, parent)
0021 {
0022     connect(&m_sseStream, &ServerSentEventsStream::messageReceived, this, [this](const SSEMessage &sse) {
0023         qCDebug(Log) << sse.event << sse.data;
0024         if (sse.event == "message") {
0025             QJsonObject msgObj = QJsonDocument::fromJson(sse.data).object();
0026             Message msg;
0027             msg.clientRemoteId = msgObj.value(QLatin1String("token")).toString();
0028             msg.content = QByteArray::fromBase64(msgObj.value(QLatin1String("message")).toString().toUtf8());
0029             Q_EMIT messageReceived(msg);
0030         }
0031     });
0032 }
0033 
0034 NextPushProvider::~NextPushProvider() = default;
0035 
0036 bool NextPushProvider::loadSettings(const QSettings &settings)
0037 {
0038     m_appPassword = settings.value(QStringLiteral("AppPassword"), QString()).toString();
0039     m_url = QUrl(settings.value(QStringLiteral("Url"), QString()).toString());
0040     m_userName = settings.value(QStringLiteral("Username"), QString()).toString();
0041     m_deviceId = settings.value(QStringLiteral("DeviceId"), QString()).toString();
0042     qCDebug(Log) << m_url << m_userName << m_deviceId;
0043     return m_url.isValid() && !m_appPassword.isEmpty() && !m_userName.isEmpty();
0044 }
0045 
0046 void NextPushProvider::connectToProvider()
0047 {
0048     qCDebug(Log) << m_deviceId;
0049 
0050     if (m_deviceId.isEmpty()) {
0051         auto req = prepareRequest("device");
0052         req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0053 
0054         QJsonObject content;
0055         content.insert(QLatin1String("deviceName"), QHostInfo::localHostName());
0056         auto reply = nam()->put(req, QJsonDocument(content).toJson(QJsonDocument::Compact));
0057         connect(reply, &QNetworkReply::finished, this, [reply, this]() {
0058             reply->deleteLater();
0059             if (reply->error() != QNetworkReply::NoError) {
0060                 qCWarning(Log) << reply->errorString();
0061                 Q_EMIT disconnected(ProviderRejected, reply->errorString());
0062                 return;
0063             }
0064 
0065             const auto content = QJsonDocument::fromJson(reply->readAll()).object();
0066             qCDebug(Log) << QJsonDocument(content).toJson(QJsonDocument::Compact);
0067             // TODO check "success" field
0068             m_deviceId = content.value(QLatin1String("deviceId")).toString();
0069 
0070             QSettings settings;
0071             settings.setValue(QStringLiteral("NextPush/DeviceId"), m_deviceId);
0072             waitForMessage();
0073         });
0074     } else {
0075         waitForMessage();
0076     }
0077 }
0078 
0079 void NextPushProvider::disconnectFromProvider()
0080 {
0081     if (m_sseReply) {
0082         m_sseReply->abort();
0083     } else {
0084         Q_EMIT disconnected(NoError);
0085     }
0086 }
0087 
0088 void NextPushProvider::registerClient(const Client &client)
0089 {
0090     qCDebug(Log) << client.serviceName;
0091     auto req = prepareRequest("app");
0092     req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
0093 
0094     QJsonObject content;
0095     content.insert(QLatin1String("deviceId"), m_deviceId);
0096     content.insert(QLatin1String("appName"), client.serviceName);
0097     auto reply = nam()->put(req, QJsonDocument(content).toJson(QJsonDocument::Compact));
0098     connect(reply, &QNetworkReply::finished, this, [reply, this, client]() {
0099         reply->deleteLater();
0100         if (reply->error() != QNetworkReply::NoError) {
0101             Q_EMIT clientRegistered(client, TransientNetworkError, reply->errorString());
0102             return;
0103         }
0104 
0105         const auto content = QJsonDocument::fromJson(reply->readAll()).object();
0106         qCDebug(Log) << QJsonDocument(content).toJson(QJsonDocument::Compact);
0107         if (!content.value(QLatin1String("success")).toBool()) {
0108             Q_EMIT clientRegistered(client, ProviderRejected, QString()); // TODO do we get an error message in this case?
0109             return;
0110         }
0111         auto newClient = client;
0112         newClient.remoteId = content.value(QLatin1String("token")).toString();
0113 
0114         QUrl endpointUrl = m_url;
0115         auto path = endpointUrl.path();
0116         path += QLatin1String("/index.php/apps/uppush/push/") + newClient.remoteId;
0117         endpointUrl.setPath(path);
0118         newClient.endpoint = endpointUrl.toString();
0119 
0120         Q_EMIT clientRegistered(newClient);
0121     });
0122 }
0123 
0124 void NextPushProvider::unregisterClient(const Client &client)
0125 {
0126     qCDebug(Log) << client.serviceName << client.remoteId;
0127     auto req = prepareRequest("app", client.remoteId);
0128     auto reply = nam()->deleteResource(req);
0129     connect(reply, &QNetworkReply::finished, this, [reply, this, client]() {
0130         reply->deleteLater();
0131         if (reply->error() != QNetworkReply::NoError) {
0132             qCWarning(Log) << reply->errorString();
0133             Q_EMIT clientUnregistered(client, TransientNetworkError);
0134         } else {
0135             qCDebug(Log) << "application deleted";
0136             Q_EMIT clientUnregistered(client);
0137         }
0138     });
0139 }
0140 
0141 void NextPushProvider::waitForMessage()
0142 {
0143     qCDebug(Log);
0144     auto req = prepareRequest("device", m_deviceId);
0145     auto reply = nam()->get(req);
0146     connect(reply, &QNetworkReply::finished, this, [reply, this]() {
0147         reply->deleteLater();
0148         if (reply->error() != QNetworkReply::NoError) {
0149             qCWarning(Log) << reply->errorString();
0150             Q_EMIT disconnected(TransientNetworkError, reply->errorString());
0151         } else {
0152             qCDebug(Log) << "GET finished";
0153             Q_EMIT disconnected(NoError);
0154         }
0155     });
0156     m_sseStream.read(reply);
0157     m_sseReply = reply;
0158     Q_EMIT connected();
0159 }
0160 
0161 QNetworkRequest NextPushProvider::prepareRequest(const char *restCmd, const QString &restArg) const
0162 {
0163     QUrl url = m_url;
0164     auto path = url.path();
0165     path += QLatin1String("/index.php/apps/uppush/") + QLatin1String(restCmd) + QLatin1Char('/') + restArg;
0166     url.setPath(path);
0167 
0168     QNetworkRequest req(url);
0169     req.setRawHeader("Authorization", "Basic " + QByteArray(m_userName.toUtf8() + ':' + m_appPassword.toUtf8()).toBase64());
0170     return req;
0171 }