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

View File

@ -63,6 +63,9 @@ method browserSectionDidLoad*(self: AccessInterface) {.base.} =
method networksModuleDidLoad*(self: AccessInterface) {.base.} = method networksModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method networkConnectionModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method nodeSectionDidLoad*(self: AccessInterface) {.base.} = method nodeSectionDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") 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 node_section/module as node_section_module
import networks/module as networks_module import networks/module as networks_module
import communities/tokens/models/token_item 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/contacts/dto/contacts
import ../../../app_service/service/keychain/service as keychain_service 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/network/service as network_service
import ../../../app_service/service/general/service as general_service import ../../../app_service/service/general/service as general_service
import ../../../app_service/service/keycard/service as keycard_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/types
import ../../../app_service/common/social_links import ../../../app_service/common/social_links
@ -87,6 +89,7 @@ type
accountsService: accounts_service.Service accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
keychainService: keychain_service.Service keychainService: keychain_service.Service
networkConnectionService: network_connection_service.Service
walletSectionModule: wallet_section_module.AccessInterface walletSectionModule: wallet_section_module.AccessInterface
browserSectionModule: browser_section_module.AccessInterface browserSectionModule: browser_section_module.AccessInterface
profileSectionModule: profile_section_module.AccessInterface profileSectionModule: profile_section_module.AccessInterface
@ -98,6 +101,7 @@ type
networksModule: networks_module.AccessInterface networksModule: networks_module.AccessInterface
keycardSharedModule: keycard_shared_module.AccessInterface keycardSharedModule: keycard_shared_module.AccessInterface
keycardSharedModuleKeycardSyncPurpose: keycard_shared_module.AccessInterface keycardSharedModuleKeycardSyncPurpose: keycard_shared_module.AccessInterface
networkConnectionModule: network_connection_module.AccessInterface
moduleLoaded: bool moduleLoaded: bool
chatsLoaded: bool chatsLoaded: bool
communityDataLoaded: bool communityDataLoaded: bool
@ -141,7 +145,8 @@ proc newModule*[T](
communityTokensService: community_tokens_service.Service, communityTokensService: community_tokens_service.Service,
networkService: network_service.Service, networkService: network_service.Service,
generalService: general_service.Service, generalService: general_service.Service,
keycardService: keycard_service.Service keycardService: keycard_service.Service,
networkConnectionService: network_connection_service.Service
): Module[T] = ): Module[T] =
result = Module[T]() result = Module[T]()
result.delegate = delegate result.delegate = delegate
@ -206,6 +211,7 @@ proc newModule*[T](
messageService) messageService)
result.nodeSectionModule = node_section_module.newModule(result, events, settingsService, nodeService, nodeConfigurationService) result.nodeSectionModule = node_section_module.newModule(result, events, settingsService, nodeService, nodeConfigurationService)
result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService) 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]) = method delete*[T](self: Module[T]) =
self.controller.delete self.controller.delete
@ -225,6 +231,7 @@ method delete*[T](self: Module[T]) =
self.keycardSharedModule.delete self.keycardSharedModule.delete
if not self.keycardSharedModuleKeycardSyncPurpose.isNil: if not self.keycardSharedModuleKeycardSyncPurpose.isNil:
self.keycardSharedModuleKeycardSyncPurpose.delete self.keycardSharedModuleKeycardSyncPurpose.delete
self.networkConnectionModule.delete
self.view.delete self.view.delete
self.viewVariant.delete self.viewVariant.delete
@ -498,6 +505,7 @@ method load*[T](
self.nodeSectionModule.load() self.nodeSectionModule.load()
# Load wallet last as it triggers events that are listened by other modules # Load wallet last as it triggers events that are listened by other modules
self.walletSectionModule.load() self.walletSectionModule.load()
self.networkConnectionModule.load()
# Set active section on app start # Set active section on app start
# If section is empty or profile then open the loading section until chats are loaded # 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()): if(not self.networksModule.isLoaded()):
return return
if(not self.networkConnectionModule.isLoaded()):
return
self.moduleLoaded = true self.moduleLoaded = true
self.delegate.mainDidLoad() self.delegate.mainDidLoad()
@ -696,6 +707,9 @@ method nodeSectionDidLoad*[T](self: Module[T]) =
method networksModuleDidLoad*[T](self: Module[T]) = method networksModuleDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()
method networkConnectionModuleDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad()
method viewDidLoad*[T](self: Module[T]) = method viewDidLoad*[T](self: Module[T]) =
self.checkIfModuleDidLoad() 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/collectible/service as collectible_service
import ../../../../../app_service/service/wallet_account/service as wallet_account_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/service as network_service
import ../../../../../app_service/service/network_connection/service as network_connection_service
import ../../../../core/eventemitter import ../../../../core/eventemitter
type type
@ -50,6 +51,12 @@ proc init*(self: Controller) =
let args = OwnedCollectiblesUpdateArgs(e) let args = OwnedCollectiblesUpdateArgs(e)
self.refreshCollectibles(args.chainId, args.address) 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 = proc getWalletAccount*(self: Controller, accountIndex: int): wallet_account_service.WalletAccountDto =
return self.walletAccountService.getWalletAccount(accountIndex) return self.walletAccountService.getWalletAccount(accountIndex)

View File

@ -155,6 +155,14 @@ QtObject:
of "wallet-tick-reload": of "wallet-tick-reload":
self.resetAllOwnedCollectibles() 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) = proc prepareOwnershipData(self: Service, chainId: int, address: string) =
if not self.accountsOwnershipData.hasKey(chainId): if not self.accountsOwnershipData.hasKey(chainId):
self.accountsOwnershipData[chainId] = newAddressesData() self.accountsOwnershipData[chainId] = newAddressesData()
@ -428,4 +436,4 @@ QtObject:
proc resetAllOwnedCollectibles*(self: Service) = proc resetAllOwnedCollectibles*(self: Service) =
for chainId, addressesData in self.accountsOwnershipData: for chainId, addressesData in self.accountsOwnershipData:
for address, _ in addressesData: 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 # Forward declaration
proc buildAllTokens(self: Service, accounts: seq[string], store: bool) proc buildAllTokens(self: Service, accounts: seq[string], store: bool)
proc checkRecentHistory*(self: Service) proc checkRecentHistory*(self: Service)
proc checkConnected(self: Service)
proc startWallet(self: Service) proc startWallet(self: Service)
proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto]) proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto])
proc handleKeycardsState(self: Service, keycardsState: seq[KeyPairDto]) proc handleKeycardsState(self: Service, keycardsState: seq[KeyPairDto])
@ -217,7 +216,7 @@ QtObject:
withLock self.walletAccountsLock: withLock self.walletAccountsLock:
result = toSeq(self.walletAccounts.values) result = toSeq(self.walletAccounts.values)
proc getAddresses(self: Service): seq[string] = proc getAddresses*(self: Service): seq[string] =
withLock self.walletAccountsLock: withLock self.walletAccountsLock:
result = toSeq(self.walletAccounts.keys()) result = toSeq(self.walletAccounts.keys())
@ -253,8 +252,10 @@ QtObject:
of "wallet-tick-reload": of "wallet-tick-reload":
self.buildAllTokens(self.getAddresses(), store = true) self.buildAllTokens(self.getAddresses(), store = true)
self.checkRecentHistory() 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 = proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
let accounts = self.getWalletAccounts() let accounts = self.getWalletAccounts()
@ -274,19 +275,6 @@ QtObject:
discard backend.startWallet() 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) = proc checkRecentHistory*(self: Service) =
if(not main_constants.WALLET_ENABLED): if(not main_constants.WALLET_ENABLED):
return return
@ -855,3 +843,11 @@ QtObject:
totalTokenBalance += token.getTotalBalanceOfSupportedChains() totalTokenBalance += token.getTotalBalanceOfSupportedChains()
return totalTokenBalance 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) { function resolveENS(value) {
mainModuleInst.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" objectName: "connectionInfoBanner"
Layout.fillWidth: true 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 type: isConnected ? ModuleWarning.Success : ModuleWarning.Danger
function updateState() { 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 { 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 NoImageUploadedPanel 1.0 NoImageUploadedPanel.qml
StatusAssetSelector 1.0 StatusAssetSelector.qml StatusAssetSelector 1.0 StatusAssetSelector.qml
AcceptRejectOptionsButtonsPanel 1.0 AcceptRejectOptionsButtonsPanel.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 seedWalletType: "seed"
readonly property string generatedWalletType: "generated" 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 string dummyText: "Dummy"
readonly property int dummyModelItems: 25 readonly property int dummyModelItems: 25

View File

@ -603,6 +603,20 @@ QtObject {
return 34 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 // Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) { function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c) return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)

2
vendor/status-go vendored

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