fix(dapps) Wallet Connect internet connection reestablishing issue

Add a new NetworkChecker QObject to StatusQ to be used in checking
internet connection status. This is used by the WebEngineLoader
to only allow loading of web pages when there is an active internet
to cover for a corner case on MacOS where the internet connection is
not reestablished if the WebEngineView was loaded without an active
internet connection.

Closes: #15598, #15806
This commit is contained in:
Stefan 2024-07-29 16:50:08 +03:00 committed by Stefan Dunca
parent 910af539d6
commit 98c18901e0
11 changed files with 191 additions and 37 deletions

View File

@ -57,8 +57,7 @@ proc getActiveSessions*(validAtTimestamp: int): JsonNode =
return nil
let jsonResultStr = rpcRes.result.getStr()
if jsonResultStr == "null":
# nil means error
if jsonResultStr == "null" or jsonResultStr == "":
return newJArray()
if rpcRes.result.kind != JArray:

View File

@ -83,6 +83,15 @@ Item {
font.bold: true
}
}
RowLayout {
StatusBaseText { text: "SDK status:" }
Rectangle {
Layout.preferredWidth: 20
Layout.preferredHeight: Layout.preferredWidth
radius: Layout.preferredWidth / 2
color: walletConnectService.wcSDK.sdkReady ? "green" : "red"
}
}
CheckBox {
text: "Testnet Mode"
@ -289,7 +298,7 @@ Item {
id: walletConnectService
wcSDK: WalletConnectSDK {
active: settings.enableSDK
enableSdk: settings.enableSDK
projectId: projectIdText.projectId
}

View File

@ -108,6 +108,7 @@ add_library(StatusQ SHARED
include/StatusQ/modelsyncedcontainer.h
include/StatusQ/modelutilsinternal.h
include/StatusQ/movablemodel.h
include/StatusQ/networkchecker.h
include/StatusQ/objectproxymodel.h
include/StatusQ/permissionutilsinternal.h
include/StatusQ/rolesrenamingmodel.h
@ -137,6 +138,7 @@ add_library(StatusQ SHARED
src/modelentry.cpp
src/modelutilsinternal.cpp
src/movablemodel.cpp
src/networkchecker.cpp
src/objectproxymodel.cpp
src/permissionutilsinternal.cpp
src/plugin.cpp

View File

@ -0,0 +1,41 @@
#pragma once
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QTimer>
#include <chrono>
using namespace std::chrono_literals;
/// Checks if the internet connection is available, when active.
/// It checks the connection every 30 seconds as long as the \c active property is \c true.
class NetworkChecker : public QObject
{
Q_OBJECT
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged)
public:
explicit NetworkChecker(QObject* parent = nullptr);
bool isOnline() const;
bool isActive() const;
void setActive(bool active);
signals:
void isOnlineChanged(bool online);
void activeChanged(bool active);
private:
QNetworkAccessManager manager;
QTimer timer;
bool online = false;
bool active = true;
constexpr static std::chrono::milliseconds checkInterval = 30s;
void checkNetwork();
void onFinished(QNetworkReply* reply);
void updateRegularCheck(bool active);
};

View File

@ -3,6 +3,8 @@ import QtQuick 2.15
import QtWebEngine 1.10
import QtWebChannel 1.15
import StatusQ 0.1
// Helper to load and setup an instance of \c WebEngineView
//
// The \c webChannelObjects property is used to register specific objects
@ -11,34 +13,33 @@ import QtWebChannel 1.15
// qrc:/StatusQ/Components/private/qwebchannel/helpers.js will provide
// access to window.statusq APIs used to exchange data between the internal
// web engine and the QML application
//
// It doesn't load the web engine until NetworkChecker detects and active internet
// connection to avoid the corner case of initializing the web engine without
// network connectivity. If the web engine is initialized without network connectivity
// it won't restore the connectivity when it's available on Mac OS
Item {
id: root
required property url url
required property var webChannelObjects
property alias active: loader.active
// Used to control the loading of the web engine
property bool active: false
// Useful to monitor the loading state of the web engine (depends on active and internet connectivity)
readonly property bool isActive: loader.active
property alias instance: loader.item
property bool waitForInternet: true
signal engineLoaded(WebEngineView instance)
signal engineUnloaded()
signal pageLoaded()
signal pageLoadingError(string errorString)
Loader {
id: loader
Component {
id: webEngineViewComponent
active: false
onStatusChanged: function() {
if (status === Loader.Ready) {
root.engineLoaded(loader.item)
} else if (status === Loader.Null) {
root.engineUnloaded()
}
}
sourceComponent: WebEngineView {
WebEngineView {
id: webEngineView
anchors.fill: parent
@ -65,4 +66,35 @@ Item {
}
}
}
Loader {
id: loader
active: root.active && (!root.waitForInternet || (d.passedFirstTimeInitialization || networkChecker.isOnline))
onStatusChanged: function() {
if (status === Loader.Ready) {
root.engineLoaded(loader.item)
d.passedFirstTimeInitialization = true
} else if (status === Loader.Null) {
root.engineUnloaded()
}
}
sourceComponent: webEngineViewComponent
}
NetworkChecker {
id: networkChecker
// Deactivate searching for network connectivity after the web engine is loaded
active: !d.passedFirstTimeInitialization
}
QtObject {
id: d
// Used to hold the loading of the web engine until internet connectivity is available
property bool passedFirstTimeInitialization: false
}
}

View File

@ -0,0 +1,61 @@
#include "StatusQ/networkchecker.h"
NetworkChecker::NetworkChecker(QObject* parent)
: QObject(parent)
{
connect(&manager, &QNetworkAccessManager::finished, this, &NetworkChecker::onFinished);
connect(&timer, &QTimer::timeout, this, &NetworkChecker::checkNetwork);
updateRegularCheck(active);
}
bool NetworkChecker::isOnline() const
{
return online;
}
void NetworkChecker::checkNetwork()
{
QNetworkRequest request(QUrl("https://fedoraproject.org/static/hotspot.txt"));
manager.get(request);
}
void NetworkChecker::onFinished(QNetworkReply* reply)
{
bool wasOnline = online;
online = (reply->error() == QNetworkReply::NoError);
reply->deleteLater();
if(wasOnline != online)
{
emit isOnlineChanged(online);
}
}
bool NetworkChecker::isActive() const
{
return active;
}
void NetworkChecker::setActive(bool active)
{
if(active == this->active) return;
this->active = active;
emit activeChanged(active);
updateRegularCheck(active);
}
void NetworkChecker::updateRegularCheck(bool active)
{
if(active)
{
checkNetwork();
timer.start(checkInterval);
}
else
{
timer.stop();
}
}

View File

@ -17,6 +17,7 @@
#include "StatusQ/modelentry.h"
#include "StatusQ/modelutilsinternal.h"
#include "StatusQ/movablemodel.h"
#include "StatusQ/networkchecker.h"
#include "StatusQ/objectproxymodel.h"
#include "StatusQ/permissionutilsinternal.h"
#include "StatusQ/rolesrenamingmodel.h"
@ -58,6 +59,7 @@ public:
qmlRegisterType<SourceModel>("StatusQ", 0, 1, "SourceModel");
qmlRegisterType<ConcatModel>("StatusQ", 0, 1, "ConcatModel");
qmlRegisterType<MovableModel>("StatusQ", 0, 1, "MovableModel");
qmlRegisterType<NetworkChecker>("StatusQ", 0, 1, "NetworkChecker");
qmlRegisterType<FastExpressionFilter>("StatusQ", 0, 1, "FastExpressionFilter");
qmlRegisterType<FastExpressionRole>("StatusQ", 0, 1, "FastExpressionRole");

View File

@ -38,6 +38,8 @@ TestCase {
sourceComponent: WebEngineLoader {
url: "./WebEngineLoader/test.html"
webChannelObjects: [testObject]
waitForInternet: false
}
}
SignalSpy { id: loadedSpy; target: loader; signalName: "loaded" }
@ -67,13 +69,13 @@ TestCase {
compare(webEngine.instance, null, "By default the engine is not loaded")
webEngine.active = true
webEngineLoadedSpy.wait(1000);
webEngineLoadedSpy.wait(1000)
verify(webEngine.instance !== null , "The WebEngineView should be available")
if (Qt.platform.os === "linux") {
skip("fails to load page on linux")
}
pageLoadedSpy.wait(1000);
pageLoadedSpy.wait(1000)
webEngine.active = false
engineUnloadedSpy.wait(1000);

View File

@ -14,10 +14,10 @@ WalletConnectSDKBase {
id: root
readonly property alias sdkReady: d.sdkReady
readonly property alias webEngineLoader: loader
property alias active: loader.active
property alias url: loader.url
// Enable the WalletConnect SDK
property alias enableSdk: loader.active
readonly property alias url: loader.url
implicitWidth: 1
implicitHeight: 1
@ -81,15 +81,15 @@ WalletConnectSDKBase {
function init() {
console.debug(`WC WalletConnectSDK.wcCall.init; root.projectId: ${root.projectId}`)
d.engine.runJavaScript(`wc.init("${root.projectId}").catch((error) => {wc.statusObject.sdkInitialized("SDK init error: "+error);})`, function(result) {
console.debug(`WC WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result)}`)
if (result && !!result.error)
{
console.error("init: ", result.error)
}
})
d.engine.runJavaScript(`
wc.init("${root.projectId}")
.then(()=> {
wc.statusObject.sdkInitialized("");
})
.catch((error) => {
wc.statusObject.sdkInitialized("SDK init error: "+error)
})
`)
}
function getPairings(callback) {
@ -259,7 +259,7 @@ WalletConnectSDKBase {
WebChannel.id: "statusObject"
function bubbleConsoleMessage(type, message) {
function echo(type, message) {
if (type === "warn") {
console.warn(message)
} else if (type === "debug") {
@ -272,7 +272,7 @@ WalletConnectSDKBase {
}
function sdkInitialized(error) {
console.debug(`WC WalletConnectSDK.sdkInitialized; error: ${error}`)
console.debug(`WC WalletConnectSDK.sdkInitialized: ${!!error ? "error: " + error : "success"}`)
d.sdkReady = !error
root.sdkInit(d.sdkReady, error)
}

View File

@ -54,8 +54,8 @@ function buildSupportedNamespacesFromModels(chainsModel, accountsModel, methods)
}
function buildSupportedNamespaces(chainIds, addresses, methods) {
var eipChainIds = []
var eipAddresses = []
let eipChainIds = []
let eipAddresses = []
for (let i = 0; i < chainIds.length; i++) {
let chainId = chainIds[i]
eipChainIds.push(`"eip155:${chainId}"`)
@ -65,7 +65,13 @@ function buildSupportedNamespaces(chainIds, addresses, methods) {
}
let methodsStr = methods.map(method => `"${method}"`).join(',')
return `{
"eip155":{"chains": [${eipChainIds.join(',')}],"methods": [${methodsStr}],"events": ["accountsChanged", "chainChanged"],"accounts": [${eipAddresses.join(',')}]}}`
"eip155":{
"chains": [${eipChainIds.join(',')}],
"methods": [${methodsStr}],
"events": ["accountsChanged", "chainChanged"],
"accounts": [${eipAddresses.join(',')}]
}
}`
}
function validURI(uri) {

View File

@ -2191,7 +2191,7 @@ Item {
id: walletConnectService
wcSDK: WalletConnectSDK {
active: WalletStore.RootStore.walletSectionInst.walletReady
enableSdk: WalletStore.RootStore.walletSectionInst.walletReady
projectId: WalletStore.RootStore.appSettings.walletConnectProjectID
}