feat(WalletConnect): Handle sign request expiration
Implementing the user-story for sign request expiry and add qml tests + other minor fixes ## Acceptance Criteria ``` //Always show the expiration Given the sign/transaction request dialog is shown When request has an expiration date Then the user sees a 1 minute countdown in the dialog ``` ``` // Show 1 minute timer Given the sign/transaction request dialog is shown When the request has 1 minute or less before expiring Then the user sees a 1 second countdown in the dialog ``` ``` Given the sign/transaction dialog is open When the request expires Then the Accept button is removed And the only option for the user is to close the dialog ``` ``` Given the sign/transaction request dialog is open When the request expired Then the `Sign` and `Reject` buttons are removed And the `Close` button is visible ``` ``` Given the sign/transaction request expired Then a toast message is showing And it contains the "<dapp domain> sign request timed out" message ``` ``` Given the sign/transaction request dialog is open When the request expired Then the sign/transaction request dialog is still visible ``` ``` Given the sign/transaction request expires Then a console message is shown And it contains 'WC WalletConnectSDK.onSessionRequestExpire; id: ${id}`' ```
This commit is contained in:
parent
f536c3447f
commit
fd99b96cb5
|
@ -74,6 +74,12 @@ SplitView {
|
||||||
pill.expirationSeconds = 6
|
pill.expirationSeconds = 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Button {
|
||||||
|
text: "Set expired now"
|
||||||
|
onClicked: {
|
||||||
|
pill.expirationSeconds = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
Button {
|
Button {
|
||||||
text: "Set 5 minutes (10 minutes ago) -> expired"
|
text: "Set 5 minutes (10 minutes ago) -> expired"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
|
@ -156,13 +156,14 @@ function formatApproveSessionResponse(networksArray, accountsArray, custom) {
|
||||||
|
|
||||||
function formatSessionRequest(chainId, method, params, topic, requestId) {
|
function formatSessionRequest(chainId, method, params, topic, requestId) {
|
||||||
const reqId = requestId || 1717149885151715
|
const reqId = requestId || 1717149885151715
|
||||||
|
const expiry = Date.now() / 1000 + 6000
|
||||||
let paramsStr = params.map(param => `${param}`).join(',')
|
let paramsStr = params.map(param => `${param}`).join(',')
|
||||||
return `{
|
return `{
|
||||||
"id": ${reqId},
|
"id": ${reqId},
|
||||||
"params": {
|
"params": {
|
||||||
"chainId": "eip155:${chainId}",
|
"chainId": "eip155:${chainId}",
|
||||||
"request": {
|
"request": {
|
||||||
"expiryTimestamp": 1717150185,
|
"expiryTimestamp": ${expiry},
|
||||||
"method": "${method}",
|
"method": "${method}",
|
||||||
"params": [${paramsStr}]
|
"params": [${paramsStr}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ Item {
|
||||||
chainId: network,
|
chainId: network,
|
||||||
data: "hello world",
|
data: "hello world",
|
||||||
preparedData: "hello world",
|
preparedData: "hello world",
|
||||||
expirationTimestamp: Date.now() + 1000,
|
expirationTimestamp: (Date.now() + 10000) / 1000,
|
||||||
sourceId: Constants.DAppConnectors.WalletConnect
|
sourceId: Constants.DAppConnectors.WalletConnect
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -114,8 +114,8 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
property var onDisplayToastMessageTriggers: []
|
property var onDisplayToastMessageTriggers: []
|
||||||
onDisplayToastMessage: function(message, error) {
|
onDisplayToastMessage: function(message, type) {
|
||||||
onDisplayToastMessageTriggers.push({message, error})
|
onDisplayToastMessageTriggers.push({message, type})
|
||||||
}
|
}
|
||||||
|
|
||||||
property var onPairingValidatedTriggers: []
|
property var onPairingValidatedTriggers: []
|
||||||
|
@ -444,6 +444,150 @@ Item {
|
||||||
compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees to be set")
|
compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees to be set")
|
||||||
verify(!!request.feesInfo, "expected feesInfo to be set")
|
verify(!!request.feesInfo, "expected feesInfo to be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_sessionRequestExpiryInTheFuture() {
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp > Date.now() / 1000, "expected expiryTimestamp to be in the future")
|
||||||
|
|
||||||
|
// Expect to have calls to getActiveSessions from service initialization
|
||||||
|
const prevRequests = sdk.getActiveSessionsCallbacks.length
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
|
||||||
|
verify(handler.requestsModel.count === 1, "expected a request to be added")
|
||||||
|
const request = handler.requestsModel.findRequest(topic, session.id)
|
||||||
|
verify(!!request, "expected request to be found")
|
||||||
|
verify(!request.isExpired(), "expected request to not be expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_sessionRequestExpiryInThePast()
|
||||||
|
{
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past")
|
||||||
|
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
|
||||||
|
verify(handler.requestsModel.count === 1, "expected a request to be added")
|
||||||
|
const request = handler.requestsModel.findRequest(topic, session.id)
|
||||||
|
verify(!!request, "expected request to be found")
|
||||||
|
verify(request.isExpired(), "expected request to be expired")
|
||||||
|
verify(displayToastMessageSpy.count === 0, "no toast message should be displayed")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_wcSignalsSessionRequestExpiry()
|
||||||
|
{
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp > Date.now() / 1000, "expected expiryTimestamp to be in the future")
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
const request = handler.requestsModel.findRequest(topic, session.id)
|
||||||
|
verify(!!request, "expected request to be found")
|
||||||
|
verify(!request.isExpired(), "expected request to not be expired")
|
||||||
|
|
||||||
|
sdk.sessionRequestExpired(session.id)
|
||||||
|
verify(request.isExpired(), "expected request to be expired")
|
||||||
|
verify(displayToastMessageSpy.count === 0, "no toast message should be displayed")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_acceptExpiredSessionRequest()
|
||||||
|
{
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past")
|
||||||
|
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
|
||||||
|
verify(handler.requestsModel.count === 1, "expected a request to be added")
|
||||||
|
const request = handler.requestsModel.findRequest(topic, session.id)
|
||||||
|
request.resolveDappInfoFromSession({peer: {metadata: {name: "Test DApp", url: "https://test.dapp", icons:[]}}})
|
||||||
|
verify(!!request, "expected request to be found")
|
||||||
|
verify(request.isExpired(), "expected request to be expired")
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest")
|
||||||
|
|
||||||
|
ignoreWarning("Error: request expired")
|
||||||
|
handler.store.userAuthenticated(topic, session.id, "1234", "", message)
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest")
|
||||||
|
sdk.sessionRequestUserAnswerResult(topic, session.id, false, "")
|
||||||
|
verify(displayToastMessageSpy.count === 1, "expected a toast message to be displayed")
|
||||||
|
compare(displayToastMessageSpy.signalArguments[0][0], "test.dapp sign request timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_rejectExpiredSessionRequest()
|
||||||
|
{
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past")
|
||||||
|
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest")
|
||||||
|
|
||||||
|
ignoreWarning("Error: request expired")
|
||||||
|
handler.store.userAuthenticationFailed(topic, session.id)
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_signFailedAuthOnExpiredRequest()
|
||||||
|
{
|
||||||
|
const sdk = handler.sdk
|
||||||
|
const testAddressUpper = "0x3A"
|
||||||
|
const chainId = 2
|
||||||
|
const method = "personal_sign"
|
||||||
|
const message = "hello world"
|
||||||
|
const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`]
|
||||||
|
const topic = "b536a"
|
||||||
|
const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic))
|
||||||
|
session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000
|
||||||
|
|
||||||
|
verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past")
|
||||||
|
|
||||||
|
sdk.sessionRequestEvent(session)
|
||||||
|
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest")
|
||||||
|
|
||||||
|
ignoreWarning("Error: request expired")
|
||||||
|
handler.store.userAuthenticationFailed(topic, session.id)
|
||||||
|
verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TestCase {
|
TestCase {
|
||||||
|
@ -561,7 +705,7 @@ Item {
|
||||||
verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set")
|
verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set")
|
||||||
|
|
||||||
compare(service.onDisplayToastMessageTriggers.length, 1, "expected a success message to be displayed")
|
compare(service.onDisplayToastMessageTriggers.length, 1, "expected a success message to be displayed")
|
||||||
verify(!service.onDisplayToastMessageTriggers[0].error, "expected no error")
|
verify(service.onDisplayToastMessageTriggers[0].type !== Constants.ephemeralNotificationType.danger, "expected no error")
|
||||||
verify(service.onDisplayToastMessageTriggers[0].message, "expected message to be set")
|
verify(service.onDisplayToastMessageTriggers[0].message, "expected message to be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,5 +1171,77 @@ Item {
|
||||||
verify(!popup.opened)
|
verify(!popup.opened)
|
||||||
verify(!popup.visible)
|
verify(!popup.visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_SignRequestExpired() {
|
||||||
|
const topic = "abcd"
|
||||||
|
const requestId = "12345"
|
||||||
|
let popup = showRequestModal(topic, requestId)
|
||||||
|
|
||||||
|
const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId)
|
||||||
|
verify(!!request)
|
||||||
|
|
||||||
|
const countDownPill = findChild(popup, "countdownPill")
|
||||||
|
verify(!!countDownPill)
|
||||||
|
tryVerify(() => countDownPill.remainingSeconds > 0)
|
||||||
|
// Hackish -> countdownPill internals ask for a refresh before going to expired state
|
||||||
|
const remainingSeconds = countDownPill.remainingSeconds
|
||||||
|
tryVerify(() => countDownPill.visible)
|
||||||
|
tryVerify(() => countDownPill.remainingSeconds !== remainingSeconds)
|
||||||
|
|
||||||
|
request.setExpired()
|
||||||
|
tryVerify(() => countDownPill.isExpired)
|
||||||
|
verify(countDownPill.visible)
|
||||||
|
|
||||||
|
const signButton = findChild(popup, "signButton")
|
||||||
|
const rejectButton = findChild(popup, "rejectButton")
|
||||||
|
const closeButton = findChild(popup, "closeButton")
|
||||||
|
|
||||||
|
tryVerify(() => !signButton.visible)
|
||||||
|
verify(!rejectButton.visible)
|
||||||
|
verify(closeButton.visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_SignRequestDoesWithoutExpiry()
|
||||||
|
{
|
||||||
|
const topic = "abcd"
|
||||||
|
const requestId = "12345"
|
||||||
|
let popup = showRequestModal(topic, requestId)
|
||||||
|
|
||||||
|
const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId)
|
||||||
|
verify(!!request)
|
||||||
|
request.expirationTimestamp = undefined
|
||||||
|
|
||||||
|
const countDownPill = findChild(popup, "countdownPill")
|
||||||
|
verify(!!countDownPill)
|
||||||
|
tryVerify(() => !countDownPill.visible)
|
||||||
|
|
||||||
|
request.setExpired()
|
||||||
|
tryVerify(() => countDownPill.visible)
|
||||||
|
|
||||||
|
const signButton = findChild(popup, "signButton")
|
||||||
|
const rejectButton = findChild(popup, "rejectButton")
|
||||||
|
const closeButton = findChild(popup, "closeButton")
|
||||||
|
|
||||||
|
verify(signButton.visible)
|
||||||
|
verify(rejectButton.visible)
|
||||||
|
verify(!closeButton.visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_SignRequestModalAfterModelRemove()
|
||||||
|
{
|
||||||
|
const topic = "abcd"
|
||||||
|
const requestId = "12345"
|
||||||
|
let popup = showRequestModal(topic, requestId)
|
||||||
|
|
||||||
|
const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId)
|
||||||
|
verify(!!request)
|
||||||
|
|
||||||
|
controlUnderTest.sessionRequestsModel.removeRequest(topic, requestId)
|
||||||
|
verify(!controlUnderTest.sessionRequestsModel.findRequest(topic, requestId))
|
||||||
|
|
||||||
|
waitForRendering(controlUnderTest)
|
||||||
|
popup = findChild(controlUnderTest, "dappsRequestModal")
|
||||||
|
verify(!popup)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -328,6 +328,9 @@ DappsComboBox {
|
||||||
signingTransaction: !!request.method && (request.method === SessionRequest.methods.signTransaction.name
|
signingTransaction: !!request.method && (request.method === SessionRequest.methods.signTransaction.name
|
||||||
|| request.method === SessionRequest.methods.sendTransaction.name)
|
|| request.method === SessionRequest.methods.sendTransaction.name)
|
||||||
requestPayload: request.preparedData
|
requestPayload: request.preparedData
|
||||||
|
expirationSeconds: request.expirationTimestamp ? request.expirationTimestamp - requestTimestamp.getTime() / 1000
|
||||||
|
: 0
|
||||||
|
hasExpiryDate: !!request.expirationTimestamp
|
||||||
|
|
||||||
onClosed: {
|
onClosed: {
|
||||||
Qt.callLater(rejectRequest)
|
Qt.callLater(rejectRequest)
|
||||||
|
|
|
@ -40,6 +40,7 @@ StatusDialog {
|
||||||
|
|
||||||
property date requestTimestamp: new Date()
|
property date requestTimestamp: new Date()
|
||||||
property int expirationSeconds
|
property int expirationSeconds
|
||||||
|
property bool hasExpiryDate: false
|
||||||
|
|
||||||
property ObjectModel leftFooterContents
|
property ObjectModel leftFooterContents
|
||||||
property ObjectModel rightFooterContents: ObjectModel {
|
property ObjectModel rightFooterContents: ObjectModel {
|
||||||
|
@ -49,7 +50,7 @@ StatusDialog {
|
||||||
StatusFlatButton {
|
StatusFlatButton {
|
||||||
objectName: "rejectButton"
|
objectName: "rejectButton"
|
||||||
Layout.preferredHeight: signButton.height
|
Layout.preferredHeight: signButton.height
|
||||||
visible: !countdownPill.isExpired
|
visible: !root.hasExpiryDate || !countdownPill.isExpired
|
||||||
text: qsTr("Reject")
|
text: qsTr("Reject")
|
||||||
onClicked: root.reject() // close and emit rejected() signal
|
onClicked: root.reject() // close and emit rejected() signal
|
||||||
}
|
}
|
||||||
|
@ -57,7 +58,7 @@ StatusDialog {
|
||||||
objectName: "signButton"
|
objectName: "signButton"
|
||||||
id: signButton
|
id: signButton
|
||||||
interactive: !root.feesLoading && root.signButtonEnabled
|
interactive: !root.feesLoading && root.signButtonEnabled
|
||||||
visible: !countdownPill.isExpired
|
visible: !root.hasExpiryDate || !countdownPill.isExpired
|
||||||
icon.name: Constants.authenticationIconByType[root.loginType]
|
icon.name: Constants.authenticationIconByType[root.loginType]
|
||||||
disabledColor: Theme.palette.directColor8
|
disabledColor: Theme.palette.directColor8
|
||||||
text: qsTr("Sign")
|
text: qsTr("Sign")
|
||||||
|
@ -66,7 +67,7 @@ StatusDialog {
|
||||||
StatusButton {
|
StatusButton {
|
||||||
objectName: "closeButton"
|
objectName: "closeButton"
|
||||||
id: closeButton
|
id: closeButton
|
||||||
visible: countdownPill.isExpired
|
visible: root.hasExpiryDate && countdownPill.isExpired
|
||||||
text: qsTr("Close")
|
text: qsTr("Close")
|
||||||
onClicked: root.close()
|
onClicked: root.close()
|
||||||
}
|
}
|
||||||
|
@ -225,12 +226,13 @@ StatusDialog {
|
||||||
|
|
||||||
CountdownPill {
|
CountdownPill {
|
||||||
id: countdownPill
|
id: countdownPill
|
||||||
|
objectName: "countdownPill"
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: Style.current.padding
|
anchors.margins: Style.current.padding
|
||||||
timestamp: root.requestTimestamp
|
timestamp: root.requestTimestamp
|
||||||
expirationSeconds: root.expirationSeconds
|
expirationSeconds: root.expirationSeconds
|
||||||
visible: !!expirationSeconds
|
visible: !!root.hasExpiryDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
signal sessionRequest(string id)
|
signal sessionRequest(string id)
|
||||||
signal displayToastMessage(string message, bool error)
|
/*type - maps to Constants.ephemeralNotificationType*/
|
||||||
|
signal displayToastMessage(string message, int type)
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: sdk
|
target: sdk
|
||||||
|
@ -61,29 +62,49 @@ SQUtils.QObject {
|
||||||
console.error("Error finding event for topic", topic, "id", id)
|
console.error("Error finding event for topic", topic, "id", id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let methodStr = SessionRequest.methodToUserString(request.method)
|
let methodStr = SessionRequest.methodToUserString(request.method)
|
||||||
if (!methodStr) {
|
if (!methodStr) {
|
||||||
console.error("Error finding user string for method", request.method)
|
console.error("Error finding user string for method", request.method)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d.lookupSession(topic, function(session) {
|
const appUrl = request.dappUrl
|
||||||
if (session === null)
|
const appDomain = SQUtils.StringUtils.extractDomainFromLink(appUrl)
|
||||||
return
|
const requestExpired = request.isExpired()
|
||||||
const appUrl = session.peer.metadata.url
|
|
||||||
const appDomain = SQUtils.StringUtils.extractDomainFromLink(appUrl)
|
|
||||||
if (error) {
|
|
||||||
root.displayToastMessage(qsTr("Fail to %1 from %2").arg(methodStr).arg(appDomain), true)
|
|
||||||
|
|
||||||
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
requests.removeRequest(topic, id)
|
||||||
|
|
||||||
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
|
if (error) {
|
||||||
return
|
root.displayToastMessage(qsTr("Fail to %1 from %2").arg(methodStr).arg(appDomain), Constants.ephemeralNotificationType.danger)
|
||||||
}
|
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||||
|
console.error(`Error accepting session request for topic: ${topic}, id: ${id}, accept: ${accept}, error: ${error}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestExpired) {
|
||||||
let actionStr = accept ? qsTr("accepted") : qsTr("rejected")
|
let actionStr = accept ? qsTr("accepted") : qsTr("rejected")
|
||||||
root.displayToastMessage("%1 %2 %3".arg(appDomain).arg(methodStr).arg(actionStr), false)
|
root.displayToastMessage("%1 %2 %3".arg(appDomain).arg(methodStr).arg(actionStr), Constants.ephemeralNotificationType.success)
|
||||||
})
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
root.displayToastMessage("%1 sign request timed out".arg(appDomain), Constants.ephemeralNotificationType.normal)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSessionRequestExpired(sessionId) {
|
||||||
|
// Expired event coming from WC
|
||||||
|
// Handling as a failsafe in case the event is not processed by the SDK
|
||||||
|
let request = requests.findById(sessionId)
|
||||||
|
if (request === null) {
|
||||||
|
console.error("Error finding event for session id", sessionId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.isExpired()) {
|
||||||
|
return //nothing to do. The request is already expired
|
||||||
|
}
|
||||||
|
|
||||||
|
request.setExpired()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +117,12 @@ SQUtils.QObject {
|
||||||
console.error("Error finding event for topic", topic, "id", id)
|
console.error("Error finding event for topic", topic, "id", id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (request.isExpired()) {
|
||||||
|
console.warn("Error: request expired")
|
||||||
|
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
d.executeSessionRequest(request, password, pin, payload)
|
d.executeSessionRequest(request, password, pin, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,13 +132,16 @@ SQUtils.QObject {
|
||||||
if (request === null || !methodStr) {
|
if (request === null || !methodStr) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.lookupSession(topic, function(session) {
|
|
||||||
if (session === null)
|
if (request.isExpired()) {
|
||||||
return
|
console.warn("Error: request expired")
|
||||||
const appDomain = SQUtils.StringUtils.extractDomainFromLink(session.peer.metadata.url)
|
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||||
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(appDomain), true)
|
return
|
||||||
root.rejectSessionRequest(topic, id, false /*hasErrors*/)
|
}
|
||||||
})
|
|
||||||
|
const appDomain = SQUtils.StringUtils.extractDomainFromLink(request.dappUrl)
|
||||||
|
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(appDomain), Constants.ephemeralNotificationType.danger)
|
||||||
|
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSigningResult(topic, id, data) {
|
function onSigningResult(topic, id, data) {
|
||||||
|
@ -166,6 +196,7 @@ SQUtils.QObject {
|
||||||
const interpreted = d.prepareData(method, data)
|
const interpreted = d.prepareData(method, data)
|
||||||
|
|
||||||
const enoughFunds = !d.isTransactionMethod(method)
|
const enoughFunds = !d.isTransactionMethod(method)
|
||||||
|
const requestExpiry = event.params.request.expiryTimestamp
|
||||||
|
|
||||||
let obj = sessionRequestComponent.createObject(null, {
|
let obj = sessionRequestComponent.createObject(null, {
|
||||||
event,
|
event,
|
||||||
|
@ -179,6 +210,7 @@ SQUtils.QObject {
|
||||||
maxFeesText: "?",
|
maxFeesText: "?",
|
||||||
maxFeesEthText: "?",
|
maxFeesEthText: "?",
|
||||||
enoughFunds: enoughFunds,
|
enoughFunds: enoughFunds,
|
||||||
|
expirationTimestamp: requestExpiry
|
||||||
})
|
})
|
||||||
if (obj === null) {
|
if (obj === null) {
|
||||||
console.error("Error creating SessionRequestResolved for event")
|
console.error("Error creating SessionRequestResolved for event")
|
||||||
|
@ -213,6 +245,7 @@ SQUtils.QObject {
|
||||||
} else {
|
} else {
|
||||||
console.error("Error finding mainnet network")
|
console.error("Error finding mainnet network")
|
||||||
}
|
}
|
||||||
|
|
||||||
let st = getEstimatedFeesStatus(data, method, obj.chainId, mainChainId)
|
let st = getEstimatedFeesStatus(data, method, obj.chainId, mainChainId)
|
||||||
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, obj.accountAddress, obj.chainId, mainNet.chainId, interpreted.value)
|
let fundsStatus = checkFundsStatus(st.feesInfo.maxFees, st.feesInfo.l1GasFee, obj.accountAddress, obj.chainId, mainNet.chainId, interpreted.value)
|
||||||
obj.fiatMaxFees = st.fiatMaxFees
|
obj.fiatMaxFees = st.fiatMaxFees
|
||||||
|
|
|
@ -93,7 +93,7 @@ WalletConnectSDKBase {
|
||||||
preparedData: interpreted.preparedData,
|
preparedData: interpreted.preparedData,
|
||||||
maxFeesText: "?",
|
maxFeesText: "?",
|
||||||
maxFeesEthText: "?",
|
maxFeesEthText: "?",
|
||||||
enoughFunds: enoughFunds,
|
enoughFunds: enoughFunds
|
||||||
})
|
})
|
||||||
|
|
||||||
if (obj === null) {
|
if (obj === null) {
|
||||||
|
|
|
@ -198,7 +198,7 @@ WalletConnectSDKBase {
|
||||||
console.debug(`WC WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
|
console.debug(`WC WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
|
||||||
|
|
||||||
d.engine.runJavaScript(`
|
d.engine.runJavaScript(`
|
||||||
wc.rejectSessionRequest("${topic}", ${id}, "${error}")
|
wc.rejectSessionRequest("${topic}", ${id}, ${error})
|
||||||
.then((value) => {
|
.then((value) => {
|
||||||
wc.statusObject.onRejectSessionRequestResponse("${topic}", ${id}, "")
|
wc.statusObject.onRejectSessionRequestResponse("${topic}", ${id}, "")
|
||||||
})
|
})
|
||||||
|
@ -366,6 +366,11 @@ WalletConnectSDKBase {
|
||||||
console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details)}`)
|
console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details)}`)
|
||||||
root.sessionProposalExpired()
|
root.sessionProposalExpired()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSessionRequestExpire(id) {
|
||||||
|
console.debug(`WC WalletConnectSDK.onSessionRequestExpire; id: ${id}`)
|
||||||
|
root.sessionRequestExpired(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebEngineLoader {
|
WebEngineLoader {
|
||||||
|
|
|
@ -112,7 +112,10 @@ QObject {
|
||||||
signal approveSessionResult(var key, var error, var topic)
|
signal approveSessionResult(var key, var error, var topic)
|
||||||
// Emitted when a new session is requested by a dApp
|
// Emitted when a new session is requested by a dApp
|
||||||
signal sessionRequest(string id)
|
signal sessionRequest(string id)
|
||||||
signal displayToastMessage(string message, bool error)
|
// Emitted when the services requests to display a toast message
|
||||||
|
// @param message The message to display
|
||||||
|
// @param type The type of the message. Maps to Constants.ephemeralNotificationType
|
||||||
|
signal displayToastMessage(string message, int type)
|
||||||
// Emitted as a response to WalletConnectService.validatePairingUri or other WalletConnectService.pair
|
// Emitted as a response to WalletConnectService.validatePairingUri or other WalletConnectService.pair
|
||||||
// and WalletConnectService.approvePair errors
|
// and WalletConnectService.approvePair errors
|
||||||
signal pairingValidated(int validationState)
|
signal pairingValidated(int validationState)
|
||||||
|
@ -298,9 +301,9 @@ QObject {
|
||||||
function notifyDappDisconnect(dappUrl, err) {
|
function notifyDappDisconnect(dappUrl, err) {
|
||||||
const appDomain = StringUtils.extractDomainFromLink(dappUrl)
|
const appDomain = StringUtils.extractDomainFromLink(dappUrl)
|
||||||
if(err) {
|
if(err) {
|
||||||
root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(appDomain), true)
|
root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(appDomain), Constants.ephemeralNotificationType.danger)
|
||||||
} else {
|
} else {
|
||||||
root.displayToastMessage(qsTr("Disconnected from %1").arg(appDomain), false)
|
root.displayToastMessage(qsTr("Disconnected from %1").arg(appDomain), Constants.ephemeralNotificationType.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +402,7 @@ QObject {
|
||||||
// TODO #14754: implement custom dApp notification
|
// TODO #14754: implement custom dApp notification
|
||||||
const app_url = proposal.params.proposer.metadata.url ?? "-"
|
const app_url = proposal.params.proposer.metadata.url ?? "-"
|
||||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||||
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_domain), false)
|
root.displayToastMessage(qsTr("Connected to %1 via WalletConnect").arg(app_domain), Constants.ephemeralNotificationType.success)
|
||||||
|
|
||||||
// Persist session
|
// Persist session
|
||||||
if(!store.addWalletConnectSession(JSON.stringify(session))) {
|
if(!store.addWalletConnectSession(JSON.stringify(session))) {
|
||||||
|
@ -425,9 +428,9 @@ QObject {
|
||||||
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
const app_domain = StringUtils.extractDomainFromLink(app_url)
|
||||||
if(err) {
|
if(err) {
|
||||||
d.reportPairErrorState(Pairing.errors.unknownError)
|
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), Constants.ephemeralNotificationType.danger)
|
||||||
} 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), Constants.ephemeralNotificationType.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,8 +457,8 @@ QObject {
|
||||||
timeoutTimer.stop()
|
timeoutTimer.stop()
|
||||||
root.sessionRequest(id)
|
root.sessionRequest(id)
|
||||||
}
|
}
|
||||||
onDisplayToastMessage: (message, error) => {
|
onDisplayToastMessage: (message, type) => {
|
||||||
root.displayToastMessage(message, error)
|
root.displayToastMessage(message, type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -93,8 +93,8 @@ window.wc = {
|
||||||
// const { topic } = event;
|
// const { topic } = event;
|
||||||
});
|
});
|
||||||
window.wc.web3wallet.on("session_request_expire", (event) => {
|
window.wc.web3wallet.on("session_request_expire", (event) => {
|
||||||
wc.statusObject.echo("debug", `WC unhandled event: "session_request_expire" ${JSON.stringify(event)}`);
|
const { id } = event
|
||||||
// const { id } = event
|
wc.statusObject.onSessionRequestExpire(id)
|
||||||
});
|
});
|
||||||
window.wc.core.relayer.on("relayer_connect", () => {
|
window.wc.core.relayer.on("relayer_connect", () => {
|
||||||
wc.statusObject.echo("debug", `WC unhandled event: "relayer_connect" connection to the relay server is established`);
|
wc.statusObject.echo("debug", `WC unhandled event: "relayer_connect" connection to the relay server is established`);
|
||||||
|
|
|
@ -21,6 +21,9 @@ QObject {
|
||||||
required property string method
|
required property string method
|
||||||
required property string accountAddress
|
required property string accountAddress
|
||||||
required property string chainId
|
required property string chainId
|
||||||
|
// optional expiry date in ms
|
||||||
|
property var expirationTimestamp
|
||||||
|
|
||||||
// Maps to Constants.DAppConnectors values
|
// Maps to Constants.DAppConnectors values
|
||||||
required property int sourceId
|
required property int sourceId
|
||||||
|
|
||||||
|
@ -54,6 +57,14 @@ QObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isExpired() {
|
||||||
|
return !!expirationTimestamp && expirationTimestamp > 0 && Math.floor(Date.now() / 1000) >= expirationTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
function setExpired() {
|
||||||
|
expirationTimestamp = Math.floor(Date.now() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
// dApp info
|
// dApp info
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
@ -61,5 +72,6 @@ QObject {
|
||||||
property string dappName
|
property string dappName
|
||||||
property string dappUrl
|
property string dappUrl
|
||||||
property url dappIcon
|
property url dappIcon
|
||||||
|
property bool hasExpiry
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,6 +17,16 @@ ListModel {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeRequest(topic, id) {
|
||||||
|
for (var i = 0; i < root.count; i++) {
|
||||||
|
let entry = root.get(i).requestItem
|
||||||
|
if (entry.topic == topic && entry.id == id) {
|
||||||
|
root.remove(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// returns null if not found
|
/// returns null if not found
|
||||||
function findRequest(topic, id) {
|
function findRequest(topic, id) {
|
||||||
for (var i = 0; i < root.count; i++) {
|
for (var i = 0; i < root.count; i++) {
|
||||||
|
|
|
@ -2188,12 +2188,10 @@ Item {
|
||||||
Global.walletConnectService = walletConnectService
|
Global.walletConnectService = walletConnectService
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisplayToastMessage: (message, isErr) => {
|
onDisplayToastMessage: (message, type) => {
|
||||||
Global.displayToastMessage(message, "",
|
const icon = type === Constants.ephemeralNotificationType.danger ? "warning" :
|
||||||
isErr ? "warning" : "checkmark-circle", false,
|
type === Constants.ephemeralNotificationType.success ? "checkmark-circle" : "info"
|
||||||
isErr ? Constants.ephemeralNotificationType.danger
|
Global.displayToastMessage(message, "", icon, false, type, "")
|
||||||
: Constants.ephemeralNotificationType.success,
|
|
||||||
"")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ IssuePill {
|
||||||
required property int expirationSeconds
|
required property int expirationSeconds
|
||||||
onExpirationSecondsChanged: Qt.callLater(reset)
|
onExpirationSecondsChanged: Qt.callLater(reset)
|
||||||
|
|
||||||
readonly property bool isExpired: expirationSeconds > 0 && d.secsDiff <= 0
|
readonly property bool isExpired: remainingSeconds <= 0
|
||||||
readonly property int remainingSeconds: d.secsDiff
|
readonly property int remainingSeconds: d.secsDiff
|
||||||
|
|
||||||
signal expired
|
signal expired
|
||||||
|
@ -44,6 +44,8 @@ IssuePill {
|
||||||
function reset() {
|
function reset() {
|
||||||
if (expirationSeconds === 0) {
|
if (expirationSeconds === 0) {
|
||||||
timer.stop()
|
timer.stop()
|
||||||
|
d.secsDiff = -1
|
||||||
|
root.expired()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue