fix(@deaktop/wallet): Implement error states with automatic retries

fixes #9688
This commit is contained in:
Khushboo Mehta 2023-03-01 21:59:08 +01:00 committed by Khushboo-dev-cpp
parent 054ad18532
commit df121445ca
18 changed files with 701 additions and 23 deletions

View File

@ -32,6 +32,7 @@ import ../../app_service/service/mailservers/service as mailservers_service
import ../../app_service/service/gif/service as gif_service
import ../../app_service/service/ens/service as ens_service
import ../../app_service/service/community_tokens/service as tokens_service
import ../../app_service/service/network_connection/service as network_connection_service
import ../modules/shared_modules/keycard_popup/module as keycard_shared_module
import ../modules/startup/module as startup_module
@ -96,6 +97,7 @@ type
gifService: gif_service.Service
ensService: ens_service.Service
tokensService: tokens_service.Service
networkConnectionService: network_connection_service.Service
# Modules
startupModule: startup_module.AccessInterface
@ -216,6 +218,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.tokensService = tokens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.transactionService)
result.providerService = provider_service.newService(statusFoundation.events, statusFoundation.threadpool, result.ensService)
result.networkConnectionService = network_connection_service.newService(statusFoundation.events, result.walletAccountService, result.networkService, result.collectibleService, result.nodeService)
# Modules
result.startupModule = startup_module.newModule[AppController](
@ -264,7 +267,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.tokensService,
result.networkService,
result.generalService,
result.keycardService
result.keycardService,
result.networkConnectionService
)
# Do connections
@ -321,6 +325,7 @@ proc delete*(self: AppController) =
self.tokensService.delete
self.gifService.delete
self.keycardService.delete
self.networkConnectionService.delete
proc disconnectKeychain(self: AppController) =
for id in self.keychainConnectionIds:
@ -410,6 +415,7 @@ proc load(self: AppController) =
self.ensService.init()
self.tokensService.init()
self.gifService.init()
self.networkConnectionService.init()
# Accessible after user login
singletonInstance.engine.setRootContextProperty("appSettings", self.appSettingsVariant)

View File

