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->disconnect(); // Prevent StateChanged from being signaled again during the following deleteLater
62 m_socket->deleteLater();
63 m_socket = nullptr;
64 m_pingTimer.stop();
65 }
66 });
67 connect(m_socket, &QWebSocket::textMessageReceived, this, &AutopushProvider::wsMessageReceived);
68 connect(&m_pingTimer, &QTimer::timeout, m_socket, [this]() { m_socket->ping(); });
69
70 auto wsUrl = m_url;
71 if (wsUrl.scheme() == QLatin1String("https")) {
72 wsUrl.setScheme(QStringLiteral("wss"));
73 } else if (wsUrl.scheme() == QLatin1String("http")) {
74 wsUrl.setScheme(QStringLiteral("ws"));
75 } else {
76 qCWarning(Log) << "Unknown URL scheme:" << m_url;
78 return;
79 }
80
81 m_socket->open(wsUrl);
82}
83
85{
86 m_socket->close();
87}
88
89void AutopushProvider::sendMessage(const QJsonObject &msg)
90{
92}
93
94void AutopushProvider::wsMessageReceived(const QString &msg)
95{
96 qCDebug(Log) << msg;
97 const auto msgObj = QJsonDocument::fromJson(msg.toUtf8()).object();
98 const auto msgType = msgObj.value("messageType"_L1).toString();
99 const auto status = msgObj.value("status"_L1).toInt();
100
101 if (msgType == "hello"_L1 && status == 200) {
102 m_uaid = msgObj.value("uaid"_L1).toString();
103 storeState();
105 return;
106 }
107
108 if (msgType == "register"_L1) {
109 const auto channelId = msgObj.value("channelID"_L1).toString();
110 if (m_currentClient.remoteId != channelId) {
111 qCCritical(Log) << "Got registration for a different client!" << channelId << m_currentClient.remoteId;
112 return;
113 }
114 m_currentClient.endpoint = msgObj.value("pushEndpoint"_L1).toString();
115 Q_EMIT clientRegistered(m_currentClient, status == 200 ? NoError : ProviderRejected);
116 m_currentClient = {};
117 return;
118 }
119
120 if (msgType == "notification"_L1) {
121 Message m;
122 m.clientRemoteId = msgObj.value("channelID"_L1).toString();
123 m.content = QByteArray::fromBase64(msgObj.value("data"_L1).toString().toLatin1(), QByteArray::Base64UrlEncoding);
124 m.messageId = msgObj.value("version"_L1).toString();
126 return;
127 }
128
129 if (msgType == "unregister"_L1) {
130 const auto channelId = msgObj.value("channelID"_L1).toString();
131 if (m_currentClient.remoteId != channelId) {
132 qCCritical(Log) << "Got unregistration for a different client!" << channelId << m_currentClient.remoteId;
133 return;
134 }
135 Q_EMIT clientUnregistered(m_currentClient, status == 200 ? NoError : ProviderRejected);
136 return;
137 }
138
139 if (msgType == "urgency"_L1) {
141 return;
142 }
143}
144
146{
147 qCDebug(Log) << client.serviceName << client.token;
148
149 m_currentClient = client;
150 m_currentClient.remoteId = QUuid::createUuid().toString(QUuid::WithoutBraces);
151 QJsonObject msg{{
152 { "messageType"_L1, "register"_L1 },
153 { "channelID"_L1, m_currentClient.remoteId },
154 }};
155 if (!client.vapidKey.isEmpty()) {
156 msg.insert("key"_L1, client.vapidKey);
157 }
158
159 qCDebug(Log) << msg;
160 sendMessage(msg);
161}
162
164{
165 qCDebug(Log) << client.serviceName << client.token << client.remoteId;
166
167 m_currentClient = client;
168 QJsonObject msg{{
169 { "messageType"_L1, "unregister"_L1 },
170 { "channelID"_L1, client.remoteId },
171 }};
172 sendMessage(msg);
173}
174
175void AutopushProvider::acknowledgeMessage(const Client &client, const QString &messageIdentifier)
176{
177 qCDebug(Log) << client.serviceName << client.remoteId << messageIdentifier;
178
179 QJsonObject msg{{
180 { "messageType"_L1, "ack"_L1 },
181 { "updates"_L1, QJsonArray{{
183 { "channelID"_L1, client.remoteId },
184 { "version"_L1, messageIdentifier }
185 }}
186 }}},
187 }};
188 sendMessage(msg);
189 Q_EMIT messageAcknowledged(client, messageIdentifier);
190}
191
193{
194 qCDebug(Log) << qToUnderlying(urgency);
195 // TODO not integrated upstream yet: https://github.com/mozilla-services/autopush-rs/tree/feat/urgency
196#if 0
197 QJsonObject msg{{
198 { "messageType"_L1, "urgency"_L1 },
199 { "min"_L1, QLatin1StringView(urgencyValue(urgency)) }
200 }};
201 sendMessage(msg);
202 setUrgency(urgency);
203#else
205#endif
206}
207
208void AutopushProvider::storeState()
209{
210 QSettings settings;
211 settings.beginGroup(providerId() + "-internal"_L1);
212 settings.setValue("UAID", m_uaid);
213}
214
215#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 25 2025 12:05:39 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.