Files
php-qml/framework/qml/src/MercureClient.h

96 lines
2.7 KiB
C
Raw Normal View History

#pragma once
#include <QByteArray>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QtQmlIntegration>
class QNetworkAccessManager;
class QNetworkReply;
class QTimer;
namespace PhpQml::Bridge {
/// Single-topic Mercure SSE subscriber.
///
/// Implements the `text/event-stream` line protocol on top of QNetworkReply,
/// with exponential reconnect (1s → 2s → … → cap 30s) and `Last-Event-ID`
/// resume across reconnects. See PLAN.md §7 (MercureClient) and §3
/// *Edge cases — Sleep / wake* for the resume contract.
///
/// One client = one topic. Application code instantiates one per
/// subscribed topic; multi-topic aggregation is a Phase 2 concern.
class MercureClient : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString hubUrl READ hubUrl WRITE setHubUrl NOTIFY hubUrlChanged)
Q_PROPERTY(QString topic READ topic WRITE setTopic NOTIFY topicChanged)
Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged)
Q_PROPERTY(bool active READ active NOTIFY activeChanged)
Q_PROPERTY(QString lastEventId READ lastEventId NOTIFY lastEventIdChanged)
public:
explicit MercureClient(QObject* parent = nullptr);
~MercureClient() override;
QString hubUrl() const { return m_hubUrl; }
void setHubUrl(const QString& url);
QString topic() const { return m_topic; }
void setTopic(const QString& t);
QString token() const { return m_token; }
void setToken(const QString& t);
bool active() const noexcept { return m_active; }
QString lastEventId() const { return m_lastEventId; }
Q_INVOKABLE void start();
Q_INVOKABLE void stop();
signals:
void hubUrlChanged();
void topicChanged();
void tokenChanged();
void activeChanged();
void lastEventIdChanged();
/// Emitted for every dispatched SSE event with a `data:` field.
/// `data` is the joined data lines as the hub sent them; `id` is
/// the SSE event id (often a UUIDv7).
void update(const QString& data, const QString& id);
void error(const QString& detail);
private slots:
void onReadyRead();
void onFinished();
void doConnect();
private:
void scheduleReconnect();
void teardownReply();
void emitMessage();
void setActive(bool a);
QNetworkAccessManager* m_nam = nullptr;
QNetworkReply* m_reply = nullptr;
QTimer* m_reconnectTimer = nullptr;
QString m_hubUrl;
QString m_topic;
QString m_token;
QString m_lastEventId;
QStringList m_dataLines;
QByteArray m_pendingEventId;
bool m_active = false;
bool m_userStopped = false;
int m_currentBackoffMs = kInitialBackoffMs;
static constexpr int kInitialBackoffMs = 1000;
static constexpr int kMaxBackoffMs = 30000;
};
} // namespace PhpQml::Bridge