feat(WC): Handle unavailable chains or internet connection
This commits implements the `connect` `disconnect` session events for WC and also disables primary buttons for WC whenever there is no connection to internet or chains. + update tests
This commit is contained in:
parent
519bfaedfa
commit
cb772dc6b5
|
@ -72,6 +72,7 @@ Item {
|
||||||
accountsModel: wcService.validAccounts
|
accountsModel: wcService.validAccounts
|
||||||
networksModel: wcService.flatNetworks
|
networksModel: wcService.flatNetworks
|
||||||
sessionRequestsModel: wcService.sessionRequestsModel
|
sessionRequestsModel: wcService.sessionRequestsModel
|
||||||
|
enabled: wcService.isServiceOnline
|
||||||
|
|
||||||
//formatBigNumber: (number, symbol, noSymbolOption) => wcService.walletRootStore.currencyStore.formatBigNumber(number, symbol, noSymbolOption)
|
//formatBigNumber: (number, symbol, noSymbolOption) => wcService.walletRootStore.currencyStore.formatBigNumber(number, symbol, noSymbolOption)
|
||||||
|
|
||||||
|
@ -200,6 +201,13 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StatusBaseText { text: "Networks Down" }
|
||||||
|
|
||||||
|
NetworkFilter {
|
||||||
|
id: networkFilter
|
||||||
|
flatNetworks: walletConnectService.walletRootStore.filteredFlatModel
|
||||||
|
}
|
||||||
|
|
||||||
// spacer
|
// spacer
|
||||||
ColumnLayout {}
|
ColumnLayout {}
|
||||||
|
|
||||||
|
@ -337,6 +345,8 @@ Item {
|
||||||
projectId: projectIdText.projectId
|
projectId: projectIdText.projectId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockchainNetworksDown: networkFilter.selection
|
||||||
|
|
||||||
store: SharedStores.DAppsStore {
|
store: SharedStores.DAppsStore {
|
||||||
signal dappsListReceived(string dappsJson)
|
signal dappsListReceived(string dappsJson)
|
||||||
signal userAuthenticated(string topic, string id, string password, string pin)
|
signal userAuthenticated(string topic, string id, string password, string pin)
|
||||||
|
|
|
@ -121,6 +121,7 @@ Item {
|
||||||
onPairingValidated: function(validationState) {
|
onPairingValidated: function(validationState) {
|
||||||
onPairingValidatedTriggers.push({validationState})
|
onPairingValidatedTriggers.push({validationState})
|
||||||
}
|
}
|
||||||
|
blockchainNetworksDown: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +222,33 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property ListModel filteredFlatModelWithOnlineStat: ListModel {
|
||||||
|
ListElement {
|
||||||
|
chainId: 1
|
||||||
|
layer: 1
|
||||||
|
isOnline: true
|
||||||
|
}
|
||||||
|
ListElement {
|
||||||
|
chainId: 2
|
||||||
|
chainName: "Test Chain"
|
||||||
|
iconUrl: "network/Network=Ethereum"
|
||||||
|
layer: 2
|
||||||
|
isOnline: true
|
||||||
|
}
|
||||||
|
// Used by tst_balanceCheck
|
||||||
|
ListElement {
|
||||||
|
chainId: 11155111
|
||||||
|
layer: 1
|
||||||
|
isOnline: true
|
||||||
|
}
|
||||||
|
// Used by tst_balanceCheck
|
||||||
|
ListElement {
|
||||||
|
chainId: 421613
|
||||||
|
layer: 2
|
||||||
|
isOnline: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
readonly property ListModel nonWatchAccounts: ListModel {
|
readonly property ListModel nonWatchAccounts: ListModel {
|
||||||
ListElement {
|
ListElement {
|
||||||
address: "0x1"
|
address: "0x1"
|
||||||
|
@ -299,9 +327,10 @@ Item {
|
||||||
sdk: sdk,
|
sdk: sdk,
|
||||||
store: store,
|
store: store,
|
||||||
accountsModel: walletStore.nonWatchAccounts,
|
accountsModel: walletStore.nonWatchAccounts,
|
||||||
networksModel: walletStore.filteredFlatModel
|
networksModel: walletStore.filteredFlatModelWithOnlineStat
|
||||||
})
|
})
|
||||||
verify(!!handler)
|
verify(!!handler)
|
||||||
|
sdk.getActiveSessionsCallbacks = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
@ -791,6 +820,7 @@ Item {
|
||||||
verify(!!walletStore)
|
verify(!!walletStore)
|
||||||
provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store, supportedAccountsModel: walletStore.nonWatchAccounts})
|
provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store, supportedAccountsModel: walletStore.nonWatchAccounts})
|
||||||
verify(!!provider)
|
verify(!!provider)
|
||||||
|
sdk.getActiveSessionsCallbacks = []
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import StatusQ 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
// This component receives the networksModel
|
||||||
|
// and breaks down the online status of each chain
|
||||||
|
QObject {
|
||||||
|
id: root
|
||||||
|
// Required roleNames: chainId, isOnline
|
||||||
|
required property var networksModel
|
||||||
|
|
||||||
|
readonly property bool allOffline: d.allOffline
|
||||||
|
readonly property bool allOnline: d.allOnline
|
||||||
|
|
||||||
|
property bool active: true
|
||||||
|
|
||||||
|
signal chainOnlineChanged(int chainId, bool isOnline)
|
||||||
|
|
||||||
|
// Network online observer
|
||||||
|
// `chainOnlineChanged` signal when the online status of a chain changes
|
||||||
|
Instantiator {
|
||||||
|
id: networkOnlineObserver
|
||||||
|
model: root.networksModel
|
||||||
|
active: root.active
|
||||||
|
delegate: QtObject {
|
||||||
|
required property var model
|
||||||
|
property var /*var intended*/ isOnline: model.isOnline
|
||||||
|
|
||||||
|
onIsOnlineChanged: {
|
||||||
|
root.chainOnlineChanged(model.chainId, isOnline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregator to count the number of online chains
|
||||||
|
SumAggregator{
|
||||||
|
id: aggregator
|
||||||
|
model: root.active ? root.networksModel : null
|
||||||
|
roleName: "isOnline"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network checker to check if the device is online
|
||||||
|
NetworkChecker {
|
||||||
|
id: networkChecker
|
||||||
|
active: root.active
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
readonly property bool allOffline: !networkChecker.isOnline || aggregator.value === 0
|
||||||
|
readonly property bool allOnline: networkChecker.isOnline &&
|
||||||
|
aggregator.value > 0 &&
|
||||||
|
aggregator.value === networksModel.ModelCount.count
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ ChartDataBase 1.0 ChartDataBase.qml
|
||||||
TokenBalanceHistoryData 1.0 TokenBalanceHistoryData.qml
|
TokenBalanceHistoryData 1.0 TokenBalanceHistoryData.qml
|
||||||
TokenMarketValuesData 1.0 TokenMarketValuesData.qml
|
TokenMarketValuesData 1.0 TokenMarketValuesData.qml
|
||||||
singleton NetworkModelHelpers 1.0 NetworkModelHelpers.qml
|
singleton NetworkModelHelpers 1.0 NetworkModelHelpers.qml
|
||||||
|
ChainsAvailabilityWatchdog 1.0 ChainsAvailabilityWatchdog.qml
|
||||||
|
|
|
@ -146,7 +146,7 @@ Item {
|
||||||
visible: !root.walletStore.showSavedAddresses
|
visible: !root.walletStore.showSavedAddresses
|
||||||
&& root.dappsEnabled
|
&& root.dappsEnabled
|
||||||
&& wcService.serviceAvailableToCurrentAddress
|
&& wcService.serviceAvailableToCurrentAddress
|
||||||
enabled: !!Global.walletConnectService
|
enabled: !!wcService && wcService.isServiceOnline
|
||||||
|
|
||||||
|
|
||||||
loginType: root.loginType
|
loginType: root.loginType
|
||||||
|
|
|
@ -16,11 +16,13 @@ SQUtils.QObject {
|
||||||
required property WalletConnectSDKBase sdk
|
required property WalletConnectSDKBase sdk
|
||||||
required property DAppsStore store
|
required property DAppsStore store
|
||||||
required property var accountsModel
|
required property var accountsModel
|
||||||
|
// Required roles: chainId, layer, isOnline
|
||||||
required property var networksModel
|
required property var networksModel
|
||||||
required property CurrenciesStore currenciesStore
|
required property CurrenciesStore currenciesStore
|
||||||
required property WalletStore.WalletAssetsStore assetsStore
|
required property WalletStore.WalletAssetsStore assetsStore
|
||||||
|
|
||||||
property alias requestsModel: requests
|
property alias requestsModel: requests
|
||||||
|
readonly property bool isServiceOnline: chainsSupervisorPlugin.anyChainAvailable && sdk.sdkReady
|
||||||
|
|
||||||
function subscribeForFeeUpdates(topic, id) {
|
function subscribeForFeeUpdates(topic, id) {
|
||||||
d.subscribeForFeeUpdates(topic, id)
|
d.subscribeForFeeUpdates(topic, id)
|
||||||
|
@ -1175,4 +1177,11 @@ SQUtils.QObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChainsSupervisorPlugin {
|
||||||
|
id: chainsSupervisorPlugin
|
||||||
|
|
||||||
|
sdk: root.sdk
|
||||||
|
networksModel: root.networksModel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -364,14 +364,6 @@ WalletConnectSDKBase {
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function connected() {
|
|
||||||
console.debug(`WC WalletConnectSDK.wcCall.connected;`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function disconnected() {
|
|
||||||
console.debug(`WC WalletConnectSDK.wcCall.disconnected;`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
|
|
@ -32,6 +32,8 @@ QObject {
|
||||||
required property WalletConnectSDKBase wcSDK
|
required property WalletConnectSDKBase wcSDK
|
||||||
required property DAppsStore store
|
required property DAppsStore store
|
||||||
required property var walletRootStore
|
required property var walletRootStore
|
||||||
|
// // Array[chainId] of the networks that are down
|
||||||
|
required property var blockchainNetworksDown
|
||||||
|
|
||||||
//output properties
|
//output properties
|
||||||
/// Model contaning all dApps available for the currently selected account
|
/// Model contaning all dApps available for the currently selected account
|
||||||
|
@ -49,6 +51,8 @@ QObject {
|
||||||
/// TODO: refactor
|
/// TODO: refactor
|
||||||
readonly property alias connectorDAppsProvider: connectorDAppsProvider
|
readonly property alias connectorDAppsProvider: connectorDAppsProvider
|
||||||
|
|
||||||
|
readonly property bool isServiceOnline: requestHandler.isServiceOnline
|
||||||
|
|
||||||
// methods
|
// methods
|
||||||
/// Triggers the signing process for the given session request
|
/// Triggers the signing process for the given session request
|
||||||
/// @param topic The topic of the session
|
/// @param topic The topic of the session
|
||||||
|
@ -250,7 +254,16 @@ QObject {
|
||||||
sdk: root.wcSDK
|
sdk: root.wcSDK
|
||||||
store: root.store
|
store: root.store
|
||||||
accountsModel: root.validAccounts
|
accountsModel: root.validAccounts
|
||||||
networksModel: root.flatNetworks
|
networksModel: SortFilterProxyModel {
|
||||||
|
sourceModel: root.flatNetworks
|
||||||
|
proxyRoles: [
|
||||||
|
FastExpressionRole {
|
||||||
|
name: "isOnline"
|
||||||
|
expression: !root.blockchainNetworksDown.map(Number).includes(model.chainId)
|
||||||
|
expectedRoles: "chainId"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
currenciesStore: root.walletRootStore.currencyStore
|
currenciesStore: root.walletRootStore.currencyStore
|
||||||
assetsStore: root.walletRootStore.walletAssetsStore
|
assetsStore: root.walletRootStore.walletAssetsStore
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import AppLayouts.Wallet.helpers 1.0
|
||||||
|
import AppLayouts.Wallet.services.dapps 1.0
|
||||||
|
import AppLayouts.Wallet.services.dapps.types 1.0 as DAppsTypes
|
||||||
|
|
||||||
|
import StatusQ 0.1
|
||||||
|
import StatusQ.Core.Utils 0.1
|
||||||
|
|
||||||
|
// This plugin handles the chain ability for the dapps
|
||||||
|
// It monitors the chain availability and updates the dapps accordingly
|
||||||
|
// When all chains are offline, it will inform each session that the chains are offline
|
||||||
|
// When a chain comes back online, it will check if there are any active sessions and inform them
|
||||||
|
// See [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193)
|
||||||
|
QObject {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property WalletConnectSDKBase sdk
|
||||||
|
// Required roles: chainId, isOnline
|
||||||
|
required property var networksModel
|
||||||
|
// Happens when any chain is available
|
||||||
|
readonly property bool anyChainAvailable: !chainsAvailabilityWatchdog.allOffline
|
||||||
|
|
||||||
|
ChainsAvailabilityWatchdog {
|
||||||
|
id: chainsAvailabilityWatchdog
|
||||||
|
|
||||||
|
// property used for spam protection
|
||||||
|
property var lastNotifiedCStatuses: new Map()
|
||||||
|
networksModel: root.networksModel
|
||||||
|
|
||||||
|
onChainOnlineChanged: (chainId, isOnline) => {
|
||||||
|
if (!isOnline || !sdk.sdkReady) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spam protection. Spamming the SDK could result to blocks
|
||||||
|
if (lastNotifiedCStatuses.has(chainId) && lastNotifiedCStatuses.get(chainId) === isOnline) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNotifiedCStatuses.set(chainId, isOnline)
|
||||||
|
d.notifyChainConnected(chainId)
|
||||||
|
}
|
||||||
|
|
||||||
|
onAllOfflineChanged: {
|
||||||
|
if(!sdk.sdkReady) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allOffline) {
|
||||||
|
lastNotifiedCStatuses.clear()
|
||||||
|
d.notifyAllChainsDisconnected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: d
|
||||||
|
|
||||||
|
function sessionHasEventSupport(session, event) {
|
||||||
|
try {
|
||||||
|
if (!event) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.namespaces.eip155.events.includes(event)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error checking sessionHasEventSupport: ", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sessionHasChainSupport(session, chainId) {
|
||||||
|
try {
|
||||||
|
if (!chainId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let chainIds = session.namespaces.eip155.chains.map(DAppsHelpers.chainIdFromEip155);
|
||||||
|
if (!chainIds.includes(chainId)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let accounts = session.namespaces.eip155.accounts
|
||||||
|
let chainAccounts = accounts.map((account) => account.startsWith(`eip155:${chainId}`))
|
||||||
|
if (!chainAccounts.includes(true)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error checking sessionHasChainSupport: ", e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyAllChainsDisconnected() {
|
||||||
|
sdk.getActiveSessions((allSessions) => {
|
||||||
|
Object.values(allSessions).forEach((session) => {
|
||||||
|
if (!d.sessionHasEventSupport(session, "disconnect")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
session.namespaces.eip155.chains.forEach((chainId) => {
|
||||||
|
const chainInt = DAppsHelpers.chainIdFromEip155(chainId)
|
||||||
|
if (!d.sessionHasChainSupport(session, chainInt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sdk.emitSessionEvent(session.topic, {
|
||||||
|
name: "disconnect",
|
||||||
|
data: {
|
||||||
|
code: DAppsTypes.ErrorCodes.rpcErrors.disconnected,
|
||||||
|
}
|
||||||
|
}, chainId)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error emitting session event: ", e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyChainConnected(chainId) {
|
||||||
|
sdk.getActiveSessions((sessions) => {
|
||||||
|
Object.values(sessions).forEach((session) => {
|
||||||
|
if (!d.sessionHasEventSupport(session, "connect")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!d.sessionHasChainSupport(session, chainId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexChain = `0x${chainId.toString(16)}`
|
||||||
|
sdk.emitSessionEvent(session.topic, {
|
||||||
|
name: "connect",
|
||||||
|
data: {
|
||||||
|
chainId: hexChain,
|
||||||
|
}
|
||||||
|
}, `eip155:${chainId}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
ChainsSupervisorPlugin 1.0 ChainsSupervisorPlugin.qml
|
||||||
SiweRequestPlugin 1.0 SiweRequestPlugin.qml
|
SiweRequestPlugin 1.0 SiweRequestPlugin.qml
|
||||||
SiweLifeCycle 1.0 SiweLifeCycle.qml
|
SiweLifeCycle 1.0 SiweLifeCycle.qml
|
|
@ -0,0 +1,13 @@
|
||||||
|
pragma Singleton
|
||||||
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
// Maps to https://eips.ethereum.org/EIPS/eip-1193#rpc-errors
|
||||||
|
readonly property QtObject rpcErrors: QtObject {
|
||||||
|
readonly property int userRejectedRequest: 4001
|
||||||
|
readonly property int unauthorized: 4100
|
||||||
|
readonly property int unsupportedMethod: 4200
|
||||||
|
readonly property int disconnected: 4900
|
||||||
|
readonly property int chainDisconnected: 4901
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,4 +2,5 @@ SessionRequestResolved 1.0 SessionRequestResolved.qml
|
||||||
SessionRequestWithAuth 1.0 SessionRequestWithAuth.qml
|
SessionRequestWithAuth 1.0 SessionRequestWithAuth.qml
|
||||||
SessionRequestsModel 1.0 SessionRequestsModel.qml
|
SessionRequestsModel 1.0 SessionRequestsModel.qml
|
||||||
singleton SessionRequest 1.0 SessionRequest.qml
|
singleton SessionRequest 1.0 SessionRequest.qml
|
||||||
singleton Pairing 1.0 Pairing.qml
|
singleton Pairing 1.0 Pairing.qml
|
||||||
|
singleton ErrorCodes 1.0 ErrorCodes.qml
|
|
@ -2212,6 +2212,7 @@ Item {
|
||||||
controller: WalletStores.RootStore.walletConnectController
|
controller: WalletStores.RootStore.walletConnectController
|
||||||
}
|
}
|
||||||
walletRootStore: WalletStores.RootStore
|
walletRootStore: WalletStores.RootStore
|
||||||
|
blockchainNetworksDown: appMain.networkConnectionStore.blockchainNetworksDown
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Global.walletConnectService = walletConnectService
|
Global.walletConnectService = walletConnectService
|
||||||
|
|
Loading…
Reference in New Issue