@ -63,6 +63,9 @@ method browserSectionDidLoad*(self: AccessInterface) {.base.} =
method networksModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method networkConnectionModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method nodeSectionDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -23,6 +23,7 @@ import communities/module as communities_module
import node_section/module as node_section_module
import networks/module as networks_module
import communities/tokens/models/token_item
import network_connection/module as network_connection_module
import ../../../app_service/service/contacts/dto/contacts
import ../../../app_service/service/keychain/service as keychain_service
@ -57,6 +58,7 @@ import ../../../app_service/service/community_tokens/service as community_tokens
import ../../../app_service/service/network/service as network_service
import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/keycard/service as keycard_service
import ../../../app_service/service/network_connection/service as network_connection_service
import ../../../app_service/common/types
import ../../../app_service/common/social_links
@ -87,6 +89,7 @@ type
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
keychainService: keychain_service.Service
networkConnectionService: network_connection_service.Service
walletSectionModule: wallet_section_module.AccessInterface
browserSectionModule: browser_section_module.AccessInterface
profileSectionModule: profile_section_module.AccessInterface
@ -98,6 +101,7 @@ type
networksModule: networks_module.AccessInterface
keycardSharedModule: keycard_shared_module.AccessInterface
keycardSharedModuleKeycardSyncPurpose: keycard_shared_module.AccessInterface
networkConnectionModule: network_connection_module.AccessInterface
moduleLoaded: bool
chatsLoaded: bool
communityDataLoaded: bool
@ -141,7 +145,8 @@ proc newModule*[T](
communityTokensService: community_tokens_service.Service,
networkService: network_service.Service,
generalService: general_service.Service,
keycardService: keycard_service.Service
keycardService: keycard_service.Service,
networkConnectionService: network_connection_service.Service
): Module[T] =
result = Module[T]()
result.delegate = delegate
@ -206,6 +211,7 @@ proc newModule*[T](
messageService)
result.nodeSectionModule = node_section_module.newModule(result, events, settingsService, nodeService, nodeConfigurationService)
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService)
result.networkConnectionModule = network_connection_module.newModule(result, events, networkConnectionService)
method delete*[T](self: Module[T]) =
self.controller.delete
@ -225,6 +231,7 @@ method delete*[T](self: Module[T]) =
self.keycardSharedModule.delete
if not self.keycardSharedModuleKeycardSyncPurpose.isNil:
self.keycardSharedModuleKeycardSyncPurpose.delete
self.networkConnectionModule.delete
self.view.delete
self.viewVariant.delete
@ -498,6 +505,7 @@ method load*[T](
self.nodeSectionModule.load()
# Load wallet last as it triggers events that are listened by other modules
self.walletSectionModule.load()
self.networkConnectionModule.load()
# Set active section on app start
# If section is empty or profile then open the loading section until chats are loaded
@ -657,6 +665,9 @@ proc checkIfModuleDidLoad [T](self: Module[T]) =
if(not self.networksModule.isLoaded()):
return
if(not self.networkConnectionModule.isLoaded()):
return
self.moduleLoaded = true
self.delegate.mainDidLoad()
@ -696,6 +707,9 @@ method nodeSectionDidLoad*[T](self: Module[T]) =
method networksModuleDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method networkConnectionModuleDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method viewDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()

View File

@ -0,0 +1,42 @@
import NimQml
import ./io_interface
import ../../../core/eventemitter
import ../../../../app_service/service/network_connection/service as network_connection_service
import ../../../../app_service/service/node/service as node_service
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
networkConnectionService: network_connection_service.Service
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
networkConnectionService: network_connection_service.Service
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.networkConnectionService = networkConnectionService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
self.events.on(SIGNAL_CONNECTION_UPDATE) do(e:Args):
let args = NetworkConnectionsArgs(e)
self.delegate.networkConnectionStatusUpdate(args.website, args.completelyDown, ord(args.connectionState), args.chainIds, args.lastCheckedAt, args.timeToAutoRetryInSecs, args.withCache)
self.events.on(SIGNAL_NETWORK_CONNECTED) do(e: Args):
self.networkConnectionService.networkConnected()
proc refreshBlockchainValues*(self: Controller) =
self.networkConnectionService.blockchainsRetry()
proc refreshMarketValues*(self: Controller) =
self.networkConnectionService.marketRetry()
proc refreshCollectiblesValues*(self: Controller) =
self.networkConnectionService.collectiblesRetry()

View File

@ -0,0 +1,32 @@
import NimQml
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method networkConnectionStatusUpdate*(self: AccessInterface, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method refreshBlockchainValues*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method refreshMarketValues*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method refreshCollectiblesValues*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,62 @@
import NimQml
import io_interface, view, controller
import ../io_interface as delegate_interface
import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/network_connection/service as network_connection_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
networkConnectionService: network_connection_service.Service,
): Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, networkConnectionService)
result.moduleLoaded = false
singletonInstance.engine.setRootContextProperty("networkConnectionModule", result.viewVariant)
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
proc checkIfModuleDidLoad(self: Module) =
self.moduleLoaded = true
self.delegate.networkConnectionModuleDidLoad()
method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method networkConnectionStatusUpdate*(self: Module, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) =
self.view.networkConnectionStatusUpdate(website, completelyDown, connectionState, chainIds, lastCheckedAt, timeToAutoRetryInSecs, withCache)
method refreshBlockchainValues*(self: Module) =
self.controller.refreshBlockchainValues()
method refreshMarketValues*(self: Module) =
self.controller.refreshMarketValues()
method refreshCollectiblesValues*(self: Module) =
self.controller.refreshCollectiblesValues()

View File

