#pragma once #include #include #include #include class QNetworkAccessManager; class QNetworkReply; class QTimer; class QQmlEngine; class QJSEngine; namespace PhpQml::Bridge { /// Owns the backend lifecycle and exposes its health to QML. /// /// Phase 1 implements **dev mode**: reads `BRIDGE_URL` and `BRIDGE_TOKEN` /// from env, periodically probes `/healthz`, and reports the result /// as `connectionState`. Bundled mode (spawning FrankenPHP as a child) /// arrives in Phase 4. See PLAN.md §3 (Run modes), §7 (BackendConnection). class BackendConnection : public QObject { Q_OBJECT QML_ELEMENT QML_SINGLETON Q_PROPERTY(Mode mode READ mode CONSTANT) Q_PROPERTY(QString url READ url CONSTANT) Q_PROPERTY(QString token READ token NOTIFY tokenChanged) Q_PROPERTY(ConnectionState connectionState READ connectionState NOTIFY connectionStateChanged) Q_PROPERTY(QString error READ error NOTIFY errorChanged) public: enum class Mode { Dev, Bundled, // Phase 4 }; Q_ENUM(Mode) /// Full Update Semantics enum (PLAN.md §5). /// - Connecting : initial state until first probe response /// - Online : last probe succeeded /// - Reconnecting : ≥1 probe failed since last success; backing off /// - Offline : reconnect failures exceeded the threshold (default 30 s) enum class ConnectionState { Connecting, Online, Reconnecting, Offline, }; Q_ENUM(ConnectionState) explicit BackendConnection(QObject* parent = nullptr); ~BackendConnection() override; static BackendConnection* create(QQmlEngine* engine, QJSEngine*); Mode mode() const noexcept { return m_mode; } QString url() const { return m_url; } QString token() const { return m_token; } ConnectionState connectionState() const noexcept { return m_state; } QString error() const { return m_error; } Q_INVOKABLE void restart(); signals: void tokenChanged(); void connectionStateChanged(); void errorChanged(); /// Forward-compatible signal for §3 *Edge cases — Per-session secret /// rotation*. In Phase 1 dev mode it is never emitted; bundled mode /// in Phase 4 will fire it on child restart. void tokenRotated(const QString& newToken); private slots: void probe(); void onProbeFinished(); private: void setState(ConnectionState s); void setError(const QString& msg); Mode m_mode = Mode::Dev; QString m_url; QString m_token; ConnectionState m_state = ConnectionState::Connecting; QString m_error; QNetworkAccessManager* m_nam = nullptr; QNetworkReply* m_pendingReply = nullptr; QTimer* m_retryTimer = nullptr; QElapsedTimer m_firstFailureSinceOnline; // not started while Online int m_offlineThresholdMs = 30000; }; } // namespace PhpQml::Bridge