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:
parent
1e256c8bf1
commit
136194c112
|
@ -88,6 +88,7 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusBaseText { text: "Custom Accounts" }
|
||||||
StatusTextArea {
|
StatusTextArea {
|
||||||
text: settings.customAccounts
|
text: settings.customAccounts
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
|
@ -99,7 +100,8 @@ Item {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: !!text ? 400 : -1
|
Layout.maximumHeight: 300
|
||||||
|
clip: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -185,9 +185,9 @@ DappsComboBox {
|
||||||
Connections {
|
Connections {
|
||||||
target: root.wcService
|
target: root.wcService
|
||||||
|
|
||||||
function onPairingUriValidated(validationState) {
|
function onPairingValidated(validationState) {
|
||||||
if (pairWCLoader.item) {
|
if (pairWCLoader.item) {
|
||||||
pairWCLoader.item.pairingUriValidated(validationState)
|
pairWCLoader.item.pairingValidated(validationState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,15 @@ import utils 1.0
|
||||||
|
|
||||||
import "types"
|
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 {
|
QObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
@ -47,38 +56,39 @@ QObject {
|
||||||
|
|
||||||
function validatePairingUri(uri) {
|
function validatePairingUri(uri) {
|
||||||
if(Helpers.containsOnlyEmoji(uri)) {
|
if(Helpers.containsOnlyEmoji(uri)) {
|
||||||
root.pairingUriValidated(Pairing.uriErrors.tooCool)
|
root.pairingValidated(Pairing.errors.tooCool)
|
||||||
return
|
return
|
||||||
} else if(!Helpers.validURI(uri)) {
|
} else if(!Helpers.validURI(uri)) {
|
||||||
root.pairingUriValidated(Pairing.uriErrors.invalidUri)
|
root.pairingValidated(Pairing.errors.invalidUri)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let info = Helpers.extractInfoFromPairUri(uri)
|
let info = Helpers.extractInfoFromPairUri(uri)
|
||||||
wcSDK.getActiveSessions((sessions) => {
|
wcSDK.getActiveSessions((sessions) => {
|
||||||
// Check if the URI is already paired
|
// Check if the URI is already paired
|
||||||
var validationState = Pairing.uriErrors.ok
|
var validationState = Pairing.errors.ok
|
||||||
for (let key in sessions) {
|
for (let key in sessions) {
|
||||||
if (sessions[key].pairingTopic == info.topic) {
|
if (sessions[key].pairingTopic == info.topic) {
|
||||||
validationState = Pairing.uriErrors.alreadyUsed
|
validationState = Pairing.errors.alreadyUsed
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if expired
|
// Check if expired
|
||||||
if (validationState == Pairing.uriErrors.ok) {
|
if (validationState == Pairing.errors.ok) {
|
||||||
const now = (new Date().getTime())/1000
|
const now = (new Date().getTime())/1000
|
||||||
if (info.expiry < now) {
|
if (info.expiry < now) {
|
||||||
validationState = Pairing.uriErrors.expired
|
validationState = Pairing.errors.expired
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root.pairingUriValidated(validationState)
|
root.pairingValidated(validationState)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function pair(uri) {
|
function pair(uri) {
|
||||||
d.acceptedSessionProposal = null
|
d.acceptedSessionProposal = null
|
||||||
|
timeoutTimer.start()
|
||||||
wcSDK.pair(uri)
|
wcSDK.pair(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,11 +126,19 @@ QObject {
|
||||||
signal approveSessionResult(var session, var error)
|
signal approveSessionResult(var session, var error)
|
||||||
signal sessionRequest(SessionRequestResolved request)
|
signal sessionRequest(SessionRequestResolved request)
|
||||||
signal displayToastMessage(string message, bool error)
|
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 {
|
readonly property Connections sdkConnections: Connections {
|
||||||
target: wcSDK
|
target: wcSDK
|
||||||
|
|
||||||
|
function onPairResponse(ok) {
|
||||||
|
if (!ok) {
|
||||||
|
d.reportPairErrorState(Pairing.errors.unknownError)
|
||||||
|
} // else waiting for onSessionProposal
|
||||||
|
}
|
||||||
|
|
||||||
function onSessionProposal(sessionProposal) {
|
function onSessionProposal(sessionProposal) {
|
||||||
d.currentSessionProposal = sessionProposal
|
d.currentSessionProposal = sessionProposal
|
||||||
|
|
||||||
|
@ -133,9 +151,9 @@ QObject {
|
||||||
if(error) {
|
if(error) {
|
||||||
// Check that it contains Non conforming namespaces"
|
// Check that it contains Non conforming namespaces"
|
||||||
if (error.includes("Non conforming namespaces")) {
|
if (error.includes("Non conforming namespaces")) {
|
||||||
root.pairingUriValidated(Pairing.uriErrors.unsupportedNetwork)
|
d.reportPairErrorState(Pairing.errors.unsupportedNetwork)
|
||||||
} else {
|
} else {
|
||||||
root.pairingUriValidated(Pairing.uriErrors.unknownError)
|
d.reportPairErrorState(Pairing.errors.unknownError)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -151,8 +169,7 @@ QObject {
|
||||||
|
|
||||||
function onApproveSessionResult(session, err) {
|
function onApproveSessionResult(session, err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
// TODO #14676: handle the error
|
d.reportPairErrorState(Pairing.errors.unknownError)
|
||||||
console.error("Failed to approve session", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +193,7 @@ QObject {
|
||||||
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-"
|
||||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||||
if(err) {
|
if(err) {
|
||||||
|
d.reportPairErrorState(Pairing.errors.unknownError)
|
||||||
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_domain), true)
|
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_domain), true)
|
||||||
} else {
|
} else {
|
||||||
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_domain), false)
|
root.displayToastMessage(qsTr("Connection request for %1 was rejected").arg(app_domain), false)
|
||||||
|
@ -202,21 +220,9 @@ QObject {
|
||||||
property var currentSessionProposal: null
|
property var currentSessionProposal: null
|
||||||
property var acceptedSessionProposal: null
|
property var acceptedSessionProposal: null
|
||||||
|
|
||||||
// TODO #14676: use it to check if already paired
|
function reportPairErrorState(state) {
|
||||||
function getPairingTopicFromPairingUrl(url)
|
timeoutTimer.stop()
|
||||||
{
|
root.pairingValidated(state)
|
||||||
if (!url.startsWith("wc:"))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const atIndex = url.indexOf("@");
|
|
||||||
if (atIndex < 0)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return url.slice(3, atIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +239,7 @@ QObject {
|
||||||
networksModel: root.flatNetworks
|
networksModel: root.flatNetworks
|
||||||
|
|
||||||
onSessionRequest: (request) => {
|
onSessionRequest: (request) => {
|
||||||
|
timeoutTimer.stop()
|
||||||
root.sessionRequest(request)
|
root.sessionRequest(request)
|
||||||
}
|
}
|
||||||
onDisplayToastMessage: (message, error) => {
|
onDisplayToastMessage: (message, error) => {
|
||||||
|
@ -246,4 +253,17 @@ QObject {
|
||||||
sdk: root.wcSDK
|
sdk: root.wcSDK
|
||||||
store: root.store
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ pragma Singleton
|
||||||
import QtQml 2.15
|
import QtQml 2.15
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
readonly property QtObject uriErrors: QtObject {
|
readonly property QtObject errors: QtObject {
|
||||||
readonly property int notChecked: 0
|
readonly property int notChecked: 0
|
||||||
readonly property int ok: 1
|
readonly property int ok: 1
|
||||||
readonly property int tooCool: 2
|
readonly property int tooCool: 2
|
||||||
|
|
|
@ -27,9 +27,9 @@ StatusDialog {
|
||||||
|
|
||||||
property bool isPairing: false
|
property bool isPairing: false
|
||||||
|
|
||||||
function pairingUriValidated(validationState) {
|
function pairingValidated(validationState) {
|
||||||
uriInput.errorState = validationState
|
uriInput.errorState = validationState
|
||||||
if (validationState === Pairing.uriErrors.ok) {
|
if (validationState === Pairing.errors.ok) {
|
||||||
d.doPair()
|
d.doPair()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ StatusDialog {
|
||||||
WCUriInput {
|
WCUriInput {
|
||||||
id: uriInput
|
id: uriInput
|
||||||
|
|
||||||
pending: uriInput.errorState === Pairing.uriErrors.notChecked
|
pending: uriInput.errorState === Pairing.errors.notChecked
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
root.isPairing = false
|
root.isPairing = false
|
||||||
|
@ -91,7 +91,7 @@ StatusDialog {
|
||||||
enabled: uriInput.valid
|
enabled: uriInput.valid
|
||||||
&& !root.isPairing
|
&& !root.isPairing
|
||||||
&& uriInput.text.length > 0
|
&& uriInput.text.length > 0
|
||||||
&& uriInput.errorState === Pairing.uriErrors.ok
|
&& uriInput.errorState === Pairing.errors.ok
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
d.doPair()
|
d.doPair()
|
||||||
|
|
|
@ -17,7 +17,7 @@ ColumnLayout {
|
||||||
readonly property bool valid: input.valid && input.text.length > 0
|
readonly property bool valid: input.valid && input.text.length > 0
|
||||||
readonly property alias text: input.text
|
readonly property alias text: input.text
|
||||||
property alias pending: input.pending
|
property alias pending: input.pending
|
||||||
property int errorState: Pairing.uriErrors.notChecked
|
property int errorState: Pairing.errors.notChecked
|
||||||
|
|
||||||
StatusBaseInput {
|
StatusBaseInput {
|
||||||
id: input
|
id: input
|
||||||
|
@ -37,22 +37,21 @@ ColumnLayout {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if(root.errorState === Pairing.uriErrors.tooCool) {
|
if(root.errorState === Pairing.errors.tooCool) {
|
||||||
errorText.text = qsTr("WalletConnect URI too cool")
|
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")
|
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")
|
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")
|
errorText.text = qsTr("WalletConnect URI has expired")
|
||||||
}
|
} else if(root.errorState === Pairing.errors.unsupportedNetwork) {
|
||||||
if (errorText.text.length > 0) {
|
|
||||||
return false
|
|
||||||
} else if(root.errorState === Pairing.uriErrors.unsupportedNetwork) {
|
|
||||||
errorText.text = qsTr("dApp is requesting to connect on an unsupported network")
|
errorText.text = qsTr("dApp is requesting to connect on an unsupported network")
|
||||||
return false
|
} else if(root.errorState === Pairing.errors.unknownError) {
|
||||||
} else if(root.errorState === Pairing.uriErrors.unknownError) {
|
errorText.text = qsTr("Unexpected error occurred. Try again.")
|
||||||
errorText.text = qsTr("Unexpected error occurred, please try again")
|
}
|
||||||
|
|
||||||
|
if (errorText.text.length > 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue