diff --git a/src/app/modules/main/network_connection/view.nim b/src/app/modules/main/network_connection/view.nim index 4950ae282f..4c8afa79df 100644 --- a/src/app/modules/main/network_connection/view.nim +++ b/src/app/modules/main/network_connection/view.nim @@ -62,7 +62,7 @@ QtObject: proc refreshCollectiblesValues*(self: View) {.slot.} = self.delegate.refreshCollectiblesValues() - proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) {.signal.} + proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: float) {.signal.} proc updateNetworkConnectionStatus*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) = case website: @@ -75,5 +75,5 @@ QtObject: of MARKET: self.marketValuesNetworkConnection.updateValues(completelyDown, connectionState, chainIds, lastCheckedAt) self.marketValuesNetworkConnectionChanged() - self.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, lastCheckedAt) + self.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, float(lastCheckedAt)) diff --git a/src/app_service/service/network_connection/service.nim b/src/app_service/service/network_connection/service.nim index 5aa9b035a8..2a6d962306 100644 --- a/src/app_service/service/network_connection/service.nim +++ b/src/app_service/service/network_connection/service.nim @@ -7,6 +7,8 @@ import ../../../app/core/signals/types import ../wallet_account/service as wallet_service import ../network/service as network_service import ../node/service as node_service +import backend/connection_status as connection_status_backend +import backend/collectibles as collectibles_backend logScope: topics = "network-connection-service" @@ -23,7 +25,6 @@ type ConnectionStatus* = ref object of RootObj lastCheckedAt*: int const SIGNAL_CONNECTION_UPDATE* = "signalConnectionUpdate" -const SIGNAL_REFRESH_COLLECTIBLES* = "signalRefreshCollectibles" type NetworkConnectionsArgs* = ref object of Args website*: string @@ -43,7 +44,7 @@ proc newConnectionStatus(): ConnectionStatus = connectionState: ConnectionState.Successful, completelyDown: false, chainIds: @[], - lastCheckedAt: 0, + lastCheckedAt: connection_status_backend.INVALID_TIMESTAMP, ) QtObject: @@ -56,10 +57,11 @@ QtObject: connectionStatus: Table[string, ConnectionStatus] # Forward declaration - proc updateBlockchainsStatus(self: Service, completelyDown: bool, chaindIdsDown: seq[int], at: int) - proc updateMarketOrCollectibleStatus(self: Service, website: string, isDown: bool, at: int) - proc getChainIdsDown(self: Service, message: string): (bool, seq[int]) - proc getIsDown(self: Service,message: string): bool + proc updateSimpleStatus(self: Service, website: string, isDown: bool, at: int) + proc updateMultichainStatus(self: Service, website: string, completelyDown: bool, chaindIdsDown: seq[int], at: int) + proc getChainIdsDown(self: Service, chainStatusTable: ConnectionStatusNotification): (bool, bool, seq[int], int) + proc getIsDown(message: string): bool + proc getChainStatusTable(message: string): ConnectionStatusNotification proc delete*(self: Service) = self.closingApp = true @@ -88,42 +90,67 @@ QtObject: case data.eventType: of "wallet-blockchain-status-changed": if self.nodeService.isConnected(): - let (allDown, chainsDown) = self.getChainIdsDown(data.message) - self.updateBlockchainsStatus(allDown, chainsDown, data.at) + let chainStateTable = getChainStatusTable(data.message) + let (allKnown, allDown, chainsDown, at) = self.getChainIdsDown(chainStateTable) + self.updateMultichainStatus(BLOCKCHAINS, allDown, chainsDown, data.at) of "wallet-market-status-changed": if self.nodeService.isConnected(): - self.updateMarketOrCollectibleStatus(MARKET, self.getIsDown(data.message), data.at) + self.updateSimpleStatus(MARKET, getIsDown(data.message), data.at) of "wallet-collectible-status-changed": if self.nodeService.isConnected(): - self.updateMarketOrCollectibleStatus(COLLECTIBLES, self.getIsDown(data.message), data.at) + let chainStateTable = fromJson(parseJson(data.message), ConnectionStatusNotification) + let (allKnown, allDown, chainsDown, at) = self.getChainIdsDown(chainStateTable) + if allKnown: + self.updateMultichainStatus(COLLECTIBLES, allDown, chainsDown, at) self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args): if self.connectionStatus.hasKey(MARKET): let connectionStatus = self.connectionStatus[MARKET] - self.updateMarketOrCollectibleStatus(MARKET, connectionStatus.completelyDown, connectionStatus.lastCheckedAt) + self.updateSimpleStatus(MARKET, connectionStatus.completelyDown, connectionStatus.lastCheckedAt) if self.connectionStatus.hasKey(BLOCKCHAINS): let connectionStatus = self.connectionStatus[BLOCKCHAINS] - self.updateBlockchainsStatus(connectionStatus.completelyDown, connectionStatus.chainIds, connectionStatus.lastCheckedAt) + self.updateMultichainStatus(BLOCKCHAINS, connectionStatus.completelyDown, connectionStatus.chainIds, connectionStatus.lastCheckedAt) - proc getIsDown(self: Service, message: string): bool = + proc getIsDown(message: string): bool = result = message == "down" - proc getChainIdsDown(self: Service, message: string): (bool, seq[int]) = - let chainStatusTable = parseJson(message) + proc getStateValue(message: string): connection_status_backend.StateValue = + if message == "down": + return connection_status_backend.StateValue.Disconnected + elif message == "up": + return connection_status_backend.StateValue.Connected + else: + return connection_status_backend.StateValue.Unknown + + proc getChainStatusTable(message: string): ConnectionStatusNotification = + result = initCustomStatusNotification() + + let chainStatusTable = parseJson(message) + if chainStatusTable.kind != JNull: + for k, v in chainStatusTable.pairs: + result[k] = connection_status_backend.initConnectionState( + value = getStateValue(v.getStr) + ) + + proc getChainIdsDown(self: Service, chainStatusTable: ConnectionStatusNotification): (bool, bool, seq[int], int) = + var allKnown: bool = true var allDown: bool = true var chaindIdsDown: seq[int] = @[] + var lastSuccessAt: int = connection_status_backend.INVALID_TIMESTAMP # latest succesful connectinon between the down chains let allChainIds = self.networkService.getNetworks().map(a => a.chainId) - if chainStatusTable.kind != JNull: - for id in allChainIds: - if chainStatusTable[$id].kind != JNull: - let isDown = self.getIsDown(chainStatusTable[$id].getStr) - if isDown: - chaindIdsDown.add(id) - else: - allDown = false - return (allDown, chaindIdsDown) + for id in allChainIds: + if chainStatusTable.hasKey($id) and chainStatusTable[$id].value != connection_status_backend.StateValue.Unknown: + if chainStatusTable[$id].value == connection_status_backend.StateValue.Connected: + allDown = false + else: + chaindIdsDown.add(id) + lastSuccessAt = max(lastSuccessAt, chainStatusTable[$id].lastSuccessAt) + else: + allKnown = false + + return (allKnown, allDown, chaindIdsDown, lastSuccessAt) proc getFormattedStringForChainIds(self: Service, chainIds: seq[int]): string = for chainId in chainIds: @@ -142,6 +169,9 @@ QtObject: lastCheckedAt: connectionStatus.lastCheckedAt ) + proc resetConnectionStatus(self: Service, website: string) = + self.connectionStatus[website] = newConnectionStatus() + proc updateConnectionStatus(self: Service, website: string, connectionState: ConnectionState, @@ -155,7 +185,7 @@ QtObject: self.connectionStatus[website].chainIds = chainIds self.connectionStatus[website].lastCheckedAt = lastCheckedAt - proc updateMarketOrCollectibleStatus(self: Service, website: string, isDown: bool, at: int) = + proc updateSimpleStatus(self: Service, website: string, isDown: bool, at: int) = if self.connectionStatus.hasKey(website): if isDown: # trigger event @@ -167,24 +197,23 @@ QtObject: self.connectionStatus[website] = newConnectionStatus() self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website])) - proc updateBlockchainsStatus(self: Service, completelyDown: bool, chaindIdsDown: seq[int], at: int) = - if self.connectionStatus.hasKey(BLOCKCHAINS): - # if all the networks are down for the BLOCKCHAINS, trigger event + proc updateMultichainStatus(self: Service, website: string, completelyDown: bool, chaindIdsDown: seq[int], at: int) = + if self.connectionStatus.hasKey(website): + # if all the networks are down for the website, trigger event if completelyDown: - self.updateConnectionStatus(BLOCKCHAINS, ConnectionState.Failed, true, chaindIdsDown, at) - self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS])) - + self.updateConnectionStatus(website, ConnectionState.Failed, true, chaindIdsDown, at) + self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website])) # if all the networks are not down for the website else: # case where a down website is back up - if self.connectionStatus[BLOCKCHAINS].completelyDown or (chaindIdsDown.len == 0 and self.connectionStatus[BLOCKCHAINS].chainIds.len != 0): - self.connectionStatus[BLOCKCHAINS] = newConnectionStatus() - self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS])) + if self.connectionStatus[website].completelyDown or (chaindIdsDown.len == 0 and self.connectionStatus[website].chainIds.len != 0): + self.resetConnectionStatus(website) + self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website])) # case where a some of networks on the website are down, trigger event if chaindIdsDown.len > 0: - self.updateConnectionStatus(BLOCKCHAINS, ConnectionState.Failed, false, chaindIdsDown, at) - self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS])) + self.updateConnectionStatus(website, ConnectionState.Failed, false, chaindIdsDown, at) + self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website])) proc blockchainsRetry*(self: Service) {.slot.} = if(self.connectionStatus.hasKey(BLOCKCHAINS)): @@ -202,12 +231,12 @@ QtObject: if(self.connectionStatus.hasKey(COLLECTIBLES)): self.connectionStatus[COLLECTIBLES].connectionState = ConnectionState.Retrying self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(COLLECTIBLES, self.connectionStatus[COLLECTIBLES])) - self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, Args()) + discard collectibles_backend.refetchOwnedCollectibles() proc networkConnected*(self: Service, connected: bool) = if connected: self.walletService.reloadAccountTokens() - self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, Args()) + discard collectibles_backend.refetchOwnedCollectibles() else: if(self.connectionStatus.hasKey(BLOCKCHAINS)): self.connectionStatus[BLOCKCHAINS] = newConnectionStatus() diff --git a/src/backend/collectibles.nim b/src/backend/collectibles.nim index 70c0c7ac12..bd6eb411d0 100644 --- a/src/backend/collectibles.nim +++ b/src/backend/collectibles.nim @@ -138,3 +138,6 @@ rpc(filterOwnedCollectiblesAsync, "wallet"): rpc(getCollectiblesDetailsAsync, "wallet"): requestId: int32 uniqueIds: seq[CollectibleUniqueID] + +rpc(refetchOwnedCollectibles, "wallet"): + discard diff --git a/src/backend/connection_status.nim b/src/backend/connection_status.nim new file mode 100644 index 0000000000..ddfaef7329 --- /dev/null +++ b/src/backend/connection_status.nim @@ -0,0 +1,50 @@ +import json, strformat, tables + +const INVALID_TIMESTAMP* = -1 + +type + # Mirrors services/wallet/connection/types.go StateValue + StateValue* {.pure.} = enum + Unknown, + Connected, + Disconnected + + # Mirrors services/wallet/connection/types.go State + ConnectionState* = ref object of RootObj + value*: StateValue + lastCheckedAt*: int + lastSuccessAt*: int + + # Mirrors services/wallet/connection/status_notifier.go StatusNotification + ConnectionStatusNotification* = Table[string, ConnectionState] + +# ConnectionState +proc initConnectionState*(value: StateValue, lastCheckedAt: int = INVALID_TIMESTAMP, lastSuccessAt: int = INVALID_TIMESTAMP): ConnectionState = + result = ConnectionState() + result.value = value + result.lastCheckedAt = lastCheckedAt + result.lastSuccessAt = lastSuccessAt + +proc `$`*(self: ConnectionState): string = + return fmt"""ConnectionState( + value:{self.value}, + lastCheckedAt:{self.lastCheckedAt}, + lastSuccessAt:{self.lastSuccessAt} + )""" + +proc fromJson*(t: JsonNode, T: typedesc[ConnectionState]): ConnectionState {.inline.} = + result = ConnectionState() + result.value = StateValue(t["value"].getInt()) + result.lastCheckedAt = t["last_checked_at"].getInt() + result.lastSuccessAt = t["last_success_at"].getInt() + +# ConnectionStatusNotification +proc initCustomStatusNotification*(): ConnectionStatusNotification = + result = initTable[string, ConnectionState]() + +proc fromJson*(t: JsonNode, T: typedesc[ConnectionStatusNotification]): ConnectionStatusNotification {.inline.} = + result = initCustomStatusNotification() + if t.kind != JNull: + for k, v in t.pairs: + if v.kind != JNull: + result[k] = fromJson(v, ConnectionState) diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 98f4fa769c..0978cf37d2 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -925,21 +925,49 @@ Item { objectName: "walletCollectiblesConnectionBanner" Layout.fillWidth: true websiteDown: Constants.walletConnections.collectibles - withCache: networkConnectionStore.collectiblesCache + withCache: lastCheckedAtUnix > 0 networkConnectionStore: appMain.networkConnectionStore + tooltipMessage: { + if(withCache) + return qsTr("Collectibles providers are currently unavailable for %1. Collectibles for those chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt) + else + return qsTr("Collectibles providers are currently unavailable for %1.").arg(jointChainIdString) + } toastText: { switch(connectionState) { case Constants.ConnectionStatus.Success: - return qsTr("Opensea connection successful") + return qsTr("Collectibles providers connection successful") case Constants.ConnectionStatus.Failure: - if(withCache){ - return qsTr("Opensea down. Collectibles are as of %1.").arg(lastCheckedAt) + if(completelyDown) { + if(withCache) + return qsTr("Collectibles providers down. Collectibles are as of %1.").arg(lastCheckedAt) + else + return qsTr("Collectibles providers down. Collectibles cannot be retrieved.") } - else { - return qsTr("Opensea down.") + else if(chainIdsDown.length > 0) { + if(chainIdsDown.length > 2) { + if(withCache) + return qsTr("Collectibles providers down for multiple chains. Collectibles for these chains are as of %1.".arg(lastCheckedAt)) + else + return qsTr("Collectibles providers down for multiple chains. Collectibles for these chains cannot be retrieved.") + } + else if(chainIdsDown.length === 1) { + if(withCache) + return qsTr("Collectibles providers down for %1. Collectibles for this chain are as of %2.").arg(jointChainIdString).arg(lastCheckedAt) + else + return qsTr("Collectibles providers down for %1. Collectibles for this chain cannot be retrieved.").arg(jointChainIdString) + } + else { + if(withCache) + return qsTr("Collectibles providers down for %1. Collectibles for these chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt) + else + return qsTr("Collectibles providers down for %1. Collectibles for these chains cannot be retrieved.").arg(jointChainIdString) + } } + else + return "" case Constants.ConnectionStatus.Retrying: - return qsTr("Retrying connection to Opensea...") + return qsTr("Retrying connection to collectibles providers...") default: return "" } diff --git a/ui/imports/shared/panels/ConnectionWarnings.qml b/ui/imports/shared/panels/ConnectionWarnings.qml index 10f9dd12e2..c13f5cb788 100644 --- a/ui/imports/shared/panels/ConnectionWarnings.qml +++ b/ui/imports/shared/panels/ConnectionWarnings.qml @@ -15,6 +15,7 @@ Loader { property int connectionState: -1 property var chainIdsDown: [] property bool completelyDown: false + property double lastCheckedAtUnix: -1 property string lastCheckedAt property bool withCache: false property string tooltipMessage @@ -57,12 +58,13 @@ Loader { Connections { target: networkConnectionStore.networkConnectionModuleInst - function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int) { + function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAtUnix: double) { if (website === websiteDown) { root.connectionState = connectionState root.chainIdsDown = chainIds.split(";") root.completelyDown = completelyDown - root.lastCheckedAt = LocaleUtils.formatDateTime(new Date(lastCheckedAt*1000)) + root.lastCheckedAtUnix = lastCheckedAtUnix + root.lastCheckedAt = LocaleUtils.formatDateTime(new Date(lastCheckedAtUnix*1000)) root.updateBanner() } } diff --git a/ui/imports/shared/stores/NetworkConnectionStore.qml b/ui/imports/shared/stores/NetworkConnectionStore.qml index 7cb672bb7b..63d605f956 100644 --- a/ui/imports/shared/stores/NetworkConnectionStore.qml +++ b/ui/imports/shared/stores/NetworkConnectionStore.qml @@ -13,7 +13,6 @@ QtObject { readonly property bool balanceCache: walletSectionAssets.hasBalanceCache readonly property bool marketValuesCache: walletSectionAssets.hasMarketValuesCache - readonly property bool collectiblesCache: false // TODO: Issue #11636 readonly property var blockchainNetworksDown: !!networkConnectionModule.blockchainNetworkConnection.chainIds ? networkConnectionModule.blockchainNetworkConnection.chainIds.split(";") : [] readonly property bool atleastOneBlockchainNetworkAvailable: blockchainNetworksDown.length < networksModule.all.count diff --git a/vendor/status-go b/vendor/status-go index 2afe5a269d..bd6f9b098b 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 2afe5a269de11bad5412a64e27d33bd49a28d588 +Subproject commit bd6f9b098b018aba59d89d40343e4aee09e0531e