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
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set expired now"
|
||||
onClicked: {
|
||||
pill.expirationSeconds = 0
|
||||
}
|
||||
}
|
||||
Button {
|
||||
text: "Set 5 minutes (10 minutes ago) -> expired"
|
||||
onClicked: {
|
||||
|
|
|
@ -156,13 +156,14 @@ function formatApproveSessionResponse(networksArray, accountsArray, custom) {
|
|||
|
||||
function formatSessionRequest(chainId, method, params, topic, requestId) {
|
||||
const reqId = requestId || 1717149885151715
|
||||
const expiry = Date.now() / 1000 + 6000
|
||||
let paramsStr = params.map(param => `${param}`).join(',')
|
||||
return `{
|
||||
"id": ${reqId},
|
||||
"params": {
|
||||
"chainId": "eip155:${chainId}",
|
||||
"request": {
|
||||
"expiryTimestamp": 1717150185,
|
||||
"expiryTimestamp": ${expiry},
|
||||
"method": "${method}",
|
||||
"params": [${paramsStr}]
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ Item {
|
|||
chainId: network,
|
||||
data: "hello world",
|
||||
preparedData: "hello world",
|
||||
expirationTimestamp: Date.now() + 1000,
|
||||
expirationTimestamp: (Date.now() + 10000) / 1000,
|
||||
sourceId: Constants.DAppConnectors.WalletConnect
|
||||
})
|
||||
|
||||
|
@ -114,8 +114,8 @@ Item {
|
|||
}
|
||||
|
||||
property var onDisplayToastMessageTriggers: []
|
||||
onDisplayToastMessage: function(message, error) {
|
||||
onDisplayToastMessageTriggers.push({message, error})
|
||||
onDisplayToastMessage: function(message, type) {
|
||||
onDisplayToastMessageTriggers.push({message, type})
|
||||
}
|
||||
|
||||
property var onPairingValidatedTriggers: []
|
||||
|
@ -444,6 +444,150 @@ Item {
|
|||
compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees 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 {
|
||||
|
@ -561,7 +705,7 @@ Item {
|
|||
verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set")
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
|
@ -1027,5 +1171,77 @@ Item {
|
|||
verify(!popup.opened)
|
||||
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
|
||||
|| request.method === SessionRequest.methods.sendTransaction.name)
|
||||
requestPayload: request.preparedData
|
||||
expirationSeconds: request.expirationTimestamp ? request.expirationTimestamp - requestTimestamp.getTime() / 1000
|
||||
: 0
|
||||
hasExpiryDate: !!request.expirationTimestamp
|
||||
|
||||
onClosed: {
|
||||
Qt.callLater(rejectRequest)
|
||||
|
|
|
@ -40,6 +40,7 @@ StatusDialog {
|
|||
|
||||
property date requestTimestamp: new Date()
|
||||
property int expirationSeconds
|
||||
property bool hasExpiryDate: false
|
||||
|
||||
property ObjectModel leftFooterContents
|
||||
property ObjectModel rightFooterContents: ObjectModel {
|
||||
|
@ -49,7 +50,7 @@ StatusDialog {
|
|||
StatusFlatButton {
|
||||
objectName: "rejectButton"
|
||||
Layout.preferredHeight: signButton.height
|
||||
visible: !countdownPill.isExpired
|
||||
visible: !root.hasExpiryDate || !countdownPill.isExpired
|
||||
text: qsTr("Reject")
|
||||
onClicked: root.reject() // close and emit rejected() signal
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ StatusDialog {
|
|||
objectName: "signButton"
|
||||
id: signButton
|
||||
interactive: !root.feesLoading && root.signButtonEnabled
|
||||
visible: !countdownPill.isExpired
|
||||
visible: !root.hasExpiryDate || !countdownPill.isExpired
|
||||
icon.name: Constants.authenticationIconByType[root.loginType]
|
||||
disabledColor: Theme.palette.directColor8
|
||||
text: qsTr("Sign")
|
||||
|
@ -66,7 +67,7 @@ StatusDialog {
|
|||
StatusButton {
|
||||
objectName: "closeButton"
|
||||
id: closeButton
|
||||
visible: countdownPill.isExpired
|
||||
visible: root.hasExpiryDate && countdownPill.isExpired
|
||||
text: qsTr("Close")
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
@ -225,12 +226,13 @@ StatusDialog {
|
|||
|
||||
CountdownPill {
|
||||
id: countdownPill
|
||||
objectName: "countdownPill"
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Style.current.padding
|
||||
timestamp: root.requestTimestamp
|
||||
expirationSeconds: root.expirationSeconds
|
||||
visible: !!expirationSeconds
|
||||
visible: !!root.hasExpiryDate
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ SQUtils.QObject {
|
|||
}
|
||||
|
||||
signal sessionRequest(string id)
|
||||
signal displayToastMessage(string message, bool error)
|
||||
/*type - maps to Constants.ephemeralNotificationType*/
|
||||
signal displayToastMessage(string message, int type)
|
||||
|
||||
Connections {
|
||||
target: sdk
|
||||
|
@ -61,29 +62,49 @@ SQUtils.QObject {
|
|||
console.error("Error finding event for topic", topic, "id", id)
|
||||
return
|
||||
}
|
||||
|
||||
let methodStr = SessionRequest.methodToUserString(request.method)
|
||||
if (!methodStr) {
|
||||
console.error("Error finding user string for method", request.method)
|
||||
return
|
||||
}
|
||||
|
||||
d.lookupSession(topic, function(session) {
|
||||
if (session === null)
|
||||
return
|
||||
const appUrl = session.peer.metadata.url
|
||||
const appUrl = request.dappUrl
|
||||
const appDomain = SQUtils.StringUtils.extractDomainFromLink(appUrl)
|
||||
const requestExpired = request.isExpired()
|
||||
|
||||
requests.removeRequest(topic, id)
|
||||
|
||||
if (error) {
|
||||
root.displayToastMessage(qsTr("Fail to %1 from %2").arg(methodStr).arg(appDomain), true)
|
||||
|
||||
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")
|
||||
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)
|
||||
return
|
||||
}
|
||||
if (request.isExpired()) {
|
||||
console.warn("Error: request expired")
|
||||
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||
return
|
||||
}
|
||||
|
||||
d.executeSessionRequest(request, password, pin, payload)
|
||||
}
|
||||
|
||||
|
@ -105,13 +132,16 @@ SQUtils.QObject {
|
|||
if (request === null || !methodStr) {
|
||||
return
|
||||
}
|
||||
d.lookupSession(topic, function(session) {
|
||||
if (session === null)
|
||||
|
||||
if (request.isExpired()) {
|
||||
console.warn("Error: request expired")
|
||||
root.rejectSessionRequest(topic, id, true /*hasError*/)
|
||||
return
|
||||
const appDomain = SQUtils.StringUtils.extractDomainFromLink(session.peer.metadata.url)
|
||||
root.displayToastMessage(qsTr("Failed to authenticate %1 from %2").arg(methodStr).arg(appDomain), true)
|
||||
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) {
|
||||
|
@ -166,6 +196,7 @@ SQUtils.QObject {
|
|||
const interpreted = d.prepareData(method, data)
|
||||
|
||||
const enoughFunds = !d.isTransactionMethod(method)
|
||||
const requestExpiry = event.params.request.expiryTimestamp
|
||||
|
||||
let obj = sessionRequestComponent.createObject(null, {
|
||||
event,
|
||||
|
@ -179,6 +210,7 @@ SQUtils.QObject {
|
|||
maxFeesText: "?",
|
||||
maxFeesEthText: "?",
|
||||
enoughFunds: enoughFunds,
|
||||
expirationTimestamp: requestExpiry
|
||||
})
|
||||
if (obj === null) {
|
||||
console.error("Error creating SessionRequestResolved for event")
|
||||
|
@ -213,6 +245,7 @@ SQUtils.QObject {
|
|||
} else {
|
||||
console.error("Error finding mainnet network")
|
||||
}
|
||||
|
||||
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)
|
||||
obj.fiatMaxFees = st.fiatMaxFees
|
||||
|
|
|
@ -93,7 +93,7 @@ WalletConnectSDKBase {
|
|||
preparedData: interpreted.preparedData,
|
||||
maxFeesText: "?",
|
||||
maxFeesEthText: "?",
|
||||
enoughFunds: enoughFunds,
|
||||
enoughFunds: enoughFunds
|
||||
})
|
||||
|
||||
if (obj === null) {
|
||||
|
|
|
@ -198,7 +198,7 @@ WalletConnectSDKBase {
|
|||
console.debug(`WC WalletConnectSDK.wcCall.rejectSessionRequest; topic: "${topic}", id: ${id}, error: "${error}"`)
|
||||
|
||||
d.engine.runJavaScript(`
|
||||
wc.rejectSessionRequest("${topic}", ${id}, "${error}")
|
||||
wc.rejectSessionRequest("${topic}", ${id}, ${error})
|
||||
.then((value) => {
|
||||
wc.statusObject.onRejectSessionRequestResponse("${topic}", ${id}, "")
|
||||
})
|
||||
|
@ -366,6 +366,11 @@ WalletConnectSDKBase {
|
|||
console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details)}`)
|
||||
root.sessionProposalExpired()
|
||||
}
|
||||
|
||||
function onSessionRequestExpire(id) {
|
||||
console.debug(`WC WalletConnectSDK.onSessionRequestExpire; id: ${id}`)
|
||||
root.sessionRequestExpired(id)
|
||||
}
|
||||
}
|
||||
|
||||
WebEngineLoader {
|
||||
|
|
|
@ -112,7 +112,10 @@ QObject {
|
|||
signal approveSessionResult(var key, var error, var topic)
|
||||
// Emitted when a new session is requested by a dApp
|
||||
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
|
||||
// and WalletConnectService.approvePair errors
|
||||
signal pairingValidated(int validationState)
|
||||
|
@ -298,9 +301,9 @@ QObject {
|
|||
function notifyDappDisconnect(dappUrl, err) {
|
||||
const appDomain = StringUtils.extractDomainFromLink(dappUrl)
|
||||
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 {
|
||||
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
|
||||
const app_url = proposal.params.proposer.metadata.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
|
||||
if(!store.addWalletConnectSession(JSON.stringify(session))) {
|
||||
|
@ -425,9 +428,9 @@ QObject {
|
|||
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)
|
||||
root.displayToastMessage(qsTr("Failed to reject connection request for %1").arg(app_domain), Constants.ephemeralNotificationType.danger)
|
||||
} 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()
|
||||
root.sessionRequest(id)
|
||||
}
|
||||
onDisplayToastMessage: (message, error) => {
|
||||
root.displayToastMessage(message, error)
|
||||
onDisplayToastMessage: (message, type) => {
|
||||
root.displayToastMessage(message, type)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -93,8 +93,8 @@ window.wc = {
|
|||
// const { topic } = 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", () => {
|
||||
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 accountAddress
|
||||
required property string chainId
|
||||
// optional expiry date in ms
|
||||
property var expirationTimestamp
|
||||
|
||||
// Maps to Constants.DAppConnectors values
|
||||
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
|
||||
QtObject {
|
||||
id: d
|
||||
|
@ -61,5 +72,6 @@ QObject {
|
|||
property string dappName
|
||||
property string dappUrl
|
||||
property url dappIcon
|
||||
property bool hasExpiry
|
||||
}
|
||||
}
|
|
@ -17,6 +17,16 @@ ListModel {
|
|||
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
|
||||
function findRequest(topic, id) {
|
||||
for (var i = 0; i < root.count; i++) {
|
||||
|
|
|
@ -2188,12 +2188,10 @@ Item {
|
|||
Global.walletConnectService = walletConnectService
|
||||
}
|
||||
|
||||
onDisplayToastMessage: (message, isErr) => {
|
||||
Global.displayToastMessage(message, "",
|
||||
isErr ? "warning" : "checkmark-circle", false,
|
||||
isErr ? Constants.ephemeralNotificationType.danger
|
||||
: Constants.ephemeralNotificationType.success,
|
||||
"")
|
||||
onDisplayToastMessage: (message, type) => {
|
||||
const icon = type === Constants.ephemeralNotificationType.danger ? "warning" :
|
||||
type === Constants.ephemeralNotificationType.success ? "checkmark-circle" : "info"
|
||||
Global.displayToastMessage(message, "", icon, false, type, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ IssuePill {
|
|||
required property int expirationSeconds
|
||||
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
|
||||
|
||||
signal expired
|
||||
|
@ -44,6 +44,8 @@ IssuePill {
|
|||
function reset() {
|
||||
if (expirationSeconds === 0) {
|
||||
timer.stop()
|
||||
d.secsDiff = -1
|
||||
root.expired()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue