diff --git a/Adamant.xcodeproj/project.pbxproj b/Adamant.xcodeproj/project.pbxproj index 0f21beb0c..0cad71528 100644 --- a/Adamant.xcodeproj/project.pbxproj +++ b/Adamant.xcodeproj/project.pbxproj @@ -226,6 +226,7 @@ 93294B9A2AAD624100911109 /* WalletFactoryCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93294B992AAD624100911109 /* WalletFactoryCompose.swift */; }; 932B34E92974AA4A002A75BA /* ChatPreservationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */; }; 932F77592989F999006D8801 /* ChatCellManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 932F77582989F999006D8801 /* ChatCellManager.swift */; }; + 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */; }; 9338AE7F2AEF43DA001D32DF /* NodesStorageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */; }; 9338AE812AEF4B8E001D32DF /* NodesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */; }; 9338AE842AEF5EFA001D32DF /* APICoreProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */; }; @@ -322,6 +323,8 @@ 93CCAE7B2B06D9B500EA5B94 /* DogeBlocksDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7A2B06D9B500EA5B94 /* DogeBlocksDTO.swift */; }; 93CCAE7E2B06DA6C00EA5B94 /* DogeBlockDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7D2B06DA6C00EA5B94 /* DogeBlockDTO.swift */; }; 93CCAE802B06E2D100EA5B94 /* ApiServiceError+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */; }; + 93D02C802C564EF90011D819 /* NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */; }; + 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */; }; 93E1232F2A6DF8EF004DF33B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93E123312A6DF8EF004DF33B /* InfoPlist.strings */; }; 93E123382A6DFD15004DF33B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 93E1233A2A6DFD15004DF33B /* Localizable.strings */; }; 93E1233F2A6DFE24004DF33B /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 93E123412A6DFE24004DF33B /* Localizable.stringsdict */; }; @@ -873,6 +876,7 @@ 93294B992AAD624100911109 /* WalletFactoryCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletFactoryCompose.swift; sourceTree = ""; }; 932B34E82974AA4A002A75BA /* ChatPreservationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreservationProtocol.swift; sourceTree = ""; }; 932F77582989F999006D8801 /* ChatCellManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatCellManager.swift; sourceTree = ""; }; + 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldNodeWithGroupDTO.swift; sourceTree = ""; }; 9338AE7E2AEF43DA001D32DF /* NodesStorageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesStorageProtocol.swift; sourceTree = ""; }; 9338AE802AEF4B8E001D32DF /* NodesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodesStorage.swift; sourceTree = ""; }; 9338AE832AEF5EFA001D32DF /* APICoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APICoreProtocol.swift; sourceTree = ""; }; @@ -964,6 +968,8 @@ 93CCAE7A2B06D9B500EA5B94 /* DogeBlocksDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeBlocksDTO.swift; sourceTree = ""; }; 93CCAE7D2B06DA6C00EA5B94 /* DogeBlockDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DogeBlockDTO.swift; sourceTree = ""; }; 93CCAE7F2B06E2D100EA5B94 /* ApiServiceError+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApiServiceError+Extension.swift"; sourceTree = ""; }; + 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeWithGroupDTO.swift; sourceTree = ""; }; + 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NodeWithGroup+NodeWithGroupDTO.swift"; sourceTree = ""; }; 93E123302A6DF8EF004DF33B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 93E123322A6DF8F1004DF33B /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 93E123332A6DF8F2004DF33B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1835,6 +1841,15 @@ path = DTO; sourceTree = ""; }; + 93D02C7E2C564DEE0011D819 /* Keychain */ = { + isa = PBXGroup; + children = ( + 93D02C7F2C564EF90011D819 /* NodeWithGroupDTO.swift */, + 9332DBCC2C58C398000EC872 /* OldNodeWithGroupDTO.swift */, + ); + path = Keychain; + sourceTree = ""; + }; 93E123342A6DFCA6004DF33B /* NotificationsShared */ = { isa = PBXGroup; children = ( @@ -2045,6 +2060,7 @@ E913C9091FFFA95A001A83F7 /* Models */ = { isa = PBXGroup; children = ( + 93D02C7E2C564DEE0011D819 /* Keychain */, E91947B72000326B001362F8 /* ServerResponses */, E95F859220094B8E0070534A /* CoreData */, E91947B320002809001362F8 /* AdamantAccount.swift */, @@ -2117,6 +2133,7 @@ 936658942B0AC15300BDB2D3 /* Node+UI.swift */, 3AF53F8C2B3DCFA300B30312 /* NodeGroup+Constants.swift */, 3AA3880B2B69201B00125684 /* ADM+JsonDecode.swift */, + 93D02C812C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift */, ); path = Helpers; sourceTree = ""; @@ -3059,6 +3076,7 @@ E9E7CD8D20026B6600DFC4DB /* DialogService.swift in Sources */, E9E7CDB72003994E00DFC4DB /* AdamantUtilities+extended.swift in Sources */, E9147B6320505C7500145913 /* QRCodeReader+adamant.swift in Sources */, + 9332DBCD2C58C398000EC872 /* OldNodeWithGroupDTO.swift in Sources */, E90055F720EC200900D0CB2D /* SecurityViewController.swift in Sources */, 939FA3422B0D6F0000710EC6 /* SelfRemovableHostingController.swift in Sources */, 644EC35E20F34F1E00F40C73 /* DelegateDetailsViewController.swift in Sources */, @@ -3132,6 +3150,7 @@ E9E7CD932002740500DFC4DB /* AdamantAccountService.swift in Sources */, 41330F7629F1509400CB587C /* AdamantCellAnimation.swift in Sources */, 64FA53CD20E1300B006783C9 /* EthTransactionsViewController.swift in Sources */, + 93D02C822C564F710011D819 /* NodeWithGroup+NodeWithGroupDTO.swift in Sources */, 6449BA6A235CA0930033B936 /* ERC20Wallet.swift in Sources */, E9147B612050599000145913 /* LoginViewController+QR.swift in Sources */, 9399F5ED29A85A48006C3E30 /* ChatCacheService.swift in Sources */, @@ -3190,6 +3209,7 @@ 648BCA6D213D384F00875EB5 /* AvatarService.swift in Sources */, E95F856F2007B61D0070534A /* GetPublicKeyResponse.swift in Sources */, 3A26D94B2C3D3838003AD832 /* KlyTransactionsViewController.swift in Sources */, + 93D02C802C564EF90011D819 /* NodeWithGroupDTO.swift in Sources */, 936658992B0AD32600BDB2D3 /* CoinsNodesListViewModel+ApiServices.swift in Sources */, 644EC34D20EFA60900F40C73 /* AdamantApi+Delegates.swift in Sources */, E940088F2119A9E800CD2D67 /* BigInt+Decimal.swift in Sources */, diff --git a/Adamant/Helpers/Node+UI.swift b/Adamant/Helpers/Node+UI.swift index 25a06a4c8..151ee35e5 100644 --- a/Adamant/Helpers/Node+UI.swift +++ b/Adamant/Helpers/Node+UI.swift @@ -46,7 +46,7 @@ extension Node { func indicatorString(isRest: Bool, isWs: Bool) -> String { let connections = [ - isRest ? scheme.rawValue : nil, + isRest ? preferredOrigin.scheme.rawValue : nil, isWs ? "ws" : nil ].compactMap { $0 } diff --git a/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift b/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift new file mode 100644 index 000000000..88041ad56 --- /dev/null +++ b/Adamant/Helpers/NodeWithGroup+NodeWithGroupDTO.swift @@ -0,0 +1,19 @@ +// +// NodeWithGroup+NodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 28.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +extension NodeWithGroup { + func mapToDto() -> NodeWithGroupDTO { + .init(group: group, node: node.mapToDto()) + } +} + +extension NodeWithGroupDTO { + func mapToModel() -> NodeWithGroup { + .init(group: group, node: node.mapToModel()) + } +} diff --git a/Adamant/Models/Keychain/NodeWithGroupDTO.swift b/Adamant/Models/Keychain/NodeWithGroupDTO.swift new file mode 100644 index 000000000..3d572ab3c --- /dev/null +++ b/Adamant/Models/Keychain/NodeWithGroupDTO.swift @@ -0,0 +1,14 @@ +// +// NodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 28.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct NodeWithGroupDTO: Codable { + let group: NodeGroup + let node: NodeDTO +} diff --git a/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift new file mode 100644 index 000000000..3ae581a57 --- /dev/null +++ b/Adamant/Models/Keychain/OldNodeWithGroupDTO.swift @@ -0,0 +1,20 @@ +// +// OldNodeWithGroupDTO.swift +// Adamant +// +// Created by Andrew G on 30.07.2024. +// Copyright © 2024 Adamant. All rights reserved. +// + +import CommonKit + +struct OldNodeWithGroupDTO: Codable { + let group: NodeGroup + let node: OldNodeDTO +} + +extension OldNodeWithGroupDTO { + func mapToModernDto() -> NodeWithGroupDTO { + .init(group: group, node: node.mapToModernDto()) + } +} diff --git a/Adamant/Models/NodeWithGroup.swift b/Adamant/Models/NodeWithGroup.swift index 7574cb556..e06510bec 100644 --- a/Adamant/Models/NodeWithGroup.swift +++ b/Adamant/Models/NodeWithGroup.swift @@ -8,7 +8,7 @@ import CommonKit -struct NodeWithGroup: Codable, Equatable { +struct NodeWithGroup: Equatable { let group: NodeGroup var node: Node } diff --git a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift index d9f126b7b..91edbfa92 100644 --- a/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift +++ b/Adamant/Modules/CoinsNodesList/ViewModel/CoinsNodesListViewModel.swift @@ -37,7 +37,7 @@ final class CoinsNodesListViewModel: ObservableObject { } func setIsEnabled(id: UUID, value: Bool) { - nodesStorage.updateNodeParams(id: id, isEnabled: value) + nodesStorage.updateNode(id: id) { $0.isEnabled = value } } func reset() { diff --git a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift index e4c4422be..905bdf538 100644 --- a/Adamant/Modules/NodesEditor/NodeEditorViewController.swift +++ b/Adamant/Modules/NodesEditor/NodeEditorViewController.swift @@ -114,7 +114,7 @@ final class NodeEditorViewController: FormViewController { super.viewDidLoad() if let node = node { - self.navigationItem.title = node.host + self.navigationItem.title = node.mainOrigin.host } else { self.navigationItem.title = String.adamant.nodesEditor.newNodeTitle } @@ -131,7 +131,7 @@ final class NodeEditorViewController: FormViewController { $0.tag = Rows.host.tag $0.placeholder = Rows.host.placeholder - $0.value = node?.host + $0.value = node?.mainOrigin.host } // Port @@ -140,18 +140,18 @@ final class NodeEditorViewController: FormViewController { $0.tag = Rows.port.tag if let node = node { - $0.value = node.port - $0.placeholder = String(node.scheme.defaultPort) + $0.value = node.mainOrigin.port + $0.placeholder = String(node.mainOrigin.scheme.defaultPort) } else { - $0.placeholder = String(Node.URLScheme.default.defaultPort) + $0.placeholder = String(NodeOrigin.URLScheme.default.defaultPort) } } // Scheme - <<< PickerInlineRow { + <<< PickerInlineRow { $0.title = Rows.scheme.localized $0.tag = Rows.scheme.tag - $0.value = node?.scheme ?? Node.URLScheme.default + $0.value = node?.mainOrigin.scheme ?? NodeOrigin.URLScheme.default $0.options = [.https, .http] $0.baseCell.detailTextLabel?.textColor = .adamant.textColor }.onExpandInlineRow { (cell, _, inlineRow) in @@ -161,7 +161,7 @@ final class NodeEditorViewController: FormViewController { if let scheme = row.value { portRow.placeholder = String(scheme.defaultPort) } else { - portRow.placeholder = String(Node.URLScheme.default.defaultPort) + portRow.placeholder = String(NodeOrigin.URLScheme.default.defaultPort) } portRow.updateCell() @@ -223,11 +223,11 @@ extension NodeEditorViewController { } let host = rawUrl.trimmingCharacters(in: .whitespaces) - let scheme: Node.URLScheme + let scheme: NodeOrigin.URLScheme if let row = form.rowBy(tag: Rows.scheme.tag), - let value = row.baseValue as? Node.URLScheme + let value = row.baseValue as? NodeOrigin.URLScheme { scheme = value } else { @@ -243,21 +243,29 @@ extension NodeEditorViewController { let result: NodeEditorResult if let node = node { - nodesStorage.updateNodeParams( - id: node.id, - scheme: scheme, - host: host, - port: port - ) + nodesStorage.updateNode(id: node.id) { node in + node.mainOrigin.scheme = scheme + node.mainOrigin.host = host + node.mainOrigin.port = port + } result = .nodeUpdated } else { - result = .new(node: Node( - scheme: scheme, - host: host, + result = .new(node: .init( + id: .init(), isEnabled: true, wsEnabled: false, - port: port + mainOrigin: .init( + scheme: scheme, + host: host, + port: port + ), + altOrigin: nil, + version: nil, + height: nil, + ping: nil, + connectionStatus: nil, + preferMainOrigin: nil )) } diff --git a/Adamant/Modules/NodesEditor/NodesListViewController.swift b/Adamant/Modules/NodesEditor/NodesListViewController.swift index 69a7cf3a6..ea4599e8b 100644 --- a/Adamant/Modules/NodesEditor/NodesListViewController.swift +++ b/Adamant/Modules/NodesEditor/NodesListViewController.swift @@ -429,7 +429,7 @@ extension NodesListViewController { statusString: node.statusString(showVersion: true) ?? .empty, isEnabled: node.isEnabled, nodeUpdateAction: .init(id: node.id.uuidString) { [nodesStorage] isEnabled in - nodesStorage.updateNodeParams(id: node.id, isEnabled: isEnabled) + nodesStorage.updateNode(id: node.id) { $0.isEnabled = isEnabled } } ) } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift index 6834da542..f41593a22 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcApiService.swift @@ -17,17 +17,17 @@ final class BtcApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: BtcApiCommands.getRPC(), requests: [ .init(method: BtcApiCommands.blockchainInfoMethod), @@ -80,16 +80,16 @@ final class BtcApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift index 46f0f0f07..484f85583 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService+Send.swift @@ -65,9 +65,9 @@ extension BtcWalletService: WalletServiceTwoStepSend { let txHex = transaction.serialized().hex // MARK: Sending request - let responseData = try await btcApiService.request { core, node in + let responseData = try await btcApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: BtcApiCommands.sendTransaction(), method: .post, parameters: [String.empty: txHex], @@ -88,9 +88,9 @@ extension BtcWalletService: WalletServiceTwoStepSend { let address = wallet.address let parameters = ["noCache": "1"] - let responseData = try await btcApiService.request { core, node in + let responseData = try await btcApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: BtcApiCommands.getUnspentTransactions(for: address), method: .get, parameters: parameters, @@ -128,5 +128,4 @@ extension BtcWalletService: WalletServiceTwoStepSend { return utxos } - } diff --git a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift index 2792f8b5b..b54564b31 100644 --- a/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift +++ b/Adamant/Modules/Wallets/Bitcoin/BtcWalletService.swift @@ -516,16 +516,16 @@ extension BtcWalletService { } func getBalance(address: String) async throws -> Decimal { - let response: BtcBalanceResponse = try await btcApiService.request { api, node in - await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.balance(for: address)) + let response: BtcBalanceResponse = try await btcApiService.request { api, origin in + await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.balance(for: address)) }.get() return response.value / BtcWalletService.multiplier } func getFeeRate() async throws -> Decimal { - let response: [String: Decimal] = try await btcApiService.request { api, node in - await api.sendRequestJsonResponse(node: node, path: BtcApiCommands.getFeeRate()) + let response: [String: Decimal] = try await btcApiService.request { api, origin in + await api.sendRequestJsonResponse(origin: origin, path: BtcApiCommands.getFeeRate()) }.get() return response["2"] ?? 1 @@ -663,9 +663,9 @@ extension BtcWalletService { for address: String, fromTx: String? = nil ) async throws -> [RawBtcTransactionResponse] { - return try await btcApiService.request { api, node in + return try await btcApiService.request { api, origin in await api.sendRequestJsonResponse( - node: node, + origin: origin, path: BtcApiCommands.getTransactions( for: address, fromTx: fromTx @@ -679,9 +679,9 @@ extension BtcWalletService { throw WalletServiceError.notLogged } - let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, node in + let rawTransaction: RawBtcTransactionResponse = try await btcApiService.request { api, origin in await api.sendRequestJsonResponse( - node: node, + origin: origin, path: BtcApiCommands.getTransaction(by: hash) ) }.get() diff --git a/Adamant/Modules/Wallets/Dash/DashApiService.swift b/Adamant/Modules/Wallets/Dash/DashApiService.swift index 051441f0a..9d0312d2e 100644 --- a/Adamant/Modules/Wallets/Dash/DashApiService.swift +++ b/Adamant/Modules/Wallets/Dash/DashApiService.swift @@ -17,17 +17,17 @@ final class DashApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: [ .init(method: DashApiComand.networkInfoMethod), @@ -80,16 +80,16 @@ final class DashApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift index e2aa6cf8a..cd5b1a860 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Send.swift @@ -85,9 +85,9 @@ extension DashWalletService: WalletServiceTwoStepSend { func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { let txHex = transaction.serialized().hex - let response: BTCRPCServerResponce = try await dashApiService.request { core, node in + let response: BTCRPCServerResponce = try await dashApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashSendRawTransactionDTO(txHex: txHex), diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift index c465fe5f1..22f289e63 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService+Transactions.swift @@ -36,9 +36,9 @@ extension DashWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - let result: BTCRawTransaction? = try await dashApiService.request { core, node in + let result: BTCRawTransaction? = try await dashApiService.request { core, origin in let response = await core.sendRequestRPC( - node: node, + origin: origin, path: .empty, request: .init( method: DashApiComand.rawTransactionMethod, @@ -73,9 +73,9 @@ extension DashWalletService { ) } - let result: [BTCRawTransaction] = try await dashApiService.request { core, node in + let result: [BTCRawTransaction] = try await dashApiService.request { core, origin in let response = await core.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: params ) @@ -102,9 +102,9 @@ extension DashWalletService { throw WalletServiceError.internalError(message: "Hash is empty", error: nil) } - let result: BTCRPCServerResponce = try await dashApiService.request { core, node in + let result: BTCRPCServerResponce = try await dashApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetBlockDTO(hash: hash), @@ -125,9 +125,9 @@ extension DashWalletService { } let response: BTCRPCServerResponce<[DashUnspentTransaction]> = try await dashApiService.request { - core, node in + core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetUnspentTransactionDTO(address: wallet.address), @@ -190,9 +190,9 @@ private extension DashWalletService { extension DashWalletService { func requestTransactionsIds(for address: String) async throws -> [String] { let response: BTCRPCServerResponce<[String]> = try await dashApiService.request { - core, node in + core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetAddressTransactionIds(address: address), diff --git a/Adamant/Modules/Wallets/Dash/DashWalletService.swift b/Adamant/Modules/Wallets/Dash/DashWalletService.swift index 695fa2c32..9b6ae2edc 100644 --- a/Adamant/Modules/Wallets/Dash/DashWalletService.swift +++ b/Adamant/Modules/Wallets/Dash/DashWalletService.swift @@ -396,9 +396,9 @@ extension DashWalletService { } func getBalance(address: String) async throws -> Decimal { - let data: Data = try await dashApiService.request { core, node in + let data: Data = try await dashApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: .empty, method: .post, parameters: DashGetAddressBalanceDTO(address: address), diff --git a/Adamant/Modules/Wallets/Doge/DogeApiService.swift b/Adamant/Modules/Wallets/Doge/DogeApiService.swift index 5874978e2..7446fb989 100644 --- a/Adamant/Modules/Wallets/Doge/DogeApiService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeApiService.swift @@ -17,18 +17,18 @@ final class DogeApiCore: BlockchainHealthCheckableService { } func request( - node: Node, - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + origin: NodeOrigin, + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await request(apiCore, node).mapError { $0.asWalletServiceError() } + await request(apiCore, origin).mapError { $0.asWalletServiceError() } } - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - let response: WalletServiceResult = await request(node: node) { core, node in + let response: WalletServiceResult = await request(origin: origin) { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getInfo() ) } @@ -61,16 +61,16 @@ final class DogeApiService: WalletApiService { } func request( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, request) + await api.request { core, origin in + await core.request(origin: origin, request) } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift index b3e61438f..4725d556b 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService+Send.swift @@ -69,9 +69,9 @@ extension DogeWalletService: WalletServiceTwoStepSend { func sendTransaction(_ transaction: BitcoinKit.Transaction) async throws { let txHex = transaction.serialized().hex - _ = try await dogeApiService.api.request { core, node in + _ = try await dogeApiService.api.request { core, origin in let response: APIResponseModel = await core.apiCore.sendRequestBasic( - node: node, + origin: origin, path: DogeApiCommands.sendTransaction(), method: .post, parameters: ["rawtx": txHex], diff --git a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift index 062706875..3e574ce5e 100644 --- a/Adamant/Modules/Wallets/Doge/DogeWalletService.swift +++ b/Adamant/Modules/Wallets/Doge/DogeWalletService.swift @@ -383,9 +383,9 @@ extension DogeWalletService { } func getBalance(address: String) async throws -> Decimal { - let data: Data = try await dogeApiService.request { core, node in + let data: Data = try await dogeApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: DogeApiCommands.balance(for: address) ) }.get() @@ -525,9 +525,9 @@ extension DogeWalletService { "to": to ] - return try await dogeApiService.request { core, node in + return try await dogeApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getTransactions(for: address), method: .get, parameters: parameters, @@ -548,9 +548,9 @@ extension DogeWalletService { ] // MARK: Sending request - let data = try await dogeApiService.request { core, node in + let data = try await dogeApiService.request { core, origin in await core.sendRequest( - node: node, + origin: origin, path: DogeApiCommands.getUnspentTransactions(for: address), method: .get, parameters: parameters, @@ -597,17 +597,17 @@ extension DogeWalletService { } func getTransaction(by hash: String) async throws -> BTCRawTransaction { - try await dogeApiService.request { core, node in + try await dogeApiService.request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: DogeApiCommands.getTransaction(by: hash) ) }.get() } func getBlockId(by hash: String) async throws -> String { - let data = try await dogeApiService.request { core, node in - await core.sendRequest(node: node, path: DogeApiCommands.getBlock(by: hash)) + let data = try await dogeApiService.request { core, origin in + await core.sendRequest(origin: origin, path: DogeApiCommands.getBlock(by: hash)) }.get() let json = try? JSONSerialization.jsonObject( diff --git a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift index 88e73bec2..852d71488 100644 --- a/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift +++ b/Adamant/Modules/Wallets/ERC20/ERC20WalletService.swift @@ -548,9 +548,9 @@ extension ERC20WalletService { "order": "time.desc" ] - var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, node in + var transactions: [EthTransactionShort] = try await erc20ApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txQueryParameters, diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift index 833f3160a..0e29ab563 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiCore.swift @@ -17,10 +17,10 @@ actor EthApiCore { private var web3Cache: [URL: Web3] = .init() func performRequest( - node: Node, + origin: NodeOrigin, _ body: @escaping @Sendable (_ web3: Web3) async throws -> Success ) async -> WalletServiceResult { - switch await getWeb3(node: node) { + switch await getWeb3(origin: origin) { case let .success(web3): do { return .success(try await body(web3)) @@ -43,11 +43,11 @@ actor EthApiCore { } extension EthApiCore: BlockchainHealthCheckableService { - func getStatusInfo(node: Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 let response = await apiCore.sendRequestRPC( - node: node, + origin: origin, path: .empty, requests: [ .init(method: EthApiComand.blockNumberMethod), @@ -91,8 +91,8 @@ extension EthApiCore: BlockchainHealthCheckableService { } private extension EthApiCore { - func getWeb3(node: Node) async -> WalletServiceResult { - guard let url = node.asURL() else { + func getWeb3(origin: NodeOrigin) async -> WalletServiceResult { + guard let url = origin.asURL() else { return .failure(.internalError(.endpointBuildFailed)) } diff --git a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift index 701bad3d2..928c4dc7b 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthApiService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthApiService.swift @@ -33,22 +33,22 @@ class EthApiService: WalletApiService { func requestWeb3( _ request: @Sendable @escaping (Web3) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.performRequest(node: node, request) + await api.request { core, origin in + await core.performRequest(origin: origin, request) } } func requestApiCore( - _ request: @Sendable @escaping (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable @escaping (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> WalletServiceResult { - await api.request { core, node in - await request(core.apiCore, node).mapError { $0.asWalletServiceError() } + await api.request { core, origin in + await request(core.apiCore, origin).mapError { $0.asWalletServiceError() } } } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } diff --git a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift index fb14484ae..e9d74d3c4 100644 --- a/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift +++ b/Adamant/Modules/Wallets/Ethereum/EthWalletService.swift @@ -701,9 +701,9 @@ extension EthWalletService { "contract_to": "eq." ] - let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in + let transactionsFrom: [EthTransactionShort] = try await ethApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txFromQueryParameters, @@ -711,9 +711,9 @@ extension EthWalletService { ) }.get() - let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, node in + let transactionsTo: [EthTransactionShort] = try await ethApiService.requestApiCore { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: EthWalletService.transactionsListApiSubpath, method: .get, parameters: txToQueryParameters, diff --git a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift index 769cdc204..faa3e8221 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyApiCore.swift @@ -11,33 +11,33 @@ import Foundation import LiskKit class KlyApiCore: BlockchainHealthCheckableService { - func makeClient(node: CommonKit.Node) -> APIClient { + func makeClient(origin: NodeOrigin) -> APIClient { .init(options: .init( - nodes: [.init(origin: node.asString())], + nodes: [.init(origin: origin.asString())], nethash: .mainnet, randomNode: false )) } func request( - node: CommonKit.Node, + origin: NodeOrigin, body: @escaping @Sendable ( _ client: APIClient, _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { await withCheckedContinuation { continuation in - body(makeClient(node: node)) { result in + body(makeClient(origin: origin)) { result in continuation.resume(returning: result.asWalletServiceResult()) } } } func request( - node: CommonKit.Node, + origin: NodeOrigin, _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - let client = makeClient(node: node) + let client = makeClient(origin: origin) do { return .success(try await body(client)) @@ -46,10 +46,10 @@ class KlyApiCore: BlockchainHealthCheckableService { } } - func getStatusInfo(node: CommonKit.Node) async -> WalletServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - return await request(node: node) { client in + return await request(origin: origin) { client in try await LiskKit.Node(client: client).info() }.map { model in .init( diff --git a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift index 885bbd5da..5959d6288 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyNodeApiService.swift @@ -52,8 +52,8 @@ final class KlyNodeApiService: WalletApiService { } func getStatusInfo() async -> WalletServiceResult { - await api.request { core, node in - await core.getStatusInfo(node: node) + await api.request { core, origin in + await core.getStatusInfo(origin: origin) } } } @@ -65,16 +65,16 @@ private extension KlyNodeApiService { _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body: body) + await api.request { core, origin in + await core.request(origin: origin, body: body) } } func requestClient( _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body) + await api.request { core, origin in + await core.request(origin: origin, body) } } } diff --git a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift index b34ded0eb..ebc8e8dff 100644 --- a/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift +++ b/Adamant/Modules/Wallets/Klayr/KlyServiceApiService.swift @@ -12,11 +12,11 @@ import CommonKit final class KlyServiceApiCore: KlyApiCore { override func getStatusInfo( - node: CommonKit.Node + origin: NodeOrigin ) async -> WalletServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - return await request(node: node) { client in + return await request(origin: origin) { client in let service = LiskKit.Service(client: client) return try await (fee: service.fees(), info: service.info()) }.map { model in @@ -73,16 +73,16 @@ private extension KlyServiceApiService { _ completion: @escaping @Sendable (LiskKit.Result) -> Void ) -> Void ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body: body) + await api.request { core, origin in + await core.request(origin: origin, body: body) } } func requestClient( _ body: @Sendable @escaping (APIClient) async throws -> Output ) async -> WalletServiceResult { - await api.request { core, node in - await core.request(node: node, body) + await api.request { core, origin in + await core.request(origin: origin, body) } } } diff --git a/Adamant/ServiceProtocols/APICoreProtocol.swift b/Adamant/ServiceProtocols/APICoreProtocol.swift index 4c1ea329a..e9e7bd9e9 100644 --- a/Adamant/ServiceProtocols/APICoreProtocol.swift +++ b/Adamant/ServiceProtocols/APICoreProtocol.swift @@ -15,7 +15,7 @@ enum ApiCommands {} protocol APICoreProtocol: Actor { func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, @@ -24,7 +24,7 @@ protocol APICoreProtocol: Actor { /// jsonParameters - arrays and dictionaries are allowed only func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any @@ -35,14 +35,14 @@ extension APICoreProtocol { var emptyParameters: [String: Bool] { [:] } func sendRequest( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding ) async -> ApiServiceResult { await sendRequestBasic( - node: node, + origin: origin, path: path, method: method, parameters: parameters, @@ -51,14 +51,14 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, encoding: APIParametersEncoding ) async -> ApiServiceResult { await sendRequest( - node: node, + origin: origin, path: path, method: method, parameters: parameters, @@ -67,11 +67,11 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String ) async -> ApiServiceResult { await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .get, parameters: emptyParameters, @@ -80,11 +80,11 @@ extension APICoreProtocol { } func sendRequest( - node: Node, + origin: NodeOrigin, path: String ) async -> ApiServiceResult { await sendRequest( - node: node, + origin: origin, path: path, method: .get, parameters: emptyParameters, @@ -93,13 +93,13 @@ extension APICoreProtocol { } func sendRequestJsonResponse( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any ) async -> ApiServiceResult { await sendRequestBasic( - node: node, + origin: origin, path: path, method: method, jsonParameters: jsonParameters @@ -107,7 +107,7 @@ extension APICoreProtocol { } func sendRequestRPC( - node: Node, + origin: NodeOrigin, path: String, requests: [RpcRequest] ) async -> ApiServiceResult<[RPCResponseModel]> { @@ -116,7 +116,7 @@ extension APICoreProtocol { } return await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, jsonParameters: parameters @@ -124,12 +124,12 @@ extension APICoreProtocol { } func sendRequestRPC( - node: Node, + origin: NodeOrigin, path: String, request: RpcRequest ) async -> ApiServiceResult { await sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, jsonParameters: request.asDictionary() ?? [:] diff --git a/Adamant/ServiceProtocols/NodesStorageProtocol.swift b/Adamant/ServiceProtocols/NodesStorageProtocol.swift index be9077df9..380ce364c 100644 --- a/Adamant/ServiceProtocols/NodesStorageProtocol.swift +++ b/Adamant/ServiceProtocols/NodesStorageProtocol.swift @@ -24,59 +24,5 @@ protocol NodesStorageProtocol { func resetNodes(group: NodeGroup) func removeNode(id: UUID) func haveActiveNode(in group: NodeGroup) -> Bool - - func updateNode( - id: UUID, - scheme: CommonKit.Node.URLScheme?, - host: String?, - isEnabled: Bool?, - wsEnabled: Bool?, - port: Int??, - wsPort: Int??, - version: String??, - height: Int??, - ping: TimeInterval??, - connectionStatus: CommonKit.Node.ConnectionStatus?? - ) -} - -extension NodesStorageProtocol { - func updateNodeStatus(id: UUID, statusInfo: NodeStatusInfo?) { - updateNodeParams( - id: id, - wsEnabled: .some(statusInfo?.wsEnabled ?? false), - wsPort: .some(statusInfo?.wsPort), - version: .some(statusInfo?.version), - height: .some(statusInfo?.height), - ping: .some(statusInfo?.ping) - ) - } - - func updateNodeParams( - id: UUID, - scheme: CommonKit.Node.URLScheme? = nil, - host: String? = nil, - isEnabled: Bool? = nil, - wsEnabled: Bool? = nil, - port: Int?? = nil, - wsPort: Int?? = nil, - version: String?? = nil, - height: Int?? = nil, - ping: TimeInterval?? = nil, - connectionStatus: CommonKit.Node.ConnectionStatus?? = nil - ) { - updateNode( - id: id, - scheme: scheme, - host: host, - isEnabled: isEnabled, - wsEnabled: wsEnabled, - port: port, - wsPort: wsPort, - version: version, - height: height, - ping: ping, - connectionStatus: connectionStatus - ) - } + func updateNode(id: UUID, mutate: (inout Node) -> Void) } diff --git a/Adamant/Services/APICore.swift b/Adamant/Services/APICore.swift index f3748aa34..4c0e74b73 100644 --- a/Adamant/Services/APICore.swift +++ b/Adamant/Services/APICore.swift @@ -26,7 +26,7 @@ actor APICore: APICoreProtocol { }() func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, parameters: Parameters, @@ -34,7 +34,7 @@ actor APICore: APICoreProtocol { ) async -> APIResponseModel { do { let request = session.request( - try buildUrl(node: node, path: path), + try buildUrl(origin: origin, path: path), method: method, parameters: parameters.asDictionary(), encoding: encoding.parametersEncoding, @@ -52,7 +52,7 @@ actor APICore: APICoreProtocol { } func sendRequestBasic( - node: Node, + origin: NodeOrigin, path: String, method: HTTPMethod, jsonParameters: Any @@ -63,7 +63,7 @@ actor APICore: APICoreProtocol { ) var request = try URLRequest( - url: try buildUrl(node: node, path: path), + url: try buildUrl(origin: origin, path: path), method: method ) @@ -93,8 +93,8 @@ private extension APICore { } } - func buildUrl(node: Node, path: String) throws -> URL { - guard let url = node.asURL()?.appendingPathComponent(path, conformingTo: .url) + func buildUrl(origin: NodeOrigin, path: String) throws -> URL { + guard let url = origin.asURL()?.appendingPathComponent(path, conformingTo: .url) else { throw InternalAPIError.endpointBuildFailed } return url } diff --git a/Adamant/Services/ApiService/AdamantApi+Accounts.swift b/Adamant/Services/ApiService/AdamantApi+Accounts.swift index 7b8c6bd71..00c4aabfc 100644 --- a/Adamant/Services/ApiService/AdamantApi+Accounts.swift +++ b/Adamant/Services/ApiService/AdamantApi+Accounts.swift @@ -30,9 +30,9 @@ extension AdamantApiService { /// Get account by publicKey func getAccount(byPublicKey publicKey: String) async -> ApiServiceResult { - switch await request({ apiCore, node in + switch await request({ apiCore, origin in let response: ApiServiceResult> = await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.root, method: .get, parameters: ["publicKey": publicKey], @@ -54,11 +54,11 @@ extension AdamantApiService { } func getAccount(byAddress address: String) async -> ApiServiceResult { - await request { apiCore, node in + await request { apiCore, origin in let response: ApiServiceResult< ServerModelResponse > = await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.root, method: .get, parameters: ["address": address], diff --git a/Adamant/Services/ApiService/AdamantApi+Chats.swift b/Adamant/Services/ApiService/AdamantApi+Chats.swift index 1ec2c866e..601d74937 100644 --- a/Adamant/Services/ApiService/AdamantApi+Chats.swift +++ b/Adamant/Services/ApiService/AdamantApi+Chats.swift @@ -40,9 +40,9 @@ extension AdamantApiService { } let response: ApiServiceResult> - response = await request { [parameters] service, node in + response = await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.get, method: .get, parameters: parameters, @@ -72,9 +72,9 @@ extension AdamantApiService { parameters["offset"] = String(offset) } - return await request { [parameters] service, node in + return await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.getChatRooms + "/\(address)", method: .get, parameters: parameters, @@ -99,9 +99,9 @@ extension AdamantApiService { parameters["limit"] = String(limit) } - return await request { [parameters] service, node in + return await request { [parameters] service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Chats.getChatRooms + "/\(address)/\(addressRecipient)", method: .get, parameters: parameters, diff --git a/Adamant/Services/ApiService/AdamantApi+Delegates.swift b/Adamant/Services/ApiService/AdamantApi+Delegates.swift index 0ce14a3f2..fa3fe2f6f 100644 --- a/Adamant/Services/ApiService/AdamantApi+Delegates.swift +++ b/Adamant/Services/ApiService/AdamantApi+Delegates.swift @@ -33,9 +33,9 @@ extension AdamantApiService { currentDelegates: [Delegate] ) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getDelegates, method: .get, parameters: ["limit": String(limit), "offset": String(offset)], @@ -82,9 +82,9 @@ extension AdamantApiService { } func getForgedByAccount(publicKey: String) async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getForgedByAccount, method: .get, parameters: ["generatorPublicKey": publicKey], @@ -104,18 +104,18 @@ extension AdamantApiService { } private func getDelegatesCount() async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getDelegatesCount ) } } private func getNextForgers() async -> ApiServiceResult { - await request { core, node in + await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getNextForgers, method: .get, parameters: ["limit": "\(101)"], @@ -126,9 +126,9 @@ extension AdamantApiService { func getVotes(for address: String) async -> ApiServiceResult<[Delegate]> { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.votes, method: .get, parameters: ["address": address], @@ -183,9 +183,9 @@ extension AdamantApiService { // MARK: - Private methods private func getBlocks() async -> ApiServiceResult<[Block]> { - let response: ApiServiceResult> = await request { core, node in + let response: ApiServiceResult> = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Delegates.getBlocks, method: .get, parameters: ["orderBy": "height:desc", "limit": "\(101)"], diff --git a/Adamant/Services/ApiService/AdamantApi+Keys.swift b/Adamant/Services/ApiService/AdamantApi+Keys.swift index 2226421f4..2fe178b90 100644 --- a/Adamant/Services/ApiService/AdamantApi+Keys.swift +++ b/Adamant/Services/ApiService/AdamantApi+Keys.swift @@ -11,9 +11,9 @@ import CommonKit extension AdamantApiService { func getPublicKey(byAddress address: String) async -> ApiServiceResult { - let response: ApiServiceResult = await request { service, node in + let response: ApiServiceResult = await request { service, origin in await service.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Accounts.getPublicKey, method: .get, parameters: ["address": address], diff --git a/Adamant/Services/ApiService/AdamantApi+States.swift b/Adamant/Services/ApiService/AdamantApi+States.swift index 4800bb41b..08c5c21d2 100644 --- a/Adamant/Services/ApiService/AdamantApi+States.swift +++ b/Adamant/Services/ApiService/AdamantApi+States.swift @@ -63,9 +63,9 @@ extension AdamantApiService { ] let response: ApiServiceResult> - response = await request { [parameters] core, node in + response = await request { [parameters] core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.States.get, method: .get, parameters: parameters, diff --git a/Adamant/Services/ApiService/AdamantApi+Transactions.swift b/Adamant/Services/ApiService/AdamantApi+Transactions.swift index f63400128..d33f5cbda 100644 --- a/Adamant/Services/ApiService/AdamantApi+Transactions.swift +++ b/Adamant/Services/ApiService/AdamantApi+Transactions.swift @@ -23,9 +23,9 @@ extension AdamantApiService { path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { - let response: ApiServiceResult = await request { core, node in + let response: ApiServiceResult = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, parameters: ["transaction": transaction], @@ -40,9 +40,9 @@ extension AdamantApiService { path: String, transaction: UnregisteredTransaction ) async -> ApiServiceResult { - let response: ApiServiceResult = await request { core, node in + let response: ApiServiceResult = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: path, method: .post, parameters: transaction, @@ -62,9 +62,9 @@ extension AdamantApiService { func getTransaction(id: UInt64, withAsset: Bool) async -> ApiServiceResult { let response: ApiServiceResult> - response = await request { core, node in + response = await request { core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Transactions.getTransaction, method: .get, parameters: [ @@ -129,9 +129,9 @@ extension AdamantApiService { } let response: ApiServiceResult> - response = await request { [queryItems] core, node in + response = await request { [queryItems] core, origin in await core.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.Transactions.root, method: .get, parameters: core.emptyParameters, diff --git a/Adamant/Services/ApiService/AdamantApiCore.swift b/Adamant/Services/ApiService/AdamantApiCore.swift index 1a4861d62..0904f8a65 100644 --- a/Adamant/Services/ApiService/AdamantApiCore.swift +++ b/Adamant/Services/ApiService/AdamantApiCore.swift @@ -22,18 +22,18 @@ final class AdamantApiCore { self.apiCore = apiCore } - func getNodeStatus(node: Node) async -> ApiServiceResult { + func getNodeStatus(origin: NodeOrigin) async -> ApiServiceResult { await apiCore.sendRequestJsonResponse( - node: node, + origin: origin, path: ApiCommands.status ) } } extension AdamantApiCore: BlockchainHealthCheckableService { - func getStatusInfo(node: Node) async -> ApiServiceResult { + func getStatusInfo(origin: NodeOrigin) async -> ApiServiceResult { let startTimestamp = Date.now.timeIntervalSince1970 - let statusResponse = await getNodeStatus(node: node) + let statusResponse = await getNodeStatus(origin: origin) let ping = Date.now.timeIntervalSince1970 - startTimestamp return statusResponse.map { statusDto in diff --git a/Adamant/Services/ApiService/AdamantApiService.swift b/Adamant/Services/ApiService/AdamantApiService.swift index 035a0e3cb..605d10b69 100644 --- a/Adamant/Services/ApiService/AdamantApiService.swift +++ b/Adamant/Services/ApiService/AdamantApiService.swift @@ -22,10 +22,10 @@ final class AdamantApiService { } func request( - _ request: @Sendable (APICoreProtocol, Node) async -> ApiServiceResult + _ request: @Sendable (APICoreProtocol, NodeOrigin) async -> ApiServiceResult ) async -> ApiServiceResult { - await service.request { admApiCore, node in - await request(admApiCore.apiCore, node) + await service.request { admApiCore, origin in + await request(admApiCore.apiCore, origin) } } } diff --git a/Adamant/Services/BlockchainHealthCheckWrapper.swift b/Adamant/Services/BlockchainHealthCheckWrapper.swift index 105eea8d5..7ba1b6897 100644 --- a/Adamant/Services/BlockchainHealthCheckWrapper.swift +++ b/Adamant/Services/BlockchainHealthCheckWrapper.swift @@ -12,7 +12,7 @@ import Foundation protocol BlockchainHealthCheckableService { associatedtype Error: HealthCheckableError - func getStatusInfo(node: Node) async -> Result + func getStatusInfo(origin: NodeOrigin) async -> Result } final class BlockchainHealthCheckWrapper< @@ -57,8 +57,7 @@ final class BlockchainHealthCheckWrapper< await withTaskGroup(of: Void.self, returning: Void.self) { group in nodes.filter { $0.isEnabled }.forEach { node in group.addTask { [weak self] in - guard let self = self, !currentRequests.contains(node.id) else { return } - await updateNodeStatusInfo(node: node) + await self?.updateNodeStatusInfo(node: node) } } @@ -70,27 +69,90 @@ final class BlockchainHealthCheckWrapper< private extension BlockchainHealthCheckWrapper { func updateNodeStatusInfo(node: Node) async { + guard !currentRequests.contains(node.id) else { return } currentRequests.insert(node.id) - defer { currentRequests.remove(node.id) } + var forceInclude: UUID? - let statusInfo = await service.getStatusInfo(node: node) - nodesStorage.updateNodeStatus(id: node.id, statusInfo: try? statusInfo.get()) + defer { + currentRequests.remove(node.id) + updateNodesAvailability(forceInclude: forceInclude) + } - switch statusInfo { - case .success(let info): - if let versionNumber = Node.stringToDouble(info.version), - versionNumber < nodeGroup.minNodeVersion { - nodesStorage.updateNodeParams( - id: node.id, - connectionStatus: .notAllowed(.outdatedApiVersion) - ) + guard node.preferMainOrigin == nil else { + switch await updateNodeStatusInfo( + id: node.id, + origin: node.preferredOrigin, + markAsOfflineIfFailed: true + ) { + case .success: + forceInclude = node.id + case .failure, .none: + break } - updateNodesAvailability(forceInclude: node.id) + return + } + + switch await updateNodeStatusInfo( + id: node.id, + origin: node.mainOrigin, + markAsOfflineIfFailed: false + ) { + case .success: + nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = true } + forceInclude = node.id + case .failure: + switch await updateNodeStatusInfo( + id: node.id, + origin: node.mainOrigin, + markAsOfflineIfFailed: true + ) { + case .success: + nodesStorage.updateNode(id: node.id) { $0.preferMainOrigin = false } + forceInclude = node.id + case .failure, .none: + break + } + case .none: + break + } + } + + @discardableResult + func updateNodeStatusInfo( + id: UUID, + origin: NodeOrigin, + markAsOfflineIfFailed: Bool + ) async -> Result? { + switch await service.getStatusInfo(origin: origin) { + case let .success(info): + applyStatusInfo(id: id, info: info) + return .success(()) case let .failure(error): - guard !error.isRequestCancelledError else { return } - nodesStorage.updateNodeParams(id: node.id, connectionStatus: .offline) - updateNodesAvailability() + guard !error.isRequestCancelledError else { return nil } + + if markAsOfflineIfFailed { + nodesStorage.updateNode(id: id) { $0.connectionStatus = .offline } + } + + return .failure(error) + } + } + + func applyStatusInfo(id: UUID, info: NodeStatusInfo) { + nodesStorage.updateNode(id: id) { node in + node.wsEnabled = info.wsEnabled + node.updateWsPort(info.wsPort) + node.version = info.version + node.height = info.height + node.ping = info.ping + + guard + let versionNumber = Node.stringToDouble(info.version), + versionNumber < nodeGroup.minNodeVersion + else { return } + + node.connectionStatus = .notAllowed(.outdatedApiVersion) } } @@ -108,12 +170,12 @@ private extension BlockchainHealthCheckWrapper { ) workingNodes.forEach { node in - var status: Node.ConnectionStatus? + var status: NodeConnectionStatus? let actualNodeVersion = Node.stringToDouble(node.version) if let actualNodeVersion = actualNodeVersion, actualNodeVersion < nodeGroup.minNodeVersion { - status = Node.ConnectionStatus.notAllowed(.outdatedApiVersion) + status = .notAllowed(.outdatedApiVersion) } else { status = node.height.map { height in actualHeightsRange?.contains(height) ?? false @@ -122,10 +184,7 @@ private extension BlockchainHealthCheckWrapper { } ?? .none } - nodesStorage.updateNodeParams( - id: node.id, - connectionStatus: status - ) + nodesStorage.updateNode(id: node.id) { $0.connectionStatus = status } } } } diff --git a/Adamant/Services/HealthCheckWrapper.swift b/Adamant/Services/HealthCheckWrapper.swift index e1afb94c5..571fb2d8d 100644 --- a/Adamant/Services/HealthCheckWrapper.swift +++ b/Adamant/Services/HealthCheckWrapper.swift @@ -95,7 +95,7 @@ class HealthCheckWrapper { } func request( - _ request: @Sendable (Service, Node) async -> Result + _ request: @Sendable (Service, NodeOrigin) async -> Result ) async -> Result { var lastConnectionError = allowedNodes.isEmpty ? Error.noEndpointsError(coin: nodeGroup.name) @@ -106,7 +106,7 @@ class HealthCheckWrapper { : allowedNodes.shuffled() for node in nodesList { - let response = await request(service, node) + let response = await request(service, node.preferredOrigin) switch response { case .success: @@ -154,10 +154,9 @@ private extension HealthCheckWrapper { private extension Node { func doesNeedHealthCheck(_ node: Node) -> Bool { - scheme != node.scheme || - host != node.host || - isEnabled != node.isEnabled || - port != node.port + mainOrigin != node.mainOrigin || + altOrigin != node.altOrigin || + isEnabled != node.isEnabled } } diff --git a/Adamant/Services/NodesStorage.swift b/Adamant/Services/NodesStorage.swift index f58c484f4..ce18b8134 100644 --- a/Adamant/Services/NodesStorage.swift +++ b/Adamant/Services/NodesStorage.swift @@ -38,43 +38,17 @@ final class NodesStorage: NodesStorageProtocol { } } - func updateNode( - id: UUID, - scheme: CommonKit.Node.URLScheme?, - host: String?, - isEnabled: Bool?, - wsEnabled: Bool?, - port: Int??, - wsPort: Int??, - version: String??, - height: Int??, - ping: TimeInterval??, - connectionStatus: CommonKit.Node.ConnectionStatus?? - ) { + func updateNode(id: UUID, mutate: (inout Node) -> Void) { $items.mutate { items in guard let index = items.wrappedValue.getIndex(id: id), var node = items.wrappedValue[safe: index]?.node else { return } - scheme.map { node.scheme = $0 } - host.map { node.host = $0 } - wsEnabled.map { node.wsEnabled = $0 } - port.map { node.port = $0 } - wsPort.map { node.wsPort = $0 } - version.map { node.version = $0 } - height.map { node.height = $0 } - ping.map { node.ping = $0 } - connectionStatus.map { node.connectionStatus = $0 } - - if let isEnabled = isEnabled { - node.isEnabled = isEnabled - - if !isEnabled { - node.connectionStatus = nil - } - } + let previousValue = node + mutate(&node) + guard node != previousValue else { return } items.wrappedValue[index].node = node } } @@ -97,10 +71,19 @@ final class NodesStorage: NodesStorageProtocol { init(securedStore: SecuredStore) { self.securedStore = securedStore + var nodesDto: [NodeWithGroupDTO]? = securedStore.get(StoreKey.NodesStorage.nodes) + + if nodesDto == nil { + let oldNodesDto: SafeDecodingArray? = securedStore.get( + StoreKey.NodesStorage.nodes + ) + + nodesDto = oldNodesDto?.values.map { $0.mapToModernDto() } + } - var nodes = securedStore.get(StoreKey.NodesStorage.nodes) ?? Self.defaultItems + var nodes = nodesDto.map { $0.map { $0.mapToModel() } } ?? Self.defaultItems let nodesToAdd = Self.defaultItems.filter { defaultNode in - !nodes.contains { $0.node.host == defaultNode.node.host } + !nodes.contains { $0.node.mainOrigin.host == defaultNode.node.mainOrigin.host } } nodes.append(contentsOf: nodesToAdd) @@ -140,7 +123,8 @@ private extension NodesStorage { } func saveNodes(nodes: [NodeWithGroup]) { - securedStore.set(nodes, for: StoreKey.NodesStorage.nodes) + let nodesDto = nodes.map { $0.mapToDto() } + securedStore.set(nodesDto, for: StoreKey.NodesStorage.nodes) } } diff --git a/AdamantTests/AdamantHealthCheckServiceTests.swift b/AdamantTests/AdamantHealthCheckServiceTests.swift index 6549ca478..cd21bcd65 100644 --- a/AdamantTests/AdamantHealthCheckServiceTests.swift +++ b/AdamantTests/AdamantHealthCheckServiceTests.swift @@ -113,7 +113,7 @@ class AdamantHealthCheckServiceTests: XCTestCase { // MARK: - Helpers - private func makeTestNode(connectionStatus: Node.ConnectionStatus = .synchronizing) -> Node { + private func makeTestNode(connectionStatus: NodeConnectionStatus = .synchronizing) -> Node { let node = Node(scheme: .default, host: "", port: nil) node.connectionStatus = connectionStatus return node diff --git a/AdamantTests/NodesAllowanceTests.swift b/AdamantTests/NodesAllowanceTests.swift index 39d983c73..86c83d21a 100644 --- a/AdamantTests/NodesAllowanceTests.swift +++ b/AdamantTests/NodesAllowanceTests.swift @@ -104,7 +104,7 @@ class NodesAllowanceTests: XCTest { nodes.getAllowedNodes(sortedBySpeedDescending: true, needWS: ws) } - private func makeTestNode(connectionStatus: Node.ConnectionStatus = .synchronizing) -> Node { + private func makeTestNode(connectionStatus: NodeConnectionStatus = .synchronizing) -> Node { let node = Node(scheme: .default, host: "", port: nil) node.connectionStatus = connectionStatus return node diff --git a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift index 5d8080212..4c07a8ae7 100644 --- a/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift +++ b/CommonKit/Sources/CommonKit/ExtensionsTools/ExtensionsApi.swift @@ -15,7 +15,10 @@ public final class ExtensionsApi { public let keychainStore: KeychainStore public private(set) lazy var nodes: [Node] = { - let nodes = keychainStore.get(nodesStoreKey) ?? AdamantResources.nodes + let nodesDto: [NodeDTO]? = keychainStore.get(nodesStoreKey) + let nodes = nodesDto.map { $0.map { $0.mapToModel() } } + ?? AdamantResources.nodes + return nodes.filter { $0.isEnabled }.shuffled() }() diff --git a/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift b/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift new file mode 100644 index 000000000..62279fbc5 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/Node+NodeDTO.swift @@ -0,0 +1,38 @@ +// +// Node+NodeDTO.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +public extension Node { + func mapToDto() -> NodeDTO { + .init( + mainOrigin: mainOrigin, + altOrigin: altOrigin, + wsEnabled: wsEnabled, + isEnabled: isEnabled, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus + ) + } +} + +public extension NodeDTO { + func mapToModel() -> Node { + .init( + id: .init(), + isEnabled: isEnabled, + wsEnabled: wsEnabled, + mainOrigin: mainOrigin, + altOrigin: altOrigin, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus, + preferMainOrigin: nil + ) + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift new file mode 100644 index 000000000..8bb5c328b --- /dev/null +++ b/CommonKit/Sources/CommonKit/Helpers/SafeDecodingArray.swift @@ -0,0 +1,40 @@ +// +// SafeDecodingArray.swift +// +// +// Created by Andrew G on 30.07.2024. +// + +public struct SafeDecodingArray { + public let values: [T] + + init(_ values: [T]) { + self.values = values + } +} + +extension SafeDecodingArray: Encodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(self.values) + } +} + +extension SafeDecodingArray: Decodable { + struct Item { + let value: Value? + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let items = try container.decode([Item].self) + values = items.compactMap { $0.value } + } +} + +extension SafeDecodingArray.Item: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + value = try? container.decode(Value.self) + } +} diff --git a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift b/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift index 8ce6b6d67..c183ac767 100644 --- a/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift +++ b/CommonKit/Sources/CommonKit/Helpers/SwiftUI/View+Extension.swift @@ -39,6 +39,7 @@ public extension View { return resultView } + // TODO: Remove this function (or fix) func fullScreen() -> some View { return frame(width: .infinity, height: .infinity) .ignoresSafeArea() diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift new file mode 100644 index 000000000..5371bec64 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/NodeDTO.swift @@ -0,0 +1,19 @@ +// +// NodeDTO.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +import Foundation + +public struct NodeDTO: Codable { + public let mainOrigin: NodeOrigin + public let altOrigin: NodeOrigin? + public let wsEnabled: Bool + public let isEnabled: Bool + public let version: String? + public let height: Int? + public let ping: TimeInterval? + public let connectionStatus: NodeConnectionStatus? +} diff --git a/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift new file mode 100644 index 000000000..e5bd875e1 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/Keychain/OldNodeDTO.swift @@ -0,0 +1,93 @@ +// +// OldNodeDTO.swift +// +// +// Created by Andrew G on 30.07.2024. +// + +import Foundation + +public struct OldNodeDTO: Codable { + public let id: UUID + public let scheme: URLScheme + public let host: String + public let isEnabled: Bool + public let wsEnabled: Bool + public let port: Int? + public let wsPort: Int? + public let version: String? + public let height: Int? + public let ping: TimeInterval? + public let connectionStatus: ConnectionStatus? +} + +public extension OldNodeDTO { + enum RejectedReason: Codable, Equatable { + case outdatedApiVersion + } + + enum ConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed + case notAllowed(RejectedReason) + } + + enum URLScheme: String, Codable { + case http + case https + } + + func mapToModernDto() -> NodeDTO { + .init( + mainOrigin: .init( + scheme: scheme.map(), + host: host, + port: port, + wsPort: wsPort + ), + altOrigin: nil, + wsEnabled: wsEnabled, + isEnabled: isEnabled, + version: version, + height: height, + ping: ping, + connectionStatus: connectionStatus?.map() + ) + } +} + +private extension OldNodeDTO.URLScheme { + func map() -> NodeOrigin.URLScheme { + switch self { + case .http: + return .http + case .https: + return .https + } + } +} + +private extension OldNodeDTO.ConnectionStatus { + func map() -> NodeConnectionStatus { + switch self { + case .offline: + return .offline + case .synchronizing: + return .synchronizing + case .allowed: + return .allowed + case let .notAllowed(reason): + return .notAllowed(reason.map()) + } + } +} + +private extension OldNodeDTO.RejectedReason { + func map() -> NodeConnectionStatus.RejectedReason { + switch self { + case .outdatedApiVersion: + return .outdatedApiVersion + } + } +} diff --git a/CommonKit/Sources/CommonKit/Models/Node.swift b/CommonKit/Sources/CommonKit/Models/Node.swift index 258648786..182753a54 100644 --- a/CommonKit/Sources/CommonKit/Models/Node.swift +++ b/CommonKit/Sources/CommonKit/Models/Node.swift @@ -8,130 +8,85 @@ import Foundation -public struct Node: Equatable, Codable, Identifiable { +public struct Node: Equatable, Identifiable { public let id: UUID - public var scheme: URLScheme - public var host: String - public var isEnabled: Bool + public var mainOrigin: NodeOrigin + public var altOrigin: NodeOrigin? public var wsEnabled: Bool - public var port: Int? - public var wsPort: Int? public var version: String? public var height: Int? public var ping: TimeInterval? - public var connectionStatus: ConnectionStatus? + public var connectionStatus: NodeConnectionStatus? + public var preferMainOrigin: Bool? + + public var isEnabled: Bool { + didSet { + guard !isEnabled else { return } + connectionStatus = nil + } + } public init( - id: UUID = .init(), - scheme: URLScheme, - host: String, + id: UUID, isEnabled: Bool, wsEnabled: Bool, - port: Int? = nil, - wsPort: Int? = nil, - version: String? = nil, - height: Int? = nil, - ping: TimeInterval? = nil, - connectionStatus: ConnectionStatus? = nil + mainOrigin: NodeOrigin, + altOrigin: NodeOrigin?, + version: String?, + height: Int?, + ping: TimeInterval?, + connectionStatus: NodeConnectionStatus?, + preferMainOrigin: Bool? ) { self.id = id - self.scheme = scheme - self.host = host + self.mainOrigin = mainOrigin + self.altOrigin = altOrigin self.isEnabled = isEnabled self.wsEnabled = wsEnabled - self.port = port - self.wsPort = wsPort self.version = version self.height = height self.ping = ping self.connectionStatus = connectionStatus + self.preferMainOrigin = preferMainOrigin } } public extension Node { - enum RejectedReason: Codable, Equatable { - case outdatedApiVersion - - public var text: String { - switch self { - case .outdatedApiVersion: - return Strings.outdated - } - } - } - - enum ConnectionStatus: Equatable, Codable { - case offline - case synchronizing - case allowed - case notAllowed(RejectedReason) + var preferredOrigin: NodeOrigin { + preferMainOrigin ?? true + ? mainOrigin + : altOrigin ?? mainOrigin } - - enum URLScheme: String, Codable { - case http, https - - public static let `default`: URLScheme = .https - public var defaultPort: Int { - switch self { - case .http: return 36666 - case .https: return 443 - } - } - } - - init(url: URL, altUrl _: URL? = nil) { + init(url: URL, altUrl: URL? = nil) { self.init( - scheme: URLScheme(rawValue: url.scheme ?? .empty) ?? .https, - host: url.host ?? .empty, + id: .init(), isEnabled: true, wsEnabled: false, - port: url.port + mainOrigin: .init(url: url), + altOrigin: altUrl.map { .init(url: $0) }, + version: nil, + height: nil, + ping: nil, + connectionStatus: nil, + preferMainOrigin: nil ) } func asString() -> String { - if let url = asURL(forcePort: scheme != .https) { - return url.absoluteString - } else { - return host - } + preferredOrigin.asString() } func asSocketURL() -> URL? { - asURL(forcePort: false, useWsPort: true) + preferredOrigin.asSocketURL() } func asURL() -> URL? { - asURL(forcePort: true) + preferredOrigin.asURL() } -} - -private extension Node { - func asURL(forcePort: Bool, useWsPort: Bool = false) -> URL? { - var components = URLComponents() - components.scheme = scheme.rawValue - components.host = host - - let usePort = useWsPort ? wsPort : port - - if let port = usePort, scheme == .http { - components.port = port - } else if forcePort { - components.port = usePort ?? scheme.defaultPort - } - - return components.url - } -} - -private extension Node { - enum Strings { - static var outdated: String { - String.localized( - "NodesList.NodeCell.Outdated", - comment: "NodesList.NodeCell: Node is outdated" - ) - } + + mutating func updateWsPort(_ wsPort: Int?) { + mainOrigin.wsPort = wsPort + altOrigin?.wsPort = wsPort } } diff --git a/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift b/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift new file mode 100644 index 000000000..5e0fcad7c --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeConnectionStatus.swift @@ -0,0 +1,33 @@ +// +// NodeConnectionStatus.swift +// +// +// Created by Andrew G on 28.07.2024. +// + +import Foundation + +public enum NodeConnectionStatus: Equatable, Codable { + case offline + case synchronizing + case allowed + case notAllowed(RejectedReason) +} + +public extension NodeConnectionStatus { + enum RejectedReason: Codable, Equatable { + case outdatedApiVersion + } +} + +public extension NodeConnectionStatus.RejectedReason { + var text: String { + switch self { + case .outdatedApiVersion: + return String.localized( + "NodesList.NodeCell.Outdated", + comment: "NodesList.NodeCell: Node is outdated" + ) + } + } +} diff --git a/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift b/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift new file mode 100644 index 000000000..1174f46d3 --- /dev/null +++ b/CommonKit/Sources/CommonKit/Models/NodeOrigin.swift @@ -0,0 +1,84 @@ +// +// NodeOrigin.swift +// +// +// Created by Andrew G on 27.07.2024. +// + +import Foundation + +public struct NodeOrigin: Codable, Equatable { + public var scheme: URLScheme + public var host: String + public var port: Int? + public var wsPort: Int? + + public init( + scheme: URLScheme, + host: String, + port: Int? = nil, + wsPort: Int? = nil + ) { + self.scheme = scheme + self.host = host + self.port = port + self.wsPort = wsPort + } +} + +public extension NodeOrigin { + enum URLScheme: String, Codable { + case http, https + + public static let `default`: URLScheme = .https + + public var defaultPort: Int { + switch self { + case .http: return 36666 + case .https: return 443 + } + } + } + + init(url: URL) { + self.init( + scheme: URLScheme(rawValue: url.scheme ?? .empty) ?? .https, + host: url.host ?? .empty, + port: url.port + ) + } + + func asString() -> String { + if let url = asURL(forcePort: scheme != .https) { + return url.absoluteString + } else { + return host + } + } + + func asSocketURL() -> URL? { + asURL(forcePort: false, useWsPort: true) + } + + func asURL() -> URL? { + asURL(forcePort: true) + } +} + +private extension NodeOrigin { + func asURL(forcePort: Bool, useWsPort: Bool = false) -> URL? { + var components = URLComponents() + components.scheme = scheme.rawValue + components.host = host + + let usePort = useWsPort ? wsPort : port + + if let port = usePort, scheme == .http { + components.port = port + } else if forcePort { + components.port = usePort ?? scheme.defaultPort + } + + return components.url + } +}