feat(dapp) handle pairing errors or timeout if no response

Found out while testing that in some corner cases there will be no
response of error in case of pairing. This is handled now by showing
a generic error message. The implementation is using a timer to handle
this case.
Extend the logic to report errors in the pairing process.

Closes #14676
This commit is contained in:
Stefan 2024-07-08 14:18:14 +03:00 committed by Stefan Dunca
parent 1e256c8bf1
commit 136194c112
6 changed files with 68 additions and 47 deletions

View File

@ -88,6 +88,7 @@ Item {
}
}
StatusBaseText { text: "Custom Accounts" }
StatusTextArea {
text: settings.customAccounts
onTextChanged: {
@ -99,7 +100,8 @@ Item {
})
}
Layout.fillWidth: true
Layout.preferredHeight: !!text ? 400 : -1
Layout.maximumHeight: 300
clip: true
}
Rectangle {

View File

@ -185,9 +185,9 @@ DappsComboBox {
Connections {
target: root.wcService
function onPairingUriValidated(validationState) {
function onPairingValidated(validationState) {
if (pairWCLoader.item) {
pairWCLoader.item.pairingUriValidated(validationState)
pairWCLoader.item.pairingValidated(validationState)
}
}

View File

@ -17,6 +17,15 @@ import utils 1.0
import "types"
// The WC SDK has an async (function call then signal response)
// A complete pairing flow to connect a dApp:
// - user provides pairing url -> root.validatePairingUri -> signal pairingValidated
// - user requests pair -> root.pair(uri) -> pairResponse(ok)
// -> if pairResponse ok -> onSessionProposal -> sdk.buildApprovedNamespaces
// -> onBuildApprovedNamespace -> signal connectDApp
// - user requests root.approvePairSession/root.rejectPairSession
// -> if approvePairSession -> sdk.buildApprovedNamespaces
// -> onBuildApprovedNamespace -> sdk.approveSession -> onApproveSessionResult
QObject {
id: root
@ -47,38 +56,39 @@ QObject {
function validatePairingUri(uri) {
if(Helpers.containsOnlyEmoji(uri)) {
root.pairingUriValidated(Pairing.uriErrors.tooCool)
root.pairingValidated(Pairing.errors.tooCool)
return
} else if(!Helpers.validURI(uri)) {
root.pairingUriValidated(Pairing.uriErrors.invalidUri)
root.pairingValidated(Pairing.errors.invalidUri)
return
}
let info = Helpers.extractInfoFromPairUri(uri)
wcSDK.getActiveSessions((sessions) => {
// Check if the URI is already paired
var validationState = Pairing.uriErrors.ok
var validationState = Pairing.errors.ok
for (let key in sessions) {
if (sessions[key].pairingTopic == info.topic) {
validationState = Pairing.uriErrors.alreadyUsed
validationState = Pairing.errors.alreadyUsed
break
}
}
// Check if expired
if (validationState == Pairing.uriErrors.ok) {
if (validationState == Pairing.errors.ok) {
const now = (new Date().getTime())/1000
if (info.expiry < now) {
validationState = Pairing.uriErrors.expired
validationState = Pairing.errors.expired
}
}
root.pairingUriValidated(validationState)
root.pairingValidated(validationState)
});
}
function pair(uri) {
d.acceptedSessionProposal = null
timeoutTimer.start()
wcSDK.pair(uri)
}
@ -116,11 +126,19 @@ QObject {
signal approveSessionResult(var session, var error)
signal sessionRequest(SessionRequestResolved request)
signal displayToastMessage(string message, bool error)
signal pairingUriValidated(int validationState)
// Emitted as a response to WalletConnectService.validatePairingUri or other WalletConnectService.pair
// and WalletConnectService.approvePair errors
signal pairingValidated(int validationState)
readonly property Connections sdkConnections: Connections {
target: wcSDK
function onPairResponse(ok) {
if (!ok) {
d.reportPairErrorState(Pairing.errors.unknownError)
} // else waiting for onSessionProposal
}
function onSessionProposal(sessionProposal) {
d.currentSessionProposal = sessionProposal
@ -133,9 +151,9 @@ QObject {
if(error) {
// Check that it contains Non conforming namespaces"
if (error.includes("Non conforming namespaces")) {
root.pairingUriValidated(Pairing.uriErrors.unsupportedNetwork)
d.reportPairErrorState(Pairing.errors.unsupportedNetwork)
} else {
root.pairingUriValidated(Pairing.uriErrors.unknownError)
d.reportPairErrorState(Pairing.errors.unknownError)
}
return
}
@ -151,8 +169,7 @@ QObject {
function onApproveSessionResult(session, err) {
if (err) {
// TODO #14676: handle the error
console.error("Failed to approve session", err)
d.reportPairErrorState(Pairing.errors.unknownError)
return
}
@ -176,6 +193,7 @@ QObject {
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
const app_domain = StringUtils.extractDomainFromLink(app_url)
if(err) {
d.reportPairErrorState(Pairing.errors.unknownError)
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_domain), true)
} else {
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_domain), false)
@ -202,21 +220,9 @@ QObject {
property var currentSessionProposal: null
property var acceptedSessionProposal: null
// TODO #14676: use it to check if already paired
function getPairingTopicFromPairingUrl(url)
{
if (!url.startsWith("wc:"))
{
return null;
}
const atIndex = url.indexOf("@");
if (atIndex < 0)
{
return null;
}
return url.slice(3, atIndex);
function reportPairErrorState(state) {
timeoutTimer.stop()
root.pairingValidated(state)
}
}
@ -233,6 +239,7 @@ QObject {
networksModel: root.flatNetworks
onSessionRequest: (request) => {
timeoutTimer.stop()
root.sessionRequest(request)
}
onDisplayToastMessage: (message, error) => {
@ -246,4 +253,17 @@ QObject {
sdk: root.wcSDK
store: root.store
}
// Timeout for the corner case where the URL was already dismissed and the SDK doesn't respond with an error nor advances with the proposal
Timer {
id: timeoutTimer
interval: 10000 // (10 seconds)
running: false
repeat: false
onTriggered: {
d.reportPairErrorState(Pairing.errors.unknownError)
}
}
}

View File

@ -3,7 +3,7 @@ pragma Singleton
import QtQml 2.15
QtObject {
readonly property QtObject uriErrors: QtObject {
readonly property QtObject errors: QtObject {
readonly property int notChecked: 0
readonly property int ok: 1
readonly property int tooCool: 2

View File

@ -27,9 +27,9 @@ StatusDialog {
property bool isPairing: false
function pairingUriValidated(validationState) {
function pairingValidated(validationState) {
uriInput.errorState = validationState
if (validationState === Pairing.uriErrors.ok) {
if (validationState === Pairing.errors.ok) {
d.doPair()
}
}
@ -51,7 +51,7 @@ StatusDialog {
WCUriInput {
id: uriInput
pending: uriInput.errorState === Pairing.uriErrors.notChecked
pending: uriInput.errorState === Pairing.errors.notChecked
onTextChanged: {
root.isPairing = false
@ -91,7 +91,7 @@ StatusDialog {
enabled: uriInput.valid
&& !root.isPairing
&& uriInput.text.length > 0
&& uriInput.errorState === Pairing.uriErrors.ok
&& uriInput.errorState === Pairing.errors.ok
onClicked: {
d.doPair()

View File

@ -17,7 +17,7 @@ ColumnLayout {
readonly property bool valid: input.valid && input.text.length > 0
readonly property alias text: input.text
property alias pending: input.pending
property int errorState: Pairing.uriErrors.notChecked
property int errorState: Pairing.errors.notChecked
StatusBaseInput {
id: input
@ -37,22 +37,21 @@ ColumnLayout {
return true
}
if(root.errorState === Pairing.uriErrors.tooCool) {
if(root.errorState === Pairing.errors.tooCool) {
errorText.text = qsTr("WalletConnect URI too cool")
} else if(root.errorState === Pairing.uriErrors.invalidUri) {
} else if(root.errorState === Pairing.errors.invalidUri) {
errorText.text = qsTr("WalletConnect URI invalid")
} else if(root.errorState === Pairing.uriErrors.alreadyUsed) {
} else if(root.errorState === Pairing.errors.alreadyUsed) {
errorText.text = qsTr("WalletConnect URI already used")
} else if(root.errorState === Pairing.uriErrors.expired) {
} else if(root.errorState === Pairing.errors.expired) {
errorText.text = qsTr("WalletConnect URI has expired")
}
if (errorText.text.length > 0) {
return false
} else if(root.errorState === Pairing.uriErrors.unsupportedNetwork) {
} else if(root.errorState === Pairing.errors.unsupportedNetwork) {
errorText.text = qsTr("dApp is requesting to connect on an unsupported network")
return false
} else if(root.errorState === Pairing.uriErrors.unknownError) {
errorText.text = qsTr("Unexpected error occurred, please try again")
} else if(root.errorState === Pairing.errors.unknownError) {
errorText.text = qsTr("Unexpected error occurred. Try again.")
}
if (errorText.text.length > 0) {
return false
}