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
|
||||
networksModel: wcService.flatNetworks
|
||||
sessionRequestsModel: wcService.sessionRequestsModel
|
||||
enabled: wcService.isServiceOnline
|
||||
|
||||
//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
|
||||
ColumnLayout {}
|
||||
|
||||
|
@ -337,6 +345,8 @@ Item {
|
|||
projectId: projectIdText.projectId
|
||||
}
|
||||
|
||||
blockchainNetworksDown: networkFilter.selection
|
||||
|
||||
store: SharedStores.DAppsStore {
|
||||
signal dappsListReceived(string dappsJson)
|
||||
signal userAuthenticated(string topic, string id, string password, string pin)
|
||||
|
|
|
@ -121,6 +121,7 @@ Item {
|
|||
onPairingValidated: function(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 {
|
||||
ListElement {
|
||||
address: "0x1"
|
||||
|
@ -299,9 +327,10 @@ Item {
|
|||
sdk: sdk,
|
||||
store: store,
|
||||
accountsModel: walletStore.nonWatchAccounts,
|
||||
networksModel: walletStore.filteredFlatModel
|
||||
networksModel: walletStore.filteredFlatModelWithOnlineStat
|
||||
})
|
||||
verify(!!handler)
|
||||
sdk.getActiveSessionsCallbacks = []
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
|
@ -791,6 +820,7 @@ Item {
|
|||
verify(!!walletStore)
|
||||
provider = createTemporaryObject(dappsListProviderComponent, root, {sdk: sdk, store: store, supportedAccountsModel: walletStore.nonWatchAccounts})
|
||||
verify(!!provider)
|
||||
sdk.getActiveSessionsCallbacks = []
|
||||
}
|
||||
|
||||
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
|
||||
TokenMarketValuesData 1.0 TokenMarketValuesData.qml
|
||||
singleton NetworkModelHelpers 1.0 NetworkModelHelpers.qml
|
||||
ChainsAvailabilityWatchdog 1.0 ChainsAvailabilityWatchdog.qml
|
||||
|
|
|
@ -146,7 +146,7 @@ Item {
|
|||
visible: !root.walletStore.showSavedAddresses
|
||||
&& root.dappsEnabled
|
||||
&& wcService.serviceAvailableToCurrentAddress
|
||||
enabled: !!Global.walletConnectService
|
||||
enabled: !!wcService && wcService.isServiceOnline
|
||||
|
||||
|
||||
loginType: root.loginType
|
||||
|
|
|
@ -16,11 +16,13 @@ SQUtils.QObject {
|
|||
required property WalletConnectSDKBase sdk
|
||||
required property DAppsStore store
|
||||
required property var accountsModel
|
||||
// Required roles: chainId, layer, isOnline
|
||||
required property var networksModel
|
||||
required property CurrenciesStore currenciesStore
|
||||
required property WalletStore.WalletAssetsStore assetsStore
|
||||
|
||||
property alias requestsModel: requests
|
||||
readonly property bool isServiceOnline: chainsSupervisorPlugin.anyChainAvailable && sdk.sdkReady
|
||||
|
||||
function 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 {
|
||||
|
|
|
@ -32,6 +32,8 @@ QObject {
|
|||
required property WalletConnectSDKBase wcSDK
|
||||
required property DAppsStore store
|
||||
required property var walletRootStore
|
||||
// // Array[chainId] of the networks that are down
|
||||
required property var blockchainNetworksDown
|
||||
|
||||
//output properties
|
||||
/// Model contaning all dApps available for the currently selected account
|
||||
|
@ -49,6 +51,8 @@ QObject {
|
|||
/// TODO: refactor
|
||||
readonly property alias connectorDAppsProvider: connectorDAppsProvider
|
||||
|
||||
readonly property bool isServiceOnline: requestHandler.isServiceOnline
|
||||
|
||||
// methods
|
||||
/// Triggers the signing process for the given session request
|
||||
/// @param topic The topic of the session
|
||||
|
@ -250,7 +254,16 @@ QObject {
|
|||
sdk: root.wcSDK
|
||||
store: root.store
|
||||
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
|
||||
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
|
||||
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
|
||||
SessionRequestsModel 1.0 SessionRequestsModel.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
|
||||
}
|
||||
walletRootStore: WalletStores.RootStore
|
||||
blockchainNetworksDown: appMain.networkConnectionStore.blockchainNetworksDown
|
||||
|
||||
Component.onCompleted: {
|
||||
Global.walletConnectService = walletConnectService
|
||||
|
|
Loading…
Reference in New Issue