KUnifiedPush

autopushprovider.cpp
1/*
2 SPDX-FileCopyrightText: 2025 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "autopushprovider.h"
7#include "client.h"
8#include "logging.h"
9#include "message.h"
10
11#include <QJsonArray>
12#include <QJsonDocument>
13#include <QJsonObject>
14#include <QNetworkReply>
15#include <QSettings>
16#include <QUrlQuery>
17#include <QWebSocket>
18
19using namespace Qt::Literals;
20using namespace KUnifiedPush;
21
22AutopushProvider::AutopushProvider(QObject *parent)
23 : AbstractPushProvider(Id, parent)
24{
25 qCDebug(Log);
26
27 m_pingTimer.setTimerType(Qt::VeryCoarseTimer);
28 m_pingTimer.setInterval(std::chrono::minutes(5));
29}
30
32{
33 m_url = QUrl(settings.value("Url", QString()).toString());
34
35 QSettings internal;
36 internal.beginGroup(providerId() + "-internal"_L1);
37 m_uaid = internal.value("UAID", QString()).toString();
38
39 qCDebug(Log) << m_url << m_uaid;
40 return m_url.isValid();
41}
42
43void AutopushProvider::connectToProvider([[maybe_unused]] Urgency urgency)
44{
45 qCDebug(Log);
46 m_socket = new QWebSocket();
47 m_socket->setParent(this);
48 connect(m_socket, &QWebSocket::stateChanged, this, [this](auto state) {
49 qCDebug(Log) << m_socket->state();
51 QJsonObject msg {{
52 { "messageType"_L1, "hello"_L1 },
53 }};
54 if (!m_uaid.isEmpty()) {
55 msg.insert("uaid"_L1, m_uaid);
56 }
57 sendMessage(msg);
58 m_pingTimer.start();
59 } else if (state == QAbstractSocket::UnconnectedState) {
60 Q_EMIT disconnected(TransientNetworkError, m_socket->errorString());
61 m_socket->deleteLater();
62 m_socket = nullptr;
63 m_pingTimer.stop();
64 }
65 });
66 connect(m_socket, &QWebSocket::textMessageReceived, this, &AutopushProvider::wsMessageReceived);
67 connect(&m_pingTimer, &QTimer::timeout, m_socket, [this]() { m_socket->ping(); });
68
69 auto wsUrl = m_url;
70 if (wsUrl.scheme() == QLatin1String("https")) {
71 wsUrl.setScheme(QStringLiteral("wss"));
72 } else if (wsUrl.scheme() == QLatin1String("http")) {
73 wsUrl.setScheme(QStringLiteral("ws"));
74 } else {
75 qCWarning(Log) << "Unknown URL scheme:" << m_url;
77 return;
78 }
79
80 m_socket->open(wsUrl);
81}
82
84{
85 m_socket->close();
86}
87
88void AutopushProvider::sendMessage(const QJsonObject &msg)
89{
91}
92
93void AutopushProvider::wsMessageReceived(const QString &msg)
94{
95 qCDebug(Log) << msg;
96 const auto msgObj = QJsonDocument::fromJson(msg.toUtf8()).object();
97 const auto msgType = msgObj.value("messageType"_L1).toString();
98 const auto status = msgObj.value("status"_L1).toInt();
99
100 if (msgType == "hello"_L1 && status == 200) {
101 m_uaid = msgObj.value("uaid"_L1).toString();
102 storeState();
104 return;
105 }
106
107 if (msgType == "register"_L1) {
108 const auto channelId = msgObj.value("channelID"_L1).toString();
109 if (m_currentClient.remoteId != channelId) {
110 qCCritical(Log) << "Got registration for a different client!" << channelId << m_currentClient.remoteId;
111 return;
112 }
113 m_currentClient.endpoint = msgObj.value("pushEndpoint"_L1).toString();
114 Q_EMIT clientRegistered(m_currentClient, status == 200 ? NoError : ProviderRejected);
115 m_currentClient = {};
116 return;
117 }
118
119 if (msgType == "notification"_L1) {
120 Message m;
121 m.clientRemoteId = msgObj.value("channelID"_L1).toString();
122 m.content = QByteArray::fromBase64(msgObj.value("data"_L1).toString().toLatin1(), QByteArray::Base64UrlEncoding);
123 m.messageId = msgObj.value("version"_L1).toString();
125 return;
126 }
127
128 if (msgType == "unregister"_L1) {
129 const auto channelId = msgObj.value("channelID"_L1).toString();
130 if (m_currentClient.remoteId != channelId) {
131 qCCritical(Log) << "Got unregistration for a different client!" << channelId << m_currentClient.remoteId;
132 return;
133 }
134 Q_EMIT clientUnregistered(m_currentClient, status == 200 ? NoError : ProviderRejected);
135 return;
136 }
137
138 if (msgType == "urgency"_L1) {
140 return;
141 }
142}
143
145{
146 qCDebug(Log) << client.serviceName << client.token;
147
148 m_currentClient = client;
149 m_currentClient.remoteId = QUuid::createUuid().toString(QUuid::WithoutBraces);
150 QJsonObject msg{{
151 { "messageType"_L1, "register"_L1 },
152 { "channelID"_L1, m_currentClient.remoteId },
153 }};
154 if (!client.vapidKey.isEmpty()) {
155 msg.insert("key"_L1, client.vapidKey);
156 }
157
158 qCDebug(Log) << msg;
159 sendMessage(msg);
160}
161
163{
164 qCDebug(Log) << client.serviceName << client.token << client.remoteId;
165
166 m_currentClient = client;
167 QJsonObject msg{{
168 { "messageType"_L1, "unregister"_L1 },
169 { "channelID"_L1, client.remoteId },
170 }};
171 sendMessage(msg);
172}
173
174void AutopushProvider::acknowledgeMessage(const Client &client, const QString &messageIdentifier)
175{
176 qCDebug(Log) << client.serviceName << client.remoteId << messageIdentifier;
177
178 QJsonObject msg{{
179 { "messageType"_L1, "ack"_L1 },
180 { "updates"_L1, QJsonArray{{
182 { "channelID"_L1, client.remoteId },
183 { "version"_L1, messageIdentifier }
184 }}
185 }}},
186 }};
187 sendMessage(msg);
188 Q_EMIT messageAcknowledged(client, messageIdentifier);
189}
190
192{
193 qCDebug(Log) << qToUnderlying(urgency);
194 // TODO not integrated upstream yet: https://github.com/mozilla-services/autopush-rs/tree/feat/urgency
195#if 0
196 QJsonObject msg{{
197 { "messageType"_L1, "urgency"_L1 },
198 { "min"_L1, QLatin1StringView(urgencyValue(urgency)) }
199 }};
200 sendMessage(msg);
201 setUrgency(urgency);
202#else
204#endif
205}
206
207void AutopushProvider::storeState()
208{
209 QSettings settings;
210 settings.beginGroup(providerId() + "-internal"_L1);
211 settings.setValue("UAID", m_uaid);
212}
213
214#include "moc_autopushprovider.cpp"
Base class for push provider protocol implementations.
void messageReceived(const KUnifiedPush::Message &msg)
Inform about a received push notification.
virtual void doChangeUrgency(Urgency urgency)
Re-implement if urgency leve changes are done as a separate command.
void connected()
Emitted after the connection to the push provider has been established successfully.
Urgency urgency() const
The urgency level currently used by this provider.
void clientUnregistered(const KUnifiedPush::Client &client, KUnifiedPush::AbstractPushProvider::Error error=NoError)
Emitted after successful client unregistration.
void disconnected(KUnifiedPush::AbstractPushProvider::Error error, const QString &errorMsg={})
Emitted after the connection to the push provider disconnected or failed to be established.
void urgencyChanged()
Emitted when the urgency level change request has been executed.
void clientRegistered(const KUnifiedPush::Client &client, KUnifiedPush::AbstractPushProvider::Error error=NoError, const QString &errorMsg={})
Emitted after successful client registration.
@ ProviderRejected
communication worked, but the provider refused to complete the operation
@ TransientNetworkError
temporary network error, try again
QLatin1StringView providerId() const
Provider id used e.g.
void messageAcknowledged(const KUnifiedPush::Client &client, const QString &messageIdentifier)
Emitted after a message reception has been acknowledge to the push server.
void registerClient(const Client &client) override
Register a new client with the provider.
void unregisterClient(const Client &client) override
Unregister a client from the provider.
void doChangeUrgency(Urgency urgency) override
Re-implement if urgency leve changes are done as a separate command.
bool loadSettings(const QSettings &settings) override
Load connection settings.
void acknowledgeMessage(const Client &client, const QString &messageIdentifier) override
Acknowledge a message.
void connectToProvider(Urgency urgency) override
Attempt to establish a connection to the push provider.
void disconnectFromProvider() override
Disconnect and existing connection to the push provider.
Information about a registered client.
Definition client.h:20
Q_SCRIPTABLE CaptureState status()
Client-side integration with UnifiedPush.
Definition connector.h:14
int64_t Id
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void beginGroup(QAnyStringView prefix)
void setValue(QAnyStringView key, const QVariant &value)
QVariant value(QAnyStringView key) const const
QString fromUtf8(QByteArrayView str)
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QByteArray toUtf8() const const
VeryCoarseTimer
void timeout()
QUuid createUuid()
QString toString(StringFormat mode) const const
QString toString() const const
qint64 sendTextMessage(const QString &message)
void stateChanged(QAbstractSocket::SocketState state)
void textMessageReceived(const QString &message)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 18 2025 12:16:55 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.