@ -0,0 +1,34 @@
import NimQml
import ./io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
proc setup(self: View) =
self.QObject.setup
proc delete*(self: View) =
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.setup()
proc load*(self: View) =
self.delegate.viewDidLoad()
proc networkConnectionStatusUpdate*(self: View, website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {.signal.}
proc refreshBlockchainValues*(self: View) {.slot.} =
self.delegate.refreshBlockchainValues()
proc refreshMarketValues*(self: View) {.slot.} =
self.delegate.refreshMarketValues()
proc refreshCollectiblesValues*(self: View) {.slot.} =
self.delegate.refreshCollectiblesValues()

View File

@ -3,6 +3,7 @@ import io_interface
import ../../../../../app_service/service/collectible/service as collectible_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ../../../../../app_service/service/network/service as network_service
import ../../../../../app_service/service/network_connection/service as network_connection_service
import ../../../../core/eventemitter
type
@ -50,6 +51,12 @@ proc init*(self: Controller) =
let args = OwnedCollectiblesUpdateArgs(e)
self.refreshCollectibles(args.chainId, args.address)
self.events.on(SIGNAL_REFRESH_COLLECTIBLES) do(e:Args):
let args = RetryCollectibleArgs(e)
let chainId = self.networkService.getNetworkForCollectibles().chainId
for address in args.addresses:
self.refreshCollectibles(chainId, address)
proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto =
return self.walletAccountService.getWalletAccount(accountIndex)

View File

@ -155,6 +155,14 @@ QtObject:
of "wallet-tick-reload":
self.resetAllOwnedCollectibles()
# needs to be re-written once cache for colletibles works
proc areCollectionsLoaded*(self: Service): bool =
for chainId, adressesData in self.ownershipData:
for address, collectionsData in adressesData:
if collectionsData.allLoaded:
return true
return false
proc prepareOwnershipData(self: Service, chainId: int, address: string) =
if not self.accountsOwnershipData.hasKey(chainId):
self.accountsOwnershipData[chainId] = newAddressesData()
@ -428,4 +436,4 @@ QtObject:
proc resetAllOwnedCollectibles*(self: Service) =
for chainId, addressesData in self.accountsOwnershipData:
for address, _ in addressesData:
self.resetOwnedCollectibles(chainId, address)
self.resetOwnedCollectibles(chainId, address)

View File

@ -0,0 +1,265 @@
import NimQml, chronicles, Tables, strutils, sequtils, sugar, strformat, json
import ../../../app/global/global_singleton
import ../../../app/core/eventemitter
import ../../../app/core/signals/types
import ../collectible/service as collectible_service
import ../wallet_account/service as wallet_service
import ../network/service as network_service
import ../node/service as node_service
import ../../../backend/backend as backend
logScope:
topics = "network-connection-service"
type ConnectionState* {.pure.} = enum
Successful = 0,
Failed = 1,
Retrying = 2
type ConnectionStatus* = ref object of RootObj
connectionState*: ConnectionState
completelyDown*: bool
chainIds*: seq[int]
lastCheckedAt*: int
timeToAutoRetryInSecs*: int
timer*: QTimer
withCache*: bool
const SIGNAL_CONNECTION_UPDATE* = "signalConnectionUpdate"
const SIGNAL_REFRESH_COLLECTIBLES* = "signalRefreshCollectibles"
type NetworkConnectionsArgs* = ref object of Args
website*: string
completelyDown*: bool
connectionState*: ConnectionState
chainIds*: string
lastCheckedAt*: int
timeToAutoRetryInSecs*: int
withCache*: bool
type RetryCollectibleArgs* = ref object of Args
addresses*: seq[string]
const BLOCKCHAINS* = "blockchains"
const MARKET* = "market"
const COLLECTIBLES* = "collectibles"
const BACKOFF_TIMERS* = [30, 60, 180, 600, 3600, 10800]
include ../../common/json_utils
proc newConnectionStatus(): ConnectionStatus =
return ConnectionStatus(
connectionState: ConnectionState.Successful,
completelyDown: false,
chainIds: @[],
lastCheckedAt: 0,
timeToAutoRetryInSecs: BACKOFF_TIMERS[0],
timer: newQTimer(),
withCache: false
)
QtObject:
type Service* = ref object of QObject
closingApp: bool
events: EventEmitter
walletService: wallet_service.Service
networkService: network_service.Service
collectibleService: collectible_service.Service
nodeService: node_service.Service
connectionStatus: Table[string, ConnectionStatus]
# Forward declaration
proc checkConnected(self: Service)
proc delete*(self: Service) =
self.closingApp = true
self.QObject.delete
proc newService*(
events: EventEmitter,
walletService: wallet_service.Service,
networkService: network_service.Service,
collectibleService: collectible_service.Service,
nodeService: node_service.Service,
): Service =
new(result, delete)
result.QObject.setup
result.closingApp = false
result.events = events
result.walletService = walletService
result.networkService = networkService
result.collectibleService = collectibleService
result.nodeService = nodeService
result.connectionStatus = {BLOCKCHAINS: newConnectionStatus(),
MARKET: newConnectionStatus(),
COLLECTIBLES: newConnectionStatus()}.toTable
proc init*(self: Service) =
self.events.on(SignalType.Wallet.event) do(e:Args):
var data = WalletSignal(e)
case data.eventType:
of "wallet-tick-check-connected":
self.checkConnected()
proc getFormattedStringForChainIds(self: Service, chainIds: seq[int]): string =
var result: string = ""
for chainId in chainIds:
if result.isEmptyOrWhitespace:
result = $chainId
else:
result = result & ";" & $chainId
return result
proc convertConnectionStatusToNetworkConnectionsArgs(self: Service, website: string, connectionStatus: ConnectionStatus): NetworkConnectionsArgs =
result = NetworkConnectionsArgs(
website: website,
completelyDown: connectionStatus.completelyDown,
connectionState: connectionStatus.connectionState,
chainIds: self.getFormattedStringForChainIds(connectionStatus.chainIds),
lastCheckedAt: connectionStatus.lastCheckedAt,
timeToAutoRetryInSecs: connectionStatus.timeToAutoRetryInSecs,
withCache: connectionStatus.withCache
)
proc blockchainsRetry*(self: Service) {.slot.} =
if(self.connectionStatus.hasKey(BLOCKCHAINS)):
self.connectionStatus[BLOCKCHAINS].timer.stop()
self.connectionStatus[BLOCKCHAINS].connectionState = ConnectionState.Retrying
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(BLOCKCHAINS, self.connectionStatus[BLOCKCHAINS]))
self.walletService.reloadAccountTokens()
proc marketRetry*(self: Service) {.slot.} =
if(self.connectionStatus.hasKey(MARKET)):
self.connectionStatus[MARKET].timer.stop()
self.connectionStatus[MARKET].connectionState = ConnectionState.Retrying
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(MARKET, self.connectionStatus[MARKET]))
self.walletService.reloadAccountTokens()
proc collectiblesRetry*(self: Service) {.slot.} =
if(self.connectionStatus.hasKey(COLLECTIBLES)):
self.connectionStatus[COLLECTIBLES].timer.stop()
self.connectionStatus[COLLECTIBLES].connectionState = ConnectionState.Retrying
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(COLLECTIBLES, self.connectionStatus[COLLECTIBLES]))
self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, RetryCollectibleArgs(addresses: self.walletService.getAddresses()))
# needs to be re-written once cache for market, blockchain and collectibles is implemented
proc hasCache(self: Service, website: string): bool =
case website:
of BLOCKCHAINS:
return self.walletService.hasCache()
of MARKET:
return self.walletService.hasCache()
of COLLECTIBLES:
return self.collectibleService.areCollectionsLoaded()
proc checkStatus(self: Service, status: JsonNode, website: string) =
var allDown: bool = true
var lastCheckedAt: int = 0
var chaindIdsDown: seq[int] = @[]
# checking only for networks currently active (for test net only testnet networks etc...)
let currentChainIds = self.networkService.getNetworks().map(a => a.chainId)
for chainId, state in status:
if state["up"].getBool:
allDown = false
# only add chains that belong to the test node or not based on current user setting
if currentChainIds.contains(chainId.parseInt):
lastCheckedAt = state["lastCheckedAt"].getInt
if not state["up"].getBool:
chaindIdsDown.add(chainId.parseInt)
if self.connectionStatus.hasKey(website):
self.connectionStatus[website].withCache = self.hasCache(website)
# if all the networks are down for the website
if allDown:
if not self.connectionStatus[website].timer.isActive():
var backOffTimer: int = self.connectionStatus[website].timeToAutoRetryInSecs
# if all the networks are down for the website after a retry increment the backoff timer
if self.connectionStatus[website].completelyDown and self.connectionStatus[website].connectionState == ConnectionState.Retrying:
let index = BACKOFF_TIMERS.find(self.connectionStatus[website].timeToAutoRetryInSecs)
if index != -1 and index < BACKOFF_TIMERS.len:
backOffTimer = BACKOFF_TIMERS[index + 1]
self.connectionStatus[website].connectionState = ConnectionState.Failed
self.connectionStatus[website].completelyDown = true
self.connectionStatus[website].lastCheckedAt = lastCheckedAt
self.connectionStatus[website].timeToAutoRetryInSecs = backOffTimer
self.connectionStatus[website].chainIds = chaindIdsDown
signalConnect(self.connectionStatus[website].timer, "timeout()", self, website&"Retry()", 2)
self.connectionStatus[website].timer.setInterval(backOffTimer*1000)
self.connectionStatus[website].timer.start()
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[website].completelyDown or (chaindIdsDown.len == 0 and self.connectionStatus[website].chainIds.len != 0):
self.connectionStatus[website] = newConnectionStatus()
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
# case where a some of networks on the website are down
if chaindIdsDown.len > 0:
var backOffTimer: int = self.connectionStatus[website].timeToAutoRetryInSecs
if self.connectionStatus[website].connectionState == ConnectionState.Retrying:
let index = BACKOFF_TIMERS.find(self.connectionStatus[website].timeToAutoRetryInSecs)
if index != -1 and index < BACKOFF_TIMERS.len:
backOffTimer = BACKOFF_TIMERS[index + 1]
self.connectionStatus[website].completelyDown = false
self.connectionStatus[website].chainIds = chaindIdsDown
self.connectionStatus[website].timeToAutoRetryInSecs = backOffTimer
self.connectionStatus[website].connectionState = ConnectionState.Failed
self.connectionStatus[website].lastCheckedAt = lastCheckedAt
signalConnect(self.connectionStatus[website].timer, "timeout()", self, website&"Retry()", 2)
self.connectionStatus[website].timer.setInterval(self.connectionStatus[website].timeToAutoRetryInSecs*1000)
self.connectionStatus[website].timer.start()
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
proc checkMarketStatus(self: Service, status: JsonNode, website: string) =
if self.connectionStatus.hasKey(website):
self.connectionStatus[website].withCache = self.hasCache(website)
if not status["up"].getBool:
if not self.connectionStatus[website].timer.isActive():
var backOffTimer: int = self.connectionStatus[website].timeToAutoRetryInSecs
if self.connectionStatus[website].connectionState == ConnectionState.Retrying:
let index = BACKOFF_TIMERS.find(self.connectionStatus[website].timeToAutoRetryInSecs)
if index != -1 and index < BACKOFF_TIMERS.len:
backOffTimer = BACKOFF_TIMERS[index + 1]
self.connectionStatus[website].completelyDown = true
self.connectionStatus[website].connectionState = ConnectionState.Failed
self.connectionStatus[website].timeToAutoRetryInSecs = backOffTimer
self.connectionStatus[website].lastCheckedAt = status["lastCheckedAt"].getInt
signalConnect(self.connectionStatus[website].timer, "timeout()", self, website&"Retry()", 2)
self.connectionStatus[website].timer.setInterval(self.connectionStatus[website].timeToAutoRetryInSecs*1000)
self.connectionStatus[website].timer.start()
self.events.emit(SIGNAL_CONNECTION_UPDATE,self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
else:
# site was completely down and is back up now
if self.connectionStatus[website].completelyDown:
self.connectionStatus[website] = newConnectionStatus()
self.events.emit(SIGNAL_CONNECTION_UPDATE, self.convertConnectionStatusToNetworkConnectionsArgs(website, self.connectionStatus[website]))
proc checkConnected(self: Service) =
if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
return
try:
if self.nodeService.isConnected():
let response = backend.checkConnected()
self.checkStatus(response.result[BLOCKCHAINS], BLOCKCHAINS)
self.checkStatus(response.result[COLLECTIBLES], COLLECTIBLES)
self.checkMarketStatus(response.result[MARKET], MARKET)
except Exception as e:
let errDescription = e.msg
error "error: ", errDescription
return
proc networkConnected*(self: Service) =
self.walletService.reloadAccountTokens()
self.events.emit(SIGNAL_REFRESH_COLLECTIBLES, RetryCollectibleArgs(addresses: self.walletService.getAddresses()))

View File

@ -125,7 +125,6 @@ QtObject:
# Forward declaration
proc buildAllTokens(self: Service, accounts: seq[string], store: bool)
proc checkRecentHistory*(self: Service)
proc checkConnected(self: Service)
proc startWallet(self: Service)
proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto])
proc handleKeycardsState(self: Service, keycardsState: seq[KeyPairDto])
@ -217,7 +216,7 @@ QtObject:
withLock self.walletAccountsLock:
result = toSeq(self.walletAccounts.values)
proc getAddresses(self: Service): seq[string] =
proc getAddresses*(self: Service): seq[string] =
withLock self.walletAccountsLock:
result = toSeq(self.walletAccounts.keys())
@ -253,8 +252,10 @@ QtObject:
of "wallet-tick-reload":
self.buildAllTokens(self.getAddresses(), store = true)
self.checkRecentHistory()
of "wallet-tick-check-connected":
self.checkConnected()
proc reloadAccountTokens*(self: Service) =
self.buildAllTokens(self.getAddresses(), store = true)
self.checkRecentHistory()
proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
let accounts = self.getWalletAccounts()
@ -274,19 +275,6 @@ QtObject:
discard backend.startWallet()
proc checkConnected(self: Service) =
if(not main_constants.WALLET_ENABLED):
return
try:
# TODO: add event for UI (Waiting for design)
discard backend.checkConnected()
except Exception as e:
let errDescription = e.msg
error "error: ", errDescription
return
proc checkRecentHistory*(self: Service) =
if(not main_constants.WALLET_ENABLED):
return
@ -855,3 +843,11 @@ QtObject:
totalTokenBalance += token.getTotalBalanceOfSupportedChains()
return totalTokenBalance
# needs to be re-written once cache for market, blockchain and collectibles is implemented
proc hasCache*(self: Service): bool =
withLock self.walletAccountsLock:
for address, accountDto in self.walletAccounts:
if self.walletAccounts[address].tokens.len > 0:
return true
return false

