fix(@deaktop/wallet): Implement error states with automatic retries
fixes #9688
This commit is contained in:
parent
054ad18532
commit
df121445ca
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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")
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()))
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,3 +23,4 @@ NoImageUploadedPanel 1.0 NoImageUploadedPanel.qml
|
|||
StatusAssetSelector 1.0 StatusAssetSelector.qml
|
||||
AcceptRejectOptionsButtonsPanel 1.0 AcceptRejectOptionsButtonsPanel.qml
|
||||
DidYouKnowSplashScreen 1.0 DidYouKnowSplashScreen.qml
|
||||
ConnectionWarnings 1.0 ConnectionWarnings.qml
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7bc03e22f724408cf8ac14117915f72ef7888d88
|
||||
Subproject commit a3f1a84d291749d32408f340d331877555cee5c1
|
Loading…
Reference in New Issue