View File

@ -156,4 +156,29 @@ QtObject {
function resolveENS(value) {
mainModuleInst.resolveENS(value, "")
}
property var networkConnectionModuleInst: networkConnectionModule
function getChainIdsJointString(chainIdsDown) {
let jointChainIdString = ""
for (const chain of chainIdsDown) {
jointChainIdString = (!!jointChainIdString) ? jointChainIdString + " & " : jointChainIdString
jointChainIdString += allNetworks.getNetworkFullName(parseInt(chain))
}
return jointChainIdString
}
function retryConnection(websiteDown) {
switch(websiteDown) {
case Constants.walletConnections.blockchains:
networkConnectionModule.refreshBlockchainValues()
break
case Constants.walletConnections.market:
networkConnectionModule.refreshMarketValues()
break
case Constants.walletConnections.collectibles:
networkConnectionModule.refreshCollectiblesValues()
break
}
}
}

View File

@ -602,7 +602,7 @@ Item {
objectName: "connectionInfoBanner"
Layout.fillWidth: true
text: isConnected ? qsTr("Connected") : qsTr("Disconnected")
text: isConnected ? qsTr("You are back online") : qsTr("Internet connection lost. Reconnect to ensure everything is up to date.")
type: isConnected ? ModuleWarning.Success : ModuleWarning.Danger
function updateState() {
@ -676,6 +676,105 @@ Item {
}
}
}
ConnectionWarnings {
id: walletBlockchainConnectionBanner
objectName: "walletBlockchainConnectionBanner"
readonly property string jointChainIdString: appMain.rootStore.getChainIdsJointString(chainIdsDown)
Layout.fillWidth: true
websiteDown: Constants.walletConnections.blockchains
text: {
switch(connectionState) {
case Constants.ConnectionStatus.Success:
return qsTr("Pocket Network (POKT) connection successful")
case Constants.ConnectionStatus.Failure:
if(completelyDown) {
updateTimer.restart()
if(withCache)
return qsTr("POKT & Infura down. Token balances are as of %1. Retrying in %2.").arg(lastCheckedAt).arg(Utils.getTimerString(autoTryTimerInSecs))
else
return qsTr("POKT & Infura down. Token balances cannot be retrieved. Retrying in %1.").arg(Utils.getTimerString(autoTryTimerInSecs))
}
else if(chainIdsDown.length > 0) {
updateTimer.restart()
if(chainIdsDown.length > 2) {
return qsTr("POKT & Infura down for <a href='#'>multiple chains </a>. Token balances for those chains cannot be retrieved. Retrying in %1.").arg(Utils.getTimerString(autoTryTimerInSecs))
}
else if(chainIdsDown.length === 1) {
return qsTr("POKT & Infura down for %1. %1 token balances are as of %2. Retrying in %3.").arg(jointChainIdString).arg(lastCheckedAt).arg(Utils.getTimerString(autoTryTimerInSecs))
}
else {
return qsTr("POKT & Infura down for %1. %1 token balances cannot be retrieved. Retrying in %2.").arg(jointChainIdString).arg(Utils.getTimerString(autoTryTimerInSecs))
}
}
else
return ""
case Constants.ConnectionStatus.Retrying:
return qsTr("Retrying connection to Pocket Network (POKT).")
default:
return ""
}
}
onLinkActivated: {
let tootipMessage = qsTr("Pocket Network (POKT) & Infura are currently both unavailable for %1. Balances for those chains are as of %2.").arg(jointChainIdString).arg(lastCheckedAt)
toolTip.show(tootipMessage, 3000)
}
StatusToolTip {
id: toolTip
orientation: StatusToolTip.Orientation.Bottom
}
}
ConnectionWarnings {
id: walletCollectiblesConnectionBanner
objectName: "walletCollectiblesConnectionBanner"
Layout.fillWidth: true
websiteDown: Constants.walletConnections.collectibles
text: {
switch(connectionState) {
case Constants.ConnectionStatus.Success:
return qsTr("Opensea connection successful")
case Constants.ConnectionStatus.Failure:
updateTimer.restart()
if(withCache){
return qsTr("Opensea down. Collectibles are as of %1. Retrying in %2.").arg(lastCheckedAt).arg(Utils.getTimerString(autoTryTimerInSecs))
}
else {
return qsTr("Opensea down. Retrying in %1.").arg(Utils.getTimerString(autoTryTimerInSecs))
}
case Constants.ConnectionStatus.Retrying:
return qsTr("Retrying connection to Opensea...")
default:
return ""
}
}
}
ConnectionWarnings {
id: walletMarketConnectionBanner
objectName: "walletMarketConnectionBanner"
Layout.fillWidth: true
websiteDown: Constants.walletConnections.market
text: {
switch(connectionState) {
case Constants.ConnectionStatus.Success:
return qsTr("CryptoCompare and CoinGecko connection successful")
case Constants.ConnectionStatus.Failure: {
updateTimer.restart()
if(withCache) {
return qsTr("CryptoCompare and CoinGecko down. Market values are as of %1. Retrying in %2.").arg(lastCheckedAt).arg(Utils.getTimerString(autoTryTimerInSecs))
}
else {
return qsTr("CryptoCompare and CoinGecko down. Market values cannot be retrieved. Trying again in %1.").arg(Utils.getTimerString(autoTryTimerInSecs))
}
}
case Constants.ConnectionStatus.Retrying:
return qsTr("Retrying connection to CryptoCompare and CoinGecko...")
}
}
}
}
Item {

View File

@ -0,0 +1,57 @@
import QtQuick 2.14
import StatusQ.Core 0.1
import utils 1.0
ModuleWarning {
id: root
property string websiteDown
property int connectionState: -1
property int autoTryTimerInSecs: 0
property var chainIdsDown: []
property bool completelyDown: false
property string lastCheckedAt
property bool withCache: false
property Timer updateTimer: Timer {
interval: 1000
repeat: true
onTriggered: {
if (root.autoTryTimerInSecs === 0) {
stop()
return
}
root.autoTryTimerInSecs = root.autoTryTimerInSecs - 1
}
}
function updateBanner() {
hide()
if (connectionState === Constants.ConnectionStatus.Failure)
show()
else
showFor(3000)
}
type: connectionState === Constants.ConnectionStatus.Success ? ModuleWarning.Success : ModuleWarning.Danger
buttonText: connectionState === Constants.ConnectionStatus.Failure ? qsTr("Retry now") : ""
onClicked: appMain.rootStore.retryConnection(websiteDown)
onCloseClicked: hide()
Connections {
target: appMain.rootStore.networkConnectionModuleInst
function onNetworkConnectionStatusUpdate(website: string, completelyDown: bool, connectionState: int, chainIds: string, lastCheckedAt: int, timeToAutoRetryInSecs: int, withCache: bool) {
if (website === websiteDown) {
root.connectionState = connectionState
root.autoTryTimerInSecs = timeToAutoRetryInSecs
root.chainIdsDown = chainIds.split(";")
root.completelyDown = completelyDown
root.withCache = withCache
root.lastCheckedAt = LocaleUtils.formatDateTime(new Date(lastCheckedAt*1000))
root.updateBanner()
}
}
}
}

View File

@ -22,4 +22,5 @@ EditCroppedImagePanel 1.0 EditCroppedImagePanel.qml
NoImageUploadedPanel 1.0 NoImageUploadedPanel.qml
StatusAssetSelector 1.0 StatusAssetSelector.qml
AcceptRejectOptionsButtonsPanel 1.0 AcceptRejectOptionsButtonsPanel.qml
DidYouKnowSplashScreen 1.0 DidYouKnowSplashScreen.qml
DidYouKnowSplashScreen 1.0 DidYouKnowSplashScreen.qml
ConnectionWarnings 1.0 ConnectionWarnings.qml

View File

@ -676,6 +676,19 @@ QtObject {
readonly property string seedWalletType: "seed"
readonly property string generatedWalletType: "generated"
readonly property QtObject walletConnections: QtObject {
readonly property string collectibles: "collectibles"
readonly property string blockchains: "blockchains"
readonly property string market: "market"
}
enum ConnectionStatus {
Success = 0,
Failure = 1,
Retrying = 2
}
readonly property string dummyText: "Dummy"
readonly property int dummyModelItems: 25

View File

@ -603,6 +603,20 @@ QtObject {
return 34
}
function getTimerString(timeInSecs) {
let result = ""
const hour = Math.floor(timeInSecs/60/60)
const mins = Math.floor(timeInSecs/60)
const secs = Math.floor(timeInSecs%60)
if(hour > 0 )
result += qsTr(" %n hour(s) ", "", hour)
if(mins > 0)
result += qsTr(" %n min(s) ", "", mins)
if(secs > 0)
result += qsTr(" %n sec(s) ", "", secs)
return result
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 7bc03e22f724408cf8ac14117915f72ef7888d88
Subproject commit a3f1a84d291749d32408f340d331877555cee5c1